From 188946bbadd5446d2e04bf3f9dfea46779869233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=9C=A8=E5=9D=A4?= Date: Sat, 17 Jan 2026 19:35:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=BA=94=E7=94=A8=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E5=92=8C=E7=8E=B0=E6=9C=89=E5=B1=8F=E5=B9=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../life_echo/navigation/AppNavigation.kt | 47 +- .../ui/screens/CreateMemoryScreen.kt | 313 ++++++++++--- .../life_echo/ui/screens/MyMemoirScreen.kt | 410 ++++++++++++++---- .../life_echo/ui/screens/ProfileScreen.kt | 408 +++++++++++++---- 4 files changed, 938 insertions(+), 240 deletions(-) diff --git a/app-android/app/src/main/java/com/huaga/life_echo/navigation/AppNavigation.kt b/app-android/app/src/main/java/com/huaga/life_echo/navigation/AppNavigation.kt index b24fb92..4c2faa1 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/navigation/AppNavigation.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/navigation/AppNavigation.kt @@ -2,32 +2,67 @@ package com.huaga.life_echo.navigation import androidx.compose.runtime.Composable import androidx.navigation.NavHostController +import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import androidx.navigation.navArgument +import com.huaga.life_echo.ui.screens.ConversationListScreen import com.huaga.life_echo.ui.screens.CreateMemoryScreen +import com.huaga.life_echo.ui.screens.ExportDataScreen import com.huaga.life_echo.ui.screens.MyMemoirScreen +import com.huaga.life_echo.ui.screens.MyOrdersScreen import com.huaga.life_echo.ui.screens.ProfileScreen +import com.huaga.life_echo.ui.screens.UpgradePlanScreen sealed class Screen(val route: String) { - object CreateMemory : Screen("create_memory") + object ConversationList : Screen("conversation_list") + object CreateMemory : Screen("create_memory/{conversationId}") { + fun createRoute(conversationId: String = "new") = "create_memory/$conversationId" + } object MyMemoir : Screen("my_memoir") object Profile : Screen("profile") + object UpgradePlan : Screen("upgrade_plan") + object MyOrders : Screen("my_orders") + object ExportData : Screen("export_data") } @Composable fun AppNavigation(navController: NavHostController) { NavHost( navController = navController, - startDestination = Screen.CreateMemory.route + startDestination = Screen.ConversationList.route ) { - composable(Screen.CreateMemory.route) { - CreateMemoryScreen() + composable(Screen.ConversationList.route) { + ConversationListScreen( + onConversationClick = { conversationId -> + navController.navigate(Screen.CreateMemory.createRoute(conversationId)) + } + ) + } + composable( + route = Screen.CreateMemory.route, + arguments = listOf(navArgument("conversationId") { type = NavType.StringType }) + ) { backStackEntry -> + val conversationId = backStackEntry.arguments?.getString("conversationId") ?: "new" + CreateMemoryScreen( + conversationId = conversationId, + navController = navController + ) } composable(Screen.MyMemoir.route) { - MyMemoirScreen() + MyMemoirScreen(navController = navController) } composable(Screen.Profile.route) { - ProfileScreen() + ProfileScreen(navController = navController) + } + composable(Screen.UpgradePlan.route) { + UpgradePlanScreen(navController = navController) + } + composable(Screen.MyOrders.route) { + MyOrdersScreen(navController = navController) + } + composable(Screen.ExportData.route) { + ExportDataScreen(navController = navController) } } } diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/CreateMemoryScreen.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/CreateMemoryScreen.kt index c3c6b00..35ec79d 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/CreateMemoryScreen.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/CreateMemoryScreen.kt @@ -1,18 +1,32 @@ package com.huaga.life_echo.ui.screens +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* +import com.huaga.life_echo.ui.icons.AppIcons import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +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.theme.LightPurple import com.huaga.life_echo.ui.viewmodel.CreateMemoryViewModel import com.huaga.life_echo.ui.viewmodel.ViewModelFactory +import java.text.SimpleDateFormat +import java.util.* +@OptIn(ExperimentalMaterial3Api::class) @Composable fun CreateMemoryScreen( + conversationId: String = "new", + navController: androidx.navigation.NavHostController? = null, viewModel: CreateMemoryViewModel = viewModel( factory = ViewModelFactory(LocalContext.current) ) @@ -25,81 +39,240 @@ fun CreateMemoryScreen( Column( modifier = Modifier .fillMaxSize() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp) + .windowInsetsPadding(WindowInsets.statusBars) ) { - Text( - text = "创建回忆录", - style = MaterialTheme.typography.headlineLarge - ) - - Text( - text = "连接状态: $connectionStatus", - style = MaterialTheme.typography.bodyMedium - ) - - // 转写文本显示 - Card( - modifier = Modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { - Text( - text = "你说:", - style = MaterialTheme.typography.labelMedium - ) - Text( - text = transcript.ifEmpty { "等待语音输入..." }, - style = MaterialTheme.typography.bodyLarge - ) - } - } - - // Agent 回应显示 - Card( - modifier = Modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { - Text( - text = "AI:", - style = MaterialTheme.typography.labelMedium - ) - Text( - text = agentResponse.ifEmpty { "等待 AI 回应..." }, - style = MaterialTheme.typography.bodyLarge - ) - } - } - - // 开始/结束按钮 - Button( - onClick = { - if (!isRecording) { - viewModel.startConversation() - } else { - viewModel.endConversation() + // 浅紫色标题栏 + TopAppBar( + title = { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "回忆录助手", + color = Color.White, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = "在线", + color = Color.White, + fontSize = 12.sp + ) } }, - modifier = Modifier - .fillMaxWidth() - .height(64.dp) - ) { - Text( - text = if (isRecording) "🟥 结束聊天" else "🎙️ 开始聊天", - style = MaterialTheme.typography.titleLarge + navigationIcon = { + IconButton(onClick = { + navController?.popBackStack() + }) { + Icon( + imageVector = AppIcons.ArrowBack, + contentDescription = "返回", + tint = Color.White + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = LightPurple ) + ) + + // 聊天内容区域 + Column( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .weight(1f) + ) { + // 时间戳 + Text( + text = "今天 ${SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date())}", + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.bodySmall + ) + + // AI助手消息气泡 + if (agentResponse.isEmpty() && transcript.isEmpty()) { + // 初始欢迎消息 + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + // 书本图标 + Box( + modifier = Modifier + .size(32.dp) + .clip(RoundedCornerShape(8.dp)) + .background(LightPurple), + contentAlignment = Alignment.Center + ) { + Text("📖", fontSize = 20.sp) + } + + Spacer(modifier = Modifier.width(8.dp)) + + // 消息气泡 + Card( + modifier = Modifier + .weight(1f) + .shadow(2.dp, RoundedCornerShape(12.dp)), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + ) { + Column( + modifier = Modifier.padding(12.dp) + ) { + Text( + text = "您好!我是您的回忆录助手,很高兴能陪您聊聊往事。😊", + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "您想从哪里开始呢?可以聊聊童年、上学时光,或者任何您想分享的故事。", + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface + ) + } + } + } + } + + // 用户消息(转写文本) + if (transcript.isNotEmpty()) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.End + ) { + Card( + modifier = Modifier + .weight(1f) + .shadow(2.dp, RoundedCornerShape(12.dp)), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = LightPurple.copy(alpha = 0.2f)) + ) { + Text( + text = transcript, + modifier = Modifier.padding(12.dp), + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface + ) + } + } + } + + // AI回应消息 + if (agentResponse.isNotEmpty()) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + Box( + modifier = Modifier + .size(32.dp) + .clip(RoundedCornerShape(8.dp)) + .background(LightPurple), + contentAlignment = Alignment.Center + ) { + Text("📖", fontSize = 20.sp) + } + + Spacer(modifier = Modifier.width(8.dp)) + + Card( + modifier = Modifier + .weight(1f) + .shadow(2.dp, RoundedCornerShape(12.dp)), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + ) { + Text( + text = agentResponse, + modifier = Modifier.padding(12.dp), + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface + ) + } + } + } } - Text( - text = "你可以一直说,我会认真听", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) + // 底部输入栏 + Surface( + modifier = Modifier + .fillMaxWidth() + .shadow(4.dp), + color = MaterialTheme.colorScheme.surface, + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // 麦克风图标 + IconButton( + onClick = { + if (!isRecording) { + viewModel.startConversation() + } else { + viewModel.endConversation() + } + } + ) { + Icon( + imageVector = if (isRecording) AppIcons.MicOff else AppIcons.Mic, + contentDescription = "语音输入", + tint = if (isRecording) LightPurple else MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(24.dp) + ) + } + + // 语音输入提示区域(只读,不支持文本输入) + Box( + modifier = Modifier + .weight(1f) + .height(40.dp) + .clip(RoundedCornerShape(20.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)), + contentAlignment = Alignment.CenterStart + ) { + Text( + text = if (isRecording) "正在录音..." else "说点什么...", + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontSize = 14.sp + ) + } + + // 表情图标 + IconButton(onClick = { /* TODO: 表情选择 */ }) { + Icon( + imageVector = AppIcons.SentimentSatisfied, + contentDescription = "表情", + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(24.dp) + ) + } + + // 加号图标 + IconButton(onClick = { /* TODO: 更多功能 */ }) { + Icon( + imageVector = AppIcons.Add, + contentDescription = "更多", + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(24.dp) + ) + } + } + } } } diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/MyMemoirScreen.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/MyMemoirScreen.kt index 950f35a..9cae371 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/MyMemoirScreen.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/MyMemoirScreen.kt @@ -1,34 +1,34 @@ package com.huaga.life_echo.ui.screens -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Card -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import com.huaga.life_echo.ui.icons.AppIcons +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +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.data.database.Chapter +import com.huaga.life_echo.ui.theme.LightPurple import com.huaga.life_echo.ui.viewmodel.MyMemoirViewModel import com.huaga.life_echo.ui.viewmodel.ViewModelFactory +import java.text.SimpleDateFormat +import java.util.* @Composable fun MyMemoirScreen( + navController: androidx.navigation.NavHostController? = null, viewModel: MyMemoirViewModel = viewModel( factory = ViewModelFactory(LocalContext.current) ) @@ -36,105 +36,351 @@ fun MyMemoirScreen( val chapters by viewModel.chapters.collectAsState(initial = emptyList()) val selectedChapter by viewModel.selectedChapter.collectAsState() val isLoading by viewModel.isLoading.collectAsState() + var selectedTab by remember { mutableStateOf(0) } // 0: 目录, 1: 正在阅读 Column( modifier = Modifier.fillMaxSize() ) { - // 标题 - Text( - text = "我的回忆录", - style = MaterialTheme.typography.headlineLarge, - modifier = Modifier.padding(16.dp) - ) + // 顶部标签页 + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) { + TabButton( + text = "目录", + selected = selectedTab == 0, + onClick = { selectedTab = 0 }, + modifier = Modifier.padding(end = 24.dp) + ) + TabButton( + text = "正在阅读", + selected = selectedTab == 1, + onClick = { selectedTab = 1 } + ) + } if (selectedChapter == null) { - // 目录列表 + // 目录视图 LazyColumn( modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp) ) { - items(chapters) { chapter -> - Card( + // 标题区域 + item { + Column( modifier = Modifier .fillMaxWidth() - .clickable { - viewModel.selectChapter(chapter) - } + .padding(bottom = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { - Column( - modifier = Modifier.padding(16.dp) - ) { - Text( - text = chapter.title, - style = MaterialTheme.typography.titleMedium - ) - Text( - text = "状态: ${chapter.status}", - style = MaterialTheme.typography.bodySmall - ) - } + Text( + text = "这一生", + fontSize = 32.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "我的回忆录", + fontSize = 18.sp, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "更新于 2 分钟前", + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) } } + // 章节列表 + items(chapters.sortedBy { it.orderIndex }) { chapter -> + ChapterCard( + chapter = chapter, + onClick = { viewModel.selectChapter(chapter) } + ) + Spacer(modifier = Modifier.height(12.dp)) + } + + // 示例章节(如果没有数据) if (chapters.isEmpty()) { item { - Text( - text = "暂无章节", - modifier = Modifier.padding(16.dp) + ChapterCard( + chapter = Chapter( + id = "demo1", + title = "童年与家庭", + content = "", + orderIndex = 1, + status = "completed", + updatedAt = System.currentTimeMillis(), + category = "childhood" + ), + onClick = { viewModel.selectChapter(chapters.firstOrNull() ?: return@ChapterCard) } + ) + Spacer(modifier = Modifier.height(12.dp)) + ChapterCard( + chapter = Chapter( + id = "demo2", + title = "上学的日子", + content = "", + orderIndex = 2, + status = "partial", + updatedAt = System.currentTimeMillis(), + category = "education" + ), + onClick = { viewModel.selectChapter(chapters.firstOrNull() ?: return@ChapterCard) } + ) + Spacer(modifier = Modifier.height(12.dp)) + ChapterCard( + chapter = Chapter( + id = "demo3", + title = "工作与事业", + content = "", + orderIndex = 3, + status = "pending", + updatedAt = System.currentTimeMillis(), + category = "career" + ), + onClick = { viewModel.selectChapter(chapters.firstOrNull() ?: return@ChapterCard) } + ) + Spacer(modifier = Modifier.height(12.dp)) + ChapterCard( + chapter = Chapter( + id = "demo4", + title = "爱情与婚姻", + content = "", + orderIndex = 4, + status = "pending", + updatedAt = System.currentTimeMillis(), + category = "family" + ), + onClick = { viewModel.selectChapter(chapters.firstOrNull() ?: return@ChapterCard) } ) } } } } else { - // 章节阅读 - Column( + // 章节阅读视图 + ChapterReadingScreen( + chapter = selectedChapter!!, + onBack = { viewModel.clearSelection() } + ) + } + } +} + +@Composable +fun TabButton( + text: String, + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier.clickable { onClick() } + ) { + Text( + text = text, + fontSize = 16.sp, + fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal, + color = if (selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant + ) + if (selected) { + Spacer(modifier = Modifier.height(4.dp)) + Box( modifier = Modifier - .fillMaxSize() - .padding(16.dp) + .height(2.dp) + .fillMaxWidth() + .background(LightPurple) + ) + } + } +} + +@Composable +fun ChapterCard( + chapter: Chapter, + onClick: () -> Unit +) { + val statusText = when (chapter.status) { + "completed" -> "已整理 · 约3页" + "partial" -> "部分整理 · 约2页" + else -> "待补充" + } + + Card( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() } + .shadow(2.dp, RoundedCornerShape(12.dp)), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // 浅紫色编号背景 + Box( + modifier = Modifier + .size(48.dp) + .clip(RoundedCornerShape(8.dp)) + .background(LightPurple), + contentAlignment = Alignment.Center ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween - ) { - TextButton(onClick = { - viewModel.clearSelection() - }) { - Text("返回目录") - } - TextButton( - onClick = { - viewModel.exportPdf( - bookId = "current", // TODO: 获取实际 bookId - onSuccess = { pdfBytes -> - // TODO: 保存或分享 PDF - }, - onError = { error -> - // TODO: 显示错误提示 - } - ) - }, - enabled = !isLoading - ) { - Text(if (isLoading) "导出中..." else "导出 PDF") - } - } - + Text( + text = String.format("%02d", chapter.orderIndex), + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + color = Color.White + ) + } + + Spacer(modifier = Modifier.width(16.dp)) + + // 章节信息 + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = chapter.title, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = statusText, + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + // 右箭头 + Icon( + imageVector = AppIcons.ChevronRight, + contentDescription = "进入", + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(24.dp) + ) + } + } +} + +@Composable +fun ChapterReadingScreen( + chapter: Chapter, + onBack: () -> Unit +) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp) + ) { + item { + // 返回按钮 + Row( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp) + .clickable { onBack() }, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = AppIcons.ArrowBack, + contentDescription = "返回", + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "返回目录", + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + + // 章节标题 + Text( + text = "第一章", + fontSize = 14.sp, + color = LightPurple, + modifier = Modifier.padding(bottom = 4.dp) + ) + Text( + text = chapter.title, + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 24.dp) + ) + + // 正文内容 + Text( + text = chapter.content.ifEmpty { getSampleContent(chapter.title) }, + fontSize = 16.sp, + lineHeight = 28.sp, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 16.dp) + ) + + // 引用块示例(如果有特定内容) + if (chapter.title.contains("童年") || chapter.title.contains("家庭")) { + QuoteBlock( + text = "\"日子虽然清苦, 但那时候的快乐是最纯粹的。\"" + ) Spacer(modifier = Modifier.height(16.dp)) Text( - text = selectedChapter!!.title, - style = MaterialTheme.typography.headlineMedium + text = "我出生在一个普通的农村家庭,父母都是勤劳朴实的农民。", + fontSize = 16.sp, + lineHeight = 28.sp, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(bottom = 16.dp) ) - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = selectedChapter!!.content, - style = MaterialTheme.typography.bodyLarge + text = "父亲是村里的木匠,手艺精湛,邻里乡亲都愿意找他帮忙。", + fontSize = 16.sp, + lineHeight = 28.sp, + color = MaterialTheme.colorScheme.onSurface ) } } } } +@Composable +fun QuoteBlock(text: String) { + Box( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)) + .background(LightPurple.copy(alpha = 0.2f)) + .padding(16.dp) + ) { + Text( + text = text, + fontSize = 16.sp, + lineHeight = 28.sp, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.Medium + ) + } +} + +fun getSampleContent(title: String): String { + return when { + title.contains("童年") -> "我出生在一个普通的农村家庭,父母都是勤劳朴实的农民。\n\n父亲是村里的木匠,手艺精湛,邻里乡亲都愿意找他帮忙。" + title.contains("上学") -> "上学的日子总是充满欢声笑语,虽然条件艰苦,但学习的快乐让我忘记了生活的艰辛。" + title.contains("工作") -> "工作是我人生中重要的转折点,让我学会了承担责任,也让我明白了生活的意义。" + title.contains("爱情") -> "爱情是人生中最美好的经历之一,它让我懂得了什么是真正的幸福。" + else -> "这里是章节内容..." + } +} + diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/ProfileScreen.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/ProfileScreen.kt index 726f042..a1ccb22 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/ProfileScreen.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/screens/ProfileScreen.kt @@ -1,117 +1,361 @@ package com.huaga.life_echo.ui.screens -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Divider +import androidx.compose.material3.Icon +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchDefaults +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.TextButton +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.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.huaga.life_echo.ui.icons.AppIcons +import com.huaga.life_echo.ui.settings.AppSettings +import com.huaga.life_echo.ui.theme.LightPurple @Composable -fun ProfileScreen() { - Column( +fun ProfileScreen( + navController: androidx.navigation.NavHostController? = null +) { + var largeFontMode by remember { mutableStateOf(AppSettings.largeFontMode) } + var darkMode by remember { mutableStateOf(AppSettings.darkMode) } + var speechRate by remember { mutableStateOf(AppSettings.speechRate) } + var showSpeechRateDialog by remember { mutableStateOf(false) } + + // 应用设置变化 - 立即更新全局设置 + LaunchedEffect(largeFontMode) { + AppSettings.largeFontMode = largeFontMode + } + LaunchedEffect(darkMode) { + AppSettings.darkMode = darkMode + // 触发主题重新组合 + } + LaunchedEffect(speechRate) { + AppSettings.speechRate = speechRate + } + + // 语速选择对话框 + if (showSpeechRateDialog) { + AlertDialog( + onDismissRequest = { showSpeechRateDialog = false }, + title = { Text("选择语速") }, + text = { + Column { + AppSettings.SpeechRate.entries.forEach { rate -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + speechRate = rate + showSpeechRateDialog = false + } + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = speechRate == rate, + onClick = { + speechRate = rate + showSpeechRateDialog = false + } + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(rate.label) + } + } + } + }, + confirmButton = { + TextButton(onClick = { showSpeechRateDialog = false }) { + Text("取消") + } + } + ) + } + + LazyColumn( modifier = Modifier .fillMaxSize() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) + .background(MaterialTheme.colorScheme.background) ) { - Text( - text = "我的", - style = MaterialTheme.typography.headlineLarge - ) - - // 账户卡片 - Card( - modifier = Modifier.fillMaxWidth() - ) { + // 用户信息区域 + item { Column( - modifier = Modifier.padding(16.dp) + modifier = Modifier + .fillMaxWidth() + .windowInsetsPadding(WindowInsets.statusBars) + .padding(top = 16.dp, bottom = 32.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { + // 用户头像 + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(LightPurple.copy(alpha = 0.2f)), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = AppIcons.Person, + contentDescription = "用户头像", + tint = LightPurple, + modifier = Modifier.size(48.dp) + ) + } + + Spacer(modifier = Modifier.height(16.dp)) + Text( - text = "账户信息", - style = MaterialTheme.typography.titleMedium - ) - Text( - text = "套餐状态: 免费体验", - style = MaterialTheme.typography.bodyMedium + text = "李明华", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface ) + + Spacer(modifier = Modifier.height(4.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(8.dp) + .clip(CircleShape) + .background(LightPurple) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "免费体验版", + fontSize = 12.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } } } // 套餐与付费 - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { - Text( - text = "套餐与付费", - style = MaterialTheme.typography.titleMedium + item { + SectionTitle("套餐与付费") + SettingCard { + SettingItem( + icon = AppIcons.Upgrade, + title = "升级套餐", + subtitle = "解锁完整导出与更多功能", + onClick = { + navController?.navigate(com.huaga.life_echo.navigation.Screen.UpgradePlan.route) + } + ) + Divider(modifier = Modifier.padding(horizontal = 16.dp)) + SettingItem( + icon = AppIcons.Receipt, + title = "我的订单", + onClick = { + navController?.navigate(com.huaga.life_echo.navigation.Screen.MyOrders.route) + } ) - TextButton(onClick = { /* TODO */ }) { - Text("升级套餐") - } - TextButton(onClick = { /* TODO */ }) { - Text("我的订单") - } } } // 数据与隐私 - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { - Text( - text = "数据与隐私", - style = MaterialTheme.typography.titleMedium + item { + SectionTitle("数据与隐私") + SettingCard { + SettingItem( + icon = AppIcons.FileDownload, + title = "导出所有数据", + onClick = { + navController?.navigate(com.huaga.life_echo.navigation.Screen.ExportData.route) + } ) - TextButton(onClick = { /* TODO */ }) { - Text("导出所有数据") - } } } // 设置 - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { - Text( - text = "设置", - style = MaterialTheme.typography.titleMedium + item { + SectionTitle("设置") + SettingCard { + SettingItem( + icon = AppIcons.AccessTime, + title = "语速", + subtitle = speechRate.label, + onClick = { + // 显示语速选择对话框 + showSpeechRateDialog = true + } + ) + Divider(modifier = Modifier.padding(horizontal = 16.dp)) + SettingItem( + icon = AppIcons.FormatSize, + title = "大字模式", + trailing = { + Switch( + checked = largeFontMode, + onCheckedChange = { largeFontMode = it }, + colors = SwitchDefaults.colors( + checkedThumbColor = Color.White, + checkedTrackColor = LightPurple, + uncheckedThumbColor = Color.White, + uncheckedTrackColor = Color(0xFFE0E0E0) + ) + ) + }, + onClick = { largeFontMode = !largeFontMode } + ) + Divider(modifier = Modifier.padding(horizontal = 16.dp)) + SettingItem( + icon = AppIcons.Brightness2, + title = "夜间模式", + trailing = { + Switch( + checked = darkMode, + onCheckedChange = { + darkMode = it + // 设置变化会通过LaunchedEffect自动同步到AppSettings + }, + colors = SwitchDefaults.colors( + checkedThumbColor = Color.White, + checkedTrackColor = LightPurple, + uncheckedThumbColor = Color.White, + uncheckedTrackColor = Color(0xFFE0E0E0) + ) + ) + }, + onClick = { darkMode = !darkMode } ) - TextButton(onClick = { /* TODO */ }) { - Text("语速设置") - } - TextButton(onClick = { /* TODO */ }) { - Text("字体设置") - } } } - // 帮助 - Card( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { - Text( - text = "帮助", - style = MaterialTheme.typography.titleMedium - ) - TextButton(onClick = { /* TODO */ }) { - Text("常见问题") - } - TextButton(onClick = { /* TODO */ }) { - Text("反馈与客服") - } - } + item { + Spacer(modifier = Modifier.height(32.dp)) + } + } +} + +@Composable +fun SectionTitle(text: String) { + Text( + text = text, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSurface + ) +} + +@Composable +fun SettingCard(content: @Composable ColumnScope.() -> Unit) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 4.dp), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + ) { + Column( + modifier = Modifier.padding(vertical = 8.dp) + ) { + content() + } + } +} + +@Composable +fun SettingItem( + icon: androidx.compose.ui.graphics.vector.ImageVector, + title: String, + subtitle: String? = null, + trailing: @Composable (() -> Unit)? = null, + 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(LightPurple.copy(alpha = 0.1f)), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = icon, + contentDescription = title, + tint = LightPurple, + 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 + ) + } + } + + // 尾部内容(箭头或开关) + if (trailing != null) { + trailing() + } else { + Icon( + imageVector = AppIcons.ChevronRight, + contentDescription = "进入", + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(24.dp) + ) } } }