feat: 新增前端认证UI组件
- 新增auth/认证相关UI组件
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
package com.huaga.life_echo.ui.components.auth
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
/**
|
||||
* 密码强度等级
|
||||
*/
|
||||
enum class PasswordStrength(val label: String, val color: Color) {
|
||||
WEAK("弱", Color(0xFFEF5350)),
|
||||
MEDIUM("中", Color(0xFFFF9800)),
|
||||
STRONG("强", Color(0xFF66BB6A))
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算密码强度
|
||||
*/
|
||||
fun calculatePasswordStrength(password: String): PasswordStrength? {
|
||||
if (password.isEmpty()) return null
|
||||
|
||||
var score = 0
|
||||
|
||||
// 长度评分
|
||||
when {
|
||||
password.length >= 12 -> score += 3
|
||||
password.length >= 8 -> score += 2
|
||||
password.length >= 6 -> score += 1
|
||||
}
|
||||
|
||||
// 包含小写字母
|
||||
if (password.any { it.isLowerCase() }) score += 1
|
||||
|
||||
// 包含大写字母
|
||||
if (password.any { it.isUpperCase() }) score += 1
|
||||
|
||||
// 包含数字
|
||||
if (password.any { it.isDigit() }) score += 1
|
||||
|
||||
// 包含特殊字符
|
||||
if (password.any { !it.isLetterOrDigit() }) score += 1
|
||||
|
||||
return when {
|
||||
score >= 6 -> PasswordStrength.STRONG
|
||||
score >= 4 -> PasswordStrength.MEDIUM
|
||||
else -> PasswordStrength.WEAK
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码强度指示器组件
|
||||
*
|
||||
* @param password 当前密码
|
||||
*/
|
||||
@Composable
|
||||
fun PasswordStrengthIndicator(
|
||||
password: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val strength = remember(password) { calculatePasswordStrength(password) }
|
||||
|
||||
if (strength != null) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 强度条
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
repeat(3) { index ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.height(4.dp)
|
||||
.background(
|
||||
color = when {
|
||||
index == 0 -> strength.color
|
||||
index == 1 && strength != PasswordStrength.WEAK -> strength.color
|
||||
index == 2 && strength == PasswordStrength.STRONG -> strength.color
|
||||
else -> MaterialTheme.colorScheme.surfaceVariant
|
||||
},
|
||||
shape = RoundedCornerShape(2.dp)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 强度文字
|
||||
Text(
|
||||
text = "强度:${strength.label}",
|
||||
fontSize = 12.sp,
|
||||
color = strength.color
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 密码要求提示组件
|
||||
*/
|
||||
@Composable
|
||||
fun PasswordRequirements(
|
||||
password: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "密码要求:",
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
PasswordRequirementItem(
|
||||
text = "至少6个字符",
|
||||
met = password.length >= 6
|
||||
)
|
||||
|
||||
PasswordRequirementItem(
|
||||
text = "建议包含大小写字母、数字和特殊字符",
|
||||
met = password.any { it.isLowerCase() } &&
|
||||
password.any { it.isUpperCase() } &&
|
||||
password.any { it.isDigit() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PasswordRequirementItem(
|
||||
text: String,
|
||||
met: Boolean
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = if (met) "✓" else "○",
|
||||
fontSize = 12.sp,
|
||||
color = if (met) {
|
||||
Color(0xFF66BB6A)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
)
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
fontSize = 12.sp,
|
||||
color = if (met) {
|
||||
Color(0xFF66BB6A)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.huaga.life_echo.ui.components.auth
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
/**
|
||||
* 发送短信验证码按钮组件
|
||||
*
|
||||
* @param countdown 倒计时秒数,0表示可以发送
|
||||
* @param enabled 是否启用按钮
|
||||
* @param onClick 点击回调
|
||||
*/
|
||||
@Composable
|
||||
fun SendSmsButton(
|
||||
countdown: Int,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val canSend = countdown == 0 && enabled
|
||||
|
||||
Button(
|
||||
onClick = onClick,
|
||||
enabled = canSend,
|
||||
modifier = modifier.height(56.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (canSend) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.surfaceVariant
|
||||
},
|
||||
contentColor = if (canSend) {
|
||||
MaterialTheme.colorScheme.onPrimary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = if (countdown > 0) {
|
||||
"${countdown}秒后重发"
|
||||
} else {
|
||||
"发送验证码"
|
||||
},
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 紧凑版发送短信验证码按钮(用于输入框右侧)
|
||||
*/
|
||||
@Composable
|
||||
fun CompactSendSmsButton(
|
||||
countdown: Int,
|
||||
enabled: Boolean = true,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val canSend = countdown == 0 && enabled
|
||||
|
||||
TextButton(
|
||||
onClick = onClick,
|
||||
enabled = canSend,
|
||||
modifier = modifier,
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
contentColor = if (canSend) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = if (countdown > 0) {
|
||||
"${countdown}s"
|
||||
} else {
|
||||
"获取验证码"
|
||||
},
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.huaga.life_echo.ui.components.auth
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
/**
|
||||
* 短信验证码输入框组件
|
||||
*
|
||||
* @param code 当前输入的验证码
|
||||
* @param onCodeChange 验证码变化回调
|
||||
* @param codeLength 验证码长度,默认6位
|
||||
* @param enabled 是否启用输入
|
||||
*/
|
||||
@Composable
|
||||
fun SmsCodeInput(
|
||||
code: String,
|
||||
onCodeChange: (String) -> Unit,
|
||||
codeLength: Int = 6,
|
||||
enabled: Boolean = true,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
BasicTextField(
|
||||
value = code,
|
||||
onValueChange = { newValue ->
|
||||
// 只允许输入数字,且不超过指定长度
|
||||
if (newValue.length <= codeLength && newValue.all { it.isDigit() }) {
|
||||
onCodeChange(newValue)
|
||||
}
|
||||
},
|
||||
enabled = enabled,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
textStyle = TextStyle(
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
modifier = modifier,
|
||||
decorationBox = { innerTextField ->
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
repeat(codeLength) { index ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.border(
|
||||
width = 2.dp,
|
||||
color = when {
|
||||
!enabled -> MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
|
||||
index == code.length -> MaterialTheme.colorScheme.primary
|
||||
index < code.length -> MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
|
||||
else -> MaterialTheme.colorScheme.outline
|
||||
},
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = if (index < code.length) code[index].toString() else "",
|
||||
style = TextStyle(
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = if (enabled) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.3f)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 隐藏原始输入框
|
||||
Box(modifier = Modifier.size(0.dp)) {
|
||||
innerTextField()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user