refactor: 优化前端功能屏幕
- 优化ConversationListScreen对话列表页面 - 优化CreateMemoryScreen创建回忆页面 - 优化MyMemoirScreen我的回忆录页面 - 优化ProfileScreen个人资料页面 - 优化ExportDataScreen导出数据页面 - 优化AboutScreen关于页面
This commit is contained in:
@@ -160,7 +160,7 @@ fun AboutScreen(
|
||||
|
||||
// 版权信息
|
||||
Text(
|
||||
text = "© 2024 Life Echo. All rights reserved.",
|
||||
text = "© 2026 Life Echo. All rights reserved.",
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
package com.huaga.life_echo.ui.screens
|
||||
|
||||
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.items
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
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.Conversation
|
||||
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.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.viewmodel.ConversationListViewModel
|
||||
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ConversationListScreen(
|
||||
@@ -41,11 +46,26 @@ fun ConversationListScreen(
|
||||
viewModel.refreshConversations()
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// 处理新建对话
|
||||
val handleCreateConversation: () -> Unit = {
|
||||
scope.launch {
|
||||
val result = viewModel.createConversation()
|
||||
result.fold(
|
||||
onSuccess = { conversationId ->
|
||||
onConversationClick(conversationId)
|
||||
},
|
||||
onFailure = { exception ->
|
||||
// 错误处理可以在这里添加
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
// 使用新的头部组件
|
||||
ConversationListHeader()
|
||||
|
||||
// 对话列表区域
|
||||
Column(
|
||||
@@ -53,6 +73,7 @@ fun ConversationListScreen(
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
ConversationListHeader(onCreateConversation = handleCreateConversation)
|
||||
Text(
|
||||
text = "我的对话",
|
||||
modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 8.dp),
|
||||
@@ -66,12 +87,20 @@ fun ConversationListScreen(
|
||||
LoadingIndicator()
|
||||
}
|
||||
error != null -> {
|
||||
// 即使有错误,也显示默认对话
|
||||
DefaultConversationItem(onClick = { onConversationClick("demo") })
|
||||
// 显示错误信息
|
||||
EmptyStateView(
|
||||
title = "加载失败",
|
||||
message = error ?: "未知错误",
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
conversations.isEmpty() -> {
|
||||
// 空状态 - 显示默认对话
|
||||
DefaultConversationItem(onClick = { onConversationClick("demo") })
|
||||
// 空状态 - 提示用户创建新对话
|
||||
EmptyStateView(
|
||||
title = "还没有对话",
|
||||
message = "点击上方「新建对话」按钮开始您的回忆录之旅",
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
LazyColumn(
|
||||
@@ -92,7 +121,12 @@ fun ConversationListScreen(
|
||||
)
|
||||
ConversationListItem(
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.huaga.life_echo.data.database.Message
|
||||
import com.huaga.life_echo.network.models.MessageDto
|
||||
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.viewmodel.CreateMemoryViewModel
|
||||
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
|
||||
@@ -39,32 +40,58 @@ fun CreateMemoryScreen(
|
||||
val agentResponse by viewModel.agentResponse.collectAsState()
|
||||
val connectionStatus by viewModel.connectionStatus.collectAsState()
|
||||
val userMessages by viewModel.userMessages.collectAsState()
|
||||
val historyMessages by viewModel.historyMessages.collectAsState()
|
||||
val isStreaming by viewModel.isStreaming.collectAsState()
|
||||
val streamingText by viewModel.streamingText.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("") }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
// 构建消息列表
|
||||
val messages = remember(userMessages, agentResponse) {
|
||||
// 构建消息列表(包含历史消息和当前消息)
|
||||
val messages = remember(historyMessages, userMessages, agentResponse) {
|
||||
buildList {
|
||||
// 添加用户消息
|
||||
// 先添加历史消息
|
||||
addAll(historyMessages)
|
||||
|
||||
// 添加当前会话的用户消息(排除已存在的)
|
||||
val existingUserMessageIds = historyMessages.filter { it.senderType == "user" }.map { it.content }.toSet()
|
||||
userMessages.forEachIndexed { index, text ->
|
||||
add(MessageDto(
|
||||
id = "user_$index",
|
||||
conversationId = conversationId,
|
||||
content = text,
|
||||
senderType = "user",
|
||||
timestamp = System.currentTimeMillis() - (userMessages.size - index) * 1000L,
|
||||
messageType = "text"
|
||||
))
|
||||
if (!existingUserMessageIds.contains(text)) {
|
||||
add(MessageDto(
|
||||
id = "user_${historyMessages.size + index}",
|
||||
conversationId = conversationId,
|
||||
content = text,
|
||||
senderType = "user",
|
||||
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(
|
||||
id = "ai_response",
|
||||
id = "ai_response_${historyMessages.size + userMessages.size}",
|
||||
conversationId = conversationId,
|
||||
content = agentResponse,
|
||||
senderType = "assistant",
|
||||
@@ -72,7 +99,7 @@ fun CreateMemoryScreen(
|
||||
messageType = "text"
|
||||
))
|
||||
}
|
||||
}
|
||||
}.sortedBy { it.timestamp } // 按时间排序
|
||||
}
|
||||
|
||||
Column(
|
||||
@@ -85,6 +112,22 @@ fun CreateMemoryScreen(
|
||||
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(
|
||||
messages = messages,
|
||||
|
||||
@@ -149,42 +149,40 @@ fun ExportDataScreen(
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// 导出按钮
|
||||
// 导出按钮 - 显示开发中提示
|
||||
var showDevDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
isExporting = true
|
||||
// TODO: 执行导出操作
|
||||
// 模拟导出完成
|
||||
scope.launch {
|
||||
delay(2000)
|
||||
isExporting = false
|
||||
exportCompleted = true
|
||||
}
|
||||
showDevDialog = true
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = !isExporting && !exportCompleted,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = LightPurple
|
||||
)
|
||||
) {
|
||||
if (isExporting) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(20.dp),
|
||||
color = Color.White
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("导出中...", color = Color.White)
|
||||
} else if (exportCompleted) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.CheckCircle,
|
||||
contentDescription = "完成",
|
||||
tint = Color.White
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("导出完成", color = Color.White)
|
||||
} else {
|
||||
Text("开始导出", color = Color.White)
|
||||
}
|
||||
Text("开始导出", color = Color.White)
|
||||
}
|
||||
|
||||
// 开发中提示对话框
|
||||
if (showDevDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDevDialog = false },
|
||||
title = { Text("功能开发中") },
|
||||
text = {
|
||||
Text(
|
||||
text = "导出所有数据功能正在开发中,敬请期待!",
|
||||
modifier = Modifier.wrapContentHeight()
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = { showDevDialog = false }
|
||||
) {
|
||||
Text("确定", color = LightPurple)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.huaga.life_echo.ui.screens
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
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.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
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.network.models.ChapterContentDto
|
||||
import com.huaga.life_echo.network.models.ChapterDto
|
||||
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.viewmodel.MyMemoirViewModel
|
||||
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.network.models.ConversationListItemDto
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun MyMemoirScreen(
|
||||
navController: androidx.navigation.NavHostController? = null,
|
||||
viewModel: MyMemoirViewModel = viewModel(
|
||||
factory = ViewModelFactory(LocalContext.current)
|
||||
),
|
||||
conversationListViewModel: ConversationListViewModel = viewModel(
|
||||
factory = ViewModelFactory(LocalContext.current)
|
||||
)
|
||||
) {
|
||||
val chapters by viewModel.chapters.collectAsState(initial = emptyList())
|
||||
@@ -40,7 +54,53 @@ fun MyMemoirScreen(
|
||||
val isLoading by viewModel.isLoading.collectAsState()
|
||||
val bookInfo by viewModel.bookInfo.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) {
|
||||
@@ -104,31 +164,18 @@ fun MyMemoirScreen(
|
||||
)
|
||||
} else {
|
||||
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) {
|
||||
// 目录视图
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp)
|
||||
// 目录视图(带下拉刷新)
|
||||
PullToRefreshBox(
|
||||
isRefreshing = isRefreshing,
|
||||
onRefresh = { handleRefresh() },
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp),
|
||||
state = rememberLazyListState()
|
||||
) {
|
||||
// 书籍信息卡片
|
||||
item {
|
||||
bookInfo?.let { book ->
|
||||
@@ -160,62 +207,150 @@ fun MyMemoirScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// 「阅读全文」浮动按钮区域
|
||||
// 操作按钮区域
|
||||
item {
|
||||
Row(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
.padding(vertical = 16.dp)
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = { viewModel.toggleFullTextReading() },
|
||||
containerColor = LightPurple
|
||||
) {
|
||||
Icon(
|
||||
imageVector = AppIcons.Reading,
|
||||
contentDescription = "阅读全文",
|
||||
tint = Color.White
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("阅读全文", color = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 章节列表
|
||||
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)
|
||||
// 整理进度条(如果正在整理)
|
||||
if (isOrganizing) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = organizingStatus.ifEmpty { "正在整理对话..." },
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
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))
|
||||
}
|
||||
|
||||
// 如果没有章节,显示示例
|
||||
if (chapterDtos.isEmpty()) {
|
||||
item {
|
||||
// 显示示例章节
|
||||
val now = java.time.Instant.now().toString()
|
||||
listOf(
|
||||
ChapterDto("demo1", "童年与家庭", "", 1, "completed", "childhood", emptyList(), now, false, emptyList()),
|
||||
ChapterDto("demo2", "上学的日子", "", 2, "partial", "education", emptyList(), now, false, emptyList()),
|
||||
ChapterDto("demo3", "工作与事业", "", 3, "pending", "career", emptyList(), now, true, emptyList()),
|
||||
ChapterDto("demo4", "爱情与婚姻", "", 4, "pending", "family", emptyList(), now, true, emptyList())
|
||||
).forEach { chapterDto ->
|
||||
ChapterCard(
|
||||
chapter = chapterDto,
|
||||
onClick = { }
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
// 操作按钮
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
// 整理对话按钮(改进样式)
|
||||
Button(
|
||||
onClick = { showOrganizeDialog = true },
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = !isOrganizing,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = LightPurple
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
Row(
|
||||
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 {
|
||||
// 章节阅读视图
|
||||
@@ -261,30 +396,48 @@ fun MyMemoirScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
.height(2.dp)
|
||||
.fillMaxWidth()
|
||||
.background(LightPurple)
|
||||
// 整理对话对话框
|
||||
if (showOrganizeDialog) {
|
||||
OrganizeConversationDialog(
|
||||
conversations = conversationDtos,
|
||||
isLoading = conversationsLoading,
|
||||
onDismiss = { showOrganizeDialog = false },
|
||||
onSelectConversation = { conversationId ->
|
||||
viewModel.organizeConversation(
|
||||
conversationId = conversationId,
|
||||
onSuccess = {
|
||||
showOrganizeSuccessDialog = true
|
||||
},
|
||||
onError = { errorMsg ->
|
||||
// 错误处理可以在这里添加
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 整理成功对话框
|
||||
if (showOrganizeSuccessDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showOrganizeSuccessDialog = false },
|
||||
title = { Text("整理成功") },
|
||||
text = {
|
||||
Text(
|
||||
text = "对话内容正在整理中,请稍后刷新查看章节。",
|
||||
modifier = Modifier.wrapContentHeight()
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
showOrganizeSuccessDialog = false
|
||||
viewModel.refreshChapters()
|
||||
}
|
||||
) {
|
||||
Text("确定", color = LightPurple)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.ViewModelFactory
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import java.io.File
|
||||
|
||||
@Composable
|
||||
fun ProfileScreen(
|
||||
@@ -73,11 +76,30 @@ fun ProfileScreen(
|
||||
var speechRate by remember { mutableStateOf(AppSettings.speechRate) }
|
||||
var showSpeechRateDialog by remember { mutableStateOf(false) }
|
||||
var showLogoutDialog by remember { mutableStateOf(false) }
|
||||
var showUpgradePlanDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val isLoggedIn by authViewModel.isLoggedIn.collectAsState()
|
||||
val currentUser by authViewModel.currentUser.collectAsState()
|
||||
val userProfile by profileViewModel.userProfile.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
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -97,42 +119,22 @@ fun ProfileScreen(
|
||||
AppSettings.speechRate = speechRate
|
||||
}
|
||||
|
||||
// 语速选择对话框
|
||||
// 语速选择对话框 - 显示开发中提示
|
||||
if (showSpeechRateDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showSpeechRateDialog = false },
|
||||
title = { Text("选择语速") },
|
||||
title = { Text("提示") },
|
||||
text = {
|
||||
Column(
|
||||
Text(
|
||||
text = "语音模块正在开发中,敬请期待!",
|
||||
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 = {
|
||||
TextButton(onClick = { showSpeechRateDialog = false }) {
|
||||
Text("取消")
|
||||
TextButton(
|
||||
onClick = { showSpeechRateDialog = false }
|
||||
) {
|
||||
Text("确定", color = LightPurple)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -153,14 +155,7 @@ fun ProfileScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
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 = userProfile?.nickname ?: currentUser!!.nickname,
|
||||
@@ -169,7 +164,26 @@ fun ProfileScreen(
|
||||
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组件
|
||||
com.huaga.life_echo.ui.components.profile.PlanStatusBadge(
|
||||
@@ -270,9 +284,9 @@ fun ProfileScreen(
|
||||
SettingItem(
|
||||
icon = AppIcons.Upgrade,
|
||||
title = "升级/管理套餐",
|
||||
subtitle = "解锁完整导出与更多功能",
|
||||
subtitle = "功能开发中",
|
||||
onClick = {
|
||||
navController?.navigate(com.huaga.life_echo.navigation.Screen.UpgradePlan.route)
|
||||
showUpgradePlanDialog = true
|
||||
}
|
||||
)
|
||||
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||
@@ -329,30 +343,15 @@ fun ProfileScreen(
|
||||
SettingItem(
|
||||
icon = AppIcons.AccessTime,
|
||||
title = "语速",
|
||||
subtitle = speechRate.label,
|
||||
subtitle = "正在开发语音模块",
|
||||
onClick = {
|
||||
// 显示语速选择对话框
|
||||
// 显示提示对话框
|
||||
showSpeechRateDialog = true
|
||||
}
|
||||
)
|
||||
HorizontalDivider(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 }
|
||||
)
|
||||
// 大字模式暂时不开放
|
||||
// HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||
// SettingItem(...)
|
||||
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||
SettingItem(
|
||||
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) {
|
||||
AlertDialog(
|
||||
|
||||
Reference in New Issue
Block a user