feat: 新增个人信息管理功能

- 新增PersonalInfoCard个人信息卡片组件
- 新增PersonalInfoScreen个人信息屏幕
- 更新AppNavigation导航结构
This commit is contained in:
iammm0
2026-01-29 10:57:22 +08:00
parent e0199b13f5
commit 6a4b140da6
3 changed files with 302 additions and 23 deletions

View File

@@ -16,8 +16,8 @@ sealed class Screen(val route: String) {
}
object MyMemoir : Screen("my_memoir")
object Profile : Screen("profile")
object PersonalInfo : Screen("personal_info")
object Login : Screen("login")
object Register : Screen("register")
object ResetPassword : Screen("reset_password")
object AccountManagement : Screen("account_management")
object UpgradePlan : Screen("upgrade_plan")
@@ -88,6 +88,15 @@ fun AppNavigation(
) {
ProfileScreen(navController = navController)
}
composable(
route = Screen.PersonalInfo.route,
enterTransition = { slideInHorizontally() },
exitTransition = { slideOutHorizontally() },
popEnterTransition = { slideInHorizontallyFromLeft() },
popExitTransition = { slideOutHorizontallyToRight() }
) {
PersonalInfoScreen(navController = navController)
}
composable(
route = Screen.UpgradePlan.route,
enterTransition = { slideInVertically() },
@@ -190,9 +199,6 @@ fun AppNavigation(
popUpTo(Screen.Login.route) { inclusive = true }
}
},
onNavigateToRegister = {
navController.navigate(Screen.Register.route)
},
onNavigateToResetPassword = {
navController.navigate(Screen.ResetPassword.route)
},
@@ -204,25 +210,6 @@ fun AppNavigation(
}
)
}
composable(
route = Screen.Register.route,
enterTransition = { slideInHorizontally() },
exitTransition = { slideOutHorizontally() },
popEnterTransition = { slideInHorizontallyFromLeft() },
popExitTransition = { slideOutHorizontallyToRight() }
) {
RegisterScreen(
onRegisterSuccess = {
navController.popBackStack()
},
onNavigateToTerms = {
navController.navigate(Screen.Terms.route)
},
onNavigateToPrivacy = {
navController.navigate(Screen.Privacy.route)
}
)
}
composable(
route = Screen.ResetPassword.route,
enterTransition = { slideInHorizontally() },

View File

@@ -0,0 +1,97 @@
package com.huaga.life_echo.ui.components.profile
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.huaga.life_echo.ui.icons.AppIcons
@Composable
fun PersonalInfoCard(
nickname: String,
phone: String,
planName: String,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Card(
modifier = modifier
.fillMaxWidth()
.clickable(onClick = onClick),
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// 左侧:图标和信息
Row(
modifier = Modifier.weight(1f),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 图标
Box(
modifier = Modifier
.size(40.dp)
.clip(RoundedCornerShape(8.dp))
.background(MaterialTheme.colorScheme.primaryContainer),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = AppIcons.Person,
contentDescription = "个人信息",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(24.dp)
)
}
// 信息
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = nickname,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = phone,
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
// 右侧:套餐标签和箭头
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
PlanStatusBadge(planName = planName)
Icon(
imageVector = AppIcons.ChevronRight,
contentDescription = "查看详情",
tint = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.size(20.dp)
)
}
}
}
}

View File

@@ -0,0 +1,195 @@
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.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
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.profile.PlanStatusBadge
import com.huaga.life_echo.ui.theme.LightPurple
import com.huaga.life_echo.ui.viewmodel.AuthViewModel
import com.huaga.life_echo.ui.viewmodel.PaymentViewModel
import com.huaga.life_echo.ui.viewmodel.ProfileViewModel
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
import androidx.compose.ui.platform.LocalContext
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PersonalInfoScreen(
navController: androidx.navigation.NavHostController? = null,
authViewModel: AuthViewModel = viewModel(factory = ViewModelFactory(LocalContext.current)),
profileViewModel: ProfileViewModel = viewModel(factory = ViewModelFactory(LocalContext.current)),
paymentViewModel: PaymentViewModel = viewModel(factory = ViewModelFactory(LocalContext.current))
) {
val currentUser by authViewModel.currentUser.collectAsState()
val userProfile by profileViewModel.userProfile.collectAsState()
val currentPlan by paymentViewModel.currentPlan.collectAsState()
Scaffold(
topBar = {
TopAppBar(
title = { Text("个人信息") },
navigationIcon = {
IconButton(onClick = { navController?.popBackStack() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "返回"
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface
)
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// 个人信息卡片
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// 昵称
InfoRow(
label = "昵称",
value = userProfile?.nickname ?: currentUser?.nickname ?: "未设置"
)
HorizontalDivider()
// 手机号
InfoRow(
label = "手机号",
value = currentUser?.phone ?: "未设置"
)
// 邮箱(如果有)
if (!currentUser?.email.isNullOrBlank()) {
HorizontalDivider()
InfoRow(
label = "邮箱",
value = currentUser?.email ?: "未设置"
)
}
}
}
// 套餐信息卡片
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "当前套餐",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
PlanStatusBadge(
planName = currentPlan?.displayName ?: when (currentUser?.subscription_type) {
"free" -> "免费体验版"
"premium" -> "高级版"
"professional" -> "专业版"
else -> "免费体验版"
}
)
}
// 套餐详情(如果有)
currentPlan?.let { plan ->
HorizontalDivider()
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "套餐详情",
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
plan.features.forEach { feature ->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "",
fontSize = 14.sp,
color = LightPurple
)
Text(
text = feature,
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
}
}
}
}
@Composable
private fun InfoRow(
label: String,
value: String
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = label,
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = value,
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface
)
}
}