Merge remote-tracking branch 'origin/development' into development
This commit is contained in:
@@ -11,9 +11,14 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -25,6 +30,7 @@ import com.huaga.life_echo.ui.theme.AppTypography
|
||||
import com.huaga.life_echo.ui.theme.LightPurple
|
||||
import com.huaga.life_echo.utils.TimeUtils
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* 消息列表组件
|
||||
@@ -52,7 +58,9 @@ fun MessageList(
|
||||
audioDurations: Map<String, Int> = emptyMap() // messageId -> 时长(秒)
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
var lastHeightPx by remember { mutableIntStateOf(0) }
|
||||
|
||||
// 计算实际的列表项数量(考虑分割消息和附加项)
|
||||
val estimatedItemCount = remember(messages, isStreaming, streamingText, isTyping) {
|
||||
var count = 0
|
||||
@@ -110,7 +118,25 @@ fun MessageList(
|
||||
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.onSizeChanged { size ->
|
||||
val h = size.height
|
||||
// 键盘弹出导致列表高度变矮时,滚到最底部,让最后一条气泡紧贴输入框上方
|
||||
if (lastHeightPx > 0 && h < lastHeightPx) {
|
||||
scope.launch {
|
||||
delay(80)
|
||||
val count = listState.layoutInfo.totalItemsCount
|
||||
if (count > 0) {
|
||||
val viewportHeight = listState.layoutInfo.viewportSize.height
|
||||
// 一次平滑滚到底:用 scrollOffset 让最后一项贴底,避免两段滚动和中间停顿
|
||||
val scrollOffset = (viewportHeight - 120).coerceAtLeast(0)
|
||||
listState.animateScrollToItem(count - 1, scrollOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
lastHeightPx = h
|
||||
},
|
||||
contentPadding = PaddingValues(vertical = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
|
||||
@@ -14,12 +14,15 @@ 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.clickable
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -37,8 +40,10 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -54,6 +59,7 @@ import com.huaga.life_echo.ui.theme.AppWhite
|
||||
import com.huaga.life_echo.ui.theme.Cream
|
||||
import com.huaga.life_echo.ui.theme.DeepPurple
|
||||
import com.huaga.life_echo.ui.theme.DividerColor
|
||||
import com.huaga.life_echo.ui.theme.Lavender
|
||||
import com.huaga.life_echo.ui.theme.MediumPurple
|
||||
import com.huaga.life_echo.ui.theme.SlatePurple
|
||||
import com.huaga.life_echo.ui.viewmodel.ConversationListViewModel
|
||||
@@ -77,33 +83,11 @@ fun ConversationListScreen(
|
||||
var isSelectionMode by remember { mutableStateOf(false) }
|
||||
var selectedIds by remember { mutableStateOf(mutableSetOf<String>()) }
|
||||
|
||||
// 是否正在自动创建对话
|
||||
var isAutoCreating by remember { mutableStateOf(false) }
|
||||
// 是否正在创建新对话(点击「打个招呼」)
|
||||
var isCreating by remember { mutableStateOf(false) }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
// 加载完成后,当对话列表为空时,自动创建一个对话并进入
|
||||
LaunchedEffect(conversations, isLoading, isAutoCreating, hasLoadedInitialConversations, error) {
|
||||
if (
|
||||
hasLoadedInitialConversations &&
|
||||
!isLoading &&
|
||||
error == null &&
|
||||
conversations.isEmpty() &&
|
||||
!isAutoCreating
|
||||
) {
|
||||
isAutoCreating = true
|
||||
val result = viewModel.createConversation()
|
||||
result.fold(
|
||||
onSuccess = { conversationId ->
|
||||
onConversationClick(conversationId)
|
||||
},
|
||||
onFailure = { exception ->
|
||||
isAutoCreating = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理长按进入多选模式
|
||||
val handleLongClick: (String) -> Unit = { conversationId ->
|
||||
if (!isSelectionMode) {
|
||||
@@ -192,22 +176,43 @@ fun ConversationListScreen(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
conversations.isEmpty() -> {
|
||||
if (isAutoCreating) {
|
||||
LoadingIndicator()
|
||||
} else {
|
||||
EmptyStateView(
|
||||
title = "正在初始化",
|
||||
message = "正在为您准备回忆录对话...",
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = AppDimensions.screenPadding)
|
||||
) {
|
||||
// 置顶入口:打个招呼(点击即新建空会话)
|
||||
item(key = "say_hi") {
|
||||
SayHiEntry(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(enabled = !isCreating) {
|
||||
if (isCreating) return@clickable
|
||||
scope.launch {
|
||||
isCreating = true
|
||||
viewModel.createConversation()
|
||||
.fold(
|
||||
onSuccess = { conversationId ->
|
||||
onConversationClick(conversationId)
|
||||
},
|
||||
onFailure = { }
|
||||
)
|
||||
isCreating = false
|
||||
}
|
||||
}
|
||||
.padding(
|
||||
horizontal = AppDimensions.screenPadding,
|
||||
vertical = AppDimensions.itemSpacing
|
||||
),
|
||||
isLoading = isCreating
|
||||
)
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(horizontal = AppDimensions.screenPadding),
|
||||
thickness = AppDimensions.dividerThickness,
|
||||
color = DividerColor
|
||||
)
|
||||
}
|
||||
|
||||
// 区块标题(与章节正文字号一致,大字模式更易读)
|
||||
item {
|
||||
Text(
|
||||
@@ -224,7 +229,7 @@ fun ConversationListScreen(
|
||||
|
||||
// 对话列表
|
||||
items(conversations, key = { it.id }) { conversation ->
|
||||
// 兼容新旧数据:将"岁月知己"和旧名称"回忆录助手"都识别为默认助手
|
||||
// 兼容新旧数据:将"岁月知己"和旧名称"回忆录助手"都识别为默认助手,展示为「岁月知己」
|
||||
val isAssistant = conversation.title == null || conversation.title == "岁月知己" || conversation.title == "回忆录助手"
|
||||
val displayTitle = if (isAssistant) "岁月知己" else conversation.title!!
|
||||
val dto = ConversationListItemDto(
|
||||
@@ -284,6 +289,68 @@ fun ConversationListScreen(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 置顶入口:「打个招呼」— 点击即新建空会话
|
||||
*/
|
||||
@Composable
|
||||
private fun SayHiEntry(
|
||||
modifier: Modifier = Modifier,
|
||||
isLoading: Boolean = false
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(AppDimensions.avatarSizeSmall)
|
||||
.clip(RoundedCornerShape(AppDimensions.iconContainerRadius))
|
||||
.background(Lavender),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = AppIcons.Conversation,
|
||||
contentDescription = "打个招呼",
|
||||
tint = DeepPurple,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = "打个招呼",
|
||||
fontSize = AppTypography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = DeepPurple,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "开始新对话",
|
||||
fontSize = AppTypography.bodyMedium,
|
||||
color = SlatePurple,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
if (isLoading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = MediumPurple,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = AppIcons.ChevronRight,
|
||||
contentDescription = null,
|
||||
tint = SlatePurple,
|
||||
modifier = Modifier.size(AppDimensions.iconSizeSmall)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 多选模式头部
|
||||
*/
|
||||
|
||||
@@ -152,7 +152,12 @@ fun CreateMemoryScreen(
|
||||
contentWindowInsets = WindowInsets(0, 0, 0, 0),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) { paddingValues ->
|
||||
Box(modifier = Modifier.fillMaxSize().padding(paddingValues)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.imePadding()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
@@ -200,7 +205,6 @@ fun CreateMemoryScreen(
|
||||
|
||||
// 使用新的ChatInputField组件(支持语音输入)
|
||||
ChatInputField(
|
||||
modifier = Modifier.imePadding(),
|
||||
value = inputText,
|
||||
onValueChange = {
|
||||
conversationDrafts = ConversationDrafts.updateDraft(
|
||||
|
||||
Reference in New Issue
Block a user