diff --git a/app/src/androidTest/java/com/m57/hermescontrol/ui/chat/TodoTaskCardTest.kt b/app/src/androidTest/java/com/m57/hermescontrol/ui/chat/TodoTaskCardTest.kt index d86e945e..2a24f7b9 100644 --- a/app/src/androidTest/java/com/m57/hermescontrol/ui/chat/TodoTaskCardTest.kt +++ b/app/src/androidTest/java/com/m57/hermescontrol/ui/chat/TodoTaskCardTest.kt @@ -42,7 +42,7 @@ class TodoTaskCardTest { composeTestRule.setContent { HermesControlTheme { - ChatBubble(message = message, isDarkTheme = true) + ToolBubble(message = message) } } @@ -66,7 +66,7 @@ class TodoTaskCardTest { composeTestRule.setContent { HermesControlTheme { - ChatBubble(message = message, isDarkTheme = true) + ToolBubble(message = message) } } @@ -90,7 +90,7 @@ class TodoTaskCardTest { composeTestRule.setContent { HermesControlTheme { - ChatBubble(message = message, isDarkTheme = true) + ToolBubble(message = message) } } @@ -113,7 +113,7 @@ class TodoTaskCardTest { composeTestRule.setContent { HermesControlTheme { - ChatBubble(message = message, isDarkTheme = true) + ToolBubble(message = message) } } diff --git a/app/src/main/java/com/m57/hermescontrol/data/config/ServerStoreState.kt b/app/src/main/java/com/m57/hermescontrol/data/config/ServerStoreState.kt index 5dd785a7..8616b737 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/config/ServerStoreState.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/config/ServerStoreState.kt @@ -1,7 +1,6 @@ package com.m57.hermescontrol.data.config import com.m57.hermescontrol.data.model.PinnedModel -import com.m57.hermescontrol.theme.ChatStyle import com.m57.hermescontrol.theme.ThemePreference import com.m57.hermescontrol.theme.ThemePreset import kotlinx.serialization.Serializable @@ -22,9 +21,6 @@ data class ServerStoreState( val wsAuthParam: String = "token", val typingEffectEnabled: Boolean = true, val typingEffectDelayMs: Int = 30, - // Chat rendering style (issue #866): full-bleed renderer (default) or the - // legacy bubble renderer. - val chatStyle: ChatStyle = ChatStyle.FULL_BLEED, // App display language. "system" = follow device locale; otherwise a BCP-47 // language code such as "en" or "ko". Applied via ContextWrapper in MainActivity. val appLanguage: String = "system", diff --git a/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt b/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt index e1ca8a8e..23d28756 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/local/AuthManager.kt @@ -17,7 +17,6 @@ import com.m57.hermescontrol.data.remote.CleartextPolicy import com.m57.hermescontrol.data.remote.CookieManager import com.m57.hermescontrol.data.remote.ServerEndpoint import com.m57.hermescontrol.data.session.ActiveSessionHolder -import com.m57.hermescontrol.theme.ChatStyle import com.m57.hermescontrol.theme.ThemePreference import com.m57.hermescontrol.theme.ThemePreset import kotlinx.coroutines.CoroutineScope @@ -75,9 +74,6 @@ object AuthManager { private val _themePresetFlow = MutableStateFlow(ThemePreset.DEFAULT) val themePresetFlow: StateFlow = _themePresetFlow.asStateFlow() - private val _chatStyleFlow = MutableStateFlow(ChatStyle.FULL_BLEED) - val chatStyleFlow: StateFlow = _chatStyleFlow.asStateFlow() - private val _tokenFlow = MutableStateFlow(null) val tokenFlow: StateFlow = _tokenFlow.asStateFlow() @@ -184,7 +180,6 @@ object AuthManager { _themePreferenceFlow.value = state.themePreference _useDynamicColorsFlow.value = state.useDynamicColors _themePresetFlow.value = state.themePreset - _chatStyleFlow.value = state.chatStyle // B7 (Jul 08 2026, kanban t_470): keep cookie scope aligned with active profile. appScope?.launch { syncCookieStoreForProfile(state.selectedProfileId) } } @@ -589,15 +584,6 @@ object AuthManager { fun getThemePreset(): ThemePreset = serverStore.getLatestState().themePreset - // ── Chat style (issue #866) ────────────────────────────────────────── - - fun getChatStyle(): ChatStyle = serverStore.getLatestState().chatStyle - - fun setChatStyle(style: ChatStyle) { - _chatStyleFlow.value = style - serverStore.update { it.copy(chatStyle = style) } - } - fun setThemePreset(preset: ThemePreset) { serverStore.update { it.copy(themePreset = preset) } } diff --git a/app/src/main/java/com/m57/hermescontrol/theme/ChatStyle.kt b/app/src/main/java/com/m57/hermescontrol/theme/ChatStyle.kt deleted file mode 100644 index 5fdec866..00000000 --- a/app/src/main/java/com/m57/hermescontrol/theme/ChatStyle.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.m57.hermescontrol.theme - -/** - * Chat message rendering style (issue #866). App-local preference, - * persisted in [com.m57.hermescontrol.data.config.ServerStoreState]. - * - * BUBBLES — legacy bubble renderer (default, unchanged behavior). - * FULL_BLEED — agent prose renders directly on the background with turn - * headers; user messages keep bubbles (parallel renderer). - */ -enum class ChatStyle { BUBBLES, FULL_BLEED } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt index e851c89b..f71e0e3d 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt @@ -3,7 +3,6 @@ package com.m57.hermescontrol.ui.chat import android.content.ClipData import android.text.format.DateFormat import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.animation.expandVertically @@ -74,20 +73,22 @@ import com.m57.hermescontrol.theme.HermesStatusColors import com.m57.hermescontrol.theme.LightOnSurface import com.m57.hermescontrol.theme.LocalHermesStatusColors import com.m57.hermescontrol.theme.onColorFor -import com.m57.hermescontrol.ui.chat.components.ReasoningCard import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter +/** + * The user-message bubble — the universal anchor in the full-bleed chat + * renderer (issue #866). Agent prose renders full-bleed; user messages keep + * this bubble so the conversation stays scannable. + */ @Composable fun ChatBubble( message: ChatMessage, - isDarkTheme: Boolean, searchQuery: String = "", isCurrentMatch: Boolean = false, - onRespondApproval: (String) -> Unit = {}, onOpenAttachment: (Attachment) -> Unit = {}, onSaveAttachment: (Attachment) -> Unit = {}, savingAttachmentPath: String? = null, @@ -106,324 +107,133 @@ fun ChatBubble( animationSpec = spring(stiffness = Spring.StiffnessMediumLow), ), ) { - when (message.role) { - MessageRole.USER -> { - UserBubble( - message = message, - maxWidth = maxBubbleWidth, - searchQuery = searchQuery, - isCurrentMatch = isCurrentMatch, - onOpenAttachment = onOpenAttachment, - onSaveAttachment = onSaveAttachment, - savingAttachmentPath = savingAttachmentPath, - canSaveAttachment = canSaveAttachment, - onImageClick = onImageClick, - modifier = modifier, - ) - } + val clipboard = LocalClipboard.current + val scope = rememberCoroutineScope() + var copied by remember { mutableStateOf(false) } - MessageRole.ASSISTANT -> { - AssistantBubble( - message = message, - maxWidth = maxBubbleWidth, - isDarkTheme = isDarkTheme, - searchQuery = searchQuery, - isCurrentMatch = isCurrentMatch, - onOpenAttachment = onOpenAttachment, - onSaveAttachment = onSaveAttachment, - savingAttachmentPath = savingAttachmentPath, - canSaveAttachment = canSaveAttachment, - onImageClick = onImageClick, - modifier = modifier, - ) - } - - MessageRole.SYSTEM -> { - SystemBubble( - message = message, - onRespondApproval = onRespondApproval, - modifier = modifier, - ) - } - - MessageRole.TOOL -> { - ToolBubble(message, modifier) + // Copy feedback: briefly show ✓ then revert + LaunchedEffect(copied) { + if (copied) { + delay(1500) + copied = false } } - } -} -@Composable -private fun UserBubble( - message: ChatMessage, - maxWidth: androidx.compose.ui.unit.Dp, - searchQuery: String = "", - isCurrentMatch: Boolean = false, - onOpenAttachment: (Attachment) -> Unit = {}, - onSaveAttachment: (Attachment) -> Unit = {}, - savingAttachmentPath: String? = null, - canSaveAttachment: Boolean = true, - onImageClick: (ImageViewerModel) -> Unit = {}, - modifier: Modifier = Modifier, -) { - val clipboard = LocalClipboard.current - val scope = rememberCoroutineScope() - var copied by remember { mutableStateOf(false) } + val statusColors = LocalHermesStatusColors.current - // Copy feedback: briefly show ✓ then revert - LaunchedEffect(copied) { - if (copied) { - delay(1500) - copied = false - } - } - - val statusColors = LocalHermesStatusColors.current - - val highlightedText = - remember(message.content, searchQuery, isCurrentMatch, statusColors) { - if (searchQuery.isNotBlank()) { - buildHighlightedString( - message.content, - searchQuery, - isCurrentMatch, - statusColors, - ) - } else { - AnnotatedString(message.content) - } - } - Box( - modifier = - modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 2.dp), - contentAlignment = Alignment.CenterEnd, - ) { - val primary = MaterialTheme.colorScheme.primary - val userBubbleTextColor = - if (primary.luminance() > 0.5f) { - if (MaterialTheme.colorScheme.onPrimary.luminance() < 0.5f) { - MaterialTheme.colorScheme.onPrimary + val highlightedText = + remember(message.content, searchQuery, isCurrentMatch, statusColors) { + if (searchQuery.isNotBlank()) { + buildHighlightedString( + message.content, + searchQuery, + isCurrentMatch, + statusColors, + ) } else { - LightOnSurface - } - } else { - if (MaterialTheme.colorScheme.onPrimary.luminance() > 0.5f) { - MaterialTheme.colorScheme.onPrimary - } else { - DarkOnSurface + AnnotatedString(message.content) } } - Box { - Surface( - modifier = - Modifier - .widthIn(max = maxWidth) - .clip( - RoundedCornerShape( - topStart = 16.dp, - topEnd = 16.dp, - bottomStart = 16.dp, - bottomEnd = 4.dp, - ), - ).background(color = primary) - .testTag("chat_bubble_user"), - color = Color.Transparent, - tonalElevation = 0.dp, - ) { - Column(modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp)) { - SelectionContainer { - Text( - text = highlightedText, - color = userBubbleTextColor, - style = MaterialTheme.typography.bodyMedium, - ) + Box( + modifier = + modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 2.dp), + contentAlignment = Alignment.CenterEnd, + ) { + val primary = MaterialTheme.colorScheme.primary + val userBubbleTextColor = + if (primary.luminance() > 0.5f) { + if (MaterialTheme.colorScheme.onPrimary.luminance() < 0.5f) { + MaterialTheme.colorScheme.onPrimary + } else { + LightOnSurface } - // Render inline attachments - if (!message.attachments.isNullOrEmpty()) { - Spacer(modifier = Modifier.height(6.dp)) - message.attachments.forEach { attachment -> - InlineAttachment( - attachment = attachment, - textColor = userBubbleTextColor, - onOpen = { onOpenAttachment(it) }, - onSave = { onSaveAttachment(it) }, - savingPath = savingAttachmentPath, - canSave = canSaveAttachment, - onImageClick = onImageClick, + } else { + if (MaterialTheme.colorScheme.onPrimary.luminance() > 0.5f) { + MaterialTheme.colorScheme.onPrimary + } else { + DarkOnSurface + } + } + Box { + Surface( + modifier = + Modifier + .widthIn(max = maxBubbleWidth) + .clip( + RoundedCornerShape( + topStart = 16.dp, + topEnd = 16.dp, + bottomStart = 16.dp, + bottomEnd = 4.dp, + ), + ).background(color = primary) + .testTag("chat_bubble_user"), + color = Color.Transparent, + tonalElevation = 0.dp, + ) { + Column(modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp)) { + SelectionContainer { + Text( + text = highlightedText, + color = userBubbleTextColor, + style = MaterialTheme.typography.bodyMedium, ) - Spacer(modifier = Modifier.height(4.dp)) } - } - if (!message.isStreaming) { - Row( - modifier = - Modifier - .align(Alignment.End) - .padding(top = 4.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - IconButton( - onClick = { - scope.launch { - clipboard.setClipEntry(ClipEntry(ClipData.newPlainText(null, message.content))) - } - copied = true - }, - modifier = Modifier.size(20.dp), + // Render inline attachments + if (!message.attachments.isNullOrEmpty()) { + Spacer(modifier = Modifier.height(6.dp)) + message.attachments.forEach { attachment -> + InlineAttachment( + attachment = attachment, + textColor = userBubbleTextColor, + onOpen = { onOpenAttachment(it) }, + onSave = { onSaveAttachment(it) }, + savingPath = savingAttachmentPath, + canSave = canSaveAttachment, + onImageClick = onImageClick, + ) + Spacer(modifier = Modifier.height(4.dp)) + } + } + if (!message.isStreaming) { + Row( + modifier = + Modifier + .align(Alignment.End) + .padding(top = 4.dp), + verticalAlignment = Alignment.CenterVertically, ) { - Icon( - imageVector = if (copied) Icons.Filled.Check else Icons.Filled.ContentCopy, - contentDescription = stringResource(R.string.content_desc_copy), - modifier = Modifier.size(12.dp), - tint = userBubbleTextColor.copy(alpha = 0.7f), + IconButton( + onClick = { + scope.launch { + clipboard.setClipEntry( + ClipEntry(ClipData.newPlainText(null, message.content)), + ) + } + copied = true + }, + modifier = Modifier.size(20.dp), + ) { + Icon( + imageVector = if (copied) Icons.Filled.Check else Icons.Filled.ContentCopy, + contentDescription = stringResource(R.string.content_desc_copy), + modifier = Modifier.size(12.dp), + tint = userBubbleTextColor.copy(alpha = 0.7f), + ) + } + Spacer(modifier = Modifier.width(2.dp)) + Text( + text = + formatTimestamp( + message.timestamp, + DateFormat.is24HourFormat(LocalContext.current), + ), + color = userBubbleTextColor.copy(alpha = 0.6f), + style = MaterialTheme.typography.labelSmall, ) } - Spacer(modifier = Modifier.width(2.dp)) - Text( - text = - formatTimestamp( - message.timestamp, - DateFormat.is24HourFormat(LocalContext.current), - ), - color = userBubbleTextColor.copy(alpha = 0.6f), - style = MaterialTheme.typography.labelSmall, - ) - } - } - } - } - } - } -} - -@Composable -private fun AssistantBubble( - message: ChatMessage, - maxWidth: androidx.compose.ui.unit.Dp, - isDarkTheme: Boolean, - searchQuery: String = "", - isCurrentMatch: Boolean = false, - onOpenAttachment: (Attachment) -> Unit = {}, - onSaveAttachment: (Attachment) -> Unit = {}, - savingAttachmentPath: String? = null, - canSaveAttachment: Boolean = true, - onImageClick: (ImageViewerModel) -> Unit = {}, - modifier: Modifier = Modifier, -) { - val bubbleColor = MaterialTheme.colorScheme.surfaceVariant - val textColor = MaterialTheme.colorScheme.onSurfaceVariant - val clipboard = LocalClipboard.current - val scope = rememberCoroutineScope() - var copied by remember { mutableStateOf(false) } - - // Copy feedback: briefly show ✓ then revert - LaunchedEffect(copied) { - if (copied) { - delay(1500) - copied = false - } - } - - Box( - modifier = - modifier - .fillMaxWidth() - .padding(horizontal = 8.dp, vertical = 2.dp), - contentAlignment = Alignment.CenterStart, - ) { - Box { - Surface( - modifier = - Modifier - .widthIn(max = maxWidth) - .animateContentSize() - .clip( - RoundedCornerShape( - topStart = 4.dp, - topEnd = 16.dp, - bottomStart = 16.dp, - bottomEnd = 16.dp, - ), - ).testTag("chat_bubble_assistant"), - color = bubbleColor, - border = - BorderStroke( - width = 1.dp, - color = MaterialTheme.colorScheme.outline.copy(alpha = 0.1f), - ), - tonalElevation = 1.dp, - ) { - Column(modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp)) { - if (message.reasoningText.isNotBlank()) { - ReasoningCard( - reasoningText = message.reasoningText, - isStreaming = message.isStreaming, - ) - Spacer(modifier = Modifier.height(6.dp)) - } - SelectionContainer { - MarkdownText( - text = message.content, - textColor = textColor, - isStreaming = message.isStreaming, - searchQuery = searchQuery, - isCurrentMatch = isCurrentMatch, - onImageClick = onImageClick, - ) - } - // Render inline attachments (mirrors UserBubble so agent-delivered - // media — images, files — shows in assistant bubbles too). - if (!message.attachments.isNullOrEmpty()) { - Spacer(modifier = Modifier.height(6.dp)) - message.attachments.forEach { attachment -> - InlineAttachment( - attachment = attachment, - textColor = textColor, - onOpen = { onOpenAttachment(it) }, - onSave = { onSaveAttachment(it) }, - savingPath = savingAttachmentPath, - canSave = canSaveAttachment, - onImageClick = onImageClick, - ) - Spacer(modifier = Modifier.height(4.dp)) - } - } - if (!message.isStreaming) { - Row( - modifier = - Modifier - .align(Alignment.End) - .padding(top = 4.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - IconButton( - onClick = { - scope.launch { - clipboard.setClipEntry(ClipEntry(ClipData.newPlainText(null, message.content))) - } - copied = true - }, - modifier = Modifier.size(20.dp), - ) { - Icon( - imageVector = if (copied) Icons.Filled.Check else Icons.Filled.ContentCopy, - contentDescription = stringResource(R.string.content_desc_copy), - modifier = Modifier.size(12.dp), - tint = textColor.copy(alpha = 0.6f), - ) - } - Spacer(modifier = Modifier.width(2.dp)) - Text( - text = - formatTimestamp( - message.timestamp, - DateFormat.is24HourFormat(LocalContext.current), - ), - color = textColor.copy(alpha = 0.5f), - style = MaterialTheme.typography.labelSmall, - ) } } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt index 69091295..d630fc11 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatScreen.kt @@ -81,13 +81,11 @@ import com.m57.hermescontrol.data.model.Attachment import com.m57.hermescontrol.data.model.AttachmentSource import com.m57.hermescontrol.data.ws.ConnectionStatus import com.m57.hermescontrol.data.ws.HermesWsClient -import com.m57.hermescontrol.theme.ChatStyle import com.m57.hermescontrol.theme.LocalHermesStatusColors import com.m57.hermescontrol.ui.chat.components.ChatConnectionBanner import com.m57.hermescontrol.ui.chat.components.ChatInputBar import com.m57.hermescontrol.ui.chat.components.ChatLifecycleEffects import com.m57.hermescontrol.ui.chat.components.ChatLoadingOverlay -import com.m57.hermescontrol.ui.chat.components.ChatMessageList import com.m57.hermescontrol.ui.chat.components.ChatResumeErrorOverlay import com.m57.hermescontrol.ui.chat.components.ChatScrollToBottomFab import com.m57.hermescontrol.ui.chat.components.ContextDetailSheet @@ -580,59 +578,32 @@ fun ChatScreen( } } - when (state.chatStyle) { - ChatStyle.BUBBLES -> - ChatMessageList( - messages = state.messages, - streamingMessage = streamingState.streamingMessage, - isSearchActive = state.isSearchActive, - searchQuery = state.searchQuery, - currentSearchMatchIndex = state.currentSearchMatchIndex, - searchMatchIndices = state.searchMatchIndices, - typingEffectEnabled = state.typingEffectEnabled, - typingEffectDelayMs = state.typingEffectDelayMs, - maxToolCallsPerTurn = state.maxToolCallsPerTurn, - isLoading = state.isLoading, - isLoadingOlder = state.isLoadingOlder, - isDark = isDark, - listState = listState, - lastAnimatedMessageId = lastAnimatedMessageId, - onLastAnimatedMessageIdChange = { lastAnimatedMessageId = it }, - viewModel = viewModel, - clarifyRequest = state.clarifyRequest, - onRespondClarify = viewModel::respondToClarify, - onDismissClarify = viewModel::dismissClarify, - onSaveAttachment = onSaveAttachment, - savingAttachmentPath = pendingSavePath ?: state.savingAttachmentPath, - onImageClick = { viewingImage = it }, - ) - - ChatStyle.FULL_BLEED -> - FullBleedChatList( - messages = state.messages, - streamingMessage = streamingState.streamingMessage, - isSearchActive = state.isSearchActive, - searchQuery = state.searchQuery, - currentSearchMatchIndex = state.currentSearchMatchIndex, - searchMatchIndices = state.searchMatchIndices, - typingEffectEnabled = state.typingEffectEnabled, - typingEffectDelayMs = state.typingEffectDelayMs, - maxToolCallsPerTurn = state.maxToolCallsPerTurn, - isLoading = state.isLoading, - isLoadingOlder = state.isLoadingOlder, - isDark = isDark, - listState = listState, - lastAnimatedMessageId = lastAnimatedMessageId, - onLastAnimatedMessageIdChange = { lastAnimatedMessageId = it }, - viewModel = viewModel, - clarifyRequest = state.clarifyRequest, - onRespondClarify = viewModel::respondToClarify, - onDismissClarify = viewModel::dismissClarify, - onSaveAttachment = onSaveAttachment, - savingAttachmentPath = pendingSavePath ?: state.savingAttachmentPath, - onImageClick = { viewingImage = it }, - ) - } + // Full-bleed chat renderer (issue #866) — the single chat + // surface since the bubble renderer was removed. + FullBleedChatList( + messages = state.messages, + streamingMessage = streamingState.streamingMessage, + isSearchActive = state.isSearchActive, + searchQuery = state.searchQuery, + currentSearchMatchIndex = state.currentSearchMatchIndex, + searchMatchIndices = state.searchMatchIndices, + typingEffectEnabled = state.typingEffectEnabled, + typingEffectDelayMs = state.typingEffectDelayMs, + maxToolCallsPerTurn = state.maxToolCallsPerTurn, + isLoading = state.isLoading, + isLoadingOlder = state.isLoadingOlder, + isDark = isDark, + listState = listState, + lastAnimatedMessageId = lastAnimatedMessageId, + onLastAnimatedMessageIdChange = { lastAnimatedMessageId = it }, + viewModel = viewModel, + clarifyRequest = state.clarifyRequest, + onRespondClarify = viewModel::respondToClarify, + onDismissClarify = viewModel::dismissClarify, + onSaveAttachment = onSaveAttachment, + savingAttachmentPath = pendingSavePath ?: state.savingAttachmentPath, + onImageClick = { viewingImage = it }, + ) // Loading overlay ChatLoadingOverlay(isLoading = state.isLoading && state.resumeError == null) diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt index ee4c92f3..0aa8d291 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt @@ -33,7 +33,6 @@ import com.m57.hermescontrol.data.ws.HermesWsClient import com.m57.hermescontrol.data.ws.WsEvent import com.m57.hermescontrol.data.ws.WsMethods import com.m57.hermescontrol.data.ws.toJsonElement -import com.m57.hermescontrol.theme.ChatStyle import com.m57.hermescontrol.ui.common.ActionProgressController import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -274,9 +273,6 @@ data class ChatUiState( // Cached settings val typingEffectEnabled: Boolean = true, val typingEffectDelayMs: Int = 30, - // Chat rendering style (issue #866), cached like the typing-effect - // settings so ChatScreen can pick the renderer without a store read. - val chatStyle: ChatStyle = ChatStyle.FULL_BLEED, // Commands catalog val commandCatalog: CommandCatalog = CommandCatalog(), // Per-command usage counts for the slash-autocomplete ranking (issue @@ -1723,7 +1719,6 @@ class ChatViewModel( state.copy( typingEffectEnabled = AuthManager.isTypingEffectEnabled(), typingEffectDelayMs = AuthManager.getTypingEffectDelayMs(), - chatStyle = AuthManager.getChatStyle(), ) } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/components/ChatMessageList.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/components/ChatMessageList.kt deleted file mode 100644 index 98198c3f..00000000 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/components/ChatMessageList.kt +++ /dev/null @@ -1,169 +0,0 @@ -package com.m57.hermescontrol.ui.chat.components - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.m57.hermescontrol.R -import com.m57.hermescontrol.ui.chat.ChatBubble -import com.m57.hermescontrol.ui.chat.ChatMessage -import com.m57.hermescontrol.ui.chat.ChatViewModel -import com.m57.hermescontrol.ui.chat.ClarifyUi -import com.m57.hermescontrol.ui.chat.ImageViewerModel -import com.m57.hermescontrol.ui.chat.MessageRole -import com.m57.hermescontrol.ui.chat.ToolCallDivider -import com.m57.hermescontrol.ui.chat.toolCallMilestones -import com.m57.hermescontrol.ui.common.EmptyState - -/** - * The chat message list. - */ -@Composable -fun ChatMessageList( - messages: List, - streamingMessage: ChatMessage?, - isSearchActive: Boolean, - searchQuery: String, - currentSearchMatchIndex: Int, - searchMatchIndices: List, - typingEffectEnabled: Boolean, - typingEffectDelayMs: Int, - maxToolCallsPerTurn: Int? = null, - isLoading: Boolean, - isLoadingOlder: Boolean, - isDark: Boolean, - listState: androidx.compose.foundation.lazy.LazyListState, - lastAnimatedMessageId: String?, - onLastAnimatedMessageIdChange: (String?) -> Unit, - viewModel: ChatViewModel, - clarifyRequest: ClarifyUi? = null, - onRespondClarify: ((String) -> Unit)? = null, - onDismissClarify: (() -> Unit)? = null, - onSaveAttachment: (com.m57.hermescontrol.data.model.Attachment) -> Unit = {}, - savingAttachmentPath: String? = null, - onImageClick: (ImageViewerModel) -> Unit = {}, -) { - if (messages.isEmpty() && !isLoading) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center, - ) { - EmptyState( - title = stringResource(R.string.chat_empty_title), - subtitle = stringResource(R.string.chat_empty_subtitle), - ) - } - } else { - val toolMilestones = toolCallMilestones(messages) - LazyColumn( - state = listState, - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(vertical = 8.dp), - ) { - if (isLoadingOlder) { - item(key = "loading-older") { - Box( - modifier = Modifier.fillMaxWidth().padding(12.dp), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator(modifier = Modifier.size(24.dp)) - } - } - } - itemsIndexed( - items = messages, - key = { _, message -> message.id }, - ) { index, message -> - val isCurrentMatch = - isSearchActive && - currentSearchMatchIndex >= 0 && - currentSearchMatchIndex < searchMatchIndices.size && - searchMatchIndices[currentSearchMatchIndex] == index - - val isLastMessage = index == messages.lastIndex - val isAssistant = message.role == MessageRole.ASSISTANT - - Column { - if (typingEffectEnabled && isLastMessage && isAssistant && message.isStreaming && - lastAnimatedMessageId != message.id - ) { - StreamingBubbleWithTypingEffect( - streaming = message, - typingDelayMs = typingEffectDelayMs, - isDark = isDark, - onAnimationComplete = { - onLastAnimatedMessageIdChange(message.id) - }, - ) - } else { - ChatBubble( - message = message, - isDarkTheme = isDark, - searchQuery = if (isSearchActive) searchQuery else "", - isCurrentMatch = isCurrentMatch, - onRespondApproval = viewModel::respondToApproval, - onOpenAttachment = viewModel::openAttachment, - onSaveAttachment = onSaveAttachment, - savingAttachmentPath = savingAttachmentPath, - canSaveAttachment = savingAttachmentPath == null, - onImageClick = onImageClick, - ) - } - // Subtle beat counter after every 5th tool call (issue #767). - toolMilestones[index]?.let { count -> - ToolCallDivider(count = count, maxPerTurn = maxToolCallsPerTurn) - } - } - } - - // Streaming message - streamingMessage?.let { streaming -> - item(key = "streaming-${streaming.id}") { - if (typingEffectEnabled && streaming.isStreaming) { - StreamingBubbleWithTypingEffect( - streaming = streaming, - typingDelayMs = typingEffectDelayMs, - isDark = isDark, - ) - } else { - ChatBubble( - message = streaming, - isDarkTheme = isDark, - searchQuery = "", - isCurrentMatch = false, - onOpenAttachment = viewModel::openAttachment, - onSaveAttachment = onSaveAttachment, - savingAttachmentPath = savingAttachmentPath, - canSaveAttachment = savingAttachmentPath == null, - onImageClick = onImageClick, - ) - } - } - } - - // Clarify bubble — rendered at the very bottom - if (clarifyRequest != null) { - item(key = "clarify_bubble") { - ClarifyBubble( - text = clarifyRequest.text, - options = clarifyRequest.options, - onOptionSelected = { option -> onRespondClarify?.invoke(option) }, - onDismiss = { onDismissClarify?.invoke() }, - ) - } - } - } - } -} diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/components/ChatStreaming.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/components/ChatStreaming.kt deleted file mode 100644 index c5002918..00000000 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/components/ChatStreaming.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.m57.hermescontrol.ui.chat.components - -import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue -import com.m57.hermescontrol.ui.chat.ChatBubble -import com.m57.hermescontrol.ui.chat.ChatMessage -import kotlinx.coroutines.delay - -/** - * Wraps a streaming [ChatBubble] with a word-by-word typing reveal effect. - * Shows words one at a time at [typingDelayMs] intervals while the message is - * still streaming. When streaming completes the full text is shown immediately. - * The underlying [ChatMessage.content] in state is never modified — this is a - * display-only transformation. - */ -@Composable -fun StreamingBubbleWithTypingEffect( - streaming: ChatMessage, - typingDelayMs: Int, - isDark: Boolean, - onAnimationComplete: () -> Unit = {}, -) { - var visibleWordCount by remember { mutableIntStateOf(0) } - var caretVisible by remember { mutableStateOf(true) } - val currentContent = rememberUpdatedState(streaming.content) - val currentIsStreaming = rememberUpdatedState(streaming.isStreaming) - val currentDelayMs = rememberUpdatedState(typingDelayMs) - - // Blinking typing caret while the message is still streaming. - LaunchedEffect(Unit) { - while (currentIsStreaming.value) { - delay(400) - caretVisible = !caretVisible - } - caretVisible = false - } - - // Timer that ticks at the configured delay, incrementing the visible word - // count each tick. Stops ticking when streaming ends, then shows all words. - // Optimized: split is only called when waiting for new content, not per tick. - LaunchedEffect(Unit) { - var wordCount = 0 - while (true) { - if (visibleWordCount < wordCount) { - delay(currentDelayMs.value.toLong()) - visibleWordCount++ - } else { - if (!currentIsStreaming.value) { - onAnimationComplete() - break - } - // Only split when we need to check for new content arriving - val words = currentContent.value.split(" ") - wordCount = words.size - if (visibleWordCount < wordCount) continue - delay(100) // was 10 — reduced from 100Hz to 10Hz - } - } - visibleWordCount = Int.MAX_VALUE - } - - // Derive display text from the latest full content at each recomposition - val words = streaming.content.split(" ") - val visibleCount = - if (visibleWordCount >= Int.MAX_VALUE / 2) { - words.size - } else { - visibleWordCount.coerceIn(0, words.size) - } - val displayText = - words.take(visibleCount.coerceAtLeast(1)).joinToString(" ") + - if (caretVisible && currentIsStreaming.value) "▍" else "" - - ChatBubble( - message = streaming.copy(content = displayText), - isDarkTheme = isDark, - searchQuery = "", - isCurrentMatch = false, - ) -} diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/fullbleed/FullBleedChatList.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/fullbleed/FullBleedChatList.kt index ae058c37..a2d53402 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/fullbleed/FullBleedChatList.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/fullbleed/FullBleedChatList.kt @@ -28,12 +28,11 @@ import com.m57.hermescontrol.ui.chat.toolCallMilestones import com.m57.hermescontrol.ui.common.EmptyState /** - * The chat message list for FULL-BLEED style (issue #866). - * - * Parallel renderer to [com.m57.hermescontrol.ui.chat.components.ChatMessageList]: - * user messages keep their bubble (the universal anchor), agent turns render - * full-bleed with a turn header, and tool rows / system events render as - * distinct compact cards. Spacing contract: + * The chat message list for FULL-BLEED style (issue #866) — the single chat + * surface since the bubble renderer was removed. User messages keep their + * bubble (the universal anchor), agent turns render full-bleed with a turn + * header, and tool rows / system events render as distinct compact cards. + * Spacing contract: * - intra-turn: entries separated by 6.dp (Column padding on agent turn items) * - inter-turn: 12.dp bottom padding after each turn's last item * @@ -112,7 +111,6 @@ fun FullBleedChatList( Column(modifier = Modifier.padding(bottom = 12.dp)) { renderChatBubble( message = userMessage, - isDark = isDark, searchQuery = searchQuery, isCurrentMatch = isCurrentMatchFor( @@ -259,7 +257,6 @@ fun FullBleedChatList( @Composable private fun renderChatBubble( message: ChatMessage, - isDark: Boolean, searchQuery: String, isCurrentMatch: Boolean, onOpenAttachment: (com.m57.hermescontrol.data.model.Attachment) -> Unit, @@ -269,7 +266,6 @@ private fun renderChatBubble( ) { ChatBubble( message = message, - isDarkTheme = isDark, searchQuery = searchQuery, isCurrentMatch = isCurrentMatch, onOpenAttachment = onOpenAttachment, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/fullbleed/FullBleedStreaming.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/fullbleed/FullBleedStreaming.kt index 64ae2680..a5db9d55 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/fullbleed/FullBleedStreaming.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/fullbleed/FullBleedStreaming.kt @@ -13,8 +13,7 @@ import kotlinx.coroutines.delay /** * Wraps a streaming [FullBleedAgentMessage] with a word-by-word typing reveal - * effect (full-bleed counterpart to - * [com.m57.hermescontrol.ui.chat.components.StreamingBubbleWithTypingEffect]). + * effect (full-bleed counterpart to the removed bubble streaming wrapper). * * Shows words one at a time at [typingDelayMs] intervals while the message is * still streaming. When streaming completes the full text is shown diff --git a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsSubPages.kt b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsSubPages.kt index 99b6df6a..6b42c352 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsSubPages.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsSubPages.kt @@ -163,8 +163,6 @@ internal fun SettingsChatPage( onTypingEffectEnabledChange = viewModel::onTypingEffectEnabledChange, typingEffectDelayMs = state.typingEffectDelayMs, onTypingEffectDelayMsChange = viewModel::onTypingEffectDelayMsChange, - chatStyle = state.chatStyle, - onChatStyleChange = viewModel::onChatStyleChange, ) } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt index 8e5804fb..14b5eeda 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/settings/SettingsViewModel.kt @@ -11,7 +11,6 @@ import com.m57.hermescontrol.data.remote.NetworkResult import com.m57.hermescontrol.data.remote.ServerEndpoint import com.m57.hermescontrol.data.remote.safeApiCall import com.m57.hermescontrol.data.ws.HermesWsClient -import com.m57.hermescontrol.theme.ChatStyle import com.m57.hermescontrol.theme.ThemePreference import com.m57.hermescontrol.theme.ThemePreset import kotlinx.coroutines.CoroutineDispatcher @@ -36,7 +35,6 @@ data class SettingsUiState( val isSaved: Boolean = false, val typingEffectEnabled: Boolean = false, val typingEffectDelayMs: Int = 30, - val chatStyle: ChatStyle = ChatStyle.FULL_BLEED, val profiles: List = emptyList(), val selectedProfileId: String? = null, val renameProfileName: String = "", @@ -77,7 +75,6 @@ class SettingsViewModel( val themePreset = AuthManager.getThemePreset() val typingEffectEnabled = AuthManager.isTypingEffectEnabled() val typingEffectDelayMs = AuthManager.getTypingEffectDelayMs() - val chatStyle = AuthManager.getChatStyle() val profiles = AuthManager.getConnectionProfiles() val appLanguage = AuthManager.getAppLanguage() val renameProfileName = @@ -97,7 +94,6 @@ class SettingsViewModel( themePreset = themePreset, typingEffectEnabled = typingEffectEnabled, typingEffectDelayMs = typingEffectDelayMs, - chatStyle = chatStyle, profiles = profiles, selectedProfileId = selectedId, renameProfileName = renameProfileName, @@ -298,11 +294,6 @@ class SettingsViewModel( AuthManager.setTypingEffectDelayMs(delayMs) } - fun onChatStyleChange(style: ChatStyle) { - _uiState.update { it.copy(chatStyle = style, isSaved = false) } - AuthManager.setChatStyle(style) - } - /** Clear all auth credentials — logs out and returns to landing screen. */ fun logout() { AuthManager.setToken(null) @@ -329,7 +320,6 @@ class SettingsViewModel( AuthManager.setThemePreset(state.themePreset) AuthManager.setTypingEffectEnabled(state.typingEffectEnabled) AuthManager.setTypingEffectDelayMs(state.typingEffectDelayMs) - AuthManager.setChatStyle(state.chatStyle) ApiClient.rebuild() viewModelScope.launch(ioDispatcher) { diff --git a/app/src/main/java/com/m57/hermescontrol/ui/settings/components/AppearanceSection.kt b/app/src/main/java/com/m57/hermescontrol/ui/settings/components/AppearanceSection.kt index 8df6d38f..fa2c99ea 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/settings/components/AppearanceSection.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/settings/components/AppearanceSection.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.m57.hermescontrol.R -import com.m57.hermescontrol.theme.ChatStyle import com.m57.hermescontrol.theme.ThemePreference import com.m57.hermescontrol.theme.ThemePreset import com.m57.hermescontrol.ui.settings.SectionCard @@ -235,46 +234,8 @@ internal fun ChatSection( onTypingEffectEnabledChange: (Boolean) -> Unit, typingEffectDelayMs: Int, onTypingEffectDelayMsChange: (Int) -> Unit, - chatStyle: ChatStyle, - onChatStyleChange: (ChatStyle) -> Unit, ) { SectionCard { - // Chat rendering style (issue #866): bubble renderer (default) or the - // parallel full-bleed renderer. Kept at the top of the section — it's - // the primary chat-behavior choice. - Text( - text = stringResource(R.string.settings_item_chat_style), - style = MaterialTheme.typography.bodyLarge, - ) - Text( - text = stringResource(R.string.settings_desc_chat_style), - style = - MaterialTheme.typography.bodySmall.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant, - ), - ) - Spacer(modifier = Modifier.height(8.dp)) - SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) { - ChatStyle.entries.forEachIndexed { index, style -> - SegmentedButton( - selected = chatStyle == style, - onClick = { onChatStyleChange(style) }, - shape = - SegmentedButtonDefaults.itemShape( - index = index, - count = ChatStyle.entries.size, - ), - ) { - Text( - when (style) { - ChatStyle.BUBBLES -> stringResource(R.string.chat_style_bubbles) - ChatStyle.FULL_BLEED -> stringResource(R.string.chat_style_full_bleed) - }, - ) - } - } - } - Spacer(modifier = Modifier.height(16.dp)) Row( diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index eee4dd73..7fde9d15 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -143,10 +143,6 @@ 채팅 타이핑 효과 새 메시지를 단어 단위로 애니메이션 - 채팅 스타일 - 에이전트 응답 표시 방식 - 버블 - 전체 폭 어시스턴트 지연: %1$d ms 10 ms diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2bd91dc4..b0889bee 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -163,10 +163,6 @@ Chat Typing Effect Animate new messages word by word - Chat style - How agent replies are rendered - Bubbles - Full-bleed Assistant Delay: %1$d ms 10 ms diff --git a/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatUpdateCommandTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatUpdateCommandTest.kt index 2fe8049d..d8ceaff4 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatUpdateCommandTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatUpdateCommandTest.kt @@ -11,7 +11,6 @@ import com.m57.hermescontrol.data.ws.ConnectionStatus import com.m57.hermescontrol.data.ws.HermesWsClient import com.m57.hermescontrol.data.ws.WsEvent import com.m57.hermescontrol.data.ws.WsMethods -import com.m57.hermescontrol.theme.ChatStyle import com.m57.hermescontrol.ui.chat.fakes.FakeChatPersistenceRepository import com.m57.hermescontrol.ui.chat.fakes.FakeSlashUsageStore import io.mockk.coEvery @@ -90,7 +89,6 @@ class ChatUpdateCommandTest { every { AuthManager.getToken() } returns "test-token" every { AuthManager.isTypingEffectEnabled() } returns true every { AuthManager.getTypingEffectDelayMs() } returns 30 - every { AuthManager.getChatStyle() } returns ChatStyle.BUBBLES every { AuthManager.isAutoReconnect() } returns false every { HermesWsClient.events } returns mockEventsFlow every { HermesWsClient.connectionStatus } returns mockConnectionStatus diff --git a/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt index 766854ad..65807b67 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/chat/ChatViewModelTest.kt @@ -21,7 +21,6 @@ import com.m57.hermescontrol.data.ws.HermesWsClient import com.m57.hermescontrol.data.ws.JsonRpcError import com.m57.hermescontrol.data.ws.WsEvent import com.m57.hermescontrol.data.ws.WsMethods -import com.m57.hermescontrol.theme.ChatStyle import com.m57.hermescontrol.ui.chat.fakes.FakeChatPersistenceRepository import com.m57.hermescontrol.ui.chat.fakes.FakeSlashUsageStore import io.mockk.* @@ -129,7 +128,6 @@ class ChatViewModelTest { every { ProfileSwitchCoordinator.switched } returns mockSwitchFlow every { AuthManager.isTypingEffectEnabled() } returns true every { AuthManager.getTypingEffectDelayMs() } returns 30 - every { AuthManager.getChatStyle() } returns ChatStyle.BUBBLES every { AuthManager.isAutoReconnect() } returns false every { HermesWsClient.events } returns mockEventsFlow every { HermesWsClient.connectionStatus } returns mockConnectionStatus @@ -2718,7 +2716,6 @@ class ChatViewModelTest { // re-reads AuthManager live (the real regression scenario). every { AuthManager.isTypingEffectEnabled() } returns false every { AuthManager.getTypingEffectDelayMs() } returns 50 - every { AuthManager.getChatStyle() } returns ChatStyle.BUBBLES viewModel.refreshSettings() advanceUntilIdle() diff --git a/app/src/test/java/com/m57/hermescontrol/ui/chat/SlashCommandDispatchRpcTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/chat/SlashCommandDispatchRpcTest.kt index 46f7056c..7d4b367f 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/chat/SlashCommandDispatchRpcTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/chat/SlashCommandDispatchRpcTest.kt @@ -9,7 +9,6 @@ import com.m57.hermescontrol.data.ws.ConnectionStatus import com.m57.hermescontrol.data.ws.HermesWsClient import com.m57.hermescontrol.data.ws.WsEvent import com.m57.hermescontrol.data.ws.WsMethods -import com.m57.hermescontrol.theme.ChatStyle import com.m57.hermescontrol.ui.chat.fakes.FakeChatPersistenceRepository import com.m57.hermescontrol.ui.chat.fakes.FakeSlashUsageStore import io.mockk.every @@ -94,7 +93,6 @@ class SlashCommandDispatchRpcTest { every { AuthManager.getToken() } returns "test-token" every { AuthManager.isTypingEffectEnabled() } returns true every { AuthManager.getTypingEffectDelayMs() } returns 30 - every { AuthManager.getChatStyle() } returns ChatStyle.BUBBLES every { AuthManager.isAutoReconnect() } returns false every { HermesWsClient.events } returns mockEventsFlow every { HermesWsClient.connectionStatus } returns mockConnectionStatus diff --git a/app/src/test/java/com/m57/hermescontrol/ui/settings/SettingsViewModelTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/settings/SettingsViewModelTest.kt index f6b3a4dd..9c02a32a 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/settings/SettingsViewModelTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/settings/SettingsViewModelTest.kt @@ -3,7 +3,6 @@ package com.m57.hermescontrol.ui.settings import com.m57.hermescontrol.data.config.ConnectionProfile import com.m57.hermescontrol.data.local.AuthManager import com.m57.hermescontrol.data.remote.ApiClient -import com.m57.hermescontrol.theme.ChatStyle import com.m57.hermescontrol.theme.ThemePreference import com.m57.hermescontrol.theme.ThemePreset import io.mockk.every @@ -66,7 +65,6 @@ class SettingsViewModelTest { every { AuthManager.getThemePreset() } returns ThemePreset.DEFAULT every { AuthManager.isTypingEffectEnabled() } returns true every { AuthManager.getTypingEffectDelayMs() } returns 30 - every { AuthManager.getChatStyle() } returns ChatStyle.BUBBLES every { AuthManager.getConnectionProfiles() } returns emptyList() every { AuthManager.getSelectedProfileId() } answers { storedSelectedProfileId } every { AuthManager.baseUrl() } returns "http://127.0.0.1:9119/" @@ -121,7 +119,6 @@ class SettingsViewModelTest { every { AuthManager.getThemePreset() } returns ThemePreset.CATPPUCCIN every { AuthManager.isTypingEffectEnabled() } returns false every { AuthManager.getTypingEffectDelayMs() } returns 15 - every { AuthManager.getChatStyle() } returns ChatStyle.BUBBLES every { AuthManager.getConnectionProfiles() } returns testProfiles every { AuthManager.getAppLanguage() } returns "fr"