refactor: 优化前端功能屏幕

- 优化ConversationListScreen对话列表页面
- 优化CreateMemoryScreen创建回忆页面
- 优化MyMemoirScreen我的回忆录页面
- 优化ProfileScreen个人资料页面
- 优化ExportDataScreen导出数据页面
- 优化AboutScreen关于页面
This commit is contained in:
iammm0
2026-01-23 14:02:50 +08:00
parent 26247c3427
commit 2592143789
6 changed files with 459 additions and 227 deletions

View File

@@ -160,7 +160,7 @@ fun AboutScreen(
// 版权信息 // 版权信息
Text( Text(
text = "© 2024 Life Echo. All rights reserved.", text = "© 2026 Life Echo. All rights reserved.",
fontSize = 14.sp, fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 16.dp) modifier = Modifier.padding(top = 16.dp)

View File

@@ -1,22 +1,26 @@
package com.huaga.life_echo.ui.screens package com.huaga.life_echo.ui.screens
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.* import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.huaga.life_echo.data.database.Conversation
import com.huaga.life_echo.network.models.ConversationListItemDto import com.huaga.life_echo.network.models.ConversationListItemDto
import com.huaga.life_echo.ui.components.common.EmptyStateView import com.huaga.life_echo.ui.components.common.EmptyStateView
import com.huaga.life_echo.ui.components.common.LoadingIndicator import com.huaga.life_echo.ui.components.common.LoadingIndicator
@@ -24,6 +28,7 @@ import com.huaga.life_echo.ui.components.conversation.ConversationListHeader
import com.huaga.life_echo.ui.components.conversation.ConversationListItem import com.huaga.life_echo.ui.components.conversation.ConversationListItem
import com.huaga.life_echo.ui.viewmodel.ConversationListViewModel import com.huaga.life_echo.ui.viewmodel.ConversationListViewModel
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
import kotlinx.coroutines.launch
@Composable @Composable
fun ConversationListScreen( fun ConversationListScreen(
@@ -41,11 +46,26 @@ fun ConversationListScreen(
viewModel.refreshConversations() viewModel.refreshConversations()
} }
val scope = rememberCoroutineScope()
// 处理新建对话
val handleCreateConversation: () -> Unit = {
scope.launch {
val result = viewModel.createConversation()
result.fold(
onSuccess = { conversationId ->
onConversationClick(conversationId)
},
onFailure = { exception ->
// 错误处理可以在这里添加
}
)
}
}
Column( Column(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
// 使用新的头部组件
ConversationListHeader()
// 对话列表区域 // 对话列表区域
Column( Column(
@@ -53,6 +73,7 @@ fun ConversationListScreen(
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colorScheme.background) .background(MaterialTheme.colorScheme.background)
) { ) {
ConversationListHeader(onCreateConversation = handleCreateConversation)
Text( Text(
text = "我的对话", text = "我的对话",
modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 8.dp), modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 8.dp),
@@ -66,12 +87,20 @@ fun ConversationListScreen(
LoadingIndicator() LoadingIndicator()
} }
error != null -> { error != null -> {
// 即使有错误,也显示默认对话 // 显示错误信息
DefaultConversationItem(onClick = { onConversationClick("demo") }) EmptyStateView(
title = "加载失败",
message = error ?: "未知错误",
modifier = Modifier.fillMaxSize()
)
} }
conversations.isEmpty() -> { conversations.isEmpty() -> {
// 空状态 - 显示默认对话 // 空状态 - 提示用户创建新对话
DefaultConversationItem(onClick = { onConversationClick("demo") }) EmptyStateView(
title = "还没有对话",
message = "点击上方「新建对话」按钮开始您的回忆录之旅",
modifier = Modifier.fillMaxSize()
)
} }
else -> { else -> {
LazyColumn( LazyColumn(
@@ -92,7 +121,12 @@ fun ConversationListScreen(
) )
ConversationListItem( ConversationListItem(
conversation = dto, conversation = dto,
onClick = { onConversationClick(conversation.id) } onClick = { onConversationClick(conversation.id) },
onDelete = {
scope.launch {
viewModel.deleteConversation(conversation.id)
}
}
) )
} }
} }
@@ -102,19 +136,3 @@ fun ConversationListScreen(
} }
} }
@Composable
private fun DefaultConversationItem(onClick: () -> Unit) {
val defaultDto = ConversationListItemDto(
id = "demo",
title = "回忆录助手",
avatarUrl = null,
latestMessagePreview = "您想从哪里开始呢?可以聊聊童年...",
latestMessageTime = System.currentTimeMillis(),
unreadCount = 0,
isDefaultAssistant = true
)
ConversationListItem(
conversation = defaultDto,
onClick = onClick
)
}

View File

@@ -19,6 +19,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.huaga.life_echo.data.database.Message import com.huaga.life_echo.data.database.Message
import com.huaga.life_echo.network.models.MessageDto import com.huaga.life_echo.network.models.MessageDto
import com.huaga.life_echo.ui.components.chat.* import com.huaga.life_echo.ui.components.chat.*
import com.huaga.life_echo.ui.components.debug.WebSocketDebugPanel
import com.huaga.life_echo.ui.theme.LightPurple import com.huaga.life_echo.ui.theme.LightPurple
import com.huaga.life_echo.ui.viewmodel.CreateMemoryViewModel import com.huaga.life_echo.ui.viewmodel.CreateMemoryViewModel
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
@@ -39,32 +40,58 @@ fun CreateMemoryScreen(
val agentResponse by viewModel.agentResponse.collectAsState() val agentResponse by viewModel.agentResponse.collectAsState()
val connectionStatus by viewModel.connectionStatus.collectAsState() val connectionStatus by viewModel.connectionStatus.collectAsState()
val userMessages by viewModel.userMessages.collectAsState() val userMessages by viewModel.userMessages.collectAsState()
val historyMessages by viewModel.historyMessages.collectAsState()
val isStreaming by viewModel.isStreaming.collectAsState() val isStreaming by viewModel.isStreaming.collectAsState()
val streamingText by viewModel.streamingText.collectAsState() val streamingText by viewModel.streamingText.collectAsState()
val isTyping by viewModel.isTyping.collectAsState() val isTyping by viewModel.isTyping.collectAsState()
// 调试信息
val wsIsConnected by viewModel.wsIsConnected.collectAsState()
val lastMessageType by viewModel.lastMessageType.collectAsState()
val lastMessageTime by viewModel.lastMessageTime.collectAsState()
val errorMessages by viewModel.errorMessages.collectAsState()
val messageCount by viewModel.messageCount.collectAsState()
val conversationIdState by viewModel.conversationId.collectAsState()
// 初始化对话
LaunchedEffect(conversationId) {
if (conversationId != "new") {
viewModel.initializeConversation(conversationId)
} else {
// 如果是新建对话,启动新对话
viewModel.startConversation()
}
}
// 输入框状态 // 输入框状态
var inputText by remember { mutableStateOf("") } var inputText by remember { mutableStateOf("") }
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
// 构建消息列表 // 构建消息列表(包含历史消息和当前消息)
val messages = remember(userMessages, agentResponse) { val messages = remember(historyMessages, userMessages, agentResponse) {
buildList { buildList {
// 添加用户消息 // 添加历史消息
addAll(historyMessages)
// 添加当前会话的用户消息(排除已存在的)
val existingUserMessageIds = historyMessages.filter { it.senderType == "user" }.map { it.content }.toSet()
userMessages.forEachIndexed { index, text -> userMessages.forEachIndexed { index, text ->
add(MessageDto( if (!existingUserMessageIds.contains(text)) {
id = "user_$index", add(MessageDto(
conversationId = conversationId, id = "user_${historyMessages.size + index}",
content = text, conversationId = conversationId,
senderType = "user", content = text,
timestamp = System.currentTimeMillis() - (userMessages.size - index) * 1000L, senderType = "user",
messageType = "text" timestamp = System.currentTimeMillis() - (userMessages.size - index) * 1000L,
)) messageType = "text"
))
}
} }
// 添加AI回复
if (agentResponse.isNotEmpty()) { // 添加AI回复如果不在历史消息中
if (agentResponse.isNotEmpty() && !historyMessages.any { it.content == agentResponse && it.senderType == "assistant" }) {
add(MessageDto( add(MessageDto(
id = "ai_response", id = "ai_response_${historyMessages.size + userMessages.size}",
conversationId = conversationId, conversationId = conversationId,
content = agentResponse, content = agentResponse,
senderType = "assistant", senderType = "assistant",
@@ -72,7 +99,7 @@ fun CreateMemoryScreen(
messageType = "text" messageType = "text"
)) ))
} }
} }.sortedBy { it.timestamp } // 按时间排序
} }
Column( Column(
@@ -85,6 +112,22 @@ fun CreateMemoryScreen(
onBackClick = { navController?.popBackStack() } onBackClick = { navController?.popBackStack() }
) )
// WebSocket调试面板开发测试用
WebSocketDebugPanel(
connectionStatus = connectionStatus,
conversationId = conversationIdState,
isConnected = wsIsConnected,
isStreaming = isStreaming,
isTyping = isTyping,
lastMessageType = lastMessageType,
lastMessageTime = lastMessageTime,
errorMessages = errorMessages,
messageCount = messageCount,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
)
// 使用新的MessageList组件包含所有消息、流式内容和输入指示器 // 使用新的MessageList组件包含所有消息、流式内容和输入指示器
MessageList( MessageList(
messages = messages, messages = messages,

View File

@@ -149,42 +149,40 @@ fun ExportDataScreen(
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
// 导出按钮 // 导出按钮 - 显示开发中提示
var showDevDialog by remember { mutableStateOf(false) }
Button( Button(
onClick = { onClick = {
isExporting = true showDevDialog = true
// TODO: 执行导出操作
// 模拟导出完成
scope.launch {
delay(2000)
isExporting = false
exportCompleted = true
}
}, },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
enabled = !isExporting && !exportCompleted,
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
containerColor = LightPurple containerColor = LightPurple
) )
) { ) {
if (isExporting) { Text("开始导出", color = Color.White)
CircularProgressIndicator( }
modifier = Modifier.size(20.dp),
color = Color.White // 开发中提示对话框
) if (showDevDialog) {
Spacer(modifier = Modifier.width(8.dp)) AlertDialog(
Text("导出中...", color = Color.White) onDismissRequest = { showDevDialog = false },
} else if (exportCompleted) { title = { Text("功能开发中") },
Icon( text = {
imageVector = Icons.Default.CheckCircle, Text(
contentDescription = "完成", text = "导出所有数据功能正在开发中,敬请期待!",
tint = Color.White modifier = Modifier.wrapContentHeight()
) )
Spacer(modifier = Modifier.width(8.dp)) },
Text("导出完成", color = Color.White) confirmButton = {
} else { TextButton(
Text("开始导出", color = Color.White) onClick = { showDevDialog = false }
} ) {
Text("确定", color = LightPurple)
}
}
)
} }
} }
} }

View File

@@ -1,5 +1,7 @@
package com.huaga.life_echo.ui.screens package com.huaga.life_echo.ui.screens
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
@@ -18,21 +20,33 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.compose.foundation.lazy.rememberLazyListState
import kotlinx.coroutines.launch
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import com.huaga.life_echo.data.database.Chapter import com.huaga.life_echo.data.database.Chapter
import com.huaga.life_echo.network.models.ChapterContentDto import com.huaga.life_echo.network.models.ChapterContentDto
import com.huaga.life_echo.network.models.ChapterDto import com.huaga.life_echo.network.models.ChapterDto
import com.huaga.life_echo.ui.components.memoir.* import com.huaga.life_echo.ui.components.memoir.*
import com.huaga.life_echo.ui.components.common.EmptyStateView
import com.huaga.life_echo.ui.theme.LightPurple import com.huaga.life_echo.ui.theme.LightPurple
import com.huaga.life_echo.ui.viewmodel.MyMemoirViewModel import com.huaga.life_echo.ui.viewmodel.MyMemoirViewModel
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
import com.huaga.life_echo.ui.viewmodel.ConversationListViewModel
import com.huaga.life_echo.ui.icons.AppIcons import com.huaga.life_echo.ui.icons.AppIcons
import com.huaga.life_echo.network.models.ConversationListItemDto
@OptIn(ExperimentalMaterial3Api::class)
@RequiresApi(Build.VERSION_CODES.O)
@Composable @Composable
fun MyMemoirScreen( fun MyMemoirScreen(
navController: androidx.navigation.NavHostController? = null, navController: androidx.navigation.NavHostController? = null,
viewModel: MyMemoirViewModel = viewModel( viewModel: MyMemoirViewModel = viewModel(
factory = ViewModelFactory(LocalContext.current) factory = ViewModelFactory(LocalContext.current)
),
conversationListViewModel: ConversationListViewModel = viewModel(
factory = ViewModelFactory(LocalContext.current)
) )
) { ) {
val chapters by viewModel.chapters.collectAsState(initial = emptyList()) val chapters by viewModel.chapters.collectAsState(initial = emptyList())
@@ -40,7 +54,53 @@ fun MyMemoirScreen(
val isLoading by viewModel.isLoading.collectAsState() val isLoading by viewModel.isLoading.collectAsState()
val bookInfo by viewModel.bookInfo.collectAsState() val bookInfo by viewModel.bookInfo.collectAsState()
val showFullTextReading by viewModel.showFullTextReading.collectAsState() val showFullTextReading by viewModel.showFullTextReading.collectAsState()
var selectedTab by remember { mutableStateOf(0) }
// 整理对话相关状态
var showOrganizeDialog by remember { mutableStateOf(false) }
var showOrganizeSuccessDialog by remember { mutableStateOf(false) }
val conversations by conversationListViewModel.conversations.collectAsState(initial = emptyList())
val conversationsLoading by conversationListViewModel.isLoading.collectAsState()
val isOrganizing by viewModel.isOrganizing.collectAsState()
val organizingProgress by viewModel.organizingProgress.collectAsState()
val organizingStatus by viewModel.organizingStatus.collectAsState()
// 下拉刷新状态
var isRefreshing by remember { mutableStateOf(false) }
val refreshScope = rememberCoroutineScope()
// 下拉刷新处理
fun handleRefresh() {
refreshScope.launch {
isRefreshing = true
viewModel.refreshChapters()
viewModel.loadBookInfo()
// 等待刷新完成
kotlinx.coroutines.delay(500)
isRefreshing = false
}
}
// 加载对话列表转换为ConversationListItemDto格式
val conversationDtos = remember(conversations) {
conversations.map { conv ->
ConversationListItemDto(
id = conv.id,
title = conv.title ?: "回忆录助手",
avatarUrl = conv.avatarUrl,
latestMessagePreview = conv.latestMessagePreview,
latestMessageTime = conv.latestMessageTime ?: conv.startedAt,
unreadCount = 0,
isDefaultAssistant = conv.title == null
)
}
}
// 加载对话列表
LaunchedEffect(showOrganizeDialog) {
if (showOrganizeDialog) {
conversationListViewModel.refreshConversations()
}
}
// 加载书籍信息 // 加载书籍信息
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -104,31 +164,18 @@ fun MyMemoirScreen(
) )
} else { } else {
Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) {
// 顶部标签页
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) { if (selectedChapter == null) {
// 目录视图 // 目录视图(带下拉刷新)
LazyColumn( PullToRefreshBox(
modifier = Modifier.fillMaxSize(), isRefreshing = isRefreshing,
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp) onRefresh = { handleRefresh() },
modifier = Modifier.fillMaxSize()
) { ) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp),
state = rememberLazyListState()
) {
// 书籍信息卡片 // 书籍信息卡片
item { item {
bookInfo?.let { book -> bookInfo?.let { book ->
@@ -160,62 +207,150 @@ fun MyMemoirScreen(
} }
} }
// 「阅读全文」浮动按钮区域 // 操作按钮区域
item { item {
Row( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 16.dp), .padding(vertical = 16.dp)
horizontalArrangement = Arrangement.Center
) { ) {
FloatingActionButton( // 整理进度条(如果正在整理)
onClick = { viewModel.toggleFullTextReading() }, if (isOrganizing) {
containerColor = LightPurple Card(
) { modifier = Modifier
Icon( .fillMaxWidth()
imageVector = AppIcons.Reading, .padding(horizontal = 16.dp, vertical = 8.dp),
contentDescription = "阅读全文", shape = RoundedCornerShape(12.dp),
tint = Color.White colors = CardDefaults.cardColors(
) containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)
Spacer(modifier = Modifier.width(8.dp)) )
Text("阅读全文", color = Color.White) ) {
} Column(
} modifier = Modifier
} .fillMaxWidth()
.padding(16.dp)
// 章节列表 ) {
items(chapterDtos.sortedBy { it.order_index }, key = { it.id }) { chapterDto -> Text(
ChapterCard( text = organizingStatus.ifEmpty { "正在整理对话..." },
chapter = chapterDto, fontSize = 14.sp,
onClick = { fontWeight = FontWeight.Medium,
// 查找对应的Chapter实体 color = MaterialTheme.colorScheme.onSurface,
chapters.find { it.id == chapterDto.id }?.let { chapter -> modifier = Modifier.padding(bottom = 8.dp)
viewModel.selectChapter(chapter) )
LinearProgressIndicator(
progress = organizingProgress,
modifier = Modifier.fillMaxWidth(),
color = LightPurple,
trackColor = MaterialTheme.colorScheme.surfaceVariant
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "${(organizingProgress * 100).toInt()}%",
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.align(Alignment.End)
)
}
} }
} }
)
Spacer(modifier = Modifier.height(12.dp)) // 操作按钮
} Row(
modifier = Modifier
// 如果没有章节,显示示例 .fillMaxWidth()
if (chapterDtos.isEmpty()) { .padding(horizontal = 16.dp),
item { horizontalArrangement = Arrangement.spacedBy(12.dp)
// 显示示例章节 ) {
val now = java.time.Instant.now().toString() // 整理对话按钮(改进样式)
listOf( Button(
ChapterDto("demo1", "童年与家庭", "", 1, "completed", "childhood", emptyList(), now, false, emptyList()), onClick = { showOrganizeDialog = true },
ChapterDto("demo2", "上学的日子", "", 2, "partial", "education", emptyList(), now, false, emptyList()), modifier = Modifier.weight(1f),
ChapterDto("demo3", "工作与事业", "", 3, "pending", "career", emptyList(), now, true, emptyList()), enabled = !isOrganizing,
ChapterDto("demo4", "爱情与婚姻", "", 4, "pending", "family", emptyList(), now, true, emptyList()) colors = ButtonDefaults.buttonColors(
).forEach { chapterDto -> containerColor = LightPurple
ChapterCard( ),
chapter = chapterDto, shape = RoundedCornerShape(12.dp)
onClick = { } ) {
) Row(
Spacer(modifier = Modifier.height(12.dp)) verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
imageVector = AppIcons.Edit,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
"整理对话",
color = Color.White,
fontSize = 14.sp
)
}
}
// 「阅读全文」按钮 - 只在有章节时显示
if (chapterDtos.isNotEmpty()) {
Button(
onClick = { viewModel.toggleFullTextReading() },
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = LightPurple
),
shape = RoundedCornerShape(12.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
imageVector = AppIcons.Reading,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
"阅读全文",
color = Color.White,
fontSize = 14.sp
)
}
}
}
} }
} }
} }
// 如果没有章节,显示空状态
if (chapterDtos.isEmpty()) {
item {
EmptyStateView(
title = "还没有章节",
message = "开始对话让AI帮您整理回忆录章节",
icon = "📖",
modifier = Modifier
.fillMaxWidth()
.padding(top = 48.dp)
)
}
} else {
// 章节列表
items(chapterDtos.sortedBy { it.order_index }, key = { it.id }) { chapterDto ->
ChapterCard(
chapter = chapterDto,
onClick = {
// 查找对应的Chapter实体
chapters.find { it.id == chapterDto.id }?.let { chapter ->
viewModel.selectChapter(chapter)
}
}
)
Spacer(modifier = Modifier.height(12.dp))
}
}
}
} }
} else { } else {
// 章节阅读视图 // 章节阅读视图
@@ -261,30 +396,48 @@ fun MyMemoirScreen(
} }
} }
} }
}
} // 整理对话对话框
if (showOrganizeDialog) {
@Composable OrganizeConversationDialog(
fun TabButton( conversations = conversationDtos,
text: String, isLoading = conversationsLoading,
selected: Boolean, onDismiss = { showOrganizeDialog = false },
onClick: () -> Unit, onSelectConversation = { conversationId ->
modifier: Modifier = Modifier viewModel.organizeConversation(
) { conversationId = conversationId,
Column(modifier = modifier.clickable { onClick() }) { onSuccess = {
Text( showOrganizeSuccessDialog = true
text = text, },
fontSize = 16.sp, onError = { errorMsg ->
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
.height(2.dp) // 整理成功对话框
.fillMaxWidth() if (showOrganizeSuccessDialog) {
.background(LightPurple) AlertDialog(
onDismissRequest = { showOrganizeSuccessDialog = false },
title = { Text("整理成功") },
text = {
Text(
text = "对话内容正在整理中,请稍后刷新查看章节。",
modifier = Modifier.wrapContentHeight()
)
},
confirmButton = {
TextButton(
onClick = {
showOrganizeSuccessDialog = false
viewModel.refreshChapters()
}
) {
Text("确定", color = LightPurple)
}
}
) )
} }
} }

View File

@@ -58,6 +58,9 @@ import com.huaga.life_echo.ui.viewmodel.PaymentViewModel
import com.huaga.life_echo.ui.viewmodel.ProfileViewModel import com.huaga.life_echo.ui.viewmodel.ProfileViewModel
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import java.io.File
@Composable @Composable
fun ProfileScreen( fun ProfileScreen(
@@ -73,11 +76,30 @@ fun ProfileScreen(
var speechRate by remember { mutableStateOf(AppSettings.speechRate) } var speechRate by remember { mutableStateOf(AppSettings.speechRate) }
var showSpeechRateDialog by remember { mutableStateOf(false) } var showSpeechRateDialog by remember { mutableStateOf(false) }
var showLogoutDialog by remember { mutableStateOf(false) } var showLogoutDialog by remember { mutableStateOf(false) }
var showUpgradePlanDialog by remember { mutableStateOf(false) }
val isLoggedIn by authViewModel.isLoggedIn.collectAsState() val isLoggedIn by authViewModel.isLoggedIn.collectAsState()
val currentUser by authViewModel.currentUser.collectAsState() val currentUser by authViewModel.currentUser.collectAsState()
val userProfile by profileViewModel.userProfile.collectAsState() val userProfile by profileViewModel.userProfile.collectAsState()
val currentPlan by paymentViewModel.currentPlan.collectAsState() val currentPlan by paymentViewModel.currentPlan.collectAsState()
val isLoading by authViewModel.isLoading.collectAsState()
// 图片选择器
val imagePickerLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent()
) { uri ->
uri?.let {
// 将URI转换为File并上传
val inputStream = context.contentResolver.openInputStream(uri)
val tempFile = File(context.cacheDir, "temp_avatar.jpg")
inputStream?.use { input ->
tempFile.outputStream().use { output ->
input.copyTo(output)
}
}
authViewModel.uploadAvatar(tempFile)
}
}
// 初始化TokenManager // 初始化TokenManager
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -97,42 +119,22 @@ fun ProfileScreen(
AppSettings.speechRate = speechRate AppSettings.speechRate = speechRate
} }
// 语速选择对话框 // 语速选择对话框 - 显示开发中提示
if (showSpeechRateDialog) { if (showSpeechRateDialog) {
AlertDialog( AlertDialog(
onDismissRequest = { showSpeechRateDialog = false }, onDismissRequest = { showSpeechRateDialog = false },
title = { Text("选择语速") }, title = { Text("提示") },
text = { text = {
Column( Text(
text = "语音模块正在开发中,敬请期待!",
modifier = Modifier.wrapContentHeight() modifier = Modifier.wrapContentHeight()
) { )
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 = { confirmButton = {
TextButton(onClick = { showSpeechRateDialog = false }) { TextButton(
Text("取消") onClick = { showSpeechRateDialog = false }
) {
Text("确定", color = LightPurple)
} }
} }
) )
@@ -153,14 +155,7 @@ fun ProfileScreen(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
if (isLoggedIn && currentUser != null) { if (isLoggedIn && currentUser != null) {
// 已登录:显示用户信息 // 已登录:显示用户信息(暂时不显示头像)
// 使用新的UserAvatar组件
com.huaga.life_echo.ui.components.profile.UserAvatar(
avatarUrl = userProfile?.avatarUrl ?: currentUser!!.avatar_url,
modifier = Modifier.size(80.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Text( Text(
text = userProfile?.nickname ?: currentUser!!.nickname, text = userProfile?.nickname ?: currentUser!!.nickname,
@@ -169,7 +164,26 @@ fun ProfileScreen(
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurface
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(8.dp))
// 显示邮箱
if (!currentUser!!.email.isNullOrBlank()) {
Text(
text = currentUser!!.email ?: "",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(4.dp))
}
// 显示手机号
Text(
text = currentUser!!.phone,
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(8.dp))
// 使用新的PlanStatusBadge组件 // 使用新的PlanStatusBadge组件
com.huaga.life_echo.ui.components.profile.PlanStatusBadge( com.huaga.life_echo.ui.components.profile.PlanStatusBadge(
@@ -270,9 +284,9 @@ fun ProfileScreen(
SettingItem( SettingItem(
icon = AppIcons.Upgrade, icon = AppIcons.Upgrade,
title = "升级/管理套餐", title = "升级/管理套餐",
subtitle = "解锁完整导出与更多功能", subtitle = "功能开发中",
onClick = { onClick = {
navController?.navigate(com.huaga.life_echo.navigation.Screen.UpgradePlan.route) showUpgradePlanDialog = true
} }
) )
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp)) HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
@@ -329,30 +343,15 @@ fun ProfileScreen(
SettingItem( SettingItem(
icon = AppIcons.AccessTime, icon = AppIcons.AccessTime,
title = "语速", title = "语速",
subtitle = speechRate.label, subtitle = "正在开发语音模块",
onClick = { onClick = {
// 显示语速选择对话框 // 显示提示对话框
showSpeechRateDialog = true showSpeechRateDialog = true
} }
) )
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp)) // 大字模式暂时不开放
SettingItem( // HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
icon = AppIcons.FormatSize, // SettingItem(...)
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 }
)
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp)) HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
SettingItem( SettingItem(
icon = AppIcons.Brightness2, icon = AppIcons.Brightness2,
@@ -397,6 +396,27 @@ fun ProfileScreen(
} }
} }
// 升级套餐提示对话框
if (showUpgradePlanDialog) {
AlertDialog(
onDismissRequest = { showUpgradePlanDialog = false },
title = { Text("功能开发中") },
text = {
Text(
text = "升级/管理套餐功能正在开发中,敬请期待!",
modifier = Modifier.wrapContentHeight()
)
},
confirmButton = {
TextButton(
onClick = { showUpgradePlanDialog = false }
) {
Text("确定", color = LightPurple)
}
}
)
}
// 登出确认对话框 // 登出确认对话框
if (showLogoutDialog) { if (showLogoutDialog) {
AlertDialog( AlertDialog(