feat: 新增个人信息管理功能
- 新增PersonalInfoCard个人信息卡片组件 - 新增PersonalInfoScreen个人信息屏幕 - 更新AppNavigation导航结构
This commit is contained in:
@@ -16,8 +16,8 @@ sealed class Screen(val route: String) {
|
|||||||
}
|
}
|
||||||
object MyMemoir : Screen("my_memoir")
|
object MyMemoir : Screen("my_memoir")
|
||||||
object Profile : Screen("profile")
|
object Profile : Screen("profile")
|
||||||
|
object PersonalInfo : Screen("personal_info")
|
||||||
object Login : Screen("login")
|
object Login : Screen("login")
|
||||||
object Register : Screen("register")
|
|
||||||
object ResetPassword : Screen("reset_password")
|
object ResetPassword : Screen("reset_password")
|
||||||
object AccountManagement : Screen("account_management")
|
object AccountManagement : Screen("account_management")
|
||||||
object UpgradePlan : Screen("upgrade_plan")
|
object UpgradePlan : Screen("upgrade_plan")
|
||||||
@@ -88,6 +88,15 @@ fun AppNavigation(
|
|||||||
) {
|
) {
|
||||||
ProfileScreen(navController = navController)
|
ProfileScreen(navController = navController)
|
||||||
}
|
}
|
||||||
|
composable(
|
||||||
|
route = Screen.PersonalInfo.route,
|
||||||
|
enterTransition = { slideInHorizontally() },
|
||||||
|
exitTransition = { slideOutHorizontally() },
|
||||||
|
popEnterTransition = { slideInHorizontallyFromLeft() },
|
||||||
|
popExitTransition = { slideOutHorizontallyToRight() }
|
||||||
|
) {
|
||||||
|
PersonalInfoScreen(navController = navController)
|
||||||
|
}
|
||||||
composable(
|
composable(
|
||||||
route = Screen.UpgradePlan.route,
|
route = Screen.UpgradePlan.route,
|
||||||
enterTransition = { slideInVertically() },
|
enterTransition = { slideInVertically() },
|
||||||
@@ -190,9 +199,6 @@ fun AppNavigation(
|
|||||||
popUpTo(Screen.Login.route) { inclusive = true }
|
popUpTo(Screen.Login.route) { inclusive = true }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onNavigateToRegister = {
|
|
||||||
navController.navigate(Screen.Register.route)
|
|
||||||
},
|
|
||||||
onNavigateToResetPassword = {
|
onNavigateToResetPassword = {
|
||||||
navController.navigate(Screen.ResetPassword.route)
|
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(
|
composable(
|
||||||
route = Screen.ResetPassword.route,
|
route = Screen.ResetPassword.route,
|
||||||
enterTransition = { slideInHorizontally() },
|
enterTransition = { slideInHorizontally() },
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user