diff --git a/app/src/main/java/io/legado/app/ui/ai/chat/AiChatScreen.kt b/app/src/main/java/io/legado/app/ui/ai/chat/AiChatScreen.kt index ac2d9b39a..ec39bd447 100644 --- a/app/src/main/java/io/legado/app/ui/ai/chat/AiChatScreen.kt +++ b/app/src/main/java/io/legado/app/ui/ai/chat/AiChatScreen.kt @@ -45,8 +45,10 @@ import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.Lightbulb import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Stop import androidx.compose.material3.DrawerValue +import androidx.compose.material3.DismissibleNavigationDrawer import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.ModalDrawerSheet @@ -70,6 +72,7 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource @@ -83,10 +86,12 @@ import io.legado.app.domain.model.AiMessagePart import io.legado.app.domain.model.AiReasoningLevel import io.legado.app.domain.model.AiMessageRole import io.legado.app.ui.theme.LegadoTheme +import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.button.series.MediumTonalButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton import io.legado.app.ui.widget.components.card.GlassCard +import io.legado.app.ui.widget.components.card.NormalCard import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet import io.legado.app.ui.widget.components.image.cover.CoilBookCover import io.legado.app.ui.widget.components.text.AppText @@ -99,6 +104,10 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.concurrent.TimeUnit @Composable fun AiChatRouteScreen( @@ -157,6 +166,7 @@ fun AiChatScreen( } ) { AppScaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(snackbarHostState) }, topBar = { GlassMediumFlexibleTopAppBar( @@ -461,7 +471,21 @@ private fun RecentChatsDrawer( onNewChat: () -> Unit, onSelectConversation: (String) -> Unit ) { - ModalDrawerSheet { + var searchQuery by remember { mutableStateOf("") } + val filteredConversations = remember(conversations, searchQuery) { + if (searchQuery.isBlank()) { + conversations + } else { + conversations.filter { + it.title.contains(searchQuery, ignoreCase = true) + } + } + } + + ModalDrawerSheet( + modifier = Modifier.width(280.dp), + drawerContainerColor = LegadoTheme.colorScheme.surfaceContainerLow + ) { Row( modifier = Modifier .fillMaxWidth() @@ -480,34 +504,79 @@ private fun RecentChatsDrawer( contentDescription = stringResource(R.string.ai_new_chat) ) } + AppTextField( + value = searchQuery, + onValueChange = { searchQuery = it }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 4.dp), + backgroundColor = LegadoTheme.colorScheme.surface, + label = stringResource(R.string.search), + singleLine = true + ) LazyColumn( modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(2.dp) + contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) ) { - items(conversations, key = { it.id }) { conversation -> - val background = if (conversation.isSelected) { - LegadoTheme.colorScheme.primaryContainer - } else { - LegadoTheme.colorScheme.surface - } - Column( - modifier = Modifier - .fillMaxWidth() - .background(background) - .clickable(onClick = { onSelectConversation(conversation.id) }) - .padding(horizontal = 16.dp, vertical = 12.dp) + items(filteredConversations, key = { it.id }) { conversation -> + val isSelected = conversation.isSelected + NormalCard( + modifier = Modifier.fillMaxWidth(), + onClick = { onSelectConversation(conversation.id) }, + containerColor = if (isSelected) { + LegadoTheme.colorScheme.primaryContainer + } else { + LegadoTheme.colorScheme.surface + } ) { - AppText( - text = conversation.title, - style = LegadoTheme.typography.bodyLarge, - maxLines = 2 - ) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 10.dp) + ) { + AppText( + text = conversation.title, + style = LegadoTheme.typography.bodyMedium, + maxLines = 2, + color = if (isSelected) { + LegadoTheme.colorScheme.onPrimaryContainer + } else { + LegadoTheme.colorScheme.onSurface + } + ) + Spacer(modifier = Modifier.height(2.dp)) + AppText( + text = formatRelativeTime(conversation.updatedAt), + style = LegadoTheme.typography.labelSmall, + color = LegadoTheme.colorScheme.outline + ) + } } } } } } +private fun formatRelativeTime(timestamp: Long): String { + val now = System.currentTimeMillis() + val diff = now - timestamp + val minutes = TimeUnit.MILLISECONDS.toMinutes(diff) + val hours = TimeUnit.MILLISECONDS.toHours(diff) + val days = TimeUnit.MILLISECONDS.toDays(diff) + return when { + minutes < 1 -> "刚刚" + minutes < 60 -> "${minutes}分钟前" + hours < 24 -> "${hours}小时前" + days < 7 -> "${days}天前" + days < 30 -> "${days / 7}周前" + else -> { + val sdf = SimpleDateFormat("MM/dd", Locale.getDefault()) + sdf.format(Date(timestamp)) + } + } +} + @Composable private fun ChatMessageItem( message: AiChatMessageUi,