From 313c1ad8510711357827ce879b449dcb770bce9a Mon Sep 17 00:00:00 2001
From: Administrator <123>
Date: Tue, 02 Nov 2021 09:40:19 +0000
Subject: [PATCH] titlebar base 登录页UI

---
 app/src/main/java/com/duqing/missions/base/BaseActivity.java                           |   70 +++
 app/src/main/res/values/styles.xml                                                     |   12 
 app/src/main/res/values/themes.xml                                                     |   12 
 app/src/main/java/com/duqing/missions/ui/login/view/LoggedInUserView.java              |   17 
 app/src/main/java/com/duqing/missions/ui/login/view/LoginFormState.java                |   40 ++
 app/src/main/res/mipmap-hdpi/icon_white_back.png                                       |    0 
 app/src/main/res/values/colors.xml                                                     |    5 
 app/src/main/res/values-night/themes.xml                                               |   10 
 app/src/main/java/com/duqing/missions/ui/main/home/HomeFragment.java                   |    6 
 app/src/main/java/com/duqing/missions/ui/login/view/LoginViewModel.java                |   89 ++++
 app/src/main/java/com/duqing/missions/MainActivity.java                                |   11 
 app/src/main/java/com/duqing/missions/ui/login/data/LoginDataSource.java               |   40 ++
 app/src/main/res/mipmap-xhdpi/icon_white_back.png                                      |    0 
 app/src/main/res/mipmap-xxhdpi/icon_delete.png                                         |    0 
 app/src/main/java/com/duqing/missions/base/BaseTitleBarActivity.java                   |   57 +++
 app/src/main/java/com/duqing/missions/ui/login/view/LoginResult.java                   |   31 +
 app/src/main/java/com/duqing/missions/ui/login/data/LoginRepository.java               |   53 ++
 app/src/main/res/values/strings.xml                                                    |    8 
 app/src/main/res/mipmap-xxhdpi/icon_back_black.png                                     |    0 
 app/src/main/res/mipmap-xxhdpi/icon_white_back.png                                     |    0 
 app/src/main/AndroidManifest.xml                                                       |    3 
 app/src/main/res/mipmap-xhdpi/icon_back_black.png                                      |    0 
 app/src/main/java/com/duqing/missions/ui/login/view/LoginViewModelFactory.java         |   27 +
 app/src/main/java/com/duqing/missions/ui/login/data/model/LoggedInUser.java            |   23 +
 app/src/main/java/com/duqing/missions/base/BaseFragment.java                           |    8 
 app/src/main/res/mipmap-hdpi/icon_back_black.png                                       |    0 
 app/src/main/java/com/duqing/missions/widgets/ClearEditText.java                       |  194 ++++++++++
 app/src/main/java/com/duqing/missions/ui/main/notifications/NotificationsFragment.java |   25 -
 app/src/main/res/mipmap-xxxhdpi/icon_back_black.png                                    |    0 
 app/src/main/java/com/duqing/missions/ui/login/view/LoginActivity.java                 |  153 ++++++++
 app/src/main/java/com/duqing/missions/ui/main/dashboard/DashboardFragment.java         |    4 
 app/src/main/res/layout/activity_login.xml                                             |  161 ++++++++
 app/src/main/res/mipmap-xxxhdpi/icon_white_back.png                                    |    0 
 app/src/main/java/com/duqing/missions/ui/main/home/HomeViewModel.java                  |    2 
 34 files changed, 1,006 insertions(+), 55 deletions(-)

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 468dd29..f2b6215 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="ProtectedPermissions"
     package="com.duqing.missions" >
 
     <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
@@ -20,6 +22,7 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:name=".ui.login.view.LoginActivity" />
     </application>
 
 </manifest>
\ No newline at end of file
diff --git a/app/src/main/java/com/duqing/missions/MainActivity.java b/app/src/main/java/com/duqing/missions/MainActivity.java
index 6c2f39a..d0ce711 100644
--- a/app/src/main/java/com/duqing/missions/MainActivity.java
+++ b/app/src/main/java/com/duqing/missions/MainActivity.java
@@ -1,10 +1,7 @@
 package com.duqing.missions;
 
-import android.os.Bundle;
-
 import androidx.navigation.NavController;
 import androidx.navigation.Navigation;
-import androidx.navigation.ui.AppBarConfiguration;
 import androidx.navigation.ui.NavigationUI;
 
 import com.duqing.missions.base.BaseActivity;
@@ -14,16 +11,10 @@
 
 
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
+    public void initViews() {
         // Passing each menu ID as a set of Ids because each
         // menu should be considered as top level destinations.
-        AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
-                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications)
-                .build();
         NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
-        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
         NavigationUI.setupWithNavController(binding.navView, navController);
     }
 
diff --git a/app/src/main/java/com/duqing/missions/base/BaseActivity.java b/app/src/main/java/com/duqing/missions/base/BaseActivity.java
index ef14ad3..b756705 100644
--- a/app/src/main/java/com/duqing/missions/base/BaseActivity.java
+++ b/app/src/main/java/com/duqing/missions/base/BaseActivity.java
@@ -14,12 +14,16 @@
 import android.provider.Settings;
 import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
 import android.widget.Toast;
 
 import androidx.annotation.ColorRes;
 import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.core.content.FileProvider;
 import androidx.viewbinding.ViewBinding;
@@ -114,7 +118,10 @@
         } catch (Exception e) {
         }
         TAG = this.getClass().getSimpleName();
+        initViews();
     }
