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