新增重置密码校验

This commit is contained in:
2024-10-04 23:37:38 +08:00
parent c2764754fd
commit 40bbb8a0a0
11 changed files with 199 additions and 100 deletions

View File

@@ -4,8 +4,9 @@ import android.content.Context
object ConstVars { object ConstVars {
// api 地址 // api 地址
// const val BASE_SERVER = "http://192.168.31.190:8088" const val BASE_SERVER = "https://8.137.22.101:8088" const val BASE_SERVER = "http://192.168.31.130:8088"
const val BASE_SERVER = "https://8.137.22.101:8088" // const val BASE_SERVER = "https://8.137.22.101:8088"
// const val BASE_SERVER = "https://8.137.22.101:8088"
const val MOMENT_LIKE_CHANNEL_ID = "moment_like" const val MOMENT_LIKE_CHANNEL_ID = "moment_like"
const val MOMENT_LIKE_CHANNEL_NAME = "Moment Like" const val MOMENT_LIKE_CHANNEL_NAME = "Moment Like"
@@ -27,10 +28,15 @@ object ConstVars {
*/ */
const val BANNER_IMAGE_MAX_SIZE = 2048 const val BANNER_IMAGE_MAX_SIZE = 2048
// 用户协议地址
const val DICT_KEY_PRIVATE_POLICY_URL = "private_policy"
// 重置邮箱间隔
const val DIC_KEY_RESET_EMAIL_INTERVAL = "send_reset_password_timeout"
} }
// //
enum class ErrorCode(val code: Int) { enum class ErrorCode(val code: Int) {
USER_EXIST(10001) USER_EXIST(40001),
USER_NOT_EXIST(40002),
} }
fun Context.getErrorMessageCode(code: Int?): String { fun Context.getErrorMessageCode(code: Int?): String {

View File

@@ -3,6 +3,7 @@ package com.aiosman.riderpro.data
import com.aiosman.riderpro.data.api.ApiClient import com.aiosman.riderpro.data.api.ApiClient
import com.aiosman.riderpro.data.api.DictItem import com.aiosman.riderpro.data.api.DictItem
interface DictService { interface DictService {
/** /**
* 获取字典项 * 获取字典项

View File

@@ -1,19 +1,17 @@
package com.aiosman.riderpro.ui.account package com.aiosman.riderpro.ui.account
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -23,33 +21,41 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.aiosman.riderpro.ConstVars
import com.aiosman.riderpro.ErrorCode
import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.LocalNavController
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountService
import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.AccountServiceImpl
import com.aiosman.riderpro.data.DictService
import com.aiosman.riderpro.data.DictServiceImpl
import com.aiosman.riderpro.data.ServiceException
import com.aiosman.riderpro.getErrorMessageCode
import com.aiosman.riderpro.ui.comment.NoticeScreenHeader import com.aiosman.riderpro.ui.comment.NoticeScreenHeader
import com.aiosman.riderpro.ui.composables.ActionButton import com.aiosman.riderpro.ui.composables.ActionButton
import com.aiosman.riderpro.ui.composables.StatusBarSpacer import com.aiosman.riderpro.ui.composables.StatusBarSpacer
import com.aiosman.riderpro.ui.composables.TextInputField import com.aiosman.riderpro.ui.composables.TextInputField
import com.aiosman.riderpro.ui.modifiers.noRippleClickable import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
fun ResetPasswordScreen() { fun ResetPasswordScreen() {
var username by remember { mutableStateOf("") } var username by remember { mutableStateOf("") }
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
val dictService: DictService = DictServiceImpl()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
var isSendSuccess by remember { mutableStateOf<Boolean?>(null) } var isSendSuccess by remember { mutableStateOf<Boolean?>(null) }
var isLoading by remember { mutableStateOf(false) } var isLoading by remember { mutableStateOf(false) }
val navController = LocalNavController.current val navController = LocalNavController.current
var usernameError by remember { mutableStateOf<String?>(null) } var usernameError by remember { mutableStateOf<String?>(null) }
var countDown by remember { mutableStateOf<Int?>(null) }
var countDownMax by remember { mutableStateOf(60) }
fun validate(): Boolean { fun validate(): Boolean {
if (username.isEmpty()) { if (username.isEmpty()) {
usernameError = context.getString(R.string.text_error_email_required) usernameError = context.getString(R.string.text_error_email_required)
@@ -58,6 +64,28 @@ fun ResetPasswordScreen() {
usernameError = null usernameError = null
return true return true
} }
LaunchedEffect(Unit) {
try {
dictService.getDictByKey(ConstVars.DIC_KEY_RESET_EMAIL_INTERVAL).let {
countDownMax = it.value.toInt()
}
} catch (e: Exception) {
countDownMax = 60
}
}
fun startCountDown() {
scope.launch {
countDown = countDownMax
while (countDown!! > 0) {
delay(1000)
countDown = countDown!! - 1
}
countDown = null
}
}
fun resetPassword() { fun resetPassword() {
if (!validate()) return if (!validate()) return
scope.launch { scope.launch {
@@ -65,6 +93,13 @@ fun ResetPasswordScreen() {
try { try {
accountService.resetPassword(username) accountService.resetPassword(username)
isSendSuccess = true isSendSuccess = true
startCountDown()
} catch (e: ServiceException) {
if (e.code == ErrorCode.USER_NOT_EXIST.code){
usernameError = context.getString(R.string.error_40002_user_not_exist)
} else {
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
isSendSuccess = false isSendSuccess = false
@@ -74,6 +109,7 @@ fun ResetPasswordScreen() {
} }
} }
Column( Column(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
@@ -91,80 +127,78 @@ fun ResetPasswordScreen() {
moreIcon = false moreIcon = false
) )
} }
Spacer(modifier = Modifier.height(72.dp))
Column( Column(
modifier = Modifier.padding(horizontal = 24.dp), modifier = Modifier.padding(horizontal = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Spacer(modifier = Modifier.height(36.dp)) TextInputField(
text = username,
if (isSendSuccess != null) { onValueChange = { username = it },
if (isSendSuccess!!) { hint = stringResource(R.string.text_hint_email),
Spacer(modifier = Modifier.height(16.dp)) enabled = !isLoading && countDown == null,
Text( error = usernameError,
text = stringResource(R.string.reset_mail_send_success), )
style = TextStyle( Spacer(modifier = Modifier.height(16.dp))
color = Color(0xFF333333), Box(
fontSize = 14.sp, modifier = Modifier.height(72.dp)
fontWeight = FontWeight.Bold ) {
isSendSuccess?.let {
if (it) {
Text(
text = stringResource(R.string.reset_mail_send_success),
style = TextStyle(
color = Color(0xFF333333),
fontSize = 14.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier.fillMaxSize()
) )
) } else {
} else { Text(
Spacer(modifier = Modifier.height(16.dp)) text = stringResource(R.string.reset_mail_send_failed),
Text( style = TextStyle(
text = stringResource(R.string.reset_mail_send_failed), color = Color(0xFF333333),
style = TextStyle( fontSize = 14.sp,
color = Color(0xFF333333), fontWeight = FontWeight.Bold
fontSize = 14.sp, ),
fontWeight = FontWeight.Bold modifier = Modifier.fillMaxSize()
) )
)
}
Spacer(modifier = Modifier.height(40.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.noRippleClickable {
navController.popBackStack()
}
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_back),
contentDescription = "Back",
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
androidx.compose.material3.Text(
stringResource(R.string.back_upper),
color = Color.Black,
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
}
} else {
Spacer(modifier = Modifier.height(120.dp))
TextInputField(
text = username,
onValueChange = { username = it },
hint = stringResource(R.string.text_hint_email),
enabled = !isLoading,
error = usernameError,
)
Spacer(modifier = Modifier.height(72.dp))
if (isLoading) {
CircularProgressIndicator()
} else {
ActionButton(
modifier = Modifier
.width(345.dp)
.height(48.dp),
text = stringResource(R.string.recover),
backgroundColor = Color(0xffda3832),
) {
resetPassword()
} }
} }
} }
ActionButton(
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
text = if (countDown != null) {
stringResource(R.string.resend, "(${countDown})")
} else {
stringResource(R.string.recover)
},
backgroundColor = Color(0xffda3832),
color = Color.White,
isLoading = isLoading,
contentPadding = PaddingValues(0.dp),
enabled = countDown == null,
) {
resetPassword()
}
isSendSuccess?.let {
Spacer(modifier = Modifier.height(16.dp))
ActionButton(
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
text = stringResource(R.string.back_upper),
contentPadding = PaddingValues(0.dp),
) {
navController.popBackStack()
}
}
} }
} }
} }

View File

@@ -1,14 +1,21 @@
package com.aiosman.riderpro.ui.composables package com.aiosman.riderpro.ui.composables
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -29,33 +36,82 @@ fun ActionButton(
leading: @Composable (() -> Unit)? = null, leading: @Composable (() -> Unit)? = null,
expandText: Boolean = false, expandText: Boolean = false,
contentPadding: PaddingValues = PaddingValues(vertical = 16.dp), contentPadding: PaddingValues = PaddingValues(vertical = 16.dp),
isLoading: Boolean = false,
loadingTextColor: Color = Color.White,
loadingText: String = "Loading",
loadingBackgroundColor: Color = Color(0xFFD95757),
disabledBackgroundColor: Color = Color(0xFFD0D0D0),
enabled: Boolean = true,
click: () -> Unit = {} click: () -> Unit = {}
) { ) {
val animatedBackgroundColor by animateColorAsState(
targetValue = run{
if (enabled) {
if (isLoading) {
loadingBackgroundColor
} else {
backgroundColor
}
} else {
disabledBackgroundColor
}
},
animationSpec = tween(300)
)
Box( Box(
modifier = modifier modifier = modifier
.clip(RoundedCornerShape(24.dp)) .clip(RoundedCornerShape(24.dp))
.background(backgroundColor) .background(animatedBackgroundColor)
.padding(contentPadding) .padding(contentPadding)
.noRippleClickable { .noRippleClickable {
click() if (enabled && !isLoading) {
} click()
}
},
contentAlignment = Alignment.CenterStart
) { ) {
Row( if (!isLoading) {
modifier = Modifier.align(Alignment.Center), Row(
verticalAlignment = Alignment.CenterVertically modifier = Modifier.align(Alignment.Center),
) { verticalAlignment = Alignment.CenterVertically
leading?.invoke() ) {
Text( leading?.invoke()
text, Text(
fontSize = 14.sp, text,
color = color, fontSize = 14.sp,
fontWeight = FontWeight.W600, color = color,
modifier = Modifier.let { fontWeight = FontWeight.W600,
if (expandText) it.weight(1f) else it modifier = Modifier.let {
}, if (expandText) it.weight(1f) else it
textAlign = if (expandText) TextAlign.Center else TextAlign.Start },
) textAlign = if (expandText) TextAlign.Center else TextAlign.Start
)
}
}else{
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.CenterStart
) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = Color.White
)
Text(
loadingText,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
color = loadingTextColor
)
}
}
} }
} }
} }

View File

@@ -28,7 +28,7 @@ fun Checkbox(
) { ) {
val backgroundColor by animateColorAsState(if (checked) Color.Black else Color.Transparent) val backgroundColor by animateColorAsState(if (checked) Color.Black else Color.Transparent)
val borderColor by animateColorAsState(if (checked) Color.Transparent else Color(0xffebebeb)) val borderColor by animateColorAsState(if (checked) Color.Transparent else Color(0xffebebeb))
val borderWidth by animateDpAsState(if (checked) 0.dp else 1.dp) val borderWidth by animateDpAsState(if (checked) 0.dp else 2.dp)
Box( Box(
modifier = Modifier modifier = Modifier

View File

@@ -31,6 +31,7 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import com.aiosman.riderpro.ConstVars
import com.aiosman.riderpro.R import com.aiosman.riderpro.R
import com.aiosman.riderpro.data.DictService import com.aiosman.riderpro.data.DictService
import com.aiosman.riderpro.data.DictServiceImpl import com.aiosman.riderpro.data.DictServiceImpl
@@ -53,7 +54,7 @@ fun PolicyCheckbox(
fun openPolicyModel() { fun openPolicyModel() {
scope.launch { scope.launch {
try { try {
val resp = dictService.getDictByKey("private_policy") val resp = dictService.getDictByKey(ConstVars.DICT_KEY_PRIVATE_POLICY_URL)
policyUrl = resp.value policyUrl = resp.value
showModal = true showModal = true

View File

@@ -67,7 +67,7 @@ fun TextInputField(
.clip(RoundedCornerShape(24.dp)) .clip(RoundedCornerShape(24.dp))
.background(Color(0xFFF7f7f7)) .background(Color(0xFFF7f7f7))
.border( .border(
width = 1.dp, width = 2.dp,
color = if (error == null) Color.Transparent else Color(0xFFE53935), color = if (error == null) Color.Transparent else Color(0xFFE53935),
shape = RoundedCornerShape(24.dp) shape = RoundedCornerShape(24.dp)
) )

View File

@@ -1,12 +1,9 @@
package com.aiosman.riderpro.ui.index.tabs.profile package com.aiosman.riderpro.ui.index.tabs.profile
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Log import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background

View File

@@ -168,7 +168,7 @@ fun UserAuthScreen() {
hint = stringResource(R.string.text_hint_email), hint = stringResource(R.string.text_hint_email),
error = emailError error = emailError
) )
Spacer(modifier = Modifier.padding(16.dp)) Spacer(modifier = Modifier.padding(8.dp))
TextInputField( TextInputField(
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),

View File

@@ -79,4 +79,6 @@
<string name="like_your_post">喜欢了你的动态</string> <string name="like_your_post">喜欢了你的动态</string>
<string name="favourite_your_post">收藏了你的动态</string> <string name="favourite_your_post">收藏了你的动态</string>
<string name="like_your_comment">喜欢了你的评论</string> <string name="like_your_comment">喜欢了你的评论</string>
<string name="resend">重新发送 %s</string>
<string name="error_40002_user_not_exist">用户不存在</string>
</resources> </resources>

View File

@@ -78,4 +78,6 @@
<string name="like_your_post">Like your post</string> <string name="like_your_post">Like your post</string>
<string name="favourite_your_post">Favourite your post</string> <string name="favourite_your_post">Favourite your post</string>
<string name="like_your_comment">Like your comment</string> <string name="like_your_comment">Like your comment</string>
<string name="resend">Resend in %s</string>
<string name="error_40002_user_not_exist">user not exist</string>
</resources> </resources>