+
+    public abstract void initViews();
 
 
     public void setStatusBarTransparent(boolean isBlack){
@@ -164,6 +171,62 @@
 
     }
 
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {  //把操作放在用户点击的时候
+            View v = getCurrentFocus();      //得到当前页面的焦点,ps:有输入框的页面焦点一般会被输入框占据
+            if (isShouldHideKeyboard(v, ev)) { //判断用户点击的是否是输入框以外的区域
+                hideSoftKeyboard ();   //收起键盘
+            }
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
+    /**
+     * 根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘,因为当用户点击EditText时则不能隐藏
+     *
+     * @param v
+     * @param event
+     * @return
+     */
+    private boolean isShouldHideKeyboard(View v, MotionEvent event) {
+        if (v != null && (v instanceof EditText)) {  //判断得到的焦点控件是否包含EditText
+            int[] l = {0, 0};
+            v.getLocationInWindow(l);
+            int left = l[0],    //得到输入框在屏幕中上下左右的位置
+                    top = l[1],
+                    bottom = top + v.getHeight(),
+                    right = left + v.getWidth();
+            if (event.getX() > left && event.getX() < right
+                    && event.getY() > top && event.getY() < bottom) {
+                // 点击位置如果是EditText的区域,忽略它,不收起键盘。
+                return false;
+            } else {
+                return true;
+            }
+        }
+        // 如果焦点不是EditText则忽略
+        return false;
+    }
+    /**
+     * 判断软键盘输入法是否弹出
+     */
+    public boolean isKeyboardVisbility(Context context, View v) {
+        InputMethodManager imm = (InputMethodManager) context.getSystemService(context.INPUT_METHOD_SERVICE);
+        if (imm.hideSoftInputFromWindow(v.getWindowToken(), 0)) {
+            imm.showSoftInput(v, 0);
+            return true;//键盘显示中
+        } else {
+            return false;//键盘未显示
+        }
+    }
+    protected void hideSoftKeyboard() {
+        if (getWindow().getAttributes().softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) {
+            if (getCurrentFocus() != null)
+                ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),
+                        InputMethodManager.HIDE_NOT_ALWAYS);
+        }
+    }
     /**
      * 状态栏高度
      * @return
@@ -193,6 +256,13 @@
             //quitApp();
         }
     }
+    public void showToast(String message){
+        Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
+    }
+
+    public void showToast(@StringRes int msg){
+        showToast(getString(msg));
+    }
     ApkUpGradeResult.AppInfo apkUpGrade;
     ProgressDialog progressDialog ;
 
diff --git a/app/src/main/java/com/duqing/missions/base/BaseFragment.java b/app/src/main/java/com/duqing/missions/base/BaseFragment.java
index c4eccf8..099381b 100644
--- a/app/src/main/java/com/duqing/missions/base/BaseFragment.java
+++ b/app/src/main/java/com/duqing/missions/base/BaseFragment.java
@@ -15,16 +15,16 @@
 /**
  * Created by Administrator on 2021/10/28 0028.
  */
