├── .gitignore ├── .idea ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── bmob │ │ └── yellowdoing │ │ └── com │ │ └── bmobcommunity │ │ ├── LoginActivity.java │ │ ├── MainActivity.java │ │ ├── MyComment.java │ │ ├── MyCommunity.java │ │ ├── User.java │ │ └── UserInfoActivity.java │ └── res │ ├── drawable-hdpi │ └── ic_post.png │ ├── drawable-mdpi │ └── ic_post.png │ ├── drawable-xhdpi │ └── ic_post.png │ ├── drawable-xxhdpi │ └── ic_post.png │ ├── layout │ ├── activity_login.xml │ ├── activity_main.xml │ ├── activity_user_info.xml │ ├── content_main.xml │ └── dialog_register.xml │ ├── menu │ └── menu_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── communityUI ├── build.gradle ├── gradlew ├── gradlew.bat ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── hg │ │ └── yellowdoing │ │ └── communityui │ │ ├── ChildReplyAdapter.java │ │ ├── ChildReplyAdapter2.java │ │ ├── CircleImageView.java │ │ ├── Comment.java │ │ ├── CommentAdapter.java │ │ ├── CommentDetailActivity.java │ │ ├── Community.java │ │ ├── CommunityAdapter.java │ │ ├── CommunityDetialActivity.java │ │ ├── CommunityFragment.java │ │ ├── CommunityInterface.java │ │ ├── ImageAdapter.java │ │ ├── ImageSelectAdapter.java │ │ ├── MyListView.java │ │ └── PostActivity.java │ └── res │ ├── drawable-hdpi │ ├── ic_add_picture.png │ ├── ic_back.png │ ├── ic_reply.png │ ├── ic_zan.png │ └── ic_zan_hover.png │ ├── drawable-mdpi │ ├── edit_style.xml │ ├── edit_text_cursor.xml │ ├── ic_add_picture.png │ ├── ic_back.png │ ├── ic_reply.png │ ├── ic_zan.png │ ├── ic_zan_hover.png │ └── text_item_reply.xml │ ├── drawable-xhdpi │ ├── ic_add_picture.png │ ├── ic_back.png │ ├── ic_reply.png │ ├── ic_zan.png │ └── ic_zan_hover.png │ ├── drawable-xxhdpi │ ├── hospital_bitmap.png │ ├── ic_add_picture.png │ ├── ic_back.png │ ├── ic_reply.png │ ├── ic_zan.png │ └── ic_zan_hover.png │ ├── drawable │ ├── zan_style.xml │ └── zan_style_2.xml │ ├── layout │ ├── activity_comment_detail.xml │ ├── activity_community_detail.xml │ ├── activity_post.xml │ ├── fragment_communicty.xml │ ├── list_item_child_reply.xml │ ├── list_item_child_reply_2.xml │ ├── list_item_community.xml │ ├── list_item_image.xml │ ├── list_item_more.xml │ ├── list_item_reply.xml │ └── reply_layout.xml │ └── values │ └── values.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .idea/vcs.xml 11 | app/libs/ 12 | app/src/main/res/drawable/ 13 | communityUI/libs/ 14 | communityUI/src/androidTest/ 15 | 16 | communityUI/src/test/ 17 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## communityUI 2 | 一个可快速集成的朋友圈界面,支持评论、子评论、点赞、回复人数。 3 | 4 | ![](https://note.youdao.com/yws/api/personal/file/WEB065b58ab97645293aaa2f2287d10bea1?method=download&shareKey=eb82539165ecee4e1694a44d889405bf) ![](https://note.youdao.com/yws/api/personal/file/WEB2caef7ed89cd62aa5e048c5e22892262?method=download&shareKey=163142cec3144bf4fb135fac6c0abf87) 5 | 6 | > 建议有一个自己的后台数据库,可直接集成,示例代码用的是作者自己的简单后台 7 | 8 | #### 集成步骤 9 | 1 . 继承CommunityInterface接口,添加fragment 10 | 11 | ``` 12 | //朋友圈列表是一个fragment,需要事务添加 13 | public class MainActivity extends AppCompatActivity implements CommunityInterface { 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_main); 19 | 20 | getSupportFragmentManager().beginTransaction() 21 | .add(R.id.container, new CommunityFragment().setCommunityInterface(this)) 22 | .commit(); 23 | } 24 | ``` 25 | 2 . 实现接口的六个方法,进行数据转换 26 | 27 | void loadCommunityList(CommunitySubsriber subsriber,int page); 28 | 29 | void comment(CommentSubsriber2 subsriber,String communityId,String parentId,String commentId,String content); 30 | 31 | void loadComments(CommentSubsriber subsriber,String communityId,int page); 32 | 33 | void like(Subsriber subsriber,String communityId); 34 | 35 | void unLike(Subsriber subsriber,String communityId); 36 | 37 | void post(Subsriber subsriber,ArrayList imagePaths,String content); 38 | 39 | 3 . 在Mainifest.xml文件添加activity声明 40 | 41 | 42 | ``` 43 | 44 | 46 | 48 | 49 | 50 | 51 | ``` 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion "26.0.2" 6 | defaultConfig { 7 | applicationId "bmob.yellowdoing.com.bmobcommunity" 8 | minSdkVersion 15 9 | targetSdkVersion 26 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile project(':communityUI') 25 | compile 'com.android.support:design:26.+' 26 | compile 'com.droi.sdk:Core:+' 27 | compile 'com.squareup.okhttp3:okhttp:3.8.1' 28 | 29 | } 30 | -------------------------------------------------------------------------------- /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 /Users/ganhuang/Library/Android/sdk/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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/java/bmob/yellowdoing/com/bmobcommunity/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package bmob.yellowdoing.com.bmobcommunity; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.DialogInterface; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.util.Log; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.widget.Button; 12 | import android.widget.EditText; 13 | import android.widget.TextView; 14 | import android.widget.Toast; 15 | import com.droi.sdk.DroiError; 16 | import com.droi.sdk.core.DroiPermission; 17 | import com.droi.sdk.core.DroiUser; 18 | 19 | public class LoginActivity extends AppCompatActivity implements View.OnClickListener { 20 | 21 | private EditText mEtUsername, mEtPassword; 22 | private TextView mTvRegister; 23 | private Button mBtLogin; 24 | 25 | @Override 26 | protected void onCreate(@Nullable Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_login); 29 | 30 | mEtUsername = (EditText) findViewById(R.id.et_username); 31 | mEtPassword = (EditText) findViewById(R.id.et_password); 32 | mTvRegister = (TextView) findViewById(R.id.tv_regitser); 33 | mBtLogin = (Button) findViewById(R.id.bt_login); 34 | 35 | mBtLogin.setOnClickListener(this); 36 | mTvRegister.setOnClickListener(this); 37 | } 38 | 39 | @Override 40 | public void onClick(View v) { 41 | switch (v.getId()) { 42 | case R.id.bt_login: //登陆 43 | login(); 44 | break; 45 | case R.id.tv_regitser: //注册 46 | register(); 47 | break; 48 | } 49 | } 50 | 51 | private void login() { 52 | DroiError error = new DroiError(); 53 | User user = DroiUser.login(mEtUsername.getText().toString(), mEtPassword.getText().toString(), User.class, error); 54 | if (error.isOk() && user != null && user.isAuthorized()) { 55 | Toast.makeText(LoginActivity.this, "登陆成功", Toast.LENGTH_SHORT).show(); 56 | getSharedPreferences("user",MODE_PRIVATE).edit().putBoolean("isLogin",true).apply(); 57 | finish(); 58 | }else 59 | Toast.makeText(LoginActivity.this, error.getAppendedMessage(), Toast.LENGTH_SHORT).show(); 60 | 61 | } 62 | 63 | private void register() { 64 | View view = LayoutInflater.from(this).inflate(R.layout.dialog_register, null); 65 | final EditText username = (EditText) view.findViewById(R.id.et_username); 66 | final EditText password = (EditText) view.findViewById(R.id.et_password); 67 | 68 | new AlertDialog.Builder(this) 69 | .setView(view) 70 | .setNegativeButton("注册", new DialogInterface.OnClickListener() { 71 | @Override 72 | public void onClick(DialogInterface dialog, int which) { 73 | User user = new User(); 74 | user.setUserId(username.getText().toString()); 75 | user.setPassword(password.getText().toString()); 76 | DroiPermission permission = new DroiPermission(); 77 | permission.setPublicReadPermission(true); 78 | user.setPermission(permission); 79 | DroiError result = user.signUp(); 80 | if (result.isOk()) { 81 | Toast.makeText(LoginActivity.this, "注册成功", Toast.LENGTH_SHORT).show(); 82 | getSharedPreferences("user",MODE_PRIVATE).edit().putBoolean("isLogin",true).apply(); 83 | finish(); 84 | }else Log.d("aaaa", "login: " + result.toString()); 85 | } 86 | }) 87 | .setPositiveButton("取消", new DialogInterface.OnClickListener() { 88 | @Override 89 | public void onClick(DialogInterface dialog, int which) { 90 | 91 | } 92 | }).show(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/bmob/yellowdoing/com/bmobcommunity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package bmob.yellowdoing.com.bmobcommunity; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.os.Bundle; 7 | import android.support.design.widget.FloatingActionButton; 8 | import android.support.v4.app.ActivityCompat; 9 | import android.support.v4.content.ContextCompat; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.support.v7.widget.Toolbar; 12 | import android.util.Log; 13 | import android.view.View; 14 | import android.view.Menu; 15 | import android.view.MenuItem; 16 | import android.widget.Toast; 17 | 18 | import com.droi.sdk.DroiCallback; 19 | import com.droi.sdk.DroiError; 20 | import com.droi.sdk.core.Core; 21 | import com.droi.sdk.core.DroiCondition; 22 | import com.droi.sdk.core.DroiFile; 23 | import com.droi.sdk.core.DroiObject; 24 | import com.droi.sdk.core.DroiPermission; 25 | import com.droi.sdk.core.DroiQuery; 26 | import com.droi.sdk.core.DroiUser; 27 | 28 | import org.json.JSONException; 29 | import org.json.JSONObject; 30 | 31 | import java.io.IOException; 32 | import java.text.SimpleDateFormat; 33 | import java.util.ArrayList; 34 | import java.util.Date; 35 | import java.util.List; 36 | 37 | import hg.yellowdoing.communityui.ChildReplyAdapter2; 38 | import hg.yellowdoing.communityui.Comment; 39 | import hg.yellowdoing.communityui.Community; 40 | import hg.yellowdoing.communityui.CommunityFragment; 41 | import hg.yellowdoing.communityui.CommunityInterface; 42 | import hg.yellowdoing.communityui.PostActivity; 43 | import okhttp3.Call; 44 | import okhttp3.Callback; 45 | import okhttp3.MediaType; 46 | import okhttp3.OkHttpClient; 47 | import okhttp3.Request; 48 | import okhttp3.RequestBody; 49 | import okhttp3.Response; 50 | import okio.BufferedSink; 51 | 52 | 53 | public class MainActivity extends AppCompatActivity implements CommunityInterface { 54 | 55 | @Override 56 | protected void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | setContentView(R.layout.activity_main); 59 | 60 | //初始化Droibaas SDK 61 | DroiObject.registerCustomClass(User.class); 62 | DroiObject.registerCustomClass(MyCommunity.class); 63 | DroiObject.registerCustomClass(MyComment.class); 64 | Core.initialize(this); 65 | 66 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 67 | setSupportActionBar(toolbar); 68 | 69 | getSupportFragmentManager().beginTransaction() 70 | .add(R.id.container, new CommunityFragment().setCommunityInterface(this)) 71 | .commit(); 72 | 73 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 74 | fab.setOnClickListener(new View.OnClickListener() { 75 | @Override 76 | public void onClick(View view) { 77 | if (getSharedPreferences("user", MODE_PRIVATE).getBoolean("isLogin", false)) { 78 | User user = DroiUser.getCurrentUser(User.class); 79 | if (user.getAvatar() == null || user.getNickName() == null) 80 | startActivity(new Intent(MainActivity.this, UserInfoActivity.class)); 81 | else 82 | startActivity(new Intent(MainActivity.this, PostActivity.class)); 83 | } else 84 | startActivity(new Intent(MainActivity.this, LoginActivity.class)); 85 | } 86 | }); 87 | } 88 | 89 | @Override 90 | public boolean onCreateOptionsMenu(Menu menu) { 91 | getMenuInflater().inflate(R.menu.menu_main, menu); 92 | return true; 93 | } 94 | 95 | @Override 96 | public boolean onOptionsItemSelected(MenuItem item) { 97 | int id = item.getItemId(); 98 | if (id == R.id.action_settings) { 99 | DroiUser.getCurrentUser().logout(); 100 | getSharedPreferences("user", MODE_PRIVATE).edit().putBoolean("isLogin", false).apply(); 101 | startActivity(new Intent(this, LoginActivity.class)); 102 | return true; 103 | } else if (id == R.id.user_info) { 104 | if (DroiUser.getCurrentUser(User.class) != null) 105 | startActivity(new Intent(this, UserInfoActivity.class)); 106 | else 107 | startActivity(new Intent(this, LoginActivity.class)); 108 | } 109 | return super.onOptionsItemSelected(item); 110 | } 111 | 112 | 113 | @Override 114 | public void loadCommunityList(CommunitySubsriber subsriber, int page) { 115 | DroiQuery query = DroiQuery.Builder.newBuilder() 116 | .query(MyCommunity.class) 117 | .limit(20) 118 | .offset((page - 1) * 20) 119 | .orderBy("_CreationTime", false) 120 | .build(); 121 | DroiError error = new DroiError(); 122 | List result = query.runQuery(error); 123 | List communities = new ArrayList<>(); 124 | if (error.isOk()) { 125 | for (int i = 0; i < result.size(); i++) { 126 | MyCommunity myCommunity = result.get(i); 127 | communities.add(new Community() 128 | .setId(myCommunity.getObjectId()) 129 | .setContent(myCommunity.getContent()) 130 | .setReplyNum(myCommunity.getReplyNum()) 131 | .setLike(DroiUser.getCurrentUser(User.class) != null && myCommunity.getLikePersons().contains(DroiUser.getCurrentUser(User.class).getObjectId())) 132 | .setImagePaths(myCommunity.getImagePaths()) 133 | .setLikeNum(myCommunity.getLikePersons().size()) 134 | .setNickName(myCommunity.getAuthor().getNickName()) 135 | .setCreateTime(dateCompare(myCommunity.getCreationTime().getTime())) 136 | .setAvatar(myCommunity.getAuthor().getAvatar().getUri().toString().replaceAll("\\\\", ""))); 137 | } 138 | subsriber.onComplete(communities); 139 | } 140 | } 141 | 142 | 143 | @Override 144 | public void comment(CommentSubsriber2 subsriber, String communityId, String parentId, String commentId, String content) { 145 | 146 | 147 | if (!getSharedPreferences("user", MODE_PRIVATE).getBoolean("isLogin", false)) { 148 | startActivity(new Intent(this, LoginActivity.class)); 149 | return; 150 | }else { 151 | if (DroiUser.getCurrentUser(User.class).getAvatar() == null || DroiUser.getCurrentUser(User.class).getNickName() == null){ 152 | startActivity(new Intent(this, UserInfoActivity.class)); 153 | return; 154 | } 155 | } 156 | MyComment comment = new MyComment(); 157 | DroiPermission droiPermission = new DroiPermission(); 158 | droiPermission.setPublicReadPermission(true); 159 | comment.setPermission(droiPermission); 160 | comment.setContent(content); 161 | comment.setCommunityId(communityId); 162 | comment.setCommentId(commentId); 163 | comment.setParentId(parentId); 164 | comment.setAuthor(DroiUser.getCurrentUser(User.class)); 165 | DroiError error = comment.save(); 166 | if (error.isOk()) { 167 | Comment c = new Comment(); 168 | c.setContent(content); 169 | c.setCommunityId(communityId); 170 | c.setCommentId(commentId); 171 | c.setParentId(parentId); 172 | c.setId(comment.getObjectId()); 173 | c.setNickName(DroiUser.getCurrentUser(User.class).getNickName()); 174 | c.setAvatar(DroiUser.getCurrentUser(User.class).getAvatar().getUri().toString().replaceAll("\\\\","")); 175 | subsriber.onComplete(c); 176 | likeOrUnlike(new Subsriber() { 177 | @Override 178 | public void onComplete() { 179 | 180 | } 181 | }, communityId, "replyNum", "Increment"); 182 | } else Toast.makeText(this, "回复失败", Toast.LENGTH_SHORT).show(); 183 | } 184 | 185 | @Override 186 | public void loadComments(CommentSubsriber subsriber, String communityId, int page) { 187 | DroiCondition cond = DroiCondition.cond("communityId", DroiCondition.Type.EQ, communityId); 188 | 189 | DroiQuery query = DroiQuery.Builder.newBuilder() 190 | .where(cond) 191 | .query(MyComment.class) 192 | .build(); 193 | DroiError error = new DroiError(); 194 | List myComments = query.runQuery(error); 195 | if (error.isOk()) { 196 | List comments = new ArrayList<>(); 197 | for (int i = 0; i < myComments.size(); i++) { 198 | Comment comment1 = new Comment(); 199 | comment1.setAvatar(myComments.get(i).getAuthor().getAvatar().getUri().toString().replaceAll("\\\\", "")); 200 | comment1.setContent(myComments.get(i).getContent()); 201 | comment1.setId(myComments.get(i).getObjectId()); 202 | comment1.setNickName(myComments.get(i).getAuthor().getNickName()); 203 | comment1.setParentId(myComments.get(i).getParentId()); 204 | comment1.setCommentId(myComments.get(i).getCommentId()); 205 | comment1.setCreateTime(myComments.get(i).getCreationTime().getTime()); 206 | comments.add(comment1); 207 | } 208 | subsriber.onComplete(comments); 209 | } else Toast.makeText(this, "回复失败", Toast.LENGTH_SHORT).show(); 210 | } 211 | 212 | @Override 213 | public void like(Subsriber subsriber, final String communityId) { 214 | likeOrUnlike(subsriber, communityId, "likePersons", "Add"); 215 | 216 | } 217 | 218 | @Override 219 | public void unLike(Subsriber subsriber, final String communityId) { 220 | likeOrUnlike(subsriber, communityId, "likePersons", "Remove"); 221 | } 222 | 223 | private void likeOrUnlike(final Subsriber subsriber, final String communityId, final String name, final String action) { 224 | new Thread(new Runnable() { 225 | @Override 226 | public void run() { 227 | OkHttpClient okHttpClient = new OkHttpClient(); 228 | 229 | RequestBody body = new RequestBody() { 230 | @Override 231 | public MediaType contentType() { 232 | return MediaType.parse("application/json"); 233 | } 234 | 235 | @Override 236 | public void writeTo(BufferedSink sink) throws IOException { 237 | if (name.equals("likePersons")) 238 | sink.write(("{\"" + name + "\" :{\"__op\":\"" + action + "\",\"objects\":[\"" + DroiUser.getCurrentUser(User.class).getObjectId() + "\"]}}").getBytes()); 239 | else 240 | sink.write(("{\"" + name + "\" :{\"__op\":\"" + action + "\",\"amount\":1}}").getBytes()); 241 | } 242 | }; 243 | Request request = new Request.Builder() 244 | .url("https://api.droibaas.com/rest/objects/v2/Community/" + communityId) 245 | .addHeader("X-Droi-AppID", "3gltmbzhTh3BSQhS1kQMSa-QyLaEmjTPlQD0QtoM") 246 | .addHeader("X-Droi-Api-Key", "hCjT6NULJXTcJV4cbOcZkocGCoQEHpzdEHGzu4WwBrOiY3anhVm7ZKqMsjTRDzWD") 247 | .addHeader("Content-Type", "application/json").patch(body).build(); 248 | 249 | okHttpClient.newCall(request).enqueue(new Callback() { 250 | @Override 251 | public void onFailure(Call call, final IOException e) { 252 | runOnUiThread(new Runnable() { 253 | @Override 254 | public void run() { 255 | Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); 256 | } 257 | }); 258 | } 259 | 260 | @Override 261 | public void onResponse(Call call, final Response response) throws IOException { 262 | final String resp = response.body().string(); 263 | runOnUiThread(new Runnable() { 264 | @Override 265 | public void run() { 266 | try { 267 | JSONObject jsonObject = new JSONObject(resp); 268 | if (jsonObject.getInt("Code") == 0) subsriber.onComplete(); 269 | else 270 | Toast.makeText(MainActivity.this, jsonObject.getString("Message"), Toast.LENGTH_SHORT).show(); 271 | } catch (JSONException e) { 272 | e.printStackTrace(); 273 | } 274 | } 275 | }); 276 | } 277 | }); 278 | } 279 | }).start(); 280 | } 281 | 282 | @Override 283 | public void post(final Subsriber subsriber, final ArrayList imagePaths, String content) { 284 | final MyCommunity myCommunity = new MyCommunity(); 285 | DroiPermission permission = new DroiPermission(); 286 | permission.setPublicReadPermission(true); 287 | permission.setPublicWritePermission(true); 288 | myCommunity.setPermission(permission); 289 | myCommunity.setContent(content); 290 | myCommunity.setAuthor(User.getCurrentUser(User.class)); 291 | myCommunity.setReplyNum(0); 292 | myCommunity.setLikePersons(new ArrayList()); 293 | final ArrayList list = new ArrayList<>(); 294 | if (imagePaths.size() == 0) 295 | saveCommunity(subsriber, myCommunity); 296 | else 297 | for (int i = 0; i < imagePaths.size(); i++) { 298 | final DroiFile file = new DroiFile(UserInfoActivity.Bitmap2Bytes(UserInfoActivity.decodeSampledBitmapFromPath(imagePaths.get(i), 120, 120))); 299 | file.saveInBackground(new DroiCallback() { 300 | @Override 301 | public void result(Boolean aBoolean, DroiError droiError) { 302 | if (aBoolean) { 303 | list.add(file.getUri().toString().replaceAll("\\\\", "")); 304 | if (list.size() == imagePaths.size()) { 305 | myCommunity.setImagePaths(list); 306 | saveCommunity(subsriber, myCommunity); 307 | } 308 | } else 309 | Toast.makeText(MainActivity.this, droiError.getTicket(), Toast.LENGTH_SHORT).show(); 310 | } 311 | }); 312 | } 313 | } 314 | 315 | private void saveCommunity(final Subsriber subsriber, MyCommunity myCommunity) { 316 | DroiError error = myCommunity.save(); 317 | if (error.isOk()) { 318 | subsriber.onComplete(); 319 | Toast.makeText(MainActivity.this, "发帖成功", Toast.LENGTH_SHORT).show(); 320 | } else 321 | Toast.makeText(MainActivity.this, error.getAppendedMessage(), Toast.LENGTH_SHORT).show(); 322 | 323 | } 324 | 325 | private String dateCompare(long createTime) { 326 | Date now = new Date(); 327 | long day = (now.getTime() - createTime) / (86400000); 328 | if (day == 0) { 329 | long time = (now.getTime() - createTime) / 1000; 330 | if (time < 60) 331 | return "刚刚"; 332 | if (60 <= time && time < 3600) 333 | return (time / 60) + "分钟前"; 334 | else 335 | return (time / 3600) + "小时前"; 336 | } else if (day == 1) 337 | return "昨天"; 338 | else if (day > 1 && day < 31) 339 | return day + "天前"; 340 | else if (day >= 31 && day < 365) 341 | return (day / 30) + "个月前"; 342 | else 343 | return (day / 365) + "年前"; 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /app/src/main/java/bmob/yellowdoing/com/bmobcommunity/MyComment.java: -------------------------------------------------------------------------------- 1 | package bmob.yellowdoing.com.bmobcommunity; 2 | 3 | import com.droi.sdk.core.DroiExpose; 4 | import com.droi.sdk.core.DroiObject; 5 | import com.droi.sdk.core.DroiObjectName; 6 | import com.droi.sdk.core.DroiReference; 7 | 8 | /** 9 | * Created by YellowDoing on 2017/11/21. 10 | */ 11 | @DroiObjectName("Comment") 12 | public class MyComment extends DroiObject { 13 | @DroiReference 14 | private User author; 15 | @DroiExpose 16 | private String content; 17 | @DroiExpose 18 | private String commentId; 19 | @DroiExpose 20 | private String communityId; 21 | @DroiExpose 22 | private String parentId; 23 | 24 | 25 | public String getParentId() { 26 | return parentId; 27 | } 28 | 29 | public void setParentId(String parentId) { 30 | this.parentId = parentId; 31 | } 32 | 33 | public User getAuthor() { 34 | return author; 35 | } 36 | 37 | public void setAuthor(User author) { 38 | this.author = author; 39 | } 40 | 41 | public String getContent() { 42 | return content; 43 | } 44 | 45 | public void setContent(String content) { 46 | this.content = content; 47 | } 48 | 49 | public String getCommentId() { 50 | return commentId; 51 | } 52 | 53 | public void setCommentId(String commentId) { 54 | this.commentId = commentId; 55 | } 56 | 57 | public String getCommunityId() { 58 | return communityId; 59 | } 60 | 61 | public void setCommunityId(String communityId) { 62 | this.communityId = communityId; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/bmob/yellowdoing/com/bmobcommunity/MyCommunity.java: -------------------------------------------------------------------------------- 1 | package bmob.yellowdoing.com.bmobcommunity; 2 | 3 | import com.droi.sdk.core.DroiExpose; 4 | import com.droi.sdk.core.DroiObject; 5 | import com.droi.sdk.core.DroiObjectName; 6 | import com.droi.sdk.core.DroiReference; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by YellowDoing on 2017/11/18. 13 | * 14 | */ 15 | 16 | @DroiObjectName("Community") 17 | public class MyCommunity extends DroiObject { 18 | 19 | @DroiExpose 20 | private ArrayList imagePaths; 21 | @DroiExpose 22 | private String content; 23 | @DroiReference 24 | private User author; 25 | @DroiExpose 26 | private String nickName; 27 | @DroiExpose 28 | private int replyNum; 29 | @DroiExpose 30 | private List likePersons; 31 | 32 | 33 | public List getLikePersons() { 34 | return likePersons; 35 | } 36 | 37 | public void setLikePersons(List likePersons) { 38 | this.likePersons = likePersons; 39 | } 40 | 41 | public String getNickName() { 42 | return nickName; 43 | } 44 | 45 | public void setNickName(String nickName) { 46 | this.nickName = nickName; 47 | } 48 | 49 | public int getReplyNum() { 50 | return replyNum; 51 | } 52 | 53 | public void setReplyNum(int replyNum) { 54 | this.replyNum = replyNum; 55 | } 56 | 57 | public ArrayList getImagePaths() { 58 | return imagePaths; 59 | } 60 | 61 | public void setImagePaths(ArrayList imagePaths) { 62 | this.imagePaths = imagePaths; 63 | } 64 | 65 | public String getContent() { 66 | return content; 67 | } 68 | 69 | public void setContent(String content) { 70 | this.content = content; 71 | } 72 | 73 | public User getAuthor() { 74 | return author; 75 | } 76 | 77 | public void setAuthor(User author) { 78 | this.author = author; 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return "MyCommunity{" + 84 | "imagePaths=" + imagePaths + 85 | ", content='" + content + '\'' + 86 | ", author=" + author + 87 | ", nickName='" + nickName + '\'' + 88 | ", replyNum=" + replyNum + 89 | ", likePersons=" + likePersons + 90 | '}'; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/bmob/yellowdoing/com/bmobcommunity/User.java: -------------------------------------------------------------------------------- 1 | package bmob.yellowdoing.com.bmobcommunity; 2 | import com.droi.sdk.core.DroiExpose; 3 | import com.droi.sdk.core.DroiFile; 4 | import com.droi.sdk.core.DroiUser; 5 | 6 | /** 7 | * Created by YellowDoing on 2017/11/18. 8 | */ 9 | 10 | public class User extends DroiUser { 11 | 12 | @DroiExpose 13 | private DroiFile avatar; 14 | @DroiExpose 15 | private String nickName; 16 | 17 | public DroiFile getAvatar() { 18 | return avatar; 19 | } 20 | 21 | public void setAvatar(DroiFile avatar) { 22 | this.avatar = avatar; 23 | } 24 | 25 | public String getNickName() { 26 | return nickName; 27 | } 28 | 29 | public void setNickName(String nickName) { 30 | this.nickName = nickName; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "User{" + 36 | "avatar=" + avatar + 37 | ", nickName='" + nickName + '\'' + 38 | "id=" + getObjectId()+ 39 | '}'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/bmob/yellowdoing/com/bmobcommunity/UserInfoActivity.java: -------------------------------------------------------------------------------- 1 | package bmob.yellowdoing.com.bmobcommunity; 2 | 3 | import android.content.Intent; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.app.AlertDialog; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.util.Log; 11 | import android.view.View; 12 | import android.widget.EditText; 13 | import android.widget.ImageView; 14 | import android.widget.ProgressBar; 15 | import android.widget.Toast; 16 | import com.bumptech.glide.Glide; 17 | import com.droi.sdk.DroiCallback; 18 | import com.droi.sdk.DroiError; 19 | import com.droi.sdk.DroiProgressCallback; 20 | import com.droi.sdk.core.DroiFile; 21 | import com.droi.sdk.core.DroiPermission; 22 | import com.droi.sdk.core.DroiUser; 23 | 24 | import java.io.ByteArrayOutputStream; 25 | 26 | 27 | import me.iwf.photopicker.PhotoPicker; 28 | 29 | /** 30 | * Created by YellowDoing on 2017/11/8. 31 | * 32 | */ 33 | 34 | public class UserInfoActivity extends AppCompatActivity implements View.OnClickListener { 35 | 36 | 37 | private ImageView ivAvatar; 38 | private EditText etNickName; 39 | private DroiFile mFile; 40 | private AlertDialog.Builder mBuilder; 41 | 42 | @Override 43 | protected void onCreate(@Nullable Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | setContentView(R.layout.activity_user_info); 46 | 47 | ivAvatar = (ImageView) findViewById(R.id.iv_avatar); 48 | etNickName = (EditText) findViewById(R.id.et_nickname); 49 | ivAvatar.setOnClickListener(this); 50 | etNickName.setOnClickListener(this); 51 | findViewById(R.id.save).setOnClickListener(this); 52 | 53 | User user = DroiUser.getCurrentUser(User.class); 54 | if (user.getNickName() != null) 55 | etNickName.setText(user.getNickName()); 56 | if (user.getAvatar() != null) 57 | Glide.with(this).load(user.getAvatar()).centerCrop().into(ivAvatar); 58 | 59 | mBuilder = new AlertDialog.Builder(this).setView(new ProgressBar(this)); 60 | } 61 | 62 | @Override 63 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 64 | super.onActivityResult(requestCode, resultCode, data); 65 | if (resultCode == RESULT_OK) { 66 | switch (requestCode) { 67 | case PhotoPicker.REQUEST_CODE: //选择图片返回 68 | String path = data.getStringArrayListExtra(PhotoPicker.KEY_SELECTED_PHOTOS).get(0); 69 | mFile = new DroiFile(Bitmap2Bytes(decodeSampledBitmapFromPath(path, 80, 80))); 70 | Glide.with(this).load(path).centerCrop().into(ivAvatar); 71 | break; 72 | } 73 | } 74 | } 75 | 76 | 77 | private void saveUser(AlertDialog dialog) { 78 | User user = DroiUser.getCurrentUser(User.class); 79 | if (mFile != null) 80 | user.setAvatar(mFile); 81 | 82 | user.setNickName(etNickName.getText().toString()); 83 | DroiError error = user.save(); 84 | if (error.isOk()) { 85 | Toast.makeText(UserInfoActivity.this, "保存成功", Toast.LENGTH_SHORT).show(); 86 | finish(); 87 | } else 88 | Toast.makeText(UserInfoActivity.this, error.getAppendedMessage(), Toast.LENGTH_SHORT).show(); 89 | dialog.cancel(); 90 | } 91 | 92 | private void uploadAvatar() { 93 | final AlertDialog dialog = mBuilder.show(); 94 | 95 | if (mFile != null) { 96 | DroiPermission permission = new DroiPermission(); 97 | permission.setPublicReadPermission(true); 98 | mFile.setPermission(permission); 99 | mFile.saveInBackground(new DroiCallback() { 100 | @Override 101 | public void result(Boolean aBoolean, DroiError droiError) { 102 | if (aBoolean) 103 | saveUser(dialog); 104 | else 105 | Toast.makeText(UserInfoActivity.this, droiError.getAppendedMessage(), Toast.LENGTH_SHORT).show(); 106 | } 107 | }, new DroiProgressCallback() { 108 | @Override 109 | public void progress(Object o, long l, long l1) { 110 | Log.d("aaaa", "上传了: " + (int)((l*100)/l1) + "%"); 111 | } 112 | }); 113 | } else 114 | saveUser(dialog); 115 | } 116 | 117 | @Override 118 | public void onClick(View v) { 119 | switch (v.getId()) { 120 | case R.id.iv_avatar: 121 | PhotoPicker.builder() 122 | .setPreviewEnabled(true) 123 | .setShowGif(false) 124 | .setPhotoCount(1) 125 | .setShowCamera(false) 126 | .setGridColumnCount(3) 127 | .start(this, PhotoPicker.REQUEST_CODE); 128 | break; 129 | case R.id.save: 130 | uploadAvatar(); 131 | break; 132 | } 133 | } 134 | 135 | private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { 136 | // Raw height and width of image 137 | final int height = options.outHeight; 138 | final int width = options.outWidth; 139 | int inSampleSize = 1; 140 | 141 | if (height > reqHeight || width > reqWidth) { 142 | 143 | final int halfHeight = height / 2; 144 | final int halfWidth = width / 2; 145 | 146 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both 147 | // height and width larger than the requested height and width. 148 | while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { 149 | inSampleSize *= 2; 150 | } 151 | } 152 | 153 | return inSampleSize; 154 | } 155 | 156 | public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth, int reqHeight) { 157 | // First decode with inJustDecodeBounds=true to check dimensions 158 | final BitmapFactory.Options options = new BitmapFactory.Options(); 159 | options.inJustDecodeBounds = true; 160 | BitmapFactory.decodeFile(path, options); 161 | 162 | // Calculate inSampleSize 163 | options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 164 | 165 | // Decode bitmap with inSampleSize set 166 | options.inJustDecodeBounds = false; 167 | return BitmapFactory.decodeFile(path, options); 168 | } 169 | 170 | public static byte[] Bitmap2Bytes(Bitmap bm) { 171 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 172 | bm.compress(Bitmap.CompressFormat.PNG, 95, baos); 173 | return baos.toByteArray(); 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YellowDoing/communityUI/c9510548607b2196870547b5825178ed6a2775ff/app/src/main/res/drawable-hdpi/ic_post.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YellowDoing/communityUI/c9510548607b2196870547b5825178ed6a2775ff/app/src/main/res/drawable-mdpi/ic_post.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YellowDoing/communityUI/c9510548607b2196870547b5825178ed6a2775ff/app/src/main/res/drawable-xhdpi/ic_post.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YellowDoing/communityUI/c9510548607b2196870547b5825178ed6a2775ff/app/src/main/res/drawable-xxhdpi/ic_post.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 21 | 22 | 29 | 30 | 31 | 32 | 36 | 37 | 45 | 46 | 54 | 55 | 56 | 57 |