Showing preview only (222K chars total). Download the full file or copy to clipboard to get everything.
Repository: wjwang0914/wj-todo-wanandroid
Branch: master
Commit: 91046a88307e
Files: 67
Total size: 198.9 KB
Directory structure:
gitextract_pavxei7w/
├── .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/
│ │ │ ├── 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
│ │ ├── 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/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── release/
│ └── app-release.apk
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.iml
.gradle
.idea
/local.properties
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
.DS_Store
/build
/captures
.externalNativeBuild
================================================
FILE: README.md
================================================
# wj-todo-wanandroid
用心打造一款极致体验的TODO开源客户端,数据接口来自鸿神的玩Android,不放过每一个细节,用心写代码,如果您觉得还不错的话,就点个Star吧~(您的每次支持,是我开源的动力)
# APK
<a href="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/release/app-release.apk">app-release.apk</a>
# 项目架构
该项目使用最简单的MVC架构,整体代码实现层次分明,高内聚低耦合,代码逻辑清晰,通俗易懂,使用BottomNavigationView+ViewPager+Fragment完成UI主体实现,引入butterknife依赖注入框架,简化了代码的编写,网络层的编写,主要是引入了我另一个开源框架<a href="https://github.com/wjwang0914/wj-http">wj-http(主要是对Retrofit2进行了二次封装,方便使用,提升开发效率)</a>
<ul>
<li><a href="https://github.com/wjwang0914/wj-http">wj-http</a></li>
<li><a href="https://github.com/JakeWharton/butterknife">butterknife</a></li>
<li><a href="https://github.com/CymChad/BaseRecyclerViewAdapterHelper">BaseRecyclerViewAdapterHelper</a></li>
<li><a href="https://github.com/franmontiel/PersistentCookieJar">PersistentCookieJar</a></li>
</ul>
# 项目运行截图
<div align="center">
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/000.png" width=30%>
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/001.png" width=30%>
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/002.png" width=30%>
</div>
<div align="center">
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/003.png" width=30%>
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/004.png" width=30%>
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/005.png" width=30%>
</div>
<div align="center">
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/006.png" width=30%>
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/008.png" width=30%>
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/011.png" width=30%>
</div>
<div align="center">
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/009.png" width=30%>
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/007.png" width=30%>
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/012.png" width=30%>
</div>
<div align="center">
<img src="https://raw.githubusercontent.com/wjwang0914/wj-todo-wanandroid/master/screenshots/010.png" width=30%>
</div>
## Thanks
### API:
鸿洋大大提供的
[WanAndroid API](http://www.wanandroid.com/)
### ICON:
[iconfont](http://www.iconfont.cn/)
================================================
FILE: app/.gitignore
================================================
/build
/release
/keystore
*.iml
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.wj.android.todo"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release {
keyAlias 'wj'
keyPassword '19900914'
storeFile file('keystore/wj.jks')
storePassword '19900914'
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField('String', 'BASE_URL', FORMAL_BASE_URL)
}
debug {
buildConfigField('String', 'BASE_URL', TEST_BASE_URL)
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0-beta01'
implementation 'com.android.support:design:28.0.0-beta01'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation project(":wj-http")
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.40'
implementation 'com.android.support:recyclerview-v7:28.0.0-beta01'
implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
//implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/src/androidTest/java/com/wj/android/todo/ExampleInstrumentedTest.java
================================================
package com.wj.android.todo;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.wj.android.todo", appContext.getPackageName());
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wj.android.todo">
<!-- To auto-complete the email text field in the login form with the user's emails -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".MainApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".activity.SplashActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".activity.MainActivity"
android:launchMode="singleTask"/>
<activity android:name=".activity.LoginActivity"
android:launchMode="singleTask"/>
<activity android:name=".activity.RegisterActivity"
android:launchMode="singleTask"/>
<activity android:name=".activity.AddTodoActivity" />
<activity android:name=".activity.EditTodoActivity" />
</application>
</manifest>
================================================
FILE: app/src/main/java/com/wj/android/todo/MainApplication.java
================================================
package com.wj.android.todo;
import android.app.Application;
import com.wj.android.http.XRetrofit;
import com.wj.android.todo.manager.PersistentCookieJarManager;
/**
* 作者:wangwnejie on 2018/8/7 16:03
* 邮箱:wang20080990@163.com
*/
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
XRetrofit.init()
.debug(BuildConfig.DEBUG)
.cookieJar(PersistentCookieJarManager.getInstance(this).getPersistentCookieJar());
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/activity/AddTodoActivity.java
================================================
package com.wj.android.todo.activity;
import android.app.DatePickerDialog;
import android.content.Intent;
import android.support.design.widget.TextInputEditText;
import android.text.TextUtils;
import android.view.View;
import android.widget.DatePicker;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.gson.JsonObject;
import com.wj.android.todo.R;
import com.wj.android.todo.activity.base.BaseActivity;
import com.wj.android.todo.bean.TodoDesBean;
import com.wj.android.todo.http.HttpUtils;
import com.wj.android.todo.http.ResponseItem;
import com.wj.android.todo.util.TimeUtils;
import java.util.Calendar;
import java.util.Date;
import butterknife.BindView;
import butterknife.OnClick;
/**
* 作者:wangwnejie on 2018/8/8 19:21
* 邮箱:wang20080990@163.com
*/
public class AddTodoActivity extends BaseActivity {
@BindView(R.id.title)
TextView mTitle;
@BindView(R.id.back)
ImageView mBack;
@BindView(R.id.todo_date)
TextView mTodoDate;
@BindView(R.id.todo_name)
TextInputEditText mTodoName;
@BindView(R.id.todo_des)
TextInputEditText mTodoDes;
@Override
protected int getLayoutId() {
return R.layout.activity_add_todo;
}
@Override
protected void initData() {
mTitle.setText(R.string.add_todo);
mBack.setVisibility(View.VISIBLE);
mTodoDate.setText(TimeUtils.date2String(new Date(),"yyyy-MM-dd"));
}
@OnClick(R.id.back)
void onClickBack() {
finish();
}
@OnClick(R.id.todo_date)
void onClickTodoDate() {
Calendar calendar = Calendar.getInstance();
DatePickerDialog datePickerDialog = new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker datePicker, int year, int month, int dayOfMonth) {
mTodoDate.setText(String.format("%d-%d-%d", year, month+1, dayOfMonth));
}
},calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH));
datePickerDialog.getDatePicker().setMinDate(new Date().getTime());
datePickerDialog.show();
}
@OnClick(R.id.save_todo)
void onClickAddTodo() {
mTodoName.setError(null);
if (TextUtils.isEmpty(mTodoName.getText())) {
mTodoName.setError(getString(R.string.input_todo_name_toast));
mTodoName.setFocusable(true);
mTodoName.setFocusableInTouchMode(true);
mTodoName.requestFocus();
return;
}
requestAddTodoData();
}
private void requestAddTodoData() {
HttpUtils.requestAddTodoData(this, mTodoName.getText().toString(), mTodoDes.getText().toString(), mTodoDate.getText().toString());
}
public void updateUI(ResponseItem<TodoDesBean> response) {
if (response.isSuccess()) {
showToast(getString(R.string.add_todo_success));
Intent intent = new Intent();
intent.putExtra("add_todo", response.getData());
setResult(0x200, intent);
finish();
}
}
@Override
public boolean isLoadingEnable(int requestId) {
return super.isLoadingEnable(requestId);
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/activity/EditTodoActivity.java
================================================
package com.wj.android.todo.activity;
import android.app.DatePickerDialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.TextInputEditText;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.ImageView;
import android.widget.TextView;
import com.wj.android.todo.R;
import com.wj.android.todo.activity.base.BaseActivity;
import com.wj.android.todo.bean.TodoDesBean;
import com.wj.android.todo.http.HttpUtils;
import com.wj.android.todo.http.ResponseItem;
import com.wj.android.todo.util.TimeUtils;
import java.util.Calendar;
import java.util.Date;
import butterknife.BindView;
import butterknife.OnClick;
/**
* 作者:wangwnejie on 2018/8/8 19:21
* 邮箱:wang20080990@163.com
*/
public class EditTodoActivity extends BaseActivity {
@BindView(R.id.title)
TextView mTitle;
@BindView(R.id.back)
ImageView mBack;
@BindView(R.id.todo_date)
TextView mTodoDate;
@BindView(R.id.todo_name)
TextInputEditText mTodoName;
@BindView(R.id.todo_des)
TextInputEditText mTodoDes;
@BindView(R.id.save_todo)
Button mSaveTodo;
private TodoDesBean mTodoDesBean;
@Override
protected int getLayoutId() {
return R.layout.activity_edit_todo;
}
@Override
protected void initData() {
mTitle.setText(R.string.update_todo);
mBack.setVisibility(View.VISIBLE);
Bundle bundle = getIntent().getExtras();
if (bundle == null) {
return;
}
mTodoDesBean = (TodoDesBean) bundle.getSerializable("todo_des");
if (mTodoDesBean == null) {
return;
}
mTodoName.setText(mTodoDesBean.getTitle());
if (!TextUtils.isEmpty(mTodoDesBean.getContent())) {
mTodoDes.setText(mTodoDesBean.getContent());
}
mTodoDate.setText(mTodoDesBean.getDateStr());
if (mTodoDesBean.getStatus() == 1) {
mSaveTodo.setVisibility(View.GONE);
mTodoName.setEnabled(false);
mTodoName.setFocusable(false);
mTodoDes.setEnabled(false);
mTodoDes.setFocusable(false);
mTodoDate.setEnabled(false);
mTodoDate.setFocusable(false);
}
}
@OnClick(R.id.back)
void onClickBack() {
finish();
}
@OnClick(R.id.todo_date)
void onClickTodoDate() {
Calendar calendar = Calendar.getInstance();
DatePickerDialog datePickerDialog = new DatePickerDialog(this, new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker datePicker, int year, int month, int dayOfMonth) {
mTodoDate.setText(String.format("%d-%d-%d", year, month+1, dayOfMonth));
}
},calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH));
datePickerDialog.getDatePicker().setMinDate(new Date().getTime());
datePickerDialog.show();
}
@OnClick(R.id.save_todo)
void onClickAddTodo() {
mTodoName.setError(null);
if (TextUtils.isEmpty(mTodoName.getText())) {
mTodoName.setError(getString(R.string.input_todo_name_toast));
mTodoName.setFocusable(true);
mTodoName.setFocusableInTouchMode(true);
mTodoName.requestFocus();
return;
}
requestUpdateTodoData();
}
private void requestUpdateTodoData() {
HttpUtils.requestUpdateTodoData(this, mTodoDesBean.getId(), mTodoName.getText().toString(), mTodoDes.getText().toString(), mTodoDate.getText().toString());
}
public void updateUI(ResponseItem<TodoDesBean> response) {
if (response.isSuccess()) {
showToast(getString(R.string.update_todo_success));
Intent intent = new Intent();
intent.putExtra("update_todo", response.getData());
setResult(0x210, intent);
finish();
}
}
@Override
public boolean isLoadingEnable(int requestId) {
return super.isLoadingEnable(requestId);
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/activity/LoginActivity.java
================================================
package com.wj.android.todo.activity;
import android.text.TextUtils;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import com.google.gson.JsonObject;
import com.wj.android.todo.R;
import com.wj.android.todo.activity.base.BaseActivity;
import com.wj.android.todo.http.HttpUtils;
import com.wj.android.todo.http.ResponseItem;
import com.wj.android.todo.manager.SharePreferenceManager;
import butterknife.BindView;
import butterknife.OnClick;
public class LoginActivity extends BaseActivity{
@BindView(R.id.account)
AutoCompleteTextView mAccount;
@BindView(R.id.password)
EditText mPassword;
@Override
protected int getLayoutId() {
return R.layout.activity_login;
}
@Override
protected void initData() {
}
@OnClick(R.id.regitster)
void clickRegister() {
startActivity(RegisterActivity.class);
}
@OnClick(R.id.login)
void clickLogin() {
mAccount.setError(null);
mPassword.setError(null);
if (TextUtils.isEmpty(mAccount.getText())) {
mAccount.setError(getString(R.string.input_account));
mAccount.setFocusable(true);
mAccount.setFocusableInTouchMode(true);
mAccount.requestFocus();
return;
}
if (TextUtils.isEmpty(mPassword.getText())) {
mPassword.setError(getString(R.string.input_password));
mPassword.setFocusable(true);
mPassword.setFocusableInTouchMode(true);
mPassword.requestFocus();
return;
}
requestLogin();
}
@Override
public boolean isLoadingEnable(int requestId) {
return super.isLoadingEnable(requestId);
}
private void requestLogin() {
HttpUtils.requestLogin(this, mAccount.getText().toString(), mPassword.getText().toString());
}
public void updateUI(ResponseItem<JsonObject> response) {
if (response.isSuccess()) {
SharePreferenceManager.getInstance(this).setUserName(response.getData().get("username").getAsString());
startActivity(MainActivity.class);
finish();
}
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/activity/MainActivity.java
================================================
package com.wj.android.todo.activity;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.wj.android.todo.R;
import com.wj.android.todo.activity.base.BaseActivity;
import com.wj.android.todo.bean.TodoDesBean;
import com.wj.android.todo.fragment.SettingFragment;
import com.wj.android.todo.fragment.TodoFragment;
import com.wj.android.todo.widget.BottomNavigationViewEx;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.OnClick;
/**
* 作者:wangwnejie on 2018/8/7 11:17
* 邮箱:wang20080990@163.com
*/
public class MainActivity extends BaseActivity {
private static final int REQUEST_CODE_ADD_TODO = 0x100;
@BindView(R.id.view_pager)
ViewPager mViewPager;
@BindView(R.id.navigation)
BottomNavigationViewEx mNavigationView;
@BindView(R.id.title)
TextView mTitle;
@BindView(R.id.add_todo)
ImageView mAddTodo;
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void initData() {
mTitle.setText(R.string.to_do_list);
mAddTodo.setVisibility(View.VISIBLE);
mViewPager.setOffscreenPageLimit(3);
List<Fragment> fragments = new ArrayList<>(3);
fragments.add(TodoFragment.newInstance(false));
fragments.add(TodoFragment.newInstance(true));
fragments.add(SettingFragment.newInstance());
MainViewPagerAdapter adapter = new MainViewPagerAdapter(getSupportFragmentManager(), fragments);
mViewPager.setAdapter(adapter);
mNavigationView.setupWithViewPager(mViewPager);
}
@Override
protected void applyEvent() {
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float v, int i1) {
}
@Override
public void onPageSelected(int position) {
switch (position) {
case 0:
mTitle.setText(R.string.to_do_list);
mAddTodo.setVisibility(View.VISIBLE);
break;
case 1:
mTitle.setText(R.string.complete_list);
mAddTodo.setVisibility(View.INVISIBLE);
break;
case 2:
mTitle.setText(R.string.setting);
mAddTodo.setVisibility(View.INVISIBLE);
break;
}
}
@Override
public void onPageScrollStateChanged(int position) {
}
});
}
@OnClick(R.id.add_todo)
void onClickAddTodo() {
startActivityForResult(AddTodoActivity.class, REQUEST_CODE_ADD_TODO);
}
private static class MainViewPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> mFragments;
public MainViewPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
mFragments = fragments;
}
@Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
@Override
public int getCount() {
return mFragments.size();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_ADD_TODO) {
switch (resultCode) {
case 0x200:
TodoDesBean todoDesBean = (TodoDesBean) data.getSerializableExtra("add_todo");
TodoFragment todoFragment = (TodoFragment)((MainViewPagerAdapter)mViewPager.getAdapter()).getItem(0);
todoFragment.updateAddTodoData(todoDesBean);
break;
}
}
}
public void updateDoneOrCancelData(TodoDesBean todoDesBean, int postition) {
TodoFragment todoFragment = (TodoFragment)((MainViewPagerAdapter)mViewPager.getAdapter()).getItem(postition);
todoFragment.updateAddTodoData(todoDesBean);
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/activity/RegisterActivity.java
================================================
package com.wj.android.todo.activity;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.gson.JsonObject;
import com.wj.android.todo.R;
import com.wj.android.todo.activity.base.BaseActivity;
import com.wj.android.todo.http.HttpUtils;
import com.wj.android.todo.http.ResponseItem;
import butterknife.BindView;
import butterknife.OnClick;
/**
* 作者:wangwnejie on 2018/8/8 18:20
* 邮箱:wang20080990@163.com
*/
public class RegisterActivity extends BaseActivity {
@BindView(R.id.title)
TextView mTitle;
@BindView(R.id.back)
ImageView mBack;
@BindView(R.id.account)
EditText mAccount;
@BindView(R.id.password)
EditText mPassword;
@BindView(R.id.repassword)
EditText mRepassword;
@Override
protected int getLayoutId() {
return R.layout.activity_register;
}
@Override
protected void initData() {
mTitle.setText(R.string.register);
mBack.setVisibility(View.VISIBLE);
}
@OnClick(R.id.regitster)
void onClickRegister() {
mAccount.setError(null);
mPassword.setError(null);
if (TextUtils.isEmpty(mAccount.getText())) {
mAccount.setError(getString(R.string.input_account));
mAccount.setFocusable(true);
mAccount.setFocusableInTouchMode(true);
mAccount.requestFocus();
return;
}
if (TextUtils.isEmpty(mPassword.getText())) {
mPassword.setError(getString(R.string.input_password));
mPassword.setFocusable(true);
mPassword.setFocusableInTouchMode(true);
mPassword.requestFocus();
return;
}
if (TextUtils.isEmpty(mRepassword.getText())) {
mRepassword.setError(getString(R.string.input_repassword));
mRepassword.setFocusable(true);
mRepassword.setFocusableInTouchMode(true);
mRepassword.requestFocus();
return;
}
if (!TextUtils.equals(mPassword.getText(), mRepassword.getText())) {
mRepassword.setError(getString(R.string.valid_repassword));
return;
}
requestRegister();
}
private void requestRegister() {
HttpUtils.requestRegister(this, mAccount.getText().toString(), mPassword.getText().toString(), mRepassword.getText().toString());
}
@OnClick(R.id.back)
void onClickBack() {
finish();
}
public void updateUI(ResponseItem<JsonObject> response) {
if (response.isSuccess()) {
showToast(getResources().getString(R.string.register_success));
finish();
}
}
@Override
public boolean isLoadingEnable(int requestId) {
return super.isLoadingEnable(requestId);
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/activity/SplashActivity.java
================================================
package com.wj.android.todo.activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import com.wj.android.todo.BuildConfig;
import com.wj.android.todo.R;
import com.wj.android.todo.activity.base.BaseActivity;
import com.wj.android.todo.constant.Constant;
import com.wj.android.todo.manager.PersistentCookieJarManager;
import java.util.List;
import butterknife.BindView;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
/**
* 作者:wangwnejie on 2018/8/8 16:01
* 邮箱:wang20080990@163.com
*/
public class SplashActivity extends BaseActivity {
@BindView(R.id.root_view)
View mView;
@Override
protected int getLayoutId() {
return R.layout.activity_splash;
}
@Override
protected void initData() {
AlphaAnimation animation = new AlphaAnimation(0.3f, 1.0f);
animation.setDuration(3000);
mView.startAnimation(animation);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
redirectTo();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
private void redirectTo() {
List<Cookie> cookies = PersistentCookieJarManager.getInstance(this).getPersistentCookieJar().loadForRequest(HttpUrl.parse(BuildConfig.BASE_URL));
if (cookies.isEmpty()) {
startActivity(LoginActivity.class);
} else {
Bundle bundle = new Bundle();
bundle.putString("user_name", cookies.get(0).name());
startActivity(MainActivity.class, bundle);
}
finish();
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/activity/base/BaseActivity.java
================================================
package com.wj.android.todo.activity.base;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
import com.wj.android.http.BaseView;
import com.wj.android.todo.R;
import com.wj.android.todo.exception.ApiException;
import butterknife.ButterKnife;
/**
* 作者:wangwnejie on 2018/3/22 16:57
* 邮箱:wang20080990@163.com
*/
public abstract class BaseActivity extends AppCompatActivity implements BaseView {
private static final String TAG = BaseActivity.class.getSimpleName();
private ProgressDialog mProgressDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, String.format("%s:onCreate", this));
if (getLayoutId() != 0) {
setContentView(getLayoutId());
}
ButterKnife.bind(this);
initView();
initData();
applyEvent();
}
@Override
protected void onStart() {
super.onStart();
Log.i(TAG, String.format("%s:onStart", this));
}
@Override
protected void onRestart() {
super.onRestart();
Log.i(TAG, String.format("%s:onRestart", this));
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG, String.format("%s:onResume", this));
}
@Override
protected void onPause() {
super.onPause();
Log.i(TAG, String.format("%s:onPause", this));
}
@Override
protected void onStop() {
super.onStop();
Log.i(TAG, String.format("%s:onStop", this));
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i(TAG, String.format("%s:onDestroy", this));
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
}
/**
* 返回当前界面布局文件
* @return
*/
protected abstract int getLayoutId();
/**
* 初始化View
*/
protected void initView(){}
/**
* 初始化数据
*/
protected abstract void initData();
/**
* 设置事件监听
*/
protected void applyEvent(){}
@Override
public boolean isLoadingEnable(int requestId) {
return false;
}
@Override
public void start(int requestId) {
if (isLoadingEnable(requestId) && !isFinishing()) {
mProgressDialog = new ProgressDialog(this,ProgressDialog.THEME_HOLO_LIGHT);
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setCancelable(false);
mProgressDialog.setMessage(getResources().getString(R.string.loading));
mProgressDialog.show();
}
}
@Override
public void error(Throwable t, int code, int requestId) {
if (t instanceof ApiException) {
showToast(t.getMessage());
} else {
showToast(getResources().getString(R.string.service_error));
}
}
@Override
public void end(int requestId) {
if (isLoadingEnable(requestId) && !isFinishing()) {
mProgressDialog.dismiss();
}
}
public void showToast(String msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
public void startActivity(Class<?> cls) {
Intent intent = new Intent(this, cls);
startActivity(intent);
}
public void startActivity(Class<?> cls, Bundle bundle) {
Intent intent = new Intent(this, cls);
intent.putExtras(bundle);
startActivity(intent);
}
public void startActivityForResult(Class<?> cls, int requestCode) {
Intent intent = new Intent(this, cls);
startActivityForResult(intent, requestCode);
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/adapter/TodoSectionAdapter.java
================================================
package com.wj.android.todo.adapter;
import android.text.TextUtils;
import com.chad.library.adapter.base.BaseSectionQuickAdapter;
import com.chad.library.adapter.base.BaseViewHolder;
import com.wj.android.todo.R;
import com.wj.android.todo.bean.TodoSection;
import java.util.List;
/**
* 作者:wangwnejie on 2018/8/9 14:41
* 邮箱:wang20080990@163.com
*/
public class TodoSectionAdapter extends BaseSectionQuickAdapter<TodoSection, BaseViewHolder> {
private boolean isDone;
public TodoSectionAdapter(int layoutResId, int sectionHeadResId, List<TodoSection> data, boolean isDone) {
super(layoutResId, sectionHeadResId, data);
this.isDone = isDone;
}
@Override
protected void convertHead(BaseViewHolder helper, TodoSection item) {
helper.setText(R.id.todo_head, item.header);
if (isDone) {
helper.setTextColor(R.id.todo_head, mContext.getResources().getColor(R.color.done_todo_date));
} else {
helper.setTextColor(R.id.todo_head, mContext.getResources().getColor(R.color.colorPrimary));
}
}
@Override
protected void convert(BaseViewHolder helper, TodoSection item) {
helper.setText(R.id.item_name, item.t.getTitle());
if (TextUtils.isEmpty(item.t.getContent())) {
helper.setGone(R.id.item_des, false);
} else {
helper.setGone(R.id.item_des, true);
helper.setText(R.id.item_des, item.t.getContent());
}
if (isDone) {
helper.setGone(R.id.item_done_time, true);
helper.setText(R.id.item_done_time, String.format(mContext.getResources().getString(R.string.done_todo_date),item.t.getCompleteDateStr()));
helper.setImageResource(R.id.item_complete, R.drawable.cancel_todo);
} else {
helper.setGone(R.id.item_done_time, false);
helper.setImageResource(R.id.item_complete, R.drawable.complete_todo);
}
helper.addOnClickListener(R.id.item_complete);
helper.addOnClickListener(R.id.item_delete);
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/bean/TodoDesBean.java
================================================
package com.wj.android.todo.bean;
import java.io.Serializable;
/**
* 作者:wangwnejie on 2018/8/9 14:29
* 邮箱:wang20080990@163.com
*/
public class TodoDesBean implements Serializable{
private Object completeDate;
private String completeDateStr;
private String content;
private long date;
private String dateStr;
private int id;
private int status;
private String title;
private int type;
private int userId;
public Object getCompleteDate() {
return completeDate;
}
public void setCompleteDate(Object completeDate) {
this.completeDate = completeDate;
}
public String getCompleteDateStr() {
return completeDateStr;
}
public void setCompleteDateStr(String completeDateStr) {
this.completeDateStr = completeDateStr;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public long getDate() {
return date;
}
public void setDate(long date) {
this.date = date;
}
public String getDateStr() {
return dateStr;
}
public void setDateStr(String dateStr) {
this.dateStr = dateStr;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/bean/TodoListBean.java
================================================
package com.wj.android.todo.bean;
import java.util.List;
/**
* 作者:wangwnejie on 2018/8/9 14:55
* 邮箱:wang20080990@163.com
*/
public class TodoListBean {
private int curPage;
private int offset;
private boolean over;
private int pageCount;
private int size;
private int total;
private List<TodoDesBean> datas;
public int getCurPage() {
return curPage;
}
public void setCurPage(int curPage) {
this.curPage = curPage;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public boolean isOver() {
return over;
}
public void setOver(boolean over) {
this.over = over;
}
public int getPageCount() {
return pageCount;
}
public void setPageCount(int pageCount) {
this.pageCount = pageCount;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public List<TodoDesBean> getDatas() {
return datas;
}
public void setDatas(List<TodoDesBean> datas) {
this.datas = datas;
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/bean/TodoSection.java
================================================
package com.wj.android.todo.bean;
import com.chad.library.adapter.base.entity.SectionEntity;
/**
* 作者:wangwnejie on 2018/8/9 14:33
* 邮箱:wang20080990@163.com
*/
public class TodoSection extends SectionEntity<TodoDesBean> {
public TodoSection(boolean isHeader, String header) {
super(isHeader, header);
}
public TodoSection(TodoDesBean todoBean) {
super(todoBean);
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/constant/Constant.java
================================================
package com.wj.android.todo.constant;
/**
* 作者:wangwnejie on 2018/8/7 15:24
* 邮箱:wang20080990@163.com
*/
public class Constant {
//public static final String BASE_URL = "http://www.wanandroid.com";
public static final String LOGIN_URI = "/user/login";
public static final String REGISTER_URI = "/user/register";
public static final String ADD_TODO_URI = "/lg/todo/add/json";
public static final String TODO_LIST_URI = "/lg/todo/listnotdo/0/json/%d";
public static final String DELETE_TODO_URI = "/lg/todo/delete/%d/json";
public static final String UPDATE_TODO_URI = "/lg/todo/update/%d/json";
public static final String DONE_TODO_URI = "/lg/todo/done/%d/json";
public static final String DONE_LIST_URI = "/lg/todo/listdone/0/json/%d";
}
================================================
FILE: app/src/main/java/com/wj/android/todo/constant/TimeConstants.java
================================================
package com.wj.android.todo.constant;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* <pre>
* author: Blankj
* blog : http://blankj.com
* time : 2017/03/13
* desc : constants of time
* </pre>
*/
public final class TimeConstants {
public static final int MSEC = 1;
public static final int SEC = 1000;
public static final int MIN = 60000;
public static final int HOUR = 3600000;
public static final int DAY = 86400000;
@IntDef({MSEC, SEC, MIN, HOUR, DAY})
@Retention(RetentionPolicy.SOURCE)
public @interface Unit {
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/exception/ApiException.java
================================================
package com.wj.android.todo.exception;
/**
* 作者:wangwnejie on 2018/8/8 14:06
* 邮箱:wang20080990@163.com
*/
public class ApiException extends RuntimeException{
private int errorCode;
public ApiException(String message, int errorCode) {
super(message);
setErrorCode(errorCode);
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/fragment/SettingFragment.java
================================================
package com.wj.android.todo.fragment;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.TextView;
import com.wj.android.todo.R;
import com.wj.android.todo.activity.LoginActivity;
import com.wj.android.todo.activity.MainActivity;
import com.wj.android.todo.fragment.base.BaseFragment;
import com.wj.android.todo.manager.PersistentCookieJarManager;
import com.wj.android.todo.manager.SharePreferenceManager;
import java.util.List;
import butterknife.BindView;
import butterknife.OnClick;
import okhttp3.Cookie;
/**
* 作者:wangwnejie on 2018/8/8 13:42
* 邮箱:wang20080990@163.com
*/
public class SettingFragment extends BaseFragment {
@BindView(R.id.user_name)
TextView mUserName;
public static SettingFragment newInstance() {
Bundle args = new Bundle();
SettingFragment fragment = new SettingFragment();
fragment.setArguments(args);
return fragment;
}
@Override
protected int getLayoutId() {
return R.layout.fragment_setting;
}
@Override
protected void initData() {
String userName = SharePreferenceManager.getInstance(getContext()).getUserName();
if (!TextUtils.isEmpty(userName)) {
mUserName.setText(SharePreferenceManager.getInstance(getContext()).getUserName());
}
}
@OnClick(R.id.logout)
void onClickLogout() {
SharePreferenceManager.getInstance(getContext()).clear();
PersistentCookieJarManager.getInstance(getContext()).getPersistentCookieJar().clear();
startActivity(LoginActivity.class);
getActivity().finish();
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/fragment/TodoFragment.java
================================================
package com.wj.android.todo.fragment;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.BottomSheetDialog;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.wj.android.todo.R;
import com.wj.android.todo.activity.EditTodoActivity;
import com.wj.android.todo.activity.MainActivity;
import com.wj.android.todo.adapter.TodoSectionAdapter;
import com.wj.android.todo.bean.TodoDesBean;
import com.wj.android.todo.bean.TodoListBean;
import com.wj.android.todo.bean.TodoSection;
import com.wj.android.todo.fragment.base.BaseFragment;
import com.wj.android.todo.http.HttpUtils;
import com.wj.android.todo.http.ResponseItem;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import butterknife.BindView;
/**
* 作者:wangwnejie on 2018/8/8 13:42
* 邮箱:wang20080990@163.com
*/
public class TodoFragment extends BaseFragment {
private static final String KEY_IS_DONE = "is_done";
private static final int REQUEST_CODE_EDIT_TODO = 0x110;
@BindView(R.id.todo_rv)
RecyclerView mRecyclerView;
@BindView(R.id.swipe_layout)
SwipeRefreshLayout mSwipeRefreshLayout;
private int page = 1;
private TodoListBean mTodoListBean;
private TodoSectionAdapter mAdapter;
private int deletePosition = -1;
private int donePosition = -1;
private boolean isDone;
public static TodoFragment newInstance(boolean isDone) {
Bundle args = new Bundle();
args.putBoolean(KEY_IS_DONE, isDone);
TodoFragment fragment = new TodoFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
if (bundle != null) {
isDone = bundle.getBoolean(KEY_IS_DONE);
}
}
@Override
protected int getLayoutId() {
return R.layout.fragment_todo;
}
@Override
protected void initData() {
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
page = 1;
requestTodoListData();
}
});
requestTodoListData();
}
private void requestTodoListData() {
HttpUtils.requestTodoList(this, page, isDone);
}
private void deleteTodoById(int todoId) {
HttpUtils.deleteTodoById(this,todoId);
}
private void doneTodoById(int todoId) {
HttpUtils.doneTodoById(this, todoId, isDone ? 0 : 1);
}
public void updateUI(final ResponseItem<TodoListBean> response) {
if (response.isSuccess() && response.getData() != null) {
if (page == 1) {
mTodoListBean = response.getData();
mAdapter = new TodoSectionAdapter(R.layout.todo_item_view,R.layout.todo_item_head,getTodoSectionData(mTodoListBean.getDatas()),isDone);
mAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
@Override
public void onLoadMoreRequested() {
if (mTodoListBean.getPageCount() == 1) {
mAdapter.loadMoreEnd(true);
} else if (page >= mTodoListBean.getPageCount()) {
mAdapter.loadMoreEnd(false);
} else {
page++;
requestTodoListData();
}
}
}, mRecyclerView);
mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
switch (view.getId()){
case R.id.item_complete:
donePosition = position;
doneTodoById(mAdapter.getData().get(position).t.getId());
break;
case R.id.item_delete:
showDialog(position);
break;
}
}
});
mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
TodoSection todoSection = mAdapter.getData().get(position);
//showTodoDes(todoSection);
Bundle bundle = new Bundle();
bundle.putSerializable("todo_des", todoSection.t);
startActivityForResult(EditTodoActivity.class, bundle, REQUEST_CODE_EDIT_TODO);
}
});
mAdapter.openLoadAnimation(BaseQuickAdapter.SLIDEIN_BOTTOM);
mRecyclerView.setAdapter(mAdapter);
} else {
mAdapter.addData(getTodoSectionData(response.getData().getDatas()));
mAdapter.loadMoreComplete();
}
}
}
private void showTodoDes(TodoSection todoSection) {
View view = View.inflate(getContext(),R.layout.dialog_todo_des_view, null);
TextView todoName = view.findViewById(R.id.todo_name);
todoName.setText(todoSection.t.getTitle());
TextView todoContent = view.findViewById(R.id.todo_content);
if (TextUtils.isEmpty(todoSection.t.getContent())) {
todoContent.setText(R.string.no_text);
} else {
todoContent.setText(todoSection.t.getContent());
}
BottomSheetDialog sheetDialog = new BottomSheetDialog(getContext());
sheetDialog.setContentView(view);
sheetDialog.show();
}
private List<TodoSection> getTodoSectionData(List<TodoDesBean> datas) {
List<TodoSection> todoSections = new ArrayList<>();
LinkedHashSet<String> dates = new LinkedHashSet<>();
for (TodoDesBean todoDesBean : datas) {
dates.add(todoDesBean.getDateStr());
}
for (String date : dates) {
TodoSection todoSectionHead = new TodoSection(true, date);
todoSections.add(todoSectionHead);
for (TodoDesBean todoDesBean : datas) {
if (TextUtils.equals(date, todoDesBean.getDateStr())) {
TodoSection todoSectionContent = new TodoSection(todoDesBean);
todoSections.add(todoSectionContent);
}
}
}
return todoSections;
}
@Override
public boolean isLoadingEnable(int requestId) {
return requestId == 1;
}
@Override
public void start(int requestId) {
if (requestId == 1) {
super.start(requestId);
} else {
if (page == 1) {
mSwipeRefreshLayout.setRefreshing(true);
}
}
}
@Override
public void error(Throwable t, int code, int requestId) {
super.error(t,code,requestId);
if (requestId == 0) {
if (page > 1) {
mAdapter.loadMoreFail();
}
}
}
@Override
public void end(int requestId) {
if (requestId == 1) {
super.end(requestId);
} else {
if (page == 1) {
mSwipeRefreshLayout.setRefreshing(false);
}
}
}
public void updateAddTodoData(TodoDesBean todoDesBean) {
List<TodoSection> todoSections = mAdapter.getData();
for (int i = 0; i < todoSections.size(); i++) {
TodoSection todoSection = todoSections.get(i);
if (todoSection.isHeader && TextUtils.equals(todoSection.header, todoDesBean.getDateStr())) {
TodoSection section = new TodoSection(todoDesBean);
mAdapter.getData().add(i+1,section);
mAdapter.notifyItemInserted(i+1);
mRecyclerView.scrollToPosition(i+1);
return;
}
}
TodoSection sectionHead = new TodoSection(true,todoDesBean.getDateStr());
mAdapter.getData().add(0, sectionHead);
TodoSection section = new TodoSection(todoDesBean);
mAdapter.getData().add(1, section);
mAdapter.notifyItemRangeInserted(0,2);
mRecyclerView.scrollToPosition(0);
}
public void updateRemovedData(ResponseItem response) {
if (response.isSuccess() && deletePosition != -1) {
if (mAdapter.getData().get(deletePosition-1).isHeader && (mAdapter.getData().size()== deletePosition+2 || mAdapter.getData().get(deletePosition+1).isHeader)) {
mAdapter.getData().remove(deletePosition-1);
mAdapter.getData().remove(deletePosition-1);
mAdapter.notifyItemRangeRemoved(deletePosition-1,2);
} else {
mAdapter.getData().remove(deletePosition);
mAdapter.notifyItemRemoved(deletePosition);
}
showToast(getString(R.string.delete_todo_success));
}
}
public void updateDoneData(ResponseItem<TodoDesBean> response) {
if (response.isSuccess() && donePosition != -1) {
if (mAdapter.getData().get(donePosition-1).isHeader && (mAdapter.getData().size()== donePosition+2 || mAdapter.getData().get(donePosition+1).isHeader)) {
mAdapter.getData().remove(donePosition-1);
mAdapter.getData().remove(donePosition-1);
mAdapter.notifyItemRangeRemoved(donePosition-1,2);
} else {
mAdapter.getData().remove(donePosition);
mAdapter.notifyItemRemoved(donePosition);
}
showToast(getString(isDone ? R.string.notdo_todo_success : R.string.done_todo_success));
((MainActivity)getActivity()).updateDoneOrCancelData(response.getData(), isDone ? 0 : 1);
}
}
private void showDialog(final int position) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.delete_todo);
builder.setMessage(R.string.sure_delete_todo);
builder.setNegativeButton(R.string.cancel,null);
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
deletePosition = position;
deleteTodoById(mAdapter.getData().get(position).t.getId());
}
});
builder.show();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_EDIT_TODO) {
switch (resultCode) {
case 0x210:
page = 1;
requestTodoListData();
break;
}
}
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/fragment/base/BaseFragment.java
================================================
package com.wj.android.todo.fragment.base;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.wj.android.http.BaseView;
import com.wj.android.todo.R;
import com.wj.android.todo.exception.ApiException;
import butterknife.ButterKnife;
import butterknife.Unbinder;
/**
* 作者:wangwnejie on 2018/4/2 16:58
* 邮箱:wang20080990@163.com
*/
public abstract class BaseFragment extends Fragment implements BaseView {
private Unbinder mUnbinder;
private ProgressDialog mProgressDialog;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(getLayoutId(), container, false);
mUnbinder = ButterKnife.bind(this,view);
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initData();
}
@Override
public void onDestroyView() {
super.onDestroyView();
mUnbinder.unbind();
}
/**
* 返回当前界面布局文件
* @return
*/
protected abstract int getLayoutId();
/**
* 初始化数据
*/
protected abstract void initData();
public boolean isLoadingEnable(int requestId) {
return false;
}
@Override
public void start(int requestId) {
if (isLoadingEnable(requestId) && !getActivity().isFinishing()) {
mProgressDialog = new ProgressDialog(getContext(),ProgressDialog.THEME_HOLO_LIGHT);
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setCancelable(false);
mProgressDialog.setMessage(getResources().getString(R.string.loading));
mProgressDialog.show();
}
}
@Override
public void error(Throwable t, int code, int requestId) {
if (isAdded()) {
if (t instanceof ApiException) {
showToast(t.getMessage());
} else {
showToast(getResources().getString(R.string.service_error));
}
}
}
@Override
public void end(int requestId) {
if (isLoadingEnable(requestId) && !getActivity().isFinishing()) {
mProgressDialog.dismiss();
}
}
public void showToast(String msg) {
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
}
public void startActivity(Class<?> cls) {
Intent intent = new Intent(getActivity(), cls);
startActivity(intent);
}
public void startActivity(Class<?> cls, Bundle bundle) {
Intent intent = new Intent(getActivity(), cls);
intent.putExtras(bundle);
startActivity(intent);
}
public void startActivityForResult(Class<?> cls, Bundle bundle,int requestCode) {
Intent intent = new Intent(getActivity(), cls);
intent.putExtras(bundle);
startActivityForResult(intent, requestCode);
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/http/HttpUtils.java
================================================
package com.wj.android.todo.http;
import android.text.TextUtils;
import com.google.gson.JsonObject;
import com.wj.android.http.BaseView;
import com.wj.android.http.XRetrofit;
import com.wj.android.todo.BuildConfig;
import com.wj.android.todo.activity.AddTodoActivity;
import com.wj.android.todo.activity.EditTodoActivity;
import com.wj.android.todo.activity.LoginActivity;
import com.wj.android.todo.activity.RegisterActivity;
import com.wj.android.todo.bean.TodoDesBean;
import com.wj.android.todo.bean.TodoListBean;
import com.wj.android.todo.constant.Constant;
import com.wj.android.todo.fragment.TodoFragment;
import java.util.HashMap;
import java.util.Map;
/**
* 作者:wangwnejie on 2018/8/7 15:21
* 邮箱:wang20080990@163.com
*/
public class HttpUtils {
private static String buildUrl(String uri) {
return String.format("%s%s", BuildConfig.BASE_URL, uri);
}
public static void requestLogin(BaseView baseView, String userName, String password){
Map<String, String> params = new HashMap<>();
params.put("username", userName);
params.put("password", password);
XRetrofit.post(buildUrl(Constant.LOGIN_URI), params, new MyGsonCallback<ResponseItem<JsonObject>>(baseView) {
@Override
protected void onSuccess(ResponseItem<JsonObject> response, BaseView baseView) {
((LoginActivity)baseView).updateUI(response);
}
});
}
public static void requestRegister(BaseView baseView, String userName, String password, String repassword) {
Map<String, String> params = new HashMap<>();
params.put("username", userName);
params.put("password", password);
params.put("repassword", repassword);
XRetrofit.post(buildUrl(Constant.REGISTER_URI), params, new MyGsonCallback<ResponseItem<JsonObject>>(baseView) {
@Override
protected void onSuccess(ResponseItem<JsonObject> response, BaseView baseView) {
((RegisterActivity)baseView).updateUI(response);
}
});
}
public static void requestAddTodoData(BaseView baseView, String todoName, String todoDes, String todoDate) {
Map<String, String> params = new HashMap<>();
params.put("title", todoName);
if (!TextUtils.isEmpty(todoDes)) {
params.put("content", todoDes);
}
params.put("date", todoDate);
params.put("type", "0");
XRetrofit.post(buildUrl(Constant.ADD_TODO_URI), params, new MyGsonCallback<ResponseItem<TodoDesBean>>(baseView) {
@Override
protected void onSuccess(ResponseItem<TodoDesBean> response, BaseView baseView) {
((AddTodoActivity)baseView).updateUI(response);
}
});
}
public static void requestTodoList(BaseView baseView, int page, boolean isDone) {
XRetrofit.post(isDone ? buildUrl(String.format(Constant.DONE_LIST_URI, page)) : buildUrl(String.format(Constant.TODO_LIST_URI, page)), new MyGsonCallback<ResponseItem<TodoListBean>>(baseView) {
@Override
protected void onSuccess(ResponseItem<TodoListBean> response, BaseView baseView) {
((TodoFragment)baseView).updateUI(response);
}
});
}
public static void deleteTodoById(BaseView baseView, int todoId) {
XRetrofit.post(buildUrl(String.format(Constant.DELETE_TODO_URI, todoId)), new MyGsonCallback<ResponseItem>(baseView,1) {
@Override
protected void onSuccess(ResponseItem response, BaseView baseView) {
((TodoFragment)baseView).updateRemovedData(response);
}
});
}
public static void updateTodoById(BaseView baseView, int todoId, String title, String content, String date) {
}
public static void doneTodoById(BaseView baseView, int todoId, int status) {
Map<String, String> params = new HashMap<>();
params.put("status", String.valueOf(status));
XRetrofit.post(buildUrl(String.format(Constant.DONE_TODO_URI, todoId)),params, new MyGsonCallback<ResponseItem<TodoDesBean>>(baseView,1) {
@Override
protected void onSuccess(ResponseItem<TodoDesBean> response, BaseView baseView) {
((TodoFragment)baseView).updateDoneData(response);
}
});
}
public static void requestUpdateTodoData(BaseView baseView, int todoId, String todoName, String todoDes, String todoDate) {
Map<String, String> params = new HashMap<>();
params.put("title", todoName);
if (!TextUtils.isEmpty(todoDes)) {
params.put("content", todoDes);
}
params.put("date", todoDate);
XRetrofit.post(buildUrl(String.format(Constant.UPDATE_TODO_URI, todoId)), params, new MyGsonCallback<ResponseItem<TodoDesBean>>(baseView) {
@Override
protected void onSuccess(ResponseItem<TodoDesBean> response, BaseView baseView) {
((EditTodoActivity)baseView).updateUI(response);
}
});
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/http/MyGsonCallback.java
================================================
package com.wj.android.todo.http;
import com.google.gson.Gson;
import com.wj.android.http.BaseView;
import com.wj.android.http.GsonCallback;
import com.wj.android.todo.exception.ApiException;
/**
* 作者:wangwnejie on 2018/8/8 13:57
* 邮箱:wang20080990@163.com
*/
public abstract class MyGsonCallback<T> extends GsonCallback<T> {
public MyGsonCallback(BaseView baseView) {
super(baseView);
}
public MyGsonCallback(BaseView baseView, int requestId) {
super(baseView, requestId);
}
@Override
protected String convertResponse(String response) {
ResponseItem responseItem = new Gson().fromJson(response, ResponseItem.class);
if (!responseItem.isSuccess()) {
throw new ApiException(responseItem.getErrorMsg(), responseItem.getErrorCode());
}
return super.convertResponse(response);
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/http/ResponseItem.java
================================================
package com.wj.android.todo.http;
import java.io.Serializable;
/**
* 响应类型
* @author wangwenjie
*泛型T是实际的响应类型
*响应类型T为Object
*/
public class ResponseItem<T> implements Serializable {
/**
* 错误的内部编号,success表示成功
*/
private int errorCode;
/**
* 错误描述
*/
private String errorMsg;
/**
* 返回值
*/
private T data;
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public boolean isSuccess() {
return errorCode == 0;
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/manager/PersistentCookieJarManager.java
================================================
package com.wj.android.todo.manager;
import android.content.Context;
import com.franmontiel.persistentcookiejar.PersistentCookieJar;
import com.franmontiel.persistentcookiejar.cache.SetCookieCache;
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor;
import com.wj.android.http.RetrofitHttpManager;
/**
* 作者:wangwnejie on 2018/8/8 16:34
* 邮箱:wang20080990@163.com
*/
public class PersistentCookieJarManager {
private volatile static PersistentCookieJarManager sInstance;
private PersistentCookieJar mPersistentCookieJar;
private PersistentCookieJarManager(Context context) {
mPersistentCookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context));
}
public static PersistentCookieJarManager getInstance(Context context) {
if (sInstance == null) {
synchronized (RetrofitHttpManager.class) {
if (sInstance == null) {
sInstance = new PersistentCookieJarManager(context.getApplicationContext());
}
}
}
return sInstance;
}
public PersistentCookieJar getPersistentCookieJar() {
return mPersistentCookieJar;
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/manager/PreferenceHelper.java
================================================
/*******************************************************************************
* Copyright (c) Baina Info Tech Co. Ltd
* <p/>
* DolphinCoreLibrary_Webzine
* <p/>
* PreferenceHelper
* TODO File description or class description.
*
* @author: dhu
* @since: 2011-8-6
* @version: 1.0
******************************************************************************/
package com.wj.android.todo.manager;
import android.annotation.SuppressLint;
import android.content.SharedPreferences.Editor;
import android.os.Build;
/**
* PreferenceHelper of DolphinCoreLibrary_Webzine.
*
* @author dhu
*/
public abstract class PreferenceHelper {
public abstract void save(Editor editor);
private static PreferenceHelper sHelper;
public static PreferenceHelper getInstance() {
if (null == sHelper) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
sHelper = new NewPreferenceHelper();
} else {
sHelper = new OlderPreferenceHelper();
}
}
return sHelper;
}
private static class OlderPreferenceHelper extends PreferenceHelper {
@Override
public void save(Editor editor) {
editor.commit();
}
}
private static class NewPreferenceHelper extends PreferenceHelper {
@SuppressLint("NewApi")
@Override
public void save(Editor editor) {
editor.apply();
}
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/manager/SharePreferenceManager.java
================================================
package com.wj.android.todo.manager;
import android.content.Context;
import android.content.SharedPreferences;
/**
* Created by Administrator on 2017/5/25.
*/
public class SharePreferenceManager {
private static final String PREF_FILE = "wj_todo";
private static final String KEY_USER_NAME = "user_name";
private volatile static SharePreferenceManager sInstance;
private SharedPreferences mSharedPreferences;
private SharePreferenceManager(Context context) {
mSharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE);
}
public static SharePreferenceManager getInstance(Context context) {
if (sInstance == null) {
synchronized (SharePreferenceManager.class) {
if (sInstance == null) {
sInstance = new SharePreferenceManager(context.getApplicationContext());
}
}
}
return sInstance;
}
public void setUserName(String userName) {
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(KEY_USER_NAME, userName);
PreferenceHelper.getInstance().save(editor);
}
public String getUserName() {
return mSharedPreferences.getString(KEY_USER_NAME, null);
}
public void clear() {
mSharedPreferences.edit().clear();
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/util/TimeUtils.java
================================================
package com.wj.android.todo.util;
import android.support.annotation.NonNull;
import com.wj.android.todo.constant.TimeConstants;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
/**
* <pre>
* author: Blankj
* blog : http://blankj.com
* time : 2016/08/02
* desc : utils about time
* </pre>
*/
public final class TimeUtils {
private static final ThreadLocal<SimpleDateFormat> SDF_THREAD_LOCAL = new ThreadLocal<>();
private static SimpleDateFormat getDefaultFormat() {
SimpleDateFormat simpleDateFormat = SDF_THREAD_LOCAL.get();
if (simpleDateFormat == null) {
simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
SDF_THREAD_LOCAL.set(simpleDateFormat);
}
return simpleDateFormat;
}
private static SimpleDateFormat getDateFormat(String pattern) {
return new SimpleDateFormat(pattern, Locale.getDefault());
}
private TimeUtils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
/**
* Milliseconds to the formatted time string.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param millis The milliseconds.
* @return the formatted time string
*/
public static String millis2String(final long millis) {
return millis2String(millis, getDefaultFormat());
}
/**
* Milliseconds to the formatted time string.
*
* @param millis The milliseconds.
* @param format The format.
* @return the formatted time string
*/
public static String millis2String(final long millis, @NonNull final DateFormat format) {
return format.format(new Date(millis));
}
/**
* Formatted time string to the milliseconds.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @return the milliseconds
*/
public static long string2Millis(final String time) {
return string2Millis(time, getDefaultFormat());
}
/**
* Formatted time string to the milliseconds.
*
* @param time The formatted time string.
* @param format The format.
* @return the milliseconds
*/
public static long string2Millis(final String time, @NonNull final DateFormat format) {
try {
return format.parse(time).getTime();
} catch (ParseException e) {
e.printStackTrace();
}
return -1;
}
/**
* Formatted time string to the date.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @return the date
*/
public static Date string2Date(final String time) {
return string2Date(time, getDefaultFormat());
}
/**
* Formatted time string to the date.
*
* @param time The formatted time string.
* @param format The format.
* @return the date
*/
public static Date string2Date(final String time, @NonNull final DateFormat format) {
try {
return format.parse(time);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
/**
* Date to the formatted time string.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param date The date.
* @return the formatted time string
*/
public static String date2String(final Date date) {
return date2String(date, getDefaultFormat());
}
/**
* Date to the formatted time string.
*
* @param date The date.
* @param format The format.
* @return the formatted time string
*/
public static String date2String(final Date date, @NonNull final DateFormat format) {
return format.format(date);
}
public static String date2String(final Date date, @NonNull final String pattern) {
return date2String(date, getDateFormat(pattern));
}
/**
* Date to the milliseconds.
*
* @param date The date.
* @return the milliseconds
*/
public static long date2Millis(final Date date) {
return date.getTime();
}
/**
* Milliseconds to the date.
*
* @param millis The milliseconds.
* @return the date
*/
public static Date millis2Date(final long millis) {
return new Date(millis);
}
/**
* Return the time span, in unit.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time1 The first formatted time string.
* @param time2 The second formatted time string.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the time span, in unit
*/
public static long getTimeSpan(final String time1,
final String time2,
@TimeConstants.Unit final int unit) {
return getTimeSpan(time1, time2, getDefaultFormat(), unit);
}
/**
* Return the time span, in unit.
*
* @param time1 The first formatted time string.
* @param time2 The second formatted time string.
* @param format The format.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the time span, in unit
*/
public static long getTimeSpan(final String time1,
final String time2,
@NonNull final DateFormat format,
@TimeConstants.Unit final int unit) {
return millis2TimeSpan(string2Millis(time1, format) - string2Millis(time2, format), unit);
}
/**
* Return the time span, in unit.
*
* @param date1 The first date.
* @param date2 The second date.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the time span, in unit
*/
public static long getTimeSpan(final Date date1,
final Date date2,
@TimeConstants.Unit final int unit) {
return millis2TimeSpan(date2Millis(date1) - date2Millis(date2), unit);
}
/**
* Return the time span, in unit.
*
* @param millis1 The first milliseconds.
* @param millis2 The second milliseconds.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the time span, in unit
*/
public static long getTimeSpan(final long millis1,
final long millis2,
@TimeConstants.Unit final int unit) {
return millis2TimeSpan(millis1 - millis2, unit);
}
/**
* Return the fit time span.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time1 The first formatted time string.
* @param time2 The second formatted time string.
* @param precision The precision of time span.
* <ul>
* <li>precision = 0, return null</li>
* <li>precision = 1, return 天</li>
* <li>precision = 2, return 天, 小时</li>
* <li>precision = 3, return 天, 小时, 分钟</li>
* <li>precision = 4, return 天, 小时, 分钟, 秒</li>
* <li>precision >= 5,return 天, 小时, 分钟, 秒, 毫秒</li>
* </ul>
* @return the fit time span
*/
public static String getFitTimeSpan(final String time1,
final String time2,
final int precision) {
long delta = string2Millis(time1, getDefaultFormat()) - string2Millis(time2, getDefaultFormat());
return millis2FitTimeSpan(delta, precision);
}
/**
* Return the fit time span.
*
* @param time1 The first formatted time string.
* @param time2 The second formatted time string.
* @param format The format.
* @param precision The precision of time span.
* <ul>
* <li>precision = 0, return null</li>
* <li>precision = 1, return 天</li>
* <li>precision = 2, return 天, 小时</li>
* <li>precision = 3, return 天, 小时, 分钟</li>
* <li>precision = 4, return 天, 小时, 分钟, 秒</li>
* <li>precision >= 5,return 天, 小时, 分钟, 秒, 毫秒</li>
* </ul>
* @return the fit time span
*/
public static String getFitTimeSpan(final String time1,
final String time2,
@NonNull final DateFormat format,
final int precision) {
long delta = string2Millis(time1, format) - string2Millis(time2, format);
return millis2FitTimeSpan(delta, precision);
}
/**
* Return the fit time span.
*
* @param date1 The first date.
* @param date2 The second date.
* @param precision The precision of time span.
* <ul>
* <li>precision = 0, return null</li>
* <li>precision = 1, return 天</li>
* <li>precision = 2, return 天, 小时</li>
* <li>precision = 3, return 天, 小时, 分钟</li>
* <li>precision = 4, return 天, 小时, 分钟, 秒</li>
* <li>precision >= 5,return 天, 小时, 分钟, 秒, 毫秒</li>
* </ul>
* @return the fit time span
*/
public static String getFitTimeSpan(final Date date1, final Date date2, final int precision) {
return millis2FitTimeSpan(date2Millis(date1) - date2Millis(date2), precision);
}
/**
* Return the fit time span.
*
* @param millis1 The first milliseconds.
* @param millis2 The second milliseconds.
* @param precision The precision of time span.
* <ul>
* <li>precision = 0, return null</li>
* <li>precision = 1, return 天</li>
* <li>precision = 2, return 天, 小时</li>
* <li>precision = 3, return 天, 小时, 分钟</li>
* <li>precision = 4, return 天, 小时, 分钟, 秒</li>
* <li>precision >= 5,return 天, 小时, 分钟, 秒, 毫秒</li>
* </ul>
* @return the fit time span
*/
public static String getFitTimeSpan(final long millis1,
final long millis2,
final int precision) {
return millis2FitTimeSpan(millis1 - millis2, precision);
}
/**
* Return the current time in milliseconds.
*
* @return the current time in milliseconds
*/
public static long getNowMills() {
return System.currentTimeMillis();
}
/**
* Return the current formatted time string.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @return the current formatted time string
*/
public static String getNowString() {
return millis2String(System.currentTimeMillis(), getDefaultFormat());
}
/**
* Return the current formatted time string.
*
* @param format The format.
* @return the current formatted time string
*/
public static String getNowString(@NonNull final DateFormat format) {
return millis2String(System.currentTimeMillis(), format);
}
/**
* Return the current date.
*
* @return the current date
*/
public static Date getNowDate() {
return new Date();
}
/**
* Return the time span by now, in unit.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the time span by now, in unit
*/
public static long getTimeSpanByNow(final String time, @TimeConstants.Unit final int unit) {
return getTimeSpan(time, getNowString(), getDefaultFormat(), unit);
}
/**
* Return the time span by now, in unit.
*
* @param time The formatted time string.
* @param format The format.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the time span by now, in unit
*/
public static long getTimeSpanByNow(final String time,
@NonNull final DateFormat format,
@TimeConstants.Unit final int unit) {
return getTimeSpan(time, getNowString(format), format, unit);
}
/**
* Return the time span by now, in unit.
*
* @param date The date.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the time span by now, in unit
*/
public static long getTimeSpanByNow(final Date date, @TimeConstants.Unit final int unit) {
return getTimeSpan(date, new Date(), unit);
}
/**
* Return the time span by now, in unit.
*
* @param millis The milliseconds.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the time span by now, in unit
*/
public static long getTimeSpanByNow(final long millis, @TimeConstants.Unit final int unit) {
return getTimeSpan(millis, System.currentTimeMillis(), unit);
}
/**
* Return the fit time span by now.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @param precision The precision of time span.
* <ul>
* <li>precision = 0,返回 null</li>
* <li>precision = 1,返回天</li>
* <li>precision = 2,返回天和小时</li>
* <li>precision = 3,返回天、小时和分钟</li>
* <li>precision = 4,返回天、小时、分钟和秒</li>
* <li>precision >= 5,返回天、小时、分钟、秒和毫秒</li>
* </ul>
* @return the fit time span by now
*/
public static String getFitTimeSpanByNow(final String time, final int precision) {
return getFitTimeSpan(time, getNowString(), getDefaultFormat(), precision);
}
/**
* Return the fit time span by now.
*
* @param time The formatted time string.
* @param format The format.
* @param precision The precision of time span.
* <ul>
* <li>precision = 0,返回 null</li>
* <li>precision = 1,返回天</li>
* <li>precision = 2,返回天和小时</li>
* <li>precision = 3,返回天、小时和分钟</li>
* <li>precision = 4,返回天、小时、分钟和秒</li>
* <li>precision >= 5,返回天、小时、分钟、秒和毫秒</li>
* </ul>
* @return the fit time span by now
*/
public static String getFitTimeSpanByNow(final String time,
@NonNull final DateFormat format,
final int precision) {
return getFitTimeSpan(time, getNowString(format), format, precision);
}
/**
* Return the fit time span by now.
*
* @param date The date.
* @param precision The precision of time span.
* <ul>
* <li>precision = 0,返回 null</li>
* <li>precision = 1,返回天</li>
* <li>precision = 2,返回天和小时</li>
* <li>precision = 3,返回天、小时和分钟</li>
* <li>precision = 4,返回天、小时、分钟和秒</li>
* <li>precision >= 5,返回天、小时、分钟、秒和毫秒</li>
* </ul>
* @return the fit time span by now
*/
public static String getFitTimeSpanByNow(final Date date, final int precision) {
return getFitTimeSpan(date, getNowDate(), precision);
}
/**
* Return the fit time span by now.
*
* @param millis The milliseconds.
* @param precision The precision of time span.
* <ul>
* <li>precision = 0,返回 null</li>
* <li>precision = 1,返回天</li>
* <li>precision = 2,返回天和小时</li>
* <li>precision = 3,返回天、小时和分钟</li>
* <li>precision = 4,返回天、小时、分钟和秒</li>
* <li>precision >= 5,返回天、小时、分钟、秒和毫秒</li>
* </ul>
* @return the fit time span by now
*/
public static String getFitTimeSpanByNow(final long millis, final int precision) {
return getFitTimeSpan(millis, System.currentTimeMillis(), precision);
}
/**
* Return the friendly time span by now.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @return the friendly time span by now
* <ul>
* <li>如果小于 1 秒钟内,显示刚刚</li>
* <li>如果在 1 分钟内,显示 XXX秒前</li>
* <li>如果在 1 小时内,显示 XXX分钟前</li>
* <li>如果在 1 小时外的今天内,显示今天15:32</li>
* <li>如果是昨天的,显示昨天15:32</li>
* <li>其余显示,2016-10-15</li>
* <li>时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007</li>
* </ul>
*/
public static String getFriendlyTimeSpanByNow(final String time) {
return getFriendlyTimeSpanByNow(time, getDefaultFormat());
}
/**
* Return the friendly time span by now.
*
* @param time The formatted time string.
* @param format The format.
* @return the friendly time span by now
* <ul>
* <li>如果小于 1 秒钟内,显示刚刚</li>
* <li>如果在 1 分钟内,显示 XXX秒前</li>
* <li>如果在 1 小时内,显示 XXX分钟前</li>
* <li>如果在 1 小时外的今天内,显示今天15:32</li>
* <li>如果是昨天的,显示昨天15:32</li>
* <li>其余显示,2016-10-15</li>
* <li>时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007</li>
* </ul>
*/
public static String getFriendlyTimeSpanByNow(final String time,
@NonNull final DateFormat format) {
return getFriendlyTimeSpanByNow(string2Millis(time, format));
}
/**
* Return the friendly time span by now.
*
* @param date The date.
* @return the friendly time span by now
* <ul>
* <li>如果小于 1 秒钟内,显示刚刚</li>
* <li>如果在 1 分钟内,显示 XXX秒前</li>
* <li>如果在 1 小时内,显示 XXX分钟前</li>
* <li>如果在 1 小时外的今天内,显示今天15:32</li>
* <li>如果是昨天的,显示昨天15:32</li>
* <li>其余显示,2016-10-15</li>
* <li>时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007</li>
* </ul>
*/
public static String getFriendlyTimeSpanByNow(final Date date) {
return getFriendlyTimeSpanByNow(date.getTime());
}
/**
* Return the friendly time span by now.
*
* @param millis The milliseconds.
* @return the friendly time span by now
* <ul>
* <li>如果小于 1 秒钟内,显示刚刚</li>
* <li>如果在 1 分钟内,显示 XXX秒前</li>
* <li>如果在 1 小时内,显示 XXX分钟前</li>
* <li>如果在 1 小时外的今天内,显示今天15:32</li>
* <li>如果是昨天的,显示昨天15:32</li>
* <li>其余显示,2016-10-15</li>
* <li>时间不合法的情况全部日期和时间信息,如星期六 十月 27 14:21:20 CST 2007</li>
* </ul>
*/
public static String getFriendlyTimeSpanByNow(final long millis) {
long now = System.currentTimeMillis();
long span = now - millis;
if (span < 0)
// U can read http://www.apihome.cn/api/java/Formatter.html to understand it.
return String.format("%tc", millis);
if (span < 1000) {
return "刚刚";
} else if (span < TimeConstants.MIN) {
return String.format(Locale.getDefault(), "%d秒前", span / TimeConstants.SEC);
} else if (span < TimeConstants.HOUR) {
return String.format(Locale.getDefault(), "%d分钟前", span / TimeConstants.MIN);
}
// 获取当天 00:00
long wee = getWeeOfToday();
if (millis >= wee) {
return String.format("今天%tR", millis);
} else if (millis >= wee - TimeConstants.DAY) {
return String.format("昨天%tR", millis);
} else {
return String.format("%tF", millis);
}
}
private static long getWeeOfToday() {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTimeInMillis();
}
/**
* Return the milliseconds differ time span.
*
* @param millis The milliseconds.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the milliseconds differ time span
*/
public static long getMillis(final long millis,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return millis + timeSpan2Millis(timeSpan, unit);
}
/**
* Return the milliseconds differ time span.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the milliseconds differ time span
*/
public static long getMillis(final String time,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return getMillis(time, getDefaultFormat(), timeSpan, unit);
}
/**
* Return the milliseconds differ time span.
*
* @param time The formatted time string.
* @param format The format.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the milliseconds differ time span.
*/
public static long getMillis(final String time,
@NonNull final DateFormat format,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return string2Millis(time, format) + timeSpan2Millis(timeSpan, unit);
}
/**
* Return the milliseconds differ time span.
*
* @param date The date.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the milliseconds differ time span.
*/
public static long getMillis(final Date date,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return date2Millis(date) + timeSpan2Millis(timeSpan, unit);
}
/**
* Return the formatted time string differ time span.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param millis The milliseconds.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the formatted time string differ time span
*/
public static String getString(final long millis,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return getString(millis, getDefaultFormat(), timeSpan, unit);
}
/**
* Return the formatted time string differ time span.
*
* @param millis The milliseconds.
* @param format The format.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the formatted time string differ time span
*/
public static String getString(final long millis,
@NonNull final DateFormat format,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return millis2String(millis + timeSpan2Millis(timeSpan, unit), format);
}
/**
* Return the formatted time string differ time span.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the formatted time string differ time span
*/
public static String getString(final String time,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return getString(time, getDefaultFormat(), timeSpan, unit);
}
/**
* Return the formatted time string differ time span.
*
* @param time The formatted time string.
* @param format The format.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the formatted time string differ time span
*/
public static String getString(final String time,
@NonNull final DateFormat format,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return millis2String(string2Millis(time, format) + timeSpan2Millis(timeSpan, unit), format);
}
/**
* Return the formatted time string differ time span.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param date The date.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the formatted time string differ time span
*/
public static String getString(final Date date,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return getString(date, getDefaultFormat(), timeSpan, unit);
}
/**
* Return the formatted time string differ time span.
*
* @param date The date.
* @param format The format.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the formatted time string differ time span
*/
public static String getString(final Date date,
@NonNull final DateFormat format,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return millis2String(date2Millis(date) + timeSpan2Millis(timeSpan, unit), format);
}
/**
* Return the date differ time span.
*
* @param millis The milliseconds.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the date differ time span
*/
public static Date getDate(final long millis,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return millis2Date(millis + timeSpan2Millis(timeSpan, unit));
}
/**
* Return the date differ time span.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the date differ time span
*/
public static Date getDate(final String time,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return getDate(time, getDefaultFormat(), timeSpan, unit);
}
/**
* Return the date differ time span.
*
* @param time The formatted time string.
* @param format The format.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the date differ time span
*/
public static Date getDate(final String time,
@NonNull final DateFormat format,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return millis2Date(string2Millis(time, format) + timeSpan2Millis(timeSpan, unit));
}
/**
* Return the date differ time span.
*
* @param date The date.
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the date differ time span
*/
public static Date getDate(final Date date,
final long timeSpan,
@TimeConstants.Unit final int unit) {
return millis2Date(date2Millis(date) + timeSpan2Millis(timeSpan, unit));
}
/**
* Return the milliseconds differ time span by now.
*
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the milliseconds differ time span by now
*/
public static long getMillisByNow(final long timeSpan, @TimeConstants.Unit final int unit) {
return getMillis(getNowMills(), timeSpan, unit);
}
/**
* Return the formatted time string differ time span by now.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the formatted time string differ time span by now
*/
public static String getStringByNow(final long timeSpan, @TimeConstants.Unit final int unit) {
return getStringByNow(timeSpan, getDefaultFormat(), unit);
}
/**
* Return the formatted time string differ time span by now.
*
* @param timeSpan The time span.
* @param format The format.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the formatted time string differ time span by now
*/
public static String getStringByNow(final long timeSpan,
@NonNull final DateFormat format,
@TimeConstants.Unit final int unit) {
return getString(getNowMills(), format, timeSpan, unit);
}
/**
* Return the date differ time span by now.
*
* @param timeSpan The time span.
* @param unit The unit of time span.
* <ul>
* <li>{@link TimeConstants#MSEC}</li>
* <li>{@link TimeConstants#SEC }</li>
* <li>{@link TimeConstants#MIN }</li>
* <li>{@link TimeConstants#HOUR}</li>
* <li>{@link TimeConstants#DAY }</li>
* </ul>
* @return the date differ time span by now
*/
public static Date getDateByNow(final long timeSpan, @TimeConstants.Unit final int unit) {
return getDate(getNowMills(), timeSpan, unit);
}
/**
* Return whether it is today.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isToday(final String time) {
return isToday(string2Millis(time, getDefaultFormat()));
}
/**
* Return whether it is today.
*
* @param time The formatted time string.
* @param format The format.
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isToday(final String time, @NonNull final DateFormat format) {
return isToday(string2Millis(time, format));
}
/**
* Return whether it is today.
*
* @param date The date.
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isToday(final Date date) {
return isToday(date.getTime());
}
/**
* Return whether it is today.
*
* @param millis The milliseconds.
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isToday(final long millis) {
long wee = getWeeOfToday();
return millis >= wee && millis < wee + TimeConstants.DAY;
}
/**
* Return whether it is leap year.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isLeapYear(final String time) {
return isLeapYear(string2Date(time, getDefaultFormat()));
}
/**
* Return whether it is leap year.
*
* @param time The formatted time string.
* @param format The format.
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isLeapYear(final String time, @NonNull final DateFormat format) {
return isLeapYear(string2Date(time, format));
}
/**
* Return whether it is leap year.
*
* @param date The date.
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isLeapYear(final Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
int year = cal.get(Calendar.YEAR);
return isLeapYear(year);
}
/**
* Return whether it is leap year.
*
* @param millis The milliseconds.
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isLeapYear(final long millis) {
return isLeapYear(millis2Date(millis));
}
/**
* Return whether it is leap year.
*
* @param year The year.
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isLeapYear(final int year) {
return year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
}
/**
* Return the day of week in Chinese.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @return the day of week in Chinese
*/
public static String getChineseWeek(final String time) {
return getChineseWeek(string2Date(time, getDefaultFormat()));
}
/**
* Return the day of week in Chinese.
*
* @param time The formatted time string.
* @param format The format.
* @return the day of week in Chinese
*/
public static String getChineseWeek(final String time, @NonNull final DateFormat format) {
return getChineseWeek(string2Date(time, format));
}
/**
* Return the day of week in Chinese.
*
* @param date The date.
* @return the day of week in Chinese
*/
public static String getChineseWeek(final Date date) {
return new SimpleDateFormat("E", Locale.CHINA).format(date);
}
/**
* Return the day of week in Chinese.
*
* @param millis The milliseconds.
* @return the day of week in Chinese
*/
public static String getChineseWeek(final long millis) {
return getChineseWeek(new Date(millis));
}
/**
* Return the day of week in US.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @return the day of week in US
*/
public static String getUSWeek(final String time) {
return getUSWeek(string2Date(time, getDefaultFormat()));
}
/**
* Return the day of week in US.
*
* @param time The formatted time string.
* @param format The format.
* @return the day of week in US
*/
public static String getUSWeek(final String time, @NonNull final DateFormat format) {
return getUSWeek(string2Date(time, format));
}
/**
* Return the day of week in US.
*
* @param date The date.
* @return the day of week in US
*/
public static String getUSWeek(final Date date) {
return new SimpleDateFormat("EEEE", Locale.US).format(date);
}
/**
* Return the day of week in US.
*
* @param millis The milliseconds.
* @return the day of week in US
*/
public static String getUSWeek(final long millis) {
return getUSWeek(new Date(millis));
}
/**
* Returns the value of the given calendar field.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @param field The given calendar field.
* <ul>
* <li>{@link Calendar#ERA}</li>
* <li>{@link Calendar#YEAR}</li>
* <li>{@link Calendar#MONTH}</li>
* <li>...</li>
* <li>{@link Calendar#DST_OFFSET}</li>
* </ul>
* @return the value of the given calendar field
*/
public static int getValueByCalendarField(final String time, final int field) {
return getValueByCalendarField(string2Date(time, getDefaultFormat()), field);
}
/**
* Returns the value of the given calendar field.
*
* @param time The formatted time string.
* @param format The format.
* @param field The given calendar field.
* <ul>
* <li>{@link Calendar#ERA}</li>
* <li>{@link Calendar#YEAR}</li>
* <li>{@link Calendar#MONTH}</li>
* <li>...</li>
* <li>{@link Calendar#DST_OFFSET}</li>
* </ul>
* @return the value of the given calendar field
*/
public static int getValueByCalendarField(final String time,
@NonNull final DateFormat format,
final int field) {
return getValueByCalendarField(string2Date(time, format), field);
}
/**
* Returns the value of the given calendar field.
*
* @param date The date.
* @param field The given calendar field.
* <ul>
* <li>{@link Calendar#ERA}</li>
* <li>{@link Calendar#YEAR}</li>
* <li>{@link Calendar#MONTH}</li>
* <li>...</li>
* <li>{@link Calendar#DST_OFFSET}</li>
* </ul>
* @return the value of the given calendar field
*/
public static int getValueByCalendarField(final Date date, final int field) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal.get(field);
}
/**
* Returns the value of the given calendar field.
*
* @param millis The milliseconds.
* @param field The given calendar field.
* <ul>
* <li>{@link Calendar#ERA}</li>
* <li>{@link Calendar#YEAR}</li>
* <li>{@link Calendar#MONTH}</li>
* <li>...</li>
* <li>{@link Calendar#DST_OFFSET}</li>
* </ul>
* @return the value of the given calendar field
*/
public static int getValueByCalendarField(final long millis, final int field) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(millis);
return cal.get(field);
}
private static final String[] CHINESE_ZODIAC =
{"猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊"};
/**
* Return the Chinese zodiac.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @return the Chinese zodiac
*/
public static String getChineseZodiac(final String time) {
return getChineseZodiac(string2Date(time, getDefaultFormat()));
}
/**
* Return the Chinese zodiac.
*
* @param time The formatted time string.
* @param format The format.
* @return the Chinese zodiac
*/
public static String getChineseZodiac(final String time, @NonNull final DateFormat format) {
return getChineseZodiac(string2Date(time, format));
}
/**
* Return the Chinese zodiac.
*
* @param date The date.
* @return the Chinese zodiac
*/
public static String getChineseZodiac(final Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return CHINESE_ZODIAC[cal.get(Calendar.YEAR) % 12];
}
/**
* Return the Chinese zodiac.
*
* @param millis The milliseconds.
* @return the Chinese zodiac
*/
public static String getChineseZodiac(final long millis) {
return getChineseZodiac(millis2Date(millis));
}
/**
* Return the Chinese zodiac.
*
* @param year The year.
* @return the Chinese zodiac
*/
public static String getChineseZodiac(final int year) {
return CHINESE_ZODIAC[year % 12];
}
private static final int[] ZODIAC_FLAGS = {20, 19, 21, 21, 21, 22, 23, 23, 23, 24, 23, 22};
private static final String[] ZODIAC = {
"水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座",
"狮子座", "处女座", "天秤座", "天蝎座", "射手座", "魔羯座"
};
/**
* Return the zodiac.
* <p>The pattern is {@code yyyy-MM-dd HH:mm:ss}.</p>
*
* @param time The formatted time string.
* @return the zodiac
*/
public static String getZodiac(final String time) {
return getZodiac(string2Date(time, getDefaultFormat()));
}
/**
* Return the zodiac.
*
* @param time The formatted time string.
* @param format The format.
* @return the zodiac
*/
public static String getZodiac(final String time, @NonNull final DateFormat format) {
return getZodiac(string2Date(time, format));
}
/**
* Return the zodiac.
*
* @param date The date.
* @return the zodiac
*/
public static String getZodiac(final Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
int month = cal.get(Calendar.MONTH) + 1;
int day = cal.get(Calendar.DAY_OF_MONTH);
return getZodiac(month, day);
}
/**
* Return the zodiac.
*
* @param millis The milliseconds.
* @return the zodiac
*/
public static String getZodiac(final long millis) {
return getZodiac(millis2Date(millis));
}
/**
* Return the zodiac.
*
* @param month The month.
* @param day The day.
* @return the zodiac
*/
public static String getZodiac(final int month, final int day) {
return ZODIAC[day >= ZODIAC_FLAGS[month - 1]
? month - 1
: (month + 10) % 12];
}
private static long timeSpan2Millis(final long timeSpan, @TimeConstants.Unit final int unit) {
return timeSpan * unit;
}
private static long millis2TimeSpan(final long millis, @TimeConstants.Unit final int unit) {
return millis / unit;
}
private static String millis2FitTimeSpan(long millis, int precision) {
if (precision <= 0) return null;
precision = Math.min(precision, 5);
String[] units = {"天", "小时", "分钟", "秒", "毫秒"};
if (millis == 0) return 0 + units[precision - 1];
StringBuilder sb = new StringBuilder();
if (millis < 0) {
sb.append("-");
millis = -millis;
}
int[] unitLen = {86400000, 3600000, 60000, 1000, 1};
for (int i = 0; i < precision; i++) {
if (millis >= unitLen[i]) {
long mode = millis / unitLen[i];
millis -= mode * unitLen[i];
sb.append(mode).append(units[i]);
}
}
return sb.toString();
}
}
================================================
FILE: app/src/main/java/com/wj/android/todo/widget/BottomNavigationViewEx.java
================================================
package com.wj.android.todo.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.internal.BottomNavigationItemView;
import android.support.design.internal.BottomNavigationMenuView;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
/**
* Created by yu on 2016/11/10.
*/
public class BottomNavigationViewEx extends BottomNavigationView {
// used for animation
private int mShiftAmount;
private float mScaleUpFactor;
private float mScaleDownFactor;
private boolean animationRecord;
private float mLargeLabelSize;
private float mSmallLabelSize;
private boolean visibilityTextSizeRecord;
private boolean visibilityHeightRecord;
private int mItemHeight;
private boolean textVisibility = true;
// used for animation end
// used for setupWithViewPager
private ViewPager mViewPager;
private MyOnNavigationItemSelectedListener mMyOnNavigationItemSelectedListener;
private BottomNavigationViewExOnPageChangeListener mPageChangeListener;
private BottomNavigationMenuView mMenuView;
private BottomNavigationItemView[] mButtons;
// used for setupWithViewPager end
// detect navigation tab changes when the user clicking on navigation item
private static boolean isNavigationItemClicking = false;
public BottomNavigationViewEx(Context context) {
super(context);
}
public BottomNavigationViewEx(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BottomNavigationViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@SuppressLint("RestrictedApi")
private void refreshTextViewVisibility() {
if (!textVisibility)
return;
// 1. get mMenuView
BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
// 2. get mButtons
BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
int currentItem = getCurrentItem();
// 3. get field mShiftingMode and TextView in mButtons
for (BottomNavigationItemView button : mButtons) {
TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel");
TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel");
mLargeLabel.clearAnimation();
mSmallLabel.clearAnimation();
// mShiftingMode
boolean mShiftingMode = getField(button.getClass(), button, "mShiftingMode");
boolean selected = button.getItemPosition() == currentItem;
if (mShiftingMode) {
if (selected) {
mLargeLabel.setVisibility(VISIBLE);
} else {
mLargeLabel.setVisibility(INVISIBLE);
}
mSmallLabel.setVisibility(INVISIBLE);
} else {
if (selected) {
mLargeLabel.setVisibility(VISIBLE);
mSmallLabel.setVisibility(INVISIBLE);
} else {
mLargeLabel.setVisibility(INVISIBLE);
mSmallLabel.setVisibility(VISIBLE);
}
}
}
}
/**
* change the visibility of icon
*
* @param visibility
*/
@SuppressLint("RestrictedApi")
public void setIconVisibility(boolean visibility) {
/*
1. get field in this class
private final BottomNavigationMenuView mMenuView;
2. get field in mButtons
private BottomNavigationItemView[] mButtons;
3. get mIcon in mButtons
private ImageView mIcon
4. set mIcon visibility gone
5. change mItemHeight to only text size in mMenuView
*/
// 1. get mMenuView
final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
// 2. get mButtons
BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
// 3. get mIcon in mButtons
for (BottomNavigationItemView button : mButtons) {
ImageView mIcon = getField(button.getClass(), button, "mIcon");
// 4. set mIcon visibility gone
mIcon.setVisibility(visibility ? View.VISIBLE : View.INVISIBLE);
}
// 5. change mItemHeight to only text size in mMenuView
if (!visibility) {
// if not record mItemHeight
if (!visibilityHeightRecord) {
visibilityHeightRecord = true;
mItemHeight = getItemHeight();
}
// change mItemHeight
BottomNavigationItemView button = mButtons[0];
if (null != button) {
final ImageView mIcon = getField(button.getClass(), button, "mIcon");
// System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight());
if (null != mIcon) {
mIcon.post(new Runnable() {
@Override
public void run() {
// System.out.println("mIcon.getMeasuredHeight():" + mIcon.getMeasuredHeight());
setItemHeight(mItemHeight - mIcon.getMeasuredHeight());
}
});
}
}
} else {
// if not record the mItemHeight, we need do nothing.
if (!visibilityHeightRecord)
return;
// restore it
setItemHeight(mItemHeight);
}
mMenuView.updateMenuView();
}
/**
* change the visibility of text
*
* @param visibility
*/
@SuppressLint("RestrictedApi")
public void setTextVisibility(boolean visibility) {
this.textVisibility = visibility;
/*
1. get field in this class
private final BottomNavigationMenuView mMenuView;
2. get field in mButtons
private BottomNavigationItemView[] mButtons;
3. set text size in mButtons
private final TextView mLargeLabel
private final TextView mSmallLabel
4. change mItemHeight to only icon size in mMenuView
*/
// 1. get mMenuView
BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
// 2. get mButtons
BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
// 3. change field mShiftingMode value in mButtons
for (BottomNavigationItemView button : mButtons) {
TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel");
TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel");
if (!visibility) {
// if not record the font size, record it
if (!visibilityTextSizeRecord && !animationRecord) {
visibilityTextSizeRecord = true;
mLargeLabelSize = mLargeLabel.getTextSize();
mSmallLabelSize = mSmallLabel.getTextSize();
}
// if not visitable, set font size to 0
mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0);
mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, 0);
} else {
// if not record the font size, we need do nothing.
if (!visibilityTextSizeRecord)
break;
// restore it
mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize);
mSmallLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize);
}
}
// 4 change mItemHeight to only icon size in mMenuView
if (!visibility) {
// if not record mItemHeight
if (!visibilityHeightRecord) {
visibilityHeightRecord = true;
mItemHeight = getItemHeight();
}
// change mItemHeight to only icon size in mMenuView
// private final int mItemHeight;
// change mItemHeight
// System.out.println("mLargeLabel.getMeasuredHeight():" + getFontHeight(mSmallLabelSize));
setItemHeight(mItemHeight - getFontHeight(mSmallLabelSize));
} else {
// if not record the mItemHeight, we need do nothing.
if (!visibilityHeightRecord)
return;
// restore mItemHeight
setItemHeight(mItemHeight);
}
mMenuView.updateMenuView();
}
/**
* get text height by font size
*
* @param fontSize
* @return
*/
private static int getFontHeight(float fontSize) {
Paint paint = new Paint();
paint.setTextSize(fontSize);
Paint.FontMetrics fm = paint.getFontMetrics();
return (int) Math.ceil(fm.descent - fm.top) + 2;
}
/**
* enable or disable click item animation(text scale and icon move animation in no item shifting mode)
*
* @param enable It means the text won't scale and icon won't move when active it in no item shifting mode if false.
*/
@SuppressLint("RestrictedApi")
public void enableAnimation(boolean enable) {
/*
1. get field in this class
private final BottomNavigationMenuView mMenuView;
2. get field in mButtons
private BottomNavigationItemView[] mButtons;
3. chang mShiftAmount to 0 in mButtons
private final int mShiftAmount
change mScaleUpFactor and mScaleDownFactor to 1f in mButtons
private final float mScaleUpFactor
private final float mScaleDownFactor
4. change label font size in mButtons
private final TextView mLargeLabel
private final TextView mSmallLabel
*/
// 1. get mMenuView
BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
// 2. get mButtons
BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
// 3. change field mShiftingMode value in mButtons
for (BottomNavigationItemView button : mButtons) {
TextView mLargeLabel = getField(button.getClass(), button, "mLargeLabel");
TextView mSmallLabel = getField(button.getClass(), button, "mSmallLabel");
// if disable animation, need animationRecord the source value
if (!enable) {
if (!animationRecord) {
animationRecord = true;
mShiftAmount = getField(button.getClass(), button, "mShiftAmount");
mScaleUpFactor = getField(button.getClass(), button, "mScaleUpFactor");
mScaleDownFactor = getField(button.getClass(), button, "mScaleDownFactor");
mLargeLabelSize = mLargeLabel.getTextSize();
mSmallLabelSize = mSmallLabel.getTextSize();
// System.out.println("mShiftAmount:" + mShiftAmount + " mScaleUpFactor:"
// + mScaleUpFactor + " mScaleDownFactor:" + mScaleDownFactor
// + " mLargeLabel:" + mLargeLabelSize + " mSmallLabel:" + mSmallLabelSize);
}
// disable
setField(button.getClass(), button, "mShiftAmount", 0);
setField(button.getClass(), button, "mScaleUpFactor", 1);
setField(button.getClass(), button, "mScaleDownFactor", 1);
// let the mLargeLabel font size equal to mSmallLabel
mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mSmallLabelSize);
// debug start
// mLargeLabelSize = mLargeLabel.getTextSize();
// System.out.println("mLargeLabel:" + mLargeLabelSize);
// debug end
} else {
// haven't change the value. It means it was the first call this method. So nothing need to do.
if (!animationRecord)
return;
// enable animation
setField(button.getClass(), button, "mShiftAmount", mShiftAmount);
setField(button.getClass(), button, "mScaleUpFactor", mScaleUpFactor);
setField(button.getClass(), button, "mScaleDownFactor", mScaleDownFactor);
// restore
mLargeLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, mLargeLabelSize);
}
}
mMenuView.updateMenuView();
}
/**
* enable the shifting mode for navigation
*
* @param enable It will has a shift animation if true. Otherwise all items are the same width.
*/
@SuppressLint("RestrictedApi")
public void enableShiftingMode(boolean enable) {
/*
1. get field in this class
private final BottomNavigationMenuView mMenuView;
2. change field mShiftingMode value in mMenuView
private boolean mShiftingMode = true;
*/
// 1. get mMenuView
BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
// 2. change field mShiftingMode value in mMenuView
setField(mMenuView.getClass(), mMenuView, "mShiftingMode", enable);
mMenuView.updateMenuView();
}
/**
* enable the shifting mode for each item
*
* @param enable It will has a shift animation for item if true. Otherwise the item text always be shown.
*/
@SuppressLint("RestrictedApi")
public void enableItemShiftingMode(boolean enable) {
/*
1. get field in this class
private final BottomNavigationMenuView mMenuView;
2. get field in this mMenuView
private BottomNavigationItemView[] mButtons;
3. change field mShiftingMode value in mButtons
private boolean mShiftingMode = true;
*/
// 1. get mMenuView
BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
// 2. get mButtons
BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
// 3. change field mShiftingMode value in mButtons
for (BottomNavigationItemView button : mButtons) {
setField(button.getClass(), button, "mShiftingMode", enable);
}
mMenuView.updateMenuView();
}
/**
* get the current checked item position
*
* @return index of item, start from 0.
*/
public int getCurrentItem() {
/*
1. get field in this class
private final BottomNavigationMenuView mMenuView;
2. get field in mMenuView
private BottomNavigationItemView[] mButtons;
3. get menu and traverse it to get the checked one
*/
// 1. get mMenuView
// BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
// 2. get mButtons
BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
// 3. get menu and traverse it to get the checked one
Menu menu = getMenu();
for (int i = 0; i < mButtons.length; i++) {
if (menu.getItem(i).isChecked()) {
return i;
}
}
return 0;
}
/**
* get menu item position in menu
*
* @param item
* @return position if success, -1 otherwise
*/
public int getMenuItemPosition(MenuItem item) {
// get item id
int itemId = item.getItemId();
// get meunu
Menu menu = getMenu();
int size = menu.size();
for (int i = 0; i < size; i++) {
if (menu.getItem(i).getItemId() == itemId) {
return i;
}
}
return -1;
}
/**
* set the current checked item
*
* @param item start from 0.
*/
public void setCurrentItem(int item) {
// check bounds
if (item < 0 || item >= getMaxItemCount()) {
throw new ArrayIndexOutOfBoundsException("item is out of bounds, we expected 0 - "
+ (getMaxItemCount() - 1) + ". Actually " + item);
}
/*
1. get field in this class
private final BottomNavigationMenuView mMenuView;
2. get field in mMenuView
private BottomNavigationItemView[] mButtons;
private final OnClickListener mOnClickListener;
3. call mOnClickListener.onClick();
*/
// 1. get mMenuView
BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
// 2. get mButtons
BottomNavigationItemView[] mButtons = getBottomNavigationItemViews();
// get mOnClickListener
OnClickListener mOnClickListener = getField(mMenuView.getClass(), mMenuView, "onClickListener");
// System.out.println("mMenuView:" + mMenuView + " mButtons:" + mButtons + " mOnClickListener" + mOnClickListener);
// 3. call mOnClickListener.onClick();
mOnClickListener.onClick(mButtons[item]);
}
/**
* get OnNavigationItemSelectedListener
*
* @return
*/
public OnNavigationItemSelectedListener getOnNavigationItemSelectedListener() {
// private OnNavigationItemSelectedListener mListener;
OnNavigationItemSelectedListener mListener = getField(BottomNavigationView.class, this, "selectedListener");
return mListener;
}
@Override
public void setOnNavigationItemSelectedListener(@Nullable OnNavigationItemSelectedListener listener) {
// if not set up with view pager, the same with father
if (null == mMyOnNavigationItemSelectedListener) {
super.setOnNavigationItemSelectedListener(listener);
return;
}
mMyOnNavigationItemSelectedListener.setOnNavigationItemSelectedListener(listener);
}
/**
* get private mMenuView
*
* @return
*/
private BottomNavigationMenuView getBottomNavigationMenuView() {
if (null == mMenuView)
mMenuView = getField(BottomNavigationView.class, this, "menuView");
return mMenuView;
}
/**
* get private mButtons in mMenuView
*
* @return
*/
public BottomNavigationItemView[] getBottomNavigationItemViews() {
if (null != mButtons)
return mButtons;
/*
* 1 private final BottomNavigationMenuView mMenuView;
* 2 private BottomNavigationItemView[] mButtons;
*/
BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
mButtons = getField(mMenuView.getClass(), mMenuView, "buttons");
return mButtons;
}
/**
* get private mButton in mMenuView at position
*
* @param position
* @return
*/
public BottomNavigationItemView getBottomNavigationItemView(int position) {
return getBottomNavigationItemViews()[position];
}
/**
* get icon at position
*
* @param position
* @return
*/
public ImageView getIconAt(int position) {
/*
* 1 private final BottomNavigationMenuView mMenuView;
* 2 private BottomNavigationItemView[] mButtons;
* 3 private ImageView mIcon;
*/
BottomNavigationItemView mButtons = getBottomNavigationItemView(position);
ImageView mIcon = getField(BottomNavigationItemView.class, mButtons, "icon");
return mIcon;
}
/**
* get small label at position
* Each item has tow label, one is large, another is small.
*
* @param position
* @return
*/
public TextView getSmallLabelAt(int position) {
/*
* 1 private final BottomNavigationMenuView mMenuView;
* 2 private BottomNavigationItemView[] mButtons;
* 3 private final TextView mSmallLabel;
*/
BottomNavigationItemView mButtons = getBottomNavigationItemView(position);
TextView mSmallLabel = getField(BottomNavigationItemView.class, mButtons, "smallLabel");
return mSmallLabel;
}
/**
* get large label at position
* Each item has tow label, one is large, another is small.
*
* @param position
* @return
*/
public TextView getLargeLabelAt(int position) {
/*
* 1 private final BottomNavigationMenuView mMenuView;
* 2 private BottomNavigationItemView[] mButtons;
* 3 private final TextView mLargeLabel;
*/
BottomNavigationItemView mButtons = getBottomNavigationItemView(position);
TextView mLargeLabel = getField(BottomNavigationItemView.class, mButtons, "largeLabel");
return mLargeLabel;
}
/**
* return item count
*
* @return
*/
public int getItemCount() {
BottomNavigationItemView[] bottomNavigationItemViews = getBottomNavigationItemViews();
if (null == bottomNavigationItemViews)
return 0;
return bottomNavigationItemViews.length;
}
/**
* set all item small TextView size
* Each item has tow label, one is large, another is small.
* Small one will be shown when item state is normal
* Large one will be shown when item checked.
*
* @param sp
*/
@SuppressLint("RestrictedApi")
public void setSmallTextSize(float sp) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
getSmallLabelAt(i).setTextSize(sp);
}
mMenuView.updateMenuView();
}
/**
* set all item large TextView size
* Each item has tow label, one is large, another is small.
* Small one will be shown when item state is normal.
* Large one will be shown when item checked.
*
* @param sp
*/
@SuppressLint("RestrictedApi")
public void setLargeTextSize(float sp) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
getLargeLabelAt(i).setTextSize(sp);
}
mMenuView.updateMenuView();
}
/**
* set all item large and small TextView size
* Each item has tow label, one is large, another is small.
* Small one will be shown when item state is normal
* Large one will be shown when item checked.
*
* @param sp
*/
public void setTextSize(float sp) {
setLargeTextSize(sp);
setSmallTextSize(sp);
}
/**
* set item ImageView size which at position
*
* @param position position start from 0
* @param width in dp
* @param height in dp
*/
@SuppressLint("RestrictedApi")
public void setIconSizeAt(int position, float width, float height) {
ImageView icon = getIconAt(position);
// update size
ViewGroup.LayoutParams layoutParams = icon.getLayoutParams();
layoutParams.width = dp2px(getContext(), width);
layoutParams.height = dp2px(getContext(), height);
icon.setLayoutParams(layoutParams);
mMenuView.updateMenuView();
}
/**
* set all item ImageView size
*
* @param width in dp
* @param height in dp
*/
public void setIconSize(float width, float height) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
setIconSizeAt(i, width, height);
}
}
/**
* set menu item height
*
* @param height in px
*/
@SuppressLint("RestrictedApi")
public void setItemHeight(int height) {
// 1. get mMenuView
final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
// 2. set private final int mItemHeight in mMenuView
setField(mMenuView.getClass(), mMenuView, "itemHeight", height);
mMenuView.updateMenuView();
}
/**
* get menu item height
*
* @return in px
*/
public int getItemHeight() {
// 1. get mMenuView
final BottomNavigationMenuView mMenuView = getBottomNavigationMenuView();
// 2. get private final int mItemHeight in mMenuView
return getField(mMenuView.getClass(), mMenuView, "itemHeight");
}
/**
* dp to px
*
* @param context
* @param dpValue dp
* @return px
*/
public static int dp2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* set Typeface for all item TextView
*
* @attr ref android.R.styleable#TextView_typeface
* @attr ref android.R.styleable#TextView_textStyle
*/
@SuppressLint("RestrictedApi")
public void setTypeface(Typeface typeface, int style) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
getLargeLabelAt(i).setTypeface(typeface, style);
getSmallLabelAt(i).setTypeface(typeface, style);
}
mMenuView.updateMenuView();
}
/**
* set Typeface for all item TextView
*
* @attr ref android.R.styleable#TextView_typeface
*/
@SuppressLint("RestrictedApi")
public void setTypeface(Typeface typeface) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
getLargeLabelAt(i).setTypeface(typeface);
getSmallLabelAt(i).setTypeface(typeface);
}
mMenuView.updateMenuView();
}
/**
* get private filed in this specific object
*
* @param targetClass
* @param instance the filed owner
* @param fieldName
* @param <T>
* @return field if success, null otherwise.
*/
private <T> T getField(Class targetClass, Object instance, String fieldName) {
try {
Field field = targetClass.getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(instance);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* change the field value
*
* @param targetClass
* @param instance the filed owner
* @param fieldName
* @param value
*/
private void setField(Class targetClass, Object instance, String fieldName, Object value) {
try {
Field field = targetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(instance, value);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* This method will link the given ViewPager and this BottomNavigationViewEx together so that
* changes in one are automatically reflected in the other. This includes scroll state changes
* and clicks.
*
* @param viewPager
*/
public void setupWithViewPager(@Nullable final ViewPager viewPager) {
setupWithViewPager(viewPager, false);
}
/**
* This method will link the given ViewPager and this BottomNavigationViewEx together so that
* changes in one are automatically reflected in the other. This includes scroll state changes
* and clicks.
*
* @param viewPager
* @param smoothScroll whether ViewPager changed with smooth scroll animation
*/
public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean smoothScroll) {
if (mViewPager != null) {
// If we've already been setup with a ViewPager, remove us from it
if (mPageChangeListener != null) {
mViewPager.removeOnPageChangeListener(mPageChangeListener);
}
}
if (null == viewPager) {
mViewPager = null;
super.setOnNavigationItemSelectedListener(null);
return;
}
mViewPager = viewPager;
// Add our custom OnPageChangeListener to the ViewPager
if (mPageChangeListener == null) {
mPageChangeListener = new BottomNavigationViewExOnPageChangeListener(this);
}
viewPager.addOnPageChangeListener(mPageChangeListener);
// Now we'll add a navigation item selected listener to set ViewPager's current item
OnNavigationItemSelectedListener listener = getOnNavigationItemSelectedListener();
mMyOnNavigationItemSelectedListener = new MyOnNavigationItemSelectedListener(viewPager, this, smoothScroll, listener);
super.setOnNavigationItemSelectedListener(mMyOnNavigationItemSelectedListener);
}
/**
* A {@link ViewPager.OnPageChangeListener} class which contains the
* necessary calls back to the provided {@link BottomNavigationViewEx} so that the tab position is
* kept in sync.
* <p>
* <p>This class stores the provided BottomNavigationViewEx weakly, meaning that you can use
* {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
* addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
* not cause a leak.
*/
private static class BottomNavigationViewExOnPageChangeListener implements ViewPager.OnPageChangeListener {
private final WeakReference<BottomNavigationViewEx> mBnveRef;
public BottomNavigationViewExOnPageChangeListener(BottomNavigationViewEx bnve) {
mBnveRef = new WeakReference<>(bnve);
}
@Override
public void onPageScrollStateChanged(final int state) {
}
@Override
public void onPageScrolled(final int position, final float positionOffset,
final int positionOffsetPixels) {
}
@Override
public void onPageSelected(final int position) {
final BottomNavigationViewEx bnve = mBnveRef.get();
if (null != bnve && !isNavigationItemClicking)
bnve.setCurrentItem(position);
// Log.d("onPageSelected", "--------- position " + position + " ------------");
}
}
/**
* Decorate OnNavigationItemSelectedListener for setupWithViewPager
*/
private static class MyOnNavigationItemSelectedListener implements OnNavigationItemSelectedListener {
private OnNavigationItemSelectedListener listener;
private final WeakReference<ViewPager> viewPagerRef;
private boolean smoothScroll;
private SparseIntArray items;// used for change ViewPager selected item
private int previousPosition = -1;
MyOnNavigationItemSelectedListener(ViewPager viewPager, BottomNavigationViewEx bnve, boolean smoothScroll, OnNavigationItemSelectedListener listener) {
this.viewPagerRef = new WeakReference<>(viewPager);
this.listener = listener;
this.smoothScroll = smoothScroll;
// create items
Menu menu = bnve.getMenu();
int size = menu.size();
items = new SparseIntArray(size);
for (int i = 0; i < size; i++) {
int itemId = menu.getItem(i).getItemId();
items.put(itemId, i);
}
}
public void setOnNavigationItemSelectedListener(OnNavigationItemSelectedListener listener) {
this.listener = listener;
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
int position = items.get(item.getItemId());
// only set item when item changed
if (previousPosition == position) {
return true;
}
// Log.d("onNavigationItemSelecte", "position:" + position);
// user listener
if (null != listener) {
boolean bool = listener.onNavigationItemSelected(item);
// if the selected is invalid, no need change the view pager
if (!bool)
return false;
}
// change view pager
ViewPager viewPager = viewPagerRef.get();
if (null == viewPager)
return false;
// use isNavigationItemClicking flag to avoid `ViewPager.OnPageChangeListener` trigger
isNavigationItemClicking = true;
viewPager.setCurrentItem(items.get(item.getItemId()), smoothScroll);
isNavigationItemClicking = false;
// update previous position
previousPosition = position;
return true;
}
}
@SuppressLint("RestrictedApi")
public void enableShiftingMode(int position, boolean enable) {
getBottomNavigationItemView(position).setShifting(enable);
}
@SuppressLint("RestrictedApi")
public void setItemBackground(int position, int background) {
getBottomNavigationItemView(position).setItemBackground(background);
}
@SuppressLint("RestrictedApi")
public void setIconTintList(int position, ColorStateList tint) {
getBottomNavigationItemView(position).setIconTintList(tint);
}
@SuppressLint("RestrictedApi")
public void setTextTintList(int position, ColorStateList tint) {
getBottomNavigationItemView(position).setTextColor(tint);
}
/**
* set margin top for all icons
*
* @param marginTop in px
*/
public void setIconsMarginTop(int marginTop) {
for (int i = 0; i < getItemCount(); i++) {
setIconMarginTop(i, marginTop);
}
}
/**
* set margin top for icon
*
* @param position
* @param marginTop in px
*/
@SuppressLint("RestrictedApi")
public void setIconMarginTop(int position, int marginTop) {
/*
1. BottomNavigationItemView
2. private final int mDefaultMargin;
*/
BottomNavigationItemView itemView = getBottomNavigationItemView(position);
setField(BottomNavigationItemView.class, itemView, "defaultMargin", marginTop);
mMenuView.updateMenuView();
}
}
================================================
FILE: app/src/main/res/color/selector_color.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="@color/button_pressed"/>
<item android:state_focused="true" android:color="@color/button_pressed"/>
<item android:state_selected="true" android:color="@color/button_pressed"/>
<item android:color="@color/colorPrimary"/>
</selector>
================================================
FILE: app/src/main/res/color/selector_nav_item_color.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/colorPrimary" android:state_checked="true"/>
<item android:color="@color/colorPrimary" android:state_pressed="true"/>
<item android:color="@color/colorNavText"/>
</selector>
================================================
FILE: app/src/main/res/drawable/ic_complete.xml
================================================
<vector android:height="24dp" android:viewportHeight="1024"
android:viewportWidth="1024" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M511.96,0C229.64,0 0,229.7 0,512c0,282.3 229.64,512 511.96,512 282.32,0 512.04,-229.7 512.04,-512C1024,229.7 794.28,0 511.96,0zM511.97,932.45c-231.99,0 -420.77,-188.61 -420.77,-420.45 0,-231.84 188.78,-420.45 420.77,-420.45 232.05,0 420.84,188.61 420.84,420.45 0,231.84 -188.79,420.45 -420.84,420.45z"/>
<path android:fillColor="#FF000000" android:pathData="M443.73,565.2L303.48,437.51l-80.25,86.28 242.4,245.4c41.7,-106.38 173.83,-314.25 335.15,-461.94l-33.16,-52.43C591.94,362.03 501.02,497.3 443.73,565.2z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_setting.xml
================================================
<vector android:height="24dp" android:viewportHeight="1024"
android:viewportWidth="1024" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M512,0C229.22,0 0,229.22 0,512c0,282.77 229.22,512 512,512 282.75,0 512,-229.23 512,-512C1024,229.22 794.75,0 512,0zM512,992C246.9,992 32,777.09 32,512 32,246.9 246.9,32 512,32c265.06,0 480,214.9 480,480C992,777.09 777.06,992 512,992z"/>
<path android:fillColor="#FF000000" android:pathData="M512,416c-53.02,0 -96,42.98 -96,96 0,53.01 42.98,96 96,96 53.01,0 96,-42.99 96,-96C608,458.98 565.01,416 512,416zM512,576c-35.36,0 -64,-28.66 -64,-64 0,-35.34 28.64,-64 64,-64 35.34,0 64,28.66 64,64C576,547.34 547.34,576 512,576z"/>
<path android:fillColor="#FF000000" android:pathData="M810.24,405.12c1.74,-8.16 0.32,-16.16 -3.54,-22.72l-35.02,-60.8c-3.84,-6.56 -10.08,-11.84 -17.92,-14.4 -7.84,-2.56 -16,-1.92 -23.04,1.28l-41.44,18.72c-29.44,-28.16 -65.6,-49.44 -105.92,-61.12L578.88,220.8c-0.8,-7.68 -4.18,-15.04 -10.4,-20.48 -6.08,-5.6 -13.76,-8.32 -21.44,-8.32l-70.08,0c-7.68,0 -15.36,2.72 -21.44,8.32 -6.08,5.44 -9.62,12.8 -10.4,20.48l-4.48,45.28c-40.32,11.68 -76.46,32.96 -105.92,61.12l-41.44,-18.72c-6.88,-3.2 -15.02,-3.84 -22.88,-1.28 -8,2.56 -14.08,7.84 -17.92,14.4l-35.02,60.8c-3.84,6.56 -5.46,14.56 -3.68,22.72 1.76,8 6.4,14.72 12.64,19.2l36.96,26.56c-4.8,19.52 -7.36,40 -7.36,61.12 0,21.12 2.56,41.6 7.36,61.12l-36.96,26.56c-6.24,4.46 -10.88,11.18 -12.64,19.18 -1.76,8.18 -0.16,16.18 3.68,22.72l35.02,60.82c3.84,6.54 9.92,11.82 17.92,14.38 7.84,2.56 15.84,1.92 22.88,-1.28l41.44,-18.72c29.46,28.16 65.62,49.44 105.92,61.12l4.48,45.28c0.8,7.66 4.34,15.02 10.4,20.48 6.08,5.6 13.74,8.32 21.44,8.32l70.08,0c7.66,0 15.36,-2.72 21.44,-8.32 6.24,-5.46 9.62,-12.82 10.4,-20.48l4.46,-45.28c40.34,-11.68 76.48,-32.98 105.92,-61.12l41.44,18.72c7.04,3.2 15.2,3.84 23.04,1.28s14.08,-7.84 17.92,-14.38l35.02,-60.82c3.84,-6.54 5.28,-14.54 3.54,-22.72 -1.76,-8 -6.4,-14.72 -12.64,-19.18l-36.98,-26.56c4.82,-19.54 7.36,-40 7.36,-61.12 0,-21.12 -2.54,-41.6 -7.36,-61.12l36.98,-26.56C803.82,419.84 808.46,413.12 810.24,405.12zM771.98,394.56c-0.8,4 -3.02,7.36 -6.08,9.6l-16.46,12.16 -26.74,19.52c4.18,11.36 7.38,23.2 9.62,35.36 2.38,13.28 3.66,26.88 3.66,40.8s-1.28,27.52 -3.66,40.8c-2.24,12.16 -5.44,24 -9.62,35.36l26.74,19.54 16.46,12.16c3.04,2.24 5.28,5.6 6.08,9.6 0.98,4 0.18,8 -1.74,11.2l-17.62,30.72c-1.92,3.2 -4.96,5.92 -8.96,7.2 -3.84,1.28 -7.84,0.96 -11.36,-0.64l-18.56,-8.16 -30.56,-13.46c-0.16,0.34 -0.48,0.64 -0.96,0.98 -0.34,0.32 -0.64,0.8 -0.98,1.12l-0.16,0.16c-0.16,0.16 -0.32,0.32 -0.32,0.48 -3.2,3.82 -6.4,7.36 -10.1,10.88 -3.82,4 -7.82,7.84 -12.16,11.36 -0.46,0.48 -0.8,0.8 -1.28,1.12 -20.64,17.6 -44.46,31.52 -70.38,40.64 -11.36,4.16 -23.2,7.36 -35.36,9.44l-3.68,32.96L545.6,785.76c-0.48,3.82 -2.26,7.36 -5.28,10.24 -3.04,2.72 -6.88,4 -10.56,4l-35.36,0c-3.84,0 -7.68,-1.28 -10.72,-4 -3.04,-2.88 -4.8,-6.4 -5.28,-10.24l-2.24,-20.34 -3.68,-32.96c-12.16,-2.08 -24,-5.28 -35.36,-9.44 -25.92,-9.12 -49.76,-23.04 -70.4,-40.64 -0.46,-0.34 -0.8,-0.64 -1.26,-1.12 -4.34,-3.52 -8.34,-7.36 -12.18,-11.36 -4.46,-4.34 -8.64,-8.98 -12.32,-13.62l-30.72,13.46 -18.56,8.16c-3.36,1.6 -7.52,1.92 -11.36,0.64s-7.04,-4 -8.8,-7.2l-17.76,-30.72c-1.92,-3.2 -2.56,-7.2 -1.76,-11.2 0.82,-4 3.04,-7.36 6.1,-9.6l16.46,-12.16 26.72,-19.54c-4.16,-11.36 -7.36,-23.2 -9.6,-35.36 -2.4,-13.28 -3.68,-26.88 -3.68,-40.8s1.28,-27.52 3.68,-40.8c2.24,-12.16 5.44,-24 9.6,-35.36l-26.72,-19.68 -16.32,-12c-3.2,-2.24 -5.44,-5.6 -6.26,-9.6 -0.8,-4 -0.16,-8 1.76,-11.2l17.76,-30.72c1.74,-3.2 4.96,-5.92 8.8,-7.2s8,-0.96 11.36,0.64l18.72,8.16 30.56,13.28c0,-0.16 0.32,-0.48 0.64,-0.64 0.16,-0.32 0.48,-0.48 0.8,-0.8 0.48,-0.48 0.8,-1.12 1.28,-1.6 2.88,-3.68 6.24,-7.04 9.6,-10.4 3.84,-4 7.84,-7.84 12.18,-11.36 0.46,-0.48 0.8,-0.8 1.26,-1.12 20.64,-17.6 44.48,-31.52 70.4,-40.64 11.36,-4.16 23.2,-7.36 35.36,-9.44l3.68,-32.96 2.24,-20.32c0.48,-3.84 2.24,-7.36 5.28,-10.24 3.02,-2.72 6.88,-4 10.72,-4l35.36,0c3.68,0 7.52,1.28 10.56,4 3.02,2.88 4.8,6.4 5.28,10.24l2.24,20.32 3.68,32.96c12.16,2.08 24,5.28 35.36,9.44 25.92,9.12 49.74,23.04 70.38,40.64 0.48,0.32 0.82,0.64 1.28,1.12 2.26,1.76 4.16,3.52 6.26,5.6 2.08,1.76 4,3.68 5.9,5.76 3.38,3.2 6.56,6.4 9.28,9.92 0.18,0.16 0.34,0.48 0.48,0.64 1.12,0.96 1.92,1.92 2.72,2.88l30.4,-13.28 18.72,-8.16c3.52,-1.6 7.52,-1.92 11.36,-0.64 4,1.28 7.02,4 8.96,7.2l17.62,30.72C772.16,386.56 772.96,390.56 771.98,394.56z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_todo.xml
================================================
<vector android:height="24dp" android:viewportHeight="1024"
android:viewportWidth="1024" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M512,32C246.91,32 32,246.91 32,512c0,265.09 214.91,480 480,480s480,-214.91 480,-480c0,-265.09 -214.91,-480 -480,-480zM512,928C284.03,928 96,739.97 96,512S284.03,96 512,96s416,188.03 416,416 -188.03,416 -416,416z"/>
<path android:fillColor="#FF000000" android:pathData="M631.9,259.1l-33.22,33.25 127.1,127.14 33.22,-33.25s50.3,-50.34 -13.25,-113.89c-63.55,-63.58 -113.86,-13.25 -113.86,-13.25zM303.58,587.55l-42.37,169.5 169.47,-42.37 254.75,-254.85 -127.1,-127.14z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/selector_button.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape android:shape="rectangle">
<corners android:radius="4dp"/>
<solid android:color="@color/button_pressed"/>
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="rectangle">
<corners android:radius="4dp"/>
<solid android:color="@color/button_pressed"/>
</shape>
</item>
<item>
<shape android:shape="rectangle">
<corners android:radius="4dp"/>
<solid android:color="@color/colorPrimary"/>
</shape>
</item>
</selector>
================================================
FILE: app/src/main/res/layout/activity_add_todo.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/title_bar_layout"/>
<android.support.design.widget.TextInputEditText
android:id="@+id/todo_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="#B5B3B5"
android:textColor="@android:color/black"
android:hint="@string/input_todo_name"
android:layout_marginTop="@dimen/dp_10"
android:layout_marginLeft="@dimen/dp_10"
android:layout_marginRight="@dimen/dp_10"
android:textSize="16sp"
android:background="@null"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#B5B3B5"
android:layout_marginTop="5dp"/>
<android.support.design.widget.TextInputEditText
android:id="@+id/todo_des"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorHint="#B5B3B5"
android:textColor="@android:color/black"
android:hint="@string/input_todo_des"
android:layout_marginTop="@dimen/dp_10"
android:layout_marginLeft="@dimen/dp_10"
android:layout_marginRight="@dimen/dp_10"
android:background="@null"
android:textSize="16sp"
android:gravity="left|top"
android:minLines="8"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#B5B3B5"
android:layout_marginTop="5dp"/>
<TextView
android:id="@+id/todo_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dp_10"
android:drawableLeft="@drawable/date"
android:drawablePadding="5dp"
android:textSize="16sp"
android:textColor="@android:color/black"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#B5B3B5"
android:layout_marginTop="1dp"/>
<Button
android:id="@+id/save_todo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="16sp"
android:text="@string/save"
android:background="@drawable/selector_button"
android:layout_marginTop="30dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"/>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/activity_edit_todo.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/title_bar_layout"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="#B5B3B5"
android:text="@string/todo_title"/>
<android.support.design.widget.TextInputEditText
android:id="@+id/todo_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColorHint="#B5B3B5"
android:textColor="@android:color/black"
android:hint="@string/input_todo_name"
android:layout_marginLeft="5dp"
android:textSize="16sp"
android:background="@null" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#B5B3B5" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="#B5B3B5"
android:gravity="top|left"
android:text="@string/des"/>
<android.support.design.widget.TextInputEditText
android:id="@+id/todo_des"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColorHint="#B5B3B5"
android:textColor="@android:color/black"
android:hint="@string/input_todo_des"
android:layout_marginLeft="5dp"
android:background="@null"
android:textSize="16sp"
android:gravity="left|top"
android:minLines="8"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#B5B3B5"
android:layout_marginTop="5dp"/>
<TextView
android:id="@+id/todo_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/dp_10"
android:drawableLeft="@drawable/date"
android:drawablePadding="5dp"
android:textSize="16sp"
android:textColor="@android:color/black"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#B5B3B5"
android:layout_marginTop="1dp"/>
<Button
android:id="@+id/save_todo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="16sp"
android:text="@string/save"
android:background="@drawable/selector_button"
android:layout_marginTop="30dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"/>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/activity_login.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:background="@android:color/white">
<ScrollView
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp"
android:scrollbars="none">
<LinearLayout
android:id="@+id/email_login_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@mipmap/ic_launcher"
android:layout_margin="30dp"/>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">
<AutoCompleteTextView
android:id="@+id/account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_account"
android:textColorHint="#767676"
android:inputType="text|textAutoComplete"
android:maxLines="1"
android:singleLine="true"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_password"
android:textColorHint="#767676"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="16sp"
android:text="@string/sign_in"
android:background="@drawable/selector_button"
android:layout_marginTop="30dp"/>
<TextView
android:id="@+id/regitster"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="@color/selector_color"
android:layout_gravity="center_horizontal"
android:text="@string/register_message"
android:layout_marginTop="20dp" />
</LinearLayout>
</ScrollView>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/title_bar_layout"/>
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<com.wj.android.todo.widget.BottomNavigationViewEx
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemBackground="@color/colorNavBg"
app:itemIconTint="@color/selector_nav_item_color"
app:itemTextColor="@color/selector_nav_item_color"
app:menu="@menu/navigation" />
</LinearLayout>
================================================
FILE: app/src/main/res/layout/activity_register.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/title_bar_layout"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">
<EditText
android:id="@+id/account"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/input_account"
android:textColorHint="#767676"
android:inputType="text|textAutoComplete"
android:maxLines="1"
android:singleLine="true"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/input_password"
android:textColorHint="#767676"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp">
<EditText
android:id="@+id/repassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/input_repassword"
android:textColorHint="#767676"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/regitster"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="16sp"
android:text="@string/register"
android:background="@drawable/selector_button"
android:layout_marginTop="30dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/activity_splash.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableTop="@mipmap/ic_launcher"
android:textSize="20sp"
android:textColor="@android:color/white"
android:textStyle="bold"
android:drawablePadding="20dp"
android:text="@string/splash_message"/>
</FrameLayout>
================================================
FILE: app/src/main/res/layout/dialog_todo_des_view.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="#333333"
android:text="@string/todo_name"/>
<TextView
android:id="@+id/todo_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#767676"
android:layout_marginTop="5dp"
android:text="@string/input_todo_name"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="5dp"
android:background="#E4E4E4"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="#333333"
android:layout_marginTop="@dimen/dp_10"
android:text="@string/todo_des"/>
<TextView
android:id="@+id/todo_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#767676"
android:layout_marginTop="5dp"
android:text="@string/input_todo_des"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="5dp"
android:background="#E4E4E4"/>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/fragment_complete.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="@+id/done_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/fragment_setting.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="15dp"
android:background="#E4E4E4"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/white"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="#333333"
android:textSize="16sp"
android:text="@string/account_name"/>
<TextView
android:id="@+id/user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#767676"
android:textSize="16sp"
android:drawableRight="@drawable/arrow_right"
android:gravity="center_vertical"
android:drawablePadding="10dp"
android:text="wangandroid"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#E4E4E4"/>
<LinearLayout
android:id="@+id/modify_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="#333333"
android:textSize="16sp"
android:drawableRight="@drawable/arrow_right"
android:gravity="center_vertical"
android:text="@string/modify_password"/>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#E4E4E4"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="15dp"
android:background="#E4E4E4"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/white"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="#333333"
android:textSize="16sp"
android:text="@string/my_github_name"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#767676"
android:textSize="16sp"
android:drawableRight="@drawable/arrow_right"
android:gravity="center_vertical"
android:drawablePadding="10dp"
android:text="@string/my_github_url"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#E4E4E4"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="#333333"
android:textSize="16sp"
android:text="@string/api_support_name"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#767676"
android:textSize="16sp"
android:drawableRight="@drawable/arrow_right"
android:gravity="center_vertical"
android:drawablePadding="10dp"
android:text="@string/api_support_url"/>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#E4E4E4"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="30dp"
android:background="#E4E4E4"/>
<TextView
android:id="@+id/logout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@android:color/holo_red_light"
android:textSize="16sp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:background="@android:color/white"
android:text="退出登录"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#E4E4E4"/>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/fragment_todo.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="@+id/todo_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/title_bar_layout.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary">
<ImageView
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="10dp"
android:src="@drawable/back"
android:layout_gravity="center_vertical|left"
android:visibility="invisible"/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="18sp"
android:layout_gravity="center"/>
<ImageView
android:id="@+id/add_todo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="10dp"
android:src="@drawable/add_todo"
android:layout_gravity="center_vertical|right"
android:visibility="invisible"/>
</FrameLayout>
================================================
FILE: app/src/main/res/layout/todo_item_head.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/todo_head"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#F7FFFD"
android:padding="6dp"
android:textSize="14sp"
android:textColor="@color/colorPrimary"
android:text="2018-08-09"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#E4E4E4"/>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/todo_item_view.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="@dimen/dp_10">
<ImageView
android:id="@+id/item_complete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="2dp"
android:src="@drawable/complete_todo"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp">
<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textSize="16sp"
android:textColor="#333333" />
<TextView
android:id="@+id/item_des"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textSize="12sp"
android:textColor="#767676"
android:layout_marginTop="3dp"
android:visibility="gone"/>
<TextView
android:id="@+id/item_done_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textSize="12sp"
android:textColor="#767676"
android:layout_marginTop="5dp"
android:visibility="gone"/>
</LinearLayout>
<ImageView
android:id="@+id/item_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="2dp"
android:src="@drawable/delete_todo"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#E4E4E4"/>
</LinearLayout>
================================================
FILE: app/src/main/res/menu/navigation.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_todo"
android:icon="@drawable/ic_todo"
android:title="@string/to_do_list" />
<item
android:id="@+id/navigation_complete"
android:icon="@drawable/ic_complete"
android:title="@string/complete_list" />
<item
android:id="@+id/navigation_setting"
android:icon="@drawable/ic_setting"
android:title="@string/setting" />
</menu>
================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#2AB27F</color>
<color name="colorPrimaryDark">#2AB27F</color>
<color name="colorAccent">#FF4081</color>
<color name="colorNavText">#828282</color>
<color name="colorNavBg">#f8f8f8</color>
<color name="button_pressed">#449577</color>
<color name="border_gray">#ADADAD</color>
<color name="done_todo_date">#E4A340</color>
</resources>
================================================
FILE: app/src/main/res/values/dimens.xml
================================================
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>
================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name">TODO</string>
<string name="loading">正在加载中……</string>
<string name="service_error">服务器开小差,请稍候重试</string>
<!-- Strings related to logi
gitextract_pavxei7w/ ├── .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/ │ │ │ ├── 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 │ │ ├── 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/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── release/ │ └── app-release.apk └── settings.gradle
SYMBOL INDEX (346 symbols across 28 files)
FILE: app/src/androidTest/java/com/wj/android/todo/ExampleInstrumentedTest.java
class ExampleInstrumentedTest (line 17) | @RunWith(AndroidJUnit4.class)
method useAppContext (line 19) | @Test
FILE: app/src/main/java/com/wj/android/todo/MainApplication.java
class MainApplication (line 12) | public class MainApplication extends Application {
method onCreate (line 14) | @Override
FILE: app/src/main/java/com/wj/android/todo/activity/AddTodoActivity.java
class AddTodoActivity (line 30) | public class AddTodoActivity extends BaseActivity {
method getLayoutId (line 42) | @Override
method initData (line 47) | @Override
method onClickBack (line 55) | @OnClick(R.id.back)
method onClickTodoDate (line 60) | @OnClick(R.id.todo_date)
method onClickAddTodo (line 74) | @OnClick(R.id.save_todo)
method requestAddTodoData (line 88) | private void requestAddTodoData() {
method updateUI (line 92) | public void updateUI(ResponseItem<TodoDesBean> response) {
method isLoadingEnable (line 102) | @Override
FILE: app/src/main/java/com/wj/android/todo/activity/EditTodoActivity.java
class EditTodoActivity (line 31) | public class EditTodoActivity extends BaseActivity {
method getLayoutId (line 47) | @Override
method initData (line 52) | @Override
method onClickBack (line 82) | @OnClick(R.id.back)
method onClickTodoDate (line 87) | @OnClick(R.id.todo_date)
method onClickAddTodo (line 101) | @OnClick(R.id.save_todo)
method requestUpdateTodoData (line 115) | private void requestUpdateTodoData() {
method updateUI (line 119) | public void updateUI(ResponseItem<TodoDesBean> response) {
method isLoadingEnable (line 129) | @Override
FILE: app/src/main/java/com/wj/android/todo/activity/LoginActivity.java
class LoginActivity (line 16) | public class LoginActivity extends BaseActivity{
method getLayoutId (line 22) | @Override
method initData (line 27) | @Override
method clickRegister (line 32) | @OnClick(R.id.regitster)
method clickLogin (line 37) | @OnClick(R.id.login)
method isLoadingEnable (line 60) | @Override
method requestLogin (line 65) | private void requestLogin() {
method updateUI (line 69) | public void updateUI(ResponseItem<JsonObject> response) {
FILE: app/src/main/java/com/wj/android/todo/activity/MainActivity.java
class MainActivity (line 30) | public class MainActivity extends BaseActivity {
method getLayoutId (line 42) | @Override
method initData (line 47) | @Override
method applyEvent (line 65) | @Override
method onClickAddTodo (line 98) | @OnClick(R.id.add_todo)
class MainViewPagerAdapter (line 103) | private static class MainViewPagerAdapter extends FragmentPagerAdapter {
method MainViewPagerAdapter (line 107) | public MainViewPagerAdapter(FragmentManager fm, List<Fragment> fragm...
method getItem (line 112) | @Override
method getCount (line 117) | @Override
method onActivityResult (line 123) | @Override
method updateDoneOrCancelData (line 137) | public void updateDoneOrCancelData(TodoDesBean todoDesBean, int postit...
FILE: app/src/main/java/com/wj/android/todo/activity/RegisterActivity.java
class RegisterActivity (line 22) | public class RegisterActivity extends BaseActivity {
method getLayoutId (line 35) | @Override
method initData (line 40) | @Override
method onClickRegister (line 46) | @OnClick(R.id.regitster)
method requestRegister (line 81) | private void requestRegister() {
method onClickBack (line 85) | @OnClick(R.id.back)
method updateUI (line 90) | public void updateUI(ResponseItem<JsonObject> response) {
method isLoadingEnable (line 97) | @Override
FILE: app/src/main/java/com/wj/android/todo/activity/SplashActivity.java
class SplashActivity (line 24) | public class SplashActivity extends BaseActivity {
method getLayoutId (line 29) | @Override
method initData (line 34) | @Override
method redirectTo (line 56) | private void redirectTo() {
FILE: app/src/main/java/com/wj/android/todo/activity/base/BaseActivity.java
class BaseActivity (line 21) | public abstract class BaseActivity extends AppCompatActivity implements ...
method onCreate (line 25) | @Override
method onStart (line 39) | @Override
method onRestart (line 45) | @Override
method onResume (line 51) | @Override
method onPause (line 57) | @Override
method onStop (line 63) | @Override
method onDestroy (line 69) | @Override
method getLayoutId (line 82) | protected abstract int getLayoutId();
method initView (line 87) | protected void initView(){}
method initData (line 92) | protected abstract void initData();
method applyEvent (line 97) | protected void applyEvent(){}
method isLoadingEnable (line 99) | @Override
method start (line 104) | @Override
method error (line 115) | @Override
method end (line 124) | @Override
method showToast (line 131) | public void showToast(String msg) {
method startActivity (line 135) | public void startActivity(Class<?> cls) {
method startActivity (line 140) | public void startActivity(Class<?> cls, Bundle bundle) {
method startActivityForResult (line 146) | public void startActivityForResult(Class<?> cls, int requestCode) {
FILE: app/src/main/java/com/wj/android/todo/adapter/TodoSectionAdapter.java
class TodoSectionAdapter (line 16) | public class TodoSectionAdapter extends BaseSectionQuickAdapter<TodoSect...
method TodoSectionAdapter (line 19) | public TodoSectionAdapter(int layoutResId, int sectionHeadResId, List<...
method convertHead (line 24) | @Override
method convert (line 34) | @Override
FILE: app/src/main/java/com/wj/android/todo/bean/TodoDesBean.java
class TodoDesBean (line 9) | public class TodoDesBean implements Serializable{
method getCompleteDate (line 22) | public Object getCompleteDate() {
method setCompleteDate (line 26) | public void setCompleteDate(Object completeDate) {
method getCompleteDateStr (line 30) | public String getCompleteDateStr() {
method setCompleteDateStr (line 34) | public void setCompleteDateStr(String completeDateStr) {
method getContent (line 38) | public String getContent() {
method setContent (line 42) | public void setContent(String content) {
method getDate (line 46) | public long getDate() {
method setDate (line 50) | public void setDate(long date) {
method getDateStr (line 54) | public String getDateStr() {
method setDateStr (line 58) | public void setDateStr(String dateStr) {
method getId (line 62) | public int getId() {
method setId (line 66) | public void setId(int id) {
method getStatus (line 70) | public int getStatus() {
method setStatus (line 74) | public void setStatus(int status) {
method getTitle (line 78) | public String getTitle() {
method setTitle (line 82) | public void setTitle(String title) {
method getType (line 86) | public int getType() {
method setType (line 90) | public void setType(int type) {
method getUserId (line 94) | public int getUserId() {
method setUserId (line 98) | public void setUserId(int userId) {
FILE: app/src/main/java/com/wj/android/todo/bean/TodoListBean.java
class TodoListBean (line 9) | public class TodoListBean {
method getCurPage (line 18) | public int getCurPage() {
method setCurPage (line 22) | public void setCurPage(int curPage) {
method getOffset (line 26) | public int getOffset() {
method setOffset (line 30) | public void setOffset(int offset) {
method isOver (line 34) | public boolean isOver() {
method setOver (line 38) | public void setOver(boolean over) {
method getPageCount (line 42) | public int getPageCount() {
method setPageCount (line 46) | public void setPageCount(int pageCount) {
method getSize (line 50) | public int getSize() {
method setSize (line 54) | public void setSize(int size) {
method getTotal (line 58) | public int getTotal() {
method setTotal (line 62) | public void setTotal(int total) {
method getDatas (line 66) | public List<TodoDesBean> getDatas() {
method setDatas (line 70) | public void setDatas(List<TodoDesBean> datas) {
FILE: app/src/main/java/com/wj/android/todo/bean/TodoSection.java
class TodoSection (line 9) | public class TodoSection extends SectionEntity<TodoDesBean> {
method TodoSection (line 11) | public TodoSection(boolean isHeader, String header) {
method TodoSection (line 15) | public TodoSection(TodoDesBean todoBean) {
FILE: app/src/main/java/com/wj/android/todo/constant/Constant.java
class Constant (line 7) | public class Constant {
FILE: app/src/main/java/com/wj/android/todo/constant/TimeConstants.java
class TimeConstants (line 16) | public final class TimeConstants {
FILE: app/src/main/java/com/wj/android/todo/exception/ApiException.java
class ApiException (line 7) | public class ApiException extends RuntimeException{
method ApiException (line 11) | public ApiException(String message, int errorCode) {
method getErrorCode (line 16) | public int getErrorCode() {
method setErrorCode (line 20) | public void setErrorCode(int errorCode) {
FILE: app/src/main/java/com/wj/android/todo/fragment/SettingFragment.java
class SettingFragment (line 24) | public class SettingFragment extends BaseFragment {
method newInstance (line 28) | public static SettingFragment newInstance() {
method getLayoutId (line 36) | @Override
method initData (line 41) | @Override
method onClickLogout (line 50) | @OnClick(R.id.logout)
FILE: app/src/main/java/com/wj/android/todo/fragment/TodoFragment.java
class TodoFragment (line 38) | public class TodoFragment extends BaseFragment {
method newInstance (line 55) | public static TodoFragment newInstance(boolean isDone) {
method onCreate (line 64) | @Override
method getLayoutId (line 74) | @Override
method initData (line 79) | @Override
method requestTodoListData (line 96) | private void requestTodoListData() {
method deleteTodoById (line 100) | private void deleteTodoById(int todoId) {
method doneTodoById (line 104) | private void doneTodoById(int todoId) {
method updateUI (line 108) | public void updateUI(final ResponseItem<TodoListBean> response) {
method showTodoDes (line 160) | private void showTodoDes(TodoSection todoSection) {
method getTodoSectionData (line 177) | private List<TodoSection> getTodoSectionData(List<TodoDesBean> datas) {
method isLoadingEnable (line 196) | @Override
method start (line 201) | @Override
method error (line 213) | @Override
method end (line 223) | @Override
method updateAddTodoData (line 234) | public void updateAddTodoData(TodoDesBean todoDesBean) {
method updateRemovedData (line 254) | public void updateRemovedData(ResponseItem response) {
method updateDoneData (line 269) | public void updateDoneData(ResponseItem<TodoDesBean> response) {
method showDialog (line 284) | private void showDialog(final int position) {
method onActivityResult (line 299) | @Override
FILE: app/src/main/java/com/wj/android/todo/fragment/base/BaseFragment.java
class BaseFragment (line 26) | public abstract class BaseFragment extends Fragment implements BaseView {
method onCreateView (line 30) | @Nullable
method onActivityCreated (line 38) | @Override
method onDestroyView (line 44) | @Override
method getLayoutId (line 54) | protected abstract int getLayoutId();
method initData (line 59) | protected abstract void initData();
method isLoadingEnable (line 61) | public boolean isLoadingEnable(int requestId) {
method start (line 65) | @Override
method error (line 76) | @Override
method end (line 87) | @Override
method showToast (line 95) | public void showToast(String msg) {
method startActivity (line 99) | public void startActivity(Class<?> cls) {
method startActivity (line 104) | public void startActivity(Class<?> cls, Bundle bundle) {
method startActivityForResult (line 110) | public void startActivityForResult(Class<?> cls, Bundle bundle,int req...
FILE: app/src/main/java/com/wj/android/todo/http/HttpUtils.java
class HttpUtils (line 25) | public class HttpUtils {
method buildUrl (line 27) | private static String buildUrl(String uri) {
method requestLogin (line 31) | public static void requestLogin(BaseView baseView, String userName, St...
method requestRegister (line 43) | public static void requestRegister(BaseView baseView, String userName,...
method requestAddTodoData (line 56) | public static void requestAddTodoData(BaseView baseView, String todoNa...
method requestTodoList (line 73) | public static void requestTodoList(BaseView baseView, int page, boolea...
method deleteTodoById (line 82) | public static void deleteTodoById(BaseView baseView, int todoId) {
method updateTodoById (line 91) | public static void updateTodoById(BaseView baseView, int todoId, Strin...
method doneTodoById (line 95) | public static void doneTodoById(BaseView baseView, int todoId, int sta...
method requestUpdateTodoData (line 107) | public static void requestUpdateTodoData(BaseView baseView, int todoId...
FILE: app/src/main/java/com/wj/android/todo/http/MyGsonCallback.java
class MyGsonCallback (line 12) | public abstract class MyGsonCallback<T> extends GsonCallback<T> {
method MyGsonCallback (line 14) | public MyGsonCallback(BaseView baseView) {
method MyGsonCallback (line 18) | public MyGsonCallback(BaseView baseView, int requestId) {
method convertResponse (line 22) | @Override
FILE: app/src/main/java/com/wj/android/todo/http/ResponseItem.java
class ResponseItem (line 11) | public class ResponseItem<T> implements Serializable {
method getErrorCode (line 25) | public int getErrorCode() {
method setErrorCode (line 29) | public void setErrorCode(int errorCode) {
method getErrorMsg (line 33) | public String getErrorMsg() {
method setErrorMsg (line 37) | public void setErrorMsg(String errorMsg) {
method getData (line 41) | public T getData() {
method setData (line 45) | public void setData(T data) {
method isSuccess (line 49) | public boolean isSuccess() {
FILE: app/src/main/java/com/wj/android/todo/manager/PersistentCookieJarManager.java
class PersistentCookieJarManager (line 15) | public class PersistentCookieJarManager {
method PersistentCookieJarManager (line 20) | private PersistentCookieJarManager(Context context) {
method getInstance (line 24) | public static PersistentCookieJarManager getInstance(Context context) {
method getPersistentCookieJar (line 35) | public PersistentCookieJar getPersistentCookieJar() {
FILE: app/src/main/java/com/wj/android/todo/manager/PreferenceHelper.java
class PreferenceHelper (line 24) | public abstract class PreferenceHelper {
method save (line 26) | public abstract void save(Editor editor);
method getInstance (line 30) | public static PreferenceHelper getInstance() {
class OlderPreferenceHelper (line 41) | private static class OlderPreferenceHelper extends PreferenceHelper {
method save (line 43) | @Override
class NewPreferenceHelper (line 50) | private static class NewPreferenceHelper extends PreferenceHelper {
method save (line 52) | @SuppressLint("NewApi")
FILE: app/src/main/java/com/wj/android/todo/manager/SharePreferenceManager.java
class SharePreferenceManager (line 10) | public class SharePreferenceManager {
method SharePreferenceManager (line 18) | private SharePreferenceManager(Context context) {
method getInstance (line 22) | public static SharePreferenceManager getInstance(Context context) {
method setUserName (line 33) | public void setUserName(String userName) {
method getUserName (line 39) | public String getUserName() {
method clear (line 43) | public void clear() {
FILE: app/src/main/java/com/wj/android/todo/util/TimeUtils.java
class TimeUtils (line 23) | public final class TimeUtils {
method getDefaultFormat (line 27) | private static SimpleDateFormat getDefaultFormat() {
method getDateFormat (line 36) | private static SimpleDateFormat getDateFormat(String pattern) {
method TimeUtils (line 40) | private TimeUtils() {
method millis2String (line 51) | public static String millis2String(final long millis) {
method millis2String (line 62) | public static String millis2String(final long millis, @NonNull final D...
method string2Millis (line 73) | public static long string2Millis(final String time) {
method string2Millis (line 84) | public static long string2Millis(final String time, @NonNull final Dat...
method string2Date (line 100) | public static Date string2Date(final String time) {
method string2Date (line 111) | public static Date string2Date(final String time, @NonNull final DateF...
method date2String (line 127) | public static String date2String(final Date date) {
method date2String (line 138) | public static String date2String(final Date date, @NonNull final DateF...
method date2String (line 143) | public static String date2String(final Date date, @NonNull final Strin...
method date2Millis (line 152) | public static long date2Millis(final Date date) {
method millis2Date (line 162) | public static Date millis2Date(final long millis) {
method getTimeSpan (line 182) | public static long getTimeSpan(final String time1,
method getTimeSpan (line 204) | public static long getTimeSpan(final String time1,
method getTimeSpan (line 226) | public static long getTimeSpan(final Date date1,
method getTimeSpan (line 247) | public static long getTimeSpan(final long millis1,
method getFitTimeSpan (line 270) | public static String getFitTimeSpan(final String time1,
method getFitTimeSpan (line 294) | public static String getFitTimeSpan(final String time1,
method getFitTimeSpan (line 318) | public static String getFitTimeSpan(final Date date1, final Date date2...
method getFitTimeSpan (line 338) | public static String getFitTimeSpan(final long millis1,
method getNowMills (line 349) | public static long getNowMills() {
method getNowString (line 359) | public static String getNowString() {
method getNowString (line 369) | public static String getNowString(@NonNull final DateFormat format) {
method getNowDate (line 378) | public static Date getNowDate() {
method getTimeSpanByNow (line 397) | public static long getTimeSpanByNow(final String time, @TimeConstants....
method getTimeSpanByNow (line 416) | public static long getTimeSpanByNow(final String time,
method getTimeSpanByNow (line 436) | public static long getTimeSpanByNow(final Date date, @TimeConstants.Un...
method getTimeSpanByNow (line 454) | public static long getTimeSpanByNow(final long millis, @TimeConstants....
method getFitTimeSpanByNow (line 474) | public static String getFitTimeSpanByNow(final String time, final int ...
method getFitTimeSpanByNow (line 494) | public static String getFitTimeSpanByNow(final String time,
method getFitTimeSpanByNow (line 515) | public static String getFitTimeSpanByNow(final Date date, final int pr...
method getFitTimeSpanByNow (line 534) | public static String getFitTimeSpanByNow(final long millis, final int ...
method getFriendlyTimeSpanByNow (line 554) | public static String getFriendlyTimeSpanByNow(final String time) {
method getFriendlyTimeSpanByNow (line 574) | public static String getFriendlyTimeSpanByNow(final String time,
method getFriendlyTimeSpanByNow (line 594) | public static String getFriendlyTimeSpanByNow(final Date date) {
method getFriendlyTimeSpanByNow (line 613) | public static String getFriendlyTimeSpanByNow(final long millis) {
method getWeeOfToday (line 637) | private static long getWeeOfToday() {
method getMillis (line 661) | public static long getMillis(final long millis,
method getMillis (line 683) | public static long getMillis(final String time,
method getMillis (line 705) | public static long getMillis(final String time,
method getMillis (line 727) | public static long getMillis(final Date date,
method getString (line 749) | public static String getString(final long millis,
method getString (line 771) | public static String getString(final long millis,
method getString (line 794) | public static String getString(final String time,
method getString (line 816) | public static String getString(final String time,
method getString (line 839) | public static String getString(final Date date,
method getString (line 861) | public static String getString(final Date date,
method getDate (line 883) | public static Date getDate(final long millis,
method getDate (line 905) | public static Date getDate(final String time,
method getDate (line 927) | public static Date getDate(final String time,
method getDate (line 949) | public static Date getDate(final Date date,
method getMillisByNow (line 969) | public static long getMillisByNow(final long timeSpan, @TimeConstants....
method getStringByNow (line 988) | public static String getStringByNow(final long timeSpan, @TimeConstant...
method getStringByNow (line 1007) | public static String getStringByNow(final long timeSpan,
method getDateByNow (line 1027) | public static Date getDateByNow(final long timeSpan, @TimeConstants.Un...
method isToday (line 1038) | public static boolean isToday(final String time) {
method isToday (line 1049) | public static boolean isToday(final String time, @NonNull final DateFo...
method isToday (line 1059) | public static boolean isToday(final Date date) {
method isToday (line 1069) | public static boolean isToday(final long millis) {
method isLeapYear (line 1081) | public static boolean isLeapYear(final String time) {
method isLeapYear (line 1092) | public static boolean isLeapYear(final String time, @NonNull final Dat...
method isLeapYear (line 1102) | public static boolean isLeapYear(final Date date) {
method isLeapYear (line 1115) | public static boolean isLeapYear(final long millis) {
method isLeapYear (line 1125) | public static boolean isLeapYear(final int year) {
method getChineseWeek (line 1136) | public static String getChineseWeek(final String time) {
method getChineseWeek (line 1147) | public static String getChineseWeek(final String time, @NonNull final ...
method getChineseWeek (line 1157) | public static String getChineseWeek(final Date date) {
method getChineseWeek (line 1167) | public static String getChineseWeek(final long millis) {
method getUSWeek (line 1178) | public static String getUSWeek(final String time) {
method getUSWeek (line 1189) | public static String getUSWeek(final String time, @NonNull final DateF...
method getUSWeek (line 1199) | public static String getUSWeek(final Date date) {
method getUSWeek (line 1209) | public static String getUSWeek(final long millis) {
method getValueByCalendarField (line 1228) | public static int getValueByCalendarField(final String time, final int...
method getValueByCalendarField (line 1247) | public static int getValueByCalendarField(final String time,
method getValueByCalendarField (line 1267) | public static int getValueByCalendarField(final Date date, final int f...
method getValueByCalendarField (line 1287) | public static int getValueByCalendarField(final long millis, final int...
method getChineseZodiac (line 1303) | public static String getChineseZodiac(final String time) {
method getChineseZodiac (line 1314) | public static String getChineseZodiac(final String time, @NonNull fina...
method getChineseZodiac (line 1324) | public static String getChineseZodiac(final Date date) {
method getChineseZodiac (line 1336) | public static String getChineseZodiac(final long millis) {
method getChineseZodiac (line 1346) | public static String getChineseZodiac(final int year) {
method getZodiac (line 1363) | public static String getZodiac(final String time) {
method getZodiac (line 1374) | public static String getZodiac(final String time, @NonNull final DateF...
method getZodiac (line 1384) | public static String getZodiac(final Date date) {
method getZodiac (line 1398) | public static String getZodiac(final long millis) {
method getZodiac (line 1409) | public static String getZodiac(final int month, final int day) {
method timeSpan2Millis (line 1415) | private static long timeSpan2Millis(final long timeSpan, @TimeConstant...
method millis2TimeSpan (line 1419) | private static long millis2TimeSpan(final long millis, @TimeConstants....
method millis2FitTimeSpan (line 1423) | private static String millis2FitTimeSpan(long millis, int precision) {
FILE: app/src/main/java/com/wj/android/todo/widget/BottomNavigationViewEx.java
class BottomNavigationViewEx (line 30) | public class BottomNavigationViewEx extends BottomNavigationView {
method BottomNavigationViewEx (line 55) | public BottomNavigationViewEx(Context context) {
method BottomNavigationViewEx (line 59) | public BottomNavigationViewEx(Context context, AttributeSet attrs) {
method BottomNavigationViewEx (line 63) | public BottomNavigationViewEx(Context context, AttributeSet attrs, int...
method refreshTextViewVisibility (line 67) | @SuppressLint("RestrictedApi")
method setIconVisibility (line 114) | @SuppressLint("RestrictedApi")
method setTextVisibility (line 181) | @SuppressLint("RestrictedApi")
method getFontHeight (line 262) | private static int getFontHeight(float fontSize) {
method enableAnimation (line 274) | @SuppressLint("RestrictedApi")
method enableShiftingMode (line 352) | @SuppressLint("RestrictedApi")
method enableItemShiftingMode (line 374) | @SuppressLint("RestrictedApi")
method getCurrentItem (line 402) | public int getCurrentItem() {
method getMenuItemPosition (line 433) | public int getMenuItemPosition(MenuItem item) {
method setCurrentItem (line 452) | public void setCurrentItem(int item) {
method getOnNavigationItemSelectedListener (line 487) | public OnNavigationItemSelectedListener getOnNavigationItemSelectedLis...
method setOnNavigationItemSelectedListener (line 493) | @Override
method getBottomNavigationMenuView (line 509) | private BottomNavigationMenuView getBottomNavigationMenuView() {
method getBottomNavigationItemViews (line 520) | public BottomNavigationItemView[] getBottomNavigationItemViews() {
method getBottomNavigationItemView (line 538) | public BottomNavigationItemView getBottomNavigationItemView(int positi...
method getIconAt (line 548) | public ImageView getIconAt(int position) {
method getSmallLabelAt (line 566) | public TextView getSmallLabelAt(int position) {
method getLargeLabelAt (line 584) | public TextView getLargeLabelAt(int position) {
method getItemCount (line 600) | public int getItemCount() {
method setSmallTextSize (line 615) | @SuppressLint("RestrictedApi")
method setLargeTextSize (line 632) | @SuppressLint("RestrictedApi")
method setTextSize (line 649) | public void setTextSize(float sp) {
method setIconSizeAt (line 661) | @SuppressLint("RestrictedApi")
method setIconSize (line 679) | public void setIconSize(float width, float height) {
method setItemHeight (line 691) | @SuppressLint("RestrictedApi")
method getItemHeight (line 706) | public int getItemHeight() {
method dp2px (line 720) | public static int dp2px(Context context, float dpValue) {
method setTypeface (line 731) | @SuppressLint("RestrictedApi")
method setTypeface (line 746) | @SuppressLint("RestrictedApi")
method getField (line 765) | private <T> T getField(Class targetClass, Object instance, String fiel...
method setField (line 786) | private void setField(Class targetClass, Object instance, String field...
method setupWithViewPager (line 805) | public void setupWithViewPager(@Nullable final ViewPager viewPager) {
method setupWithViewPager (line 817) | public void setupWithViewPager(@Nullable final ViewPager viewPager, bo...
class BottomNavigationViewExOnPageChangeListener (line 855) | private static class BottomNavigationViewExOnPageChangeListener implem...
method BottomNavigationViewExOnPageChangeListener (line 858) | public BottomNavigationViewExOnPageChangeListener(BottomNavigationVi...
method onPageScrollStateChanged (line 862) | @Override
method onPageScrolled (line 866) | @Override
method onPageSelected (line 871) | @Override
class MyOnNavigationItemSelectedListener (line 883) | private static class MyOnNavigationItemSelectedListener implements OnN...
method MyOnNavigationItemSelectedListener (line 891) | MyOnNavigationItemSelectedListener(ViewPager viewPager, BottomNaviga...
method setOnNavigationItemSelectedListener (line 906) | public void setOnNavigationItemSelectedListener(OnNavigationItemSele...
method onNavigationItemSelected (line 910) | @Override
method enableShiftingMode (line 944) | @SuppressLint("RestrictedApi")
method setItemBackground (line 948) | @SuppressLint("RestrictedApi")
method setIconTintList (line 952) | @SuppressLint("RestrictedApi")
method setTextTintList (line 956) | @SuppressLint("RestrictedApi")
method setIconsMarginTop (line 966) | public void setIconsMarginTop(int marginTop) {
method setIconMarginTop (line 978) | @SuppressLint("RestrictedApi")
FILE: app/src/test/java/com/wj/android/todo/ExampleUnitTest.java
class ExampleUnitTest (line 12) | public class ExampleUnitTest {
method addition_isCorrect (line 13) | @Test
Condensed preview — 67 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (222K chars).
[
{
"path": ".gitignore",
"chars": 154,
"preview": "*.iml\r\n.gradle\r\n.idea\r\n/local.properties\r\n/.idea/libraries\r\n/.idea/modules.xml\r\n/.idea/workspace.xml\r\n.DS_Store\r\n/build\r"
},
{
"path": "README.md",
"chars": 2641,
"preview": "# wj-todo-wanandroid\r\n用心打造一款极致体验的TODO开源客户端,数据接口来自鸿神的玩Android,不放过每一个细节,用心写代码,如果您觉得还不错的话,就点个Star吧~(您的每次支持,是我开源的动力)\r\n# APK\r"
},
{
"path": "app/.gitignore",
"chars": 34,
"preview": "/build\r\n/release\r\n/keystore\r\n*.iml"
},
{
"path": "app/build.gradle",
"chars": 1835,
"preview": "apply plugin: 'com.android.application'\r\n\r\nandroid {\r\n compileSdkVersion 28\r\n defaultConfig {\r\n application"
},
{
"path": "app/proguard-rules.pro",
"chars": 772,
"preview": "# Add project specific ProGuard rules here.\r\n# You can control the set of applied configuration files using the\r\n# progu"
},
{
"path": "app/src/androidTest/java/com/wj/android/todo/ExampleInstrumentedTest.java",
"chars": 748,
"preview": "package com.wj.android.todo;\r\n\r\nimport android.content.Context;\r\nimport android.support.test.InstrumentationRegistry;\r\ni"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 1464,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n packag"
},
{
"path": "app/src/main/java/com/wj/android/todo/MainApplication.java",
"chars": 553,
"preview": "package com.wj.android.todo;\r\n\r\nimport android.app.Application;\r\n\r\nimport com.wj.android.http.XRetrofit;\r\nimport com.wj."
},
{
"path": "app/src/main/java/com/wj/android/todo/activity/AddTodoActivity.java",
"chars": 3352,
"preview": "package com.wj.android.todo.activity;\r\n\r\nimport android.app.DatePickerDialog;\r\nimport android.content.Intent;\r\nimport an"
},
{
"path": "app/src/main/java/com/wj/android/todo/activity/EditTodoActivity.java",
"chars": 4294,
"preview": "package com.wj.android.todo.activity;\r\n\r\nimport android.app.DatePickerDialog;\r\nimport android.content.Intent;\r\nimport an"
},
{
"path": "app/src/main/java/com/wj/android/todo/activity/LoginActivity.java",
"chars": 2254,
"preview": "package com.wj.android.todo.activity;\r\nimport android.text.TextUtils;\r\nimport android.widget.AutoCompleteTextView;\r\nimpo"
},
{
"path": "app/src/main/java/com/wj/android/todo/activity/MainActivity.java",
"chars": 4643,
"preview": "package com.wj.android.todo.activity;\r\n\r\nimport android.content.Intent;\r\nimport android.support.annotation.Nullable;\r\nim"
},
{
"path": "app/src/main/java/com/wj/android/todo/activity/RegisterActivity.java",
"chars": 2973,
"preview": "package com.wj.android.todo.activity;\r\n\r\nimport android.text.TextUtils;\r\nimport android.view.View;\r\nimport android.widge"
},
{
"path": "app/src/main/java/com/wj/android/todo/activity/SplashActivity.java",
"chars": 1949,
"preview": "package com.wj.android.todo.activity;\r\n\r\nimport android.os.Bundle;\r\nimport android.view.View;\r\nimport android.view.anima"
},
{
"path": "app/src/main/java/com/wj/android/todo/activity/base/BaseActivity.java",
"chars": 3838,
"preview": "package com.wj.android.todo.activity.base;\n\nimport android.app.ProgressDialog;\nimport android.content.Intent;\nimport and"
},
{
"path": "app/src/main/java/com/wj/android/todo/adapter/TodoSectionAdapter.java",
"chars": 2125,
"preview": "package com.wj.android.todo.adapter;\r\n\r\nimport android.text.TextUtils;\r\n\r\nimport com.chad.library.adapter.base.BaseSecti"
},
{
"path": "app/src/main/java/com/wj/android/todo/bean/TodoDesBean.java",
"chars": 1993,
"preview": "package com.wj.android.todo.bean;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * 作者:wangwnejie on 2018/8/9 14:29\r\n * 邮箱:wang"
},
{
"path": "app/src/main/java/com/wj/android/todo/bean/TodoListBean.java",
"chars": 1391,
"preview": "package com.wj.android.todo.bean;\r\n\r\nimport java.util.List;\r\n\r\n/**\r\n * 作者:wangwnejie on 2018/8/9 14:55\r\n * 邮箱:wang200809"
},
{
"path": "app/src/main/java/com/wj/android/todo/bean/TodoSection.java",
"chars": 424,
"preview": "package com.wj.android.todo.bean;\r\n\r\nimport com.chad.library.adapter.base.entity.SectionEntity;\r\n\r\n/**\r\n * 作者:wangwnejie"
},
{
"path": "app/src/main/java/com/wj/android/todo/constant/Constant.java",
"chars": 797,
"preview": "package com.wj.android.todo.constant;\r\n\r\n/**\r\n * 作者:wangwnejie on 2018/8/7 15:24\r\n * 邮箱:wang20080990@163.com\r\n */\r\npubli"
},
{
"path": "app/src/main/java/com/wj/android/todo/constant/TimeConstants.java",
"chars": 672,
"preview": "package com.wj.android.todo.constant;\n\nimport android.support.annotation.IntDef;\n\nimport java.lang.annotation.Retention;"
},
{
"path": "app/src/main/java/com/wj/android/todo/exception/ApiException.java",
"chars": 490,
"preview": "package com.wj.android.todo.exception;\r\n\r\n/**\r\n * 作者:wangwnejie on 2018/8/8 14:06\r\n * 邮箱:wang20080990@163.com\r\n */\r\npubl"
},
{
"path": "app/src/main/java/com/wj/android/todo/fragment/SettingFragment.java",
"chars": 1679,
"preview": "package com.wj.android.todo.fragment;\r\n\r\nimport android.os.Bundle;\r\nimport android.text.TextUtils;\r\nimport android.widge"
},
{
"path": "app/src/main/java/com/wj/android/todo/fragment/TodoFragment.java",
"chars": 11988,
"preview": "package com.wj.android.todo.fragment;\r\n\r\nimport android.content.DialogInterface;\r\nimport android.content.Intent;\r\nimport"
},
{
"path": "app/src/main/java/com/wj/android/todo/fragment/base/BaseFragment.java",
"chars": 3216,
"preview": "package com.wj.android.todo.fragment.base;\n\nimport android.app.ProgressDialog;\nimport android.content.Intent;\nimport and"
},
{
"path": "app/src/main/java/com/wj/android/todo/http/HttpUtils.java",
"chars": 5202,
"preview": "package com.wj.android.todo.http;\r\n\r\nimport android.text.TextUtils;\r\n\r\nimport com.google.gson.JsonObject;\r\nimport com.wj"
},
{
"path": "app/src/main/java/com/wj/android/todo/http/MyGsonCallback.java",
"chars": 902,
"preview": "package com.wj.android.todo.http;\r\n\r\nimport com.google.gson.Gson;\r\nimport com.wj.android.http.BaseView;\r\nimport com.wj.a"
},
{
"path": "app/src/main/java/com/wj/android/todo/http/ResponseItem.java",
"chars": 789,
"preview": "package com.wj.android.todo.http;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n* 响应类型\r\n* @author wangwenjie\r\n*泛型T是实际的响应类型\r\n*响"
},
{
"path": "app/src/main/java/com/wj/android/todo/manager/PersistentCookieJarManager.java",
"chars": 1269,
"preview": "package com.wj.android.todo.manager;\r\n\r\n\r\nimport android.content.Context;\r\n\r\nimport com.franmontiel.persistentcookiejar."
},
{
"path": "app/src/main/java/com/wj/android/todo/manager/PreferenceHelper.java",
"chars": 1532,
"preview": "/*******************************************************************************\r\n * Copyright (c) Baina Info Tech Co. L"
},
{
"path": "app/src/main/java/com/wj/android/todo/manager/SharePreferenceManager.java",
"chars": 1370,
"preview": "package com.wj.android.todo.manager;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\n/**\n * "
},
{
"path": "app/src/main/java/com/wj/android/todo/util/TimeUtils.java",
"chars": 51807,
"preview": "package com.wj.android.todo.util;\n\nimport android.support.annotation.NonNull;\n\n\nimport com.wj.android.todo.constant.Time"
},
{
"path": "app/src/main/java/com/wj/android/todo/widget/BottomNavigationViewEx.java",
"chars": 34545,
"preview": "package com.wj.android.todo.widget;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport andr"
},
{
"path": "app/src/main/res/color/selector_color.xml",
"chars": 412,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\r\n <item"
},
{
"path": "app/src/main/res/color/selector_nav_item_color.xml",
"chars": 322,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item a"
},
{
"path": "app/src/main/res/drawable/ic_complete.xml",
"chars": 758,
"preview": "<vector android:height=\"24dp\" android:viewportHeight=\"1024\"\n android:viewportWidth=\"1024\" android:width=\"24dp\" xmlns:"
},
{
"path": "app/src/main/res/drawable/ic_setting.xml",
"chars": 4470,
"preview": "<vector android:height=\"24dp\" android:viewportHeight=\"1024\"\n android:viewportWidth=\"1024\" android:width=\"24dp\" xmlns:"
},
{
"path": "app/src/main/res/drawable/ic_todo.xml",
"chars": 714,
"preview": "<vector android:height=\"24dp\" android:viewportHeight=\"1024\"\n android:viewportWidth=\"1024\" android:width=\"24dp\" xmlns:"
},
{
"path": "app/src/main/res/drawable/selector_button.xml",
"chars": 751,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\r\n <item"
},
{
"path": "app/src/main/res/layout/activity_add_todo.xml",
"chars": 2777,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n an"
},
{
"path": "app/src/main/res/layout/activity_edit_todo.xml",
"chars": 3602,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n an"
},
{
"path": "app/src/main/res/layout/activity_login.xml",
"chars": 3467,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n an"
},
{
"path": "app/src/main/res/layout/activity_main.xml",
"chars": 949,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n an"
},
{
"path": "app/src/main/res/layout/activity_register.xml",
"chars": 3332,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n an"
},
{
"path": "app/src/main/res/layout/activity_splash.xml",
"chars": 694,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n and"
},
{
"path": "app/src/main/res/layout/dialog_todo_des_view.xml",
"chars": 1725,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n an"
},
{
"path": "app/src/main/res/layout/fragment_complete.xml",
"chars": 674,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n an"
},
{
"path": "app/src/main/res/layout/fragment_setting.xml",
"chars": 6333,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n an"
},
{
"path": "app/src/main/res/layout/fragment_todo.xml",
"chars": 674,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n an"
},
{
"path": "app/src/main/res/layout/title_bar_layout.xml",
"chars": 1143,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n and"
},
{
"path": "app/src/main/res/layout/todo_item_head.xml",
"chars": 755,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n an"
},
{
"path": "app/src/main/res/layout/todo_item_view.xml",
"chars": 2518,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n an"
},
{
"path": "app/src/main/res/menu/navigation.xml",
"chars": 559,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\r\n <item\r\n "
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 455,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<resources>\r\n <color name=\"colorPrimary\">#2AB27F</color>\r\n <color name=\"co"
},
{
"path": "app/src/main/res/values/dimens.xml",
"chars": 216,
"preview": "<resources>\r\n <!-- Default screen margins, per the Android Design guidelines. -->\r\n <dimen name=\"activity_horizont"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 2306,
"preview": "<resources>\r\n <string name=\"app_name\">TODO</string>\r\n <string name=\"loading\">正在加载中……</string>\r\n <string name=\"s"
},
{
"path": "app/src/main/res/values/styles.xml",
"chars": 392,
"preview": "<resources>\r\n\r\n <!-- Base application theme. -->\r\n <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.NoActionBa"
},
{
"path": "app/src/main/res/xml/network_security_config.xml",
"chars": 147,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<network-security-config>\r\n <base-config cleartextTrafficPermitted=\"true\" />\r"
},
{
"path": "app/src/test/java/com/wj/android/todo/ExampleUnitTest.java",
"chars": 396,
"preview": "package com.wj.android.todo;\r\n\r\nimport org.junit.Test;\r\n\r\nimport static org.junit.Assert.*;\r\n\r\n/**\r\n * Example local uni"
},
{
"path": "build.gradle",
"chars": 617,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\r\n\r\nbuildscript {\r\n "
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 238,
"preview": "#Tue Aug 07 10:46:04 CST 2018\r\ndistributionBase=GRADLE_USER_HOME\r\ndistributionPath=wrapper/dists\r\nzipStoreBase=GRADLE_US"
},
{
"path": "gradle.properties",
"chars": 851,
"preview": "# Project-wide Gradle settings.\r\n# IDE (e.g. Android Studio) users:\r\n# Gradle settings configured through the IDE *will "
},
{
"path": "gradlew",
"chars": 5468,
"preview": "#!/usr/bin/env sh\r\n\r\n##############################################################################\r\n##\r\n## Gradle star"
},
{
"path": "gradlew.bat",
"chars": 2260,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "settings.gradle",
"chars": 36,
"preview": "include ':app'\r\ninclude ':wj-http'\r\n"
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the wjwang0914/wj-todo-wanandroid GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 67 files (198.9 KB), approximately 51.5k tokens, and a symbol index with 346 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.