From f7cf112dc9c5bd6cd69564da307edf83ce886774 Mon Sep 17 00:00:00 2001 From: fengjignqi <15192412330@163.com> Date: Fri, 18 Sep 2026 09:59:43 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BB=E5=BD=95=E7=9B=B8=E5=85=B3=EF=BC=8C?= =?UTF-8?q?=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 16 +- .../main/java/com/juhabi/birds/AppConfig.kt | 14 +- .../java/com/juhabi/birds/MyApplication.kt | 16 + .../juhabi/birds/login/ForgetPsdActivity.kt | 217 +++++++++++ .../com/juhabi/birds/login/LoginActivity.kt | 199 ++++++++-- .../juhabi/birds/login/RegisterActivity.kt | 192 ++++++++++ .../com/juhabi/birds/login/WelcomeActivity.kt | 86 +++++ .../com/juhabi/birds/login/model/LoginUser.kt | 18 - .../birds/login/model/SendCodeEntity.kt | 7 + .../juhabi/birds/login/model/UserEntity.kt | 11 + .../juhabi/birds/login/mvp/LoginContract.kt | 30 +- .../com/juhabi/birds/login/mvp/LoginImpl.kt | 338 +++++++++++++++-- .../juhabi/birds/login/mvp/LoginPresenter.kt | 343 ++++++++++++++++-- .../com/juhabi/birds/network/ApiService.kt | 104 +++++- app/src/main/res/layout/active_welcome.xml | 31 ++ app/src/main/res/layout/activity_login.xml | 270 ++++++++++---- app/src/main/res/layout/forget_psd_layout.xml | 277 ++++++++++++++ .../include_activity_back_gray_layout.xml | 62 ++++ app/src/main/res/layout/register_layout.xml | 258 +++++++++++++ .../main/res/mipmap-hdpi/img_back_black.png | Bin 0 -> 1311 bytes .../main/res/mipmap-xhdpi/icon_app_logo.png | Bin 0 -> 3710 bytes .../main/res/mipmap-xhdpi/icon_login_code.png | Bin 0 -> 1737 bytes .../res/mipmap-xhdpi/icon_login_phone.png | Bin 0 -> 532 bytes .../mipmap-xhdpi/icon_login_point_down.png | Bin 0 -> 285 bytes .../res/mipmap-xhdpi/icon_login_select.png | Bin 0 -> 564 bytes .../main/res/mipmap-xhdpi/icon_login_show.png | Bin 0 -> 636 bytes .../main/res/mipmap-xhdpi/icon_login_tips.png | Bin 0 -> 1064 bytes .../main/res/mipmap-xxhdpi/icon_app_logo.png | Bin 0 -> 6051 bytes .../res/mipmap-xxhdpi/icon_login_code.png | Bin 0 -> 3112 bytes .../res/mipmap-xxhdpi/icon_login_phone.png | Bin 0 -> 1057 bytes .../mipmap-xxhdpi/icon_login_point_down.png | Bin 0 -> 482 bytes .../res/mipmap-xxhdpi/icon_login_select.png | Bin 0 -> 1149 bytes .../res/mipmap-xxhdpi/icon_login_show.png | Bin 0 -> 1438 bytes .../res/mipmap-xxhdpi/icon_login_tips.png | Bin 0 -> 1995 bytes .../main/res/mipmap-xxxhdpi/icon_app_logo.png | Bin 0 -> 6407 bytes .../res/mipmap-xxxhdpi/icon_login_code.png | Bin 0 -> 3137 bytes .../res/mipmap-xxxhdpi/icon_login_phone.png | Bin 0 -> 501 bytes .../mipmap-xxxhdpi/icon_login_point_down.png | Bin 0 -> 461 bytes .../res/mipmap-xxxhdpi/icon_login_select.png | Bin 0 -> 831 bytes .../res/mipmap-xxxhdpi/icon_login_show.png | Bin 0 -> 694 bytes .../res/mipmap-xxxhdpi/icon_login_tips.png | Bin 0 -> 1943 bytes .../res/mipmap-xxxhdpi/img_back_black.png | Bin 0 -> 1311 bytes app/src/main/res/values/colors.xml | 4 +- app/src/main/res/values/strings.xml | 44 ++- 45 files changed, 2362 insertions(+), 177 deletions(-) create mode 100644 app/src/main/java/com/juhabi/birds/login/ForgetPsdActivity.kt create mode 100644 app/src/main/java/com/juhabi/birds/login/RegisterActivity.kt create mode 100644 app/src/main/java/com/juhabi/birds/login/WelcomeActivity.kt delete mode 100644 app/src/main/java/com/juhabi/birds/login/model/LoginUser.kt create mode 100644 app/src/main/java/com/juhabi/birds/login/model/SendCodeEntity.kt create mode 100644 app/src/main/java/com/juhabi/birds/login/model/UserEntity.kt create mode 100644 app/src/main/res/layout/active_welcome.xml create mode 100644 app/src/main/res/layout/forget_psd_layout.xml create mode 100644 app/src/main/res/layout/include_activity_back_gray_layout.xml create mode 100644 app/src/main/res/layout/register_layout.xml create mode 100644 app/src/main/res/mipmap-hdpi/img_back_black.png create mode 100644 app/src/main/res/mipmap-xhdpi/icon_app_logo.png create mode 100644 app/src/main/res/mipmap-xhdpi/icon_login_code.png create mode 100644 app/src/main/res/mipmap-xhdpi/icon_login_phone.png create mode 100644 app/src/main/res/mipmap-xhdpi/icon_login_point_down.png create mode 100644 app/src/main/res/mipmap-xhdpi/icon_login_select.png create mode 100644 app/src/main/res/mipmap-xhdpi/icon_login_show.png create mode 100644 app/src/main/res/mipmap-xhdpi/icon_login_tips.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_app_logo.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_login_code.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_login_phone.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_login_point_down.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_login_select.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_login_show.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_login_tips.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icon_app_logo.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icon_login_code.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icon_login_phone.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icon_login_point_down.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icon_login_select.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icon_login_show.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/icon_login_tips.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/img_back_black.png 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 0000000000000000000000000000000000000000..0d153381289ad59f8f45367c4eff655e5283ba8c GIT binary patch literal 1311 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..952f34d83f417e518e66cc4c184c7d46491715d1 GIT binary patch literal 1737 zcmV;)1~&PLP)fHdd^ENGqi@k_wSj+ER-Jkp}$12}&ypE%+k=TT~EC zv7o_d6e1RpnmR6%)2-9 z=FOY;ee-5^c9U!T&;M2dtn>n|u7J}DghHV;<>locRa8`r!~cZ87eeNwo2|_E6~N)L zoSd9)=oy?VhOdJkf&T)Ct<-%jz5*c9PvYHWoZSddD9A#&xw&0{I0_XebY{`;13&=B zYl-}M{Ak6|HE=1>xw*M8bc{w6l2QnWx6z3J;*bckr6mI>DJi)dC+czh8cww1R25v6 z`T6-VD!Wb7Y=YhaL=7?%nJVb61IVY0mHp9zojs5Su5`Tqcm}`(Tbb}>qTK@jt)QUb z8csCeOvpYNj04m;EQuShJ&p{dRrtCdCD|RSpifR3bH7mUP5mh(l*GcY(}U6F~`?3H|?~= z6#$OEg@s*EQo)(uq1U3(=z7cXpOTao3DD~U$UeZXhwA7SFq80v0)QF>N1?RE6#x$I z)fT9CLCs7Yo}QlGfHRw*6QiS}e`_7DoD~^^J`*s|TI_BSyYR2H4GV;BI&xP47*RQn zVH0N`hMr~Ors3h?>y8$eCX8XXPwc`EU_!6R;#8$8vA681qW)p^mo zDFKkSI)%i?w6tOXI3jDHEl%F)6at9r!)-+X@LXx&c>WgpnTKMT69})eWd{%ng`UH? zdPqt>N9P?>PEf!=2fQcmP0n6w?9F=>M)%=eE@=l31tzt+bADNk% zd2D=q+{;nv%L=HcBF<_@#ScY8Lqk`nd5MMz43{cw6}YU&&feGQ-CxIDm# z(~T@`VIFxWP&(Ar+^Uh{#}xq4y{yNQ4m)+-&bdX(`>)(0^@N2?Zr2Ut{5SS7Y3lOA zqq+gzqbg~Bya4F5jgP+@*kq~-h%vxQ#D|r@`a$*vY4(ZLbY%dS7a6VR`hw&N081Aj zy^-bq;*>_H7zTIw0Zy1lar%Z57)BGaH?6uW3kHMRQIfqTd!~n=WPb`-Mp`i}Me_Tw zR8kR}^a`8xK4?FYe?FC-lbmOTd<;`v1VLUkRXyF0mlZHL`kHMBGO@5DkM^kR^2zo$tBMZc`%C2_K}g1 zguNs}C-MZq8h8Y*_rh)>c8Inw#ahv@Hb$@-blr`62Eb;#is5+ViP$MN-MwxTc<5(A z1CpXT`fbeB=V6>u@eDw;0q{Cpt`9Kdw!FN&c5b3sy^wJAc$K45f0m4r`>%*`uapr0 z(=?-icpT&EBF4mWq;`-_vH=liRv7mLBjV|T?c7@$S{`oDqKzry&pCL zq&w4<5a&rleLQ2*=*0F3sboSK0}$h!?`MfteA59S1oe^*)za@m@;Sa9=TD=jFNfE; zHpo>3QD$XH0AvcH^)Vpi!J}3JnLI%|0r3IRbxbMbJ6Wk8(6y(HiJe`fr|VOfaY<## z0K}4jfXn^Jes2#G-3X4p513l08pjXQK99VMm5N{17~9J?)*aXxpuVJ%6#$u0(utE} zGf|A+T9M0b^aXf3)QRJbIId3`M26gT)g*Yo)fKpS2zz6xNuBAsfUe*pjh|Np&;&lCUv f00v1!K~w_(pdzz>&ysO700000NkvXXu0mjf-~Ti3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b974c9d0c23a1c17b8020cc6545e30e432df5cad GIT binary patch literal 532 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sO9OmDT!AzYC@3h1ii!eBDJdx+ zDJ?CnsHi9_D+?ikA`qYi20%8D29iKoG!iHd5riPp zDWG`xCC+_7SDKXs`2{nuaPSC-NXW=5tLqq;+dF&vN5?0pXXh7}msi)&+wf@Y__fZ&=4v zkoRn{X-{9-u|mGpZK`t)-MC}YdSn*A;?yM;p4(L>s?2TN+`{u~(iWY&zg0XnQ{p)e zu2RwYE^tL|hpzb9+7{WYg<@F`&vP%Fz2gCcVg$ow<`y}IyYeE3+#e|Zh_m=v^Xv1^ z+>l*~8JyD-!p}}!9#|D%(p&jj((2i=>WZDGSM``1Y_?t(nh<_1qB5l14tr^1KB`9xCk<%G2>?z&_t<{AirP+MphniWfN0ZH{XER{I+=;j$OU> zDmWO7wJb43UtGJ;*NBWFX>t(0lWRz#j*0|99%0GDpSuO1F}CuV&&5 zA-Cl&?*BrcOg#fw%qb}^8tl) ig^+cZqJ%QfNcq|KFbKIEw)1-l@}#G$pUXO@geCw+K2^W~ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..21c60b8f689e49d0719baf8edbc7c31c2399535c GIT binary patch literal 564 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy>jHd2T!Hj|vH(MfAxoGEN2EDV zjFmu~twN@kdS0M@Nw{@QqJ3S8b7Q)DOO|(go_}Xya8F5iUwQnLy5wn%^($vIubJJx zZeI7sg_F0hn7(uM{QVm@pEtfa**kmgnX4~fz4`j;adBn@Epsh8|E46Iy2goNpwubD#DpyPhlYiCxWXh8| zwt~N>Z-|IRxpnFkyFTp9eGuqx8Pt=dq4R#-wp|RX!_2odO+8n<`s(?r_UOY?Zv1#R zd+Wtr`z{*TZ)eFN%VYL1SFt(v7B>A8FG|bbd-(A``#yvDQ(ip3qyh{~ N22WQ%mvv4FO#s6_9uEKj literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6d6d046844e3219a4828e8ca62890fd499dc5a5a GIT binary patch literal 636 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-s(*k@#T!FL$HsIvs1QB*b0JX3V`ZiGA^hx2p&)YkONl+;Uc7AETDM#j5X7NE;B3%@(X5Q;ouPvk&uyBP*K;` zH+FXS_K#1`&Mz*n@1H(@@$%K{H*ep)d;js%*Kgl{{QUL%&);qTzxQQeU`+9JaSZV| z{x;;~v?c?A9>ofOiRL6TiP;Q zcby#X*5E&TkC?-)x90gpEKzf>SPF^GtBN^wBQWtf=Z?73>Mb+=aRh&S6%dqmC@aiP z&dXbv!#jCeLx9_5_CK5U_SP(AVQ&(2;t>8Ivh!5ns)9L5(H@5PaAaKzZeIPMF7C4Kk~@K0!bRSeh{R^EZrCs* zjZuv+)C%L*>s6Y_7#&)1EXlUKaLz9@Bad`u&A zZg0xc1F5kWd(SXB b{13KE<@)Iye1bP0l+XkK6`S;a literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4181593d344644e2839245ac8926e19098c7d32d GIT binary patch literal 1064 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 C3mpvr literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cf02e748a35753f22243b30022de98ea41d35e14 GIT binary patch literal 3112 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..23796c5e103c1918f680e3bb3ccb4a353e930f52 GIT binary patch literal 482 zcmeAS@N?(olHy`uVBq!ia0vp^(m*W1!3-pA=9QfUQmX@eLR^6~1Q;6|tEi|*NlB@z zs{1X9%#loPZ!4!3CYrftxQc0A}j}7T?5{T?cVTM zVDI1m`o;zf^Nly`?h<->tKj^Uxv?7m7BBeGf5;(t+3yqUHKLy-ZmIS9vyJhIjhC3^ z9aD|W2bqgzE;-I2YUXoGM9}Ly(=pb)CUJ92A7A6Mo7I#3P}?~5ib-?Fr-||HMvKlo zY|Wp(uye+xdHKJEgsf&M-gTU0nx2vN(Eaa`_F&oln-*)Ie67LlnCe~>-FRe0<(E~O aHFp>$LI;n2O^oCV48duG1 zUNgIUrU+5aQeWe(+4-7IkfHk zk?rS??znJl*Tv(zFP_+U`P9)H7f#;3eD2=$^Y?FDdvfpkllwQHKDhVt$^Dm4@4tHb z@b&XYub)4D^Ww?dmoMJGdG+z#+fN_fe*XCW%cqauzI^)j<@5KiU%r3)^5fgrAK$(}o;fByUhA`tlZ?;kM64sX}j14f=|NswPK10xd)JBNUvu!w?^k)u~q zenClT>)egow%>gG^yS+RA3y*4^Y`DzS+`CwFfjRfx;Tb-9A6vuGCs&rWUtK>Sr&O~ z4x0x7QBq;cawBf;d+$2?W>zZqwwp=S5m9^R?BAi^a3=Yg;Rb9zU6 zQu*(ui4j`UeUBV#g9xb8$q36n5A4wTIbhpPOZ9BSh*-U}VtBHaZ z6^o@NJ9Jk?|Fk`6Gg)c=8^a5`|462F`7D@S{NejXvlrY2&rfdHc4Ez(Q;&Q%@lE)0 zE%|BBGwZ?=?y{!rp~5&OO)Ozoc3IjxuMZp`~P8IJK=wKSQr?r z^Zu@yT_ZB(v;F*c>({WCg_RsOiacfNFMjIz^sK|;y8~~0UXyxXrhG|jQQ_sY zw`Qp~esn*r%cuG7(o-d_rIyDek}oq$)UDZmPv@GVqra{&7FbX?wdwLd9WyPZBTRdG8bl!J3P|r8j(Rt7zWguEKQx+&bG&o^J#; zaiu;xc`ed&VS#7Mn_W96OgyYH)m|m&Wz1TM|6A_}2QH{}{8%iy!urCxl%Ko*t<_k! z=*^^~iu!N-PXEu4RXOXqKC;-!rK>l;{>`y(&-wM=OrIT_eNq~j6d62S{an^LB{Ts5 DH%ot9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c781f1d77dbbf61caaeb044b4a70760ca7e06250 GIT binary patch literal 1438 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 bK~w_(`EITK*Wj3J00000NkvXXu0mjfob=I1 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..db4533dcebed1e84148df9396cf9218f39ecff5e GIT binary patch literal 501 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 #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 前往登录页面 计划页顶部背景图