mirror of
https://e.coding.net/mcontext/mContext/sdu.git
synced 2024-09-18 02:24:54 +08:00
1.登录/注销功能实现
2.注册页面UI
This commit is contained in:
parent
4ac14b54d9
commit
7ba043fba3
@ -60,6 +60,8 @@ dependencies {
|
||||
implementation 'com.tencent.qcloud:cosxml:5.4.31'
|
||||
//点赞动画
|
||||
implementation 'com.sackcentury:shinebutton:1.0.0'
|
||||
//工具类
|
||||
implementation 'com.blankj:utilcodex:1.28.4'
|
||||
//测试用例
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
|
@ -54,6 +54,7 @@
|
||||
android:name=".gsv.SimpleDetailActivityMode2"
|
||||
android:theme="@style/DetailTheme" />
|
||||
<activity android:name=".comm.BaseActivity" />
|
||||
<activity android:name=".ui.reg.login.RegActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
6
app/src/main/java/com/yuxihan/sdu/comm/Const.java
Normal file
6
app/src/main/java/com/yuxihan/sdu/comm/Const.java
Normal file
@ -0,0 +1,6 @@
|
||||
package com.yuxihan.sdu.comm;
|
||||
|
||||
public class Const {
|
||||
public static final String LOGIN_STATE = "LoginState";
|
||||
public static final String LOGIN_TOKEN = "LoginToken";
|
||||
}
|
@ -44,7 +44,9 @@ public class SDUApp extends Application {
|
||||
NineGridView.setImageLoader(new GlideImageLoader());
|
||||
}
|
||||
|
||||
/** Glide 加载 */
|
||||
/**
|
||||
* Glide 加载
|
||||
*/
|
||||
private static class GlideImageLoader implements NineGridView.ImageLoader {
|
||||
@Override
|
||||
public void onDisplayImage(Context context, ImageView imageView, String url) {
|
||||
@ -80,10 +82,7 @@ public class SDUApp extends Application {
|
||||
@Override
|
||||
public Response intercept(Interceptor.Chain chain) throws IOException {
|
||||
Request mRequest = chain.request().newBuilder()
|
||||
.header("User-Agent", "android/" /* +
|
||||
App.VERSIONNAME + "(" +
|
||||
DeviceUtils.getSDKVersion() + ";" +
|
||||
DeviceUtils.getModel() + ")"*/)
|
||||
.header("User-Agent", "android/")
|
||||
.build();
|
||||
return chain.proceed(mRequest);
|
||||
}
|
||||
|
19
app/src/main/java/com/yuxihan/sdu/comm/util/DataUtil.java
Normal file
19
app/src/main/java/com/yuxihan/sdu/comm/util/DataUtil.java
Normal file
@ -0,0 +1,19 @@
|
||||
package com.yuxihan.sdu.comm.util;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DataUtil {
|
||||
/**
|
||||
* 字符串判空
|
||||
*/
|
||||
public static boolean isEmpty(String src) {
|
||||
return null != src && src.length() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* List判空
|
||||
*/
|
||||
public static boolean isEmpty(List src) {
|
||||
return null != src && src.size() > 0;
|
||||
}
|
||||
}
|
34
app/src/main/java/com/yuxihan/sdu/comm/util/FormatUtils.java
Normal file
34
app/src/main/java/com/yuxihan/sdu/comm/util/FormatUtils.java
Normal file
@ -0,0 +1,34 @@
|
||||
package com.yuxihan.sdu.comm.util;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
public class FormatUtils {
|
||||
/**
|
||||
* 验证手机格式
|
||||
*/
|
||||
public static boolean isMobileNO(String mobiles) {
|
||||
/*
|
||||
移动:134、135、136、137、138、139、150、151、157(TD)、158、159、187、188
|
||||
联通:130、131、132、152、155、156、185、186
|
||||
电信:133、153、180、189、(1349卫通)
|
||||
总结起来就是第一位必定为1,第二位必定为3或5或8,其他位置的可以为0-9
|
||||
------------------------------------------------
|
||||
13(老)号段:130、131、132、133、134、135、136、137、138、139
|
||||
14(新)号段:145、147
|
||||
15(新)号段:150、151、152、153、154、155、156、157、158、159
|
||||
17(新)号段:170、171、173、175、176、177、178
|
||||
18(3G)号段:180、181、182、183、184、185、186、187、188、189
|
||||
*/
|
||||
String telRegex = "[1][34578]\\d{9}";//"[1]"代表第1位为数字1,"[358]"代表第二位可以为3、4、5、7、8中的一个,"\\d{9
|
||||
// }"代表后面是可以是0~9的数字,有9位。
|
||||
if (TextUtils.isEmpty(mobiles)) return false;
|
||||
else return mobiles.matches(telRegex);
|
||||
}
|
||||
|
||||
public static boolean isPassword(String password) {
|
||||
|
||||
if (TextUtils.isEmpty(password)) return false;
|
||||
else return password.length() >= 6;
|
||||
}
|
||||
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package com.yuxihan.sdu.data;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.yuxihan.sdu.comm.Constant;
|
||||
import com.yuxihan.sdu.comm.SDUApp;
|
||||
import com.yuxihan.sdu.data.model.DataBean;
|
||||
import com.yuxihan.sdu.data.model.LoggedInUser;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
/**
|
||||
* Class that handles authentication w/ login credentials and retrieves user information.
|
||||
*/
|
||||
public class LoginDataSource {
|
||||
|
||||
public Result<LoggedInUser> login(final String username, String password) {
|
||||
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl(Constant.BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build();
|
||||
UpdateService updateService = SDUApp.getRetrofit().create(UpdateService.class);
|
||||
Call<DataBean> call = updateService.login(username, password);
|
||||
call.enqueue(new Callback<DataBean>() {
|
||||
@Override
|
||||
public void onResponse(Call<DataBean> call, Response<DataBean> response) {
|
||||
//请求成功,返回是一个封装为DataBean的响应
|
||||
String result = response.body().toString();
|
||||
Log.e("TAG", "Url ===== " + result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<DataBean> call, Throwable t) {
|
||||
//请求失败
|
||||
Log.e("TAG", "请求失败");
|
||||
}
|
||||
});
|
||||
|
||||
return new Result.Success<>(new LoggedInUser(username, username));
|
||||
|
||||
/*try {
|
||||
// TODO: handle loggedInUser authentication
|
||||
LoggedInUser fakeUser =
|
||||
new LoggedInUser(
|
||||
java.util.UUID.randomUUID().toString(),
|
||||
"Jane Doe");
|
||||
return new Result.Success<>(fakeUser);
|
||||
} catch (Exception e) {
|
||||
return new Result.Error(new IOException("Error logging in", e));
|
||||
}*/
|
||||
}
|
||||
|
||||
public void logout() {
|
||||
// TODO: revoke authentication
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package com.yuxihan.sdu.data;
|
||||
|
||||
import com.yuxihan.sdu.data.model.LoggedInUser;
|
||||
|
||||
/**
|
||||
* 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 Result<LoggedInUser> login(String username, String password) {
|
||||
// handle login
|
||||
Result<LoggedInUser> result = dataSource.login(username, password);
|
||||
if (result instanceof Result.Success) {
|
||||
setLoggedInUser(((Result.Success<LoggedInUser>) result).getData());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -3,14 +3,18 @@ package com.yuxihan.sdu.data.model;
|
||||
public class DataBean {
|
||||
|
||||
/**
|
||||
* errCode : 1
|
||||
* errMsg :
|
||||
* result :
|
||||
* {
|
||||
* "errCode": "0",
|
||||
* "errMsg": "",
|
||||
* "result": {
|
||||
* "token": "af8bffa1-e0f7-4956-a514-48fee10719e7"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
private String errCode;
|
||||
private String errMsg;
|
||||
private String result;
|
||||
private LoginRetVo result;
|
||||
|
||||
public String getErrCode() {
|
||||
return errCode;
|
||||
@ -28,11 +32,11 @@ public class DataBean {
|
||||
this.errMsg = errMsg;
|
||||
}
|
||||
|
||||
public String getResult() {
|
||||
public LoginRetVo getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(String result) {
|
||||
public void setResult(LoginRetVo result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
@ -41,7 +45,7 @@ public class DataBean {
|
||||
return "DataBean{" +
|
||||
"errCode='" + errCode + '\'' +
|
||||
", errMsg='" + errMsg + '\'' +
|
||||
", result='" + result + '\'' +
|
||||
", result=" + result +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,19 @@ public class LoggedInUser {
|
||||
|
||||
private String userId;
|
||||
private String displayName;
|
||||
boolean loginSuccess = true;
|
||||
|
||||
public LoggedInUser(String userId, String displayName) {
|
||||
this.userId = userId;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public LoggedInUser(String userId, String displayName, boolean loginSuccess) {
|
||||
this.userId = userId;
|
||||
this.displayName = displayName;
|
||||
this.loginSuccess = loginSuccess;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
@ -20,4 +27,16 @@ public class LoggedInUser {
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public boolean isLoginSuccess() {
|
||||
return loginSuccess;
|
||||
}
|
||||
|
||||
public void setLoginSuccess(boolean loginSuccess) {
|
||||
this.loginSuccess = loginSuccess;
|
||||
}
|
||||
}
|
||||
|
36
app/src/main/java/com/yuxihan/sdu/data/model/LoginRetVo.java
Normal file
36
app/src/main/java/com/yuxihan/sdu/data/model/LoginRetVo.java
Normal file
@ -0,0 +1,36 @@
|
||||
package com.yuxihan.sdu.data.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class LoginRetVo {
|
||||
|
||||
private String token;
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LoginRetVo{" +
|
||||
"token='" + token + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
LoginRetVo that = (LoginRetVo) o;
|
||||
return Objects.equals(token, that.token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(token);
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package com.yuxihan.sdu.ui.account;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -11,8 +13,11 @@ import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.blankj.utilcode.util.SPUtils;
|
||||
import com.yuxihan.sdu.R;
|
||||
import com.yuxihan.sdu.comm.BaseFragment;
|
||||
import com.yuxihan.sdu.comm.Const;
|
||||
import com.yuxihan.sdu.ui.login.LoginActivity;
|
||||
|
||||
public class AccountFragment extends BaseFragment {
|
||||
|
||||
@ -30,6 +35,20 @@ public class AccountFragment extends BaseFragment {
|
||||
textView.setText(s);
|
||||
}
|
||||
});
|
||||
|
||||
textView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
SPUtils.getInstance().put(Const.LOGIN_STATE, false);
|
||||
SPUtils.getInstance().put(Const.LOGIN_TOKEN, "");
|
||||
startActivity(new Intent(getContext(), LoginActivity.class));
|
||||
Activity activity = getActivity();
|
||||
if (null != activity) {
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ public class AccountViewModel extends ViewModel {
|
||||
|
||||
public AccountViewModel() {
|
||||
mText = new MutableLiveData<>();
|
||||
mText.setValue("This is notifications fragment");
|
||||
mText.setValue("注销");
|
||||
}
|
||||
|
||||
public LiveData<String> getText() {
|
||||
|
@ -1,17 +0,0 @@
|
||||
package com.yuxihan.sdu.ui.login;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -23,6 +23,8 @@ import com.gyf.immersionbar.ImmersionBar;
|
||||
import com.yuxihan.sdu.MainActivity;
|
||||
import com.yuxihan.sdu.R;
|
||||
import com.yuxihan.sdu.comm.BaseActivity;
|
||||
import com.yuxihan.sdu.data.model.LoggedInUser;
|
||||
import com.yuxihan.sdu.ui.reg.login.RegActivity;
|
||||
|
||||
public class LoginActivity extends BaseActivity {
|
||||
|
||||
@ -39,8 +41,27 @@ public class LoginActivity extends BaseActivity {
|
||||
final EditText usernameEditText = findViewById(R.id.username);
|
||||
final EditText passwordEditText = findViewById(R.id.password);
|
||||
final Button loginButton = findViewById(R.id.login);
|
||||
final TextView regButton = findViewById(R.id.tv_reg);
|
||||
final ProgressBar loadingProgressBar = findViewById(R.id.loading);
|
||||
|
||||
loginViewModel.getLoggedInUser().observe(this, new Observer<LoggedInUser>() {
|
||||
@Override
|
||||
public void onChanged(LoggedInUser loginResult) {
|
||||
if (loginResult == null) {
|
||||
return;
|
||||
}
|
||||
loadingProgressBar.setVisibility(View.GONE);
|
||||
if (loginResult.isLoginSuccess()) {
|
||||
startActivity(new Intent(getApplicationContext(), MainActivity.class));
|
||||
setResult(Activity.RESULT_OK);
|
||||
finish();
|
||||
} else {
|
||||
Toast.makeText(LoginActivity.this, loginResult.getDisplayName(),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
loginViewModel.getLoginFormState().observe(this, new Observer<LoginFormState>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable LoginFormState loginFormState) {
|
||||
@ -57,26 +78,6 @@ public class LoginActivity extends BaseActivity {
|
||||
}
|
||||
});
|
||||
|
||||
loginViewModel.getLoginResult().observe(this, new Observer<LoginResult>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable LoginResult loginResult) {
|
||||
if (loginResult == null) {
|
||||
return;
|
||||
}
|
||||
loadingProgressBar.setVisibility(View.GONE);
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
TextWatcher afterTextChangedListener = new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
@ -116,15 +117,14 @@ public class LoginActivity extends BaseActivity {
|
||||
passwordEditText.getText().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
*/
|
||||
startActivity(new Intent(getApplicationContext(), MainActivity.class));
|
||||
regButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
startActivity(new Intent(getApplicationContext(), RegActivity.class));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void showLoginFailed(@StringRes Integer errorString) {
|
||||
|
@ -1,31 +0,0 @@
|
||||
package com.yuxihan.sdu.ui.login;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,73 +1,68 @@
|
||||
package com.yuxihan.sdu.ui.login;
|
||||
|
||||
import android.util.Patterns;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.yuxihan.sdu.R;
|
||||
import com.yuxihan.sdu.data.LoginRepository;
|
||||
import com.yuxihan.sdu.data.Result;
|
||||
import com.blankj.utilcode.util.SPUtils;
|
||||
import com.yuxihan.sdu.comm.SDUApp;
|
||||
import com.yuxihan.sdu.comm.util.FormatUtils;
|
||||
import com.yuxihan.sdu.data.UpdateService;
|
||||
import com.yuxihan.sdu.data.model.DataBean;
|
||||
import com.yuxihan.sdu.data.model.LoggedInUser;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class LoginViewModel extends ViewModel {
|
||||
|
||||
private MutableLiveData<LoggedInUser> loggedInUser = new MutableLiveData<>();
|
||||
private MutableLiveData<LoginFormState> loginFormState = new MutableLiveData<>();
|
||||
private MutableLiveData<LoginResult> loginResult = new MutableLiveData<>();
|
||||
private LoginRepository loginRepository;
|
||||
|
||||
LoginViewModel(LoginRepository loginRepository) {
|
||||
this.loginRepository = loginRepository;
|
||||
public MutableLiveData<LoggedInUser> getLoggedInUser() {
|
||||
return loggedInUser;
|
||||
}
|
||||
|
||||
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
|
||||
Result<LoggedInUser> result = loginRepository.login(username, password);
|
||||
|
||||
if (result instanceof Result.Success) {
|
||||
LoggedInUser data = ((Result.Success<LoggedInUser>) result).getData();
|
||||
loginResult.setValue(new LoginResult(new LoggedInUserView(data.getDisplayName())));
|
||||
} else {
|
||||
loginResult.setValue(new LoginResult(R.string.login_failed));
|
||||
}
|
||||
}
|
||||
|
||||
public void loginDataChanged(String username, String password) {
|
||||
loginFormState.setValue(new LoginFormState(true));
|
||||
/*
|
||||
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 {
|
||||
if (FormatUtils.isMobileNO(username) && FormatUtils.isPassword(password)) {
|
||||
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();
|
||||
loginFormState.setValue(new LoginFormState(false));
|
||||
}
|
||||
}
|
||||
|
||||
// A placeholder password validation check
|
||||
private boolean isPasswordValid(String password) {
|
||||
return password != null && password.trim().length() > 5;
|
||||
public void login(final String username, String password) {
|
||||
|
||||
UpdateService updateService = SDUApp.getRetrofit().create(UpdateService.class);
|
||||
Call<DataBean> call = updateService.login(username, password);
|
||||
call.enqueue(new Callback<DataBean>() {
|
||||
@Override
|
||||
public void onResponse(Call<DataBean> call, Response<DataBean> response) {
|
||||
//请求成功,返回是一个封装为DataBean的响应
|
||||
Log.e("TAG", "接口返回内容 ===== " + response.body().getResult());
|
||||
if ("0" .equals(response.body().getErrCode())) {
|
||||
String token = response.body().getResult().getToken();
|
||||
loggedInUser.setValue(new LoggedInUser(username, username));
|
||||
SPUtils.getInstance().put("LoginState", true);
|
||||
SPUtils.getInstance().put("LoginToken", response.body().getResult().getToken());
|
||||
} else {
|
||||
loggedInUser.setValue(new LoggedInUser(username, "用户名/密码 错误!", false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<DataBean> call, Throwable t) {
|
||||
//请求失败
|
||||
Log.e("TAG", "请求失败:" + t.getMessage());
|
||||
loggedInUser.setValue(new LoggedInUser(username, "\"服务器开小差了!\"", false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,8 @@
|
||||
package com.yuxihan.sdu.ui.login;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.yuxihan.sdu.data.LoginDataSource;
|
||||
import com.yuxihan.sdu.data.LoginRepository;
|
||||
|
||||
/**
|
||||
* ViewModel provider factory to instantiate LoginViewModel.
|
||||
@ -18,7 +15,7 @@ public class LoginViewModelFactory implements ViewModelProvider.Factory {
|
||||
@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()));
|
||||
return (T) new LoginViewModel();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown ViewModel class");
|
||||
}
|
||||
|
136
app/src/main/java/com/yuxihan/sdu/ui/reg/login/RegActivity.java
Normal file
136
app/src/main/java/com/yuxihan/sdu/ui/reg/login/RegActivity.java
Normal file
@ -0,0 +1,136 @@
|
||||
package com.yuxihan.sdu.ui.reg.login;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
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.gyf.immersionbar.ImmersionBar;
|
||||
import com.yuxihan.sdu.R;
|
||||
import com.yuxihan.sdu.comm.BaseActivity;
|
||||
import com.yuxihan.sdu.ui.login.LoginActivity;
|
||||
|
||||
public class RegActivity extends BaseActivity {
|
||||
|
||||
private RegViewModel regViewModel;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_reg);
|
||||
ImmersionBar.with(this).titleBar(R.id.toolbar).keyboardEnable(true).init();
|
||||
regViewModel = new ViewModelProvider(this, new RegViewModelFactory())
|
||||
.get(RegViewModel.class);
|
||||
|
||||
final EditText usernameEditText = findViewById(R.id.username);
|
||||
final EditText passwordEditText = findViewById(R.id.password);
|
||||
passwordEditText.setInputType(128);
|
||||
final TextView btGetVerification = findViewById(R.id.bt_get_verification);
|
||||
btGetVerification.setEnabled(false);
|
||||
btGetVerification.setTextColor(getResources().getColor(R.color.gray));
|
||||
final Button loginButton = findViewById(R.id.login);
|
||||
final ProgressBar loadingProgressBar = findViewById(R.id.loading);
|
||||
|
||||
btGetVerification.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
//TODO:请求发送验证码
|
||||
btGetVerification.setText("验证码已发送");
|
||||
}
|
||||
});
|
||||
|
||||
regViewModel.getRegFormState().observe(this, new Observer<RegFormState>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable RegFormState regFormState) {
|
||||
if (regFormState == null) {
|
||||
return;
|
||||
}
|
||||
btGetVerification.setEnabled(regFormState.isPhoneNumberValid());
|
||||
if (regFormState.isPhoneNumberValid()) {
|
||||
btGetVerification.setTextColor(getResources().getColor(R.color.colorPrimary));
|
||||
} else {
|
||||
btGetVerification.setTextColor(getResources().getColor(R.color.gray));
|
||||
}
|
||||
loginButton.setEnabled(regFormState.isDataValid());
|
||||
if (regFormState.getUsernameError() != null) {
|
||||
usernameEditText.setError(getString(regFormState.getUsernameError()));
|
||||
}
|
||||
if (regFormState.getPasswordError() != null) {
|
||||
passwordEditText.setError(getString(regFormState.getPasswordError()));
|
||||
}
|
||||
}
|
||||
});
|
||||
// loadingProgressBar.setVisibility(View.GONE);
|
||||
|
||||
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) {
|
||||
regViewModel.loginDataChanged(usernameEditText.getText().toString(),
|
||||
passwordEditText.getText().toString());
|
||||
}
|
||||
};
|
||||
usernameEditText.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) {
|
||||
regViewModel.login(usernameEditText.getText().toString(),
|
||||
passwordEditText.getText().toString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
loginButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
loadingProgressBar.setVisibility(View.VISIBLE);
|
||||
regViewModel.login(usernameEditText.getText().toString(),
|
||||
passwordEditText.getText().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateUiWithUser() {
|
||||
startActivity(new Intent(getApplicationContext(), LoginActivity.class));
|
||||
}
|
||||
|
||||
private void showLoginFailed(@StringRes Integer errorString) {
|
||||
Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.yuxihan.sdu.ui.reg.login;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Data validation state of the login form.
|
||||
*/
|
||||
class RegFormState {
|
||||
@Nullable
|
||||
private Integer usernameError;
|
||||
@Nullable
|
||||
private Integer passwordError;
|
||||
private boolean isDataValid;
|
||||
private boolean isPhoneNumberValid;
|
||||
|
||||
RegFormState(@Nullable Integer usernameError, @Nullable Integer passwordError) {
|
||||
this.usernameError = usernameError;
|
||||
this.passwordError = passwordError;
|
||||
this.isDataValid = false;
|
||||
}
|
||||
|
||||
RegFormState(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;
|
||||
}
|
||||
|
||||
public boolean isPhoneNumberValid() {
|
||||
return isPhoneNumberValid;
|
||||
}
|
||||
|
||||
public void setPhoneNumberValid(boolean phoneNumberValid) {
|
||||
isPhoneNumberValid = phoneNumberValid;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.yuxihan.sdu.ui.reg.login;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.yuxihan.sdu.comm.util.FormatUtils;
|
||||
|
||||
public class RegViewModel extends ViewModel {
|
||||
|
||||
private MutableLiveData<RegFormState> regFormState = new MutableLiveData<>();
|
||||
|
||||
LiveData<RegFormState> getRegFormState() {
|
||||
return regFormState;
|
||||
}
|
||||
|
||||
|
||||
public void login(String username, String password) {
|
||||
|
||||
}
|
||||
|
||||
public void loginDataChanged(String username, String password) {
|
||||
RegFormState regFormState = new RegFormState(true);
|
||||
regFormState.setPhoneNumberValid(FormatUtils.isMobileNO(username));
|
||||
this.regFormState.setValue(regFormState);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.yuxihan.sdu.ui.reg.login;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
/**
|
||||
* ViewModel provider factory to instantiate LoginViewModel.
|
||||
* Required given LoginViewModel has a non-empty constructor
|
||||
*/
|
||||
public class RegViewModelFactory implements ViewModelProvider.Factory {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
if (modelClass.isAssignableFrom(RegViewModel.class)) {
|
||||
return (T) new RegViewModel();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown ViewModel class");
|
||||
}
|
||||
}
|
||||
}
|
@ -3,8 +3,11 @@ package com.yuxihan.sdu.ui.splash;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.blankj.utilcode.util.SPUtils;
|
||||
import com.yuxihan.sdu.MainActivity;
|
||||
import com.yuxihan.sdu.R;
|
||||
import com.yuxihan.sdu.comm.BaseActivity;
|
||||
import com.yuxihan.sdu.comm.Const;
|
||||
import com.yuxihan.sdu.ui.login.LoginActivity;
|
||||
|
||||
public class InitializeActivity extends BaseActivity {
|
||||
@ -13,7 +16,11 @@ public class InitializeActivity extends BaseActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_initialize);
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
if (SPUtils.getInstance().getBoolean(Const.LOGIN_STATE)) {
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
} else {
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,7 @@
|
||||
android:background="@null"
|
||||
android:hint="请输入手机号"
|
||||
android:inputType="number"
|
||||
android:text="13812345678"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
@ -118,6 +119,7 @@
|
||||
android:background="@null"
|
||||
android:hint="请输入密码"
|
||||
android:inputType="textPassword"
|
||||
android:text="123456"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
@ -154,15 +156,14 @@
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:visibility="invisible"
|
||||
android:id="@+id/tv_forget_password"
|
||||
android:id="@+id/tv_reg"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:text="忘记密码?"
|
||||
android:textColor="#333333"
|
||||
android:textSize="14sp" />
|
||||
android:text="注册"
|
||||
android:textColor="#3333FF"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
221
app/src/main/res/layout/activity_reg.xml
Normal file
221
app/src/main/res/layout/activity_reg.xml
Normal file
@ -0,0 +1,221 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"
|
||||
android:background="@drawable/shape"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
app:title="注册"
|
||||
app:titleTextColor="@android:color/white" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#f9f9f9"
|
||||
android:fillViewport="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#f9f9f9"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:background="@android:color/white"
|
||||
app:cardCornerRadius="22dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="66dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:alpha="0.4"
|
||||
android:text="手机号"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="14dp"
|
||||
android:background="@null"
|
||||
android:hint="请输入手机号"
|
||||
android:inputType="number"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
app:cardCornerRadius="22dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/bt_get_verification"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="35dp"
|
||||
android:background="@null"
|
||||
android:gravity="center"
|
||||
android:text="获取验证码"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="14sp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:background="@android:color/white"
|
||||
app:cardCornerRadius="22dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="66dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:alpha="0.4"
|
||||
android:text="验证码"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_verification"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="14dp"
|
||||
android:background="@null"
|
||||
android:hint="请输入验证码"
|
||||
android:inputType="number"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:background="@android:color/white"
|
||||
app:cardCornerRadius="22dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="66dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:alpha="0.4"
|
||||
android:text="密码"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="14dp"
|
||||
android:background="@null"
|
||||
android:hint="请输入密码"
|
||||
android:inputType="textPassword"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="64dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="164dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
app:cardCornerRadius="2dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/login"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:background="@drawable/shape"
|
||||
android:gravity="center"
|
||||
android:text="注册"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_reg"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:text="注册"
|
||||
android:textColor="#3333FF"
|
||||
android:textSize="20sp"
|
||||
android:visibility="invisible" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
@ -6,7 +6,7 @@
|
||||
<color name="white">#FFFFFF</color>
|
||||
<color name="black">#000000</color>
|
||||
<color name="text_black">#101010</color>
|
||||
<color name="gray">#AAFFFFFF</color>
|
||||
<color name="gray">#888888</color>
|
||||
<color name="white_bg">#fafafa</color>
|
||||
<color name="divider_gray">#eeeeee</color>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user