├── .gitignore ├── Android ├── .gitignore ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── cn │ │ │ └── leancloud │ │ │ └── leanstoragegettingstarted │ │ │ └── ApplicationTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── cn │ │ │ │ └── leancloud │ │ │ │ └── leanstoragegettingstarted │ │ │ │ ├── DetailActivity.java │ │ │ │ ├── GettingStartedApp.java │ │ │ │ ├── LoginActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MainRecyclerAdapter.java │ │ │ │ ├── PublishActivity.java │ │ │ │ ├── RegisterActivity.java │ │ │ │ └── RoundedTransformation.java │ │ └── res │ │ │ ├── layout │ │ │ ├── activity_detail.xml │ │ │ ├── activity_login.xml │ │ │ ├── activity_main.xml │ │ │ ├── activity_publish.xml │ │ │ ├── activity_register.xml │ │ │ ├── content_main.xml │ │ │ └── item_list_main.xml │ │ │ ├── menu │ │ │ └── menu_main.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-v21 │ │ │ └── styles.xml │ │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── cn │ │ └── leancloud │ │ └── leanstoragegettingstarted │ │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew └── settings.gradle ├── README.md ├── Web ├── README.MD ├── header.css ├── initLeanCloud.js ├── login │ ├── login.css │ ├── login.html │ └── login.js ├── new-product │ ├── new-product.css │ ├── new-product.html │ └── new-product.js ├── products-list │ ├── products-list.css │ ├── products-list.html │ └── products-list.js ├── signup │ ├── signup.css │ ├── signup.html │ └── signup.js └── storage.png └── iOS ├── Podfile ├── Podfile.lock ├── StorageStarted.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── StorageStarted.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── StorageStarted ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── image │ │ ├── Contents.json │ │ ├── downloadFailed.imageset │ │ ├── Contents.json │ │ └── downloadFailed.png │ │ ├── edit.imageset │ │ ├── Contents.json │ │ └── edit.png │ │ ├── edit_cell.imageset │ │ ├── Contents.json │ │ └── edit_cell.png │ │ ├── home.imageset │ │ ├── Contents.json │ │ └── home.png │ │ ├── image_downloadFailed.imageset │ │ ├── Contents.json │ │ └── image_downloadFailed.png │ │ ├── not_logged_in.imageset │ │ ├── Contents.json │ │ ├── not_logged_in@2x.png │ │ └── not_logged_in@3x.png │ │ └── personal.imageset │ │ ├── Contents.json │ │ └── personal.png ├── Base.lproj │ └── LaunchScreen.storyboard ├── Classes │ ├── EditProduct │ │ ├── EditProductViewController.h │ │ ├── EditProductViewController.m │ │ └── EditProductViewController.xib │ ├── LoginOrSignup │ │ ├── LCLoginViewController.h │ │ ├── LCLoginViewController.m │ │ └── LCLoginViewController.xib │ ├── Other │ │ ├── CALayer+XibBorderColor.h │ │ ├── CALayer+XibBorderColor.m │ │ ├── LCNavigationController.h │ │ ├── LCNavigationController.m │ │ ├── LCTabBarController.h │ │ └── LCTabBarController.m │ ├── PersonalCenter │ │ ├── MyProductCell.h │ │ ├── MyProductCell.m │ │ ├── MyProductCell.xib │ │ ├── PersonalCenterViewController.h │ │ ├── PersonalCenterViewController.m │ │ ├── PersonalCenterViewController.xib │ │ ├── UpdateMyProductController.h │ │ ├── UpdateMyProductController.m │ │ └── UpdateMyProductController.xib │ └── ProductList │ │ ├── Product.h │ │ ├── Product.m │ │ ├── ProductListCell.h │ │ ├── ProductListCell.m │ │ ├── ProductListCell.xib │ │ ├── ProductListViewController.h │ │ └── ProductListViewController.m ├── Info.plist └── main.m ├── StorageStartedTests ├── Info.plist └── StorageStartedTests.m └── StorageStartedUITests ├── Info.plist └── StorageStartedUITests.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | .DS_Store 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 52 | 53 | fastlane/report.xml 54 | fastlane/screenshots 55 | 56 | #Code Injection 57 | # 58 | # After new code Injection tools there's a generated folder /iOSInjectionProject 59 | # https://github.com/johnno1962/injectionforxcode 60 | 61 | iOSInjectionProject/ 62 | 63 | # Web 64 | # 65 | .idea/ 66 | 67 | # Java specific 68 | *.iml 69 | .gradle 70 | /local.properties 71 | /.idea/workspace.xml 72 | /.idea/libraries 73 | .DS_Store 74 | /build 75 | /captures 76 | .externalNativeBuild 77 | -------------------------------------------------------------------------------- /Android/.gitignore: -------------------------------------------------------------------------------- 1 | *.apk 2 | *.ap_ 3 | 4 | # Files for the Dalvik VM 5 | *.dex 6 | 7 | # Java class files 8 | *.class 9 | 10 | # Generated files 11 | bin/ 12 | gen/ 13 | 14 | # ADT 15 | .classpath 16 | .project 17 | .settings 18 | local.properties 19 | bin 20 | gen 21 | _layouts 22 | proguard.cfg 23 | 24 | # OSX 25 | .DS_Store 26 | 27 | # Gradle files 28 | .gradle/ 29 | build/ 30 | 31 | # Local configuration file (sdk path, etc) 32 | local.properties 33 | 34 | # Proguard folder generated by Eclipse 35 | proguard/ 36 | 37 | # IDEA 38 | *.iml 39 | *.ipr 40 | *.iws 41 | out 42 | .idea 43 | 44 | # Maven 45 | target 46 | release.properties 47 | pom.xml.* 48 | 49 | # VIM 50 | *~ 51 | *.swp 52 | .gitignore 53 | app/libs/ 54 | app/src/main/res/drawable/ 55 | -------------------------------------------------------------------------------- /Android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "cn.leancloud.leanstoragegettingstarted" 7 | minSdkVersion 14 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 16 | } 17 | } 18 | compileOptions { 19 | sourceCompatibility 1.8 20 | targetCompatibility 1.8 21 | } 22 | 23 | } 24 | dependencies { 25 | implementation fileTree(dir: 'libs', include: ['*.jar']) 26 | implementation 'androidx.appcompat:appcompat:1.3.0' 27 | implementation 'com.google.android.material:material:1.3.0' 28 | 29 | implementation 'cn.leancloud:storage-android:8.0.1' 30 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 31 | 32 | implementation 'androidx.recyclerview:recyclerview:1.2.1' 33 | implementation 'androidx.cardview:cardview:1.0.0' 34 | implementation 'com.squareup.picasso:picasso:2.5.2' 35 | } 36 | -------------------------------------------------------------------------------- /Android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /SDKs/sdk/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /Android/app/src/androidTest/java/cn/leancloud/leanstoragegettingstarted/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.leanstoragegettingstarted; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /Android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Android/app/src/main/java/cn/leancloud/leanstoragegettingstarted/DetailActivity.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.leanstoragegettingstarted; 2 | 3 | import android.os.Bundle; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | import android.view.MenuItem; 6 | import android.widget.ImageView; 7 | import android.widget.TextView; 8 | 9 | import com.squareup.picasso.Picasso; 10 | 11 | import cn.leancloud.LCObject; 12 | import io.reactivex.Observer; 13 | import io.reactivex.disposables.Disposable; 14 | 15 | public class DetailActivity extends AppCompatActivity { 16 | private TextView mName; 17 | private TextView mDescription; 18 | private ImageView mImage; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_detail); 24 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 25 | getSupportActionBar().setTitle(getString(R.string.detail)); 26 | 27 | mName = (TextView) findViewById(R.id.name_detail); 28 | mDescription = (TextView) findViewById(R.id.description_detail); 29 | mImage = (ImageView) findViewById(R.id.image_detail); 30 | 31 | String goodsObjectId = getIntent().getStringExtra("goodsObjectId"); 32 | LCObject avObject = LCObject.createWithoutData("Product", goodsObjectId); 33 | avObject.fetchInBackground("owner").subscribe(new Observer() { 34 | @Override 35 | public void onSubscribe(Disposable d) { 36 | } 37 | @Override 38 | public void onNext(LCObject avObject) { 39 | mName.setText(avObject.getLCObject("owner") == null ? "" : avObject.getLCObject("owner").getString("username")); 40 | mDescription.setText(avObject.getString("description")); 41 | Picasso.with(DetailActivity.this).load(avObject.getLCFile("image") == null ? "www" : avObject.getLCFile("image").getUrl()).into(mImage); 42 | } 43 | @Override 44 | public void onError(Throwable e) { 45 | } 46 | @Override 47 | public void onComplete() { 48 | 49 | } 50 | }); 51 | } 52 | 53 | @Override 54 | public boolean onOptionsItemSelected(MenuItem item) { 55 | if (item.getItemId() == android.R.id.home) { 56 | onBackPressed(); 57 | } 58 | return super.onOptionsItemSelected(item); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Android/app/src/main/java/cn/leancloud/leanstoragegettingstarted/GettingStartedApp.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.leanstoragegettingstarted; 2 | 3 | import android.app.Application; 4 | 5 | import cn.leancloud.LCLogger; 6 | import cn.leancloud.LeanCloud; 7 | 8 | 9 | /** 10 | * Created by BinaryHB on 16/9/13. 11 | */ 12 | public class GettingStartedApp extends Application { 13 | 14 | @Override 15 | public void onCreate() { 16 | super.onCreate(); 17 | //开启调试日志 18 | LeanCloud.setLogLevel(LCLogger.Level.DEBUG); 19 | LeanCloud.initialize(this,"OLoj899IwHYi787ClrImlr3k-gzGzoHsz", "gkz35mRTqTE2aqwp7dEr5uEE","https://oloj899i.lc-cn-n1-shared.com"); 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Android/app/src/main/java/cn/leancloud/leanstoragegettingstarted/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.leanstoragegettingstarted; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.annotation.TargetApi; 6 | import android.content.Intent; 7 | import android.os.Build; 8 | import android.os.Bundle; 9 | import androidx.appcompat.app.AppCompatActivity; 10 | import android.text.TextUtils; 11 | import android.view.KeyEvent; 12 | import android.view.MenuItem; 13 | import android.view.View; 14 | import android.view.View.OnClickListener; 15 | import android.view.inputmethod.EditorInfo; 16 | import android.widget.AutoCompleteTextView; 17 | import android.widget.Button; 18 | import android.widget.EditText; 19 | import android.widget.TextView; 20 | import android.widget.Toast; 21 | 22 | 23 | import cn.leancloud.LCUser; 24 | import io.reactivex.Observer; 25 | import io.reactivex.disposables.Disposable; 26 | 27 | public class LoginActivity extends AppCompatActivity { 28 | private AutoCompleteTextView mUsernameView; 29 | private EditText mPasswordView; 30 | private View mProgressView; 31 | private View mLoginFormView; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_login); 37 | 38 | if (LCUser.getCurrentUser() != null) { 39 | startActivity(new Intent(LoginActivity.this, MainActivity.class)); 40 | LoginActivity.this.finish(); 41 | } 42 | 43 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 44 | getSupportActionBar().setTitle(getString(R.string.login)); 45 | mUsernameView = (AutoCompleteTextView) findViewById(R.id.username); 46 | 47 | mPasswordView = (EditText) findViewById(R.id.password); 48 | mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() { 49 | @Override 50 | public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { 51 | if (id == R.id.login || id == EditorInfo.IME_NULL) { 52 | attemptLogin(); 53 | return true; 54 | } 55 | return false; 56 | } 57 | }); 58 | 59 | Button mUsernameLoginButton = (Button) findViewById(R.id.username_login_button); 60 | mUsernameLoginButton.setOnClickListener(new OnClickListener() { 61 | @Override 62 | public void onClick(View view) { 63 | attemptLogin(); 64 | } 65 | }); 66 | 67 | Button mUsernameRegisterButton = (Button) findViewById(R.id.username_register_button); 68 | mUsernameRegisterButton.setOnClickListener(new OnClickListener() { 69 | @Override 70 | public void onClick(View view) { 71 | startActivity(new Intent(LoginActivity.this, RegisterActivity.class)); 72 | LoginActivity.this.finish(); 73 | } 74 | }); 75 | 76 | mLoginFormView = findViewById(R.id.login_form); 77 | mProgressView = findViewById(R.id.login_progress); 78 | } 79 | 80 | private void attemptLogin() { 81 | mUsernameView.setError(null); 82 | mPasswordView.setError(null); 83 | 84 | final String username = mUsernameView.getText().toString(); 85 | final String password = mPasswordView.getText().toString(); 86 | 87 | boolean cancel = false; 88 | View focusView = null; 89 | 90 | if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) { 91 | mPasswordView.setError(getString(R.string.error_invalid_password)); 92 | focusView = mPasswordView; 93 | cancel = true; 94 | } 95 | 96 | if (TextUtils.isEmpty(username)) { 97 | mUsernameView.setError(getString(R.string.error_field_required)); 98 | focusView = mUsernameView; 99 | cancel = true; 100 | } 101 | 102 | if (cancel) { 103 | focusView.requestFocus(); 104 | } else { 105 | showProgress(true); 106 | 107 | LCUser.logIn(username,password).subscribe(new Observer() { 108 | @Override 109 | public void onSubscribe(Disposable d) { 110 | 111 | } 112 | @Override 113 | public void onNext(LCUser avUser) { 114 | LoginActivity.this.finish(); 115 | startActivity(new Intent(LoginActivity.this, MainActivity.class)); 116 | } 117 | @Override 118 | public void onError(Throwable e) { 119 | showProgress(false); 120 | Toast.makeText(LoginActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); 121 | } 122 | @Override 123 | public void onComplete() { 124 | 125 | } 126 | }); 127 | } 128 | } 129 | 130 | private boolean isPasswordValid(String password) { 131 | //TODO: Replace this with your own logic 132 | return password.length() > 4; 133 | } 134 | 135 | /** 136 | * Shows the progress UI and hides the login form. 137 | */ 138 | @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) 139 | private void showProgress(final boolean show) { 140 | // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow 141 | // for very easy animations. If available, use these APIs to fade-in 142 | // the progress spinner. 143 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { 144 | int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime); 145 | 146 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); 147 | mLoginFormView.animate().setDuration(shortAnimTime).alpha( 148 | show ? 0 : 1).setListener(new AnimatorListenerAdapter() { 149 | @Override 150 | public void onAnimationEnd(Animator animation) { 151 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); 152 | } 153 | }); 154 | 155 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); 156 | mProgressView.animate().setDuration(shortAnimTime).alpha( 157 | show ? 1 : 0).setListener(new AnimatorListenerAdapter() { 158 | @Override 159 | public void onAnimationEnd(Animator animation) { 160 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); 161 | } 162 | }); 163 | } else { 164 | // The ViewPropertyAnimator APIs are not available, so simply show 165 | // and hide the relevant UI components. 166 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); 167 | mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); 168 | } 169 | } 170 | 171 | @Override 172 | public boolean onOptionsItemSelected(MenuItem item) { 173 | if (item.getItemId() == android.R.id.home) { 174 | onBackPressed(); 175 | } 176 | return super.onOptionsItemSelected(item); 177 | } 178 | 179 | } 180 | 181 | -------------------------------------------------------------------------------- /Android/app/src/main/java/cn/leancloud/leanstoragegettingstarted/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.leanstoragegettingstarted; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | import androidx.appcompat.widget.Toolbar; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import cn.leancloud.LCObject; 18 | import cn.leancloud.LCQuery; 19 | import cn.leancloud.LCUser; 20 | import io.reactivex.Observer; 21 | import io.reactivex.disposables.Disposable; 22 | 23 | public class MainActivity extends AppCompatActivity { 24 | private RecyclerView mRecyclerView; 25 | private MainRecyclerAdapter mRecyclerAdapter; 26 | private List mList = new ArrayList<>(); 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | setContentView(R.layout.activity_main); 32 | final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 33 | setSupportActionBar(toolbar); 34 | toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() { 35 | @Override 36 | public boolean onMenuItemClick(MenuItem item) { 37 | if (item.getItemId() == R.id.action_logout) { 38 | LCUser.getCurrentUser().logOut(); 39 | startActivity(new Intent(MainActivity.this, LoginActivity.class)); 40 | MainActivity.this.finish(); 41 | } 42 | return false; 43 | } 44 | }); 45 | 46 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 47 | fab.setOnClickListener(new View.OnClickListener() { 48 | @Override 49 | public void onClick(View view) { 50 | startActivity(new Intent(MainActivity.this, PublishActivity.class)); 51 | } 52 | }); 53 | 54 | mRecyclerView = (RecyclerView) findViewById(R.id.list_main); 55 | mRecyclerView.setHasFixedSize(true); 56 | mRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this)); 57 | mRecyclerAdapter = new MainRecyclerAdapter(mList, MainActivity.this); 58 | mRecyclerView.setAdapter(mRecyclerAdapter); 59 | } 60 | 61 | @Override 62 | protected void onResume() { 63 | super.onResume(); 64 | initData(); 65 | } 66 | 67 | @Override 68 | protected void onPause() { 69 | super.onPause(); 70 | } 71 | 72 | private void initData() { 73 | mList.clear(); 74 | LCQuery avQuery = new LCQuery<>("Product"); 75 | avQuery.orderByDescending("createdAt"); 76 | avQuery.include("owner"); 77 | avQuery.findInBackground().subscribe(new Observer>() { 78 | @Override 79 | public void onSubscribe(Disposable d) { 80 | 81 | } 82 | 83 | @Override 84 | public void onNext(List list) { 85 | mList.addAll(list); 86 | mRecyclerAdapter.notifyDataSetChanged(); 87 | } 88 | 89 | @Override 90 | public void onError(Throwable e) { 91 | 92 | } 93 | 94 | @Override 95 | public void onComplete() { 96 | 97 | } 98 | }); 99 | } 100 | 101 | @Override 102 | public boolean onCreateOptionsMenu(Menu menu) { 103 | // Inflate the menu; this adds items to the action bar if it is present. 104 | getMenuInflater().inflate(R.menu.menu_main, menu); 105 | return true; 106 | } 107 | 108 | @Override 109 | public boolean onOptionsItemSelected(MenuItem item) { 110 | // Handle action bar item clicks here. The action bar will 111 | // automatically handle clicks on the Home/Up button, so long 112 | // as you specify a parent activity in AndroidManifest.xml. 113 | int id = item.getItemId(); 114 | 115 | //noinspection SimplifiableIfStatement 116 | if (id == R.id.action_logout) { 117 | return true; 118 | } 119 | 120 | return super.onOptionsItemSelected(item); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Android/app/src/main/java/cn/leancloud/leanstoragegettingstarted/MainRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.leanstoragegettingstarted; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import androidx.cardview.widget.CardView; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | import com.squareup.picasso.Picasso; 14 | 15 | import java.util.List; 16 | 17 | import cn.leancloud.LCObject; 18 | 19 | /** 20 | * Created by BinaryHB on 11/24/15. 21 | */ 22 | public class MainRecyclerAdapter extends RecyclerView.Adapter { 23 | private Context mContext; 24 | private List mList; 25 | 26 | public MainRecyclerAdapter(List list, Context context) { 27 | this.mContext = context; 28 | this.mList = list; 29 | } 30 | 31 | @Override 32 | public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 33 | return new MainViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_list_main, parent, false)); 34 | } 35 | 36 | @Override 37 | public void onBindViewHolder(MainViewHolder holder, final int position) { 38 | holder.mTitle.setText((CharSequence) mList.get(position).get("title")); 39 | holder.mPrice.setText(mList.get(position).get("price") == null ? "¥" : "¥ " + mList.get(position).get("price")); 40 | holder.mName.setText(mList.get(position).getLCObject("owner") == null ? "" : mList.get(position).getLCObject("owner").getString("username")); 41 | Picasso.with(mContext).load(mList.get(position).getLCFile("image") == null ? "www" : mList.get(position).getLCFile("image").getUrl()).transform(new RoundedTransformation(9, 0)).into(holder.mPicture); 42 | holder.mItem.setOnClickListener(new View.OnClickListener() { 43 | @Override 44 | public void onClick(View view) { 45 | Intent intent = new Intent(mContext, DetailActivity.class); 46 | intent.putExtra("goodsObjectId", mList.get(position).getObjectId()); 47 | mContext.startActivity(intent); 48 | } 49 | }); 50 | } 51 | 52 | @Override 53 | public int getItemCount() { 54 | return mList.size(); 55 | } 56 | 57 | class MainViewHolder extends RecyclerView.ViewHolder { 58 | private TextView mName; 59 | private TextView mPrice; 60 | private TextView mTitle; 61 | private CardView mItem; 62 | private ImageView mPicture; 63 | 64 | public MainViewHolder(View itemView) { 65 | super(itemView); 66 | mName = (TextView) itemView.findViewById(R.id.name_item_main); 67 | mTitle = (TextView) itemView.findViewById(R.id.title_item_main); 68 | mPrice = (TextView) itemView.findViewById(R.id.price_item_main); 69 | mPicture = (ImageView) itemView.findViewById(R.id.picture_item_main); 70 | mItem = (CardView) itemView.findViewById(R.id.item_main); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Android/app/src/main/java/cn/leancloud/leanstoragegettingstarted/PublishActivity.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.leanstoragegettingstarted; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.provider.MediaStore; 7 | import androidx.appcompat.app.AppCompatActivity; 8 | import android.view.MenuItem; 9 | import android.view.View; 10 | import android.widget.Button; 11 | import android.widget.EditText; 12 | import android.widget.ImageView; 13 | import android.widget.ProgressBar; 14 | import android.widget.Toast; 15 | 16 | 17 | import java.io.ByteArrayOutputStream; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | 21 | import cn.leancloud.LCFile; 22 | import cn.leancloud.LCObject; 23 | import cn.leancloud.LCUser; 24 | import io.reactivex.Observer; 25 | import io.reactivex.disposables.Disposable; 26 | 27 | public class PublishActivity extends AppCompatActivity { 28 | 29 | private ImageView mImageViewSelect; 30 | private byte[] mImageBytes = null; 31 | private Handler mHandler = new Handler(); 32 | private ProgressBar mProgerss; 33 | // private ProgressCallback mImageUploadProgressCallback = new ProgressCallback() { 34 | // @Override 35 | // public void done(Integer integer) { 36 | // final int mProgressStatus = integer; 37 | // mProgerss.setProgress(mProgressStatus); 38 | // } 39 | // }; 40 | 41 | @Override 42 | protected void onCreate(Bundle savedInstanceState) { 43 | super.onCreate(savedInstanceState); 44 | setContentView(R.layout.activity_publish); 45 | 46 | mImageViewSelect = (ImageView) findViewById(R.id.imageview_select_publish); 47 | mProgerss = (ProgressBar) findViewById(R.id.mProgess); 48 | 49 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 50 | getSupportActionBar().setTitle(getString(R.string.publish)); 51 | 52 | Button mButtonSelect = (Button) findViewById(R.id.button_select_publish); 53 | mButtonSelect.setOnClickListener(new View.OnClickListener() { 54 | @Override 55 | public void onClick(View view) { 56 | Intent intent = new Intent(Intent.ACTION_GET_CONTENT); 57 | intent.setType("image/*"); 58 | startActivityForResult(intent, 42); 59 | } 60 | }); 61 | 62 | final EditText mDiscriptionEdit = (EditText) findViewById(R.id.edittext_discription_publish); 63 | final EditText mTitleEdit = (EditText) findViewById(R.id.edittext_title_publish); 64 | final EditText mPriceEdit = (EditText) findViewById(R.id.edittext_price_publish); 65 | 66 | findViewById(R.id.button_submit_publish).setOnClickListener(new View.OnClickListener() { 67 | @Override 68 | public void onClick(View view) { 69 | if ("".equals(mTitleEdit.getText().toString())) { 70 | Toast.makeText(PublishActivity.this, "请输入标题", Toast.LENGTH_SHORT).show(); 71 | return; 72 | } 73 | if ("".equals(mDiscriptionEdit.getText().toString())) { 74 | Toast.makeText(PublishActivity.this, "请输入商品描述", Toast.LENGTH_SHORT).show(); 75 | return; 76 | } 77 | if ("".equals(mPriceEdit.getText().toString())) { 78 | Toast.makeText(PublishActivity.this, "请输入金额", Toast.LENGTH_SHORT).show(); 79 | return; 80 | } 81 | if (mImageBytes == null) { 82 | Toast.makeText(PublishActivity.this, "请选择一张照片", Toast.LENGTH_SHORT).show(); 83 | return; 84 | } 85 | mProgerss.setVisibility(View.VISIBLE); 86 | LCObject product = new LCObject("Product"); 87 | product.put("title", mTitleEdit.getText().toString()); 88 | product.put("description", mDiscriptionEdit.getText().toString()); 89 | product.put("price", Integer.parseInt(mPriceEdit.getText().toString())); 90 | product.put("owner", LCUser.getCurrentUser()); 91 | product.put("image", new LCFile("productPic", mImageBytes)); 92 | product.saveInBackground().subscribe(new Observer() { 93 | @Override 94 | public void onSubscribe(Disposable d) { 95 | 96 | } 97 | 98 | @Override 99 | public void onNext(LCObject avObject) { 100 | mProgerss.setVisibility(View.GONE); 101 | PublishActivity.this.finish(); 102 | } 103 | 104 | @Override 105 | public void onError(Throwable e) { 106 | mProgerss.setVisibility(View.GONE); 107 | Toast.makeText(PublishActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); 108 | } 109 | 110 | @Override 111 | public void onComplete() { 112 | 113 | } 114 | }); 115 | } 116 | }); 117 | } 118 | 119 | @Override 120 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 121 | super.onActivityResult(requestCode, resultCode, data); 122 | if (requestCode == 42 && resultCode == RESULT_OK) { 123 | try { 124 | mImageViewSelect.setImageBitmap(MediaStore.Images.Media.getBitmap(this.getContentResolver(), data.getData())); 125 | mImageBytes = getBytes(getContentResolver().openInputStream(data.getData())); 126 | } catch (IOException e) { 127 | e.printStackTrace(); 128 | } 129 | } 130 | } 131 | 132 | public byte[] getBytes(InputStream inputStream) throws IOException { 133 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 134 | int bufferSize = 1024; 135 | byte[] buffer = new byte[bufferSize]; 136 | int len; 137 | while ((len = inputStream.read(buffer)) != -1) { 138 | byteArrayOutputStream.write(buffer, 0, len); 139 | } 140 | return byteArrayOutputStream.toByteArray(); 141 | } 142 | 143 | @Override 144 | public boolean onOptionsItemSelected(MenuItem item) { 145 | if (item.getItemId() == android.R.id.home) { 146 | onBackPressed(); 147 | } 148 | return super.onOptionsItemSelected(item); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Android/app/src/main/java/cn/leancloud/leanstoragegettingstarted/RegisterActivity.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.leanstoragegettingstarted; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.annotation.TargetApi; 6 | import android.content.Intent; 7 | import android.os.Build; 8 | import android.os.Bundle; 9 | import androidx.appcompat.app.AppCompatActivity; 10 | import android.text.TextUtils; 11 | import android.view.KeyEvent; 12 | import android.view.MenuItem; 13 | import android.view.View; 14 | import android.view.View.OnClickListener; 15 | import android.view.inputmethod.EditorInfo; 16 | import android.widget.AutoCompleteTextView; 17 | import android.widget.Button; 18 | import android.widget.EditText; 19 | import android.widget.TextView; 20 | import android.widget.Toast; 21 | 22 | import cn.leancloud.LCUser; 23 | import io.reactivex.Observer; 24 | import io.reactivex.disposables.Disposable; 25 | 26 | public class RegisterActivity extends AppCompatActivity { 27 | private AutoCompleteTextView mUsernameView; 28 | private EditText mPasswordView; 29 | private View mProgressView; 30 | private View mRegisterFormView; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_register); 36 | 37 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 38 | // Set up the register form. 39 | mUsernameView = (AutoCompleteTextView) findViewById(R.id.username); 40 | 41 | mPasswordView = (EditText) findViewById(R.id.password); 42 | mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() { 43 | @Override 44 | public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { 45 | if (id == R.id.register || id == EditorInfo.IME_NULL) { 46 | attemptRegister(); 47 | return true; 48 | } 49 | return false; 50 | } 51 | }); 52 | 53 | Button musernameSignInButton = (Button) findViewById(R.id.username_register_button); 54 | musernameSignInButton.setOnClickListener(new OnClickListener() { 55 | @Override 56 | public void onClick(View view) { 57 | attemptRegister(); 58 | } 59 | }); 60 | 61 | mRegisterFormView = findViewById(R.id.register_form); 62 | mProgressView = findViewById(R.id.register_progress); 63 | } 64 | 65 | private void attemptRegister() { 66 | mUsernameView.setError(null); 67 | mPasswordView.setError(null); 68 | 69 | String username = mUsernameView.getText().toString(); 70 | String password = mPasswordView.getText().toString(); 71 | 72 | boolean cancel = false; 73 | View focusView = null; 74 | 75 | if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) { 76 | mPasswordView.setError(getString(R.string.error_invalid_password)); 77 | focusView = mPasswordView; 78 | cancel = true; 79 | } 80 | 81 | if (TextUtils.isEmpty(username)) { 82 | mUsernameView.setError(getString(R.string.error_field_required)); 83 | focusView = mUsernameView; 84 | cancel = true; 85 | } 86 | 87 | if (cancel) { 88 | focusView.requestFocus(); 89 | } else { 90 | showProgress(true); 91 | 92 | LCUser user = new LCUser();// 新建 LCUser 对象实例 93 | user.setUsername(username);// 设置用户名 94 | user.setPassword(password);// 设置密码 95 | user.signUpInBackground().subscribe(new Observer() { 96 | @Override 97 | public void onSubscribe(Disposable d) { 98 | 99 | } 100 | @Override 101 | public void onNext(LCUser avUser) { 102 | // 注册成功,把用户对象赋值给当前用户 LCUser.getCurrentUser() 103 | startActivity(new Intent(RegisterActivity.this, MainActivity.class)); 104 | RegisterActivity.this.finish(); 105 | } 106 | @Override 107 | public void onError(Throwable e) { 108 | // 失败的原因可能有多种,常见的是用户名已经存在。 109 | showProgress(false); 110 | Toast.makeText(RegisterActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); 111 | } 112 | @Override 113 | public void onComplete() { 114 | 115 | } 116 | }); 117 | } 118 | } 119 | 120 | private boolean isusernameValid(String username) { 121 | //TODO: Replace this with your own logic 122 | return username.contains("@"); 123 | } 124 | 125 | private boolean isPasswordValid(String password) { 126 | //TODO: Replace this with your own logic 127 | return password.length() > 4; 128 | } 129 | 130 | @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2) 131 | private void showProgress(final boolean show) { 132 | // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow 133 | // for very easy animations. If available, use these APIs to fade-in 134 | // the progress spinner. 135 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { 136 | int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime); 137 | 138 | mRegisterFormView.setVisibility(show ? View.GONE : View.VISIBLE); 139 | mRegisterFormView.animate().setDuration(shortAnimTime).alpha( 140 | show ? 0 : 1).setListener(new AnimatorListenerAdapter() { 141 | @Override 142 | public void onAnimationEnd(Animator animation) { 143 | mRegisterFormView.setVisibility(show ? View.GONE : View.VISIBLE); 144 | } 145 | }); 146 | 147 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); 148 | mProgressView.animate().setDuration(shortAnimTime).alpha( 149 | show ? 1 : 0).setListener(new AnimatorListenerAdapter() { 150 | @Override 151 | public void onAnimationEnd(Animator animation) { 152 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); 153 | } 154 | }); 155 | } else { 156 | // The ViewPropertyAnimator APIs are not available, so simply show 157 | // and hide the relevant UI components. 158 | mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); 159 | mRegisterFormView.setVisibility(show ? View.GONE : View.VISIBLE); 160 | } 161 | } 162 | 163 | @Override 164 | public boolean onOptionsItemSelected(MenuItem item) { 165 | if (item.getItemId() == android.R.id.home) { 166 | onBackPressed(); 167 | } 168 | return super.onOptionsItemSelected(item); 169 | } 170 | } 171 | 172 | -------------------------------------------------------------------------------- /Android/app/src/main/java/cn/leancloud/leanstoragegettingstarted/RoundedTransformation.java: -------------------------------------------------------------------------------- 1 | package cn.leancloud.leanstoragegettingstarted; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapShader; 5 | import android.graphics.Canvas; 6 | import android.graphics.Paint; 7 | import android.graphics.RectF; 8 | import android.graphics.Shader; 9 | 10 | import com.squareup.picasso.Transformation; 11 | 12 | /** 13 | * Created by BinaryHB on 16/9/18. 14 | */ 15 | public class RoundedTransformation implements Transformation{ 16 | 17 | private final int radius; 18 | private final int margin; // dp 19 | 20 | // radius is corner radii in dp 21 | // margin is the board in dp 22 | public RoundedTransformation(final int radius, final int margin) { 23 | this.radius = radius; 24 | this.margin = margin; 25 | } 26 | 27 | @Override 28 | public Bitmap transform(final Bitmap source) { 29 | final Paint paint = new Paint(); 30 | paint.setAntiAlias(true); 31 | paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); 32 | 33 | Bitmap output = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888); 34 | Canvas canvas = new Canvas(output); 35 | canvas.drawRoundRect(new RectF(margin, margin, source.getWidth() - margin, source.getHeight() - margin), radius, radius, paint); 36 | 37 | if (source != output) { 38 | source.recycle(); 39 | } 40 | 41 | return output; 42 | } 43 | 44 | @Override 45 | public String key() { 46 | return "rounded(radius=" + radius + ", margin=" + margin + ")"; 47 | } 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /Android/app/src/main/res/layout/activity_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 22 | 23 | 32 | 33 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Android/app/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 35 | 36 | 44 | 45 | 46 | 47 | 50 | 51 | 62 | 63 | 64 | 65 |