diff --git a/app/build.gradle b/app/build.gradle index 56d28c8..2707a18 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,5 +65,5 @@ dependencies { androidTestImplementation libs.androidx.espresso.core androidTestImplementation libs.androidx.junit //屏幕适配 - implementation 'me.jessyan:autosize:1.1.2' + implementation 'me.jessyan:autosize:1.2.1' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3235e95..8404ea2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,7 +41,7 @@ android:exported="false" /> @@ -49,10 +49,24 @@ + + + + + + + + (), + LoginContract.LoginView, View.OnClickListener { + + private var codeCountDownTimer: CountDownTimer? = null + private var isPasswordVisible = false + private var isConfirmPasswordVisible = false + + override fun createBinding(inflater: LayoutInflater): ForgetPsdLayoutBinding { + return ForgetPsdLayoutBinding.inflate(inflater) + } + + override fun createPresenter(): LoginPresenter? { + return LoginPresenter(LoginImpl()) + } + + override fun initWindows() { + ImmersionBar.with(this) + .statusBarView(binding.statusBarView) + .statusBarDarkFont(true) + .navigationBarColor(R.color.color_zhu) + .navigationBarDarkIcon(true) + .init() + } + + override fun initView(savedInstanceState: Bundle?) { + binding.includedTitle.tvTitle.setText(R.string.forget_password_title) + listOf( + binding.includedTitle.imgBack, + binding.getCodeButton, + binding.passwordVisibilityButton, + binding.confirmPasswordVisibilityButton, + binding.confirmButton, + ).forEach { view -> + view.setOnClickListener(this) + } + } + + override fun onClick(view: View) { + when (view.id) { + R.id.img_back -> { + finish() + } + + R.id.getCodeButton -> {//获取验证码 + val phone = binding.phoneEditText.text.toString().trim() + if (phone.isBlank()) { + ToastUtil.showShort(mContext, "手机号不能为空") + return + } + presenter?.getPhoneSmsCode(phone, AppConfig.SEND_CODE_SCENE_RESET) + } + + R.id.passwordVisibilityButton -> { + isPasswordVisible = !isPasswordVisible + togglePasswordVisibility(binding.passwordEditText, isPasswordVisible) + } + + R.id.confirmPasswordVisibilityButton -> { + isConfirmPasswordVisible = !isConfirmPasswordVisible + togglePasswordVisibility(binding.confirmPasswordEditText, isConfirmPasswordVisible) + } + + R.id.confirmButton -> {//修改密码 + val phone = binding.phoneEditText.text.toString().trim() + val code = binding.codeEditText.text.toString().trim() + val password = binding.passwordEditText.text.toString() + val confirmPassword = binding.confirmPasswordEditText.text.toString() + + if (phone.isBlank()) { + ToastUtil.showShort(mContext, "手机号不能为空") + return + } + if (code.isBlank()) { + ToastUtil.showShort(mContext, "验证码不能为空") + return + } + if (password.isBlank()) { + ToastUtil.showShort(mContext, "密码不能为空") + return + } + if (confirmPassword.isBlank()) { + ToastUtil.showShort(mContext, "请再次输入密码") + return + } + if (password != confirmPassword) { + ToastUtil.showShort(mContext, "两次输入的密码不一致") + return + } + if (!isValidPassword(password)) { + ToastUtil.showShort( + mContext, + "密码需为8-20位,且包含大小写字母和数字" + ) + return + } + + presenter?.forgetPsdByPhone(phone, code, password) + } + } + } + + private fun togglePasswordVisibility(editText: EditText, visible: Boolean) { + val selection = editText.selectionEnd + editText.inputType = if (visible) { + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD + } else { + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD + } + editText.setSelection(selection.coerceAtLeast(0)) + editText.typeface = binding.phoneEditText.typeface + } + + override fun showError(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message) + } + + override fun sendCodeSuccess(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message) + codeCountDownTimer?.cancel() + binding.getCodeButton.apply { + isEnabled = false + isClickable = false + setBackgroundResource(R.drawable.shape_register_code_disabled_bg) + text = getString(R.string.register_code_countdown, CODE_COUNTDOWN_SECONDS) + } + codeCountDownTimer = object : CountDownTimer(CODE_COUNTDOWN_MILLIS, 1_000L) { + override fun onTick(millisUntilFinished: Long) { + val secondsRemaining = (millisUntilFinished + 999L) / 1_000L + binding.getCodeButton.text = getString( + R.string.register_code_countdown, + secondsRemaining + ) + } + + override fun onFinish() { + codeCountDownTimer = null + binding.getCodeButton.apply { + isEnabled = true + isClickable = true + setBackgroundResource(R.drawable.shape_register_code_bg) + setText(R.string.register_get_code) + } + } + }.start() + } + + override fun onRegisterSuccess(message: String) { + } + + override fun onForgetPsdSuccess(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message.ifBlank { "密码修改成功" }) + finish() + } + + override fun showUsernameError(message: String) { + ToastUtil.showShort(mContext, message) + } + + override fun showPasswordError(message: String) { + ToastUtil.showShort(mContext, message) + } + + override fun clearInputErrors() { + } + + override fun onLoginSuccess(user: UserEntity) { + } + + override fun onDestroy() { + codeCountDownTimer?.cancel() + codeCountDownTimer = null + super.onDestroy() + } + + /** 8-20 位,且同时包含大写、小写字母和数字。 */ + private fun isValidPassword(password: String): Boolean { + if (password.length !in PASSWORD_MIN_LENGTH..PASSWORD_MAX_LENGTH) { + return false + } + val hasUpper = password.any { it.isUpperCase() } + val hasLower = password.any { it.isLowerCase() } + val hasDigit = password.any { it.isDigit() } + return hasUpper && hasLower && hasDigit + } + + private companion object { + const val CODE_COUNTDOWN_SECONDS = 60L + const val CODE_COUNTDOWN_MILLIS = CODE_COUNTDOWN_SECONDS * 1_000L + const val PASSWORD_MIN_LENGTH = 8 + const val PASSWORD_MAX_LENGTH = 20 + } +} diff --git a/app/src/main/java/com/juhabi/birds/login/LoginActivity.kt b/app/src/main/java/com/juhabi/birds/login/LoginActivity.kt index 7cac569..2ba6768 100644 --- a/app/src/main/java/com/juhabi/birds/login/LoginActivity.kt +++ b/app/src/main/java/com/juhabi/birds/login/LoginActivity.kt @@ -1,20 +1,35 @@ package com.juhabi.birds.login -import android.os.Bundle import android.content.Intent +import android.os.Bundle +import android.os.CountDownTimer +import android.text.InputType import android.view.LayoutInflater +import android.view.View +import com.gyf.immersionbar.ImmersionBar +import com.juhabi.birds.AppConfig +import com.juhabi.birds.R import com.juhabi.birds.base.BaseActivity import com.juhabi.birds.databinding.ActivityLoginBinding -import com.juhabi.birds.login.model.LoginUser +import com.juhabi.birds.login.model.UserEntity import com.juhabi.birds.login.mvp.LoginContract import com.juhabi.birds.login.mvp.LoginImpl import com.juhabi.birds.login.mvp.LoginPresenter -import com.juhabi.birds.network.TokenManager import com.juhabi.birds.mine.MineCenterActivity +import com.juhabi.birds.network.TokenManager +import com.juhabi.birds.util.SharedPreferencesUtil import com.juhabi.birds.util.ToastUtil +/** + * 登录 + */ class LoginActivity : BaseActivity(), - LoginContract.LoginView { + LoginContract.LoginView, View.OnClickListener { + + /** 是否使用密码登录;false 为手机号 + 验证码登录 */ + private var loginByPassword = true + private var isPasswordVisible = false + private var codeCountDownTimer: CountDownTimer? = null override fun createBinding(inflater: LayoutInflater): ActivityLoginBinding { return ActivityLoginBinding.inflate(inflater) @@ -24,48 +39,176 @@ class LoginActivity : BaseActivity(), return LoginPresenter(LoginImpl()) } + override fun initWindows() { + ImmersionBar.with(this) + .navigationBarColor(R.color.color_zhu) + .navigationBarDarkIcon(true) + .init() + } + override fun initView(savedInstanceState: Bundle?) { -// if (TokenManager.get(this).isNotBlank()) { -// startActivity(Intent(this, MineCenterActivity::class.java)) -// finish() -// return -// } - if(binding.usernameEditText.text.toString().isNotBlank()){ - ToastUtil.showShort(mContext,""); - return + listOf( + binding.tvChangeCode, + binding.forgotPasswordButton, + binding.passwordVisibilityButton, + binding.getCodeButton, + binding.loginButton, + binding.registerButton, + ).forEach { button -> + button.setOnClickListener(this) } - binding.loginButton.setOnClickListener { - presenter?.login( - binding.usernameEditText.text?.toString().orEmpty(), - binding.passwordEditText.text?.toString().orEmpty() - ) + } + + override fun onClick(view: View) { + when (view.id) { + R.id.tvChangeCode -> {//更换登录方式 + if (loginByPassword) { + binding.linearCode.visibility = View.VISIBLE + binding.linearPsd.visibility = View.GONE + loginByPassword = false + binding.tvChangeCode.setText(R.string.register_change_psd_login) + } else { + binding.linearCode.visibility = View.GONE + binding.linearPsd.visibility = View.VISIBLE + loginByPassword = true + binding.tvChangeCode.setText(R.string.register_change_code_login) + } + } + + R.id.getCodeButton -> {//获取验证码 + val phone = binding.accountEditText.text.toString().trim() + if (phone.isBlank()) { + ToastUtil.showShort(mContext, "手机号不能为空") + return + } + presenter?.getPhoneSmsCode(phone, AppConfig.SEND_CODE_SCENE_LOGIN) + } + + R.id.registerButton -> {//注册 + goTo(RegisterActivity::class.java, null) + } + + R.id.forgotPasswordButton -> {//忘记密码 + goTo(ForgetPsdActivity::class.java, null) + } + + R.id.passwordVisibilityButton -> { + togglePasswordVisibility() + } + + R.id.loginButton -> {//登录 + val account = binding.accountEditText.text.toString().trim() + if (account.isBlank()) { + ToastUtil.showShort(mContext, "账号不能为空") + return + } + + if (loginByPassword) { + val password = binding.passwordEditText.text.toString() + if (password.isBlank()) { + ToastUtil.showShort(mContext, "密码不能为空") + return + } + presenter?.loginByPhonePsd(account, password) + } else { + val code = binding.codeEditText.text.toString().trim() + if (code.isBlank()) { + ToastUtil.showShort(mContext, "验证码不能为空") + return + } + presenter?.loginByPhoneSms(account, code) + } + } } } + private fun togglePasswordVisibility() { + isPasswordVisible = !isPasswordVisible + val selection = binding.passwordEditText.selectionEnd + binding.passwordEditText.inputType = if (isPasswordVisible) { + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD + } else { + InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD + } + // 保持光标位置,并触发 typeface 刷新 + binding.passwordEditText.setSelection(selection.coerceAtLeast(0)) + binding.passwordEditText.typeface = binding.accountEditText.typeface + } + + override fun sendCodeSuccess(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message) + codeCountDownTimer?.cancel() + binding.getCodeButton.apply { + isEnabled = false + isClickable = false + setBackgroundResource(R.drawable.shape_register_code_disabled_bg) + text = getString(R.string.register_code_countdown, CODE_COUNTDOWN_SECONDS) + } + codeCountDownTimer = object : CountDownTimer(CODE_COUNTDOWN_MILLIS, 1_000L) { + override fun onTick(millisUntilFinished: Long) { + val secondsRemaining = (millisUntilFinished + 999L) / 1_000L + binding.getCodeButton.text = getString( + R.string.register_code_countdown, + secondsRemaining + ) + } + + override fun onFinish() { + codeCountDownTimer = null + binding.getCodeButton.apply { + isEnabled = true + isClickable = true + setBackgroundResource(R.drawable.shape_register_code_bg) + setText(R.string.register_get_code) + } + } + }.start() + } + + override fun onRegisterSuccess(message: String) { + } + + override fun onForgetPsdSuccess(message: String) { + } + override fun showUsernameError(message: String) { - binding.usernameLayout.error = message - binding.usernameEditText.requestFocus() + ToastUtil.showShort(mContext, message) } override fun showPasswordError(message: String) { - binding.passwordLayout.error = message - binding.passwordEditText.requestFocus() + ToastUtil.showShort(mContext, message) } override fun clearInputErrors() { - binding.usernameLayout.error = null - binding.passwordLayout.error = null } - override fun onLoginSuccess(user: LoginUser) { - TokenManager.save(this, user.accessToken) - binding.resultTextView.text = "登录成功,Token 已保存" - showMessage("登录成功") + override fun onLoginSuccess(user: UserEntity) { + val token = user.token.trim() + // SharedPreferences 持久化 token,并同步到内存 AppConfig.userToken + TokenManager.save(this, token) + AppConfig.USER_LOGIN = token.isNotBlank() + SharedPreferencesUtil.putBoolean(this, KEY_USER_LOGIN, AppConfig.USER_LOGIN) + + ToastUtil.showShort(mContext, "登录成功") startActivity(Intent(this, MineCenterActivity::class.java)) finish() } override fun showError(message: String) { - ToastUtil.showShort(mContext,message) + hideLoading() + ToastUtil.showShort(mContext, message) + } + + override fun onDestroy() { + codeCountDownTimer?.cancel() + codeCountDownTimer = null + super.onDestroy() + } + + private companion object { + const val CODE_COUNTDOWN_SECONDS = 60L + const val CODE_COUNTDOWN_MILLIS = CODE_COUNTDOWN_SECONDS * 1_000L + const val KEY_USER_LOGIN = "user_login" } } diff --git a/app/src/main/java/com/juhabi/birds/login/RegisterActivity.kt b/app/src/main/java/com/juhabi/birds/login/RegisterActivity.kt new file mode 100644 index 0000000..8640e9d --- /dev/null +++ b/app/src/main/java/com/juhabi/birds/login/RegisterActivity.kt @@ -0,0 +1,192 @@ +package com.juhabi.birds.login + +import android.os.Bundle +import android.os.CountDownTimer +import android.view.LayoutInflater +import android.view.View +import com.gyf.immersionbar.ImmersionBar +import com.juhabi.birds.AppConfig +import com.juhabi.birds.R +import com.juhabi.birds.base.BaseActivity +import com.juhabi.birds.databinding.RegisterLayoutBinding +import com.juhabi.birds.login.model.UserEntity +import com.juhabi.birds.login.mvp.LoginContract +import com.juhabi.birds.login.mvp.LoginImpl +import com.juhabi.birds.login.mvp.LoginPresenter +import com.juhabi.birds.util.ToastUtil + +/** + * 注册 + */ +class RegisterActivity : BaseActivity(), + LoginContract.LoginView, View.OnClickListener { + + private var codeCountDownTimer: CountDownTimer? = null + + override fun createBinding(inflater: LayoutInflater): RegisterLayoutBinding { + return RegisterLayoutBinding.inflate(inflater) + } + + override fun createPresenter(): LoginPresenter? { + return LoginPresenter(LoginImpl()) + } + + override fun initWindows() { + ImmersionBar.with(this) + .statusBarView(binding.statusBarView) + .statusBarDarkFont(true) + .navigationBarColor(R.color.color_zhu) + .navigationBarDarkIcon(true) + .init() + } + + override fun initView(savedInstanceState: Bundle?) { + binding.includedTitle.tvTitle.setText(R.string.login_register) + listOf( + binding.passwordVisibilityButton, + binding.getCodeButton, + binding.confirmButton, + binding.includedTitle.imgBack, + ).forEach { button -> + button.setOnClickListener(this) + } + } + + override fun onClick(view: View) { + when (view.id) { + + R.id.img_back ->{ + finish() + } + + R.id.getCodeButton -> {//获取验证码 + if (binding.phoneEditText.text.toString().isBlank()) { + ToastUtil.showShort(mContext, "手机号不能为空") + return + } + presenter?.getPhoneSmsCode( + binding.phoneEditText.text.toString(), + AppConfig.SEND_CODE_SCENE_REGISTER + ) + } + + R.id.confirmButton -> {//注册 + val phone = binding.phoneEditText.text.toString().trim() + val code = binding.codeEditText.text.toString().trim() + val password = binding.passwordEditText.text.toString() + val confirmPassword = binding.confirmPasswordEditText.text.toString() + + if (phone.isBlank()) { + ToastUtil.showShort(mContext, "手机号不能为空") + return + } + if (code.isBlank()) { + ToastUtil.showShort(mContext, "验证码不能为空") + return + } + if (password.isBlank()) { + ToastUtil.showShort(mContext, "密码不能为空") + return + } + if (confirmPassword.isBlank()) { + ToastUtil.showShort(mContext, "请再次输入密码") + return + } + if (password != confirmPassword) { + ToastUtil.showShort(mContext, "两次输入的密码不一致") + return + } + if (!isValidPassword(password)) { + ToastUtil.showShort( + mContext, + "密码需为8-20位,且包含大小写字母和数字" + ) + return + } + + presenter?.registerByPhone(phone, code, password) + } + } + } + + override fun showError(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message) + } + + override fun sendCodeSuccess(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message) + codeCountDownTimer?.cancel() + binding.getCodeButton.apply { + isEnabled = false + isClickable = false + setBackgroundResource(R.drawable.shape_register_code_disabled_bg) + text = getString(R.string.register_code_countdown, CODE_COUNTDOWN_SECONDS) + } + codeCountDownTimer = object : CountDownTimer(CODE_COUNTDOWN_MILLIS, 1_000L) { + override fun onTick(millisUntilFinished: Long) { + val secondsRemaining = (millisUntilFinished + 999L) / 1_000L + binding.getCodeButton.text = getString( + R.string.register_code_countdown, + secondsRemaining + ) + } + + override fun onFinish() { + codeCountDownTimer = null + binding.getCodeButton.apply { + isEnabled = true + isClickable = true + setBackgroundResource(R.drawable.shape_register_code_bg) + setText(R.string.register_get_code) + } + } + }.start() + } + + override fun onRegisterSuccess(message: String) { + hideLoading() + ToastUtil.showShort(mContext, message.ifBlank { "注册成功" }) + finish() + } + + override fun onForgetPsdSuccess(message: String) { + } + + override fun showUsernameError(message: String) { + } + + override fun showPasswordError(message: String) { + } + + override fun clearInputErrors() { + } + + override fun onLoginSuccess(user: UserEntity) { + } + + override fun onDestroy() { + codeCountDownTimer?.cancel() + codeCountDownTimer = null + super.onDestroy() + } + + /** 8-20 位,且同时包含大写、小写字母和数字。 */ + private fun isValidPassword(password: String): Boolean { + if (password.length !in PASSWORD_MIN_LENGTH..PASSWORD_MAX_LENGTH) { + return false + } + val hasUpper = password.any { it.isUpperCase() } + val hasLower = password.any { it.isLowerCase() } + val hasDigit = password.any { it.isDigit() } + return hasUpper && hasLower && hasDigit + } + + private companion object { + const val CODE_COUNTDOWN_SECONDS = 60L + const val CODE_COUNTDOWN_MILLIS = CODE_COUNTDOWN_SECONDS * 1_000L + const val PASSWORD_MIN_LENGTH = 8 + const val PASSWORD_MAX_LENGTH = 20 + } +} diff --git a/app/src/main/java/com/juhabi/birds/login/WelcomeActivity.kt b/app/src/main/java/com/juhabi/birds/login/WelcomeActivity.kt new file mode 100644 index 0000000..c06d117 --- /dev/null +++ b/app/src/main/java/com/juhabi/birds/login/WelcomeActivity.kt @@ -0,0 +1,86 @@ +package com.juhabi.birds.login + +import android.content.Intent +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import com.gyf.immersionbar.ImmersionBar +import com.juhabi.birds.AppConfig +import com.juhabi.birds.R +import com.juhabi.birds.base.BaseActivity +import com.juhabi.birds.databinding.ActiveWelcomeBinding +import com.juhabi.birds.login.model.UserEntity +import com.juhabi.birds.login.mvp.LoginContract +import com.juhabi.birds.login.mvp.LoginPresenter +import com.juhabi.birds.main.MainActivity + +/** + * 启动页面 + */ +class WelcomeActivity: BaseActivity(),LoginContract.LoginView { + private val handler = Handler(Looper.getMainLooper()) + private val navigateRunnable = Runnable { + val targetActivity = if (AppConfig.USER_LOGIN) { + MainActivity::class.java + } else { + LoginActivity::class.java + } + startActivity(Intent(this, targetActivity)) + finish() + } + + override fun createBinding(inflater: LayoutInflater): ActiveWelcomeBinding { + return ActiveWelcomeBinding.inflate(inflater) + } + + + override fun initWindows() { + ImmersionBar.with(this) + .navigationBarColor(R.color.color_zhu) + .navigationBarDarkIcon(true) + .init() + } + override fun createPresenter(): LoginPresenter? { + return null + } + + override fun initView(savedInstanceState: Bundle?) { + + handler.postDelayed(navigateRunnable, SPLASH_DELAY_MILLIS) + + } + + override fun showError(message: String) { + } + + override fun sendCodeSuccess(message: String) { + } + + override fun onRegisterSuccess(message: String) { + } + + override fun onForgetPsdSuccess(message: String) { + } + + override fun showUsernameError(message: String) { + } + + override fun showPasswordError(message: String) { + } + + override fun clearInputErrors() { + } + + override fun onLoginSuccess(user: UserEntity) { + } + + override fun onDestroy() { + handler.removeCallbacks(navigateRunnable) + super.onDestroy() + } + + private companion object { + const val SPLASH_DELAY_MILLIS = 2_000L + } +} diff --git a/app/src/main/java/com/juhabi/birds/login/model/LoginUser.kt b/app/src/main/java/com/juhabi/birds/login/model/LoginUser.kt deleted file mode 100644 index 8cf30d5..0000000 --- a/app/src/main/java/com/juhabi/birds/login/model/LoginUser.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.juhabi.birds.login.model - -import com.google.gson.annotations.SerializedName - -data class LoginUser( - val scope: Any? = null, - val openid: Any? = null, - @SerializedName("access_token") - val accessToken: String = "", - @SerializedName("refresh_token") - val refreshToken: Any? = null, - @SerializedName("expire_in") - val expireIn: Long = 0, - @SerializedName("refresh_expire_in") - val refreshExpireIn: Any? = null, - @SerializedName("client_id") - val clientId: String = "" -) diff --git a/app/src/main/java/com/juhabi/birds/login/model/SendCodeEntity.kt b/app/src/main/java/com/juhabi/birds/login/model/SendCodeEntity.kt new file mode 100644 index 0000000..c26fc5d --- /dev/null +++ b/app/src/main/java/com/juhabi/birds/login/model/SendCodeEntity.kt @@ -0,0 +1,7 @@ +package com.juhabi.birds.login.model + +data class SendCodeEntity( + val code: Int, + val `data`: Any, + val msg: String +) \ No newline at end of file diff --git a/app/src/main/java/com/juhabi/birds/login/model/UserEntity.kt b/app/src/main/java/com/juhabi/birds/login/model/UserEntity.kt new file mode 100644 index 0000000..d8c9ce8 --- /dev/null +++ b/app/src/main/java/com/juhabi/birds/login/model/UserEntity.kt @@ -0,0 +1,11 @@ +package com.juhabi.birds.login.model + +import com.google.gson.annotations.SerializedName + +data class UserEntity( + val scope: Any? = null, + val openid: Any? = null, + @SerializedName("access_token") + val token: String = "", + +) diff --git a/app/src/main/java/com/juhabi/birds/login/mvp/LoginContract.kt b/app/src/main/java/com/juhabi/birds/login/mvp/LoginContract.kt index 5eb99d7..8048b31 100644 --- a/app/src/main/java/com/juhabi/birds/login/mvp/LoginContract.kt +++ b/app/src/main/java/com/juhabi/birds/login/mvp/LoginContract.kt @@ -1,7 +1,8 @@ package com.juhabi.birds.login.mvp import com.juhabi.birds.base.IView -import com.juhabi.birds.login.model.LoginUser +import com.juhabi.birds.login.model.UserEntity +import com.juhabi.birds.login.model.SendCodeEntity import com.juhabi.birds.network.BaseBean import org.json.JSONObject @@ -12,15 +13,38 @@ interface LoginContract { } interface DataModel { - fun login(json: JSONObject, callback: Callback>) + //手机验证码 + fun sendPhoneCode(json: JSONObject, callback: Callback>) + //手机号+验证码注册 + fun registerByPhone(json: JSONObject, callback: Callback>) + //手机+密码登录 + fun loginByPhonePsd(json: JSONObject, callback: Callback>) + //手机+验证码登录 + fun loginByPhoneSms(json: JSONObject, callback: Callback>) + //手机号+验证码修改密码 + fun forgetPsdByPhone(json: JSONObject, callback: Callback>) + + //邮箱验证码 + fun sendEmailCode(json: JSONObject, callback: Callback>) + //邮箱+验证码注册 + fun registerByEmail(json: JSONObject, callback: Callback>) + //邮箱+密码登录 + fun loginByEmailPsd(json: JSONObject, callback: Callback>) + //邮箱+验证码登录 + fun loginByEmailSms(json: JSONObject, callback: Callback>) + //邮箱+验证码修改密码 + fun forgetPsdByEmail(json: JSONObject, callback: Callback>) fun cancel() } interface LoginView : IView { + fun sendCodeSuccess(message: String) + fun onRegisterSuccess(message: String) + fun onForgetPsdSuccess(message: String) fun showUsernameError(message: String) fun showPasswordError(message: String) fun clearInputErrors() - fun onLoginSuccess(user: LoginUser) + fun onLoginSuccess(user: UserEntity) } } diff --git a/app/src/main/java/com/juhabi/birds/login/mvp/LoginImpl.kt b/app/src/main/java/com/juhabi/birds/login/mvp/LoginImpl.kt index d1f29e4..ca87619 100644 --- a/app/src/main/java/com/juhabi/birds/login/mvp/LoginImpl.kt +++ b/app/src/main/java/com/juhabi/birds/login/mvp/LoginImpl.kt @@ -2,7 +2,8 @@ package com.juhabi.birds.login.mvp import android.os.Handler import android.os.Looper -import com.juhabi.birds.login.model.LoginUser +import com.juhabi.birds.login.model.UserEntity +import com.juhabi.birds.login.model.SendCodeEntity import com.juhabi.birds.network.ApiClient import com.juhabi.birds.network.BaseBean import com.juhabi.birds.network.NetworkManager @@ -13,45 +14,330 @@ import retrofit2.Response class LoginImpl : LoginContract.DataModel { private val mainHandler = Handler(Looper.getMainLooper()) - private var loginCall: Call>? = null + private var loginCall: Call>? = null - override fun login( + private val calls = mutableSetOf>() + + /** + * 发送手机验证码 + */ + override fun sendPhoneCode(json: JSONObject, callback: LoginContract.Callback>) { + enqueueSendCodeCall( + ApiClient.apiService.sendPhoneCode( + NetworkManager.getToken(), + NetworkManager.toRequestBody(json) + ), + callback + ) + } + + /** + * 手机号注册 + */ + override fun registerByPhone( json: JSONObject, - callback: LoginContract.Callback> + callback: LoginContract.Callback> + ) { + enqueueSendCodeCall( + ApiClient.apiService.registerByPhone( + NetworkManager.getToken(), + NetworkManager.toRequestBody(json) + ), + callback + ) + } + + private fun enqueueSendCodeCall( + call: Call>, + callback: LoginContract.Callback> + ) { + calls += call + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + calls -= call + mainHandler.post { + val bean = response.body() + if (response.isSuccessful && bean != null) { + callback.success(bean) + } else { + callback.fail("接口响应异常:${response.code()}") + } + } + } + + override fun onFailure( + call: Call>, + error: Throwable + ) { + calls -= call + if (!call.isCanceled) { + mainHandler.post { + callback.fail(error.message ?: "网络请求失败") + } + } + } + }) + } + + override fun loginByPhonePsd( + json: JSONObject, + callback: LoginContract.Callback> + ) { + enqueueLoginCall( + ApiClient.apiService.loginByPhonePsd( + NetworkManager.getToken(), + NetworkManager.toRequestBody(json) + ), + callback + ) + } + + override fun loginByPhoneSms( + json: JSONObject, + callback: LoginContract.Callback> + ) { + enqueueLoginCall( + ApiClient.apiService.loginByPhoneSms( + NetworkManager.getToken(), + NetworkManager.toRequestBody(json) + ), + callback + ) + } + + override fun forgetPsdByPhone( + json: JSONObject, + callback: LoginContract.Callback> + ) { + enqueueLoginCall( + ApiClient.apiService.forgetPsdByPhone( + NetworkManager.getToken(), + NetworkManager.toRequestBody(json) + ), + callback + ) + } + + + override fun sendEmailCode( + json: JSONObject, + callback: LoginContract.Callback> + ) { + val call = ApiClient.apiService.sendEmailCode( + NetworkManager.getToken(), + NetworkManager.toRequestBody(json) + ) + calls += call + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + calls -= call + mainHandler.post { + val bean = response.body() + if (response.isSuccessful && bean != null) { + callback.success(bean) + } else { + callback.fail("接口响应异常:${response.code()}") + } + } + } + + override fun onFailure(call: Call>, error: Throwable) { + calls -= call + if (!call.isCanceled) { + mainHandler.post { + callback.fail(error.message ?: "网络请求失败") + } + } + } + }) + } + + override fun registerByEmail( + json: JSONObject, + callback: LoginContract.Callback> + ) { + val call = ApiClient.apiService.registerByE( + NetworkManager.getToken(), + NetworkManager.toRequestBody(json) + ) + calls += call + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + calls -= call + mainHandler.post { + val bean = response.body() + if (response.isSuccessful && bean != null) { + callback.success(bean) + } else { + callback.fail("接口响应异常:${response.code()}") + } + } + } + + override fun onFailure(call: Call>, error: Throwable) { + calls -= call + if (!call.isCanceled) { + mainHandler.post { + callback.fail(error.message ?: "网络请求失败") + } + } + } + }) + } + + override fun loginByEmailPsd( + json: JSONObject, + callback: LoginContract.Callback> + ) { + val call = ApiClient.apiService.loginByEmailPsd( + NetworkManager.getToken(), + NetworkManager.toRequestBody(json) + ) + calls += call + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + calls -= call + mainHandler.post { + val bean = response.body() + if (response.isSuccessful && bean != null) { + callback.success(bean) + } else { + callback.fail("接口响应异常:${response.code()}") + } + } + } + + override fun onFailure(call: Call>, error: Throwable) { + calls -= call + if (!call.isCanceled) { + mainHandler.post { + callback.fail(error.message ?: "网络请求失败") + } + } + } + }) + } + + override fun loginByEmailSms( + json: JSONObject, + callback: LoginContract.Callback> + ) { + val call = ApiClient.apiService.loginByEmailSms( + NetworkManager.getToken(), + NetworkManager.toRequestBody(json) + ) + calls += call + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + calls -= call + mainHandler.post { + val bean = response.body() + if (response.isSuccessful && bean != null) { + callback.success(bean) + } else { + callback.fail("接口响应异常:${response.code()}") + } + } + } + + override fun onFailure(call: Call>, error: Throwable) { + calls -= call + if (!call.isCanceled) { + mainHandler.post { + callback.fail(error.message ?: "网络请求失败") + } + } + } + }) + } + + override fun forgetPsdByEmail( + json: JSONObject, + callback: LoginContract.Callback> + ) { + val call = ApiClient.apiService.forgetPsdByEmail( + NetworkManager.getToken(), + NetworkManager.toRequestBody(json) + ) + calls += call + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + calls -= call + mainHandler.post { + val bean = response.body() + if (response.isSuccessful && bean != null) { + callback.success(bean) + } else { + callback.fail("接口响应异常:${response.code()}") + } + } + } + + override fun onFailure(call: Call>, error: Throwable) { + calls -= call + if (!call.isCanceled) { + mainHandler.post { + callback.fail(error.message ?: "网络请求失败") + } + } + } + }) + } + + private fun enqueueLoginCall( + call: Call>, + callback: LoginContract.Callback> ) { loginCall?.cancel() - loginCall = ApiClient.apiService - .login(NetworkManager.getToken(), NetworkManager.toRequestBody(json)) - .also { call -> - call.enqueue(object : Callback> { - override fun onResponse( - call: Call>, - response: Response> - ) { - mainHandler.post { - val bean = response.body() - if (response.isSuccessful && bean != null) { - callback.success(bean) - } else { - callback.fail("接口响应异常:${response.code()}") - } + loginCall = call + call.enqueue(object : Callback> { + override fun onResponse( + call: Call>, + response: Response> + ) { + mainHandler.post { + val bean = response.body() + if (response.isSuccessful && bean != null) { + callback.success(bean) + } else { + callback.fail("接口响应异常:${response.code()}") } } + } - override fun onFailure(call: Call>, error: Throwable) { - if (!call.isCanceled) { - mainHandler.post { - callback.fail(error.message ?: "网络请求失败") - } + override fun onFailure(call: Call>, error: Throwable) { + if (!call.isCanceled) { + mainHandler.post { + callback.fail(error.message ?: "网络请求失败") } } - }) - } + } + }) } override fun cancel() { loginCall?.cancel() loginCall = null + calls.forEach { it.cancel() } + calls.clear() mainHandler.removeCallbacksAndMessages(null) } } diff --git a/app/src/main/java/com/juhabi/birds/login/mvp/LoginPresenter.kt b/app/src/main/java/com/juhabi/birds/login/mvp/LoginPresenter.kt index 4106ebd..b723bc9 100644 --- a/app/src/main/java/com/juhabi/birds/login/mvp/LoginPresenter.kt +++ b/app/src/main/java/com/juhabi/birds/login/mvp/LoginPresenter.kt @@ -1,44 +1,31 @@ package com.juhabi.birds.login.mvp import com.juhabi.birds.base.BasePresenter -import com.juhabi.birds.AppConfig -import com.juhabi.birds.login.model.LoginUser +import com.juhabi.birds.login.model.UserEntity +import com.juhabi.birds.login.model.SendCodeEntity import com.juhabi.birds.network.BaseBean import org.json.JSONObject class LoginPresenter(private val loginImpl: LoginImpl) : BasePresenter() { - fun login(username: String, password: String) { - val currentView = view ?: return - currentView.clearInputErrors() - if (username.isBlank()) { - currentView.showUsernameError("请输入账号") - return - } - if (password.isBlank()) { - currentView.showPasswordError("请输入密码") - return - } - - currentView.showLoading() + fun getPhoneSmsCode(phoneNumber: String, sendCodeSceneRegister: String) { val json = JSONObject(); - json.put("grantType", "password") - json.put("username", username.trim()) - json.put("password", password) - json.put("clientId", AppConfig.APP_CLIENT_ID) - json.put("userType", AppConfig.APP_USER_TYPE) - loginImpl.login( + json.put("phoneNumber", phoneNumber) + json.put("scene", sendCodeSceneRegister) + loginImpl.sendPhoneCode( json, - object : LoginContract.Callback> { - override fun success(data: BaseBean) { + object : LoginContract.Callback> { + override fun success(data: BaseBean) { val attachedView = view ?: return attachedView.hideLoading() - val user = data.data - if ((data.code == 0 || data.code == 200) && user != null) { - attachedView.onLoginSuccess(user) + // 发码接口通常只返回 code/msg,data 可能为 null,不能依赖 data 非空 + if (data.code == 0 || data.code == 200) { + attachedView.sendCodeSuccess( + data.message.ifBlank { "验证码已发送" } + ) } else { - attachedView.showError(data.message.ifBlank { "登录失败" }) + attachedView.showError(data.message.ifBlank { "服务异常" }) } } @@ -52,8 +39,310 @@ class LoginPresenter(private val loginImpl: LoginImpl) : BasePresenter> { + return object : LoginContract.Callback> { + override fun success(data: BaseBean) { + val attachedView = view ?: return + attachedView.hideLoading() + val user = data.data + if ((data.code == 0 || data.code == 200) && user != null) { + attachedView.onLoginSuccess(user) + } else { + attachedView.showError(data.message.ifBlank { "登录失败" }) + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + } + } + override fun onDestroy() { loginImpl.cancel() super.onDestroy() } + + /** + * 手机号注册 + */ + fun registerByPhone(phone: String, code: String, password: String) { + val attachedView = view ?: return + attachedView.showLoading() + val json = JSONObject() + json.put("phoneNumber", phone.trim()) + json.put("code", code.trim()) + json.put("password", password) + loginImpl.registerByPhone( + json, + object : LoginContract.Callback> { + override fun success(data: BaseBean) { + val currentView = view ?: return + currentView.hideLoading() + if (data.code == 0 || data.code == 200) { + currentView.onRegisterSuccess( + data.message.ifBlank { "注册成功" } + ) + } else { + currentView.showError(data.message.ifBlank { "注册失败" }) + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + } + ) + } + + /** + * 手机号 + 验证码修改密码 + * 入参:phoneNumber、code、newPassword + */ + fun forgetPsdByPhone(phoneNumber: String, code: String, newPassword: String) { + val attachedView = view ?: return + attachedView.showLoading() + val json = JSONObject() + json.put("phoneNumber", phoneNumber.trim()) + json.put("code", code.trim()) + json.put("newPassword", newPassword) + loginImpl.forgetPsdByPhone( + json, + object : LoginContract.Callback> { + override fun success(data: BaseBean) { + val currentView = view ?: return + currentView.hideLoading() + // 重置密码接口 data 可能为空,以 code 判断成功 + if (data.code == 0 || data.code == 200) { + currentView.onForgetPsdSuccess( + data.message.ifBlank { "密码修改成功" } + ) + } else { + currentView.showError(data.message.ifBlank { "密码修改失败" }) + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + } + ) + } + + /** + * 获取邮箱验证码 + * 入参:email、scene + */ + fun getEmailSmsCode(email: String, scene: String) { + val json = JSONObject() + json.put("email", email.trim()) + json.put("scene", scene) + loginImpl.sendEmailCode( + json, + object : LoginContract.Callback> { + override fun success(data: BaseBean) { + val attachedView = view ?: return + attachedView.hideLoading() + if (data.code == 0 || data.code == 200) { + attachedView.sendCodeSuccess( + data.message.ifBlank { "验证码已发送" } + ) + } else { + attachedView.showError(data.message.ifBlank { "服务异常" }) + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + } + ) + } + + /** + * 邮箱注册 + * 入参:email、code、password、nickName + */ + fun registerByEmail(email: String, code: String, password: String, nickName: String) { + val attachedView = view ?: return + attachedView.showLoading() + val json = JSONObject() + json.put("email", email.trim()) + json.put("code", code.trim()) + json.put("password", password) + json.put("nickName", nickName.trim()) + loginImpl.registerByEmail( + json, + object : LoginContract.Callback> { + override fun success(data: BaseBean) { + val currentView = view ?: return + currentView.hideLoading() + if (data.code == 0 || data.code == 200) { + currentView.onRegisterSuccess( + data.message.ifBlank { "注册成功" } + ) + } else { + currentView.showError(data.message.ifBlank { "注册失败" }) + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + } + ) + } + + /** + * 邮箱 + 密码登录 + * 入参:email、password + */ + fun loginByEmailPsd(email: String, password: String) { + val attachedView = view ?: return + attachedView.showLoading() + val json = JSONObject() + json.put("email", email.trim()) + json.put("password", password) + loginImpl.loginByEmailPsd( + json, + object : LoginContract.Callback> { + override fun success(data: BaseBean) { + val currentView = view ?: return + currentView.hideLoading() + val user = data.data + if ((data.code == 0 || data.code == 200) && user != null) { + currentView.onLoginSuccess(user) + } else { + currentView.showError(data.message.ifBlank { "登录失败" }) + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + } + ) + } + + /** + * 邮箱 + 验证码登录 + * 入参:email、code + */ + fun loginByEmailSms(email: String, code: String) { + val attachedView = view ?: return + attachedView.showLoading() + val json = JSONObject() + json.put("email", email.trim()) + json.put("code", code.trim()) + loginImpl.loginByEmailSms( + json, + object : LoginContract.Callback> { + override fun success(data: BaseBean) { + val currentView = view ?: return + currentView.hideLoading() + val user = data.data + if ((data.code == 0 || data.code == 200) && user != null) { + currentView.onLoginSuccess(user) + } else { + currentView.showError(data.message.ifBlank { "登录失败" }) + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + } + ) + } + + /** + * 邮箱 + 验证码修改密码 + * 入参:email、code、newPassword + */ + fun forgetPsdByEmail(email: String, code: String, newPassword: String) { + val attachedView = view ?: return + attachedView.showLoading() + val json = JSONObject() + json.put("email", email.trim()) + json.put("code", code.trim()) + json.put("newPassword", newPassword) + loginImpl.forgetPsdByEmail( + json, + object : LoginContract.Callback> { + override fun success(data: BaseBean) { + val currentView = view ?: return + currentView.hideLoading() + if (data.code == 0 || data.code == 200) { + currentView.onForgetPsdSuccess( + data.message.ifBlank { "密码修改成功" } + ) + } else { + currentView.showError(data.message.ifBlank { "密码修改失败" }) + } + } + + override fun fail(message: String) { + view?.apply { + hideLoading() + showError(message) + } + } + } + ) + } + } diff --git a/app/src/main/java/com/juhabi/birds/network/ApiService.kt b/app/src/main/java/com/juhabi/birds/network/ApiService.kt index 2b1924e..2a50e12 100644 --- a/app/src/main/java/com/juhabi/birds/network/ApiService.kt +++ b/app/src/main/java/com/juhabi/birds/network/ApiService.kt @@ -1,6 +1,7 @@ package com.juhabi.birds.network -import com.juhabi.birds.login.model.LoginUser +import com.juhabi.birds.login.model.UserEntity +import com.juhabi.birds.login.model.SendCodeEntity import com.juhabi.birds.main.model.AppVersionInfo import com.juhabi.birds.mine.model.MineUserInfo import okhttp3.RequestBody @@ -15,8 +16,105 @@ import retrofit2.http.POST * 项目统一接口定义。所有业务接口地址集中维护在这里。 */ interface ApiService { - @POST("auth/login") - fun login(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + + //发送手机验证码 + @POST("app/auth/sms/code") + fun sendPhoneCode(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + //手机号注册 + @POST("app/auth/register") + fun registerByPhone(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + + //手机+密码登录 + /** 入参 + * { + * "phoneNumber": "18764689178", + * "password": "Zz123456" + * } + */ + @POST("app/auth/login") + fun loginByPhonePsd(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + //手机+验证码 + /**入参 + * { + * "phoneNumber": "string", + * "code": "string" + * } + */ + @POST("app/auth/sms-login") + fun loginByPhoneSms(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + //手机号+验证码 修改密码 + /** + * { + * "phoneNumber": "string", + * "code": "string", + * "newPassword": "string" + * } + */ + @POST("app/auth/reset-pwd") + fun forgetPsdByPhone(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + + + //发送邮箱验证码 + /** + * { + * "email": "string", + * "scene": "string" + * } + */ + @POST("app/auth/email/code") + fun sendEmailCode(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + //邮箱注册 + /** + * { + * "email": "string", + * "code": "string", + * "password": "string", + * "nickName": "string" + * } + */ + @POST("app/auth/email/register") + fun registerByE(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + + //邮箱+密码登录 + /** 入参 + * { + * "email": "string", + * "password": "string" + * } + */ + @POST("app/auth/email/login") + fun loginByEmailPsd(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + //邮箱+验证码登录 + /**入参 + { + "email": "string", + "code": "string" + } + */ + @POST("app/auth/email/code-login") + fun loginByEmailSms(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + //邮箱+验证码 修改密码 + /** + * { + * "email": "string", + * "code": "string", + * "newPassword": "string" + * } + */ + @POST("app/auth/email/reset-pwd") + fun forgetPsdByEmail(@HeaderMap headers: Map, @Body requestBody: RequestBody): Call> + + @GET("app/v1/getUserInfo") fun getMineUserInfo(@HeaderMap headers: Map, @QueryMap params: Map): Call> diff --git a/app/src/main/res/layout/active_welcome.xml b/app/src/main/res/layout/active_welcome.xml new file mode 100644 index 0000000..31ce9dc --- /dev/null +++ b/app/src/main/res/layout/active_welcome.xml @@ -0,0 +1,31 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 5f88e20..c7d96df 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -1,88 +1,228 @@ - + android:background="@color/color_zhu" + android:fillViewport="true" + android:overScrollMode="never"> + android:paddingStart="10dp" + android:paddingEnd="10dp" + android:paddingBottom="32dp"> - + android:layout_gravity="center_horizontal" + android:layout_marginTop="110dp" + android:contentDescription="@string/app_chinese_name" + android:src="@mipmap/icon_app_logo" /> - - - - - - - - - - - - + android:gravity="center" + android:text="@string/app_chinese_name" + android:textColor="@color/color_white" + android:textSize="23sp" + android:textStyle="bold" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + diff --git a/app/src/main/res/layout/forget_psd_layout.xml b/app/src/main/res/layout/forget_psd_layout.xml new file mode 100644 index 0000000..acf8b29 --- /dev/null +++ b/app/src/main/res/layout/forget_psd_layout.xml @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/include_activity_back_gray_layout.xml b/app/src/main/res/layout/include_activity_back_gray_layout.xml new file mode 100644 index 0000000..a9603c9 --- /dev/null +++ b/app/src/main/res/layout/include_activity_back_gray_layout.xml @@ -0,0 +1,62 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/register_layout.xml b/app/src/main/res/layout/register_layout.xml new file mode 100644 index 0000000..2cefbad --- /dev/null +++ b/app/src/main/res/layout/register_layout.xml @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/img_back_black.png b/app/src/main/res/mipmap-hdpi/img_back_black.png new file mode 100644 index 0000000..0d15338 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/img_back_black.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_app_logo.png b/app/src/main/res/mipmap-xhdpi/icon_app_logo.png new file mode 100644 index 0000000..31202b7 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_app_logo.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_login_code.png b/app/src/main/res/mipmap-xhdpi/icon_login_code.png new file mode 100644 index 0000000..952f34d Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_login_code.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_login_phone.png b/app/src/main/res/mipmap-xhdpi/icon_login_phone.png new file mode 100644 index 0000000..b974c9d Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_login_phone.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_login_point_down.png b/app/src/main/res/mipmap-xhdpi/icon_login_point_down.png new file mode 100644 index 0000000..6015524 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_login_point_down.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_login_select.png b/app/src/main/res/mipmap-xhdpi/icon_login_select.png new file mode 100644 index 0000000..21c60b8 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_login_select.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_login_show.png b/app/src/main/res/mipmap-xhdpi/icon_login_show.png new file mode 100644 index 0000000..6d6d046 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_login_show.png differ diff --git a/app/src/main/res/mipmap-xhdpi/icon_login_tips.png b/app/src/main/res/mipmap-xhdpi/icon_login_tips.png new file mode 100644 index 0000000..4181593 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/icon_login_tips.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_app_logo.png b/app/src/main/res/mipmap-xxhdpi/icon_app_logo.png new file mode 100644 index 0000000..1a4fc9e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_app_logo.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_login_code.png b/app/src/main/res/mipmap-xxhdpi/icon_login_code.png new file mode 100644 index 0000000..cf02e74 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_login_code.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_login_phone.png b/app/src/main/res/mipmap-xxhdpi/icon_login_phone.png new file mode 100644 index 0000000..d662843 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_login_phone.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_login_point_down.png b/app/src/main/res/mipmap-xxhdpi/icon_login_point_down.png new file mode 100644 index 0000000..23796c5 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_login_point_down.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_login_select.png b/app/src/main/res/mipmap-xxhdpi/icon_login_select.png new file mode 100644 index 0000000..af3616c Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_login_select.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_login_show.png b/app/src/main/res/mipmap-xxhdpi/icon_login_show.png new file mode 100644 index 0000000..c781f1d Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_login_show.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/icon_login_tips.png b/app/src/main/res/mipmap-xxhdpi/icon_login_tips.png new file mode 100644 index 0000000..0507473 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/icon_login_tips.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icon_app_logo.png b/app/src/main/res/mipmap-xxxhdpi/icon_app_logo.png new file mode 100644 index 0000000..dc69131 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icon_app_logo.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icon_login_code.png b/app/src/main/res/mipmap-xxxhdpi/icon_login_code.png new file mode 100644 index 0000000..5ec995a Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icon_login_code.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icon_login_phone.png b/app/src/main/res/mipmap-xxxhdpi/icon_login_phone.png new file mode 100644 index 0000000..db4533d Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icon_login_phone.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icon_login_point_down.png b/app/src/main/res/mipmap-xxxhdpi/icon_login_point_down.png new file mode 100644 index 0000000..124a4e9 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icon_login_point_down.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icon_login_select.png b/app/src/main/res/mipmap-xxxhdpi/icon_login_select.png new file mode 100644 index 0000000..df945c6 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icon_login_select.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icon_login_show.png b/app/src/main/res/mipmap-xxxhdpi/icon_login_show.png new file mode 100644 index 0000000..262cdb9 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icon_login_show.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/icon_login_tips.png b/app/src/main/res/mipmap-xxxhdpi/icon_login_tips.png new file mode 100644 index 0000000..2ff2b58 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/icon_login_tips.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/img_back_black.png b/app/src/main/res/mipmap-xxxhdpi/img_back_black.png new file mode 100644 index 0000000..0d15338 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/img_back_black.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 88deb7f..320e937 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -46,7 +46,7 @@ #1D76FE #1A72FF - #00CA9D + #005431 #C8C8C8 #F6F6F6 #2E2E2E @@ -117,7 +117,6 @@ #F0F8F1 #FFFCEB #1D90FF - #8000CA9D @@ -139,6 +138,7 @@ #F7F8FA #FFFFFFFF #C51609 + #286F51 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d8fe5ac..1ecdd73 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,10 +1,52 @@ - KotMvpProject + 聚哈比‑萨克尔GPS智能隼眼罩 + JUMHABI Saqr GPS Smart Falcon Hood 账号登录 请输入账号和密码 手机号 密码 登录 + 手机注册 + 请输入账号 + 请输入密码 + 显示密码 + 忘记密码? + 忘记密码 + 切换手机号验证码登录 + 切换密码登录 + 我已阅读并同意用户协议和隐私政策以及定制条款 + 没有账号?立即注册 + 获取验证码 + %1$d秒后重新获取 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 登录成功后将自动保存 Token 前往登录页面 计划页顶部背景图