diff --git a/README.md b/README.md index 3acbf702..7a7eca3a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ -# CNode Material Design [![API](https://img.shields.io/badge/API-9%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=9) # +![Banner](/art/banner-1024-500.png) -![Logo](/art/ic_launcher.png) +# CNode Material Design # -> [CNode社区](https://cnodejs.org) 第三方Android客户端,原生App,Material Design风格,支持夜间模式 +[![Platform](https://img.shields.io/badge/platform-Android-green.svg?style=flat)](http://developer.android.com/index.html) +[![API](https://img.shields.io/badge/API-9%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=9) +[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) -> [https://cnodejs.org/topic/55c2f7f15965fe2c74f4791d](https://cnodejs.org/topic/55c2f7f15965fe2c74f4791d) +![Logo](/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png) + +[CNode社区](https://cnodejs.org) 第三方Android客户端,原生App,Material Design风格,支持夜间模式 + +[https://cnodejs.org/topic/55c2f7f15965fe2c74f4791d](https://cnodejs.org/topic/55c2f7f15965fe2c74f4791d) ## Download ## @@ -40,24 +46,26 @@ - [Joda-Time](http://www.joda.org/joda-time) -- [Retrofit](http://square.github.io/retrofit) +- [Joda-Time-Android](https://github.com/dlew/joda-time-android) - [OkHttp](http://square.github.io/okhttp) -- [Picasso](http://square.github.io/picasso) +- [Retrofit](http://square.github.io/retrofit) + +- [Glide](https://github.com/bumptech/glide) - [CircleImageView](https://github.com/hdodenhof/CircleImageView) - [MaterialEditText](https://github.com/rengwuxian/MaterialEditText) -- [Material Dialogs](https://github.com/afollestad/material-dialogs) - - [materialish-progress](https://github.com/pnikosis/materialish-progress) - [FloatingActionButton](https://github.com/makovkastar/FloatingActionButton) - [QRCodeReaderView](https://github.com/dlazaro66/QRCodeReaderView) +- [PhotoView](https://github.com/chrisbanes/PhotoView) + - [MarkdownPapers](http://markdown.tautua.org) - [Google I/O Android App - ScrimInsetsViews](https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/widget/ScrimInsetsScrollView.java) @@ -92,7 +100,7 @@ > [https://cnodejs.org/topic/559bd1b91e5c761761468884](https://cnodejs.org/topic/559bd1b91e5c761761468884) -> PS:基于React Native实现,目前只有IOS版本,作者有计划适配Android。比较不错的技术实践,作者比较活跃。 +> PS:基于React Native实现,包含iOS和Android两个平台 ### [Vue-cnodejs](https://github.com/shinygang/Vue-cnodejs) ![image](/art/ic_good_project.png) ### @@ -100,6 +108,24 @@ > [https://cnodejs.org/topic/565c4473d0bc14ae279399fe](https://cnodejs.org/topic/565c4473d0bc14ae279399fe) +### [swift-cnode](https://github.com/nswbmw/swift-cnode) ### + +> [CNode社区](https://cnodejs.org/) 的又一 swift 客户端,未完成...不定期更新,欢迎 fork 与 PR。 + +> [https://cnodejs.org/topic/570a7adcbc564eaf3c6a4901](https://cnodejs.org/topic/570a7adcbc564eaf3c6a4901) + +### [cnode-angular-material](https://github.com/XGHeaven/cnode-angular-material) ### + +> CNode.JS 社区的 Angular-Material 的实现,暂时处于开发状态中 [http://cnode.xgheaven.cn](http://cnode.xgheaven.cn) + +> [https://cnodejs.org/topic/566bb3a35af0e6ab3bf1a280](https://cnodejs.org/topic/566bb3a35af0e6ab3bf1a280) + +### [Ioniclub](https://github.com/XueRainey/Ioniclub) ### + +> Ioniclub is hybird mobile app of [https://cnodejs.org](https://cnodejs.org), web demo [http://rainey.coding.io/ioniclub/](http://rainey.coding.io/ioniclub/) + +> [https://cnodejs.org/topic/57111863434cfcfa52684a8e](https://cnodejs.org/topic/57111863434cfcfa52684a8e) + ### [CNode-android](https://github.com/iwhys/CNode-android) ### > 这是为CNodejs社区(https://cnodejs.org) 开发的原生Android版客户端。适用于Android4.0及以上。 @@ -130,13 +156,13 @@ > PS:CNode社区早期的客户端实践项目,基于Ionic 和 PhoneGap。作者已经很长时间没有更新了。 -### [cnodejs-reader](https://github.com/cnodejs/cnodejs-reader) ### +### [cnodejs-reader](https://github.com/cnodejs/cnodejs-reader) ![image](/art/ic_good_project.png) ### -> CNode.js client in React. React 版本的 CNode 网页版。 +> CNode.js client in React. Demo: [http://r.nodejs-china.org](http://r.nodejs-china.org) > [https://cnodejs.org/topic/545b97cc3e1f39344c5b3c1a](https://cnodejs.org/topic/545b97cc3e1f39344c5b3c1a) -> PS:CNode社区早期的客户端实践项目。演示地址在 [http://r.nodejs-china.org](http://r.nodejs-china.org)。 +> PS:开发基于cirru,一种定制过语法的JavaScript,详情地址在 [http://script.cirru.org](http://script.cirru.org) ### [CNode4WP](https://github.com/heimeil/CNode4WP) ### diff --git a/app/build.gradle b/app/build.gradle index f69d55f7..e8572899 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,14 +11,14 @@ properties.load(project.rootProject.file('local.properties').newDataInputStream( android { compileSdkVersion 23 - buildToolsVersion "23.0.2" + buildToolsVersion "23.0.3" defaultConfig { applicationId "org.cnodejs.android.md" minSdkVersion 9 targetSdkVersion 22 versionCode Integer.parseInt(time) - versionName "1.0.14" + versionName "1.1.0" manifestPlaceholders = [ UMENG_CHANNEL: "CNodeJS", @@ -44,25 +44,24 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:design:23.2.0' + compile 'com.android.support:design:23.3.0' compile 'com.jakewharton:butterknife:7.0.1' - compile 'com.google.code.gson:gson:2.6.1' - compile 'joda-time:joda-time:2.9.2' - compile 'com.squareup.retrofit:retrofit:1.9.0' - compile 'com.squareup.okhttp:okhttp:2.6.0' - compile 'com.squareup.picasso:picasso:2.5.2' + compile 'com.google.code.gson:gson:2.6.2' + compile 'net.danlew:android.joda:2.9.3' + compile 'com.squareup.okhttp3:okhttp:3.2.0' + compile 'com.squareup.okhttp3:logging-interceptor:3.2.0' + compile 'com.squareup.retrofit2:retrofit:2.0.2' + compile 'com.squareup.retrofit2:converter-gson:2.0.2' + compile 'com.github.bumptech.glide:glide:3.7.0' compile 'de.hdodenhof:circleimageview:2.0.0' compile 'com.rengwuxian.materialedittext:library:2.1.4' - compile ('com.github.afollestad.material-dialogs:core:0.8.5.5@aar') { transitive = true } compile 'com.pnikosis:materialish-progress:1.7' compile 'com.melnykov:floatingactionbutton:1.3.0' - compile 'com.dlazaro66.qrcodereaderview:qrcodereaderview:1.0.0' - + compile 'com.github.chrisbanes:PhotoView:1.2.6' compile 'org.tautua.markdownpapers:markdownpapers-core:1.4.2' - compile project(':umeng-update-sdk') - compile 'com.umeng.analytics:analytics:5.6.4' + compile 'com.umeng.analytics:analytics:5.6.7' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f42a66bf..2183041b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,7 +17,7 @@ android:theme="@style/AppThemeLight"> + + @@ -118,17 +124,6 @@ android:name="UMENG_CHANNEL" android:value="${UMENG_CHANNEL}" /> - - - - - diff --git a/app/src/main/java/org/cnodejs/android/md/app/AppController.java b/app/src/main/java/org/cnodejs/android/md/app/AppController.java index ed858d6e..da72fdcd 100644 --- a/app/src/main/java/org/cnodejs/android/md/app/AppController.java +++ b/app/src/main/java/org/cnodejs/android/md/app/AppController.java @@ -2,12 +2,10 @@ import android.app.Application; import android.content.Context; -import android.text.TextUtils; -import com.umeng.analytics.MobclickAgent; +import net.danlew.android.joda.JodaTimeAndroid; import org.cnodejs.android.md.BuildConfig; -import org.cnodejs.android.md.storage.LoginShared; public class AppController extends Application { @@ -22,17 +20,13 @@ public void onCreate() { super.onCreate(); context = this; + // 初始化JodaTimeAndroid + JodaTimeAndroid.init(this); + // 配置全局异常捕获 if (!BuildConfig.DEBUG) { Thread.setDefaultUncaughtExceptionHandler(new AppExceptionHandler(this)); } - - // TODO 友盟账号统计 - if (!TextUtils.isEmpty(LoginShared.getAccessToken(this))) { - MobclickAgent.onProfileSignIn(LoginShared.getLoginName(this)); - } else { - MobclickAgent.onProfileSignOff(); - } } } diff --git a/app/src/main/java/org/cnodejs/android/md/app/AppExceptionHandler.java b/app/src/main/java/org/cnodejs/android/md/app/AppExceptionHandler.java index c14d2581..b7013664 100644 --- a/app/src/main/java/org/cnodejs/android/md/app/AppExceptionHandler.java +++ b/app/src/main/java/org/cnodejs/android/md/app/AppExceptionHandler.java @@ -4,7 +4,7 @@ import android.content.Intent; import android.os.Bundle; -import org.cnodejs.android.md.ui.activity.CrashLogActivity; +import org.cnodejs.android.md.display.activity.CrashLogActivity; public class AppExceptionHandler implements Thread.UncaughtExceptionHandler { diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/AboutActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/AboutActivity.java similarity index 87% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/AboutActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/AboutActivity.java index b34bdcf3..463f261f 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/AboutActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/AboutActivity.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; import android.content.Intent; import android.os.Build; @@ -6,15 +6,12 @@ import android.support.v7.widget.Toolbar; import android.widget.TextView; -import com.afollestad.materialdialogs.MaterialDialog; - import org.cnodejs.android.md.BuildConfig; import org.cnodejs.android.md.R; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; -import org.cnodejs.android.md.ui.widget.ThemeUtils; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.widget.ThemeUtils; import org.cnodejs.android.md.util.ShipUtils; -import org.cnodejs.android.md.util.UpdateUtils; import butterknife.Bind; import butterknife.ButterKnife; @@ -44,7 +41,7 @@ protected void onCreate(Bundle savedInstanceState) { @OnClick(R.id.about_btn_version) protected void onBtnVersionClick() { - UpdateUtils.forceUpdate(this); + // nothing to do } @OnClick(R.id.about_btn_open_source_url) diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/CrashLogActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/CrashLogActivity.java similarity index 92% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/CrashLogActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/CrashLogActivity.java index 24b96616..ebcf13f4 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/CrashLogActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/CrashLogActivity.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; import android.content.Intent; import android.os.Build; @@ -8,9 +8,9 @@ import android.widget.TextView; import org.cnodejs.android.md.R; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; -import org.cnodejs.android.md.ui.widget.ThemeUtils; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.widget.ThemeUtils; import org.cnodejs.android.md.util.ShipUtils; import org.joda.time.DateTime; diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/NewTopicActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/CreateTopicActivity.java similarity index 59% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/NewTopicActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/CreateTopicActivity.java index aa4ae6b4..7daf815b 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/NewTopicActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/CreateTopicActivity.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; import android.os.Bundle; import android.support.v7.widget.Toolbar; @@ -7,27 +7,28 @@ import android.widget.EditText; import android.widget.Spinner; -import com.afollestad.materialdialogs.MaterialDialog; - import org.cnodejs.android.md.R; import org.cnodejs.android.md.model.api.ApiClient; +import org.cnodejs.android.md.model.api.DefaultToastCallback; +import org.cnodejs.android.md.model.entity.Result; import org.cnodejs.android.md.model.entity.TabType; -import org.cnodejs.android.md.storage.LoginShared; -import org.cnodejs.android.md.storage.SettingShared; -import org.cnodejs.android.md.storage.TopicShared; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; -import org.cnodejs.android.md.ui.widget.EditorBarHandler; -import org.cnodejs.android.md.ui.widget.ThemeUtils; -import org.cnodejs.android.md.ui.widget.ToastUtils; +import org.cnodejs.android.md.model.storage.LoginShared; +import org.cnodejs.android.md.model.storage.SettingShared; +import org.cnodejs.android.md.model.storage.TopicShared; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.dialog.DialogUtils; +import org.cnodejs.android.md.display.dialog.ProgressDialog; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.widget.EditorBarHandler; +import org.cnodejs.android.md.display.widget.ThemeUtils; +import org.cnodejs.android.md.display.widget.ToastUtils; import butterknife.Bind; import butterknife.ButterKnife; -import retrofit.Callback; -import retrofit.RetrofitError; -import retrofit.client.Response; +import retrofit2.Call; +import retrofit2.Response; -public class NewTopicActivity extends StatusBarActivity implements Toolbar.OnMenuItemClickListener { +public class CreateTopicActivity extends StatusBarActivity implements Toolbar.OnMenuItemClickListener { @Bind(R.id.new_topic_toolbar) protected Toolbar toolbar; @@ -44,7 +45,9 @@ public class NewTopicActivity extends StatusBarActivity implements Toolbar.OnMen @Bind(R.id.new_topic_edt_content) protected EditText edtContent; - private MaterialDialog dialog; + private ProgressDialog progressDialog; + + private boolean saveTopicDraft = true; @Override protected void onCreate(Bundle savedInstanceState) { @@ -57,11 +60,9 @@ protected void onCreate(Bundle savedInstanceState) { toolbar.inflateMenu(R.menu.new_topic); toolbar.setOnMenuItemClickListener(this); - dialog = new MaterialDialog.Builder(this) - .content(R.string.posting_$_) - .progress(true, 0) - .cancelable(false) - .build(); + progressDialog = DialogUtils.createProgressDialog(this); + progressDialog.setMessage(R.string.posting_$_); + progressDialog.setCancelable(false); // 创建EditorBar new EditorBarHandler(this, editorBar, edtContent); @@ -82,17 +83,13 @@ protected void onCreate(Bundle savedInstanceState) { @Override protected void onPause() { super.onPause(); - if (SettingShared.isEnableNewTopicDraft(this)) { + if (SettingShared.isEnableNewTopicDraft(this) && saveTopicDraft) { TopicShared.setNewTopicTabPosition(this, spnTab.getSelectedItemPosition()); TopicShared.setNewTopicTitle(this, edtTitle.getText().toString()); TopicShared.setNewTopicContent(this, edtContent.getText().toString()); } } - /** - * 发送逻辑 - */ - @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { @@ -110,7 +107,7 @@ public boolean onMenuItemClick(MenuItem item) { if (SettingShared.isEnableTopicSign(this)) { // 添加小尾巴 content += "\n\n" + SettingShared.getTopicSignContent(this); } - newTipicAsyncTask(tab, title, content); + createTipicAsyncTask(tab, title, content); } return true; default: @@ -131,41 +128,27 @@ private TabType getTabByPosition(int position) { } } - private void newTipicAsyncTask(TabType tab, String title, String content) { - dialog.show(); - ApiClient.service.newTopic(LoginShared.getAccessToken(this), tab, title, content, new Callback() { + private void createTipicAsyncTask(TabType tab, String title, String content) { + progressDialog.show(); + Call call = ApiClient.service.createTopic(LoginShared.getAccessToken(this), tab, title, content); + call.enqueue(new DefaultToastCallback(this) { @Override - public void success(Void nothing, Response response) { - dialog.dismiss(); - // 清除草稿 TODO 由于保存草稿的动作在onPause中,并且保存过程是异步的,因此保险起见,优先清除控件数据 - spnTab.setSelection(0); - edtTitle.setText(null); - edtContent.setText(null); - TopicShared.clear(NewTopicActivity.this); - // 结束当前并提示 + public boolean onResultOk(Response response, Result.CreateTopic result) { + saveTopicDraft = false; + TopicShared.clear(CreateTopicActivity.this); + ToastUtils.with(CreateTopicActivity.this).show(R.string.post_success); + TopicActivity.start(CreateTopicActivity.this, result.getTopicId()); finish(); - ToastUtils.with(NewTopicActivity.this).show(R.string.post_success); + return false; } @Override - public void failure(RetrofitError error) { - dialog.dismiss(); - if (error.getResponse() != null && error.getResponse().getStatus() == 403) { - showAccessTokenErrorDialog(); - } else { - ToastUtils.with(NewTopicActivity.this).show(R.string.network_faild); - } + public void onFinish() { + progressDialog.dismiss(); } }); } - private void showAccessTokenErrorDialog() { - new MaterialDialog.Builder(this) - .content(R.string.access_token_error_tip) - .positiveText(R.string.confirm) - .show(); - } - } diff --git a/app/src/main/java/org/cnodejs/android/md/display/activity/ImagePreviewActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/ImagePreviewActivity.java new file mode 100644 index 00000000..14b47133 --- /dev/null +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/ImagePreviewActivity.java @@ -0,0 +1,73 @@ +package org.cnodejs.android.md.display.activity; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.widget.Toolbar; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; +import com.pnikosis.materialishprogress.ProgressWheel; + +import org.cnodejs.android.md.R; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; + +import butterknife.Bind; +import butterknife.ButterKnife; +import uk.co.senab.photoview.PhotoView; + +public class ImagePreviewActivity extends StatusBarActivity { + + private static final String EXTRA_IMAGE_URL = "imageUrl"; + + public static void start(@NonNull Context context, String imageUrl) { + Intent intent = new Intent(context, ImagePreviewActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRA_IMAGE_URL, imageUrl); + context.startActivity(intent); + } + + @Bind(R.id.image_preview_toolbar) + protected Toolbar toolbar; + + @Bind(R.id.image_preview_photo_view) + protected PhotoView photoView; + + @Bind(R.id.image_preview_progress_wheel) + protected ProgressWheel progressWheel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_image_preview); + ButterKnife.bind(this); + + toolbar.setNavigationOnClickListener(new NavigationFinishClickListener(this)); + + loadImageAsyncTask(); + } + + private void loadImageAsyncTask() { + progressWheel.spin(); + Glide.with(this).load(getIntent().getStringExtra(EXTRA_IMAGE_URL)).error(R.drawable.image_error).dontAnimate().listener(new RequestListener() { + + @Override + public boolean onResourceReady(GlideDrawable resource, String model, Target target, boolean isFromMemoryCache, boolean isFirstResource) { + progressWheel.stopSpinning(); + return false; + } + + @Override + public boolean onException(Exception e, String model, Target target, boolean isFirstResource) { + progressWheel.stopSpinning(); + return false; + } + + }).into(photoView); + } + +} diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/LaunchActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/LaunchActivity.java similarity index 85% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/LaunchActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/LaunchActivity.java index 782a3873..07840842 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/LaunchActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/LaunchActivity.java @@ -1,10 +1,10 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; import android.content.Intent; import android.os.Bundle; import org.cnodejs.android.md.R; -import org.cnodejs.android.md.ui.base.BaseActivity; +import org.cnodejs.android.md.display.base.BaseActivity; import org.cnodejs.android.md.util.HandlerUtils; public class LaunchActivity extends BaseActivity implements Runnable { diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/LicenseActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/LicenseActivity.java similarity index 68% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/LicenseActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/LicenseActivity.java index f82fce4b..2b26e8b6 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/LicenseActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/LicenseActivity.java @@ -1,14 +1,14 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; import android.os.Bundle; import android.support.v7.widget.Toolbar; import android.widget.TextView; import org.cnodejs.android.md.R; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; -import org.cnodejs.android.md.ui.widget.ThemeUtils; -import org.cnodejs.android.md.util.ResRawUtils; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.widget.ThemeUtils; +import org.cnodejs.android.md.util.ResUtils; import butterknife.Bind; import butterknife.ButterKnife; @@ -30,7 +30,7 @@ protected void onCreate(Bundle savedInstanceState) { toolbar.setNavigationOnClickListener(new NavigationFinishClickListener(this)); - tvLicense.setText(ResRawUtils.getString(this, R.raw.open_source)); + tvLicense.setText(ResUtils.getRawString(this, R.raw.open_source)); } } diff --git a/app/src/main/java/org/cnodejs/android/md/display/activity/LoginActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/LoginActivity.java new file mode 100644 index 00000000..1a0b46ec --- /dev/null +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/LoginActivity.java @@ -0,0 +1,141 @@ +package org.cnodejs.android.md.display.activity; + +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.widget.Toolbar; +import android.text.TextUtils; + +import com.rengwuxian.materialedittext.MaterialEditText; + +import org.cnodejs.android.md.R; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.dialog.DialogUtils; +import org.cnodejs.android.md.display.dialog.ProgressDialog; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.widget.ThemeUtils; +import org.cnodejs.android.md.display.widget.ToastUtils; +import org.cnodejs.android.md.model.api.ApiClient; +import org.cnodejs.android.md.model.api.DefaultToastCallback; +import org.cnodejs.android.md.model.entity.Result; +import org.cnodejs.android.md.model.storage.LoginShared; +import org.cnodejs.android.md.util.FormatUtils; + +import butterknife.Bind; +import butterknife.ButterKnife; +import butterknife.OnClick; +import retrofit2.Call; +import retrofit2.Response; + +public class LoginActivity extends StatusBarActivity { + + public static final int REQUEST_LOGIN = FormatUtils.createRequestCode(); + + public static void startForResult(@NonNull Activity activity) { + activity.startActivityForResult(new Intent(activity, LoginActivity.class), REQUEST_LOGIN); + } + + public static boolean startForResultWithAccessTokenCheck(@NonNull final Activity activity) { + if (TextUtils.isEmpty(LoginShared.getAccessToken(activity))) { + DialogUtils.createAlertDialogBuilder(activity) + .setMessage(R.string.need_login_tip) + .setPositiveButton(R.string.login, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + startForResult(activity); + } + + }) + .setNegativeButton(R.string.cancel, null) + .show(); + return false; + } else { + return true; + } + } + + @Bind(R.id.login_toolbar) + protected Toolbar toolbar; + + @Bind(R.id.login_edt_access_token) + protected MaterialEditText edtAccessToken; + + protected ProgressDialog progressDialog; + + @Override + protected void onCreate(Bundle savedInstanceState) { + ThemeUtils.configThemeBeforeOnCreate(this, R.style.AppThemeLight, R.style.AppThemeDark); + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + ButterKnife.bind(this); + + toolbar.setNavigationOnClickListener(new NavigationFinishClickListener(this)); + + progressDialog = DialogUtils.createProgressDialog(this); + progressDialog.setMessage(R.string.logging_in_$_); + } + + @OnClick(R.id.login_btn_login) + protected void onBtnLoginClick() { + final String accessToken = edtAccessToken.getText().toString().trim(); + if (!FormatUtils.isAccessToken(accessToken)) { + edtAccessToken.setError(getString(R.string.access_token_format_error)); + edtAccessToken.requestFocus(); + } else { + final Call call = ApiClient.service.accessToken(accessToken); + progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + + @Override + public void onCancel(DialogInterface dialog) { + call.cancel(); + } + + }); + progressDialog.show(); + call.enqueue(new DefaultToastCallback(this) { + + @Override + public boolean onResultOk(Response response, Result.Login loginInfo) { + LoginShared.login(LoginActivity.this, accessToken, loginInfo); + ToastUtils.with(LoginActivity.this).show(R.string.login_success); + setResult(RESULT_OK); + finish(); + return false; + } + + @Override + public boolean onResultErrorAuth(Response response, Result.Error error) { + edtAccessToken.setError(getString(R.string.access_token_auth_error)); + edtAccessToken.requestFocus(); + return false; + } + + @Override + public void onFinish() { + progressDialog.setOnCancelListener(null); + progressDialog.dismiss(); + } + + }); + } + } + + @OnClick(R.id.login_btn_qrcode) + protected void onBtnQrcodeClick() { + QRCodeActivity.startForResult(this); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == QRCodeActivity.REQUEST_QRCODE && resultCode == RESULT_OK) { + edtAccessToken.setText(data.getStringExtra(QRCodeActivity.EXTRA_QRCODE)); + edtAccessToken.setSelection(edtAccessToken.length()); + onBtnLoginClick(); + } + } + +} diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/MainActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/MainActivity.java similarity index 55% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/MainActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/MainActivity.java index 482b6fdf..f55f5ffb 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/MainActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/MainActivity.java @@ -1,8 +1,8 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; +import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; -import android.support.annotation.NonNull; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.SwipeRefreshLayout; @@ -11,35 +11,32 @@ import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.view.View; -import android.view.ViewGroup; import android.widget.CheckedTextView; import android.widget.ImageView; import android.widget.TextView; -import com.afollestad.materialdialogs.DialogAction; -import com.afollestad.materialdialogs.MaterialDialog; +import com.bumptech.glide.Glide; import com.melnykov.fab.FloatingActionButton; -import com.squareup.picasso.Picasso; -import com.umeng.analytics.MobclickAgent; import org.cnodejs.android.md.R; +import org.cnodejs.android.md.display.adapter.MainAdapter; +import org.cnodejs.android.md.display.base.DrawerLayoutActivity; +import org.cnodejs.android.md.display.dialog.DialogUtils; +import org.cnodejs.android.md.display.listener.NavigationOpenClickListener; +import org.cnodejs.android.md.display.listener.RecyclerViewLoadMoreListener; +import org.cnodejs.android.md.display.widget.RefreshLayoutUtils; +import org.cnodejs.android.md.display.widget.ThemeUtils; +import org.cnodejs.android.md.display.widget.ToastUtils; import org.cnodejs.android.md.model.api.ApiClient; import org.cnodejs.android.md.model.api.CallbackAdapter; import org.cnodejs.android.md.model.entity.Result; import org.cnodejs.android.md.model.entity.TabType; import org.cnodejs.android.md.model.entity.Topic; import org.cnodejs.android.md.model.entity.User; -import org.cnodejs.android.md.storage.LoginShared; -import org.cnodejs.android.md.storage.SettingShared; -import org.cnodejs.android.md.ui.adapter.MainAdapter; -import org.cnodejs.android.md.ui.base.DrawerLayoutActivity; -import org.cnodejs.android.md.ui.listener.NavigationOpenClickListener; -import org.cnodejs.android.md.ui.listener.RecyclerViewLoadMoreListener; -import org.cnodejs.android.md.ui.widget.RefreshLayoutUtils; -import org.cnodejs.android.md.ui.widget.ThemeUtils; -import org.cnodejs.android.md.ui.widget.ToastUtils; +import org.cnodejs.android.md.model.storage.LoginShared; +import org.cnodejs.android.md.model.storage.SettingShared; import org.cnodejs.android.md.util.FormatUtils; -import org.cnodejs.android.md.util.UpdateUtils; +import org.cnodejs.android.md.util.HandlerUtils; import java.util.ArrayList; import java.util.List; @@ -47,14 +44,11 @@ import butterknife.Bind; import butterknife.ButterKnife; import butterknife.OnClick; -import retrofit.Callback; -import retrofit.RetrofitError; -import retrofit.client.Response; +import retrofit2.Call; +import retrofit2.Response; public class MainActivity extends DrawerLayoutActivity implements SwipeRefreshLayout.OnRefreshListener, RecyclerViewLoadMoreListener.OnLoadMoreListener { - private static final int REQUEST_LOGIN = 1024; - // 抽屉导航布局 @Bind(R.id.main_drawer_layout) protected DrawerLayout drawerLayout; @@ -108,16 +102,16 @@ public class MainActivity extends DrawerLayoutActivity implements SwipeRefreshLa @Bind(R.id.main_recycler_view) protected RecyclerView recyclerView; - @Bind(R.id.main_fab_new_topic) - protected FloatingActionButton fabNewTopic; + @Bind(R.id.main_icon_no_data) + protected View iconNoData; - @Bind(R.id.main_layout_no_data) - protected ViewGroup layoutNoData; + @Bind(R.id.main_fab_create_topic) + protected FloatingActionButton fabCreateTopic; // 当前版块,默认为all private TabType currentTab = TabType.all; private int currentPage = 0; // 从未加载 - private List topicList = new ArrayList<>(); + private final List topicList = new ArrayList<>(); private MainAdapter adapter; // 首次按下返回键时间戳 @@ -137,7 +131,7 @@ protected void onCreate(Bundle savedInstanceState) { adaptStatusBar(navAdaptStatusBar); drawerLayout.setDrawerShadow(R.drawable.navigation_drawer_shadow, GravityCompat.START); - drawerLayout.setDrawerListener(openDrawerListener); + drawerLayout.addDrawerListener(drawerListener); toolbar.setNavigationOnClickListener(new NavigationOpenClickListener(drawerLayout)); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); @@ -145,7 +139,7 @@ protected void onCreate(Bundle savedInstanceState) { adapter = new MainAdapter(this, topicList); recyclerView.setAdapter(adapter); recyclerView.addOnScrollListener(new RecyclerViewLoadMoreListener(linearLayoutManager, this, 20)); - fabNewTopic.attachToRecyclerView(recyclerView); + fabCreateTopic.attachToRecyclerView(recyclerView); updateUserInfoViews(); @@ -154,49 +148,26 @@ protected void onCreate(Bundle savedInstanceState) { RefreshLayoutUtils.initOnCreate(refreshLayout, this); RefreshLayoutUtils.refreshOnCreate(refreshLayout, this); - - // 更新检查 - UpdateUtils.update(this); } @Override protected void onResume() { super.onResume(); getMessageCountAsyncTask(); - // TODO 判断是否需要切换主题 + // 判断是否需要切换主题 if (SettingShared.isEnableThemeDark(this) != enableThemeDark) { - ThemeUtils.recreateActivity(this); - } - } - - /** - * 未读消息数目 - */ - private void getMessageCountAsyncTask() { - if (!TextUtils.isEmpty(LoginShared.getAccessToken(this))) { - ApiClient.service.getMessageCount(LoginShared.getAccessToken(this), new Callback>() { + HandlerUtils.post(new Runnable() { @Override - public void success(Result result, Response response) { - tvBadgerNotification.setText(FormatUtils.getNavigationDisplayCountText(result.getData())); - } - - @Override - public void failure(RetrofitError error) { - if (error.getResponse() != null && error.getResponse().getStatus() == 403) { - tvBadgerNotification.setText(null); - } + public void run() { + ThemeUtils.recreateActivity(MainActivity.this); } }); } } - /** - * 用户信息更新逻辑 - */ - - private DrawerLayout.DrawerListener openDrawerListener = new DrawerLayout.SimpleDrawerListener() { + private final DrawerLayout.DrawerListener drawerListener = new DrawerLayout.SimpleDrawerListener() { @Override public void onDrawerOpened(View drawerView) { @@ -205,16 +176,56 @@ public void onDrawerOpened(View drawerView) { getMessageCountAsyncTask(); } + @Override + public void onDrawerClosed(View drawerView) { + TabType newTab = TabType.all; + for (CheckedTextView navItem : navMainItemList) { + if (navItem.isChecked()) { + switch (navItem.getId()) { + case R.id.main_nav_btn_all: + newTab = TabType.all; + break; + case R.id.main_nav_btn_good: + newTab = TabType.good; + break; + case R.id.main_nav_btn_share: + newTab = TabType.share; + break; + case R.id.main_nav_btn_ask: + newTab = TabType.ask; + break; + case R.id.main_nav_btn_job: + newTab = TabType.job; + break; + default: + newTab = TabType.all; + break; + } + break; + } + } + if (newTab != currentTab) { + currentTab = newTab; + currentPage = 0; + toolbar.setTitle(currentTab.getNameId()); + topicList.clear(); + notifyDataSetChanged(); + refreshLayout.setRefreshing(true); + onRefresh(); + fabCreateTopic.show(true); + } + } + }; private void updateUserInfoViews() { if (TextUtils.isEmpty(LoginShared.getAccessToken(this))) { - Picasso.with(this).load(R.drawable.image_placeholder).placeholder(R.drawable.image_placeholder).into(imgAvatar); + Glide.with(this).load(R.drawable.image_placeholder).placeholder(R.drawable.image_placeholder).dontAnimate().into(imgAvatar); tvLoginName.setText(R.string.click_avatar_to_login); tvScore.setText(null); btnLogout.setVisibility(View.GONE); } else { - Picasso.with(this).load(LoginShared.getAvatarUrl(this)).placeholder(R.drawable.image_placeholder).into(imgAvatar); + Glide.with(this).load(LoginShared.getAvatarUrl(this)).placeholder(R.drawable.image_placeholder).dontAnimate().into(imgAvatar); tvLoginName.setText(LoginShared.getLoginName(this)); tvScore.setText(getString(R.string.score_$) + LoginShared.getScore(this)); btnLogout.setVisibility(View.VISIBLE); @@ -222,13 +233,36 @@ private void updateUserInfoViews() { } private void getUserAsyncTask() { - if (!TextUtils.isEmpty(LoginShared.getAccessToken(this))) { - ApiClient.service.getUser(LoginShared.getLoginName(this), new CallbackAdapter>() { + final String accessToken = LoginShared.getAccessToken(this); + if (!TextUtils.isEmpty(accessToken)) { + Call> call = ApiClient.service.getUser(LoginShared.getLoginName(this)); + call.enqueue(new CallbackAdapter>() { + + @Override + public boolean onResultOk(Response> response, Result.Data result) { + if (TextUtils.equals(accessToken, LoginShared.getAccessToken(MainActivity.this))) { + LoginShared.update(MainActivity.this, result.getData()); + updateUserInfoViews(); + } + return false; + } + + }); + } + } + + private void getMessageCountAsyncTask() { + final String accessToken = LoginShared.getAccessToken(this); + if (!TextUtils.isEmpty(accessToken)) { + Call> call = ApiClient.service.getMessageCount(accessToken); + call.enqueue(new CallbackAdapter>() { @Override - public void success(Result result, Response response) { - LoginShared.update(MainActivity.this, result.getData()); - updateUserInfoViews(); + public boolean onResultOk(Response> response, Result.Data result) { + if (TextUtils.equals(accessToken, LoginShared.getAccessToken(MainActivity.this))) { + tvBadgerNotification.setText(FormatUtils.getNavigationDisplayCountText(result.getData())); + } + return false; } }); @@ -238,27 +272,42 @@ public void success(Result result, Response response) { /** * 刷新和加载逻辑 */ - @Override public void onRefresh() { final TabType tab = currentTab; - ApiClient.service.getTopicList(tab, 1, 20, true, new Callback>>() { + Call>> call = ApiClient.service.getTopicList(tab, 1, 20, true); + call.enqueue(new CallbackAdapter>>() { @Override - public void success(Result> result, Response response) { - if (currentTab == tab && result.getData() != null) { + public boolean onResultOk(Response>> response, Result.Data> result) { + if (currentTab == tab) { topicList.clear(); topicList.addAll(result.getData()); notifyDataSetChanged(); - refreshLayout.setRefreshing(false); currentPage = 1; } + return false; + } + + @Override + public boolean onResultError(Response>> response, Result.Error error) { + if (currentTab == tab) { + ToastUtils.with(MainActivity.this).show(error.getErrorMessage()); + } + return false; } @Override - public void failure(RetrofitError error) { + public boolean onCallException(Throwable t, Result.Error error) { + if (currentTab == tab) { + ToastUtils.with(MainActivity.this).show(error.getErrorMessage()); + } + return false; + } + + @Override + public void onFinish() { if (currentTab == tab) { - ToastUtils.with(MainActivity.this).show(R.string.data_load_faild); refreshLayout.setRefreshing(false); } } @@ -274,28 +323,45 @@ public void onLoadMore() { final TabType tab = currentTab; final int page = currentPage; - ApiClient.service.getTopicList(tab, page + 1, 20, true, new Callback>>() { + Call>> call = ApiClient.service.getTopicList(tab, page + 1, 20, true); + call.enqueue(new CallbackAdapter>>() { @Override - public void success(Result> result, Response response) { + public boolean onResultOk(Response>> response, Result.Data> result) { if (currentTab == tab && currentPage == page) { if (result.getData().size() > 0) { topicList.addAll(result.getData()); adapter.setLoading(false); adapter.notifyItemRangeInserted(topicList.size() - result.getData().size(), result.getData().size()); currentPage++; + return true; } else { ToastUtils.with(MainActivity.this).show(R.string.have_no_more_data); - adapter.setLoading(false); - adapter.notifyItemChanged(adapter.getItemCount() - 1); + return false; } } + return false; } @Override - public void failure(RetrofitError error) { + public boolean onResultError(Response>> response, Result.Error error) { + if (currentTab == tab && currentPage == page) { + ToastUtils.with(MainActivity.this).show(error.getErrorMessage()); + } + return false; + } + + @Override + public boolean onCallException(Throwable t, Result.Error error) { + if (currentTab == tab && currentPage == page) { + ToastUtils.with(MainActivity.this).show(error.getErrorMessage()); + } + return false; + } + + @Override + public void onFinish() { if (currentTab == tab && currentPage == page) { - ToastUtils.with(MainActivity.this).show(R.string.data_load_faild); adapter.setLoading(false); adapter.notifyItemChanged(adapter.getItemCount() - 1); } @@ -313,7 +379,7 @@ private void notifyDataSetChanged() { adapter.setLoading(false); } adapter.notifyDataSetChanged(); - layoutNoData.setVisibility(topicList.size() == 0 ? View.VISIBLE : View.GONE); + iconNoData.setVisibility(topicList.size() == 0 ? View.VISIBLE : View.GONE); } /** @@ -327,63 +393,12 @@ private void notifyDataSetChanged() { R.id.main_nav_btn_job }) public void onNavigationMainItemClick(CheckedTextView itemView) { - switch (itemView.getId()) { - case R.id.main_nav_btn_all: - drawerLayout.setDrawerListener(tabAllDrawerListener); - break; - case R.id.main_nav_btn_good: - drawerLayout.setDrawerListener(tabGoodDrawerListener); - break; - case R.id.main_nav_btn_share: - drawerLayout.setDrawerListener(tabShareDrawerListener); - break; - case R.id.main_nav_btn_ask: - drawerLayout.setDrawerListener(tabAskDrawerListener); - break; - case R.id.main_nav_btn_job: - drawerLayout.setDrawerListener(tabJobDrawerListener); - break; - default: - drawerLayout.setDrawerListener(openDrawerListener); - break; - } for (CheckedTextView navItem : navMainItemList) { navItem.setChecked(navItem.getId() == itemView.getId()); } drawerLayout.closeDrawers(); } - private class MainItemDrawerListener extends DrawerLayout.SimpleDrawerListener { - - private TabType tabType; - - protected MainItemDrawerListener(TabType tabType) { - this.tabType = tabType; - } - - @Override - public void onDrawerClosed(View drawerView) { - if (tabType != currentTab) { - currentTab = tabType; - currentPage = 0; - toolbar.setTitle(currentTab.getNameId()); - topicList.clear(); - notifyDataSetChanged(); - refreshLayout.setRefreshing(true); - onRefresh(); - fabNewTopic.show(true); - } - drawerLayout.setDrawerListener(openDrawerListener); - } - - } - - private DrawerLayout.DrawerListener tabAllDrawerListener = new MainItemDrawerListener(TabType.all); - private DrawerLayout.DrawerListener tabGoodDrawerListener = new MainItemDrawerListener(TabType.good); - private DrawerLayout.DrawerListener tabShareDrawerListener = new MainItemDrawerListener(TabType.share); - private DrawerLayout.DrawerListener tabAskDrawerListener = new MainItemDrawerListener(TabType.ask); - private DrawerLayout.DrawerListener tabJobDrawerListener = new MainItemDrawerListener(TabType.job); - /** * 次要菜单导航 */ @@ -396,69 +411,61 @@ public void onDrawerClosed(View drawerView) { public void onNavigationItemOtherClick(View itemView) { switch (itemView.getId()) { case R.id.main_nav_btn_notification: - if (TextUtils.isEmpty(LoginShared.getAccessToken(this))) { - showNeedLoginDialog(); - } else { - drawerLayout.setDrawerListener(notificationDrawerListener); - drawerLayout.closeDrawers(); + if (LoginActivity.startForResultWithAccessTokenCheck(this)) { + notificationAction.startDelayed(); } break; case R.id.main_nav_btn_setting: - drawerLayout.setDrawerListener(settingDrawerListener); - drawerLayout.closeDrawers(); + settingAction.startDelayed(); break; case R.id.main_nav_btn_about: - drawerLayout.setDrawerListener(aboutDrawerListener); - drawerLayout.closeDrawers(); - break; - default: - drawerLayout.setDrawerListener(openDrawerListener); - drawerLayout.closeDrawers(); + aboutAction.startDelayed(); break; } + drawerLayout.closeDrawers(); } - private class OtherItemDrawerListener extends DrawerLayout.SimpleDrawerListener { + private class OtherItemAction implements Runnable { private Class gotoClz; - protected OtherItemDrawerListener(Class gotoClz) { + protected OtherItemAction(Class gotoClz) { this.gotoClz = gotoClz; } @Override - public void onDrawerClosed(View drawerView) { + public void run() { startActivity(new Intent(MainActivity.this, gotoClz)); - drawerLayout.setDrawerListener(openDrawerListener); + } + + public void startDelayed() { + HandlerUtils.postDelayed(this, 400); } } - private DrawerLayout.DrawerListener notificationDrawerListener = new OtherItemDrawerListener(NotificationActivity.class); - private DrawerLayout.DrawerListener settingDrawerListener = new OtherItemDrawerListener(SettingActivity.class); - private DrawerLayout.DrawerListener aboutDrawerListener = new OtherItemDrawerListener(AboutActivity.class); + private OtherItemAction notificationAction = new OtherItemAction(NotificationActivity.class); + private OtherItemAction settingAction = new OtherItemAction(SettingActivity.class); + private OtherItemAction aboutAction = new OtherItemAction(AboutActivity.class); /** * 注销按钮 */ @OnClick(R.id.main_nav_btn_logout) protected void onBtnLogoutClick() { - new MaterialDialog.Builder(this) - .content(R.string.logout_tip) - .positiveText(R.string.logout) - .onPositive(new MaterialDialog.SingleButtonCallback() { + DialogUtils.createAlertDialogBuilder(this) + .setMessage(R.string.logout_tip) + .setPositiveButton(R.string.logout, new DialogInterface.OnClickListener() { @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + public void onClick(DialogInterface dialog, int which) { LoginShared.logout(MainActivity.this); tvBadgerNotification.setText(null); // 未读消息清空 updateUserInfoViews(); - - MobclickAgent.onProfileSignOff(); // TODO 结束友盟账号统计 } }) - .negativeText(R.string.cancel) + .setNegativeButton(R.string.cancel, null) .show(); } @@ -468,8 +475,7 @@ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) @OnClick(R.id.main_nav_btn_theme_dark) protected void onBtnThemeDarkClick() { SettingShared.setEnableThemeDark(this, !enableThemeDark); - // TODO 重启 Activity - ThemeUtils.recreateActivity(this); + ThemeUtils.recreateActivity(this); // 重启Activity } /** @@ -478,50 +484,29 @@ protected void onBtnThemeDarkClick() { @OnClick(R.id.main_nav_layout_info) protected void onBtnInfoClick() { if (TextUtils.isEmpty(LoginShared.getAccessToken(this))) { - startActivityForResult(new Intent(this, LoginActivity.class), REQUEST_LOGIN); + LoginActivity.startForResult(this); } else { - UserDetailActivity.openWithTransitionAnimation(this, LoginShared.getLoginName(this), imgAvatar, LoginShared.getAvatarUrl(this)); + UserDetailActivity.startWithTransitionAnimation(this, LoginShared.getLoginName(this), imgAvatar, LoginShared.getAvatarUrl(this)); } } /** * 发帖按钮 */ - @OnClick(R.id.main_fab_new_topic) - protected void onBtnNewTopicClick() { - if (TextUtils.isEmpty(LoginShared.getAccessToken(this))) { - showNeedLoginDialog(); - } else { - startActivity(new Intent(this, NewTopicActivity.class)); + @OnClick(R.id.main_fab_create_topic) + protected void onBtnCreateTopicClick() { + if (LoginActivity.startForResultWithAccessTokenCheck(this)) { + startActivity(new Intent(this, CreateTopicActivity.class)); } } - /** - * 显示需要登录对话框 - */ - private void showNeedLoginDialog() { - new MaterialDialog.Builder(this) - .content(R.string.need_login_tip) - .positiveText(R.string.login) - .onPositive(new MaterialDialog.SingleButtonCallback() { - - @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - startActivityForResult(new Intent(MainActivity.this, LoginActivity.class), REQUEST_LOGIN); - } - - }) - .negativeText(R.string.cancel) - .show(); - } - /** * 判断登录是否成功 */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_LOGIN && resultCode == RESULT_OK) { + if (requestCode == LoginActivity.REQUEST_LOGIN && resultCode == RESULT_OK) { updateUserInfoViews(); getUserAsyncTask(); } diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/MarkdownPreviewActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/MarkdownPreviewActivity.java similarity index 69% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/MarkdownPreviewActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/MarkdownPreviewActivity.java index 921a8ef0..7c4657b4 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/MarkdownPreviewActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/MarkdownPreviewActivity.java @@ -1,15 +1,17 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v7.widget.Toolbar; import org.cnodejs.android.md.R; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; -import org.cnodejs.android.md.ui.widget.CNodeWebView; -import org.cnodejs.android.md.ui.widget.ThemeUtils; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.widget.CNodeWebView; +import org.cnodejs.android.md.display.widget.ThemeUtils; import org.cnodejs.android.md.util.FormatUtils; import butterknife.Bind; @@ -19,10 +21,10 @@ public class MarkdownPreviewActivity extends StatusBarActivity { private static final String EXTRA_MARKDOWN = "markdown"; - public static void open(Context context, String markdown) { - Intent intent = new Intent(context, MarkdownPreviewActivity.class); + public static void start(@NonNull Activity activity, String markdown) { + Intent intent = new Intent(activity, MarkdownPreviewActivity.class); intent.putExtra(EXTRA_MARKDOWN, markdown); - context.startActivity(intent); + activity.startActivity(intent); } @Bind(R.id.markdown_preview_toolbar) diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/ModifyTopicSignActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/ModifyTopicSignActivity.java similarity index 84% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/ModifyTopicSignActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/ModifyTopicSignActivity.java index 8af4d984..bfa6e57d 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/ModifyTopicSignActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/ModifyTopicSignActivity.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; import android.os.Bundle; import android.support.v7.widget.Toolbar; @@ -6,10 +6,10 @@ import android.widget.EditText; import org.cnodejs.android.md.R; -import org.cnodejs.android.md.storage.SettingShared; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; -import org.cnodejs.android.md.ui.widget.ThemeUtils; +import org.cnodejs.android.md.model.storage.SettingShared; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.widget.ThemeUtils; import butterknife.Bind; import butterknife.ButterKnife; diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/NotificationActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/NotificationActivity.java similarity index 61% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/NotificationActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/NotificationActivity.java index 060a4e5d..732f73e9 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/NotificationActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/NotificationActivity.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; @@ -7,31 +7,27 @@ import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; - -import com.afollestad.materialdialogs.MaterialDialog; import org.cnodejs.android.md.R; +import org.cnodejs.android.md.display.adapter.NotificationAdapter; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.widget.RefreshLayoutUtils; +import org.cnodejs.android.md.display.widget.ThemeUtils; import org.cnodejs.android.md.model.api.ApiClient; +import org.cnodejs.android.md.model.api.DefaultToastCallback; import org.cnodejs.android.md.model.entity.Message; import org.cnodejs.android.md.model.entity.Notification; import org.cnodejs.android.md.model.entity.Result; -import org.cnodejs.android.md.storage.LoginShared; -import org.cnodejs.android.md.ui.adapter.NotificationAdapter; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; -import org.cnodejs.android.md.ui.widget.RefreshLayoutUtils; -import org.cnodejs.android.md.ui.widget.ThemeUtils; -import org.cnodejs.android.md.ui.widget.ToastUtils; +import org.cnodejs.android.md.model.storage.LoginShared; import java.util.ArrayList; import java.util.List; import butterknife.Bind; import butterknife.ButterKnife; -import retrofit.Callback; -import retrofit.RetrofitError; -import retrofit.client.Response; +import retrofit2.Call; +import retrofit2.Response; public class NotificationActivity extends StatusBarActivity implements Toolbar.OnMenuItemClickListener, SwipeRefreshLayout.OnRefreshListener { @@ -71,27 +67,23 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onRefresh() { - ApiClient.service.getMessages(LoginShared.getAccessToken(this), true, new Callback>() { + Call> call = ApiClient.service.getMessages(LoginShared.getAccessToken(this), true); + call.enqueue(new DefaultToastCallback>(this) { @Override - public void success(Result result, Response response) { + public boolean onResultOk(Response> response, Result.Data result) { if (!isFinishing()) { messageList.clear(); messageList.addAll(result.getData().getHasNotReadMessageList()); messageList.addAll(result.getData().getHasReadMessageList()); notifyDataSetChanged(); - refreshLayout.setRefreshing(false); } + return false; } @Override - public void failure(RetrofitError error) { + public void onFinish() { if (!isFinishing()) { - if (error.getResponse() != null && error.getResponse().getStatus() == 403) { - showAccessTokenErrorDialog(); - } else { - ToastUtils.with(NotificationActivity.this).show(R.string.data_load_faild); - } refreshLayout.setRefreshing(false); } } @@ -104,13 +96,6 @@ private void notifyDataSetChanged() { iconNoData.setVisibility(messageList.size() == 0 ? View.VISIBLE : View.GONE); } - private void showAccessTokenErrorDialog() { - new MaterialDialog.Builder(this) - .content(R.string.access_token_error_tip) - .positiveText(R.string.confirm) - .show(); - } - @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { @@ -123,27 +108,18 @@ public boolean onMenuItemClick(MenuItem item) { } private void markAllMessageReadAsyncTask() { - ApiClient.service.markAllMessageRead(LoginShared.getAccessToken(this), new Callback() { + Call call = ApiClient.service.markAllMessageRead(LoginShared.getAccessToken(this)); + call.enqueue(new DefaultToastCallback(this) { @Override - public void success(Void nothing, Response response) { + public boolean onResultOk(Response response, Result result) { if (!isFinishing()) { for (Message message : messageList) { message.setRead(true); } notifyDataSetChanged(); } - } - - @Override - public void failure(RetrofitError error) { - if (!isFinishing()) { - if (error.getResponse() != null && error.getResponse().getStatus() == 403) { - showAccessTokenErrorDialog(); - } else { - ToastUtils.with(NotificationActivity.this).show(R.string.data_load_faild); - } - } + return false; } }); diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/QRCodeActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/QRCodeActivity.java similarity index 66% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/QRCodeActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/QRCodeActivity.java index 7619f727..36f65e81 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/QRCodeActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/QRCodeActivity.java @@ -1,27 +1,35 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; +import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; import android.graphics.PointF; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v7.widget.Toolbar; import android.view.View; import android.view.animation.AnimationUtils; -import com.afollestad.materialdialogs.MaterialDialog; import com.dlazaro66.qrcodereaderview.QRCodeReaderView; import org.cnodejs.android.md.R; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.dialog.DialogUtils; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.util.FormatUtils; import butterknife.Bind; import butterknife.ButterKnife; public class QRCodeActivity extends StatusBarActivity implements QRCodeReaderView.OnQRCodeReadListener { + public static final int REQUEST_QRCODE = FormatUtils.createRequestCode(); public static final String EXTRA_QRCODE = "qrcode"; + public static void startForResult(@NonNull Activity activity) { + activity.startActivityForResult(new Intent(activity, QRCodeActivity.class), REQUEST_QRCODE); + } + @Bind(R.id.qrcode_toolbar) protected Toolbar toolbar; @@ -66,13 +74,13 @@ public void onQRCodeRead(String text, PointF[] points) { @Override public void cameraNotFound() { - new MaterialDialog.Builder(this) - .content(R.string.can_not_open_camera) - .positiveText(R.string.confirm) - .cancelListener(new DialogInterface.OnCancelListener() { + DialogUtils.createAlertDialogBuilder(this) + .setMessage(R.string.can_not_open_camera) + .setPositiveButton(R.string.confirm, null) + .setOnDismissListener(new DialogInterface.OnDismissListener() { @Override - public void onCancel(DialogInterface dialog) { + public void onDismiss(DialogInterface dialog) { setResult(RESULT_CANCELED); finish(); } @@ -82,7 +90,6 @@ public void onCancel(DialogInterface dialog) { } @Override - public void QRCodeNotFoundOnCamImage() { - } + public void QRCodeNotFoundOnCamImage() {} } diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/SettingActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/SettingActivity.java similarity index 88% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/SettingActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/SettingActivity.java index 0167ac4f..daf84bf2 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/SettingActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/SettingActivity.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; import android.content.Intent; import android.os.Bundle; @@ -7,10 +7,10 @@ import android.widget.TextView; import org.cnodejs.android.md.R; -import org.cnodejs.android.md.storage.SettingShared; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; -import org.cnodejs.android.md.ui.widget.ThemeUtils; +import org.cnodejs.android.md.model.storage.SettingShared; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.widget.ThemeUtils; import butterknife.Bind; import butterknife.ButterKnife; @@ -62,8 +62,7 @@ protected void onBtnNotificationClick() { protected void onBtnThemeDarkClick() { switchThemeDark.toggle(); SettingShared.setEnableThemeDark(this, switchThemeDark.isChecked()); - // TODO 重启 Activity - ThemeUtils.recreateActivity(this); + ThemeUtils.recreateActivity(this); // 重启Activity } @OnClick(R.id.setting_btn_new_topic_draft) diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/TopicActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/TopicActivity.java similarity index 69% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/TopicActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/TopicActivity.java index 2685b13c..3d214ebb 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/TopicActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/TopicActivity.java @@ -1,14 +1,15 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; -import android.text.TextUtils; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MenuItem; @@ -17,43 +18,49 @@ import android.widget.EditText; import android.widget.PopupWindow; -import com.afollestad.materialdialogs.MaterialDialog; import com.melnykov.fab.FloatingActionButton; import org.cnodejs.android.md.R; +import org.cnodejs.android.md.display.adapter.TopicAdapter; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.dialog.DialogUtils; +import org.cnodejs.android.md.display.dialog.ProgressDialog; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.widget.EditorBarHandler; +import org.cnodejs.android.md.display.widget.RefreshLayoutUtils; +import org.cnodejs.android.md.display.widget.ThemeUtils; +import org.cnodejs.android.md.display.widget.ToastUtils; import org.cnodejs.android.md.model.api.ApiClient; +import org.cnodejs.android.md.model.api.DefaultToastCallback; import org.cnodejs.android.md.model.entity.Author; import org.cnodejs.android.md.model.entity.Reply; import org.cnodejs.android.md.model.entity.Result; import org.cnodejs.android.md.model.entity.TopicWithReply; -import org.cnodejs.android.md.storage.LoginShared; -import org.cnodejs.android.md.storage.SettingShared; -import org.cnodejs.android.md.ui.adapter.TopicAdapter; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; -import org.cnodejs.android.md.ui.widget.EditorBarHandler; -import org.cnodejs.android.md.ui.widget.RefreshLayoutUtils; -import org.cnodejs.android.md.ui.widget.ThemeUtils; -import org.cnodejs.android.md.ui.widget.ToastUtils; +import org.cnodejs.android.md.model.storage.LoginShared; +import org.cnodejs.android.md.model.storage.SettingShared; import org.cnodejs.android.md.util.FormatUtils; import org.cnodejs.android.md.util.ShipUtils; import org.joda.time.DateTime; import java.util.ArrayList; -import java.util.Map; import butterknife.Bind; import butterknife.ButterKnife; import butterknife.OnClick; -import retrofit.Callback; -import retrofit.RetrofitError; -import retrofit.client.Response; +import retrofit2.Call; +import retrofit2.Response; public class TopicActivity extends StatusBarActivity implements SwipeRefreshLayout.OnRefreshListener, TopicAdapter.OnAtClickListener, Toolbar.OnMenuItemClickListener { private static final String EXTRA_TOPIC_ID = "topicId"; - public static void open(Context context, String topicId) { + public static void start(@NonNull Activity activity, String topicId) { + Intent intent = new Intent(activity, TopicActivity.class); + intent.putExtra(EXTRA_TOPIC_ID, topicId); + activity.startActivity(intent); + } + + public static void start(@NonNull Context context, String topicId) { Intent intent = new Intent(context, TopicActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(EXTRA_TOPIC_ID, topicId); @@ -72,16 +79,16 @@ public static void open(Context context, String topicId) { @Bind(R.id.topic_recycler_view) protected RecyclerView recyclerView; - @Bind(R.id.topic_fab_reply) - protected FloatingActionButton fabReply; - @Bind(R.id.topic_icon_no_data) protected View iconNoData; + @Bind(R.id.topic_fab_reply) + protected FloatingActionButton fabReply; + private PopupWindow replyWindow; private ReplyHandler replyHandler; - private MaterialDialog dialog; + private ProgressDialog progressDialog; private String topicId; private TopicWithReply topic; @@ -109,7 +116,7 @@ protected void onCreate(Bundle savedInstanceState) { // 创建回复窗口 LayoutInflater inflater = LayoutInflater.from(this); - View view = inflater.inflate(R.layout.activity_reply_window, layoutRoot, false); + View view = inflater.inflate(R.layout.activity_topic_reply_window, layoutRoot, false); replyHandler = new ReplyHandler(view); replyWindow = new PopupWindow(view, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); @@ -119,11 +126,9 @@ protected void onCreate(Bundle savedInstanceState) { replyWindow.setAnimationStyle(R.style.AppWidget_ReplyWindowAnim); // - END - - dialog = new MaterialDialog.Builder(this) - .content(R.string.posting_$_) - .progress(true, 0) - .cancelable(false) - .build(); + progressDialog = DialogUtils.createProgressDialog(this); + progressDialog.setMessage(R.string.posting_$_); + progressDialog.setCancelable(false); RefreshLayoutUtils.initOnCreate(refreshLayout, this); RefreshLayoutUtils.refreshOnCreate(refreshLayout, this); @@ -142,27 +147,23 @@ public boolean onMenuItemClick(MenuItem item) { @Override public void onRefresh() { - ApiClient.service.getTopic(topicId, true, new Callback>() { + Call> call = ApiClient.service.getTopic(topicId, LoginShared.getAccessToken(this), true); + call.enqueue(new DefaultToastCallback>(this) { @Override - public void success(Result result, Response response) { + public boolean onResultOk(Response> response, Result.Data result) { if (!isFinishing()) { topic = result.getData(); adapter.setTopic(result.getData()); adapter.notifyDataSetChanged(); iconNoData.setVisibility(View.GONE); - refreshLayout.setRefreshing(false); } + return false; } @Override - public void failure(RetrofitError error) { + public void onFinish() { if (!isFinishing()) { - if (error.getResponse() != null && error.getResponse().getStatus() == 404) { - ToastUtils.with(TopicActivity.this).show(R.string.topic_not_found); - } else { - ToastUtils.with(TopicActivity.this).show(R.string.data_load_faild); - } refreshLayout.setRefreshing(false); } } @@ -178,12 +179,17 @@ public void onAt(String loginName) { @OnClick(R.id.topic_fab_reply) protected void onBtnReplyClick() { - if (topic != null) { - if (TextUtils.isEmpty(LoginShared.getAccessToken(this))) { - adapter.showNeedLoginDialog(); - } else { - replyWindow.showAtLocation(layoutRoot, Gravity.BOTTOM, 0, 0); - } + if (topic != null && LoginActivity.startForResultWithAccessTokenCheck(this)) { + replyWindow.showAtLocation(layoutRoot, Gravity.BOTTOM, 0, 0); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == LoginActivity.REQUEST_LOGIN && resultCode == RESULT_OK) { + refreshLayout.setRefreshing(true); + onRefresh(); } } @@ -196,7 +202,7 @@ protected class ReplyHandler { @Bind(R.id.editor_bar_layout_root) protected ViewGroup editorBar; - @Bind(R.id.reply_window_edt_content) + @Bind(R.id.topic_reply_window_edt_content) protected EditText edtContent; protected ReplyHandler(View view) { @@ -204,18 +210,12 @@ protected ReplyHandler(View view) { new EditorBarHandler(TopicActivity.this, editorBar, edtContent); // 创建editorBar } - /** - * 关闭 - */ - @OnClick(R.id.reply_window_btn_tool_close) + @OnClick(R.id.topic_reply_window_btn_tool_close) protected void onBtnToolCloseClick() { replyWindow.dismiss(); } - /** - * 发送 - */ - @OnClick(R.id.reply_window_btn_tool_send) + @OnClick(R.id.topic_reply_window_btn_tool_send) protected void onBtnToolSendClick() { if (edtContent.length() == 0) { ToastUtils.with(TopicActivity.this).show(R.string.content_empty_error_tip); @@ -229,21 +229,21 @@ protected void onBtnToolSendClick() { } private void replyTopicAsyncTask(final String content) { - dialog.show(); - ApiClient.service.replyTopic(LoginShared.getAccessToken(TopicActivity.this), topicId, content, null, new Callback>() { + progressDialog.show(); + Call call = ApiClient.service.replyTopic(topicId, LoginShared.getAccessToken(TopicActivity.this), content, null); + call.enqueue(new DefaultToastCallback(TopicActivity.this) { @Override - public void success(Map result, Response response) { - dialog.dismiss(); + public boolean onResultOk(Response response, Result.ReplyTopic result) { // 本地创建一个回复对象 Reply reply = new Reply(); - reply.setId(result.get("reply_id")); + reply.setId(result.getReplyId()); Author author = new Author(); author.setLoginName(LoginShared.getLoginName(TopicActivity.this)); author.setAvatarUrl(LoginShared.getAvatarUrl(TopicActivity.this)); reply.setAuthor(author); reply.setContent(content); - reply.setHandleContent(FormatUtils.renderMarkdown(content)); // TODO 本地要做预渲染处理 + reply.setHandleContent(FormatUtils.renderMarkdown(content)); // 本地要做预渲染处理 reply.setCreateAt(new DateTime()); reply.setUpList(new ArrayList()); topic.getReplyList().add(reply); @@ -260,16 +260,13 @@ public void success(Map result, Response response) { edtContent.setText(null); // 提示 ToastUtils.with(TopicActivity.this).show(R.string.post_success); + // 继续执行onFinish() + return false; } @Override - public void failure(RetrofitError error) { - dialog.dismiss(); - if (error.getResponse() != null && error.getResponse().getStatus() == 403) { - adapter.showAccessTokenErrorDialog(); - } else { - ToastUtils.with(TopicActivity.this).show(R.string.network_faild); - } + public void onFinish() { + progressDialog.dismiss(); } }); diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/UserDetailActivity.java b/app/src/main/java/org/cnodejs/android/md/display/activity/UserDetailActivity.java similarity index 59% rename from app/src/main/java/org/cnodejs/android/md/ui/activity/UserDetailActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/activity/UserDetailActivity.java index 5edaaa94..8d564240 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/UserDetailActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/activity/UserDetailActivity.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.activity; +package org.cnodejs.android.md.display.activity; import android.app.Activity; import android.content.Context; @@ -8,9 +8,6 @@ import android.support.design.widget.TabLayout; import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityOptionsCompat; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.support.v7.widget.Toolbar; @@ -20,48 +17,56 @@ import android.widget.ImageView; import android.widget.TextView; +import com.bumptech.glide.Glide; import com.pnikosis.materialishprogress.ProgressWheel; -import com.squareup.picasso.Picasso; import org.cnodejs.android.md.R; +import org.cnodejs.android.md.display.adapter.UserDetailAdapter; +import org.cnodejs.android.md.display.base.StatusBarActivity; +import org.cnodejs.android.md.display.listener.NavigationFinishClickListener; +import org.cnodejs.android.md.display.widget.ThemeUtils; +import org.cnodejs.android.md.display.widget.ToastUtils; import org.cnodejs.android.md.model.api.ApiClient; +import org.cnodejs.android.md.model.api.CallbackAdapter; +import org.cnodejs.android.md.model.api.DefaultToastCallback; import org.cnodejs.android.md.model.entity.Result; +import org.cnodejs.android.md.model.entity.Topic; import org.cnodejs.android.md.model.entity.User; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.fragment.UserDetailItemFragment; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; -import org.cnodejs.android.md.ui.widget.ThemeUtils; -import org.cnodejs.android.md.ui.widget.ToastUtils; import org.cnodejs.android.md.util.HandlerUtils; import org.cnodejs.android.md.util.ShipUtils; -import java.util.ArrayList; import java.util.List; import butterknife.Bind; import butterknife.ButterKnife; import butterknife.OnClick; -import retrofit.Callback; -import retrofit.RetrofitError; -import retrofit.client.Response; +import retrofit2.Call; +import retrofit2.Response; public class UserDetailActivity extends StatusBarActivity { + private static final String EXTRA_LOGIN_NAME = "loginName"; + private static final String EXTRA_AVATAR_URL = "avatarUrl"; private static final String NAME_IMG_AVATAR = "imgAvatar"; - public static void openWithTransitionAnimation(Activity activity, String loginName, ImageView imgAvatar, String avatarUrl) { + public static void startWithTransitionAnimation(@NonNull Activity activity, String loginName, @NonNull ImageView imgAvatar, String avatarUrl) { Intent intent = new Intent(activity, UserDetailActivity.class); - intent.putExtra("loginName", loginName); - intent.putExtra("avatarUrl", avatarUrl); - + intent.putExtra(EXTRA_LOGIN_NAME, loginName); + intent.putExtra(EXTRA_AVATAR_URL, avatarUrl); ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, imgAvatar, NAME_IMG_AVATAR); ActivityCompat.startActivity(activity, intent, options.toBundle()); } - public static void open(Context context, String loginName) { + public static void start(@NonNull Activity activity, String loginName) { + Intent intent = new Intent(activity, UserDetailActivity.class); + intent.putExtra(EXTRA_LOGIN_NAME, loginName); + activity.startActivity(intent); + } + + public static void start(@NonNull Context context, String loginName) { Intent intent = new Intent(context, UserDetailActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra("loginName", loginName); + intent.putExtra(EXTRA_LOGIN_NAME, loginName); context.startActivity(intent); } @@ -92,7 +97,7 @@ public static void open(Context context, String loginName) { @Bind(R.id.user_detail_progress_wheel) protected ProgressWheel progressWheel; - private ViewPagerAdapter adapter; + private UserDetailAdapter adapter; private String loginName; private String githubUsername; @@ -111,36 +116,64 @@ protected void onCreate(Bundle savedInstanceState) { toolbar.setNavigationOnClickListener(new NavigationFinishClickListener(this)); - adapter = new ViewPagerAdapter(getSupportFragmentManager()); + adapter = new UserDetailAdapter(getSupportFragmentManager()); viewPager.setAdapter(adapter); viewPager.setOffscreenPageLimit(adapter.getCount()); tabLayout.setupWithViewPager(viewPager); - loginName = getIntent().getStringExtra("loginName"); - if (!TextUtils.isEmpty(loginName)) { - tvLoginName.setText(loginName); - } + loginName = getIntent().getStringExtra(EXTRA_LOGIN_NAME); + tvLoginName.setText(loginName); - String avatarUrl = getIntent().getStringExtra("avatarUrl"); + String avatarUrl = getIntent().getStringExtra(EXTRA_AVATAR_URL); if (!TextUtils.isEmpty(avatarUrl)) { - Picasso.with(this).load(avatarUrl).placeholder(R.drawable.image_placeholder).into(imgAvatar); + Glide.with(this).load(avatarUrl).placeholder(R.drawable.image_placeholder).dontAnimate().into(imgAvatar); } getUserAsyncTask(); + getCollectTopicListAsyncTask(); } + /** + * Activity被销毁的时候不保存Fragment + */ + @Override + protected void onSaveInstanceState(Bundle outState) {} + @OnClick(R.id.user_detail_img_avatar) protected void onBtnAvatarClick() { if (!loading) { getUserAsyncTask(); + getCollectTopicListAsyncTask(); + } + } + + @OnClick(R.id.user_detail_tv_github_username) + protected void onBtnGithubUsernameClick() { + if (!TextUtils.isEmpty(githubUsername)) { + ShipUtils.openInBrowser(this, "https://github.com/" + githubUsername); + } + } + + private void updateUserInfoViews(User user) { + Glide.with(this).load(user.getAvatarUrl()).placeholder(R.drawable.image_placeholder).dontAnimate().into(imgAvatar); + tvLoginName.setText(user.getLoginName()); + if (TextUtils.isEmpty(user.getGithubUsername())) { + tvGithubUsername.setVisibility(View.INVISIBLE); + tvGithubUsername.setText(null); + } else { + tvGithubUsername.setVisibility(View.VISIBLE); + tvGithubUsername.setText(Html.fromHtml("" + user.getGithubUsername() + "@github.com" + "")); } + tvCreateTime.setText(getString(R.string.register_time_$) + user.getCreateAt().toString("yyyy-MM-dd")); + tvScore.setText(getString(R.string.score_$) + user.getScore()); } private void getUserAsyncTask() { loading = true; startLoadingTime = System.currentTimeMillis(); progressWheel.spin(); - ApiClient.service.getUser(loginName, new Callback>() { + Call> call = ApiClient.service.getUser(loginName); + call.enqueue(new CallbackAdapter>() { private long getPostTime() { long postTime = 1000 - (System.currentTimeMillis() - startLoadingTime); @@ -152,7 +185,7 @@ private long getPostTime() { } @Override - public void success(final Result result, Response response) { + public boolean onResultOk(Response> response, final Result.Data result) { HandlerUtils.postDelayed(new Runnable() { @Override @@ -161,92 +194,70 @@ public void run() { updateUserInfoViews(result.getData()); adapter.update(result.getData()); githubUsername = result.getData().getGithubUsername(); - progressWheel.setProgress(0); - loading = false; + onFinish(); } } }, getPostTime()); + return true; } @Override - public void failure(final RetrofitError error) { + public boolean onResultError(final Response> response, final Result.Error error) { HandlerUtils.postDelayed(new Runnable() { @Override public void run() { if (!isFinishing()) { - if (error.getResponse() != null && error.getResponse().getStatus() == 404) { - ToastUtils.with(UserDetailActivity.this).show(R.string.user_not_found); + if (response.code() == 404) { + ToastUtils.with(UserDetailActivity.this).show(error.getErrorMessage()); } else { ToastUtils.with(UserDetailActivity.this).show(R.string.data_load_faild_and_click_avatar_to_reload); } - progressWheel.setProgress(0); - loading = false; + onFinish(); } } }, getPostTime()); + return true; } - }); - } - - private void updateUserInfoViews(User user) { - Picasso.with(this).load(user.getAvatarUrl()).placeholder(R.drawable.image_placeholder).into(imgAvatar); - tvLoginName.setText(user.getLoginName()); - if (TextUtils.isEmpty(user.getGithubUsername())) { - tvGithubUsername.setVisibility(View.INVISIBLE); - tvGithubUsername.setText(null); - } else { - tvGithubUsername.setVisibility(View.VISIBLE); - tvGithubUsername.setText(Html.fromHtml("" + user.getGithubUsername() + "@github.com" + "")); - } - tvCreateTime.setText(getString(R.string.register_time_$) + user.getCreateAt().toString("yyyy-MM-dd")); - tvScore.setText(getString(R.string.score_$) + user.getScore()); - } - - private class ViewPagerAdapter extends FragmentPagerAdapter { - - private List fmList = new ArrayList<>(); - private String[] titles = { - "最近回复", - "最新发布" - }; + @Override + public boolean onCallException(Throwable t, Result.Error error) { + HandlerUtils.postDelayed(new Runnable() { - public ViewPagerAdapter(FragmentManager manager) { - super(manager); - fmList.add(new UserDetailItemFragment()); - fmList.add(new UserDetailItemFragment()); - } + @Override + public void run() { + if (!isFinishing()) { + ToastUtils.with(UserDetailActivity.this).show(R.string.data_load_faild_and_click_avatar_to_reload); + onFinish(); + } + } - public void update(@NonNull User user) { - fmList.get(0).notifyDataSetChanged(user.getRecentReplyList()); - fmList.get(1).notifyDataSetChanged(user.getRecentTopicList()); - } + }, getPostTime()); + return true; + } - @Override - public Fragment getItem(int position) { - return fmList.get(position); - } + @Override + public void onFinish() { + progressWheel.stopSpinning(); + loading = false; + } - @Override - public int getCount() { - return fmList.size(); - } + }); + } - @Override - public CharSequence getPageTitle(int position) { - return titles[position]; - } + private void getCollectTopicListAsyncTask() { + Call>> call = ApiClient.service.getCollectTopicList(loginName); + call.enqueue(new DefaultToastCallback>>(this) { - } + @Override + public boolean onResultOk(Response>> response, Result.Data> result) { + adapter.update(result.getData()); + return false; + } - @OnClick(R.id.user_detail_tv_github_username) - protected void onBtnGithubUsernameClick() { - if (!TextUtils.isEmpty(githubUsername)) { - ShipUtils.openInBrowser(this, "https://github.com/" + githubUsername); - } + }); } } diff --git a/app/src/main/java/org/cnodejs/android/md/ui/adapter/MainAdapter.java b/app/src/main/java/org/cnodejs/android/md/display/adapter/MainAdapter.java similarity index 85% rename from app/src/main/java/org/cnodejs/android/md/ui/adapter/MainAdapter.java rename to app/src/main/java/org/cnodejs/android/md/display/adapter/MainAdapter.java index 381b0ed1..9bb319bb 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/adapter/MainAdapter.java +++ b/app/src/main/java/org/cnodejs/android/md/display/adapter/MainAdapter.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.adapter; +package org.cnodejs.android.md.display.adapter; import android.app.Activity; import android.graphics.Color; @@ -10,13 +10,13 @@ import android.widget.ImageView; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; import org.cnodejs.android.md.R; import org.cnodejs.android.md.model.entity.Topic; -import org.cnodejs.android.md.ui.activity.TopicActivity; -import org.cnodejs.android.md.ui.activity.UserDetailActivity; -import org.cnodejs.android.md.ui.widget.ThemeUtils; +import org.cnodejs.android.md.display.activity.TopicActivity; +import org.cnodejs.android.md.display.activity.UserDetailActivity; +import org.cnodejs.android.md.display.widget.ThemeUtils; import org.cnodejs.android.md.util.FormatUtils; import java.util.List; @@ -30,13 +30,13 @@ public class MainAdapter extends RecyclerView.Adapter { private static final int TYPE_NORMAL = 0; private static final int TYPE_LOAD_MORE = 1; - private Activity activity; - private LayoutInflater inflater; - private List topicList; + private final Activity activity; + private final LayoutInflater inflater; + private final List topicList; private boolean loading = false; - public MainAdapter(Activity activity, @NonNull List topicList) { + public MainAdapter(@NonNull Activity activity, @NonNull List topicList) { this.activity = activity; inflater = LayoutInflater.from(activity); this.topicList = topicList; @@ -154,7 +154,7 @@ protected void update(int position) { tvTab.setText(topic.isTop() ? R.string.tab_top : topic.getTab().getNameId()); tvTab.setBackgroundDrawable(ThemeUtils.getThemeAttrDrawable(activity, topic.isTop() ? R.attr.referenceBackgroundAccent : R.attr.referenceBackgroundNormal)); tvTab.setTextColor(topic.isTop() ? Color.WHITE : ThemeUtils.getThemeAttrColor(activity, android.R.attr.textColorSecondary)); - Picasso.with(activity).load(topic.getAuthor().getAvatarUrl()).placeholder(R.drawable.image_placeholder).into(imgAvatar); + Glide.with(activity).load(topic.getAuthor().getAvatarUrl()).placeholder(R.drawable.image_placeholder).dontAnimate().into(imgAvatar); tvAuthor.setText(topic.getAuthor().getLoginName()); tvCreateTime.setText(activity.getString(R.string.create_at_$) + topic.getCreateAt().toString("yyyy-MM-dd HH:mm:ss")); tvReplyCount.setText(String.valueOf(topic.getReplyCount())); @@ -165,12 +165,12 @@ protected void update(int position) { @OnClick(R.id.main_item_img_avatar) protected void onBtnAvatarClick() { - UserDetailActivity.openWithTransitionAnimation(activity, topic.getAuthor().getLoginName(), imgAvatar, topic.getAuthor().getAvatarUrl()); + UserDetailActivity.startWithTransitionAnimation(activity, topic.getAuthor().getLoginName(), imgAvatar, topic.getAuthor().getAvatarUrl()); } @OnClick(R.id.main_item_btn_item) protected void onBtnItemClick() { - TopicActivity.open(activity, topic.getId()); + TopicActivity.start(activity, topic.getId()); } } diff --git a/app/src/main/java/org/cnodejs/android/md/ui/adapter/NotificationAdapter.java b/app/src/main/java/org/cnodejs/android/md/display/adapter/NotificationAdapter.java similarity index 80% rename from app/src/main/java/org/cnodejs/android/md/ui/adapter/NotificationAdapter.java rename to app/src/main/java/org/cnodejs/android/md/display/adapter/NotificationAdapter.java index f5b206e2..0d0105f5 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/adapter/NotificationAdapter.java +++ b/app/src/main/java/org/cnodejs/android/md/display/adapter/NotificationAdapter.java @@ -1,23 +1,22 @@ -package org.cnodejs.android.md.ui.adapter; +package org.cnodejs.android.md.display.adapter; import android.app.Activity; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; import org.cnodejs.android.md.R; import org.cnodejs.android.md.model.entity.Message; -import org.cnodejs.android.md.ui.activity.TopicActivity; -import org.cnodejs.android.md.ui.activity.UserDetailActivity; -import org.cnodejs.android.md.ui.widget.CNodeWebView; -import org.cnodejs.android.md.ui.widget.ThemeUtils; +import org.cnodejs.android.md.display.activity.TopicActivity; +import org.cnodejs.android.md.display.activity.UserDetailActivity; +import org.cnodejs.android.md.display.widget.CNodeWebView; +import org.cnodejs.android.md.display.widget.ThemeUtils; import org.cnodejs.android.md.util.FormatUtils; import java.util.List; @@ -28,11 +27,11 @@ public class NotificationAdapter extends RecyclerView.Adapter { - private Activity activity; - private LayoutInflater inflater; - private List messageList; + private final Activity activity; + private final LayoutInflater inflater; + private final List messageList; - public NotificationAdapter(Activity activity, @NonNull List messageList) { + public NotificationAdapter(@NonNull Activity activity, @NonNull List messageList) { this.activity = activity; inflater = LayoutInflater.from(activity); this.messageList = messageList; @@ -83,9 +82,9 @@ protected ViewHolder(View itemView) { protected void update(int position) { message = messageList.get(position); - Picasso.with(activity).load(message.getAuthor().getAvatarUrl()).placeholder(R.drawable.image_placeholder).into(imgAvatar); + Glide.with(activity).load(message.getAuthor().getAvatarUrl()).placeholder(R.drawable.image_placeholder).dontAnimate().into(imgAvatar); tvFrom.setText(message.getAuthor().getLoginName()); - tvTime.setText(FormatUtils.getRecentlyTimeText(message.getReply().getCreateAt())); + tvTime.setText(FormatUtils.getRecentlyTimeText(message.getCreateAt())); tvTopicTitle.setText("话题:" + message.getTopic().getTitle()); // 判断通知类型 @@ -96,12 +95,12 @@ protected void update(int position) { } else { tvAction.setText("在回复中@了您"); webReplyContent.setVisibility(View.VISIBLE); - webReplyContent.loadRenderedContent(message.getReply().getHandleContent()); // TODO 这里直接使用WebView,有性能问题 + webReplyContent.loadRenderedContent(message.getReply().getHandleContent()); // 这里直接使用WebView,有性能问题 } } else { tvAction.setText("回复了您的话题"); webReplyContent.setVisibility(View.VISIBLE); - webReplyContent.loadRenderedContent(message.getReply().getHandleContent()); // TODO 这里直接使用WebView,有性能问题 + webReplyContent.loadRenderedContent(message.getReply().getHandleContent()); // 这里直接使用WebView,有性能问题 } // 消息状态 @@ -126,12 +125,12 @@ protected void update(int position) { @OnClick(R.id.notification_item_img_avatar) protected void onBtnAvatarClick() { - UserDetailActivity.openWithTransitionAnimation(activity, message.getAuthor().getLoginName(), imgAvatar, message.getAuthor().getAvatarUrl()); + UserDetailActivity.startWithTransitionAnimation(activity, message.getAuthor().getLoginName(), imgAvatar, message.getAuthor().getAvatarUrl()); } @OnClick(R.id.notification_item_btn_item) protected void onBtnItemClick() { - TopicActivity.open(activity, message.getTopic().getId()); + TopicActivity.start(activity, message.getTopic().getId()); } } diff --git a/app/src/main/java/org/cnodejs/android/md/ui/adapter/TopicAdapter.java b/app/src/main/java/org/cnodejs/android/md/display/adapter/TopicAdapter.java similarity index 57% rename from app/src/main/java/org/cnodejs/android/md/ui/adapter/TopicAdapter.java rename to app/src/main/java/org/cnodejs/android/md/display/adapter/TopicAdapter.java index 045a0b85..1310d8a8 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/adapter/TopicAdapter.java +++ b/app/src/main/java/org/cnodejs/android/md/display/adapter/TopicAdapter.java @@ -1,51 +1,48 @@ -package org.cnodejs.android.md.ui.adapter; +package org.cnodejs.android.md.display.adapter; import android.app.Activity; -import android.content.Intent; import android.graphics.Color; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import com.afollestad.materialdialogs.DialogAction; -import com.afollestad.materialdialogs.MaterialDialog; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; import org.cnodejs.android.md.R; +import org.cnodejs.android.md.display.activity.LoginActivity; +import org.cnodejs.android.md.display.activity.UserDetailActivity; +import org.cnodejs.android.md.display.widget.CNodeWebView; +import org.cnodejs.android.md.display.widget.ThemeUtils; +import org.cnodejs.android.md.display.widget.ToastUtils; import org.cnodejs.android.md.model.api.ApiClient; +import org.cnodejs.android.md.model.api.DefaultToastCallback; import org.cnodejs.android.md.model.entity.Reply; -import org.cnodejs.android.md.model.entity.TopicUpInfo; +import org.cnodejs.android.md.model.entity.Result; +import org.cnodejs.android.md.model.entity.Topic; import org.cnodejs.android.md.model.entity.TopicWithReply; -import org.cnodejs.android.md.storage.LoginShared; -import org.cnodejs.android.md.ui.activity.LoginActivity; -import org.cnodejs.android.md.ui.activity.UserDetailActivity; -import org.cnodejs.android.md.ui.widget.CNodeWebView; -import org.cnodejs.android.md.ui.widget.ThemeUtils; -import org.cnodejs.android.md.ui.widget.ToastUtils; +import org.cnodejs.android.md.model.storage.LoginShared; import org.cnodejs.android.md.util.FormatUtils; import butterknife.Bind; import butterknife.ButterKnife; import butterknife.OnClick; -import retrofit.Callback; -import retrofit.RetrofitError; -import retrofit.client.Response; +import retrofit2.Call; +import retrofit2.Response; public class TopicAdapter extends RecyclerView.Adapter { private static final int TYPE_HEADER = 0; private static final int TYPE_REPLY = 1; - private Activity activity; - private LayoutInflater inflater; + private final Activity activity; + private final LayoutInflater inflater; private TopicWithReply topic; - private boolean isHeaderShow = false; // TODO 当false时,渲染header,其他时间不渲染 + private boolean isHeaderShow = false; // 当false时,渲染header,其他时间不渲染 public interface OnAtClickListener { @@ -53,9 +50,9 @@ public interface OnAtClickListener { } - private OnAtClickListener onAtClickListener; + private final OnAtClickListener onAtClickListener; - public TopicAdapter(Activity activity, @NonNull OnAtClickListener onAtClickListener) { + public TopicAdapter(@NonNull Activity activity, @NonNull OnAtClickListener onAtClickListener) { this.activity = activity; inflater = LayoutInflater.from(activity); this.onAtClickListener = onAtClickListener; @@ -117,27 +114,30 @@ public class HeaderViewHolder extends ViewHolder { @Bind(R.id.topic_item_header_tv_title) protected TextView tvTitle; - @Bind(R.id.topic_item_header_tv_tab) - protected TextView tvTab; - - @Bind(R.id.topic_item_header_tv_visit_count) - protected TextView tvVisitCount; + @Bind(R.id.topic_item_header_icon_good) + protected View iconGood; @Bind(R.id.topic_item_header_img_avatar) protected ImageView imgAvatar; + @Bind(R.id.topic_item_header_tv_tab) + protected TextView tvTab; + @Bind(R.id.topic_item_header_tv_login_name) protected TextView tvLoginName; @Bind(R.id.topic_item_header_tv_create_time) protected TextView tvCreateTime; + @Bind(R.id.topic_item_header_tv_visit_count) + protected TextView tvVisitCount; + + @Bind(R.id.topic_item_header_btn_favorite) + protected ImageView btnFavorite; + @Bind(R.id.topic_item_header_web_content) protected CNodeWebView webContent; - @Bind(R.id.topic_item_header_icon_good) - protected View iconGood; - @Bind(R.id.topic_item_header_layout_no_reply) protected ViewGroup layoutNoReply; @@ -149,16 +149,17 @@ public HeaderViewHolder(View itemView) { public void update(int position) { if (!isHeaderShow) { tvTitle.setText(topic.getTitle()); + iconGood.setVisibility(topic.isGood() ? View.VISIBLE : View.GONE); + Glide.with(activity).load(topic.getAuthor().getAvatarUrl()).placeholder(R.drawable.image_placeholder).dontAnimate().into(imgAvatar); tvTab.setText(topic.isTop() ? R.string.tab_top : topic.getTab().getNameId()); tvTab.setBackgroundDrawable(ThemeUtils.getThemeAttrDrawable(activity, topic.isTop() ? R.attr.referenceBackgroundAccent : R.attr.referenceBackgroundNormal)); tvTab.setTextColor(topic.isTop() ? Color.WHITE : ThemeUtils.getThemeAttrColor(activity, android.R.attr.textColorSecondary)); - tvVisitCount.setText(topic.getVisitCount() + "次浏览"); - Picasso.with(activity).load(topic.getAuthor().getAvatarUrl()).placeholder(R.drawable.image_placeholder).into(imgAvatar); tvLoginName.setText(topic.getAuthor().getLoginName()); - tvCreateTime.setText(activity.getString(R.string.post_at_$) + FormatUtils.getRecentlyTimeText(topic.getCreateAt())); - iconGood.setVisibility(topic.isGood() ? View.VISIBLE : View.GONE); + tvCreateTime.setText(FormatUtils.getRecentlyTimeText(topic.getCreateAt()) + "创建"); + tvVisitCount.setText(topic.getVisitCount() + "次浏览"); + btnFavorite.setImageResource(topic.isCollect() ? R.drawable.ic_favorite_theme_24dp : R.drawable.ic_favorite_outline_grey600_24dp); - // TODO 这里直接使用WebView,有性能问题 + // 这里直接使用WebView,有性能问题 webContent.loadRenderedContent(topic.getHandleContent()); isHeaderShow = true; @@ -169,11 +170,56 @@ public void update(int position) { @OnClick(R.id.topic_item_header_img_avatar) protected void onBtnAvatarClick() { - UserDetailActivity.openWithTransitionAnimation(activity, topic.getAuthor().getLoginName(), imgAvatar, topic.getAuthor().getAvatarUrl()); + UserDetailActivity.startWithTransitionAnimation(activity, topic.getAuthor().getLoginName(), imgAvatar, topic.getAuthor().getAvatarUrl()); + } + + @OnClick(R.id.topic_item_header_btn_favorite) + protected void onBtnFavoriteClick() { + if (topic != null) { + if (LoginActivity.startForResultWithAccessTokenCheck(activity)) { + if (topic.isCollect()) { + decollectTopicAsyncTask(topic, btnFavorite); + } else { + collectTopicAsyncTask(topic, btnFavorite); + } + } + } } } + private void collectTopicAsyncTask(@NonNull final TopicWithReply topic, @NonNull final ImageView btnFavorite) { + Call call = ApiClient.service.collectTopic(LoginShared.getAccessToken(activity), topic.getId()); + call.enqueue(new DefaultToastCallback(activity) { + + @Override + public boolean onResultOk(Response response, Result result) { + if (!activity.isFinishing()) { + topic.setCollect(true); + btnFavorite.setImageResource(R.drawable.ic_favorite_theme_24dp); + } + return false; + } + + }); + } + + private void decollectTopicAsyncTask(@NonNull final TopicWithReply topic, @NonNull final ImageView btnFavorite) { + Call call = ApiClient.service.decollectTopic(LoginShared.getAccessToken(activity), topic.getId()); + call.enqueue(new DefaultToastCallback(activity) { + + @Override + public boolean onResultOk(Response response, Result result) { + if (!activity.isFinishing()) { + topic.setCollect(false); + btnFavorite.setImageResource(R.drawable.ic_favorite_outline_grey600_24dp); + } + return false; + } + + }); + } + public class ReplyViewHolder extends ViewHolder { @Bind(R.id.topic_item_reply_img_avatar) @@ -212,7 +258,7 @@ public void update(int position) { this.position = position; reply = topic.getReplyList().get(position); - Picasso.with(activity).load(reply.getAuthor().getAvatarUrl()).placeholder(R.drawable.image_placeholder).into(imgAvatar); + Glide.with(activity).load(reply.getAuthor().getAvatarUrl()).placeholder(R.drawable.image_placeholder).dontAnimate().into(imgAvatar); tvLoginName.setText(reply.getAuthor().getLoginName()); tvIndex.setText(position + 1 + "楼"); tvCreateTime.setText(FormatUtils.getRecentlyTimeText(reply.getCreateAt())); @@ -221,86 +267,56 @@ public void update(int position) { iconDeepLine.setVisibility(position == topic.getReplyList().size() - 1 ? View.GONE : View.VISIBLE); iconShadowGap.setVisibility(position == topic.getReplyList().size() - 1 ? View.VISIBLE : View.GONE); - // TODO 这里直接使用WebView,有性能问题 + // 这里直接使用WebView,有性能问题 webContent.loadRenderedContent(reply.getHandleContent()); } @OnClick(R.id.topic_item_reply_img_avatar) protected void onBtnAvatarClick() { - UserDetailActivity.openWithTransitionAnimation(activity, reply.getAuthor().getLoginName(), imgAvatar, reply.getAuthor().getAvatarUrl()); + UserDetailActivity.startWithTransitionAnimation(activity, reply.getAuthor().getLoginName(), imgAvatar, reply.getAuthor().getAvatarUrl()); } @OnClick(R.id.topic_item_reply_btn_ups) protected void onBtnUpsClick() { - if (TextUtils.isEmpty(LoginShared.getAccessToken(activity))) { - showNeedLoginDialog(); - } else if (reply.getAuthor().getLoginName().equals(LoginShared.getLoginName(activity))) { - ToastUtils.with(activity).show("不能帮自己点赞"); - } else { - upTopicAsyncTask(this); + if (LoginActivity.startForResultWithAccessTokenCheck(activity)) { + if (reply.getAuthor().getLoginName().equals(LoginShared.getLoginName(activity))) { + ToastUtils.with(activity).show(R.string.can_not_up_yourself_reply); + } else { + upReplyAsyncTask(this); + } } } @OnClick(R.id.topic_item_reply_btn_at) protected void onBtnAtClick() { - if (TextUtils.isEmpty(LoginShared.getAccessToken(activity))) { - showNeedLoginDialog(); - } else { + if (LoginActivity.startForResultWithAccessTokenCheck(activity)) { onAtClickListener.onAt(reply.getAuthor().getLoginName()); } } } - public void showNeedLoginDialog() { - new MaterialDialog.Builder(activity) - .content(R.string.need_login_tip) - .positiveText(R.string.login) - .onPositive(new MaterialDialog.SingleButtonCallback() { - - @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - activity.startActivity(new Intent(activity, LoginActivity.class)); - } - - }) - .negativeText(R.string.cancel) - .show(); - } - - public void showAccessTokenErrorDialog() { - new MaterialDialog.Builder(activity) - .content(R.string.access_token_error_tip) - .positiveText(R.string.confirm) - .show(); - } - - private void upTopicAsyncTask(final ReplyViewHolder holder) { + private void upReplyAsyncTask(final ReplyViewHolder holder) { final int position = holder.position; // 标记当时的位置信息 final Reply reply = holder.reply; // 保存当时的回复对象 - ApiClient.service.upTopic(LoginShared.getAccessToken(activity), holder.reply.getId(), new Callback() { - - @Override - public void success(TopicUpInfo info, Response response) { - if (info.getAction() == TopicUpInfo.Action.up) { - reply.getUpList().add(LoginShared.getId(activity)); - } else if (info.getAction() == TopicUpInfo.Action.down) { - reply.getUpList().remove(LoginShared.getId(activity)); - } - // 如果位置没有变,则更新界面 - if (position == holder.position) { - holder.btnUps.setText(String.valueOf(holder.reply.getUpList().size())); - holder.btnUps.setCompoundDrawablesWithIntrinsicBounds(holder.reply.getUpList().contains(LoginShared.getId(activity)) ? R.drawable.ic_thumb_up_theme_24dp : R.drawable.ic_thumb_up_grey600_24dp, 0, 0, 0); - } - } + Call call = ApiClient.service.upReply(holder.reply.getId(), LoginShared.getAccessToken(activity)); + call.enqueue(new DefaultToastCallback(activity) { @Override - public void failure(RetrofitError error) { - if (error.getResponse() != null && error.getResponse().getStatus() == 403) { - showAccessTokenErrorDialog(); - } else { - ToastUtils.with(activity).show(R.string.network_faild); + public boolean onResultOk(Response response, Result.UpReply result) { + if (!activity.isFinishing()) { + if (result.getAction() == Reply.UpAction.up) { + reply.getUpList().add(LoginShared.getId(activity)); + } else if (result.getAction() == Reply.UpAction.down) { + reply.getUpList().remove(LoginShared.getId(activity)); + } + // 如果位置没有变,则更新界面 + if (position == holder.position) { + holder.btnUps.setText(String.valueOf(holder.reply.getUpList().size())); + holder.btnUps.setCompoundDrawablesWithIntrinsicBounds(holder.reply.getUpList().contains(LoginShared.getId(activity)) ? R.drawable.ic_thumb_up_theme_24dp : R.drawable.ic_thumb_up_grey600_24dp, 0, 0, 0); + } } + return false; } }); diff --git a/app/src/main/java/org/cnodejs/android/md/display/adapter/UserDetailAdapter.java b/app/src/main/java/org/cnodejs/android/md/display/adapter/UserDetailAdapter.java new file mode 100644 index 00000000..6cf34e00 --- /dev/null +++ b/app/src/main/java/org/cnodejs/android/md/display/adapter/UserDetailAdapter.java @@ -0,0 +1,60 @@ +package org.cnodejs.android.md.display.adapter; + +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; + +import org.cnodejs.android.md.display.fragment.UserDetailItemFragment; +import org.cnodejs.android.md.model.entity.Topic; +import org.cnodejs.android.md.model.entity.TopicSimple; +import org.cnodejs.android.md.model.entity.User; + +import java.util.ArrayList; +import java.util.List; + +public class UserDetailAdapter extends FragmentPagerAdapter { + + private final List fmList = new ArrayList<>(); + private final String[] titles = { + "最近回复", + "最新发布", + "主题收藏" + }; + + public UserDetailAdapter(FragmentManager manager) { + super(manager); + fmList.add(new UserDetailItemFragment()); + fmList.add(new UserDetailItemFragment()); + fmList.add(new UserDetailItemFragment()); + } + + public void update(@NonNull User user) { + fmList.get(0).notifyDataSetChanged(user.getRecentReplyList()); + fmList.get(1).notifyDataSetChanged(user.getRecentTopicList()); + } + + public void update(@NonNull List topicList) { + List topicSimpleList = new ArrayList<>(); + for (Topic topic : topicList) { + topicSimpleList.add(topic); + } + fmList.get(2).notifyDataSetChanged(topicSimpleList); + } + + @Override + public Fragment getItem(int position) { + return fmList.get(position); + } + + @Override + public int getCount() { + return fmList.size(); + } + + @Override + public CharSequence getPageTitle(int position) { + return titles[position]; + } + +} diff --git a/app/src/main/java/org/cnodejs/android/md/ui/adapter/UserDetailItemAdapter.java b/app/src/main/java/org/cnodejs/android/md/display/adapter/UserDetailItemAdapter.java similarity index 74% rename from app/src/main/java/org/cnodejs/android/md/ui/adapter/UserDetailItemAdapter.java rename to app/src/main/java/org/cnodejs/android/md/display/adapter/UserDetailItemAdapter.java index 6f89b537..052ee8e3 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/adapter/UserDetailItemAdapter.java +++ b/app/src/main/java/org/cnodejs/android/md/display/adapter/UserDetailItemAdapter.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.adapter; +package org.cnodejs.android.md.display.adapter; import android.app.Activity; import android.support.annotation.NonNull; @@ -9,12 +9,12 @@ import android.widget.ImageView; import android.widget.TextView; -import com.squareup.picasso.Picasso; +import com.bumptech.glide.Glide; import org.cnodejs.android.md.R; import org.cnodejs.android.md.model.entity.TopicSimple; -import org.cnodejs.android.md.ui.activity.TopicActivity; -import org.cnodejs.android.md.ui.activity.UserDetailActivity; +import org.cnodejs.android.md.display.activity.TopicActivity; +import org.cnodejs.android.md.display.activity.UserDetailActivity; import org.cnodejs.android.md.util.FormatUtils; import java.util.List; @@ -25,11 +25,11 @@ public class UserDetailItemAdapter extends RecyclerView.Adapter { - private Activity activity; - private LayoutInflater inflater; - private List topicList; + private final Activity activity; + private final LayoutInflater inflater; + private final List topicList; - public UserDetailItemAdapter(Activity activity, @NonNull List topicList) { + public UserDetailItemAdapter(@NonNull Activity activity, @NonNull List topicList) { this.activity = activity; inflater = LayoutInflater.from(activity); this.topicList = topicList; @@ -75,19 +75,19 @@ protected void update(int position) { topic = topicList.get(position); tvTitle.setText(topic.getTitle()); - Picasso.with(activity).load(topic.getAuthor().getAvatarUrl()).placeholder(R.drawable.image_placeholder).into(imgAvatar); + Glide.with(activity).load(topic.getAuthor().getAvatarUrl()).placeholder(R.drawable.image_placeholder).dontAnimate().into(imgAvatar); tvLoginName.setText(topic.getAuthor().getLoginName()); tvLastReplyTime.setText(FormatUtils.getRecentlyTimeText(topic.getLastReplyAt())); } @OnClick(R.id.user_detail_item_img_avatar) protected void onBtnAvatarClick() { - UserDetailActivity.openWithTransitionAnimation(activity, topic.getAuthor().getLoginName(), imgAvatar, topic.getAuthor().getAvatarUrl()); + UserDetailActivity.startWithTransitionAnimation(activity, topic.getAuthor().getLoginName(), imgAvatar, topic.getAuthor().getAvatarUrl()); } @OnClick(R.id.user_detail_item_btn_item) protected void onBtnItemClick() { - TopicActivity.open(activity, topic.getId()); + TopicActivity.start(activity, topic.getId()); } } diff --git a/app/src/main/java/org/cnodejs/android/md/ui/base/BaseActivity.java b/app/src/main/java/org/cnodejs/android/md/display/base/BaseActivity.java similarity index 89% rename from app/src/main/java/org/cnodejs/android/md/ui/base/BaseActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/base/BaseActivity.java index af99f088..d7386f10 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/base/BaseActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/base/BaseActivity.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.base; +package org.cnodejs.android.md.display.base; import android.support.v7.app.AppCompatActivity; diff --git a/app/src/main/java/org/cnodejs/android/md/ui/base/DrawerLayoutActivity.java b/app/src/main/java/org/cnodejs/android/md/display/base/DrawerLayoutActivity.java similarity index 92% rename from app/src/main/java/org/cnodejs/android/md/ui/base/DrawerLayoutActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/base/DrawerLayoutActivity.java index 0c2ed669..8bf8350c 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/base/DrawerLayoutActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/base/DrawerLayoutActivity.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.base; +package org.cnodejs.android.md.display.base; import android.os.Build; import android.os.Bundle; @@ -6,7 +6,7 @@ import android.view.ViewGroup; import android.view.WindowManager; -import org.cnodejs.android.md.ui.widget.ThemeUtils; +import org.cnodejs.android.md.display.widget.ThemeUtils; public abstract class DrawerLayoutActivity extends BaseActivity { diff --git a/app/src/main/java/org/cnodejs/android/md/ui/base/StatusBarActivity.java b/app/src/main/java/org/cnodejs/android/md/display/base/StatusBarActivity.java similarity index 94% rename from app/src/main/java/org/cnodejs/android/md/ui/base/StatusBarActivity.java rename to app/src/main/java/org/cnodejs/android/md/display/base/StatusBarActivity.java index ff5ea362..dc842d66 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/base/StatusBarActivity.java +++ b/app/src/main/java/org/cnodejs/android/md/display/base/StatusBarActivity.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.base; +package org.cnodejs.android.md.display.base; import android.os.Build; import android.os.Bundle; @@ -9,7 +9,7 @@ import android.widget.FrameLayout; import org.cnodejs.android.md.R; -import org.cnodejs.android.md.ui.widget.ThemeUtils; +import org.cnodejs.android.md.display.widget.ThemeUtils; public abstract class StatusBarActivity extends BaseActivity { diff --git a/app/src/main/java/org/cnodejs/android/md/display/dialog/DialogUtils.java b/app/src/main/java/org/cnodejs/android/md/display/dialog/DialogUtils.java new file mode 100644 index 00000000..6f0b9e09 --- /dev/null +++ b/app/src/main/java/org/cnodejs/android/md/display/dialog/DialogUtils.java @@ -0,0 +1,20 @@ +package org.cnodejs.android.md.display.dialog; + +import android.content.Context; +import android.support.v7.app.AlertDialog; + +import org.cnodejs.android.md.display.widget.ThemeUtils; + +public final class DialogUtils { + + private DialogUtils() {} + + public static ProgressDialog createProgressDialog(Context context) { + return new ProgressDialog(context, ThemeUtils.getDialogThemeRes(context)); + } + + public static AlertDialog.Builder createAlertDialogBuilder(Context context) { + return new AlertDialog.Builder(context, ThemeUtils.getDialogThemeRes(context)); + } + +} diff --git a/app/src/main/java/org/cnodejs/android/md/display/dialog/ProgressDialog.java b/app/src/main/java/org/cnodejs/android/md/display/dialog/ProgressDialog.java new file mode 100644 index 00000000..0abd1613 --- /dev/null +++ b/app/src/main/java/org/cnodejs/android/md/display/dialog/ProgressDialog.java @@ -0,0 +1,52 @@ +package org.cnodejs.android.md.display.dialog; + +import android.content.Context; +import android.support.annotation.StringRes; +import android.support.v7.app.AppCompatDialog; +import android.text.TextUtils; +import android.view.View; +import android.view.Window; +import android.widget.TextView; + +import org.cnodejs.android.md.R; + +import butterknife.Bind; +import butterknife.ButterKnife; + +public class ProgressDialog extends AppCompatDialog { + + public ProgressDialog(Context context) { + super(context); + init(); + } + + public ProgressDialog(Context context, int theme) { + super(context, theme); + init(); + } + + protected ProgressDialog(Context context, boolean cancelable, OnCancelListener cancelListener) { + super(context, cancelable, cancelListener); + init(); + } + + @Bind(R.id.dialog_progress_tv_message) + protected TextView tvMessage; + + private void init() { + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.dialog_progress); + ButterKnife.bind(this); + } + + public void setMessage(CharSequence text) { + tvMessage.setText(text); + tvMessage.setVisibility(TextUtils.isEmpty(text) ? View.GONE : View.VISIBLE); + } + + public void setMessage(@StringRes int resId) { + tvMessage.setText(resId); + tvMessage.setVisibility(resId == 0 ? View.GONE : View.VISIBLE); + } + +} diff --git a/app/src/main/java/org/cnodejs/android/md/ui/fragment/UserDetailItemFragment.java b/app/src/main/java/org/cnodejs/android/md/display/fragment/UserDetailItemFragment.java similarity index 73% rename from app/src/main/java/org/cnodejs/android/md/ui/fragment/UserDetailItemFragment.java rename to app/src/main/java/org/cnodejs/android/md/display/fragment/UserDetailItemFragment.java index c0f7d972..a8a71768 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/fragment/UserDetailItemFragment.java +++ b/app/src/main/java/org/cnodejs/android/md/display/fragment/UserDetailItemFragment.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.fragment; +package org.cnodejs.android.md.display.fragment; import android.os.Bundle; import android.support.v4.app.Fragment; @@ -10,7 +10,7 @@ import org.cnodejs.android.md.R; import org.cnodejs.android.md.model.entity.TopicSimple; -import org.cnodejs.android.md.ui.adapter.UserDetailItemAdapter; +import org.cnodejs.android.md.display.adapter.UserDetailItemAdapter; import java.util.ArrayList; import java.util.List; @@ -24,7 +24,7 @@ public class UserDetailItemFragment extends Fragment { protected RecyclerView recyclerView; private UserDetailItemAdapter adapter; - private List topicList = new ArrayList<>(); + private final List topicList = new ArrayList<>(); @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -42,14 +42,9 @@ public void onViewCreated(View view, Bundle savedInstanceState) { } public void notifyDataSetChanged(List topicList) { - if (this.topicList.size() > 0) { - this.topicList.clear(); - this.topicList.addAll(topicList); - adapter.notifyDataSetChanged(); - } else { - this.topicList.addAll(topicList); - adapter.notifyItemRangeInserted(0, topicList.size()); - } + this.topicList.clear(); + this.topicList.addAll(topicList); + adapter.notifyDataSetChanged(); } } diff --git a/app/src/main/java/org/cnodejs/android/md/ui/listener/NavigationFinishClickListener.java b/app/src/main/java/org/cnodejs/android/md/display/listener/NavigationFinishClickListener.java similarity index 62% rename from app/src/main/java/org/cnodejs/android/md/ui/listener/NavigationFinishClickListener.java rename to app/src/main/java/org/cnodejs/android/md/display/listener/NavigationFinishClickListener.java index 90a05a7e..a833a619 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/listener/NavigationFinishClickListener.java +++ b/app/src/main/java/org/cnodejs/android/md/display/listener/NavigationFinishClickListener.java @@ -1,14 +1,15 @@ -package org.cnodejs.android.md.ui.listener; +package org.cnodejs.android.md.display.listener; import android.app.Activity; +import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.view.View; public class NavigationFinishClickListener implements View.OnClickListener { - private Activity activity; + private final Activity activity; - public NavigationFinishClickListener(Activity activity) { + public NavigationFinishClickListener(@NonNull Activity activity) { this.activity = activity; } diff --git a/app/src/main/java/org/cnodejs/android/md/ui/listener/NavigationOpenClickListener.java b/app/src/main/java/org/cnodejs/android/md/display/listener/NavigationOpenClickListener.java similarity index 62% rename from app/src/main/java/org/cnodejs/android/md/ui/listener/NavigationOpenClickListener.java rename to app/src/main/java/org/cnodejs/android/md/display/listener/NavigationOpenClickListener.java index 9ca9f384..6406d6a5 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/listener/NavigationOpenClickListener.java +++ b/app/src/main/java/org/cnodejs/android/md/display/listener/NavigationOpenClickListener.java @@ -1,14 +1,15 @@ -package org.cnodejs.android.md.ui.listener; +package org.cnodejs.android.md.display.listener; +import android.support.annotation.NonNull; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.view.View; public class NavigationOpenClickListener implements View.OnClickListener { - private DrawerLayout drawerLayout; + private final DrawerLayout drawerLayout; - public NavigationOpenClickListener(DrawerLayout drawerLayout) { + public NavigationOpenClickListener(@NonNull DrawerLayout drawerLayout) { this.drawerLayout = drawerLayout; } diff --git a/app/src/main/java/org/cnodejs/android/md/ui/listener/RecyclerViewLoadMoreListener.java b/app/src/main/java/org/cnodejs/android/md/display/listener/RecyclerViewLoadMoreListener.java similarity index 83% rename from app/src/main/java/org/cnodejs/android/md/ui/listener/RecyclerViewLoadMoreListener.java rename to app/src/main/java/org/cnodejs/android/md/display/listener/RecyclerViewLoadMoreListener.java index 64aa3f42..3ab4114d 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/listener/RecyclerViewLoadMoreListener.java +++ b/app/src/main/java/org/cnodejs/android/md/display/listener/RecyclerViewLoadMoreListener.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.listener; +package org.cnodejs.android.md.display.listener; import android.support.annotation.NonNull; import android.support.v7.widget.LinearLayoutManager; @@ -6,9 +6,9 @@ public class RecyclerViewLoadMoreListener extends RecyclerView.OnScrollListener { - private LinearLayoutManager linearLayoutManager; - private OnLoadMoreListener onLoadMoreListener; - private int limit; + private final LinearLayoutManager linearLayoutManager; + private final OnLoadMoreListener onLoadMoreListener; + private final int limit; public RecyclerViewLoadMoreListener(@NonNull LinearLayoutManager linearLayoutManager, @NonNull OnLoadMoreListener onLoadMoreListener, int limit) { super(); diff --git a/app/src/main/java/org/cnodejs/android/md/display/widget/CNodeWebView.java b/app/src/main/java/org/cnodejs/android/md/display/widget/CNodeWebView.java new file mode 100644 index 00000000..1ae538b4 --- /dev/null +++ b/app/src/main/java/org/cnodejs/android/md/display/widget/CNodeWebView.java @@ -0,0 +1,120 @@ +package org.cnodejs.android.md.display.widget; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.webkit.JavascriptInterface; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import org.cnodejs.android.md.display.activity.ImagePreviewActivity; +import org.cnodejs.android.md.display.activity.TopicActivity; +import org.cnodejs.android.md.display.activity.UserDetailActivity; +import org.cnodejs.android.md.model.storage.SettingShared; +import org.cnodejs.android.md.util.ShipUtils; + +public class CNodeWebView extends WebView { + + private static final String THEME_CSS_LIGHT = "file:///android_asset/cnode_light.css"; + private static final String THEME_CSS_DARK = "file:///android_asset/cnode_dark.css"; + + private static final String HTML_0 = "" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n"; + + private static final String HTML_1 = "" + + "\n" + + "\n"; + + private static final String HTML_2 = "" + + "\n" + + ""; + + private static final String JS_IMAGE_INTERFACE_NAME = "imageInterface"; + + private static final String JS_IMAGE_INTERFACE_SCRIPT = "" + + "javascript:\n" + + "(function() {\n" + + " var objs = document.getElementsByTagName('img');\n" + + " for (var i = 0; i < objs.length; i++) {\n" + + " objs[i].onclick = function() {\n" + + " if (this.parentNode.nodeName !== 'A') {\n" + + " window." + JS_IMAGE_INTERFACE_NAME + ".openImage(this.src);\n" + + " }\n" + + " }\n" + + " }\n" + + "})();"; + + private class ImageJavascriptInterface { + + @JavascriptInterface + public void openImage(String imageUrl) { + ImagePreviewActivity.start(getContext(), imageUrl); + } + + } + + private class CNodeWebViewClient extends WebViewClient { + + @Override + public boolean shouldOverrideUrlLoading(WebView webView, String url) { + if (url.startsWith("https://cnodejs.org/user/")) { // 用户主页协议 + UserDetailActivity.start(getContext(), url.substring(25)); + } else if (url.startsWith("https://cnodejs.org/topic/")) { // 话题主页协议 + TopicActivity.start(getContext(), url.substring(26)); + } else { // 其他连接 + ShipUtils.openInBrowser(getContext(), url); + } + return true; + } + + @Override + public void onPageFinished(WebView view, String url) { + view.loadUrl(JS_IMAGE_INTERFACE_SCRIPT); + } + + } + + public CNodeWebView(Context context) { + super(context); + init(context, null, 0, 0); + } + + public CNodeWebView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0, 0); + } + + public CNodeWebView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public CNodeWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context, attrs, defStyleAttr, defStyleRes); + } + + @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"}) + private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + getSettings().setJavaScriptEnabled(true); + addJavascriptInterface(new ImageJavascriptInterface(), JS_IMAGE_INTERFACE_NAME); + setWebViewClient(new CNodeWebViewClient()); + } + + private String getThemeCSS() { + return SettingShared.isEnableThemeDark(getContext()) ? THEME_CSS_DARK : THEME_CSS_LIGHT; + } + + public void loadRenderedContent(String data) { + data = HTML_0 + "\n" + HTML_1 + data + "\n" + HTML_2; + loadDataWithBaseURL(null, data, "text/html", "utf-8", null); + } + +} diff --git a/app/src/main/java/org/cnodejs/android/md/ui/widget/EditorBarHandler.java b/app/src/main/java/org/cnodejs/android/md/display/widget/EditorBarHandler.java similarity index 75% rename from app/src/main/java/org/cnodejs/android/md/ui/widget/EditorBarHandler.java rename to app/src/main/java/org/cnodejs/android/md/display/widget/EditorBarHandler.java index 7e058814..48f3c4e6 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/widget/EditorBarHandler.java +++ b/app/src/main/java/org/cnodejs/android/md/display/widget/EditorBarHandler.java @@ -1,31 +1,32 @@ -package org.cnodejs.android.md.ui.widget; +package org.cnodejs.android.md.display.widget; +import android.app.Activity; +import android.app.Dialog; import android.content.Context; +import android.content.DialogInterface; import android.support.annotation.NonNull; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; -import com.afollestad.materialdialogs.DialogAction; -import com.afollestad.materialdialogs.MaterialDialog; - import org.cnodejs.android.md.R; -import org.cnodejs.android.md.storage.SettingShared; -import org.cnodejs.android.md.ui.activity.MarkdownPreviewActivity; +import org.cnodejs.android.md.model.storage.SettingShared; +import org.cnodejs.android.md.display.activity.MarkdownPreviewActivity; +import org.cnodejs.android.md.display.dialog.DialogUtils; import butterknife.ButterKnife; import butterknife.OnClick; public class EditorBarHandler { - private Context context; - private EditText edtContent; - private InputMethodManager imm; + private final Activity activity; + private final EditText edtContent; + private final InputMethodManager imm; - public EditorBarHandler(Context context, View editorBar, EditText edtContent) { - this.context = context; + public EditorBarHandler(@NonNull Activity activity, @NonNull View editorBar, @NonNull EditText edtContent) { + this.activity = activity; this.edtContent = edtContent; - imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); ButterKnife.bind(this, editorBar); } @@ -72,7 +73,7 @@ protected void onBtnFormatListBulletedClick() { } /** - * 有序列表 TODO 这里算法需要优化 + * 有序列表 FIXME 这里算法需要优化 */ @OnClick(R.id.editor_bar_btn_format_list_numbered) protected void onBtnFormatListNumberedClick() { @@ -112,18 +113,16 @@ protected void onBtnInsertCodeClick() { */ @OnClick(R.id.editor_bar_btn_insert_link) protected void onBtnInsertLinkClick() { - new MaterialDialog.Builder(context) - .iconRes(R.drawable.ic_insert_link_grey600_24dp) - .title(R.string.add_link) - .customView(R.layout.dialog_tool_insert_link, false) - .positiveText(R.string.confirm) - .onPositive(new MaterialDialog.SingleButtonCallback() { + DialogUtils.createAlertDialogBuilder(activity) + .setIcon(R.drawable.ic_insert_link_grey600_24dp) + .setTitle(R.string.add_link) + .setView(R.layout.dialog_tool_insert_link) + .setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() { @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - View view = dialog.getCustomView(); - EditText edtTitle = ButterKnife.findById(view, R.id.dialog_tool_insert_link_edt_title); - EditText edtLink = ButterKnife.findById(view, R.id.dialog_tool_insert_link_edt_link); + public void onClick(DialogInterface dialog, int which) { + EditText edtTitle = ButterKnife.findById((Dialog) dialog, R.id.dialog_tool_insert_link_edt_title); + EditText edtLink = ButterKnife.findById((Dialog) dialog, R.id.dialog_tool_insert_link_edt_link); String insertText = " [" + edtTitle.getText() + "](" + edtLink.getText() + ") "; edtContent.requestFocus(); @@ -131,7 +130,7 @@ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) } }) - .negativeText(R.string.cancel) + .setNegativeButton(R.string.cancel, null) .show(); } @@ -152,10 +151,10 @@ protected void onBtnInsertPhotoClick() { @OnClick(R.id.editor_bar_btn_preview) protected void onBtnPreviewClick() { String content = edtContent.getText().toString(); - if (SettingShared.isEnableTopicSign(context)) { // 添加小尾巴 - content += "\n\n" + SettingShared.getTopicSignContent(context); + if (SettingShared.isEnableTopicSign(activity)) { // 添加小尾巴 + content += "\n\n" + SettingShared.getTopicSignContent(activity); } - MarkdownPreviewActivity.open(context, content); + MarkdownPreviewActivity.start(activity, content); } } diff --git a/app/src/main/java/org/cnodejs/android/md/ui/widget/RefreshLayoutUtils.java b/app/src/main/java/org/cnodejs/android/md/display/widget/RefreshLayoutUtils.java similarity index 95% rename from app/src/main/java/org/cnodejs/android/md/ui/widget/RefreshLayoutUtils.java rename to app/src/main/java/org/cnodejs/android/md/display/widget/RefreshLayoutUtils.java index 65c0c0ec..310ad753 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/widget/RefreshLayoutUtils.java +++ b/app/src/main/java/org/cnodejs/android/md/display/widget/RefreshLayoutUtils.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.widget; +package org.cnodejs.android.md.display.widget; import android.support.v4.widget.SwipeRefreshLayout; diff --git a/app/src/main/java/org/cnodejs/android/md/ui/widget/ScrimInsetsScrollView.java b/app/src/main/java/org/cnodejs/android/md/display/widget/ScrimInsetsScrollView.java similarity index 98% rename from app/src/main/java/org/cnodejs/android/md/ui/widget/ScrimInsetsScrollView.java rename to app/src/main/java/org/cnodejs/android/md/display/widget/ScrimInsetsScrollView.java index cde70da3..b9375ebf 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/widget/ScrimInsetsScrollView.java +++ b/app/src/main/java/org/cnodejs/android/md/display/widget/ScrimInsetsScrollView.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.widget; +package org.cnodejs.android.md.display.widget; import android.content.Context; import android.content.res.TypedArray; diff --git a/app/src/main/java/org/cnodejs/android/md/ui/widget/ThemeUtils.java b/app/src/main/java/org/cnodejs/android/md/display/widget/ThemeUtils.java similarity index 83% rename from app/src/main/java/org/cnodejs/android/md/ui/widget/ThemeUtils.java rename to app/src/main/java/org/cnodejs/android/md/display/widget/ThemeUtils.java index 421f0b72..aaaa450e 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/widget/ThemeUtils.java +++ b/app/src/main/java/org/cnodejs/android/md/display/widget/ThemeUtils.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.widget; +package org.cnodejs.android.md.display.widget; import android.app.Activity; import android.content.Context; @@ -7,15 +7,18 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.AttrRes; +import android.support.annotation.ColorInt; import android.support.annotation.NonNull; import android.support.annotation.StyleRes; -import org.cnodejs.android.md.storage.SettingShared; +import org.cnodejs.android.md.R; +import org.cnodejs.android.md.model.storage.SettingShared; public final class ThemeUtils { private ThemeUtils() {} + @ColorInt public static int getThemeAttrColor(@NonNull Context context, @AttrRes int attr) { TypedArray a = context.obtainStyledAttributes(null, new int[]{attr}); try { @@ -52,6 +55,10 @@ public static void recreateActivity(Activity activity) { } } + public static int getDialogThemeRes(Context context) { + return SettingShared.isEnableThemeDark(context) ? R.style.AppDialogDark : R.style.AppDialogLight; + } + public static int getStatusBarHeight(Context context) { int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); return resourceId > 0 ? context.getResources().getDimensionPixelSize(resourceId) : 0; diff --git a/app/src/main/java/org/cnodejs/android/md/ui/widget/ToastUtils.java b/app/src/main/java/org/cnodejs/android/md/display/widget/ToastUtils.java similarity index 94% rename from app/src/main/java/org/cnodejs/android/md/ui/widget/ToastUtils.java rename to app/src/main/java/org/cnodejs/android/md/display/widget/ToastUtils.java index 988076ac..0fca53da 100644 --- a/app/src/main/java/org/cnodejs/android/md/ui/widget/ToastUtils.java +++ b/app/src/main/java/org/cnodejs/android/md/display/widget/ToastUtils.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.ui.widget; +package org.cnodejs.android.md.display.widget; import android.content.Context; import android.widget.Toast; diff --git a/app/src/main/java/org/cnodejs/android/md/model/api/ApiClient.java b/app/src/main/java/org/cnodejs/android/md/model/api/ApiClient.java index e9c4e4bb..60dba455 100644 --- a/app/src/main/java/org/cnodejs/android/md/model/api/ApiClient.java +++ b/app/src/main/java/org/cnodejs/android/md/model/api/ApiClient.java @@ -1,20 +1,21 @@ package org.cnodejs.android.md.model.api; -import org.cnodejs.android.md.BuildConfig; -import org.cnodejs.android.md.util.gson.GsonWrapper; +import org.cnodejs.android.md.model.util.EntityUtils; +import org.cnodejs.android.md.model.util.HttpUtils; -import retrofit.RestAdapter; -import retrofit.converter.GsonConverter; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; public final class ApiClient { private ApiClient() {} - public static final ApiService service = new RestAdapter.Builder() - .setEndpoint(ApiDefine.API_HOST) - .setConverter(new GsonConverter(GsonWrapper.gson)) - .setRequestInterceptor(new ApiRequestInterceptor()) - .setLogLevel(BuildConfig.DEBUG ? RestAdapter.LogLevel.FULL : RestAdapter.LogLevel.NONE) + public static final String API_BASE_URL = "https://cnodejs.org/api/v1/"; + + public static final ApiService service = new Retrofit.Builder() + .baseUrl(API_BASE_URL) + .client(HttpUtils.client) + .addConverterFactory(GsonConverterFactory.create(EntityUtils.gson)) .build() .create(ApiService.class); diff --git a/app/src/main/java/org/cnodejs/android/md/model/api/ApiDefine.java b/app/src/main/java/org/cnodejs/android/md/model/api/ApiDefine.java deleted file mode 100644 index a643fb9c..00000000 --- a/app/src/main/java/org/cnodejs/android/md/model/api/ApiDefine.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.cnodejs.android.md.model.api; - -import android.os.Build; - -import org.cnodejs.android.md.BuildConfig; - -public final class ApiDefine { - - private ApiDefine() {} - - public static final String API_HOST = "https://cnodejs.org/api"; - - public static final String USER_AGENT = "CNodeMD/" + BuildConfig.VERSION_NAME + " (Android " + Build.VERSION.RELEASE + "; " + Build.MANUFACTURER + " - " + Build.MODEL + ")"; - public static final String HTTP_ACCEPT = "application/json"; - -} diff --git a/app/src/main/java/org/cnodejs/android/md/model/api/ApiRequestInterceptor.java b/app/src/main/java/org/cnodejs/android/md/model/api/ApiRequestInterceptor.java deleted file mode 100644 index 3f00a193..00000000 --- a/app/src/main/java/org/cnodejs/android/md/model/api/ApiRequestInterceptor.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.cnodejs.android.md.model.api; - -import retrofit.RequestInterceptor; - -public class ApiRequestInterceptor implements RequestInterceptor { - - @Override - public void intercept(RequestFacade request) { - request.addHeader("User-Agent", ApiDefine.USER_AGENT); - request.addHeader("Accept", ApiDefine.HTTP_ACCEPT); - } - -} diff --git a/app/src/main/java/org/cnodejs/android/md/model/api/ApiService.java b/app/src/main/java/org/cnodejs/android/md/model/api/ApiService.java index e1c28e08..a35f7d0a 100644 --- a/app/src/main/java/org/cnodejs/android/md/model/api/ApiService.java +++ b/app/src/main/java/org/cnodejs/android/md/model/api/ApiService.java @@ -1,24 +1,21 @@ package org.cnodejs.android.md.model.api; -import org.cnodejs.android.md.model.entity.LoginInfo; import org.cnodejs.android.md.model.entity.Notification; import org.cnodejs.android.md.model.entity.Result; import org.cnodejs.android.md.model.entity.TabType; import org.cnodejs.android.md.model.entity.Topic; -import org.cnodejs.android.md.model.entity.TopicUpInfo; import org.cnodejs.android.md.model.entity.TopicWithReply; import org.cnodejs.android.md.model.entity.User; import java.util.List; -import java.util.Map; -import retrofit.Callback; -import retrofit.http.Field; -import retrofit.http.FormUrlEncoded; -import retrofit.http.GET; -import retrofit.http.POST; -import retrofit.http.Path; -import retrofit.http.Query; +import retrofit2.Call; +import retrofit2.http.Field; +import retrofit2.http.FormUrlEncoded; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Query; public interface ApiService { @@ -26,89 +23,107 @@ public interface ApiService { // 主题 //===== - @GET("/v1/topics") - void getTopicList( + @GET("topics") + Call>> getTopicList( @Query("tab") TabType tab, @Query("page") Integer page, @Query("limit") Integer limit, - @Query("mdrender") Boolean mdrender, - Callback>> callback + @Query("mdrender") Boolean mdrender ); - @GET("/v1/topic/{id}") - void getTopic( - @Path("id") String id, - @Query("mdrender") Boolean mdrender, - Callback> callback + @GET("topic/{topicId}") + Call> getTopic( + @Path("topicId") String topicId, + @Query("accesstoken") String accessToken, + @Query("mdrender") Boolean mdrender ); - + + @POST("topics") @FormUrlEncoded - @POST("/v1/topics") - void newTopic( + Call createTopic( @Field("accesstoken") String accessToken, @Field("tab") TabType tab, @Field("title") String title, - @Field("content") String content, - Callback callback + @Field("content") String content + ); + + //========= + // 主题收藏 + //========= + + @POST("topic_collect/collect") + @FormUrlEncoded + Call collectTopic( + @Field("accesstoken") String accessToken, + @Field("topic_id") String topicId ); + @POST("topic_collect/de_collect") @FormUrlEncoded - @POST("/v1/topic/{topicId}/replies") - void replyTopic( + Call decollectTopic( @Field("accesstoken") String accessToken, + @Field("topic_id") String topicId + ); + + @GET("topic_collect/{loginName}") + Call>> getCollectTopicList( + @Path("loginName") String loginName + ); + + //===== + // 评论 + //===== + + @POST("topic/{topicId}/replies") + @FormUrlEncoded + Call replyTopic( @Path("topicId") String topicId, + @Field("accesstoken") String accessToken, @Field("content") String content, - @Field("reply_id") String replyId, - Callback> callback + @Field("reply_id") String replyId ); + @POST("reply/{replyId}/ups") @FormUrlEncoded - @POST("/v1/reply/{replyId}/ups") - void upTopic( - @Field("accesstoken") String accessToken, + Call upReply( @Path("replyId") String replyId, - Callback callback + @Field("accesstoken") String accessToken ); //===== // 用户 //===== - @GET("/v1/user/{loginName}") - void getUser( - @Path("loginName") String loginName, - Callback> callback + @GET("user/{loginName}") + Call> getUser( + @Path("loginName") String loginName ); + @POST("accesstoken") @FormUrlEncoded - @POST("/v1/accesstoken") - void accessToken( - @Field("accesstoken") String accessToken, - Callback callback + Call accessToken( + @Field("accesstoken") String accessToken ); //========= // 消息通知 //========= - @GET("/v1/message/count") - void getMessageCount( - @Query("accesstoken") String accessToken, - Callback> callback + @GET("message/count") + Call> getMessageCount( + @Query("accesstoken") String accessToken ); - @GET("/v1/messages") - void getMessages( + @GET("messages") + Call> getMessages( @Query("accesstoken") String accessToken, - @Query("mdrender") Boolean mdrender, - Callback> callback + @Query("mdrender") Boolean mdrender ); + @POST("message/mark_all") @FormUrlEncoded - @POST("/v1/message/mark_all") - void markAllMessageRead( - @Field("accesstoken") String accessToken, - Callback callback + Call markAllMessageRead( + @Field("accesstoken") String accessToken ); } diff --git a/app/src/main/java/org/cnodejs/android/md/model/api/CallbackAdapter.java b/app/src/main/java/org/cnodejs/android/md/model/api/CallbackAdapter.java index 6c776c07..2ed30893 100644 --- a/app/src/main/java/org/cnodejs/android/md/model/api/CallbackAdapter.java +++ b/app/src/main/java/org/cnodejs/android/md/model/api/CallbackAdapter.java @@ -1,15 +1,45 @@ package org.cnodejs.android.md.model.api; -import retrofit.Callback; -import retrofit.RetrofitError; -import retrofit.client.Response; +import org.cnodejs.android.md.model.entity.Result; -public class CallbackAdapter implements Callback { +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class CallbackAdapter implements Callback { @Override - public void success(T t, Response response) {} + public final void onResponse(Call call, Response response) { + boolean interrupt; + if (response.isSuccessful()) { + interrupt = onResultOk(response, response.body()); + } else { + interrupt = onResultError(response, Result.buildError(response)); + } + if (!interrupt) { + onFinish(); + } + } @Override - public void failure(RetrofitError error) {} + public final void onFailure(Call call, Throwable t) { + if (!onCallException(t, Result.buildError(t))) { + onFinish(); + } + } + + public boolean onResultOk(Response response, T result) { + return false; + } + + public boolean onResultError(Response response, Result.Error error) { + return false; + } + + public boolean onCallException(Throwable t, Result.Error error) { + return false; + } + + public void onFinish() {} } diff --git a/app/src/main/java/org/cnodejs/android/md/model/api/DefaultToastCallback.java b/app/src/main/java/org/cnodejs/android/md/model/api/DefaultToastCallback.java new file mode 100644 index 00000000..c7629b7e --- /dev/null +++ b/app/src/main/java/org/cnodejs/android/md/model/api/DefaultToastCallback.java @@ -0,0 +1,65 @@ +package org.cnodejs.android.md.model.api; + +import android.app.Activity; +import android.content.DialogInterface; +import android.support.annotation.NonNull; + +import org.cnodejs.android.md.R; +import org.cnodejs.android.md.display.activity.LoginActivity; +import org.cnodejs.android.md.display.dialog.DialogUtils; +import org.cnodejs.android.md.display.widget.ToastUtils; +import org.cnodejs.android.md.model.entity.Result; + +import retrofit2.Response; + +public class DefaultToastCallback extends CallbackAdapter { + + private final Activity activity; + + public DefaultToastCallback(@NonNull Activity activity) { + this.activity = activity; + } + + @Override + public final boolean onResultError(Response response, Result.Error error) { + if (response.code() == 401) { + return onResultErrorAuth(response, error); + } else { + return onResultErrorOther(response, error); + } + } + + public boolean onResultErrorAuth(Response response, Result.Error error) { + if (!activity.isFinishing()) { + DialogUtils.createAlertDialogBuilder(activity) + .setMessage(R.string.access_token_out_of_date) + .setPositiveButton(R.string.relogin, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + LoginActivity.startForResult(activity); + } + + }) + .setNegativeButton(R.string.cancel, null) + .show(); + } + return false; + } + + public boolean onResultErrorOther(Response response, Result.Error error) { + if (!activity.isFinishing()) { + ToastUtils.with(activity).show(error.getErrorMessage()); + } + return false; + } + + @Override + public boolean onCallException(Throwable t, Result.Error error) { + if (!activity.isFinishing()) { + ToastUtils.with(activity).show(error.getErrorMessage()); + } + return false; + } + +} diff --git a/app/src/main/java/org/cnodejs/android/md/model/entity/Author.java b/app/src/main/java/org/cnodejs/android/md/model/entity/Author.java index 189cd8c2..69f23357 100644 --- a/app/src/main/java/org/cnodejs/android/md/model/entity/Author.java +++ b/app/src/main/java/org/cnodejs/android/md/model/entity/Author.java @@ -20,7 +20,7 @@ public void setLoginName(String loginName) { this.loginName = loginName; } - public String getAvatarUrl() { // TODO 修复头像地址的历史遗留问题 + public String getAvatarUrl() { // 修复头像地址的历史遗留问题 return FormatUtils.getCompatAvatarUrl(avatarUrl); } diff --git a/app/src/main/java/org/cnodejs/android/md/model/entity/LoginInfo.java b/app/src/main/java/org/cnodejs/android/md/model/entity/LoginInfo.java deleted file mode 100644 index 262afac0..00000000 --- a/app/src/main/java/org/cnodejs/android/md/model/entity/LoginInfo.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.cnodejs.android.md.model.entity; - -import com.google.gson.annotations.SerializedName; - -import org.cnodejs.android.md.util.FormatUtils; - -public class LoginInfo { - - private String id; - - @SerializedName("loginname") - private String loginName; - - @SerializedName("avatar_url") - private String avatarUrl; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getLoginName() { - return loginName; - } - - public void setLoginName(String loginName) { - this.loginName = loginName; - } - - public String getAvatarUrl() { // TODO 修复头像地址的历史遗留问题 - return FormatUtils.getCompatAvatarUrl(avatarUrl); - } - - public void setAvatarUrl(String avatarUrl) { - this.avatarUrl = avatarUrl; - } - -} diff --git a/app/src/main/java/org/cnodejs/android/md/model/entity/Message.java b/app/src/main/java/org/cnodejs/android/md/model/entity/Message.java index b74355fd..14382620 100644 --- a/app/src/main/java/org/cnodejs/android/md/model/entity/Message.java +++ b/app/src/main/java/org/cnodejs/android/md/model/entity/Message.java @@ -4,6 +4,8 @@ import com.google.gson.annotations.SerializedName; +import org.joda.time.DateTime; + public class Message { public enum Type { @@ -20,9 +22,12 @@ public enum Type { private Author author; - private TopicSimple topic; // TODO 这里不含Author字段,注意 + private TopicSimple topic; // 这里不含Author字段,注意 + + private Reply reply; // 这里不含Author字段,注意 - private Reply reply; // TODO 这里不含Author字段,注意 + @SerializedName("create_at") + private DateTime createAt; // TODO 这个字段目前不存在,需要服务端补全 public String getId() { return id; @@ -34,7 +39,7 @@ public void setId(String id) { @NonNull public Type getType() { - return type == null ? Type.reply : type; // TODO 保证返回不为空 + return type == null ? Type.reply : type; // 保证返回不为空 } public void setType(Type type) { @@ -73,4 +78,19 @@ public void setReply(Reply reply) { this.reply = reply; } + public DateTime getCreateAt() { // TODO 这里做兼容处理 + //return createAt; + if (createAt != null) { + return createAt; + } else if (getReply() != null && getReply().getCreateAt() != null) { + return getReply().getCreateAt(); + } else { + return getTopic().getLastReplyAt(); + } + } + + public void setCreateAt(DateTime createAt) { + this.createAt = createAt; + } + } diff --git a/app/src/main/java/org/cnodejs/android/md/model/entity/Reply.java b/app/src/main/java/org/cnodejs/android/md/model/entity/Reply.java index f4cb6851..05118497 100644 --- a/app/src/main/java/org/cnodejs/android/md/model/entity/Reply.java +++ b/app/src/main/java/org/cnodejs/android/md/model/entity/Reply.java @@ -11,6 +11,11 @@ public class Reply { + public enum UpAction { + up, + down + } + private String id; private Author author; @@ -20,6 +25,9 @@ public class Reply { @SerializedName("ups") private List upList; + @SerializedName("reply_id") + private String replyId; + @SerializedName("create_at") private DateTime createAt; @@ -55,6 +63,14 @@ public void setUpList(List upList) { this.upList = upList; } + public String getReplyId() { + return replyId; + } + + public void setReplyId(String replyId) { + this.replyId = replyId; + } + public DateTime getCreateAt() { return createAt; } @@ -63,7 +79,10 @@ public void setCreateAt(DateTime createAt) { this.createAt = createAt; } - // TODO Html渲染缓存 + /** + * Html渲染缓存 + */ + private String handleContent = null; public String getHandleContent() { diff --git a/app/src/main/java/org/cnodejs/android/md/model/entity/Result.java b/app/src/main/java/org/cnodejs/android/md/model/entity/Result.java index 51de2697..75c9309c 100644 --- a/app/src/main/java/org/cnodejs/android/md/model/entity/Result.java +++ b/app/src/main/java/org/cnodejs/android/md/model/entity/Result.java @@ -1,15 +1,159 @@ package org.cnodejs.android.md.model.entity; -public class Result { +import android.support.annotation.NonNull; - private T data; +import com.google.gson.JsonSyntaxException; +import com.google.gson.annotations.SerializedName; - public T getData() { - return data; +import org.cnodejs.android.md.model.util.EntityUtils; +import org.cnodejs.android.md.util.FormatUtils; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; + +import retrofit2.Response; + +public class Result { + + protected boolean success; + + public boolean isSuccess() { + return success; } - public void setData(T data) { - this.data = data; + public static class Data extends Result { + + private T data; + + public T getData() { + return data; + } + + } + + public static class Error extends Result { + + @SerializedName("error_msg") + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + } + + public static Error buildError(@NonNull Response response) { + try { + return EntityUtils.gson.fromJson(response.errorBody().string(), Error.class); + } catch (IOException | JsonSyntaxException e) { + Error error = new Error(); + error.success = false; + switch (response.code()) { + case 400: + error.errorMessage = "请求参数有误"; + break; + case 403: + error.errorMessage = "请求被拒绝"; + break; + case 404: + error.errorMessage = "资源未找到"; + break; + case 405: + error.errorMessage = "请求方式不被允许"; + break; + case 408: + error.errorMessage = "请求超时"; + break; + case 422: + error.errorMessage = "请求语义错误"; + break; + case 500: + error.errorMessage = "服务器逻辑错误"; + break; + case 502: + error.errorMessage = "服务器网关错误"; + break; + case 504: + error.errorMessage = "服务器网关超时"; + break; + default: + error.errorMessage = response.message(); + break; + } + return error; + } + } + + public static Error buildError(@NonNull Throwable t) { + Error error = new Error(); + error.success = false; + if (t instanceof UnknownHostException) { + error.errorMessage = "网络无法连接"; + } else if (t instanceof SocketTimeoutException) { + error.errorMessage = "网络访问超时"; + } else if (t instanceof JsonSyntaxException) { + error.errorMessage = "响应数据格式错误"; + } else { + error.errorMessage = "未知错误:" + t.getLocalizedMessage(); + } + return error; + } + + public static class Login extends Result { + + private String id; + + @SerializedName("loginname") + private String loginName; + + @SerializedName("avatar_url") + private String avatarUrl; + + public String getId() { + return id; + } + + public String getLoginName() { + return loginName; + } + + public String getAvatarUrl() { // 修复头像地址的历史遗留问题 + return FormatUtils.getCompatAvatarUrl(avatarUrl); + } + + } + + public static class CreateTopic extends Result { + + @SerializedName("topic_id") + private String topicId; + + public String getTopicId() { + return topicId; + } + + } + + public static class ReplyTopic extends Result { + + @SerializedName("reply_id") + private String replyId; + + public String getReplyId() { + return replyId; + } + + } + + public static class UpReply extends Result { + + private Reply.UpAction action; + + public Reply.UpAction getAction() { + return action == null ? Reply.UpAction.down : action; + } + } } diff --git a/app/src/main/java/org/cnodejs/android/md/model/entity/TabType.java b/app/src/main/java/org/cnodejs/android/md/model/entity/TabType.java index 888a62b7..2ad9fa4b 100644 --- a/app/src/main/java/org/cnodejs/android/md/model/entity/TabType.java +++ b/app/src/main/java/org/cnodejs/android/md/model/entity/TabType.java @@ -1,5 +1,7 @@ package org.cnodejs.android.md.model.entity; +import android.support.annotation.StringRes; + import org.cnodejs.android.md.R; public enum TabType { @@ -14,12 +16,14 @@ public enum TabType { job(R.string.tab_job); + @StringRes private int nameId; - TabType(int nameId) { + TabType(@StringRes int nameId) { this.nameId = nameId; } + @StringRes public int getNameId() { return nameId; } diff --git a/app/src/main/java/org/cnodejs/android/md/model/entity/Topic.java b/app/src/main/java/org/cnodejs/android/md/model/entity/Topic.java index c0147ff0..e5ffa6f5 100644 --- a/app/src/main/java/org/cnodejs/android/md/model/entity/Topic.java +++ b/app/src/main/java/org/cnodejs/android/md/model/entity/Topic.java @@ -5,24 +5,15 @@ import org.cnodejs.android.md.util.FormatUtils; import org.joda.time.DateTime; -public class Topic { - - private String id; +public class Topic extends TopicSimple { @SerializedName("author_id") private String authorId; - private Author author; - private TabType tab; - private String title; - private String content; - @SerializedName("last_reply_at") - private DateTime lastReplyAt; - private boolean good; private boolean top; @@ -36,14 +27,6 @@ public class Topic { @SerializedName("create_at") private DateTime createAt; - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - public String getAuthorId() { return authorId; } @@ -52,30 +35,14 @@ public void setAuthorId(String authorId) { this.authorId = authorId; } - public Author getAuthor() { - return author; - } - - public void setAuthor(Author author) { - this.author = author; - } - public TabType getTab() { - return tab == null ? TabType.all : tab; // TODO 接口中有些话题没有Tab属性,这里保证Tab不为空 + return tab == null ? TabType.all : tab; // 接口中有些话题没有Tab属性,这里保证Tab不为空 } public void setTab(TabType tab) { this.tab = tab; } - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - public String getContent() { return content; } @@ -84,14 +51,6 @@ public void setContent(String content) { this.content = content; } - public DateTime getLastReplyAt() { - return lastReplyAt; - } - - public void setLastReplyAt(DateTime lastReplyAt) { - this.lastReplyAt = lastReplyAt; - } - public boolean isGood() { return good; } @@ -132,7 +91,10 @@ public void setCreateAt(DateTime createAt) { this.createAt = createAt; } - // TODO Html渲染缓存 + /** + * Html渲染缓存 + */ + private String handleContent = null; public String getHandleContent() { diff --git a/app/src/main/java/org/cnodejs/android/md/model/entity/TopicUpInfo.java b/app/src/main/java/org/cnodejs/android/md/model/entity/TopicUpInfo.java deleted file mode 100644 index 79ba7fed..00000000 --- a/app/src/main/java/org/cnodejs/android/md/model/entity/TopicUpInfo.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.cnodejs.android.md.model.entity; - -public class TopicUpInfo { - - public enum Action { - up, - down - } - - private Action action; - - public Action getAction() { - return action; - } - - public void setAction(Action action) { - this.action = action; - } - -} diff --git a/app/src/main/java/org/cnodejs/android/md/model/entity/TopicWithReply.java b/app/src/main/java/org/cnodejs/android/md/model/entity/TopicWithReply.java index 67df3109..610733fb 100644 --- a/app/src/main/java/org/cnodejs/android/md/model/entity/TopicWithReply.java +++ b/app/src/main/java/org/cnodejs/android/md/model/entity/TopicWithReply.java @@ -6,9 +6,20 @@ public class TopicWithReply extends Topic { + @SerializedName("is_collect") + private boolean isCollect; // 是否收藏该话题,只有调用时带有 accesstoken 时,这个字段才有可能为 true + @SerializedName("replies") private List replyList; + public boolean isCollect() { + return isCollect; + } + + public void setCollect(boolean collect) { + isCollect = collect; + } + public List getReplyList() { return replyList; } diff --git a/app/src/main/java/org/cnodejs/android/md/model/entity/User.java b/app/src/main/java/org/cnodejs/android/md/model/entity/User.java index 768256da..6d9f3cb3 100644 --- a/app/src/main/java/org/cnodejs/android/md/model/entity/User.java +++ b/app/src/main/java/org/cnodejs/android/md/model/entity/User.java @@ -36,7 +36,7 @@ public void setLoginName(String loginName) { this.loginName = loginName; } - public String getAvatarUrl() { // TODO 修复头像地址的历史遗留问题 + public String getAvatarUrl() { // 修复头像地址的历史遗留问题 return FormatUtils.getCompatAvatarUrl(avatarUrl); } diff --git a/app/src/main/java/org/cnodejs/android/md/storage/DeviceInfo.java b/app/src/main/java/org/cnodejs/android/md/model/storage/DeviceInfo.java similarity index 65% rename from app/src/main/java/org/cnodejs/android/md/storage/DeviceInfo.java rename to app/src/main/java/org/cnodejs/android/md/model/storage/DeviceInfo.java index 70a397a7..479165d0 100644 --- a/app/src/main/java/org/cnodejs/android/md/storage/DeviceInfo.java +++ b/app/src/main/java/org/cnodejs/android/md/model/storage/DeviceInfo.java @@ -1,7 +1,6 @@ -package org.cnodejs.android.md.storage; +package org.cnodejs.android.md.model.storage; import android.content.Context; -import android.telephony.TelephonyManager; import android.text.TextUtils; import org.cnodejs.android.md.util.codec.Digest; @@ -22,13 +21,6 @@ public static String getDeviceToken(Context context) { if (TextUtils.isEmpty(deviceToken)) { deviceToken = context.getSharedPreferences(Digest.MD5.getMessage(TAG), Context.MODE_PRIVATE).getString(KEY_DEVICE_TOKEN, null); } - if (TextUtils.isEmpty(deviceToken)) { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - if (!TextUtils.isEmpty(tm.getDeviceId())) { - deviceToken = Digest.MD5.getMessage(tm.getDeviceId()); - context.getSharedPreferences(Digest.MD5.getMessage(TAG), Context.MODE_PRIVATE).edit().putString(KEY_DEVICE_TOKEN, deviceToken).apply(); - } - } if (TextUtils.isEmpty(deviceToken)) { deviceToken = Digest.MD5.getMessage(UUID.randomUUID().toString()); context.getSharedPreferences(Digest.MD5.getMessage(TAG), Context.MODE_PRIVATE).edit().putString(KEY_DEVICE_TOKEN, deviceToken).apply(); diff --git a/app/src/main/java/org/cnodejs/android/md/storage/LoginShared.java b/app/src/main/java/org/cnodejs/android/md/model/storage/LoginShared.java similarity index 96% rename from app/src/main/java/org/cnodejs/android/md/storage/LoginShared.java rename to app/src/main/java/org/cnodejs/android/md/model/storage/LoginShared.java index 482722c9..b95a87f8 100644 --- a/app/src/main/java/org/cnodejs/android/md/storage/LoginShared.java +++ b/app/src/main/java/org/cnodejs/android/md/model/storage/LoginShared.java @@ -1,9 +1,9 @@ -package org.cnodejs.android.md.storage; +package org.cnodejs.android.md.model.storage; import android.content.Context; import android.text.TextUtils; -import org.cnodejs.android.md.model.entity.LoginInfo; +import org.cnodejs.android.md.model.entity.Result; import org.cnodejs.android.md.model.entity.User; public final class LoginShared { @@ -24,7 +24,7 @@ private LoginShared() {} private static String avatarUrl; private static Integer score; - public static void login(Context context, String accessToken, LoginInfo loginInfo) { + public static void login(Context context, String accessToken, Result.Login loginInfo) { SharedWrapper sharedWrapper = SharedWrapper.with(context, TAG); sharedWrapper.setString(KEY_ACCESS_TOKEN, accessToken); sharedWrapper.setString(KEY_ID, loginInfo.getId()); diff --git a/app/src/main/java/org/cnodejs/android/md/storage/SettingShared.java b/app/src/main/java/org/cnodejs/android/md/model/storage/SettingShared.java similarity index 98% rename from app/src/main/java/org/cnodejs/android/md/storage/SettingShared.java rename to app/src/main/java/org/cnodejs/android/md/model/storage/SettingShared.java index 371d755f..3a061080 100644 --- a/app/src/main/java/org/cnodejs/android/md/storage/SettingShared.java +++ b/app/src/main/java/org/cnodejs/android/md/model/storage/SettingShared.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.storage; +package org.cnodejs.android.md.model.storage; import android.content.Context; diff --git a/app/src/main/java/org/cnodejs/android/md/storage/SharedWrapper.java b/app/src/main/java/org/cnodejs/android/md/model/storage/SharedWrapper.java similarity index 92% rename from app/src/main/java/org/cnodejs/android/md/storage/SharedWrapper.java rename to app/src/main/java/org/cnodejs/android/md/model/storage/SharedWrapper.java index 0d413d48..5f76fa47 100644 --- a/app/src/main/java/org/cnodejs/android/md/storage/SharedWrapper.java +++ b/app/src/main/java/org/cnodejs/android/md/model/storage/SharedWrapper.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.storage; +package org.cnodejs.android.md.model.storage; import android.content.Context; import android.content.SharedPreferences; @@ -6,7 +6,7 @@ import org.cnodejs.android.md.util.codec.DES3; import org.cnodejs.android.md.util.codec.Digest; -import org.cnodejs.android.md.util.gson.GsonWrapper; +import org.cnodejs.android.md.model.util.EntityUtils; import java.lang.reflect.Type; @@ -101,7 +101,7 @@ public T getObject(String key, Class clz) { return null; } else { try { - return GsonWrapper.gson.fromJson(json, clz); + return EntityUtils.gson.fromJson(json, clz); } catch (Exception e) { return null; } @@ -114,7 +114,7 @@ public T getObject(String key, Type typeOfT) { return null; } else { try { - return GsonWrapper.gson.fromJson(json, typeOfT); + return EntityUtils.gson.fromJson(json, typeOfT); } catch (Exception e) { return null; } @@ -125,7 +125,7 @@ public void setObject(String key, Object value) { if (value == null) { set(key, ""); } else { - String json = GsonWrapper.gson.toJson(value); + String json = EntityUtils.gson.toJson(value); set(key, json); } } diff --git a/app/src/main/java/org/cnodejs/android/md/storage/TopicShared.java b/app/src/main/java/org/cnodejs/android/md/model/storage/TopicShared.java similarity index 97% rename from app/src/main/java/org/cnodejs/android/md/storage/TopicShared.java rename to app/src/main/java/org/cnodejs/android/md/model/storage/TopicShared.java index cd9d0bd4..c375ec03 100644 --- a/app/src/main/java/org/cnodejs/android/md/storage/TopicShared.java +++ b/app/src/main/java/org/cnodejs/android/md/model/storage/TopicShared.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.storage; +package org.cnodejs.android.md.model.storage; import android.content.Context; diff --git a/app/src/main/java/org/cnodejs/android/md/util/gson/DateTimeTypeAdapter.java b/app/src/main/java/org/cnodejs/android/md/model/util/DateTimeTypeAdapter.java similarity index 95% rename from app/src/main/java/org/cnodejs/android/md/util/gson/DateTimeTypeAdapter.java rename to app/src/main/java/org/cnodejs/android/md/model/util/DateTimeTypeAdapter.java index 59536155..7da1f0a8 100644 --- a/app/src/main/java/org/cnodejs/android/md/util/gson/DateTimeTypeAdapter.java +++ b/app/src/main/java/org/cnodejs/android/md/model/util/DateTimeTypeAdapter.java @@ -1,4 +1,4 @@ -package org.cnodejs.android.md.util.gson; +package org.cnodejs.android.md.model.util; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; diff --git a/app/src/main/java/org/cnodejs/android/md/util/gson/GsonWrapper.java b/app/src/main/java/org/cnodejs/android/md/model/util/EntityUtils.java similarity index 70% rename from app/src/main/java/org/cnodejs/android/md/util/gson/GsonWrapper.java rename to app/src/main/java/org/cnodejs/android/md/model/util/EntityUtils.java index 1e6779fd..f09f06ad 100644 --- a/app/src/main/java/org/cnodejs/android/md/util/gson/GsonWrapper.java +++ b/app/src/main/java/org/cnodejs/android/md/model/util/EntityUtils.java @@ -1,13 +1,13 @@ -package org.cnodejs.android.md.util.gson; +package org.cnodejs.android.md.model.util; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.joda.time.DateTime; -public final class GsonWrapper { +public final class EntityUtils { - private GsonWrapper() {} + private EntityUtils() {} public static final Gson gson = new GsonBuilder() .registerTypeAdapter(DateTime.class, new DateTimeTypeAdapter()) diff --git a/app/src/main/java/org/cnodejs/android/md/model/util/HttpUtils.java b/app/src/main/java/org/cnodejs/android/md/model/util/HttpUtils.java new file mode 100644 index 00000000..6d9de63b --- /dev/null +++ b/app/src/main/java/org/cnodejs/android/md/model/util/HttpUtils.java @@ -0,0 +1,27 @@ +package org.cnodejs.android.md.model.util; + +import android.os.Build; + +import org.cnodejs.android.md.BuildConfig; + +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; + +public final class HttpUtils { + + private HttpUtils() {} + + public static final String USER_AGENT = "CNodeMD/" + BuildConfig.VERSION_NAME + " (Android " + Build.VERSION.RELEASE + "; " + Build.MANUFACTURER + " - " + Build.MODEL + ")"; + + public static final OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(new UserAgentInterceptor(USER_AGENT)) + .addInterceptor(createHttpLoggingInterceptor()) + .build(); + + private static HttpLoggingInterceptor createHttpLoggingInterceptor() { + HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); + loggingInterceptor.setLevel(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE); + return loggingInterceptor; + } + +} diff --git a/app/src/main/java/org/cnodejs/android/md/model/util/UserAgentInterceptor.java b/app/src/main/java/org/cnodejs/android/md/model/util/UserAgentInterceptor.java new file mode 100644 index 00000000..add44882 --- /dev/null +++ b/app/src/main/java/org/cnodejs/android/md/model/util/UserAgentInterceptor.java @@ -0,0 +1,30 @@ +package org.cnodejs.android.md.model.util; + +import android.text.TextUtils; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class UserAgentInterceptor implements Interceptor { + + private final String userAgent; + + public UserAgentInterceptor(String userAgent) { + if (TextUtils.isEmpty(userAgent)) { + throw new IllegalArgumentException("userAgent is null."); + } + this.userAgent = userAgent; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request().newBuilder() + .header("User-Agent", userAgent) + .build(); + return chain.proceed(request); + } + +} diff --git a/app/src/main/java/org/cnodejs/android/md/ui/activity/LoginActivity.java b/app/src/main/java/org/cnodejs/android/md/ui/activity/LoginActivity.java deleted file mode 100644 index d0378ee6..00000000 --- a/app/src/main/java/org/cnodejs/android/md/ui/activity/LoginActivity.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.cnodejs.android.md.ui.activity; - -import android.content.Intent; -import android.os.Bundle; -import android.support.v7.widget.Toolbar; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.rengwuxian.materialedittext.MaterialEditText; -import com.umeng.analytics.MobclickAgent; - -import org.cnodejs.android.md.R; -import org.cnodejs.android.md.model.api.ApiClient; -import org.cnodejs.android.md.model.entity.LoginInfo; -import org.cnodejs.android.md.storage.LoginShared; -import org.cnodejs.android.md.ui.base.StatusBarActivity; -import org.cnodejs.android.md.ui.listener.NavigationFinishClickListener; -import org.cnodejs.android.md.ui.widget.ThemeUtils; -import org.cnodejs.android.md.ui.widget.ToastUtils; - -import butterknife.Bind; -import butterknife.ButterKnife; -import butterknife.OnClick; -import retrofit.Callback; -import retrofit.RetrofitError; -import retrofit.client.Response; - -public class LoginActivity extends StatusBarActivity { - - private static final int REQUEST_QRCODE = 100; - - @Bind(R.id.login_toolbar) - protected Toolbar toolbar; - - @Bind(R.id.login_edt_access_token) - protected MaterialEditText edtAccessToken; - - @Override - protected void onCreate(Bundle savedInstanceState) { - ThemeUtils.configThemeBeforeOnCreate(this, R.style.AppThemeLight, R.style.AppThemeDark); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_login); - ButterKnife.bind(this); - - toolbar.setNavigationOnClickListener(new NavigationFinishClickListener(this)); - } - - @OnClick(R.id.login_btn_login) - protected void onBtnLoginClick() { - if (edtAccessToken.getText().length() < 36) { - edtAccessToken.setError(getString(R.string.access_token_format_error_tip)); - } else { - - final MaterialDialog dialog = new MaterialDialog.Builder(this) - .content(R.string.logging_in_$_) - .progress(true, 0) - .build(); - dialog.show(); - - final String accessToken = edtAccessToken.getText().toString(); - - ApiClient.service.accessToken(accessToken, new Callback() { - - @Override - public void success(LoginInfo loginInfo, Response response) { - if (dialog.isShowing()) { - dialog.dismiss(); - LoginShared.login(LoginActivity.this, accessToken, loginInfo); - ToastUtils.with(LoginActivity.this).show(R.string.login_success); - setResult(RESULT_OK); - finish(); - - MobclickAgent.onProfileSignIn(loginInfo.getLoginName()); // TODO 开始友盟账号统计 - } - } - - @Override - public void failure(RetrofitError error) { - if (dialog.isShowing()) { - dialog.dismiss(); - if (error.getResponse() != null && error.getResponse().getStatus() == 403) { - edtAccessToken.setError(getString(R.string.access_token_error)); - } else { - ToastUtils.with(LoginActivity.this).show(R.string.network_faild); - } - } - } - - }); - - } - } - - @OnClick(R.id.login_btn_qrcode) - protected void onBtnQrcodeClick() { - startActivityForResult(new Intent(this, QRCodeActivity.class), REQUEST_QRCODE); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_QRCODE && resultCode == RESULT_OK) { - edtAccessToken.setText(data.getStringExtra(QRCodeActivity.EXTRA_QRCODE)); - edtAccessToken.setSelection(edtAccessToken.length()); - } - } - -} diff --git a/app/src/main/java/org/cnodejs/android/md/ui/listener/CNodeWebViewClient.java b/app/src/main/java/org/cnodejs/android/md/ui/listener/CNodeWebViewClient.java deleted file mode 100644 index ac218a3b..00000000 --- a/app/src/main/java/org/cnodejs/android/md/ui/listener/CNodeWebViewClient.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.cnodejs.android.md.ui.listener; - -import android.content.Context; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -import org.cnodejs.android.md.ui.activity.TopicActivity; -import org.cnodejs.android.md.ui.activity.UserDetailActivity; -import org.cnodejs.android.md.util.ShipUtils; - -public class CNodeWebViewClient extends WebViewClient { - - private volatile static CNodeWebViewClient singleton; - - public static CNodeWebViewClient with(Context context) { - if (singleton == null) { - synchronized (CNodeWebViewClient.class) { - if (singleton == null) { - singleton = new CNodeWebViewClient(context); - } - } - } - return singleton; - } - - private final Context context; - - private CNodeWebViewClient(Context context) { - this.context = context.getApplicationContext(); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView webView, String url) { - if (url.startsWith("https://cnodejs.org/user/")) { // 用户主页协议 - UserDetailActivity.open(context, url.substring(25)); - } else if (url.startsWith("https://cnodejs.org/topic/")) { // 话题主页协议 - TopicActivity.open(context, url.substring(26)); - } else { // 其他连接 - ShipUtils.openInBrowser(context, url); - } - return true; - } - -} diff --git a/app/src/main/java/org/cnodejs/android/md/ui/widget/CNodeWebView.java b/app/src/main/java/org/cnodejs/android/md/ui/widget/CNodeWebView.java deleted file mode 100644 index ff2b1f19..00000000 --- a/app/src/main/java/org/cnodejs/android/md/ui/widget/CNodeWebView.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.cnodejs.android.md.ui.widget; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build; -import android.util.AttributeSet; -import android.webkit.WebView; - -import org.cnodejs.android.md.storage.SettingShared; -import org.cnodejs.android.md.ui.listener.CNodeWebViewClient; - -public class CNodeWebView extends WebView { - - private static final String THEME_CSS_LIGHT = "file:///android_asset/cnode_light.css"; - private static final String THEME_CSS_DARK = "file:///android_asset/cnode_dark.css"; - - private static final String HTML_0 = "" + - "\n" + - "\n" + - "\n" + - "\n" + - "\n"; - - private static final String HTML_1 = "" + - "\n" + - "\n"; - - private static final String HTML_2 = "" + - "\n" + - ""; - - public CNodeWebView(Context context) { - super(context); - setWebViewClient(CNodeWebViewClient.with(context)); - } - - public CNodeWebView(Context context, AttributeSet attrs) { - super(context, attrs); - setWebViewClient(CNodeWebViewClient.with(context)); - } - - public CNodeWebView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setWebViewClient(CNodeWebViewClient.with(context)); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public CNodeWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - setWebViewClient(CNodeWebViewClient.with(context)); - } - - private String getThemeCSS() { - return SettingShared.isEnableThemeDark(getContext()) ? THEME_CSS_DARK : THEME_CSS_LIGHT; - } - - public void loadRenderedContent(String data) { - data = HTML_0 + "\n" + HTML_1 + data + "\n" + HTML_2; - loadDataWithBaseURL(null, data, "text/html", "utf-8", null); - } - -} diff --git a/app/src/main/java/org/cnodejs/android/md/util/FormatUtils.java b/app/src/main/java/org/cnodejs/android/md/util/FormatUtils.java index c7704d7f..4f9161e2 100644 --- a/app/src/main/java/org/cnodejs/android/md/util/FormatUtils.java +++ b/app/src/main/java/org/cnodejs/android/md/util/FormatUtils.java @@ -1,15 +1,15 @@ package org.cnodejs.android.md.util; +import android.support.annotation.NonNull; import android.text.TextUtils; -import org.cnodejs.android.md.ui.activity.SettingActivity; import org.joda.time.DateTime; import org.tautua.markdownpapers.Markdown; import org.tautua.markdownpapers.parser.ParseException; import java.io.StringReader; import java.io.StringWriter; -import java.util.Date; +import java.util.UUID; public final class FormatUtils { @@ -26,37 +26,51 @@ private FormatUtils() {} private static final long month = 31 * day; private static final long year = 12 * month; - public static String getRecentlyTimeText(DateTime dateTime) { - if (dateTime == null) { - return null; - } - long diff = new Date().getTime() - dateTime.getMillis(); - long r; + public static String getRecentlyTimeText(@NonNull DateTime dateTime) { + long diff = new DateTime().getMillis() - dateTime.getMillis(); if (diff > year) { - r = (diff / year); - return r + "年前"; - } - if (diff > month) { - r = (diff / month); - return r + "个月前"; - } - if (diff > week) { - r = (diff / week); - return r + "周前"; - } - if (diff > day) { - r = (diff / day); - return r + "天前"; - } - if (diff > hour) { - r = (diff / hour); - return r + "小时前"; + return (diff / year) + "年前"; + } else if (diff > month) { + return (diff / month) + "个月前"; + } else if (diff > week) { + return (diff / week) + "周前"; + } else if (diff > day) { + return (diff / day) + "天前"; + } else if (diff > hour) { + return (diff / hour) + "小时前"; + } else if (diff > minute) { + return (diff / minute) + "分钟前"; + } else { + return "刚刚"; } - if (diff > minute) { - r = (diff / minute); - return r + "分钟前"; + } + + /** + * 检测是否是用户accessToken + */ + public static boolean isAccessToken(String accessToken) { + if (TextUtils.isEmpty(accessToken)) { + return false; + } else { + try { + //noinspection ResultOfMethodCallIgnored + UUID.fromString(accessToken); + return true; + } catch (Exception e) { + return false; + } } - return "刚刚"; + } + + /** + * 生成一个不重复的requestCode + */ + + private static int requestCodeSeed = 0; + + public synchronized static int createRequestCode() { + requestCodeSeed++; + return requestCodeSeed; } /** @@ -90,8 +104,11 @@ public static String getCompatAvatarUrl(String avatarUrl) { private static final Markdown md = new Markdown(); public static String renderMarkdown(String text) { - StringWriter out = new StringWriter(); + if (TextUtils.isEmpty(text)) { + text = ""; + } try { + StringWriter out = new StringWriter(); md.transform(new StringReader(text), out); text = out.toString(); } catch (ParseException e) { @@ -100,10 +117,12 @@ public static String renderMarkdown(String text) { return "
" + text + "
"; } - public static String handleHtml(String text) { - text = text.replace(" + app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> diff --git a/app/src/main/res/layout/activity_image_preview.xml b/app/src/main/res/layout/activity_image_preview.xml new file mode 100644 index 00000000..e5a1ede9 --- /dev/null +++ b/app/src/main/res/layout/activity_image_preview.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main_center.xml b/app/src/main/res/layout/activity_main_center.xml index 573727c2..9486a4be 100644 --- a/app/src/main/res/layout/activity_main_center.xml +++ b/app/src/main/res/layout/activity_main_center.xml @@ -27,29 +27,17 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - + - - + diff --git a/app/src/main/res/layout/activity_markdown_preview.xml b/app/src/main/res/layout/activity_markdown_preview.xml index 8692a02d..bc223dc9 100644 --- a/app/src/main/res/layout/activity_markdown_preview.xml +++ b/app/src/main/res/layout/activity_markdown_preview.xml @@ -22,7 +22,7 @@ android:paddingBottom="16dp" android:background="?attr/widgetBackground"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_topic_item_header.xml b/app/src/main/res/layout/activity_topic_item_header.xml index a63dbcc6..f67844a1 100644 --- a/app/src/main/res/layout/activity_topic_item_header.xml +++ b/app/src/main/res/layout/activity_topic_item_header.xml @@ -25,18 +25,17 @@ android:textStyle="bold" tools:text="话题的标题" /> - + android:paddingRight="16dp"> - - - + - + - + + + android:layout_marginTop="4dp" + android:orientation="horizontal" + android:gravity="center_vertical"> + tools:text="2天前创建" /> + + - + - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_progress.xml b/app/src/main/res/layout/dialog_progress.xml new file mode 100644 index 00000000..4679a045 --- /dev/null +++ b/app/src/main/res/layout/dialog_progress.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/layout/dialog_tool_insert_link.xml b/app/src/main/res/layout/dialog_tool_insert_link.xml index 3804038c..2d845d0a 100644 --- a/app/src/main/res/layout/dialog_tool_insert_link.xml +++ b/app/src/main/res/layout/dialog_tool_insert_link.xml @@ -2,15 +2,16 @@ + + + 70% + 90% + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9b751238..3acf4fdd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,7 +12,6 @@ 分享 问答 招聘 - 未知 消息 设置 @@ -21,9 +20,9 @@ 点击头像登录 再按一次退出 - 令牌验证错误 - 您的用户令牌已经失效,您可能需要重新登录账户。 - 令牌格式错误,应为32位UUID(四段分割)字符串 + AccessToken验证错误 + AccessToken格式错误,应为UUID + 您的AccessToken已经过期,您可能需要重新登录账户。 登录 注销 @@ -31,16 +30,14 @@ 确定要注销吗? 正在登录中… 登录成功 + 重新登录 暂无数据 暂无消息 暂无回复 - 数据加载失败,请重试 - 网络访问失败,请重试 数据加载失败,点击头像重试 已没有更多数据 - 用户不存在 - 话题不存在 + 不能帮自己点赞 话题 发布话题 @@ -58,7 +55,6 @@ 好的 创建于: - 发布于: 积分: 注册时间: / @@ -111,6 +107,7 @@ http:// Markdown预览 + 查看图片 程序崩溃 非常抱歉,程序运行过程中出现了一个无法避免的错误。您可以将该问题发送给开发者,此举将有助于改善应用体验。由此给您带来的诸多不便,我们深表歉意,敬请谅解。 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 9c7dd070..394edf9d 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -48,14 +48,25 @@ @color/color_primary - - - + + + +