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
前往登录页面
计划页顶部背景图