feat: 新增前端认证相关屏幕
- 新增AccountManagementScreen账户管理屏幕 - 新增ResetPasswordScreen重置密码屏幕
This commit is contained in:
@@ -0,0 +1,701 @@
|
|||||||
|
package com.huaga.life_echo.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
|
import androidx.compose.material.icons.filled.VisibilityOff
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.huaga.life_echo.ui.components.auth.CompactSendSmsButton
|
||||||
|
import com.huaga.life_echo.ui.components.auth.PasswordStrengthIndicator
|
||||||
|
import com.huaga.life_echo.ui.components.auth.SmsCodeInput
|
||||||
|
import com.huaga.life_echo.ui.icons.AppIcons
|
||||||
|
import com.huaga.life_echo.ui.theme.LightPurple
|
||||||
|
import com.huaga.life_echo.ui.viewmodel.AuthViewModel
|
||||||
|
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账户管理页面
|
||||||
|
*
|
||||||
|
* 功能模块:
|
||||||
|
* 1. 修改密码
|
||||||
|
* 2. 修改手机号
|
||||||
|
* 3. 登出管理(当前设备/所有设备)
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun AccountManagementScreen(
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
onLogoutSuccess: () -> Unit,
|
||||||
|
viewModel: AuthViewModel = viewModel(
|
||||||
|
factory = ViewModelFactory(LocalContext.current)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
var showChangePasswordDialog by remember { mutableStateOf(false) }
|
||||||
|
var showChangePhoneDialog by remember { mutableStateOf(false) }
|
||||||
|
var showLogoutDialog by remember { mutableStateOf(false) }
|
||||||
|
var showLogoutAllDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val isLoading by viewModel.isLoading.collectAsState()
|
||||||
|
val errorMessage by viewModel.errorMessage.collectAsState()
|
||||||
|
val currentUser by viewModel.currentUser.collectAsState()
|
||||||
|
|
||||||
|
// 错误消息Snackbar
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(errorMessage) {
|
||||||
|
errorMessage?.let { error ->
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = error,
|
||||||
|
duration = SnackbarDuration.Long
|
||||||
|
)
|
||||||
|
viewModel.clearError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text("账户管理") },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onNavigateBack) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowBack,
|
||||||
|
contentDescription = "返回"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// 账户信息卡片
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "账户信息",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
InfoRow(label = "昵称", value = currentUser?.nickname ?: "")
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
InfoRow(label = "手机号", value = currentUser?.phone ?: "")
|
||||||
|
if (!currentUser?.email.isNullOrBlank()) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
InfoRow(label = "邮箱", value = currentUser?.email ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// 安全设置
|
||||||
|
Text(
|
||||||
|
text = "安全设置",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
AccountManagementItem(
|
||||||
|
icon = AppIcons.Lock,
|
||||||
|
title = "修改密码",
|
||||||
|
subtitle = "定期修改密码保护账户安全",
|
||||||
|
onClick = { showChangePasswordDialog = true }
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||||
|
|
||||||
|
AccountManagementItem(
|
||||||
|
icon = AppIcons.Phone,
|
||||||
|
title = "修改手机号",
|
||||||
|
subtitle = "当前手机号:${currentUser?.phone ?: ""}",
|
||||||
|
onClick = { showChangePhoneDialog = true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// 登出管理
|
||||||
|
Text(
|
||||||
|
text = "登出管理",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
AccountManagementItem(
|
||||||
|
icon = AppIcons.ExitToApp,
|
||||||
|
title = "登出当前设备",
|
||||||
|
subtitle = "仅登出当前设备",
|
||||||
|
onClick = { showLogoutDialog = true }
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||||
|
|
||||||
|
AccountManagementItem(
|
||||||
|
icon = AppIcons.DevicesOther,
|
||||||
|
title = "登出所有设备",
|
||||||
|
subtitle = "登出所有已登录的设备",
|
||||||
|
iconTint = MaterialTheme.colorScheme.error,
|
||||||
|
onClick = { showLogoutAllDialog = true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改密码对话框
|
||||||
|
if (showChangePasswordDialog) {
|
||||||
|
ChangePasswordDialog(
|
||||||
|
viewModel = viewModel,
|
||||||
|
onDismiss = { showChangePasswordDialog = false },
|
||||||
|
onSuccess = { showChangePasswordDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改手机号对话框
|
||||||
|
if (showChangePhoneDialog) {
|
||||||
|
ChangePhoneDialog(
|
||||||
|
viewModel = viewModel,
|
||||||
|
onDismiss = { showChangePhoneDialog = false },
|
||||||
|
onSuccess = { showChangePhoneDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登出当前设备确认对话框
|
||||||
|
if (showLogoutDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showLogoutDialog = false },
|
||||||
|
title = { Text("确认登出") },
|
||||||
|
text = { Text("确定要登出当前设备吗?") },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.logout()
|
||||||
|
showLogoutDialog = false
|
||||||
|
onLogoutSuccess()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("确认", color = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showLogoutDialog = false }) {
|
||||||
|
Text("取消")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登出所有设备确认对话框
|
||||||
|
if (showLogoutAllDialog) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { showLogoutAllDialog = false },
|
||||||
|
title = { Text("确认登出所有设备") },
|
||||||
|
text = {
|
||||||
|
Text("确定要登出所有设备吗?这将使您在所有设备上的登录状态失效,需要重新登录。")
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.logoutAll {
|
||||||
|
showLogoutAllDialog = false
|
||||||
|
onLogoutSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("确认登出", color = MaterialTheme.colorScheme.error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { showLogoutAllDialog = false }) {
|
||||||
|
Text("取消")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账户管理菜单项
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun AccountManagementItem(
|
||||||
|
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||||
|
title: String,
|
||||||
|
subtitle: String? = null,
|
||||||
|
iconTint: Color = LightPurple,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onClick() }
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// 图标
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(iconTint.copy(alpha = 0.1f)),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = title,
|
||||||
|
tint = iconTint,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
|
// 文本
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
if (subtitle != null) {
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
Text(
|
||||||
|
text = subtitle,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 箭头
|
||||||
|
Icon(
|
||||||
|
imageVector = AppIcons.ChevronRight,
|
||||||
|
contentDescription = "进入",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信息行
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun InfoRow(label: String, value: String) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码对话框
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun ChangePasswordDialog(
|
||||||
|
viewModel: AuthViewModel,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onSuccess: () -> Unit
|
||||||
|
) {
|
||||||
|
var oldPassword by remember { mutableStateOf("") }
|
||||||
|
var newPassword by remember { mutableStateOf("") }
|
||||||
|
var confirmPassword by remember { mutableStateOf("") }
|
||||||
|
var oldPasswordVisible by remember { mutableStateOf(false) }
|
||||||
|
var newPasswordVisible by remember { mutableStateOf(false) }
|
||||||
|
var confirmPasswordVisible by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val isLoading by viewModel.isLoading.collectAsState()
|
||||||
|
val operationResult by viewModel.operationResult.collectAsState()
|
||||||
|
|
||||||
|
// 表单验证
|
||||||
|
val isOldPasswordValid = oldPassword.length >= 6
|
||||||
|
val isNewPasswordValid = newPassword.length >= 6
|
||||||
|
val isConfirmPasswordValid = confirmPassword == newPassword && confirmPassword.isNotEmpty()
|
||||||
|
val canSubmit = isOldPasswordValid && isNewPasswordValid && isConfirmPasswordValid && !isLoading
|
||||||
|
|
||||||
|
// 监听操作结果
|
||||||
|
LaunchedEffect(operationResult) {
|
||||||
|
if (operationResult?.success == true) {
|
||||||
|
kotlinx.coroutines.delay(1500)
|
||||||
|
viewModel.clearOperationResult()
|
||||||
|
onSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { if (!isLoading) onDismiss() },
|
||||||
|
title = { Text("修改密码") },
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
// 旧密码
|
||||||
|
OutlinedTextField(
|
||||||
|
value = oldPassword,
|
||||||
|
onValueChange = { oldPassword = it },
|
||||||
|
label = { Text("当前密码") },
|
||||||
|
placeholder = { Text("请输入当前密码") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
visualTransformation = if (oldPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(onClick = { oldPasswordVisible = !oldPasswordVisible }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (oldPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
|
||||||
|
contentDescription = if (oldPasswordVisible) "隐藏密码" else "显示密码"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = !isLoading
|
||||||
|
)
|
||||||
|
|
||||||
|
// 新密码
|
||||||
|
OutlinedTextField(
|
||||||
|
value = newPassword,
|
||||||
|
onValueChange = { newPassword = it },
|
||||||
|
label = { Text("新密码") },
|
||||||
|
placeholder = { Text("请输入新密码(至少6位)") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
visualTransformation = if (newPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(onClick = { newPasswordVisible = !newPasswordVisible }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (newPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
|
||||||
|
contentDescription = if (newPasswordVisible) "隐藏密码" else "显示密码"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = !isLoading,
|
||||||
|
isError = newPassword.isNotEmpty() && !isNewPasswordValid
|
||||||
|
)
|
||||||
|
|
||||||
|
// 密码强度指示器
|
||||||
|
if (newPassword.isNotEmpty()) {
|
||||||
|
PasswordStrengthIndicator(password = newPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认新密码
|
||||||
|
OutlinedTextField(
|
||||||
|
value = confirmPassword,
|
||||||
|
onValueChange = { confirmPassword = it },
|
||||||
|
label = { Text("确认新密码") },
|
||||||
|
placeholder = { Text("请再次输入新密码") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
visualTransformation = if (confirmPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(onClick = { confirmPasswordVisible = !confirmPasswordVisible }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (confirmPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
|
||||||
|
contentDescription = if (confirmPasswordVisible) "隐藏密码" else "显示密码"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = !isLoading,
|
||||||
|
isError = confirmPassword.isNotEmpty() && !isConfirmPasswordValid
|
||||||
|
)
|
||||||
|
|
||||||
|
if (confirmPassword.isNotEmpty() && !isConfirmPasswordValid) {
|
||||||
|
Text(
|
||||||
|
text = "两次输入的密码不一致",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示操作结果
|
||||||
|
operationResult?.let { result ->
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (result.success) AppIcons.CheckCircle else AppIcons.Error,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (result.success) Color(0xFF4CAF50) else MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = result.message,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = if (result.success) Color(0xFF4CAF50) else MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.changePassword(oldPassword, newPassword) {
|
||||||
|
// 成功回调已通过operationResult处理
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = canSubmit
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text("确认修改", color = LightPurple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = onDismiss,
|
||||||
|
enabled = !isLoading
|
||||||
|
) {
|
||||||
|
Text("取消")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改手机号对话框
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
private fun ChangePhoneDialog(
|
||||||
|
viewModel: AuthViewModel,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onSuccess: () -> Unit
|
||||||
|
) {
|
||||||
|
var newPhone by remember { mutableStateOf("") }
|
||||||
|
var code by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val isLoading by viewModel.isLoading.collectAsState()
|
||||||
|
val smsCountdown by viewModel.smsCountdown.collectAsState()
|
||||||
|
val operationResult by viewModel.operationResult.collectAsState()
|
||||||
|
|
||||||
|
// 表单验证
|
||||||
|
val isPhoneValid = newPhone.length == 11
|
||||||
|
val isCodeValid = code.length == 6
|
||||||
|
val canSubmit = isPhoneValid && isCodeValid && !isLoading
|
||||||
|
|
||||||
|
// 监听操作结果
|
||||||
|
LaunchedEffect(operationResult) {
|
||||||
|
if (operationResult?.success == true && operationResult?.message?.contains("手机号修改成功") == true) {
|
||||||
|
kotlinx.coroutines.delay(1500)
|
||||||
|
viewModel.clearOperationResult()
|
||||||
|
onSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { if (!isLoading) onDismiss() },
|
||||||
|
title = { Text("修改手机号") },
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
// 新手机号输入(带发送验证码按钮)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = newPhone,
|
||||||
|
onValueChange = {
|
||||||
|
if (it.length <= 11) {
|
||||||
|
newPhone = it.filter { char -> char.isDigit() }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = { Text("新手机号") },
|
||||||
|
placeholder = { Text("请输入新手机号") },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
|
||||||
|
enabled = !isLoading,
|
||||||
|
isError = newPhone.isNotEmpty() && !isPhoneValid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送验证码按钮
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
viewModel.sendVerificationCode(newPhone, "change_phone")
|
||||||
|
},
|
||||||
|
enabled = isPhoneValid && smsCountdown == 0 && !isLoading,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (smsCountdown > 0) {
|
||||||
|
"${smsCountdown}秒后重发"
|
||||||
|
} else {
|
||||||
|
"发送验证码"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证码输入
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "验证码",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
SmsCodeInput(
|
||||||
|
code = code,
|
||||||
|
onCodeChange = { code = it },
|
||||||
|
enabled = !isLoading,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示操作结果
|
||||||
|
operationResult?.let { result ->
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (result.success) AppIcons.CheckCircle else AppIcons.Error,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = if (result.success) Color(0xFF4CAF50) else MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = result.message,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = if (result.success) Color(0xFF4CAF50) else MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.changePhone(newPhone, code) {
|
||||||
|
// 成功回调已通过operationResult处理
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = canSubmit
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text("确认修改", color = LightPurple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = onDismiss,
|
||||||
|
enabled = !isLoading
|
||||||
|
) {
|
||||||
|
Text("取消")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
package com.huaga.life_echo.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
|
import androidx.compose.material.icons.filled.VisibilityOff
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.huaga.life_echo.ui.components.auth.CompactSendSmsButton
|
||||||
|
import com.huaga.life_echo.ui.components.auth.PasswordStrengthIndicator
|
||||||
|
import com.huaga.life_echo.ui.components.auth.SmsCodeInput
|
||||||
|
import com.huaga.life_echo.ui.icons.AppIcons
|
||||||
|
import com.huaga.life_echo.ui.theme.LightPurple
|
||||||
|
import com.huaga.life_echo.ui.viewmodel.AuthViewModel
|
||||||
|
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码重置页面
|
||||||
|
*
|
||||||
|
* 功能:
|
||||||
|
* - 输入手机号
|
||||||
|
* - 发送验证码(带倒计时)
|
||||||
|
* - 输入验证码
|
||||||
|
* - 输入新密码
|
||||||
|
* - 确认新密码
|
||||||
|
* - 提交重置
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ResetPasswordScreen(
|
||||||
|
onResetSuccess: () -> Unit,
|
||||||
|
onBackToLogin: () -> Unit,
|
||||||
|
viewModel: AuthViewModel = viewModel(
|
||||||
|
factory = ViewModelFactory(LocalContext.current)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
var phone by remember { mutableStateOf("") }
|
||||||
|
var code by remember { mutableStateOf("") }
|
||||||
|
var newPassword by remember { mutableStateOf("") }
|
||||||
|
var confirmPassword by remember { mutableStateOf("") }
|
||||||
|
var newPasswordVisible by remember { mutableStateOf(false) }
|
||||||
|
var confirmPasswordVisible by remember { mutableStateOf(false) }
|
||||||
|
var showSuccessDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val isLoading by viewModel.isLoading.collectAsState()
|
||||||
|
val errorMessage by viewModel.errorMessage.collectAsState()
|
||||||
|
val successMessage by viewModel.successMessage.collectAsState()
|
||||||
|
val smsCountdown by viewModel.smsCountdown.collectAsState()
|
||||||
|
val operationResult by viewModel.operationResult.collectAsState()
|
||||||
|
|
||||||
|
// 表单验证
|
||||||
|
val isPhoneValid = phone.length == 11
|
||||||
|
val isCodeValid = code.length == 6
|
||||||
|
val isNewPasswordValid = newPassword.length >= 6
|
||||||
|
val isConfirmPasswordValid = confirmPassword == newPassword && confirmPassword.isNotEmpty()
|
||||||
|
val canSubmit = isPhoneValid && isCodeValid && isNewPasswordValid && isConfirmPasswordValid && !isLoading
|
||||||
|
|
||||||
|
// 错误消息Snackbar
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
|
LaunchedEffect(errorMessage) {
|
||||||
|
errorMessage?.let { error ->
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = error,
|
||||||
|
duration = SnackbarDuration.Long
|
||||||
|
)
|
||||||
|
viewModel.clearError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听成功消息
|
||||||
|
LaunchedEffect(successMessage) {
|
||||||
|
successMessage?.let {
|
||||||
|
showSuccessDialog = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text("重置密码") },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBackToLogin) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ArrowBack,
|
||||||
|
contentDescription = "返回"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.padding(24.dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// 标题说明
|
||||||
|
Text(
|
||||||
|
text = "忘记密码了?",
|
||||||
|
fontSize = 24.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "通过手机验证码重置您的密码",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
// 手机号输入框(带发送验证码按钮)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = phone,
|
||||||
|
onValueChange = {
|
||||||
|
if (it.length <= 11) {
|
||||||
|
phone = it.filter { char -> char.isDigit() }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = { Text("手机号") },
|
||||||
|
placeholder = { Text("请输入11位手机号") },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
singleLine = true,
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
|
||||||
|
enabled = !isLoading,
|
||||||
|
isError = phone.isNotEmpty() && !isPhoneValid
|
||||||
|
)
|
||||||
|
|
||||||
|
CompactSendSmsButton(
|
||||||
|
countdown = smsCountdown,
|
||||||
|
enabled = isPhoneValid && !isLoading,
|
||||||
|
onClick = {
|
||||||
|
viewModel.sendVerificationCode(phone, "reset_password")
|
||||||
|
},
|
||||||
|
modifier = Modifier.width(100.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phone.isNotEmpty() && !isPhoneValid) {
|
||||||
|
Text(
|
||||||
|
text = "请输入11位手机号",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp, top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// 验证码输入
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "验证码",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
SmsCodeInput(
|
||||||
|
code = code,
|
||||||
|
onCodeChange = { code = it },
|
||||||
|
enabled = !isLoading,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (code.isNotEmpty() && !isCodeValid) {
|
||||||
|
Text(
|
||||||
|
text = "请输入6位验证码",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
// 新密码输入框
|
||||||
|
OutlinedTextField(
|
||||||
|
value = newPassword,
|
||||||
|
onValueChange = { newPassword = it },
|
||||||
|
label = { Text("新密码") },
|
||||||
|
placeholder = { Text("请输入新密码(至少6位)") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
visualTransformation = if (newPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(onClick = { newPasswordVisible = !newPasswordVisible }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (newPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
|
||||||
|
contentDescription = if (newPasswordVisible) "隐藏密码" else "显示密码"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = !isLoading,
|
||||||
|
isError = newPassword.isNotEmpty() && !isNewPasswordValid
|
||||||
|
)
|
||||||
|
|
||||||
|
if (newPassword.isNotEmpty() && !isNewPasswordValid) {
|
||||||
|
Text(
|
||||||
|
text = "密码至少需要6位",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp, top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 密码强度指示器
|
||||||
|
if (newPassword.isNotEmpty()) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
PasswordStrengthIndicator(
|
||||||
|
password = newPassword,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// 确认密码输入框
|
||||||
|
OutlinedTextField(
|
||||||
|
value = confirmPassword,
|
||||||
|
onValueChange = { confirmPassword = it },
|
||||||
|
label = { Text("确认新密码") },
|
||||||
|
placeholder = { Text("请再次输入新密码") },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
singleLine = true,
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
visualTransformation = if (confirmPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(onClick = { confirmPasswordVisible = !confirmPasswordVisible }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (confirmPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
|
||||||
|
contentDescription = if (confirmPasswordVisible) "隐藏密码" else "显示密码"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = !isLoading,
|
||||||
|
isError = confirmPassword.isNotEmpty() && !isConfirmPasswordValid
|
||||||
|
)
|
||||||
|
|
||||||
|
if (confirmPassword.isNotEmpty() && !isConfirmPasswordValid) {
|
||||||
|
Text(
|
||||||
|
text = "两次输入的密码不一致",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp, top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
// 重置密码按钮
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
viewModel.resetPassword(phone, code, newPassword) {
|
||||||
|
// 成功回调已通过successMessage处理
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(48.dp),
|
||||||
|
enabled = canSubmit,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = LightPurple,
|
||||||
|
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = "重置密码",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// 返回登录
|
||||||
|
TextButton(onClick = onBackToLogin) {
|
||||||
|
Text(
|
||||||
|
text = "返回登录",
|
||||||
|
color = LightPurple,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成功对话框
|
||||||
|
if (showSuccessDialog && operationResult?.success == true) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { },
|
||||||
|
title = {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = AppIcons.CheckCircle,
|
||||||
|
contentDescription = "成功",
|
||||||
|
tint = Color(0xFF4CAF50),
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "密码重置成功",
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = "您的密码已成功重置,请使用新密码登录",
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
showSuccessDialog = false
|
||||||
|
viewModel.clearSuccess()
|
||||||
|
viewModel.clearOperationResult()
|
||||||
|
onResetSuccess()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("去登录", color = LightPurple)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user