├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── wj │ │ └── android │ │ └── todo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── wj │ │ │ └── android │ │ │ └── todo │ │ │ ├── MainApplication.java │ │ │ ├── activity │ │ │ ├── AddTodoActivity.java │ │ │ ├── EditTodoActivity.java │ │ │ ├── LoginActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── RegisterActivity.java │ │ │ ├── SplashActivity.java │ │ │ └── base │ │ │ │ └── BaseActivity.java │ │ │ ├── adapter │ │ │ └── TodoSectionAdapter.java │ │ │ ├── bean │ │ │ ├── TodoDesBean.java │ │ │ ├── TodoListBean.java │ │ │ └── TodoSection.java │ │ │ ├── constant │ │ │ ├── Constant.java │ │ │ └── TimeConstants.java │ │ │ ├── exception │ │ │ └── ApiException.java │ │ │ ├── fragment │ │ │ ├── SettingFragment.java │ │ │ ├── TodoFragment.java │ │ │ └── base │ │ │ │ └── BaseFragment.java │ │ │ ├── http │ │ │ ├── HttpUtils.java │ │ │ ├── MyGsonCallback.java │ │ │ └── ResponseItem.java │ │ │ ├── manager │ │ │ ├── PersistentCookieJarManager.java │ │ │ ├── PreferenceHelper.java │ │ │ └── SharePreferenceManager.java │ │ │ ├── util │ │ │ └── TimeUtils.java │ │ │ └── widget │ │ │ └── BottomNavigationViewEx.java │ └── res │ │ ├── color │ │ ├── selector_color.xml │ │ └── selector_nav_item_color.xml │ │ ├── drawable-xxhdpi │ │ ├── add_todo.png │ │ ├── arrow_right.png │ │ ├── back.png │ │ ├── cancel_todo.png │ │ ├── complete_todo.png │ │ ├── date.png │ │ └── delete_todo.png │ │ ├── drawable │ │ ├── ic_complete.xml │ │ ├── ic_setting.xml │ │ ├── ic_todo.xml │ │ └── selector_button.xml │ │ ├── layout │ │ ├── activity_add_todo.xml │ │ ├── activity_edit_todo.xml │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── activity_register.xml │ │ ├── activity_splash.xml │ │ ├── dialog_todo_des_view.xml │ │ ├── fragment_complete.xml │ │ ├── fragment_setting.xml │ │ ├── fragment_todo.xml │ │ ├── title_bar_layout.xml │ │ ├── todo_item_head.xml │ │ └── todo_item_view.xml │ │ ├── menu │ │ └── navigation.xml │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── network_security_config.xml │ └── test │ └── java │ └── com │ └── wj │ └── android │ └── todo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── release └── app-release.apk ├── screenshots ├── 000.png ├── 001.png ├── 002.png ├── 003.png ├── 004.png ├── 005.png ├── 006.png ├── 007.png ├── 008.png ├── 009.png ├── 010.png ├── 011.png └── 012.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wj-todo-wanandroid 2 | 用心打造一款极致体验的TODO开源客户端,数据接口来自鸿神的玩Android,不放过每一个细节,用心写代码,如果您觉得还不错的话,就点个Star吧~(您的每次支持,是我开源的动力) 3 | # APK 4 | app-release.apk 5 | # 项目架构 6 | 该项目使用最简单的MVC架构,整体代码实现层次分明,高内聚低耦合,代码逻辑清晰,通俗易懂,使用BottomNavigationView+ViewPager+Fragment完成UI主体实现,引入butterknife依赖注入框架,简化了代码的编写,网络层的编写,主要是引入了我另一个开源框架wj-http(主要是对Retrofit2进行了二次封装,方便使用,提升开发效率) 7 | 13 | # 项目运行截图 14 |
15 | 16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 | 24 |
25 | 26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 | 36 |
37 | 38 |
39 | 40 |
41 | 42 | ## Thanks 43 | 44 | ### API: 45 | 鸿洋大大提供的 46 | [WanAndroid API](http://www.wanandroid.com/) 47 | 48 | ### ICON: 49 | [iconfont](http://www.iconfont.cn/) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release 3 | /keystore 4 | *.iml -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.wj.android.todo" 7 | minSdkVersion 21 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | 14 | signingConfigs { 15 | release { 16 | keyAlias 'wj' 17 | keyPassword '19900914' 18 | storeFile file('keystore/wj.jks') 19 | storePassword '19900914' 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | signingConfig signingConfigs.release 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | buildConfigField('String', 'BASE_URL', FORMAL_BASE_URL) 29 | } 30 | 31 | debug { 32 | buildConfigField('String', 'BASE_URL', TEST_BASE_URL) 33 | } 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation fileTree(dir: 'libs', include: ['*.jar']) 39 | implementation 'com.android.support:appcompat-v7:28.0.0-beta01' 40 | implementation 'com.android.support:design:28.0.0-beta01' 41 | testImplementation 'junit:junit:4.12' 42 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 43 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 44 | 45 | implementation project(":wj-http") 46 | 47 | implementation 'com.jakewharton:butterknife:8.8.1' 48 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' 49 | 50 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.40' 51 | implementation 'com.android.support:recyclerview-v7:28.0.0-beta01' 52 | implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1' 53 | //implementation 'com.afollestad.material-dialogs:core:0.9.6.0' 54 | } 55 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/wj/android/todo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.wj.android.todo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 28 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo; 2 | 3 | import android.app.Application; 4 | 5 | import com.wj.android.http.XRetrofit; 6 | import com.wj.android.todo.manager.PersistentCookieJarManager; 7 | 8 | /** 9 | * 作者:wangwnejie on 2018/8/7 16:03 10 | * 邮箱:wang20080990@163.com 11 | */ 12 | public class MainApplication extends Application { 13 | 14 | @Override 15 | public void onCreate() { 16 | super.onCreate(); 17 | XRetrofit.init() 18 | .debug(BuildConfig.DEBUG) 19 | .cookieJar(PersistentCookieJarManager.getInstance(this).getPersistentCookieJar()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/activity/AddTodoActivity.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.activity; 2 | 3 | import android.app.DatePickerDialog; 4 | import android.content.Intent; 5 | import android.support.design.widget.TextInputEditText; 6 | import android.text.TextUtils; 7 | import android.view.View; 8 | import android.widget.DatePicker; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import com.google.gson.JsonObject; 13 | import com.wj.android.todo.R; 14 | import com.wj.android.todo.activity.base.BaseActivity; 15 | import com.wj.android.todo.bean.TodoDesBean; 16 | import com.wj.android.todo.http.HttpUtils; 17 | import com.wj.android.todo.http.ResponseItem; 18 | import com.wj.android.todo.util.TimeUtils; 19 | 20 | import java.util.Calendar; 21 | import java.util.Date; 22 | 23 | import butterknife.BindView; 24 | import butterknife.OnClick; 25 | 26 | /** 27 | * 作者:wangwnejie on 2018/8/8 19:21 28 | * 邮箱:wang20080990@163.com 29 | */ 30 | public class AddTodoActivity extends BaseActivity { 31 | @BindView(R.id.title) 32 | TextView mTitle; 33 | @BindView(R.id.back) 34 | ImageView mBack; 35 | @BindView(R.id.todo_date) 36 | TextView mTodoDate; 37 | @BindView(R.id.todo_name) 38 | TextInputEditText mTodoName; 39 | @BindView(R.id.todo_des) 40 | TextInputEditText mTodoDes; 41 | 42 | @Override 43 | protected int getLayoutId() { 44 | return R.layout.activity_add_todo; 45 | } 46 | 47 | @Override 48 | protected void initData() { 49 | mTitle.setText(R.string.add_todo); 50 | mBack.setVisibility(View.VISIBLE); 51 | 52 | mTodoDate.setText(TimeUtils.date2String(new Date(),"yyyy-MM-dd")); 53 | } 54 | 55 | @OnClick(R.id.back) 56 | void onClickBack() { 57 | finish(); 58 | } 59 | 60 | @OnClick(R.id.todo_date) 61 | void onClickTodoDate() { 62 | Calendar calendar = Calendar.getInstance(); 63 | 64 | DatePickerDialog datePickerDialog = new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() { 65 | @Override 66 | public void onDateSet(DatePicker datePicker, int year, int month, int dayOfMonth) { 67 | mTodoDate.setText(String.format("%d-%d-%d", year, month+1, dayOfMonth)); 68 | } 69 | },calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); 70 | datePickerDialog.getDatePicker().setMinDate(new Date().getTime()); 71 | datePickerDialog.show(); 72 | } 73 | 74 | @OnClick(R.id.save_todo) 75 | void onClickAddTodo() { 76 | mTodoName.setError(null); 77 | if (TextUtils.isEmpty(mTodoName.getText())) { 78 | mTodoName.setError(getString(R.string.input_todo_name_toast)); 79 | mTodoName.setFocusable(true); 80 | mTodoName.setFocusableInTouchMode(true); 81 | mTodoName.requestFocus(); 82 | return; 83 | } 84 | 85 | requestAddTodoData(); 86 | } 87 | 88 | private void requestAddTodoData() { 89 | HttpUtils.requestAddTodoData(this, mTodoName.getText().toString(), mTodoDes.getText().toString(), mTodoDate.getText().toString()); 90 | } 91 | 92 | public void updateUI(ResponseItem response) { 93 | if (response.isSuccess()) { 94 | showToast(getString(R.string.add_todo_success)); 95 | Intent intent = new Intent(); 96 | intent.putExtra("add_todo", response.getData()); 97 | setResult(0x200, intent); 98 | finish(); 99 | } 100 | } 101 | 102 | @Override 103 | public boolean isLoadingEnable(int requestId) { 104 | return super.isLoadingEnable(requestId); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/activity/EditTodoActivity.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.activity; 2 | 3 | import android.app.DatePickerDialog; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.design.widget.TextInputEditText; 7 | import android.text.TextUtils; 8 | import android.view.View; 9 | import android.widget.Button; 10 | import android.widget.DatePicker; 11 | import android.widget.ImageView; 12 | import android.widget.TextView; 13 | 14 | import com.wj.android.todo.R; 15 | import com.wj.android.todo.activity.base.BaseActivity; 16 | import com.wj.android.todo.bean.TodoDesBean; 17 | import com.wj.android.todo.http.HttpUtils; 18 | import com.wj.android.todo.http.ResponseItem; 19 | import com.wj.android.todo.util.TimeUtils; 20 | 21 | import java.util.Calendar; 22 | import java.util.Date; 23 | 24 | import butterknife.BindView; 25 | import butterknife.OnClick; 26 | 27 | /** 28 | * 作者:wangwnejie on 2018/8/8 19:21 29 | * 邮箱:wang20080990@163.com 30 | */ 31 | public class EditTodoActivity extends BaseActivity { 32 | @BindView(R.id.title) 33 | TextView mTitle; 34 | @BindView(R.id.back) 35 | ImageView mBack; 36 | @BindView(R.id.todo_date) 37 | TextView mTodoDate; 38 | @BindView(R.id.todo_name) 39 | TextInputEditText mTodoName; 40 | @BindView(R.id.todo_des) 41 | TextInputEditText mTodoDes; 42 | @BindView(R.id.save_todo) 43 | Button mSaveTodo; 44 | 45 | private TodoDesBean mTodoDesBean; 46 | 47 | @Override 48 | protected int getLayoutId() { 49 | return R.layout.activity_edit_todo; 50 | } 51 | 52 | @Override 53 | protected void initData() { 54 | mTitle.setText(R.string.update_todo); 55 | mBack.setVisibility(View.VISIBLE); 56 | 57 | Bundle bundle = getIntent().getExtras(); 58 | if (bundle == null) { 59 | return; 60 | } 61 | mTodoDesBean = (TodoDesBean) bundle.getSerializable("todo_des"); 62 | if (mTodoDesBean == null) { 63 | return; 64 | } 65 | mTodoName.setText(mTodoDesBean.getTitle()); 66 | if (!TextUtils.isEmpty(mTodoDesBean.getContent())) { 67 | mTodoDes.setText(mTodoDesBean.getContent()); 68 | } 69 | mTodoDate.setText(mTodoDesBean.getDateStr()); 70 | 71 | if (mTodoDesBean.getStatus() == 1) { 72 | mSaveTodo.setVisibility(View.GONE); 73 | mTodoName.setEnabled(false); 74 | mTodoName.setFocusable(false); 75 | mTodoDes.setEnabled(false); 76 | mTodoDes.setFocusable(false); 77 | mTodoDate.setEnabled(false); 78 | mTodoDate.setFocusable(false); 79 | } 80 | } 81 | 82 | @OnClick(R.id.back) 83 | void onClickBack() { 84 | finish(); 85 | } 86 | 87 | @OnClick(R.id.todo_date) 88 | void onClickTodoDate() { 89 | Calendar calendar = Calendar.getInstance(); 90 | 91 | DatePickerDialog datePickerDialog = new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() { 92 | @Override 93 | public void onDateSet(DatePicker datePicker, int year, int month, int dayOfMonth) { 94 | mTodoDate.setText(String.format("%d-%d-%d", year, month+1, dayOfMonth)); 95 | } 96 | },calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH)); 97 | datePickerDialog.getDatePicker().setMinDate(new Date().getTime()); 98 | datePickerDialog.show(); 99 | } 100 | 101 | @OnClick(R.id.save_todo) 102 | void onClickAddTodo() { 103 | mTodoName.setError(null); 104 | if (TextUtils.isEmpty(mTodoName.getText())) { 105 | mTodoName.setError(getString(R.string.input_todo_name_toast)); 106 | mTodoName.setFocusable(true); 107 | mTodoName.setFocusableInTouchMode(true); 108 | mTodoName.requestFocus(); 109 | return; 110 | } 111 | 112 | requestUpdateTodoData(); 113 | } 114 | 115 | private void requestUpdateTodoData() { 116 | HttpUtils.requestUpdateTodoData(this, mTodoDesBean.getId(), mTodoName.getText().toString(), mTodoDes.getText().toString(), mTodoDate.getText().toString()); 117 | } 118 | 119 | public void updateUI(ResponseItem response) { 120 | if (response.isSuccess()) { 121 | showToast(getString(R.string.update_todo_success)); 122 | Intent intent = new Intent(); 123 | intent.putExtra("update_todo", response.getData()); 124 | setResult(0x210, intent); 125 | finish(); 126 | } 127 | } 128 | 129 | @Override 130 | public boolean isLoadingEnable(int requestId) { 131 | return super.isLoadingEnable(requestId); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/activity/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.activity; 2 | import android.text.TextUtils; 3 | import android.widget.AutoCompleteTextView; 4 | import android.widget.EditText; 5 | 6 | import com.google.gson.JsonObject; 7 | import com.wj.android.todo.R; 8 | import com.wj.android.todo.activity.base.BaseActivity; 9 | import com.wj.android.todo.http.HttpUtils; 10 | import com.wj.android.todo.http.ResponseItem; 11 | import com.wj.android.todo.manager.SharePreferenceManager; 12 | 13 | import butterknife.BindView; 14 | import butterknife.OnClick; 15 | 16 | public class LoginActivity extends BaseActivity{ 17 | @BindView(R.id.account) 18 | AutoCompleteTextView mAccount; 19 | @BindView(R.id.password) 20 | EditText mPassword; 21 | 22 | @Override 23 | protected int getLayoutId() { 24 | return R.layout.activity_login; 25 | } 26 | 27 | @Override 28 | protected void initData() { 29 | 30 | } 31 | 32 | @OnClick(R.id.regitster) 33 | void clickRegister() { 34 | startActivity(RegisterActivity.class); 35 | } 36 | 37 | @OnClick(R.id.login) 38 | void clickLogin() { 39 | mAccount.setError(null); 40 | mPassword.setError(null); 41 | if (TextUtils.isEmpty(mAccount.getText())) { 42 | mAccount.setError(getString(R.string.input_account)); 43 | mAccount.setFocusable(true); 44 | mAccount.setFocusableInTouchMode(true); 45 | mAccount.requestFocus(); 46 | return; 47 | } 48 | 49 | if (TextUtils.isEmpty(mPassword.getText())) { 50 | mPassword.setError(getString(R.string.input_password)); 51 | mPassword.setFocusable(true); 52 | mPassword.setFocusableInTouchMode(true); 53 | mPassword.requestFocus(); 54 | return; 55 | } 56 | 57 | requestLogin(); 58 | } 59 | 60 | @Override 61 | public boolean isLoadingEnable(int requestId) { 62 | return super.isLoadingEnable(requestId); 63 | } 64 | 65 | private void requestLogin() { 66 | HttpUtils.requestLogin(this, mAccount.getText().toString(), mPassword.getText().toString()); 67 | } 68 | 69 | public void updateUI(ResponseItem response) { 70 | if (response.isSuccess()) { 71 | SharePreferenceManager.getInstance(this).setUserName(response.getData().get("username").getAsString()); 72 | startActivity(MainActivity.class); 73 | finish(); 74 | } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.activity; 2 | 3 | import android.content.Intent; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.app.FragmentManager; 7 | import android.support.v4.app.FragmentPagerAdapter; 8 | import android.support.v4.view.ViewPager; 9 | import android.view.View; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | import com.wj.android.todo.R; 14 | import com.wj.android.todo.activity.base.BaseActivity; 15 | import com.wj.android.todo.bean.TodoDesBean; 16 | import com.wj.android.todo.fragment.SettingFragment; 17 | import com.wj.android.todo.fragment.TodoFragment; 18 | import com.wj.android.todo.widget.BottomNavigationViewEx; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import butterknife.BindView; 24 | import butterknife.OnClick; 25 | 26 | /** 27 | * 作者:wangwnejie on 2018/8/7 11:17 28 | * 邮箱:wang20080990@163.com 29 | */ 30 | public class MainActivity extends BaseActivity { 31 | private static final int REQUEST_CODE_ADD_TODO = 0x100; 32 | 33 | @BindView(R.id.view_pager) 34 | ViewPager mViewPager; 35 | @BindView(R.id.navigation) 36 | BottomNavigationViewEx mNavigationView; 37 | @BindView(R.id.title) 38 | TextView mTitle; 39 | @BindView(R.id.add_todo) 40 | ImageView mAddTodo; 41 | 42 | @Override 43 | protected int getLayoutId() { 44 | return R.layout.activity_main; 45 | } 46 | 47 | @Override 48 | protected void initData() { 49 | mTitle.setText(R.string.to_do_list); 50 | mAddTodo.setVisibility(View.VISIBLE); 51 | 52 | mViewPager.setOffscreenPageLimit(3); 53 | 54 | List fragments = new ArrayList<>(3); 55 | fragments.add(TodoFragment.newInstance(false)); 56 | fragments.add(TodoFragment.newInstance(true)); 57 | fragments.add(SettingFragment.newInstance()); 58 | 59 | MainViewPagerAdapter adapter = new MainViewPagerAdapter(getSupportFragmentManager(), fragments); 60 | mViewPager.setAdapter(adapter); 61 | 62 | mNavigationView.setupWithViewPager(mViewPager); 63 | } 64 | 65 | @Override 66 | protected void applyEvent() { 67 | mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 68 | @Override 69 | public void onPageScrolled(int position, float v, int i1) { 70 | 71 | } 72 | 73 | @Override 74 | public void onPageSelected(int position) { 75 | switch (position) { 76 | case 0: 77 | mTitle.setText(R.string.to_do_list); 78 | mAddTodo.setVisibility(View.VISIBLE); 79 | break; 80 | case 1: 81 | mTitle.setText(R.string.complete_list); 82 | mAddTodo.setVisibility(View.INVISIBLE); 83 | break; 84 | case 2: 85 | mTitle.setText(R.string.setting); 86 | mAddTodo.setVisibility(View.INVISIBLE); 87 | break; 88 | } 89 | } 90 | 91 | @Override 92 | public void onPageScrollStateChanged(int position) { 93 | 94 | } 95 | }); 96 | } 97 | 98 | @OnClick(R.id.add_todo) 99 | void onClickAddTodo() { 100 | startActivityForResult(AddTodoActivity.class, REQUEST_CODE_ADD_TODO); 101 | } 102 | 103 | private static class MainViewPagerAdapter extends FragmentPagerAdapter { 104 | 105 | private List mFragments; 106 | 107 | public MainViewPagerAdapter(FragmentManager fm, List fragments) { 108 | super(fm); 109 | mFragments = fragments; 110 | } 111 | 112 | @Override 113 | public Fragment getItem(int position) { 114 | return mFragments.get(position); 115 | } 116 | 117 | @Override 118 | public int getCount() { 119 | return mFragments.size(); 120 | } 121 | } 122 | 123 | @Override 124 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 125 | super.onActivityResult(requestCode, resultCode, data); 126 | if (requestCode == REQUEST_CODE_ADD_TODO) { 127 | switch (resultCode) { 128 | case 0x200: 129 | TodoDesBean todoDesBean = (TodoDesBean) data.getSerializableExtra("add_todo"); 130 | TodoFragment todoFragment = (TodoFragment)((MainViewPagerAdapter)mViewPager.getAdapter()).getItem(0); 131 | todoFragment.updateAddTodoData(todoDesBean); 132 | break; 133 | } 134 | } 135 | } 136 | 137 | public void updateDoneOrCancelData(TodoDesBean todoDesBean, int postition) { 138 | TodoFragment todoFragment = (TodoFragment)((MainViewPagerAdapter)mViewPager.getAdapter()).getItem(postition); 139 | todoFragment.updateAddTodoData(todoDesBean); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/activity/RegisterActivity.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.activity; 2 | 3 | import android.text.TextUtils; 4 | import android.view.View; 5 | import android.widget.EditText; 6 | import android.widget.ImageView; 7 | import android.widget.TextView; 8 | 9 | import com.google.gson.JsonObject; 10 | import com.wj.android.todo.R; 11 | import com.wj.android.todo.activity.base.BaseActivity; 12 | import com.wj.android.todo.http.HttpUtils; 13 | import com.wj.android.todo.http.ResponseItem; 14 | 15 | import butterknife.BindView; 16 | import butterknife.OnClick; 17 | 18 | /** 19 | * 作者:wangwnejie on 2018/8/8 18:20 20 | * 邮箱:wang20080990@163.com 21 | */ 22 | public class RegisterActivity extends BaseActivity { 23 | @BindView(R.id.title) 24 | TextView mTitle; 25 | @BindView(R.id.back) 26 | ImageView mBack; 27 | @BindView(R.id.account) 28 | EditText mAccount; 29 | @BindView(R.id.password) 30 | EditText mPassword; 31 | @BindView(R.id.repassword) 32 | EditText mRepassword; 33 | 34 | 35 | @Override 36 | protected int getLayoutId() { 37 | return R.layout.activity_register; 38 | } 39 | 40 | @Override 41 | protected void initData() { 42 | mTitle.setText(R.string.register); 43 | mBack.setVisibility(View.VISIBLE); 44 | } 45 | 46 | @OnClick(R.id.regitster) 47 | void onClickRegister() { 48 | mAccount.setError(null); 49 | mPassword.setError(null); 50 | if (TextUtils.isEmpty(mAccount.getText())) { 51 | mAccount.setError(getString(R.string.input_account)); 52 | mAccount.setFocusable(true); 53 | mAccount.setFocusableInTouchMode(true); 54 | mAccount.requestFocus(); 55 | return; 56 | } 57 | 58 | if (TextUtils.isEmpty(mPassword.getText())) { 59 | mPassword.setError(getString(R.string.input_password)); 60 | mPassword.setFocusable(true); 61 | mPassword.setFocusableInTouchMode(true); 62 | mPassword.requestFocus(); 63 | return; 64 | } 65 | 66 | if (TextUtils.isEmpty(mRepassword.getText())) { 67 | mRepassword.setError(getString(R.string.input_repassword)); 68 | mRepassword.setFocusable(true); 69 | mRepassword.setFocusableInTouchMode(true); 70 | mRepassword.requestFocus(); 71 | return; 72 | } 73 | 74 | if (!TextUtils.equals(mPassword.getText(), mRepassword.getText())) { 75 | mRepassword.setError(getString(R.string.valid_repassword)); 76 | return; 77 | } 78 | requestRegister(); 79 | } 80 | 81 | private void requestRegister() { 82 | HttpUtils.requestRegister(this, mAccount.getText().toString(), mPassword.getText().toString(), mRepassword.getText().toString()); 83 | } 84 | 85 | @OnClick(R.id.back) 86 | void onClickBack() { 87 | finish(); 88 | } 89 | 90 | public void updateUI(ResponseItem response) { 91 | if (response.isSuccess()) { 92 | showToast(getResources().getString(R.string.register_success)); 93 | finish(); 94 | } 95 | } 96 | 97 | @Override 98 | public boolean isLoadingEnable(int requestId) { 99 | return super.isLoadingEnable(requestId); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/activity/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.activity; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.view.animation.AlphaAnimation; 6 | import android.view.animation.Animation; 7 | 8 | import com.wj.android.todo.BuildConfig; 9 | import com.wj.android.todo.R; 10 | import com.wj.android.todo.activity.base.BaseActivity; 11 | import com.wj.android.todo.constant.Constant; 12 | import com.wj.android.todo.manager.PersistentCookieJarManager; 13 | 14 | import java.util.List; 15 | 16 | import butterknife.BindView; 17 | import okhttp3.Cookie; 18 | import okhttp3.HttpUrl; 19 | 20 | /** 21 | * 作者:wangwnejie on 2018/8/8 16:01 22 | * 邮箱:wang20080990@163.com 23 | */ 24 | public class SplashActivity extends BaseActivity { 25 | 26 | @BindView(R.id.root_view) 27 | View mView; 28 | 29 | @Override 30 | protected int getLayoutId() { 31 | return R.layout.activity_splash; 32 | } 33 | 34 | @Override 35 | protected void initData() { 36 | AlphaAnimation animation = new AlphaAnimation(0.3f, 1.0f); 37 | animation.setDuration(3000); 38 | mView.startAnimation(animation); 39 | 40 | animation.setAnimationListener(new Animation.AnimationListener() { 41 | @Override 42 | public void onAnimationStart(Animation animation) { 43 | } 44 | 45 | @Override 46 | public void onAnimationEnd(Animation animation) { 47 | redirectTo(); 48 | } 49 | 50 | @Override 51 | public void onAnimationRepeat(Animation animation) { 52 | } 53 | }); 54 | } 55 | 56 | private void redirectTo() { 57 | List cookies = PersistentCookieJarManager.getInstance(this).getPersistentCookieJar().loadForRequest(HttpUrl.parse(BuildConfig.BASE_URL)); 58 | if (cookies.isEmpty()) { 59 | startActivity(LoginActivity.class); 60 | } else { 61 | Bundle bundle = new Bundle(); 62 | bundle.putString("user_name", cookies.get(0).name()); 63 | startActivity(MainActivity.class, bundle); 64 | } 65 | finish(); 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/activity/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.activity.base; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.util.Log; 9 | import android.widget.Toast; 10 | import com.wj.android.http.BaseView; 11 | import com.wj.android.todo.R; 12 | import com.wj.android.todo.exception.ApiException; 13 | 14 | import butterknife.ButterKnife; 15 | 16 | /** 17 | * 作者:wangwnejie on 2018/3/22 16:57 18 | * 邮箱:wang20080990@163.com 19 | */ 20 | 21 | public abstract class BaseActivity extends AppCompatActivity implements BaseView { 22 | private static final String TAG = BaseActivity.class.getSimpleName(); 23 | private ProgressDialog mProgressDialog; 24 | 25 | @Override 26 | protected void onCreate(@Nullable Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | Log.i(TAG, String.format("%s:onCreate", this)); 29 | if (getLayoutId() != 0) { 30 | setContentView(getLayoutId()); 31 | } 32 | ButterKnife.bind(this); 33 | 34 | initView(); 35 | initData(); 36 | applyEvent(); 37 | } 38 | 39 | @Override 40 | protected void onStart() { 41 | super.onStart(); 42 | Log.i(TAG, String.format("%s:onStart", this)); 43 | } 44 | 45 | @Override 46 | protected void onRestart() { 47 | super.onRestart(); 48 | Log.i(TAG, String.format("%s:onRestart", this)); 49 | } 50 | 51 | @Override 52 | protected void onResume() { 53 | super.onResume(); 54 | Log.i(TAG, String.format("%s:onResume", this)); 55 | } 56 | 57 | @Override 58 | protected void onPause() { 59 | super.onPause(); 60 | Log.i(TAG, String.format("%s:onPause", this)); 61 | } 62 | 63 | @Override 64 | protected void onStop() { 65 | super.onStop(); 66 | Log.i(TAG, String.format("%s:onStop", this)); 67 | } 68 | 69 | @Override 70 | protected void onDestroy() { 71 | super.onDestroy(); 72 | Log.i(TAG, String.format("%s:onDestroy", this)); 73 | if (mProgressDialog != null) { 74 | mProgressDialog.dismiss(); 75 | } 76 | 77 | } 78 | /** 79 | * 返回当前界面布局文件 80 | * @return 81 | */ 82 | protected abstract int getLayoutId(); 83 | 84 | /** 85 | * 初始化View 86 | */ 87 | protected void initView(){} 88 | 89 | /** 90 | * 初始化数据 91 | */ 92 | protected abstract void initData(); 93 | 94 | /** 95 | * 设置事件监听 96 | */ 97 | protected void applyEvent(){} 98 | 99 | @Override 100 | public boolean isLoadingEnable(int requestId) { 101 | return false; 102 | } 103 | 104 | @Override 105 | public void start(int requestId) { 106 | if (isLoadingEnable(requestId) && !isFinishing()) { 107 | mProgressDialog = new ProgressDialog(this,ProgressDialog.THEME_HOLO_LIGHT); 108 | mProgressDialog.setCanceledOnTouchOutside(false); 109 | mProgressDialog.setCancelable(false); 110 | mProgressDialog.setMessage(getResources().getString(R.string.loading)); 111 | mProgressDialog.show(); 112 | } 113 | } 114 | 115 | @Override 116 | public void error(Throwable t, int code, int requestId) { 117 | if (t instanceof ApiException) { 118 | showToast(t.getMessage()); 119 | } else { 120 | showToast(getResources().getString(R.string.service_error)); 121 | } 122 | } 123 | 124 | @Override 125 | public void end(int requestId) { 126 | if (isLoadingEnable(requestId) && !isFinishing()) { 127 | mProgressDialog.dismiss(); 128 | } 129 | } 130 | 131 | public void showToast(String msg) { 132 | Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); 133 | } 134 | 135 | public void startActivity(Class cls) { 136 | Intent intent = new Intent(this, cls); 137 | startActivity(intent); 138 | } 139 | 140 | public void startActivity(Class cls, Bundle bundle) { 141 | Intent intent = new Intent(this, cls); 142 | intent.putExtras(bundle); 143 | startActivity(intent); 144 | } 145 | 146 | public void startActivityForResult(Class cls, int requestCode) { 147 | Intent intent = new Intent(this, cls); 148 | startActivityForResult(intent, requestCode); 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/adapter/TodoSectionAdapter.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.adapter; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.chad.library.adapter.base.BaseSectionQuickAdapter; 6 | import com.chad.library.adapter.base.BaseViewHolder; 7 | import com.wj.android.todo.R; 8 | import com.wj.android.todo.bean.TodoSection; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * 作者:wangwnejie on 2018/8/9 14:41 14 | * 邮箱:wang20080990@163.com 15 | */ 16 | public class TodoSectionAdapter extends BaseSectionQuickAdapter { 17 | private boolean isDone; 18 | 19 | public TodoSectionAdapter(int layoutResId, int sectionHeadResId, List data, boolean isDone) { 20 | super(layoutResId, sectionHeadResId, data); 21 | this.isDone = isDone; 22 | } 23 | 24 | @Override 25 | protected void convertHead(BaseViewHolder helper, TodoSection item) { 26 | helper.setText(R.id.todo_head, item.header); 27 | if (isDone) { 28 | helper.setTextColor(R.id.todo_head, mContext.getResources().getColor(R.color.done_todo_date)); 29 | } else { 30 | helper.setTextColor(R.id.todo_head, mContext.getResources().getColor(R.color.colorPrimary)); 31 | } 32 | } 33 | 34 | @Override 35 | protected void convert(BaseViewHolder helper, TodoSection item) { 36 | helper.setText(R.id.item_name, item.t.getTitle()); 37 | if (TextUtils.isEmpty(item.t.getContent())) { 38 | helper.setGone(R.id.item_des, false); 39 | } else { 40 | helper.setGone(R.id.item_des, true); 41 | helper.setText(R.id.item_des, item.t.getContent()); 42 | } 43 | 44 | if (isDone) { 45 | helper.setGone(R.id.item_done_time, true); 46 | helper.setText(R.id.item_done_time, String.format(mContext.getResources().getString(R.string.done_todo_date),item.t.getCompleteDateStr())); 47 | helper.setImageResource(R.id.item_complete, R.drawable.cancel_todo); 48 | } else { 49 | helper.setGone(R.id.item_done_time, false); 50 | helper.setImageResource(R.id.item_complete, R.drawable.complete_todo); 51 | } 52 | 53 | helper.addOnClickListener(R.id.item_complete); 54 | helper.addOnClickListener(R.id.item_delete); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/bean/TodoDesBean.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.bean; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 作者:wangwnejie on 2018/8/9 14:29 7 | * 邮箱:wang20080990@163.com 8 | */ 9 | public class TodoDesBean implements Serializable{ 10 | 11 | private Object completeDate; 12 | private String completeDateStr; 13 | private String content; 14 | private long date; 15 | private String dateStr; 16 | private int id; 17 | private int status; 18 | private String title; 19 | private int type; 20 | private int userId; 21 | 22 | public Object getCompleteDate() { 23 | return completeDate; 24 | } 25 | 26 | public void setCompleteDate(Object completeDate) { 27 | this.completeDate = completeDate; 28 | } 29 | 30 | public String getCompleteDateStr() { 31 | return completeDateStr; 32 | } 33 | 34 | public void setCompleteDateStr(String completeDateStr) { 35 | this.completeDateStr = completeDateStr; 36 | } 37 | 38 | public String getContent() { 39 | return content; 40 | } 41 | 42 | public void setContent(String content) { 43 | this.content = content; 44 | } 45 | 46 | public long getDate() { 47 | return date; 48 | } 49 | 50 | public void setDate(long date) { 51 | this.date = date; 52 | } 53 | 54 | public String getDateStr() { 55 | return dateStr; 56 | } 57 | 58 | public void setDateStr(String dateStr) { 59 | this.dateStr = dateStr; 60 | } 61 | 62 | public int getId() { 63 | return id; 64 | } 65 | 66 | public void setId(int id) { 67 | this.id = id; 68 | } 69 | 70 | public int getStatus() { 71 | return status; 72 | } 73 | 74 | public void setStatus(int status) { 75 | this.status = status; 76 | } 77 | 78 | public String getTitle() { 79 | return title; 80 | } 81 | 82 | public void setTitle(String title) { 83 | this.title = title; 84 | } 85 | 86 | public int getType() { 87 | return type; 88 | } 89 | 90 | public void setType(int type) { 91 | this.type = type; 92 | } 93 | 94 | public int getUserId() { 95 | return userId; 96 | } 97 | 98 | public void setUserId(int userId) { 99 | this.userId = userId; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/bean/TodoListBean.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.bean; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 作者:wangwnejie on 2018/8/9 14:55 7 | * 邮箱:wang20080990@163.com 8 | */ 9 | public class TodoListBean { 10 | private int curPage; 11 | private int offset; 12 | private boolean over; 13 | private int pageCount; 14 | private int size; 15 | private int total; 16 | private List datas; 17 | 18 | public int getCurPage() { 19 | return curPage; 20 | } 21 | 22 | public void setCurPage(int curPage) { 23 | this.curPage = curPage; 24 | } 25 | 26 | public int getOffset() { 27 | return offset; 28 | } 29 | 30 | public void setOffset(int offset) { 31 | this.offset = offset; 32 | } 33 | 34 | public boolean isOver() { 35 | return over; 36 | } 37 | 38 | public void setOver(boolean over) { 39 | this.over = over; 40 | } 41 | 42 | public int getPageCount() { 43 | return pageCount; 44 | } 45 | 46 | public void setPageCount(int pageCount) { 47 | this.pageCount = pageCount; 48 | } 49 | 50 | public int getSize() { 51 | return size; 52 | } 53 | 54 | public void setSize(int size) { 55 | this.size = size; 56 | } 57 | 58 | public int getTotal() { 59 | return total; 60 | } 61 | 62 | public void setTotal(int total) { 63 | this.total = total; 64 | } 65 | 66 | public List getDatas() { 67 | return datas; 68 | } 69 | 70 | public void setDatas(List datas) { 71 | this.datas = datas; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/bean/TodoSection.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.bean; 2 | 3 | import com.chad.library.adapter.base.entity.SectionEntity; 4 | 5 | /** 6 | * 作者:wangwnejie on 2018/8/9 14:33 7 | * 邮箱:wang20080990@163.com 8 | */ 9 | public class TodoSection extends SectionEntity { 10 | 11 | public TodoSection(boolean isHeader, String header) { 12 | super(isHeader, header); 13 | } 14 | 15 | public TodoSection(TodoDesBean todoBean) { 16 | super(todoBean); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/constant/Constant.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.constant; 2 | 3 | /** 4 | * 作者:wangwnejie on 2018/8/7 15:24 5 | * 邮箱:wang20080990@163.com 6 | */ 7 | public class Constant { 8 | //public static final String BASE_URL = "http://www.wanandroid.com"; 9 | 10 | public static final String LOGIN_URI = "/user/login"; 11 | public static final String REGISTER_URI = "/user/register"; 12 | public static final String ADD_TODO_URI = "/lg/todo/add/json"; 13 | public static final String TODO_LIST_URI = "/lg/todo/listnotdo/0/json/%d"; 14 | public static final String DELETE_TODO_URI = "/lg/todo/delete/%d/json"; 15 | public static final String UPDATE_TODO_URI = "/lg/todo/update/%d/json"; 16 | public static final String DONE_TODO_URI = "/lg/todo/done/%d/json"; 17 | public static final String DONE_LIST_URI = "/lg/todo/listdone/0/json/%d"; 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/constant/TimeConstants.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.constant; 2 | 3 | import android.support.annotation.IntDef; 4 | 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | 8 | /** 9 | *
10 |  *     author: Blankj
11 |  *     blog  : http://blankj.com
12 |  *     time  : 2017/03/13
13 |  *     desc  : constants of time
14 |  * 
15 | */ 16 | public final class TimeConstants { 17 | 18 | public static final int MSEC = 1; 19 | public static final int SEC = 1000; 20 | public static final int MIN = 60000; 21 | public static final int HOUR = 3600000; 22 | public static final int DAY = 86400000; 23 | 24 | @IntDef({MSEC, SEC, MIN, HOUR, DAY}) 25 | @Retention(RetentionPolicy.SOURCE) 26 | public @interface Unit { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/exception/ApiException.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.exception; 2 | 3 | /** 4 | * 作者:wangwnejie on 2018/8/8 14:06 5 | * 邮箱:wang20080990@163.com 6 | */ 7 | public class ApiException extends RuntimeException{ 8 | 9 | private int errorCode; 10 | 11 | public ApiException(String message, int errorCode) { 12 | super(message); 13 | setErrorCode(errorCode); 14 | } 15 | 16 | public int getErrorCode() { 17 | return errorCode; 18 | } 19 | 20 | public void setErrorCode(int errorCode) { 21 | this.errorCode = errorCode; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/fragment/SettingFragment.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.fragment; 2 | 3 | import android.os.Bundle; 4 | import android.text.TextUtils; 5 | import android.widget.TextView; 6 | 7 | import com.wj.android.todo.R; 8 | import com.wj.android.todo.activity.LoginActivity; 9 | import com.wj.android.todo.activity.MainActivity; 10 | import com.wj.android.todo.fragment.base.BaseFragment; 11 | import com.wj.android.todo.manager.PersistentCookieJarManager; 12 | import com.wj.android.todo.manager.SharePreferenceManager; 13 | 14 | import java.util.List; 15 | 16 | import butterknife.BindView; 17 | import butterknife.OnClick; 18 | import okhttp3.Cookie; 19 | 20 | /** 21 | * 作者:wangwnejie on 2018/8/8 13:42 22 | * 邮箱:wang20080990@163.com 23 | */ 24 | public class SettingFragment extends BaseFragment { 25 | @BindView(R.id.user_name) 26 | TextView mUserName; 27 | 28 | public static SettingFragment newInstance() { 29 | 30 | Bundle args = new Bundle(); 31 | SettingFragment fragment = new SettingFragment(); 32 | fragment.setArguments(args); 33 | return fragment; 34 | } 35 | 36 | @Override 37 | protected int getLayoutId() { 38 | return R.layout.fragment_setting; 39 | } 40 | 41 | @Override 42 | protected void initData() { 43 | String userName = SharePreferenceManager.getInstance(getContext()).getUserName(); 44 | if (!TextUtils.isEmpty(userName)) { 45 | mUserName.setText(SharePreferenceManager.getInstance(getContext()).getUserName()); 46 | } 47 | 48 | } 49 | 50 | @OnClick(R.id.logout) 51 | void onClickLogout() { 52 | SharePreferenceManager.getInstance(getContext()).clear(); 53 | PersistentCookieJarManager.getInstance(getContext()).getPersistentCookieJar().clear(); 54 | startActivity(LoginActivity.class); 55 | getActivity().finish(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/fragment/TodoFragment.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.fragment; 2 | 3 | import android.content.DialogInterface; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.design.widget.BottomSheetDialog; 8 | import android.support.v4.widget.SwipeRefreshLayout; 9 | import android.support.v7.app.AlertDialog; 10 | import android.support.v7.widget.LinearLayoutManager; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.text.TextUtils; 13 | import android.view.View; 14 | import android.widget.TextView; 15 | 16 | import com.chad.library.adapter.base.BaseQuickAdapter; 17 | import com.wj.android.todo.R; 18 | import com.wj.android.todo.activity.EditTodoActivity; 19 | import com.wj.android.todo.activity.MainActivity; 20 | import com.wj.android.todo.adapter.TodoSectionAdapter; 21 | import com.wj.android.todo.bean.TodoDesBean; 22 | import com.wj.android.todo.bean.TodoListBean; 23 | import com.wj.android.todo.bean.TodoSection; 24 | import com.wj.android.todo.fragment.base.BaseFragment; 25 | import com.wj.android.todo.http.HttpUtils; 26 | import com.wj.android.todo.http.ResponseItem; 27 | 28 | import java.util.ArrayList; 29 | import java.util.LinkedHashSet; 30 | import java.util.List; 31 | 32 | import butterknife.BindView; 33 | 34 | /** 35 | * 作者:wangwnejie on 2018/8/8 13:42 36 | * 邮箱:wang20080990@163.com 37 | */ 38 | public class TodoFragment extends BaseFragment { 39 | private static final String KEY_IS_DONE = "is_done"; 40 | private static final int REQUEST_CODE_EDIT_TODO = 0x110; 41 | 42 | @BindView(R.id.todo_rv) 43 | RecyclerView mRecyclerView; 44 | @BindView(R.id.swipe_layout) 45 | SwipeRefreshLayout mSwipeRefreshLayout; 46 | 47 | private int page = 1; 48 | private TodoListBean mTodoListBean; 49 | private TodoSectionAdapter mAdapter; 50 | private int deletePosition = -1; 51 | private int donePosition = -1; 52 | 53 | private boolean isDone; 54 | 55 | public static TodoFragment newInstance(boolean isDone) { 56 | 57 | Bundle args = new Bundle(); 58 | args.putBoolean(KEY_IS_DONE, isDone); 59 | TodoFragment fragment = new TodoFragment(); 60 | fragment.setArguments(args); 61 | return fragment; 62 | } 63 | 64 | @Override 65 | public void onCreate(@Nullable Bundle savedInstanceState) { 66 | super.onCreate(savedInstanceState); 67 | 68 | Bundle bundle = getArguments(); 69 | if (bundle != null) { 70 | isDone = bundle.getBoolean(KEY_IS_DONE); 71 | } 72 | } 73 | 74 | @Override 75 | protected int getLayoutId() { 76 | return R.layout.fragment_todo; 77 | } 78 | 79 | @Override 80 | protected void initData() { 81 | mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 82 | 83 | mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 84 | @Override 85 | public void onRefresh() { 86 | page = 1; 87 | requestTodoListData(); 88 | } 89 | }); 90 | 91 | requestTodoListData(); 92 | } 93 | 94 | 95 | 96 | private void requestTodoListData() { 97 | HttpUtils.requestTodoList(this, page, isDone); 98 | } 99 | 100 | private void deleteTodoById(int todoId) { 101 | HttpUtils.deleteTodoById(this,todoId); 102 | } 103 | 104 | private void doneTodoById(int todoId) { 105 | HttpUtils.doneTodoById(this, todoId, isDone ? 0 : 1); 106 | } 107 | 108 | public void updateUI(final ResponseItem response) { 109 | if (response.isSuccess() && response.getData() != null) { 110 | if (page == 1) { 111 | mTodoListBean = response.getData(); 112 | mAdapter = new TodoSectionAdapter(R.layout.todo_item_view,R.layout.todo_item_head,getTodoSectionData(mTodoListBean.getDatas()),isDone); 113 | mAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() { 114 | @Override 115 | public void onLoadMoreRequested() { 116 | if (mTodoListBean.getPageCount() == 1) { 117 | mAdapter.loadMoreEnd(true); 118 | } else if (page >= mTodoListBean.getPageCount()) { 119 | mAdapter.loadMoreEnd(false); 120 | } else { 121 | page++; 122 | requestTodoListData(); 123 | } 124 | } 125 | }, mRecyclerView); 126 | mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() { 127 | @Override 128 | public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) { 129 | switch (view.getId()){ 130 | case R.id.item_complete: 131 | donePosition = position; 132 | doneTodoById(mAdapter.getData().get(position).t.getId()); 133 | break; 134 | case R.id.item_delete: 135 | showDialog(position); 136 | break; 137 | } 138 | } 139 | }); 140 | mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { 141 | @Override 142 | public void onItemClick(BaseQuickAdapter adapter, View view, int position) { 143 | TodoSection todoSection = mAdapter.getData().get(position); 144 | //showTodoDes(todoSection); 145 | Bundle bundle = new Bundle(); 146 | bundle.putSerializable("todo_des", todoSection.t); 147 | startActivityForResult(EditTodoActivity.class, bundle, REQUEST_CODE_EDIT_TODO); 148 | } 149 | }); 150 | mAdapter.openLoadAnimation(BaseQuickAdapter.SLIDEIN_BOTTOM); 151 | mRecyclerView.setAdapter(mAdapter); 152 | } else { 153 | mAdapter.addData(getTodoSectionData(response.getData().getDatas())); 154 | mAdapter.loadMoreComplete(); 155 | } 156 | 157 | } 158 | } 159 | 160 | private void showTodoDes(TodoSection todoSection) { 161 | View view = View.inflate(getContext(),R.layout.dialog_todo_des_view, null); 162 | TextView todoName = view.findViewById(R.id.todo_name); 163 | todoName.setText(todoSection.t.getTitle()); 164 | TextView todoContent = view.findViewById(R.id.todo_content); 165 | if (TextUtils.isEmpty(todoSection.t.getContent())) { 166 | todoContent.setText(R.string.no_text); 167 | } else { 168 | todoContent.setText(todoSection.t.getContent()); 169 | } 170 | BottomSheetDialog sheetDialog = new BottomSheetDialog(getContext()); 171 | sheetDialog.setContentView(view); 172 | sheetDialog.show(); 173 | 174 | 175 | } 176 | 177 | private List getTodoSectionData(List datas) { 178 | List todoSections = new ArrayList<>(); 179 | LinkedHashSet dates = new LinkedHashSet<>(); 180 | for (TodoDesBean todoDesBean : datas) { 181 | dates.add(todoDesBean.getDateStr()); 182 | } 183 | for (String date : dates) { 184 | TodoSection todoSectionHead = new TodoSection(true, date); 185 | todoSections.add(todoSectionHead); 186 | for (TodoDesBean todoDesBean : datas) { 187 | if (TextUtils.equals(date, todoDesBean.getDateStr())) { 188 | TodoSection todoSectionContent = new TodoSection(todoDesBean); 189 | todoSections.add(todoSectionContent); 190 | } 191 | } 192 | } 193 | return todoSections; 194 | } 195 | 196 | @Override 197 | public boolean isLoadingEnable(int requestId) { 198 | return requestId == 1; 199 | } 200 | 201 | @Override 202 | public void start(int requestId) { 203 | if (requestId == 1) { 204 | super.start(requestId); 205 | } else { 206 | if (page == 1) { 207 | mSwipeRefreshLayout.setRefreshing(true); 208 | } 209 | } 210 | 211 | } 212 | 213 | @Override 214 | public void error(Throwable t, int code, int requestId) { 215 | super.error(t,code,requestId); 216 | if (requestId == 0) { 217 | if (page > 1) { 218 | mAdapter.loadMoreFail(); 219 | } 220 | } 221 | } 222 | 223 | @Override 224 | public void end(int requestId) { 225 | if (requestId == 1) { 226 | super.end(requestId); 227 | } else { 228 | if (page == 1) { 229 | mSwipeRefreshLayout.setRefreshing(false); 230 | } 231 | } 232 | } 233 | 234 | public void updateAddTodoData(TodoDesBean todoDesBean) { 235 | List todoSections = mAdapter.getData(); 236 | for (int i = 0; i < todoSections.size(); i++) { 237 | TodoSection todoSection = todoSections.get(i); 238 | if (todoSection.isHeader && TextUtils.equals(todoSection.header, todoDesBean.getDateStr())) { 239 | TodoSection section = new TodoSection(todoDesBean); 240 | mAdapter.getData().add(i+1,section); 241 | mAdapter.notifyItemInserted(i+1); 242 | mRecyclerView.scrollToPosition(i+1); 243 | return; 244 | } 245 | } 246 | TodoSection sectionHead = new TodoSection(true,todoDesBean.getDateStr()); 247 | mAdapter.getData().add(0, sectionHead); 248 | TodoSection section = new TodoSection(todoDesBean); 249 | mAdapter.getData().add(1, section); 250 | mAdapter.notifyItemRangeInserted(0,2); 251 | mRecyclerView.scrollToPosition(0); 252 | } 253 | 254 | public void updateRemovedData(ResponseItem response) { 255 | if (response.isSuccess() && deletePosition != -1) { 256 | if (mAdapter.getData().get(deletePosition-1).isHeader && (mAdapter.getData().size()== deletePosition+2 || mAdapter.getData().get(deletePosition+1).isHeader)) { 257 | mAdapter.getData().remove(deletePosition-1); 258 | mAdapter.getData().remove(deletePosition-1); 259 | mAdapter.notifyItemRangeRemoved(deletePosition-1,2); 260 | } else { 261 | mAdapter.getData().remove(deletePosition); 262 | mAdapter.notifyItemRemoved(deletePosition); 263 | } 264 | showToast(getString(R.string.delete_todo_success)); 265 | } 266 | 267 | } 268 | 269 | public void updateDoneData(ResponseItem response) { 270 | if (response.isSuccess() && donePosition != -1) { 271 | if (mAdapter.getData().get(donePosition-1).isHeader && (mAdapter.getData().size()== donePosition+2 || mAdapter.getData().get(donePosition+1).isHeader)) { 272 | mAdapter.getData().remove(donePosition-1); 273 | mAdapter.getData().remove(donePosition-1); 274 | mAdapter.notifyItemRangeRemoved(donePosition-1,2); 275 | } else { 276 | mAdapter.getData().remove(donePosition); 277 | mAdapter.notifyItemRemoved(donePosition); 278 | } 279 | showToast(getString(isDone ? R.string.notdo_todo_success : R.string.done_todo_success)); 280 | ((MainActivity)getActivity()).updateDoneOrCancelData(response.getData(), isDone ? 0 : 1); 281 | } 282 | } 283 | 284 | private void showDialog(final int position) { 285 | AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); 286 | builder.setTitle(R.string.delete_todo); 287 | builder.setMessage(R.string.sure_delete_todo); 288 | builder.setNegativeButton(R.string.cancel,null); 289 | builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { 290 | @Override 291 | public void onClick(DialogInterface dialogInterface, int i) { 292 | deletePosition = position; 293 | deleteTodoById(mAdapter.getData().get(position).t.getId()); 294 | } 295 | }); 296 | builder.show(); 297 | } 298 | 299 | @Override 300 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 301 | super.onActivityResult(requestCode, resultCode, data); 302 | if (requestCode == REQUEST_CODE_EDIT_TODO) { 303 | switch (resultCode) { 304 | case 0x210: 305 | page = 1; 306 | requestTodoListData(); 307 | break; 308 | } 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/fragment/base/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.fragment.base; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.Toast; 12 | 13 | 14 | import com.wj.android.http.BaseView; 15 | import com.wj.android.todo.R; 16 | import com.wj.android.todo.exception.ApiException; 17 | 18 | import butterknife.ButterKnife; 19 | import butterknife.Unbinder; 20 | 21 | /** 22 | * 作者:wangwnejie on 2018/4/2 16:58 23 | * 邮箱:wang20080990@163.com 24 | */ 25 | 26 | public abstract class BaseFragment extends Fragment implements BaseView { 27 | private Unbinder mUnbinder; 28 | private ProgressDialog mProgressDialog; 29 | 30 | @Nullable 31 | @Override 32 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 33 | View view = inflater.inflate(getLayoutId(), container, false); 34 | mUnbinder = ButterKnife.bind(this,view); 35 | return view; 36 | } 37 | 38 | @Override 39 | public void onActivityCreated(Bundle savedInstanceState) { 40 | super.onActivityCreated(savedInstanceState); 41 | initData(); 42 | } 43 | 44 | @Override 45 | public void onDestroyView() { 46 | super.onDestroyView(); 47 | mUnbinder.unbind(); 48 | } 49 | 50 | /** 51 | * 返回当前界面布局文件 52 | * @return 53 | */ 54 | protected abstract int getLayoutId(); 55 | 56 | /** 57 | * 初始化数据 58 | */ 59 | protected abstract void initData(); 60 | 61 | public boolean isLoadingEnable(int requestId) { 62 | return false; 63 | } 64 | 65 | @Override 66 | public void start(int requestId) { 67 | if (isLoadingEnable(requestId) && !getActivity().isFinishing()) { 68 | mProgressDialog = new ProgressDialog(getContext(),ProgressDialog.THEME_HOLO_LIGHT); 69 | mProgressDialog.setCanceledOnTouchOutside(false); 70 | mProgressDialog.setCancelable(false); 71 | mProgressDialog.setMessage(getResources().getString(R.string.loading)); 72 | mProgressDialog.show(); 73 | } 74 | } 75 | 76 | @Override 77 | public void error(Throwable t, int code, int requestId) { 78 | if (isAdded()) { 79 | if (t instanceof ApiException) { 80 | showToast(t.getMessage()); 81 | } else { 82 | showToast(getResources().getString(R.string.service_error)); 83 | } 84 | } 85 | } 86 | 87 | @Override 88 | public void end(int requestId) { 89 | if (isLoadingEnable(requestId) && !getActivity().isFinishing()) { 90 | mProgressDialog.dismiss(); 91 | } 92 | 93 | } 94 | 95 | public void showToast(String msg) { 96 | Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show(); 97 | } 98 | 99 | public void startActivity(Class cls) { 100 | Intent intent = new Intent(getActivity(), cls); 101 | startActivity(intent); 102 | } 103 | 104 | public void startActivity(Class cls, Bundle bundle) { 105 | Intent intent = new Intent(getActivity(), cls); 106 | intent.putExtras(bundle); 107 | startActivity(intent); 108 | } 109 | 110 | public void startActivityForResult(Class cls, Bundle bundle,int requestCode) { 111 | Intent intent = new Intent(getActivity(), cls); 112 | intent.putExtras(bundle); 113 | startActivityForResult(intent, requestCode); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/http/HttpUtils.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.http; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.google.gson.JsonObject; 6 | import com.wj.android.http.BaseView; 7 | import com.wj.android.http.XRetrofit; 8 | import com.wj.android.todo.BuildConfig; 9 | import com.wj.android.todo.activity.AddTodoActivity; 10 | import com.wj.android.todo.activity.EditTodoActivity; 11 | import com.wj.android.todo.activity.LoginActivity; 12 | import com.wj.android.todo.activity.RegisterActivity; 13 | import com.wj.android.todo.bean.TodoDesBean; 14 | import com.wj.android.todo.bean.TodoListBean; 15 | import com.wj.android.todo.constant.Constant; 16 | import com.wj.android.todo.fragment.TodoFragment; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | * 作者:wangwnejie on 2018/8/7 15:21 23 | * 邮箱:wang20080990@163.com 24 | */ 25 | public class HttpUtils { 26 | 27 | private static String buildUrl(String uri) { 28 | return String.format("%s%s", BuildConfig.BASE_URL, uri); 29 | } 30 | 31 | public static void requestLogin(BaseView baseView, String userName, String password){ 32 | Map params = new HashMap<>(); 33 | params.put("username", userName); 34 | params.put("password", password); 35 | XRetrofit.post(buildUrl(Constant.LOGIN_URI), params, new MyGsonCallback>(baseView) { 36 | @Override 37 | protected void onSuccess(ResponseItem response, BaseView baseView) { 38 | ((LoginActivity)baseView).updateUI(response); 39 | } 40 | }); 41 | } 42 | 43 | public static void requestRegister(BaseView baseView, String userName, String password, String repassword) { 44 | Map params = new HashMap<>(); 45 | params.put("username", userName); 46 | params.put("password", password); 47 | params.put("repassword", repassword); 48 | XRetrofit.post(buildUrl(Constant.REGISTER_URI), params, new MyGsonCallback>(baseView) { 49 | @Override 50 | protected void onSuccess(ResponseItem response, BaseView baseView) { 51 | ((RegisterActivity)baseView).updateUI(response); 52 | } 53 | }); 54 | } 55 | 56 | public static void requestAddTodoData(BaseView baseView, String todoName, String todoDes, String todoDate) { 57 | Map params = new HashMap<>(); 58 | params.put("title", todoName); 59 | if (!TextUtils.isEmpty(todoDes)) { 60 | params.put("content", todoDes); 61 | } 62 | params.put("date", todoDate); 63 | params.put("type", "0"); 64 | 65 | XRetrofit.post(buildUrl(Constant.ADD_TODO_URI), params, new MyGsonCallback>(baseView) { 66 | @Override 67 | protected void onSuccess(ResponseItem response, BaseView baseView) { 68 | ((AddTodoActivity)baseView).updateUI(response); 69 | } 70 | }); 71 | } 72 | 73 | public static void requestTodoList(BaseView baseView, int page, boolean isDone) { 74 | XRetrofit.post(isDone ? buildUrl(String.format(Constant.DONE_LIST_URI, page)) : buildUrl(String.format(Constant.TODO_LIST_URI, page)), new MyGsonCallback>(baseView) { 75 | @Override 76 | protected void onSuccess(ResponseItem response, BaseView baseView) { 77 | ((TodoFragment)baseView).updateUI(response); 78 | } 79 | }); 80 | } 81 | 82 | public static void deleteTodoById(BaseView baseView, int todoId) { 83 | XRetrofit.post(buildUrl(String.format(Constant.DELETE_TODO_URI, todoId)), new MyGsonCallback(baseView,1) { 84 | @Override 85 | protected void onSuccess(ResponseItem response, BaseView baseView) { 86 | ((TodoFragment)baseView).updateRemovedData(response); 87 | } 88 | }); 89 | } 90 | 91 | public static void updateTodoById(BaseView baseView, int todoId, String title, String content, String date) { 92 | 93 | } 94 | 95 | public static void doneTodoById(BaseView baseView, int todoId, int status) { 96 | Map params = new HashMap<>(); 97 | params.put("status", String.valueOf(status)); 98 | 99 | XRetrofit.post(buildUrl(String.format(Constant.DONE_TODO_URI, todoId)),params, new MyGsonCallback>(baseView,1) { 100 | @Override 101 | protected void onSuccess(ResponseItem response, BaseView baseView) { 102 | ((TodoFragment)baseView).updateDoneData(response); 103 | } 104 | }); 105 | } 106 | 107 | public static void requestUpdateTodoData(BaseView baseView, int todoId, String todoName, String todoDes, String todoDate) { 108 | Map params = new HashMap<>(); 109 | params.put("title", todoName); 110 | if (!TextUtils.isEmpty(todoDes)) { 111 | params.put("content", todoDes); 112 | } 113 | params.put("date", todoDate); 114 | 115 | XRetrofit.post(buildUrl(String.format(Constant.UPDATE_TODO_URI, todoId)), params, new MyGsonCallback>(baseView) { 116 | @Override 117 | protected void onSuccess(ResponseItem response, BaseView baseView) { 118 | ((EditTodoActivity)baseView).updateUI(response); 119 | } 120 | }); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/http/MyGsonCallback.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.http; 2 | 3 | import com.google.gson.Gson; 4 | import com.wj.android.http.BaseView; 5 | import com.wj.android.http.GsonCallback; 6 | import com.wj.android.todo.exception.ApiException; 7 | 8 | /** 9 | * 作者:wangwnejie on 2018/8/8 13:57 10 | * 邮箱:wang20080990@163.com 11 | */ 12 | public abstract class MyGsonCallback extends GsonCallback { 13 | 14 | public MyGsonCallback(BaseView baseView) { 15 | super(baseView); 16 | } 17 | 18 | public MyGsonCallback(BaseView baseView, int requestId) { 19 | super(baseView, requestId); 20 | } 21 | 22 | @Override 23 | protected String convertResponse(String response) { 24 | ResponseItem responseItem = new Gson().fromJson(response, ResponseItem.class); 25 | if (!responseItem.isSuccess()) { 26 | throw new ApiException(responseItem.getErrorMsg(), responseItem.getErrorCode()); 27 | } 28 | return super.convertResponse(response); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/http/ResponseItem.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.http; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 响应类型 7 | * @author wangwenjie 8 | *泛型T是实际的响应类型 9 | *响应类型T为Object 10 | */ 11 | public class ResponseItem implements Serializable { 12 | /** 13 | * 错误的内部编号,success表示成功 14 | */ 15 | private int errorCode; 16 | /** 17 | * 错误描述 18 | */ 19 | private String errorMsg; 20 | /** 21 | * 返回值 22 | */ 23 | private T data; 24 | 25 | public int getErrorCode() { 26 | return errorCode; 27 | } 28 | 29 | public void setErrorCode(int errorCode) { 30 | this.errorCode = errorCode; 31 | } 32 | 33 | public String getErrorMsg() { 34 | return errorMsg; 35 | } 36 | 37 | public void setErrorMsg(String errorMsg) { 38 | this.errorMsg = errorMsg; 39 | } 40 | 41 | public T getData() { 42 | return data; 43 | } 44 | 45 | public void setData(T data) { 46 | this.data = data; 47 | } 48 | 49 | public boolean isSuccess() { 50 | return errorCode == 0; 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/manager/PersistentCookieJarManager.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.manager; 2 | 3 | 4 | import android.content.Context; 5 | 6 | import com.franmontiel.persistentcookiejar.PersistentCookieJar; 7 | import com.franmontiel.persistentcookiejar.cache.SetCookieCache; 8 | import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; 9 | import com.wj.android.http.RetrofitHttpManager; 10 | 11 | /** 12 | * 作者:wangwnejie on 2018/8/8 16:34 13 | * 邮箱:wang20080990@163.com 14 | */ 15 | public class PersistentCookieJarManager { 16 | 17 | private volatile static PersistentCookieJarManager sInstance; 18 | private PersistentCookieJar mPersistentCookieJar; 19 | 20 | private PersistentCookieJarManager(Context context) { 21 | mPersistentCookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context)); 22 | } 23 | 24 | public static PersistentCookieJarManager getInstance(Context context) { 25 | if (sInstance == null) { 26 | synchronized (RetrofitHttpManager.class) { 27 | if (sInstance == null) { 28 | sInstance = new PersistentCookieJarManager(context.getApplicationContext()); 29 | } 30 | } 31 | } 32 | return sInstance; 33 | } 34 | 35 | public PersistentCookieJar getPersistentCookieJar() { 36 | return mPersistentCookieJar; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/manager/PreferenceHelper.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) Baina Info Tech Co. Ltd 3 | *

4 | * DolphinCoreLibrary_Webzine 5 | *

6 | * PreferenceHelper 7 | * TODO File description or class description. 8 | * 9 | * @author: dhu 10 | * @since: 2011-8-6 11 | * @version: 1.0 12 | ******************************************************************************/ 13 | package com.wj.android.todo.manager; 14 | 15 | import android.annotation.SuppressLint; 16 | import android.content.SharedPreferences.Editor; 17 | import android.os.Build; 18 | 19 | /** 20 | * PreferenceHelper of DolphinCoreLibrary_Webzine. 21 | * 22 | * @author dhu 23 | */ 24 | public abstract class PreferenceHelper { 25 | 26 | public abstract void save(Editor editor); 27 | 28 | private static PreferenceHelper sHelper; 29 | 30 | public static PreferenceHelper getInstance() { 31 | if (null == sHelper) { 32 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { 33 | sHelper = new NewPreferenceHelper(); 34 | } else { 35 | sHelper = new OlderPreferenceHelper(); 36 | } 37 | } 38 | return sHelper; 39 | } 40 | 41 | private static class OlderPreferenceHelper extends PreferenceHelper { 42 | 43 | @Override 44 | public void save(Editor editor) { 45 | editor.commit(); 46 | } 47 | 48 | } 49 | 50 | private static class NewPreferenceHelper extends PreferenceHelper { 51 | 52 | @SuppressLint("NewApi") 53 | @Override 54 | public void save(Editor editor) { 55 | editor.apply(); 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/manager/SharePreferenceManager.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.manager; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | /** 7 | * Created by Administrator on 2017/5/25. 8 | */ 9 | 10 | public class SharePreferenceManager { 11 | 12 | private static final String PREF_FILE = "wj_todo"; 13 | private static final String KEY_USER_NAME = "user_name"; 14 | 15 | private volatile static SharePreferenceManager sInstance; 16 | private SharedPreferences mSharedPreferences; 17 | 18 | private SharePreferenceManager(Context context) { 19 | mSharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); 20 | } 21 | 22 | public static SharePreferenceManager getInstance(Context context) { 23 | if (sInstance == null) { 24 | synchronized (SharePreferenceManager.class) { 25 | if (sInstance == null) { 26 | sInstance = new SharePreferenceManager(context.getApplicationContext()); 27 | } 28 | } 29 | } 30 | return sInstance; 31 | } 32 | 33 | public void setUserName(String userName) { 34 | SharedPreferences.Editor editor = mSharedPreferences.edit(); 35 | editor.putString(KEY_USER_NAME, userName); 36 | PreferenceHelper.getInstance().save(editor); 37 | } 38 | 39 | public String getUserName() { 40 | return mSharedPreferences.getString(KEY_USER_NAME, null); 41 | } 42 | 43 | public void clear() { 44 | mSharedPreferences.edit().clear(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/wj/android/todo/widget/BottomNavigationViewEx.java: -------------------------------------------------------------------------------- 1 | package com.wj.android.todo.widget; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.res.ColorStateList; 6 | import android.graphics.Paint; 7 | import android.graphics.Typeface; 8 | import android.support.annotation.NonNull; 9 | import android.support.annotation.Nullable; 10 | import android.support.design.internal.BottomNavigationItemView; 11 | import android.support.design.internal.BottomNavigationMenuView; 12 | import android.support.design.widget.BottomNavigationView; 13 | import android.support.v4.view.ViewPager; 14 | import android.util.AttributeSet; 15 | import android.util.SparseIntArray; 16 | import android.util.TypedValue; 17 | import android.view.Menu; 18 | import android.view.MenuItem; 19 | import android.view.View; 20 | import android.view.ViewGroup; 21 | import android.widget.ImageView; 22 | import android.widget.TextView; 23 | 24 | import java.lang.ref.WeakReference; 25 | import java.lang.reflect.Field; 26 | 27 | /** 28 | * Created by yu on 2016/11/10. 29 | */ 30 | public class BottomNavigationViewEx extends BottomNavigationView { 31 | // used for animation 32 | private int mShiftAmount; 33 | private float mScaleUpFactor; 34 | private float mScaleDownFactor; 35 | private boolean animationRecord; 36 | private float mLargeLabelSize; 37 | private float mSmallLabelSize; 38 | private boolean visibilityTextSizeRecord; 39 | private boolean visibilityHeightRecord; 40 | private int mItemHeight; 41 | private boolean textVisibility = true; 42 | // used for animation end 43 | 44 | // used for setupWithViewPager 45 | private ViewPager mViewPager; 46 | private MyOnNavigationItemSelectedListener mMyOnNavigationItemSelectedListener; 47 | private BottomNavigationViewExOnPageChangeListener mPageChangeListener; 48 | private BottomNavigationMenuView mMenuView; 49 | private BottomNavigationItemView[] mButtons; 50 | // used for setupWithViewPager end 51 | 52 | // detect navigation tab changes when the user clicking on navigation item 53 | private static boolean isNavigationItemClicking = false; 54 | 55 | public BottomNavigationViewEx(Context context) { 56 | super(context); 57 | } 58 | 59 | public BottomNavigationViewEx(Context context, AttributeSet attrs) { 60 | super(context, attrs); 61 | } 62 | 63 | public BottomNavigationViewEx(Context context, AttributeSet attrs, int defStyleAttr) { 64 | super(context, attrs, defStyleAttr); 65 | } 66 | 67 | @SuppressLint("RestrictedApi") 68 | private void refreshTextViewVisibility() { 69 | if (!textVisibility) 70 | return; 71 | // 1. get mMenuView 72 | BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); 73 | // 2. get mButtons 74 | BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); 75 | 76 | int currentItem = getCurrentItem(); 77 | 78 | // 3. get field mShiftingMode and TextView in mButtons 79 | for (BottomNavigationItemView button : mButtons) { 80 | TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel"); 81 | TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel"); 82 | 83 | mLargeLabel.clearAnimation(); 84 | mSmallLabel.clearAnimation(); 85 | 86 | // mShiftingMode 87 | boolean mShiftingMode = getField(button.getClass(), button, "mShiftingMode"); 88 | boolean selected = button.getItemPosition() == currentItem; 89 | if (mShiftingMode) { 90 | if (selected) { 91 | mLargeLabel.setVisibility(VISIBLE); 92 | } else { 93 | mLargeLabel.setVisibility(INVISIBLE); 94 | } 95 | mSmallLabel.setVisibility(INVISIBLE); 96 | } else { 97 | if (selected) { 98 | mLargeLabel.setVisibility(VISIBLE); 99 | mSmallLabel.setVisibility(INVISIBLE); 100 | } else { 101 | mLargeLabel.setVisibility(INVISIBLE); 102 | mSmallLabel.setVisibility(VISIBLE); 103 | } 104 | } 105 | } 106 | } 107 | 108 | 109 | /** 110 | * change the visibility of icon 111 | * 112 | * @param visibility 113 | */ 114 | @SuppressLint("RestrictedApi") 115 | public void setIconVisibility(boolean visibility) { 116 | /* 117 | 1. get field in this class 118 | private final BottomNavigationMenuView mMenuView; 119 | 120 | 2. get field in mButtons 121 | private BottomNavigationItemView[] mButtons; 122 | 123 | 3. get mIcon in mButtons 124 | private ImageView mIcon 125 | 126 | 4. set mIcon visibility gone 127 | 128 | 5. change mItemHeight to only text size in mMenuView 129 | */ 130 | // 1. get mMenuView 131 | final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); 132 | // 2. get mButtons 133 | BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); 134 | // 3. get mIcon in mButtons 135 | for (BottomNavigationItemView button : mButtons) { 136 | ImageView mIcon = getField(button.getClass(), button, "mIcon"); 137 | // 4. set mIcon visibility gone 138 | mIcon.setVisibility(visibility ? View.VISIBLE : View.INVISIBLE); 139 | } 140 | 141 | // 5. change mItemHeight to only text size in mMenuView 142 | if (!visibility) { 143 | // if not record mItemHeight 144 | if (!visibilityHeightRecord) { 145 | visibilityHeightRecord = true; 146 | mItemHeight = getItemHeight(); 147 | } 148 | 149 | // change mItemHeight 150 | BottomNavigationItemView button = mButtons[0]; 151 | if (null != button) { 152 | final ImageView mIcon = getField(button.getClass(), button, "mIcon"); 153 | // System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight()); 154 | if (null != mIcon) { 155 | mIcon.post(new Runnable() { 156 | @Override 157 | public void run() { 158 | // System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight()); 159 | setItemHeight(mItemHeight - mIcon.getMeasuredHeight()); 160 | } 161 | }); 162 | } 163 | } 164 | } else { 165 | // if not record the mItemHeight, we need do nothing. 166 | if (!visibilityHeightRecord) 167 | return; 168 | 169 | // restore it 170 | setItemHeight(mItemHeight); 171 | } 172 | 173 | mMenuView.updateMenuView(); 174 | } 175 | 176 | /** 177 | * change the visibility of text 178 | * 179 | * @param visibility 180 | */ 181 | @SuppressLint("RestrictedApi") 182 | public void setTextVisibility(boolean visibility) { 183 | this.textVisibility = visibility; 184 | /* 185 | 1. get field in this class 186 | private final BottomNavigationMenuView mMenuView; 187 | 188 | 2. get field in mButtons 189 | private BottomNavigationItemView[] mButtons; 190 | 191 | 3. set text size in mButtons 192 | private final TextView mLargeLabel 193 | private final TextView mSmallLabel 194 | 195 | 4. change mItemHeight to only icon size in mMenuView 196 | */ 197 | // 1. get mMenuView 198 | BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); 199 | // 2. get mButtons 200 | BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); 201 | 202 | // 3. change field mShiftingMode value in mButtons 203 | for (BottomNavigationItemView button : mButtons) { 204 | TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel"); 205 | TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel"); 206 | 207 | if (!visibility) { 208 | // if not record the font size, record it 209 | if (!visibilityTextSizeRecord && !animationRecord) { 210 | visibilityTextSizeRecord = true; 211 | mLargeLabelSize = mLargeLabel.getTextSize(); 212 | mSmallLabelSize = mSmallLabel.getTextSize(); 213 | } 214 | 215 | // if not visitable, set font size to 0 216 | mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0); 217 | mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0); 218 | 219 | } else { 220 | // if not record the font size, we need do nothing. 221 | if (!visibilityTextSizeRecord) 222 | break; 223 | 224 | // restore it 225 | mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize); 226 | mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize); 227 | } 228 | } 229 | 230 | // 4 change mItemHeight to only icon size in mMenuView 231 | if (!visibility) { 232 | // if not record mItemHeight 233 | if (!visibilityHeightRecord) { 234 | visibilityHeightRecord = true; 235 | mItemHeight = getItemHeight(); 236 | } 237 | 238 | // change mItemHeight to only icon size in mMenuView 239 | // private final int mItemHeight; 240 | 241 | // change mItemHeight 242 | // System.out.println("mLargeLabel.getMeasuredHeight():" + getFontHeight(mSmallLabelSize)); 243 | setItemHeight(mItemHeight - getFontHeight(mSmallLabelSize)); 244 | 245 | } else { 246 | // if not record the mItemHeight, we need do nothing. 247 | if (!visibilityHeightRecord) 248 | return; 249 | // restore mItemHeight 250 | setItemHeight(mItemHeight); 251 | } 252 | 253 | mMenuView.updateMenuView(); 254 | } 255 | 256 | /** 257 | * get text height by font size 258 | * 259 | * @param fontSize 260 | * @return 261 | */ 262 | private static int getFontHeight(float fontSize) { 263 | Paint paint = new Paint(); 264 | paint.setTextSize(fontSize); 265 | Paint.FontMetrics fm = paint.getFontMetrics(); 266 | return (int) Math.ceil(fm.descent - fm.top) + 2; 267 | } 268 | 269 | /** 270 | * enable or disable click item animation(text scale and icon move animation in no item shifting mode) 271 | * 272 | * @param enable It means the text won't scale and icon won't move when active it in no item shifting mode if false. 273 | */ 274 | @SuppressLint("RestrictedApi") 275 | public void enableAnimation(boolean enable) { 276 | /* 277 | 1. get field in this class 278 | private final BottomNavigationMenuView mMenuView; 279 | 280 | 2. get field in mButtons 281 | private BottomNavigationItemView[] mButtons; 282 | 283 | 3. chang mShiftAmount to 0 in mButtons 284 | private final int mShiftAmount 285 | 286 | change mScaleUpFactor and mScaleDownFactor to 1f in mButtons 287 | private final float mScaleUpFactor 288 | private final float mScaleDownFactor 289 | 290 | 4. change label font size in mButtons 291 | private final TextView mLargeLabel 292 | private final TextView mSmallLabel 293 | */ 294 | 295 | // 1. get mMenuView 296 | BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); 297 | // 2. get mButtons 298 | BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); 299 | // 3. change field mShiftingMode value in mButtons 300 | for (BottomNavigationItemView button : mButtons) { 301 | TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel"); 302 | TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel"); 303 | 304 | // if disable animation, need animationRecord the source value 305 | if (!enable) { 306 | if (!animationRecord) { 307 | animationRecord = true; 308 | mShiftAmount = getField(button.getClass(), button, "mShiftAmount"); 309 | mScaleUpFactor = getField(button.getClass(), button, "mScaleUpFactor"); 310 | mScaleDownFactor = getField(button.getClass(), button, "mScaleDownFactor"); 311 | 312 | mLargeLabelSize = mLargeLabel.getTextSize(); 313 | mSmallLabelSize = mSmallLabel.getTextSize(); 314 | 315 | // System.out.println("mShiftAmount:" + mShiftAmount + " mScaleUpFactor:" 316 | // + mScaleUpFactor + " mScaleDownFactor:" + mScaleDownFactor 317 | // + " mLargeLabel:" + mLargeLabelSize + " mSmallLabel:" + mSmallLabelSize); 318 | } 319 | // disable 320 | setField(button.getClass(), button, "mShiftAmount", 0); 321 | setField(button.getClass(), button, "mScaleUpFactor", 1); 322 | setField(button.getClass(), button, "mScaleDownFactor", 1); 323 | 324 | // let the mLargeLabel font size equal to mSmallLabel 325 | mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize); 326 | 327 | // debug start 328 | // mLargeLabelSize = mLargeLabel.getTextSize(); 329 | // System.out.println("mLargeLabel:" + mLargeLabelSize); 330 | // debug end 331 | 332 | } else { 333 | // haven't change the value. It means it was the first call this method. So nothing need to do. 334 | if (!animationRecord) 335 | return; 336 | // enable animation 337 | setField(button.getClass(), button, "mShiftAmount", mShiftAmount); 338 | setField(button.getClass(), button, "mScaleUpFactor", mScaleUpFactor); 339 | setField(button.getClass(), button, "mScaleDownFactor", mScaleDownFactor); 340 | // restore 341 | mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize); 342 | } 343 | } 344 | mMenuView.updateMenuView(); 345 | } 346 | 347 | /** 348 | * enable the shifting mode for navigation 349 | * 350 | * @param enable It will has a shift animation if true. Otherwise all items are the same width. 351 | */ 352 | @SuppressLint("RestrictedApi") 353 | public void enableShiftingMode(boolean enable) { 354 | /* 355 | 1. get field in this class 356 | private final BottomNavigationMenuView mMenuView; 357 | 358 | 2. change field mShiftingMode value in mMenuView 359 | private boolean mShiftingMode = true; 360 | */ 361 | // 1. get mMenuView 362 | BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); 363 | // 2. change field mShiftingMode value in mMenuView 364 | setField(mMenuView.getClass(), mMenuView, "mShiftingMode", enable); 365 | 366 | mMenuView.updateMenuView(); 367 | } 368 | 369 | /** 370 | * enable the shifting mode for each item 371 | * 372 | * @param enable It will has a shift animation for item if true. Otherwise the item text always be shown. 373 | */ 374 | @SuppressLint("RestrictedApi") 375 | public void enableItemShiftingMode(boolean enable) { 376 | /* 377 | 1. get field in this class 378 | private final BottomNavigationMenuView mMenuView; 379 | 380 | 2. get field in this mMenuView 381 | private BottomNavigationItemView[] mButtons; 382 | 383 | 3. change field mShiftingMode value in mButtons 384 | private boolean mShiftingMode = true; 385 | */ 386 | // 1. get mMenuView 387 | BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); 388 | // 2. get mButtons 389 | BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); 390 | // 3. change field mShiftingMode value in mButtons 391 | for (BottomNavigationItemView button : mButtons) { 392 | setField(button.getClass(), button, "mShiftingMode", enable); 393 | } 394 | mMenuView.updateMenuView(); 395 | } 396 | 397 | /** 398 | * get the current checked item position 399 | * 400 | * @return index of item, start from 0. 401 | */ 402 | public int getCurrentItem() { 403 | /* 404 | 1. get field in this class 405 | private final BottomNavigationMenuView mMenuView; 406 | 407 | 2. get field in mMenuView 408 | private BottomNavigationItemView[] mButtons; 409 | 410 | 3. get menu and traverse it to get the checked one 411 | */ 412 | 413 | // 1. get mMenuView 414 | // BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); 415 | // 2. get mButtons 416 | BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); 417 | // 3. get menu and traverse it to get the checked one 418 | Menu menu = getMenu(); 419 | for (int i = 0; i < mButtons.length; i++) { 420 | if (menu.getItem(i).isChecked()) { 421 | return i; 422 | } 423 | } 424 | return 0; 425 | } 426 | 427 | /** 428 | * get menu item position in menu 429 | * 430 | * @param item 431 | * @return position if success, -1 otherwise 432 | */ 433 | public int getMenuItemPosition(MenuItem item) { 434 | // get item id 435 | int itemId = item.getItemId(); 436 | // get meunu 437 | Menu menu = getMenu(); 438 | int size = menu.size(); 439 | for (int i = 0; i < size; i++) { 440 | if (menu.getItem(i).getItemId() == itemId) { 441 | return i; 442 | } 443 | } 444 | return -1; 445 | } 446 | 447 | /** 448 | * set the current checked item 449 | * 450 | * @param item start from 0. 451 | */ 452 | public void setCurrentItem(int item) { 453 | // check bounds 454 | if (item < 0 || item >= getMaxItemCount()) { 455 | throw new ArrayIndexOutOfBoundsException("item is out of bounds, we expected 0 - " 456 | + (getMaxItemCount() - 1) + ". Actually " + item); 457 | } 458 | 459 | /* 460 | 1. get field in this class 461 | private final BottomNavigationMenuView mMenuView; 462 | 463 | 2. get field in mMenuView 464 | private BottomNavigationItemView[] mButtons; 465 | private final OnClickListener mOnClickListener; 466 | 467 | 3. call mOnClickListener.onClick(); 468 | */ 469 | // 1. get mMenuView 470 | BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); 471 | // 2. get mButtons 472 | BottomNavigationItemView[] mButtons = getBottomNavigationItemViews(); 473 | // get mOnClickListener 474 | OnClickListener mOnClickListener = getField(mMenuView.getClass(), mMenuView, "onClickListener"); 475 | 476 | // System.out.println("mMenuView:" + mMenuView + " mButtons:" + mButtons + " mOnClickListener" + mOnClickListener); 477 | // 3. call mOnClickListener.onClick(); 478 | mOnClickListener.onClick(mButtons[item]); 479 | 480 | } 481 | 482 | /** 483 | * get OnNavigationItemSelectedListener 484 | * 485 | * @return 486 | */ 487 | public OnNavigationItemSelectedListener getOnNavigationItemSelectedListener() { 488 | // private OnNavigationItemSelectedListener mListener; 489 | OnNavigationItemSelectedListener mListener = getField(BottomNavigationView.class, this, "selectedListener"); 490 | return mListener; 491 | } 492 | 493 | @Override 494 | public void setOnNavigationItemSelectedListener(@Nullable OnNavigationItemSelectedListener listener) { 495 | // if not set up with view pager, the same with father 496 | if (null == mMyOnNavigationItemSelectedListener) { 497 | super.setOnNavigationItemSelectedListener(listener); 498 | return; 499 | } 500 | 501 | mMyOnNavigationItemSelectedListener.setOnNavigationItemSelectedListener(listener); 502 | } 503 | 504 | /** 505 | * get private mMenuView 506 | * 507 | * @return 508 | */ 509 | private BottomNavigationMenuView getBottomNavigationMenuView() { 510 | if (null == mMenuView) 511 | mMenuView = getField(BottomNavigationView.class, this, "menuView"); 512 | return mMenuView; 513 | } 514 | 515 | /** 516 | * get private mButtons in mMenuView 517 | * 518 | * @return 519 | */ 520 | public BottomNavigationItemView[] getBottomNavigationItemViews() { 521 | if (null != mButtons) 522 | return mButtons; 523 | /* 524 | * 1 private final BottomNavigationMenuView mMenuView; 525 | * 2 private BottomNavigationItemView[] mButtons; 526 | */ 527 | BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); 528 | mButtons = getField(mMenuView.getClass(), mMenuView, "buttons"); 529 | return mButtons; 530 | } 531 | 532 | /** 533 | * get private mButton in mMenuView at position 534 | * 535 | * @param position 536 | * @return 537 | */ 538 | public BottomNavigationItemView getBottomNavigationItemView(int position) { 539 | return getBottomNavigationItemViews()[position]; 540 | } 541 | 542 | /** 543 | * get icon at position 544 | * 545 | * @param position 546 | * @return 547 | */ 548 | public ImageView getIconAt(int position) { 549 | /* 550 | * 1 private final BottomNavigationMenuView mMenuView; 551 | * 2 private BottomNavigationItemView[] mButtons; 552 | * 3 private ImageView mIcon; 553 | */ 554 | BottomNavigationItemView mButtons = getBottomNavigationItemView(position); 555 | ImageView mIcon = getField(BottomNavigationItemView.class, mButtons, "icon"); 556 | return mIcon; 557 | } 558 | 559 | /** 560 | * get small label at position 561 | * Each item has tow label, one is large, another is small. 562 | * 563 | * @param position 564 | * @return 565 | */ 566 | public TextView getSmallLabelAt(int position) { 567 | /* 568 | * 1 private final BottomNavigationMenuView mMenuView; 569 | * 2 private BottomNavigationItemView[] mButtons; 570 | * 3 private final TextView mSmallLabel; 571 | */ 572 | BottomNavigationItemView mButtons = getBottomNavigationItemView(position); 573 | TextView mSmallLabel = getField(BottomNavigationItemView.class, mButtons, "smallLabel"); 574 | return mSmallLabel; 575 | } 576 | 577 | /** 578 | * get large label at position 579 | * Each item has tow label, one is large, another is small. 580 | * 581 | * @param position 582 | * @return 583 | */ 584 | public TextView getLargeLabelAt(int position) { 585 | /* 586 | * 1 private final BottomNavigationMenuView mMenuView; 587 | * 2 private BottomNavigationItemView[] mButtons; 588 | * 3 private final TextView mLargeLabel; 589 | */ 590 | BottomNavigationItemView mButtons = getBottomNavigationItemView(position); 591 | TextView mLargeLabel = getField(BottomNavigationItemView.class, mButtons, "largeLabel"); 592 | return mLargeLabel; 593 | } 594 | 595 | /** 596 | * return item count 597 | * 598 | * @return 599 | */ 600 | public int getItemCount() { 601 | BottomNavigationItemView[] bottomNavigationItemViews = getBottomNavigationItemViews(); 602 | if (null == bottomNavigationItemViews) 603 | return 0; 604 | return bottomNavigationItemViews.length; 605 | } 606 | 607 | /** 608 | * set all item small TextView size 609 | * Each item has tow label, one is large, another is small. 610 | * Small one will be shown when item state is normal 611 | * Large one will be shown when item checked. 612 | * 613 | * @param sp 614 | */ 615 | @SuppressLint("RestrictedApi") 616 | public void setSmallTextSize(float sp) { 617 | int count = getItemCount(); 618 | for (int i = 0; i < count; i++) { 619 | getSmallLabelAt(i).setTextSize(sp); 620 | } 621 | mMenuView.updateMenuView(); 622 | } 623 | 624 | /** 625 | * set all item large TextView size 626 | * Each item has tow label, one is large, another is small. 627 | * Small one will be shown when item state is normal. 628 | * Large one will be shown when item checked. 629 | * 630 | * @param sp 631 | */ 632 | @SuppressLint("RestrictedApi") 633 | public void setLargeTextSize(float sp) { 634 | int count = getItemCount(); 635 | for (int i = 0; i < count; i++) { 636 | getLargeLabelAt(i).setTextSize(sp); 637 | } 638 | mMenuView.updateMenuView(); 639 | } 640 | 641 | /** 642 | * set all item large and small TextView size 643 | * Each item has tow label, one is large, another is small. 644 | * Small one will be shown when item state is normal 645 | * Large one will be shown when item checked. 646 | * 647 | * @param sp 648 | */ 649 | public void setTextSize(float sp) { 650 | setLargeTextSize(sp); 651 | setSmallTextSize(sp); 652 | } 653 | 654 | /** 655 | * set item ImageView size which at position 656 | * 657 | * @param position position start from 0 658 | * @param width in dp 659 | * @param height in dp 660 | */ 661 | @SuppressLint("RestrictedApi") 662 | public void setIconSizeAt(int position, float width, float height) { 663 | ImageView icon = getIconAt(position); 664 | // update size 665 | ViewGroup.LayoutParams layoutParams = icon.getLayoutParams(); 666 | layoutParams.width = dp2px(getContext(), width); 667 | layoutParams.height = dp2px(getContext(), height); 668 | icon.setLayoutParams(layoutParams); 669 | 670 | mMenuView.updateMenuView(); 671 | } 672 | 673 | /** 674 | * set all item ImageView size 675 | * 676 | * @param width in dp 677 | * @param height in dp 678 | */ 679 | public void setIconSize(float width, float height) { 680 | int count = getItemCount(); 681 | for (int i = 0; i < count; i++) { 682 | setIconSizeAt(i, width, height); 683 | } 684 | } 685 | 686 | /** 687 | * set menu item height 688 | * 689 | * @param height in px 690 | */ 691 | @SuppressLint("RestrictedApi") 692 | public void setItemHeight(int height) { 693 | // 1. get mMenuView 694 | final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); 695 | // 2. set private final int mItemHeight in mMenuView 696 | setField(mMenuView.getClass(), mMenuView, "itemHeight", height); 697 | 698 | mMenuView.updateMenuView(); 699 | } 700 | 701 | /** 702 | * get menu item height 703 | * 704 | * @return in px 705 | */ 706 | public int getItemHeight() { 707 | // 1. get mMenuView 708 | final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView(); 709 | // 2. get private final int mItemHeight in mMenuView 710 | return getField(mMenuView.getClass(), mMenuView, "itemHeight"); 711 | } 712 | 713 | /** 714 | * dp to px 715 | * 716 | * @param context 717 | * @param dpValue dp 718 | * @return px 719 | */ 720 | public static int dp2px(Context context, float dpValue) { 721 | final float scale = context.getResources().getDisplayMetrics().density; 722 | return (int) (dpValue * scale + 0.5f); 723 | } 724 | 725 | /** 726 | * set Typeface for all item TextView 727 | * 728 | * @attr ref android.R.styleable#TextView_typeface 729 | * @attr ref android.R.styleable#TextView_textStyle 730 | */ 731 | @SuppressLint("RestrictedApi") 732 | public void setTypeface(Typeface typeface, int style) { 733 | int count = getItemCount(); 734 | for (int i = 0; i < count; i++) { 735 | getLargeLabelAt(i).setTypeface(typeface, style); 736 | getSmallLabelAt(i).setTypeface(typeface, style); 737 | } 738 | mMenuView.updateMenuView(); 739 | } 740 | 741 | /** 742 | * set Typeface for all item TextView 743 | * 744 | * @attr ref android.R.styleable#TextView_typeface 745 | */ 746 | @SuppressLint("RestrictedApi") 747 | public void setTypeface(Typeface typeface) { 748 | int count = getItemCount(); 749 | for (int i = 0; i < count; i++) { 750 | getLargeLabelAt(i).setTypeface(typeface); 751 | getSmallLabelAt(i).setTypeface(typeface); 752 | } 753 | mMenuView.updateMenuView(); 754 | } 755 | 756 | /** 757 | * get private filed in this specific object 758 | * 759 | * @param targetClass 760 | * @param instance the filed owner 761 | * @param fieldName 762 | * @param 763 | * @return field if success, null otherwise. 764 | */ 765 | private T getField(Class targetClass, Object instance, String fieldName) { 766 | try { 767 | Field field = targetClass.getDeclaredField(fieldName); 768 | field.setAccessible(true); 769 | return (T) field.get(instance); 770 | } catch (NoSuchFieldException e) { 771 | e.printStackTrace(); 772 | } catch (IllegalAccessException e) { 773 | e.printStackTrace(); 774 | } 775 | return null; 776 | } 777 | 778 | /** 779 | * change the field value 780 | * 781 | * @param targetClass 782 | * @param instance the filed owner 783 | * @param fieldName 784 | * @param value 785 | */ 786 | private void setField(Class targetClass, Object instance, String fieldName, Object value) { 787 | try { 788 | Field field = targetClass.getDeclaredField(fieldName); 789 | field.setAccessible(true); 790 | field.set(instance, value); 791 | } catch (NoSuchFieldException e) { 792 | e.printStackTrace(); 793 | } catch (IllegalAccessException e) { 794 | e.printStackTrace(); 795 | } 796 | } 797 | 798 | /** 799 | * This method will link the given ViewPager and this BottomNavigationViewEx together so that 800 | * changes in one are automatically reflected in the other. This includes scroll state changes 801 | * and clicks. 802 | * 803 | * @param viewPager 804 | */ 805 | public void setupWithViewPager(@Nullable final ViewPager viewPager) { 806 | setupWithViewPager(viewPager, false); 807 | } 808 | 809 | /** 810 | * This method will link the given ViewPager and this BottomNavigationViewEx together so that 811 | * changes in one are automatically reflected in the other. This includes scroll state changes 812 | * and clicks. 813 | * 814 | * @param viewPager 815 | * @param smoothScroll whether ViewPager changed with smooth scroll animation 816 | */ 817 | public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean smoothScroll) { 818 | if (mViewPager != null) { 819 | // If we've already been setup with a ViewPager, remove us from it 820 | if (mPageChangeListener != null) { 821 | mViewPager.removeOnPageChangeListener(mPageChangeListener); 822 | } 823 | } 824 | 825 | if (null == viewPager) { 826 | mViewPager = null; 827 | super.setOnNavigationItemSelectedListener(null); 828 | return; 829 | } 830 | 831 | mViewPager = viewPager; 832 | 833 | // Add our custom OnPageChangeListener to the ViewPager 834 | if (mPageChangeListener == null) { 835 | mPageChangeListener = new BottomNavigationViewExOnPageChangeListener(this); 836 | } 837 | viewPager.addOnPageChangeListener(mPageChangeListener); 838 | 839 | // Now we'll add a navigation item selected listener to set ViewPager's current item 840 | OnNavigationItemSelectedListener listener = getOnNavigationItemSelectedListener(); 841 | mMyOnNavigationItemSelectedListener = new MyOnNavigationItemSelectedListener(viewPager, this, smoothScroll, listener); 842 | super.setOnNavigationItemSelectedListener(mMyOnNavigationItemSelectedListener); 843 | } 844 | 845 | /** 846 | * A {@link ViewPager.OnPageChangeListener} class which contains the 847 | * necessary calls back to the provided {@link BottomNavigationViewEx} so that the tab position is 848 | * kept in sync. 849 | *

850 | *

This class stores the provided BottomNavigationViewEx weakly, meaning that you can use 851 | * {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener) 852 | * addOnPageChangeListener(OnPageChangeListener)} without removing the listener and 853 | * not cause a leak. 854 | */ 855 | private static class BottomNavigationViewExOnPageChangeListener implements ViewPager.OnPageChangeListener { 856 | private final WeakReference mBnveRef; 857 | 858 | public BottomNavigationViewExOnPageChangeListener(BottomNavigationViewEx bnve) { 859 | mBnveRef = new WeakReference<>(bnve); 860 | } 861 | 862 | @Override 863 | public void onPageScrollStateChanged(final int state) { 864 | } 865 | 866 | @Override 867 | public void onPageScrolled(final int position, final float positionOffset, 868 | final int positionOffsetPixels) { 869 | } 870 | 871 | @Override 872 | public void onPageSelected(final int position) { 873 | final BottomNavigationViewEx bnve = mBnveRef.get(); 874 | if (null != bnve && !isNavigationItemClicking) 875 | bnve.setCurrentItem(position); 876 | // Log.d("onPageSelected", "--------- position " + position + " ------------"); 877 | } 878 | } 879 | 880 | /** 881 | * Decorate OnNavigationItemSelectedListener for setupWithViewPager 882 | */ 883 | private static class MyOnNavigationItemSelectedListener implements OnNavigationItemSelectedListener { 884 | private OnNavigationItemSelectedListener listener; 885 | private final WeakReference viewPagerRef; 886 | private boolean smoothScroll; 887 | private SparseIntArray items;// used for change ViewPager selected item 888 | private int previousPosition = -1; 889 | 890 | 891 | MyOnNavigationItemSelectedListener(ViewPager viewPager, BottomNavigationViewEx bnve, boolean smoothScroll, OnNavigationItemSelectedListener listener) { 892 | this.viewPagerRef = new WeakReference<>(viewPager); 893 | this.listener = listener; 894 | this.smoothScroll = smoothScroll; 895 | 896 | // create items 897 | Menu menu = bnve.getMenu(); 898 | int size = menu.size(); 899 | items = new SparseIntArray(size); 900 | for (int i = 0; i < size; i++) { 901 | int itemId = menu.getItem(i).getItemId(); 902 | items.put(itemId, i); 903 | } 904 | } 905 | 906 | public void setOnNavigationItemSelectedListener(OnNavigationItemSelectedListener listener) { 907 | this.listener = listener; 908 | } 909 | 910 | @Override 911 | public boolean onNavigationItemSelected(@NonNull MenuItem item) { 912 | int position = items.get(item.getItemId()); 913 | // only set item when item changed 914 | if (previousPosition == position) { 915 | return true; 916 | } 917 | // Log.d("onNavigationItemSelecte", "position:" + position); 918 | // user listener 919 | if (null != listener) { 920 | boolean bool = listener.onNavigationItemSelected(item); 921 | // if the selected is invalid, no need change the view pager 922 | if (!bool) 923 | return false; 924 | } 925 | 926 | // change view pager 927 | ViewPager viewPager = viewPagerRef.get(); 928 | if (null == viewPager) 929 | return false; 930 | 931 | // use isNavigationItemClicking flag to avoid `ViewPager.OnPageChangeListener` trigger 932 | isNavigationItemClicking = true; 933 | viewPager.setCurrentItem(items.get(item.getItemId()), smoothScroll); 934 | isNavigationItemClicking = false; 935 | 936 | // update previous position 937 | previousPosition = position; 938 | 939 | return true; 940 | } 941 | 942 | } 943 | 944 | @SuppressLint("RestrictedApi") 945 | public void enableShiftingMode(int position, boolean enable) { 946 | getBottomNavigationItemView(position).setShifting(enable); 947 | } 948 | @SuppressLint("RestrictedApi") 949 | public void setItemBackground(int position, int background) { 950 | getBottomNavigationItemView(position).setItemBackground(background); 951 | } 952 | @SuppressLint("RestrictedApi") 953 | public void setIconTintList(int position, ColorStateList tint) { 954 | getBottomNavigationItemView(position).setIconTintList(tint); 955 | } 956 | @SuppressLint("RestrictedApi") 957 | public void setTextTintList(int position, ColorStateList tint) { 958 | getBottomNavigationItemView(position).setTextColor(tint); 959 | } 960 | 961 | /** 962 | * set margin top for all icons 963 | * 964 | * @param marginTop in px 965 | */ 966 | public void setIconsMarginTop(int marginTop) { 967 | for (int i = 0; i < getItemCount(); i++) { 968 | setIconMarginTop(i, marginTop); 969 | } 970 | } 971 | 972 | /** 973 | * set margin top for icon 974 | * 975 | * @param position 976 | * @param marginTop in px 977 | */ 978 | @SuppressLint("RestrictedApi") 979 | public void setIconMarginTop(int position, int marginTop) { 980 | /* 981 | 1. BottomNavigationItemView 982 | 2. private final int mDefaultMargin; 983 | */ 984 | BottomNavigationItemView itemView = getBottomNavigationItemView(position); 985 | setField(BottomNavigationItemView.class, itemView, "defaultMargin", marginTop); 986 | mMenuView.updateMenuView(); 987 | } 988 | 989 | } 990 | -------------------------------------------------------------------------------- /app/src/main/res/color/selector_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/color/selector_nav_item_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/add_todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/91046a88307ee231f820cc7540d170c063f17ab1/app/src/main/res/drawable-xxhdpi/add_todo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/91046a88307ee231f820cc7540d170c063f17ab1/app/src/main/res/drawable-xxhdpi/arrow_right.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/91046a88307ee231f820cc7540d170c063f17ab1/app/src/main/res/drawable-xxhdpi/back.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/cancel_todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/91046a88307ee231f820cc7540d170c063f17ab1/app/src/main/res/drawable-xxhdpi/cancel_todo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/complete_todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/91046a88307ee231f820cc7540d170c063f17ab1/app/src/main/res/drawable-xxhdpi/complete_todo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/date.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/91046a88307ee231f820cc7540d170c063f17ab1/app/src/main/res/drawable-xxhdpi/date.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/delete_todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/91046a88307ee231f820cc7540d170c063f17ab1/app/src/main/res/drawable-xxhdpi/delete_todo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_complete.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_setting.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_todo.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_add_todo.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 21 | 26 | 40 | 45 | 54 | 59 | 60 |