-public abstract class BaseFragment<A extends BaseActivity,B extends ViewBinding> extends Fragment {
+public abstract class BaseFragment<B extends ViewBinding> extends Fragment {
 
-    protected  A activity;
+    protected  BaseActivity activity;
     protected  B binding;
 
     @Nullable
     @Override
     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
         // get genericity "B"
-        Class<B> entityClass = (Class<B>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
+        Class<B> entityClass = (Class<B>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
         try {
             Method method = entityClass.getMethod("inflate", LayoutInflater.class,ViewGroup.class,boolean.class);//get method from name "inflate";
             binding = (B) method.invoke(entityClass,inflater,container,false);//execute method to create a objct of viewbind;
@@ -37,7 +37,7 @@
     @Override
     public void onActivityCreated(@Nullable Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
-        activity = (A) getActivity();
+        activity = (BaseActivity) getActivity();
         initViews();
     }
 
diff --git a/app/src/main/java/com/duqing/missions/base/BaseTitleBarActivity.java b/app/src/main/java/com/duqing/missions/base/BaseTitleBarActivity.java
new file mode 100644
index 0000000..8a56999
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/base/BaseTitleBarActivity.java
@@ -0,0 +1,57 @@
+package com.duqing.missions.base;
+
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.viewbinding.ViewBinding;
+
+import com.duqing.missions.widgets.TitleBarView;
+
+/**
+ * Created by Administrator on 2021/11/2 0002.
+ */
+public abstract class BaseTitleBarActivity<B extends ViewBinding> extends BaseActivity<B> {
+    TitleBarView titleBarView;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        try {
+            titleBarView = (TitleBarView) binding.getClass().getDeclaredField("titleBar").get(binding);
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+        }
+    }
+
+    protected void setTitle(String text){
+        titleBarView.setTitleText(text);
+    }
+    
+    protected void onTitleLeftClick(){
+        onBackKeyDown();
+    }
+
+    protected void setTitleRight(String text){
+        titleBarView.setRightText(text);
+        titleBarView.setRightDra(null);
+    }
+
+    protected void setTitleRight(Drawable drawable){
+        titleBarView.setRightText(null);
+        titleBarView.setRightDra(drawable);
+    }
+
+    @Override
+    public void setStatusBarTransparent(boolean isBlack) {
+        super.setStatusBarTransparent(isBlack);
+        final ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) titleBarView.getLayoutParams();
+        layoutParams.topMargin = layoutParams.topMargin+getStatusBarHeight();
+        titleBarView.setLayoutParams(layoutParams);
+
+    }
+}
diff --git a/app/src/main/java/com/duqing/missions/ui/login/data/LoginDataSource.java b/app/src/main/java/com/duqing/missions/ui/login/data/LoginDataSource.java
new file mode 100644
index 0000000..1fb4877
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/ui/login/data/LoginDataSource.java
@@ -0,0 +1,40 @@
+package com.duqing.missions.ui.login.data;
+
+
+import com.duqing.missions.ui.login.data.model.LoggedInUser;
+
+import io.reactivex.Observable;
+import io.reactivex.ObservableEmitter;
+import io.reactivex.ObservableOnSubscribe;
+
+/**
+ * Class that handles authentication w/ login credentials and retrieves user information.
+ */
+public class LoginDataSource {
+
+    public Observable<LoggedInUser> login(String username, String password) {
+        final Observable<LoggedInUser> observable = Observable.create(new ObservableOnSubscribe<LoggedInUser>() {
+            @Override
+            public void subscribe(ObservableEmitter<LoggedInUser> e) throws Exception {
+                LoggedInUser fakeUser = new LoggedInUser( java.util.UUID.randomUUID().toString(), "Jane Doe");
+                e.onNext(fakeUser);
+            }
+        });
+        return  observable;
+    }
+
+    public Observable<LoggedInUser> loginByCode(String phone, String verifyCode){
+        final Observable<LoggedInUser> observable = Observable.create(new ObservableOnSubscribe<LoggedInUser>() {
+            @Override
+            public void subscribe(ObservableEmitter<LoggedInUser> e) throws Exception {
+                LoggedInUser fakeUser = new LoggedInUser( java.util.UUID.randomUUID().toString(), "Jane Doe");
+                e.onNext(fakeUser);
+            }
+        });
+        return  observable;
+    }
+
+    public void logout() {
+        // TODO: revoke authentication
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/duqing/missions/ui/login/data/LoginRepository.java b/app/src/main/java/com/duqing/missions/ui/login/data/LoginRepository.java
new file mode 100644
index 0000000..3767c5c
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/ui/login/data/LoginRepository.java
@@ -0,0 +1,53 @@
+package com.duqing.missions.ui.login.data;
+
+
+import com.duqing.missions.ui.login.data.model.LoggedInUser;
+
+import io.reactivex.Observable;
+
+/**
+ * Class that requests authentication and user information from the remote data source and
+ * maintains an in-memory cache of login status and user credentials information.
+ */
+public class LoginRepository {
+
+    private static volatile LoginRepository instance;
+
+    private LoginDataSource dataSource;
+
+    // If user credentials will be cached in local storage, it is recommended it be encrypted
+    // @see https://developer.android.com/training/articles/keystore
+    private LoggedInUser user = null;
+
+    // private constructor : singleton access
+    private LoginRepository(LoginDataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    public static LoginRepository getInstance(LoginDataSource dataSource) {
+        if (instance == null) {
+            instance = new LoginRepository(dataSource);
+        }
+        return instance;
+    }
+
+    public boolean isLoggedIn() {
+        return user != null;
+    }
+
+    public void logout() {
+        user = null;
+        dataSource.logout();
+    }
+
+    private void setLoggedInUser(LoggedInUser user) {
+        this.user = user;
+        // If user credentials will be cached in local storage, it is recommended it be encrypted
+        // @see https://developer.android.com/training/articles/keystore
+    }
+
+    public Observable<LoggedInUser> login(String username, String password) {
+        // handle login
+        return dataSource.login(username, password);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/duqing/missions/ui/login/data/model/LoggedInUser.java b/app/src/main/java/com/duqing/missions/ui/login/data/model/LoggedInUser.java
new file mode 100644
index 0000000..35c7e49
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/ui/login/data/model/LoggedInUser.java
@@ -0,0 +1,23 @@
+package com.duqing.missions.ui.login.data.model;
+
+/**
+ * Data class that captures user information for logged in users retrieved from LoginRepository
+ */
+public class LoggedInUser {
+
+    private String userId;
+    private String displayName;
+
+    public LoggedInUser(String userId, String displayName) {
+        this.userId = userId;
+        this.displayName = displayName;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/duqing/missions/ui/login/view/LoggedInUserView.java b/app/src/main/java/com/duqing/missions/ui/login/view/LoggedInUserView.java
new file mode 100644
index 0000000..f0dea47
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/ui/login/view/LoggedInUserView.java
@@ -0,0 +1,17 @@
+package com.duqing.missions.ui.login.view;
+
+/**
+ * Class exposing authenticated user details to the UI.
+ */
+class LoggedInUserView {
+    private String displayName;
+    //... other data fields that may be accessible to the UI
+
+    LoggedInUserView(String displayName) {
+        this.displayName = displayName;
+    }
+
+    String getDisplayName() {
+        return displayName;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/duqing/missions/ui/login/view/LoginActivity.java b/app/src/main/java/com/duqing/missions/ui/login/view/LoginActivity.java
new file mode 100644
index 0000000..59eeae1
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/ui/login/view/LoginActivity.java
@@ -0,0 +1,153 @@
+package com.duqing.missions.ui.login.view;
+
+import android.app.Activity;
+import android.graphics.Typeface;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.duqing.missions.R;
+import com.duqing.missions.base.BaseTitleBarActivity;
+import com.duqing.missions.databinding.ActivityLoginBinding;
+
+
+public class LoginActivity extends BaseTitleBarActivity<ActivityLoginBinding> {
+
+    private LoginViewModel loginViewModel;
+
+    @Override
+    public void initViews() {
+
+        loginViewModel = new ViewModelProvider(this, new LoginViewModelFactory()).get(LoginViewModel.class);
+
+        final EditText phoneEdit = binding.editPhone;
+        final EditText passwordEditText = binding.editPassword;
+        final Button loginButton = binding.login;
+
+        loginViewModel.getLoginFormState().observe(this, new Observer<LoginFormState>() {
+            @Override
+            public void onChanged(@Nullable LoginFormState loginFormState) {
+                if (loginFormState == null) {
+                    return;
+                }
+                loginButton.setEnabled(loginFormState.isDataValid());
+                if (loginFormState.getUsernameError() != null) {
+                    phoneEdit.setError(getString(loginFormState.getUsernameError()));
+                }
+                if (loginFormState.getPasswordError() != null) {
+                    passwordEditText.setError(getString(loginFormState.getPasswordError()));
+                }
+            }
+        });
+
+        loginViewModel.getLoginResult().observe(this, new Observer<LoginResult>() {
+            @Override
+            public void onChanged(@Nullable LoginResult loginResult) {
+                if (loginResult == null) {
+                    return;
+                }
+                if (loginResult.getError() != null) {
+                    showLoginFailed(loginResult.getError());
+                }
+                if (loginResult.getSuccess() != null) {
+                    updateUiWithUser(loginResult.getSuccess());
+                }
+                setResult(Activity.RESULT_OK);
+
+                //Complete and destroy login activity once successful
+                finish();
+            }
+        });
+
+        binding.txtPasswordTitle.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                binding.containerVerify.setVisibility(View.GONE);
+                binding.containerPassword.setVisibility(View.VISIBLE);
+                checkedStyle(binding.txtPasswordTitle);
+                unCheckStyle(binding.txtVerifyTitle);
+            }
+        });
+
+        binding.txtVerifyTitle.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                binding.containerPassword.setVisibility(View.GONE);
+                binding.containerVerify.setVisibility(View.VISIBLE);
+                checkedStyle(binding.txtVerifyTitle);
+                unCheckStyle(binding.txtPasswordTitle);
+            }
+        });
+
+        TextWatcher afterTextChangedListener = new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+                // ignore
+            }
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {
+                // ignore
+            }
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                loginViewModel.loginDataChanged(phoneEdit.getText().toString(),
+                        passwordEditText.getText().toString());
+            }
+        };
+        phoneEdit.addTextChangedListener(afterTextChangedListener);
+        passwordEditText.addTextChangedListener(afterTextChangedListener);
+        passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+
+            @Override
+            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+                if (actionId == EditorInfo.IME_ACTION_DONE) {
+                    loginViewModel.login(phoneEdit.getText().toString(),
+                            passwordEditText.getText().toString());
+                }
+                return false;
+            }
+        });
+
+        loginButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                loginViewModel.login(phoneEdit.getText().toString(),
+                        passwordEditText.getText().toString());
+            }
+        });
+    }
+
+    public void checkedStyle(TextView textView){
+        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,17);
+        textView.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
+    }
+
+    public void unCheckStyle(TextView textView){
+        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,14);
+        textView.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL));
+    }
+
+    private void updateUiWithUser(LoggedInUserView model) {
+        String welcome = getString(R.string.welcome) + model.getDisplayName();
+        // TODO : initiate successful logged in experience
+        Toast.makeText(getApplicationContext(), welcome, Toast.LENGTH_LONG).show();
+    }
+
+    private void showLoginFailed(@StringRes Integer errorString) {
+        Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show();
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/duqing/missions/ui/login/view/LoginFormState.java b/app/src/main/java/com/duqing/missions/ui/login/view/LoginFormState.java
new file mode 100644
index 0000000..9c77e3f
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/ui/login/view/LoginFormState.java
@@ -0,0 +1,40 @@
+package com.duqing.missions.ui.login.view;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Data validation state of the login form.
+ */
+class LoginFormState {
+    @Nullable
+    private Integer usernameError;
+    @Nullable
+    private Integer passwordError;
+    private boolean isDataValid;
+
+    LoginFormState(@Nullable Integer usernameError, @Nullable Integer passwordError) {
+        this.usernameError = usernameError;
+        this.passwordError = passwordError;
+        this.isDataValid = false;
+    }
+
+    LoginFormState(boolean isDataValid) {
+        this.usernameError = null;
+        this.passwordError = null;
+        this.isDataValid = isDataValid;
+    }
+
+    @Nullable
+    Integer getUsernameError() {
+        return usernameError;
+    }
+
+    @Nullable
+    Integer getPasswordError() {
+        return passwordError;
+    }
+
+    boolean isDataValid() {
+        return isDataValid;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/duqing/missions/ui/login/view/LoginResult.java b/app/src/main/java/com/duqing/missions/ui/login/view/LoginResult.java
new file mode 100644
index 0000000..64bd2f4
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/ui/login/view/LoginResult.java
@@ -0,0 +1,31 @@
+package com.duqing.missions.ui.login.view;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Authentication result : success (user details) or error message.
+ */
+class LoginResult {
+    @Nullable
+    private LoggedInUserView success;
+    @Nullable
+    private Integer error;
+
+    LoginResult(@Nullable Integer error) {
+        this.error = error;
+    }
+
+    LoginResult(@Nullable LoggedInUserView success) {
+        this.success = success;
+    }
+
+    @Nullable
+    LoggedInUserView getSuccess() {
+        return success;
+    }
+
+    @Nullable
+    Integer getError() {
+        return error;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/duqing/missions/ui/login/view/LoginViewModel.java b/app/src/main/java/com/duqing/missions/ui/login/view/LoginViewModel.java
new file mode 100644
index 0000000..63f177a
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/ui/login/view/LoginViewModel.java
@@ -0,0 +1,89 @@
+package com.duqing.missions.ui.login.view;
+
+import android.util.Patterns;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+import com.duqing.missions.R;
+import com.duqing.missions.ui.login.data.LoginRepository;
+import com.duqing.missions.ui.login.data.model.LoggedInUser;
+
+import io.reactivex.Observable;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.functions.Consumer;
+import io.reactivex.observers.DisposableObserver;
+
+public class LoginViewModel extends ViewModel {
+
+    private MutableLiveData<LoginFormState> loginFormState = new MutableLiveData<>();
+    private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>();
+    private LoginRepository loginRepository;
+
+    LoginViewModel(LoginRepository loginRepository) {
+        this.loginRepository = loginRepository;
+    }
+
+    LiveData<LoginFormState> getLoginFormState() {
+        return loginFormState;
+    }
+
+    LiveData<LoginResult> getLoginResult() {
+        return loginResult;
+    }
+
+    public void login(String username, String password) {
+        // can be launched in a separate asynchronous job
+        Observable<LoggedInUser> result = loginRepository.login(username, password);
+        result.doOnSubscribe(new Consumer<Disposable>() {
+            @Override
+            public void accept(Disposable disposable) throws Exception {
+
+            }
+        }).subscribe(new DisposableObserver<LoggedInUser>(){
+
+            @Override
+            public void onNext(LoggedInUser value) {
+                loginResult.setValue(new LoginResult((new LoggedInUserView(value.getDisplayName()))));
+            }
+
+            @Override
+            public void onError(Throwable e) {
+                loginResult.setValue(new LoginResult(R.string.login_failed));
+            }
+
+            @Override
+            public void onComplete() {
+
+            }
+        });
+    }
+
+    public void loginDataChanged(String username, String password) {
+        if (!isUserNameValid(username)) {
+            loginFormState.setValue(new LoginFormState(R.string.invalid_username, null));
+        } else if (!isPasswordValid(password)) {
+            loginFormState.setValue(new LoginFormState(null, R.string.invalid_password));
+        } else {
+            loginFormState.setValue(new LoginFormState(true));
+        }
+    }
+
+    // A placeholder username validation check
+    private boolean isUserNameValid(String username) {
+        if (username == null) {
+            return false;
+        }
+        if (username.contains("@")) {
+            return Patterns.EMAIL_ADDRESS.matcher(username).matches();
+        } else {
+            return !username.trim().isEmpty();
+        }
+    }
+
+    // A placeholder password validation check
+    private boolean isPasswordValid(String password) {
+        return password != null && password.trim().length() > 5;
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/duqing/missions/ui/login/view/LoginViewModelFactory.java b/app/src/main/java/com/duqing/missions/ui/login/view/LoginViewModelFactory.java
new file mode 100644
index 0000000..c7ce3ad
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/ui/login/view/LoginViewModelFactory.java
@@ -0,0 +1,27 @@
+package com.duqing.missions.ui.login.view;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.duqing.missions.ui.login.data.LoginDataSource;
+import com.duqing.missions.ui.login.data.LoginRepository;
+
+
+/**
+ * ViewModel provider factory to instantiate LoginViewModel.
+ * Required given LoginViewModel has a non-empty constructor
+ */
+public class LoginViewModelFactory implements ViewModelProvider.Factory {
+
+    @NonNull
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+        if (modelClass.isAssignableFrom(LoginViewModel.class)) {
+            return (T) new LoginViewModel(LoginRepository.getInstance(new LoginDataSource()));
+        } else {
+            throw new IllegalArgumentException("Unknown ViewModel class");
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/duqing/missions/ui/main/dashboard/DashboardFragment.java b/app/src/main/java/com/duqing/missions/ui/main/dashboard/DashboardFragment.java
index d0da00b..0f03177 100644
--- a/app/src/main/java/com/duqing/missions/ui/main/dashboard/DashboardFragment.java
+++ b/app/src/main/java/com/duqing/missions/ui/main/dashboard/DashboardFragment.java
@@ -4,14 +4,12 @@
 import androidx.lifecycle.Observer;
 import androidx.lifecycle.ViewModelProvider;
 
-import com.duqing.missions.MainActivity;
 import com.duqing.missions.base.BaseFragment;
 import com.duqing.missions.databinding.FragmentDashboardBinding;
 
-public class DashboardFragment extends BaseFragment<MainActivity,FragmentDashboardBinding> {
+public class DashboardFragment extends BaseFragment<FragmentDashboardBinding> {
 
     private DashboardViewModel dashboardViewModel;
-    private FragmentDashboardBinding binding;
 
     @Override
     public void initViews() {
diff --git a/app/src/main/java/com/duqing/missions/ui/main/home/HomeFragment.java b/app/src/main/java/com/duqing/missions/ui/main/home/HomeFragment.java
index d1fc918..f4a1f9f 100644
--- a/app/src/main/java/com/duqing/missions/ui/main/home/HomeFragment.java
+++ b/app/src/main/java/com/duqing/missions/ui/main/home/HomeFragment.java
@@ -1,5 +1,6 @@
 package com.duqing.missions.ui.main.home;
 
+import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -16,9 +17,9 @@
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.duqing.missions.MainActivity;
 import com.duqing.missions.base.BaseFragment;
 import com.duqing.missions.databinding.FragmentHomeBinding;
+import com.duqing.missions.ui.login.view.LoginActivity;
 import com.duqing.missions.ui.main.home.adapter.MissionAdapter;
 import com.duqing.missions.ui.main.home.adapter.MissionTopAdapter;
 import com.duqing.missions.ui.main.home.model.MissionDesc;
@@ -30,7 +31,7 @@
 
 import java.util.List;
 
-public class HomeFragment extends BaseFragment<MainActivity,FragmentHomeBinding> {
+public class HomeFragment extends BaseFragment<FragmentHomeBinding> {
 
     private HomeViewModel homeViewModel;
     final String TAG = "HomeFragment";
@@ -39,6 +40,7 @@
     @Override
     public void initViews() {
         homeViewModel = new ViewModelProvider(this).get(HomeViewModel.class);
+        binding.imgSearch.setOnClickListener(v -> startActivity(new Intent(getContext(), LoginActivity.class)));
         final SmartRefreshLayout smartRefresh = binding.smartRefresh;
         smartRefresh.setRefreshHeader(new ClassicsHeader(getContext()));
         smartRefresh.setRefreshFooter(new ClassicsFooter(getContext()));
diff --git a/app/src/main/java/com/duqing/missions/ui/main/home/HomeViewModel.java b/app/src/main/java/com/duqing/missions/ui/main/home/HomeViewModel.java
index be17225..eb69ae9 100644
--- a/app/src/main/java/com/duqing/missions/ui/main/home/HomeViewModel.java
+++ b/app/src/main/java/com/duqing/missions/ui/main/home/HomeViewModel.java
@@ -47,7 +47,7 @@
     }
 
     public void onLoadMore(){
-        List<MissionDesc> list = recommendMissions.getValue();
+        List<MissionDesc> list = recommendMissions.getValue() == null? new ArrayList<>():recommendMissions.getValue() ;
         list.add(new MissionDesc());
         list.add(new MissionDesc());
         list.add(new MissionDesc());
diff --git a/app/src/main/java/com/duqing/missions/ui/main/notifications/NotificationsFragment.java b/app/src/main/java/com/duqing/missions/ui/main/notifications/NotificationsFragment.java
index e94c5af..38fa92a 100644
--- a/app/src/main/java/com/duqing/missions/ui/main/notifications/NotificationsFragment.java
+++ b/app/src/main/java/com/duqing/missions/ui/main/notifications/NotificationsFragment.java
@@ -1,39 +1,24 @@
 package com.duqing.missions.ui.main.notifications;
 
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
 import androidx.lifecycle.Observer;
 import androidx.lifecycle.ViewModelProvider;
 
+import com.duqing.missions.base.BaseFragment;
 import com.duqing.missions.databinding.FragmentNotificationsBinding;
 
-public class NotificationsFragment extends Fragment {
+public class NotificationsFragment extends BaseFragment<FragmentNotificationsBinding> {
 
     private NotificationsViewModel notificationsViewModel;
-    private FragmentNotificationsBinding binding;
 
-    public View onCreateView(@NonNull LayoutInflater inflater,  ViewGroup container, Bundle savedInstanceState) {
+
+    @Override
+    public void initViews() {
         notificationsViewModel = new ViewModelProvider(this).get(NotificationsViewModel.class);
-        binding = FragmentNotificationsBinding.inflate(inflater, container, false);
-        View root = binding.getRoot();
         notificationsViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
             @Override
             public void onChanged(@Nullable String s) {
             }
         });
-        return root;
     }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        binding = null;
-    }
-
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/duqing/missions/widgets/ClearEditText.java b/app/src/main/java/com/duqing/missions/widgets/ClearEditText.java
new file mode 100644
index 0000000..c0557f4
--- /dev/null
+++ b/app/src/main/java/com/duqing/missions/widgets/ClearEditText.java
@@ -0,0 +1,194 @@
+package com.duqing.missions.widgets;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.CycleInterpolator;
+import android.view.animation.TranslateAnimation;
+import android.widget.EditText;
+
+import com.duqing.missions.R;
+
+
+/**
+ * My father is Object, ites purpose of
+ *
+ * @purpose Created by Runt (qingingrunt2010@qq.com) on 2020-2-25.
+ */
+@SuppressLint("AppCompatCustomView")
+public class ClearEditText extends EditText implements View.OnFocusChangeListener,
+        TextWatcher {
+    /**
+     * 删除按钮的引用
+     */
+    private Drawable mClearDrawable;
+    /**
+     * 控件是否有焦点
+     */
+    private boolean hasFoucs;
+
+    /**
+     * 是否可清除内容
+     */
+    private boolean isClearable = true;
+
+    /**
+     * @param context
+     * @Description TODO
+     */
+    public ClearEditText(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * @param context
+     * @param attrs
+     * @Description TODO
+     */
+    public ClearEditText(Context context, AttributeSet attrs) {
+        // 这里构造方法也很重要,不加这个很多属性不能再XML里面定义
+        this(context, attrs, android.R.attr.editTextStyle);
+    }
+
+    /**
+     * @param context
+     * @param attrs
+     * @param defStyle
+     * @Description TODO
+     */
+    public ClearEditText(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    private void init() {
+        // 获取EditText的DrawableRight,假如没有设置我们就使用默认的图片
+        mClearDrawable = getCompoundDrawables()[2];
+        if (mClearDrawable == null) {
+            // throw new
+            // NullPointerException("You can add drawableRight attribute in XML");
+            mClearDrawable = getResources().getDrawable(R.mipmap.icon_delete);
+        }
+
+        mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(),
+                mClearDrawable.getIntrinsicHeight());
+
+        // 默认设置隐藏图标
+        setClearIconVisible(false);
+        // 设置焦点改变的监听
+        setOnFocusChangeListener(this);
+        // 设置输入框里面内容发生改变的监听
+        addTextChangedListener(this);
+    }
+
+    /**
+     * 因为我们不能直接给EditText设置点击事件,所以我们用记住我们按下的位置来模拟点击事件 当我们按下的位置 在 EditText的宽度 -
+     * 图标到控件右边的间距 - 图标的宽度 和 EditText的宽度 - 图标到控件右边的间距之间我们就算点击了图标,竖直方向就没有考虑
+     */
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            if (getCompoundDrawables()[2] != null) {
+
+                boolean touchable = event.getX() > (getWidth() - getTotalPaddingRight())
+                        && (event.getX() < ((getWidth() - getPaddingRight())));
+
+                if (touchable && isClearable) {
+                    this.setText("");
+                }
+            }
+        }
+
+        return super.onTouchEvent(event);
+    }
+
+    /**
+     * 当ClearEditText焦点发生变化的时候,判断里面字符串长度设置清除图标的显示与隐藏
+     */
+    @Override
+    public void onFocusChange(View v, boolean hasFocus) {
+        this.hasFoucs = hasFocus;
+        if (hasFocus) {
+            setClearIconVisible(getText().toString().length() > 0);
+        } else {
+            setClearIconVisible(false);
+        }
+    }
+
+    /**
+     * 设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去
+     *
+     * @param visible
+     */
+    public void setClearIconVisible(boolean visible) {
+        Drawable right = visible ? mClearDrawable : null;
+        setCompoundDrawables(getCompoundDrawables()[0],
+                getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
+    }
+
+    /**
+     * 当输入框里面内容发生变化的时候回调的方法
+     */
+    @Override
+    public void onTextChanged(CharSequence s, int start, int count, int after) {
+        if (hasFoucs) {
+            setClearIconVisible(s.toString().length() > 0);
+        }
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count,
+                                  int after) {
+
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+
+    }
+
+    /**
+     * 设置晃动动画
+     */
+    public void startShakeAnimation() {
+        this.startAnimation(shakeAnimation(5));
+    }
+
+    /**
+     * 晃动动画
+     *
+     * @param counts 1秒钟晃动多少下
+     * @return
+     */
+    public static Animation shakeAnimation(int counts) {
+        Animation translateAnimation = new TranslateAnimation(0, 10, 0, 0);
+        translateAnimation.setInterpolator(new CycleInterpolator(counts));
+        translateAnimation.setDuration(1000);
+        return translateAnimation;
+    }
+
+    /**
+     * 设置是否可清除
+     *
+     * @param clearable
+     */
+    public void setClearable(boolean clearable) {
+        isClearable = clearable;
+    }
+
+    /**
+     * 获取是否可清除
+     *
+     * @return
+     */
+    public boolean getClearable() {
+        return isClearable;
+    }
+
+}
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 0000000..92e5134
--- /dev/null
+++ b/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    tools:context=".ui.login.view.LoginActivity">
+
+    <com.duqing.missions.widgets.TitleBarView
+        android:id="@+id/titleBar"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"
+        android:layout_marginBottom="150dp"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:leftDrawable="@mipmap/icon_back_black"/>
+
+
+    <TextView
+        android:id="@+id/txt_password_title"
+        android:layout_width="wrap_content"
+        android:layout_height="40dp"
+        android:layout_marginTop="20dp"
+        android:gravity="center"
+        android:text="密码登录"
+        android:textSize="17sp"
+        android:textStyle="bold"
+        app:layout_constraintTop_toBottomOf="@+id/titleBar"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toLeftOf="@id/txt_verify_title"/>
+
+
+    <TextView
+        android:id="@+id/txt_verify_title"
+        android:layout_width="wrap_content"
+        android:layout_height="40dp"
+        android:layout_marginTop="20dp"
+        android:text="短信登录"
+        android:gravity="center"
+        app:layout_constraintTop_toBottomOf="@+id/titleBar"
+        app:layout_constraintLeft_toRightOf="@id/txt_password_title"
+        app:layout_constraintRight_toRightOf="parent"/>
+
+    <com.duqing.missions.widgets.ClearEditText
+        android:id="@+id/edit_phone"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:hint="请输入手机号"
+        android:inputType="phone"
+        android:maxLength="11"
+        android:selectAllOnFocus="true"
+        android:layout_marginTop="26dp"
+        app:layout_constraintTop_toBottomOf="@id/txt_password_title"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent" />
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/container_password"
+        android:layout_width="match_parent"
+        android:layout_height="180dp"
+        app:layout_constraintTop_toBottomOf="@id/edit_phone"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent">
+
+        <com.duqing.missions.widgets.ClearEditText
+            android:id="@+id/edit_password"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:hint="请输入密码"
+            android:imeActionLabel="@string/action_sign_in_short"
+            android:imeOptions="actionDone"
+            android:inputType="textPassword"
+            android:selectAllOnFocus="true"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"/>
+        <TextView
+            android:id="@+id/text_register"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            app:layout_constraintLeft_toLeftOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/edit_password"
+            app:layout_constraintRight_toLeftOf="@id/text_forgot"
+            android:paddingTop="@dimen/frame_margin_lr"
+            android:paddingLeft="3dp"
+            android:text="注册账号" />
+        <TextView
+            android:id="@+id/text_forgot"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="忘记密码"
+            android:paddingTop="@dimen/frame_margin_lr"
+            android:paddingRight="3dp"
+            app:layout_constraintHorizontal_chainStyle="spread_inside"
+            app:layout_constraintTop_toBottomOf="@id/edit_password"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintLeft_toRightOf="@id/text_register" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/container_verify"
+        android:layout_width="match_parent"
+        android:layout_height="180dp"
+        android:visibility="gone"
+        app:layout_constraintTop_toBottomOf="@id/edit_phone"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent">
+
+
+        <EditText
+            android:id="@+id/edit_verify"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:hint="请输入验证码"
+            android:imeActionLabel="@string/action_sign_in_short"
+            android:imeOptions="actionDone"
+            android:inputType="number"
+            android:selectAllOnFocus="true"
+            android:maxLength="4"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"/>
+        <TextView
+            android:id="@+id/text_verify"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="获取验证码"
+            android:textSize="16sp"
+            android:textColor="@color/deep_sky"
+            android:paddingRight="5dp"
+            app:layout_constraintRight_toRightOf="parent"
+            app:layout_constraintTop_toTopOf="@id/edit_verify"
+            app:layout_constraintBottom_toBottomOf="@id/edit_verify" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <Button
+        android:id="@+id/login"
+        android:layout_width="200dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start"
+        android:layout_marginTop="16dp"
+        android:layout_marginBottom="64dp"
+        android:enabled="false"
+        android:text="登录"
+        android:textColor="@color/white"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.501"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/container_password"
+        app:layout_constraintVertical_bias="0.0"
+        app:layout_goneMarginTop="180dp" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/icon_back_black.png b/app/src/main/res/mipmap-hdpi/icon_back_black.png
new file mode 100644
index 0000000..4139667
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/icon_back_black.png
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/icon_white_back.png b/app/src/main/res/mipmap-hdpi/icon_white_back.png
new file mode 100644
index 0000000..c78eaf6
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/icon_white_back.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/icon_back_black.png b/app/src/main/res/mipmap-xhdpi/icon_back_black.png
new file mode 100644
index 0000000..608b650
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/icon_back_black.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/icon_white_back.png b/app/src/main/res/mipmap-xhdpi/icon_white_back.png
new file mode 100644
index 0000000..8f3faab
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/icon_white_back.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/icon_back_black.png b/app/src/main/res/mipmap-xxhdpi/icon_back_black.png
new file mode 100644
index 0000000..529b555
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/icon_back_black.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/icon_delete.png b/app/src/main/res/mipmap-xxhdpi/icon_delete.png
new file mode 100644
index 0000000..a2fdf07
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/icon_delete.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/icon_white_back.png b/app/src/main/res/mipmap-xxhdpi/icon_white_back.png
new file mode 100644
index 0000000..7d82e4c
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/icon_white_back.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/icon_back_black.png b/app/src/main/res/mipmap-xxxhdpi/icon_back_black.png
new file mode 100644
index 0000000..3e4c72b
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/icon_back_black.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/icon_white_back.png b/app/src/main/res/mipmap-xxxhdpi/icon_white_back.png
new file mode 100644
index 0000000..f982292
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/icon_white_back.png
Binary files differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 9d7a9e1..dea610f 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,13 +1,13 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <!-- Base application theme. -->
-    <style name="Theme.Missions" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+    <style name="Theme.Missions" parent="Theme.MaterialComponents.DayNight.NoActionBar">
         <!-- Primary brand color. -->
-        <item name="colorPrimary">@color/purple_200</item>
-        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorPrimary">@color/sky</item>
+        <item name="colorPrimaryVariant">@color/red</item>
         <item name="colorOnPrimary">@color/black</item>
         <!-- Secondary brand color. -->
-        <item name="colorSecondary">@color/teal_200</item>
-        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorSecondary">@color/black_4</item>
+        <item name="colorSecondaryVariant">@color/black_4</item>
         <item name="colorOnSecondary">@color/black</item>
         <!-- Status bar color. -->
         <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 5707c0a..9d53e55 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,9 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <color name="purple_200">#FFBB86FC</color>
-    <color name="purple_500">#FF6200EE</color>
-    <color name="purple_700">#FF3700B3</color>
-    <color name="teal_200">#FF03DAC5</color>
     <color name="teal_700">#FF018786</color>
     <color name="black">#FF000000</color>
     <color name="black_4">#373737</color>
@@ -12,6 +8,7 @@
     <color name="red">#FF1414</color>
     <color name="gray">#CDCDCD</color>
     <color name="enable">#ECECEC</color>
+    <color name="deep_sky">#4184D6</color>
     <color name="sky">#509CFA</color>
     <color name="gold">#FAD550</color>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0988b12..6bed2fa 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3,4 +3,12 @@
     <string name="title_home">Home</string>
     <string name="title_dashboard">Dashboard</string>
     <string name="title_notifications">Notifications</string>
+    <string name="prompt_email">Email</string>
+    <string name="prompt_password">Password</string>
+    <string name="action_sign_in">Sign in or register</string>
+    <string name="action_sign_in_short">Sign in</string>
+    <string name="welcome">"Welcome !"</string>
+    <string name="invalid_username">Not a valid username</string>
+    <string name="invalid_password">Password must be >5 characters</string>
+    <string name="login_failed">"Login failed"</string>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 4ade4fe..4834c95 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -27,4 +27,16 @@
         <item name="android:textColor">@color/black_4</item>
         <item name="android:layout_weight">1</item>
     </style>
+
+    <declare-styleable name="TitleBarView">
+        <attr name="leftDrawable" format="reference" />
+        <attr name="rightDrawable" format="reference" />
+        <attr name="rightDrawablePadding" format="dimension" />
+        <attr name="titleText" format="string" />
+        <attr name="titleTextSize" format="dimension" />
+        <attr name="titleTextColor" format="color" />
+        <attr name="rightText" format="string" />
+        <attr name="rightTextSize" format="dimension" />
+        <attr name="rightTextColor" format="color" />
+    </declare-styleable>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 5f3f2d5..f4592f1 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,13 +1,13 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <!-- Base application theme. -->
-    <style name="Theme.Missions" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+    <style name="Theme.Missions" parent="Theme.MaterialComponents.Light.NoActionBar">
         <!-- Primary brand color. -->
-        <item name="colorPrimary">@color/purple_500</item>
-        <item name="colorPrimaryVariant">@color/purple_700</item>
-        <item name="colorOnPrimary">@color/white</item>
+        <item name="colorPrimary">@color/sky</item>
+        <item name="colorPrimaryVariant">@color/red</item>
+        <item name="colorOnPrimary">@color/black</item>
         <!-- Secondary brand color. -->
-        <item name="colorSecondary">@color/teal_200</item>
-        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorSecondary">@color/black_4</item>
+        <item name="colorSecondaryVariant">@color/black_4</item>
         <item name="colorOnSecondary">@color/black</item>
         <!-- Status bar color. -->
         <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>

--
Gitblit v1.9.1