diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageBubble.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageBubble.kt index a379493..f16bc19 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageBubble.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageBubble.kt @@ -67,16 +67,7 @@ fun AIMessageBubble( .padding(horizontal = 16.dp, vertical = 8.dp), horizontalArrangement = androidx.compose.foundation.layout.Arrangement.Start ) { - // 头像 - Box( - modifier = Modifier - .size(32.dp) - .padding(end = 8.dp) - ) { - avatar() - } - - // 消息气泡 + // 消息气泡(暂时不显示头像) Card( modifier = Modifier .weight(1f) diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageList.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageList.kt index 4317644..ebac9f9 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageList.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/MessageList.kt @@ -69,19 +69,7 @@ fun MessageList( } "assistant" -> { AIMessageBubble( - text = message.content, - avatar = { - // AI头像 - Box( - modifier = Modifier - .size(32.dp) - .clip(RoundedCornerShape(8.dp)) - .background(LightPurple), - contentAlignment = Alignment.Center - ) { - Text("📖", fontSize = 20.sp) - } - } + text = message.content ) } } @@ -94,24 +82,15 @@ fun MessageList( if (isStreaming) { item { AIMessageBubble( - text = streamingText, - avatar = { - Box( - modifier = Modifier - .size(32.dp) - .clip(RoundedCornerShape(8.dp)) - .background(LightPurple), - contentAlignment = Alignment.Center - ) { - Text("📖", fontSize = 20.sp) - } - } + text = streamingText ) } } - // 正在输入指示器 - if ((isStreaming && streamingText.isEmpty()) || (isTyping && !isStreaming)) { + // 正在输入指示器 - 显示加载动画 + // 1. 如果正在流式接收但还没有内容,显示加载动画 + // 2. 如果设置了isTyping且不在流式状态,显示加载动画 + if (isTyping || (isStreaming && streamingText.isEmpty())) { item { TypingIndicator() } diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/TypingIndicator.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/TypingIndicator.kt index 2415e42..251aeba 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/TypingIndicator.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/chat/TypingIndicator.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.huaga.life_echo.ui.theme.LightPurple /** @@ -32,20 +33,7 @@ fun TypingIndicator( .padding(horizontal = 16.dp, vertical = 8.dp), horizontalArrangement = androidx.compose.foundation.layout.Arrangement.Start ) { - // 头像占位 - Box( - modifier = Modifier - .size(32.dp) - .clip(RoundedCornerShape(8.dp)) - .background(LightPurple), - contentAlignment = Alignment.Center - ) { - // 可以显示一个图标或占位符 - } - - Spacer(modifier = Modifier.width(8.dp)) - - // 消息气泡 + // 消息气泡(暂时不显示头像) Card( modifier = Modifier .shadow(2.dp, RoundedCornerShape(12.dp)), @@ -116,6 +104,6 @@ private fun Dot(alpha: Float) { modifier = Modifier .size(8.dp) .clip(CircleShape) - .background(Color.Gray.copy(alpha = alpha)) + .background(LightPurple.copy(alpha = alpha)) ) } diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/common/EmptyStateView.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/common/EmptyStateView.kt index 2af2794..156c75c 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/common/EmptyStateView.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/common/EmptyStateView.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.unit.sp */ @Composable fun EmptyStateView( + title: String? = null, message: String, icon: String = "📭", modifier: Modifier = Modifier @@ -31,6 +32,16 @@ fun EmptyStateView( fontSize = 48.sp ) Spacer(modifier = Modifier.height(16.dp)) + if (title != null) { + Text( + text = title, + fontSize = 20.sp, + fontWeight = androidx.compose.ui.text.font.FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center + ) + Spacer(modifier = Modifier.height(8.dp)) + } Text( text = message, fontSize = 16.sp, diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/conversation/ConversationListHeader.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/conversation/ConversationListHeader.kt index 897499d..4b75025 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/conversation/ConversationListHeader.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/conversation/ConversationListHeader.kt @@ -1,7 +1,11 @@ package com.huaga.life_echo.ui.components.conversation +import android.annotation.SuppressLint +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -11,6 +15,7 @@ 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.theme.LightPurple /** @@ -18,7 +23,8 @@ import com.huaga.life_echo.ui.theme.LightPurple */ @Composable fun ConversationListHeader( - modifier: Modifier = Modifier + onCreateConversation: () -> Unit = {}, + @SuppressLint("ModifierParameter") modifier: Modifier = Modifier ) { Surface( modifier = modifier.fillMaxWidth(), @@ -31,18 +37,28 @@ fun ConversationListHeader( .padding(top = 16.dp, bottom = 24.dp, start = 16.dp, end = 16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Text( - text = "往事拾遗", - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - color = Color.White - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = "用对话,留住珍贵的记忆", - fontSize = 14.sp, - color = Color.White.copy(alpha = 0.9f) - ) + Spacer(modifier = Modifier.height(16.dp)) + // 新建对话按钮 + Button( + onClick = onCreateConversation, + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = LightPurple + ), + modifier = Modifier.fillMaxWidth() + ) { + androidx.compose.material3.Icon( + imageVector = AppIcons.Add, + contentDescription = "新建对话", + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = "新建对话", + fontSize = 16.sp, + fontWeight = FontWeight.Bold + ) + } } } } diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/conversation/ConversationListItem.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/conversation/ConversationListItem.kt index ec3fdd6..10a1376 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/conversation/ConversationListItem.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/conversation/ConversationListItem.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -29,6 +30,7 @@ import com.huaga.life_echo.utils.TextUtils fun ConversationListItem( conversation: ConversationListItemDto, onClick: () -> Unit, + onDelete: (() -> Unit)? = null, modifier: Modifier = Modifier ) { Row( @@ -38,16 +40,7 @@ fun ConversationListItem( .padding(horizontal = 16.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically ) { - // 头像 - ConversationAvatar( - avatarUrl = conversation.avatarUrl, - isDefaultAssistant = conversation.isDefaultAssistant, - modifier = Modifier.size(48.dp) - ) - - Spacer(modifier = Modifier.width(12.dp)) - - // 对话信息 + // 对话信息(暂时不显示头像) Column( modifier = Modifier.weight(1f) ) { @@ -98,6 +91,22 @@ fun ConversationListItem( timestamp = conversation.latestMessageTime, modifier = Modifier.padding(start = 8.dp) ) + + // 删除按钮 + if (onDelete != null) { + Spacer(modifier = Modifier.width(8.dp)) + IconButton( + onClick = { onDelete() }, + modifier = Modifier.size(40.dp) + ) { + Icon( + imageVector = AppIcons.Delete, + contentDescription = "删除", + tint = MaterialTheme.colorScheme.error, + modifier = Modifier.size(20.dp) + ) + } + } } } diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/memoir/BookInfoCard.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/memoir/BookInfoCard.kt index b1e81a5..8d5c433 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/memoir/BookInfoCard.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/memoir/BookInfoCard.kt @@ -1,35 +1,21 @@ package com.huaga.life_echo.ui.components.memoir -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.* 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.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.huaga.life_echo.network.models.BookDto -import com.huaga.life_echo.ui.icons.AppIcons -import com.huaga.life_echo.ui.theme.LightPurple -import com.huaga.life_echo.utils.TimeUtils /** - * 书籍信息卡片(支持书名编辑) + * 书籍信息卡片 */ @Composable fun BookInfoCard( @@ -38,15 +24,6 @@ fun BookInfoCard( onSubtitleChange: ((String?) -> Unit)? = null, modifier: Modifier = Modifier ) { - var isEditingTitle by remember { mutableStateOf(false) } - var editedTitle by remember { mutableStateOf(book.title) } - val keyboardController = LocalSoftwareKeyboardController.current - - // 当book变化时更新编辑状态 - LaunchedEffect(book.title) { - editedTitle = book.title - } - Card( modifier = modifier.fillMaxWidth(), shape = RoundedCornerShape(12.dp), @@ -60,54 +37,14 @@ fun BookInfoCard( .padding(20.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - // 书名(可编辑) - if (isEditingTitle) { - TextField( - value = editedTitle, - onValueChange = { editedTitle = it }, - singleLine = true, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions( - onDone = { - isEditingTitle = false - keyboardController?.hide() - if (editedTitle.isNotBlank()) { - onTitleChange(editedTitle) - } else { - editedTitle = book.title - } - } - ), - colors = TextFieldDefaults.colors( - focusedTextColor = MaterialTheme.colorScheme.primary, - unfocusedTextColor = MaterialTheme.colorScheme.primary - ), - textStyle = MaterialTheme.typography.headlineMedium.copy( - fontWeight = FontWeight.Bold - ) - ) - } else { - Row( - modifier = Modifier - .clickable { isEditingTitle = true } - .padding(vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = book.title, - fontSize = 32.sp, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.width(8.dp)) - Icon( - imageVector = AppIcons.Edit, - contentDescription = "编辑", - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(20.dp) - ) - } - } + // 书名(只显示,不可编辑) + Text( + text = book.title, + fontSize = 32.sp, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(vertical = 4.dp) + ) Spacer(modifier = Modifier.height(4.dp)) diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/memoir/ChapterCard.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/memoir/ChapterCard.kt index 5782d31..c7cfce9 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/memoir/ChapterCard.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/memoir/ChapterCard.kt @@ -1,5 +1,7 @@ package com.huaga.life_echo.ui.components.memoir +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -9,7 +11,7 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -23,7 +25,7 @@ import com.huaga.life_echo.ui.icons.AppIcons import com.huaga.life_echo.ui.theme.LightPurple /** - * 章节卡片组件(显示页数) + * 章节卡片组件(可展开显示详细内容) */ @Composable fun ChapterCard( @@ -31,6 +33,8 @@ fun ChapterCard( onClick: () -> Unit, modifier: Modifier = Modifier ) { + var isExpanded by remember { mutableStateOf(false) } + val statusText = when (chapter.status) { "completed" -> "已整理" "partial" -> "部分整理" @@ -40,62 +44,98 @@ fun ChapterCard( Card( modifier = modifier .fillMaxWidth() - .clickable { onClick() } - .shadow(2.dp, RoundedCornerShape(12.dp)), + .shadow(2.dp, RoundedCornerShape(12.dp)) + .animateContentSize(animationSpec = tween(300)), shape = RoundedCornerShape(12.dp), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surface ) ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically + Column( + modifier = Modifier.fillMaxWidth() ) { - // 浅紫色编号背景 - Box( + // 章节头部(可点击展开/收起) + Row( modifier = Modifier - .size(48.dp) - .clip(RoundedCornerShape(8.dp)) - .background(LightPurple), - contentAlignment = Alignment.Center + .fillMaxWidth() + .clickable { isExpanded = !isExpanded } + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically ) { - Text( - text = String.format("%02d", chapter.order_index), - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - color = Color.White + // 浅紫色编号背景 + Box( + modifier = Modifier + .size(48.dp) + .clip(RoundedCornerShape(8.dp)) + .background(LightPurple), + contentAlignment = Alignment.Center + ) { + Text( + text = String.format("%02d", chapter.order_index), + 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 = if (isExpanded) AppIcons.ExpandLess else AppIcons.ExpandMore, + contentDescription = if (isExpanded) "收起" else "展开", + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(24.dp) ) } - 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 + // 展开时显示详细内容 + if (isExpanded) { + androidx.compose.material3.Divider( + modifier = Modifier.padding(horizontal = 16.dp) ) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Text( + text = chapter.content, + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface, + lineHeight = 22.sp + ) + Spacer(modifier = Modifier.height(12.dp)) + // 查看详情按钮 + androidx.compose.material3.Button( + onClick = onClick, + modifier = Modifier.fillMaxWidth(), + colors = androidx.compose.material3.ButtonDefaults.buttonColors( + containerColor = LightPurple + ) + ) { + Text("查看详情", color = Color.White) + } + } } - - // 右箭头 - Icon( - imageVector = AppIcons.ChevronRight, - contentDescription = "进入", - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(24.dp) - ) } } } diff --git a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/profile/UserAvatar.kt b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/profile/UserAvatar.kt index 74c9d8a..05893ec 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/ui/components/profile/UserAvatar.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/ui/components/profile/UserAvatar.kt @@ -9,12 +9,17 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.huaga.life_echo.config.AppConfig import com.huaga.life_echo.ui.icons.AppIcons import com.huaga.life_echo.ui.theme.LightPurple /** - * 用户头像组件(支持占位符) + * 用户头像组件(支持占位符和网络图片加载) */ @Composable fun UserAvatar( @@ -22,6 +27,8 @@ fun UserAvatar( modifier: Modifier = Modifier, size: androidx.compose.ui.unit.Dp = 80.dp ) { + val context = LocalContext.current + Box( modifier = modifier .size(size) @@ -29,13 +36,24 @@ fun UserAvatar( .background(LightPurple.copy(alpha = 0.2f)), contentAlignment = Alignment.Center ) { - if (avatarUrl != null) { - // TODO: 使用Coil或Glide加载网络图片 - Icon( - imageVector = AppIcons.Person, + if (!avatarUrl.isNullOrBlank()) { + // 构建完整的图片URL + val fullUrl = if (avatarUrl.startsWith("http")) { + avatarUrl + } else { + "${AppConfig.BASE_URL}$avatarUrl" + } + + AsyncImage( + model = ImageRequest.Builder(context) + .data(fullUrl) + .crossfade(true) + .build(), contentDescription = "用户头像", - tint = LightPurple, - modifier = Modifier.size(size * 0.6f) + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .clip(CircleShape) ) } else { Icon(