diff --git a/app/src/main/java/com/m57/hermescontrol/theme/Color.kt b/app/src/main/java/com/m57/hermescontrol/theme/Color.kt index 1de1348a..2266fede 100644 --- a/app/src/main/java/com/m57/hermescontrol/theme/Color.kt +++ b/app/src/main/java/com/m57/hermescontrol/theme/Color.kt @@ -68,6 +68,8 @@ val StatusYellowContainer = Color(0xFF3D2F0F) val StatusBlue = Color(0xFF4DA8FF) val StatusBlueContainer = Color(0xFF0F2A3D) val StatusGrey = Color(0xFF9E9E9E) +val StatusGreyDark = Color(0xFF1A1A24) +val StatusGreyLight = Color(0xFFF5F5F5) // ── Chat-specific colours (always brand-defined for consistency) ─────── diff --git a/app/src/main/java/com/m57/hermescontrol/theme/HermesStatusColors.kt b/app/src/main/java/com/m57/hermescontrol/theme/HermesStatusColors.kt index 306c09bb..f36ecd19 100644 --- a/app/src/main/java/com/m57/hermescontrol/theme/HermesStatusColors.kt +++ b/app/src/main/java/com/m57/hermescontrol/theme/HermesStatusColors.kt @@ -2,6 +2,7 @@ package com.m57.hermescontrol.theme import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance /** * Semantic status colour set — always brand-defined (never dynamic). @@ -16,25 +17,58 @@ import androidx.compose.ui.graphics.Color data class HermesStatusColors( val success: Color, val successContainer: Color, + val onSuccess: Color, val warning: Color, val warningContainer: Color, + val onWarning: Color, val error: Color, val errorContainer: Color, + val onError: Color, val info: Color, val infoContainer: Color, + val onInfo: Color, val neutral: Color = StatusGrey, ) +/** + * Theme-aware colors for markdown/search highlighting. Built per-composition from + * [LocalHermesStatusColors] so `==mark==` and search terms follow the active preset + * instead of a fixed yellow. + */ +data class SearchHighlightColors( + val markupBackground: Color, + val searchBackground: Color, + val searchForeground: Color, + val currentSearchBackground: Color, + val currentSearchForeground: Color, +) + +/** Build the highlight palette for the current theme preset. */ +internal fun onColorFor(color: Color): Color = if (color.luminance() > 0.5f) StatusGreyDark else StatusGreyLight + +internal fun searchHighlightColors(statusColors: HermesStatusColors): SearchHighlightColors = + SearchHighlightColors( + markupBackground = statusColors.warning.copy(alpha = 0.3f), + searchBackground = statusColors.warningContainer, + searchForeground = onColorFor(statusColors.warningContainer), + currentSearchBackground = statusColors.warning, + currentSearchForeground = statusColors.onWarning, + ) + val LocalHermesStatusColors = staticCompositionLocalOf { HermesStatusColors( success = StatusGreen, successContainer = StatusGreenContainer, + onSuccess = StatusGreyLight, warning = StatusYellow, warningContainer = StatusYellowContainer, + onWarning = StatusGreyLight, error = StatusRed, errorContainer = StatusRedContainer, + onError = StatusGreyLight, info = StatusBlue, infoContainer = StatusBlueContainer, + onInfo = StatusGreyLight, ) } diff --git a/app/src/main/java/com/m57/hermescontrol/theme/Theme.kt b/app/src/main/java/com/m57/hermescontrol/theme/Theme.kt index c7afb6eb..cb256059 100644 --- a/app/src/main/java/com/m57/hermescontrol/theme/Theme.kt +++ b/app/src/main/java/com/m57/hermescontrol/theme/Theme.kt @@ -339,12 +339,16 @@ fun HermesControlTheme( HermesStatusColors( success = Color(0xFFB8BB26), successContainer = Color(0xFF32302F), + onSuccess = Color(0xFF282828), warning = Color(0xFFFABD2F), warningContainer = Color(0xFF3C3836), + onWarning = Color(0xFF282828), error = Color(0xFFFB4934), errorContainer = Color(0xFF282828), + onError = Color(0xFF282828), info = Color(0xFF83A598), infoContainer = Color(0xFF3C3836), + onInfo = Color(0xFF282828), ) } @@ -352,12 +356,16 @@ fun HermesControlTheme( HermesStatusColors( success = Color(0xFF98971A), successContainer = Color(0xFFEBDBB2), + onSuccess = Color(0xFFEBDBB2), warning = Color(0xFFD79921), warningContainer = Color(0xFFEBDBB2), + onWarning = Color(0xFFEBDBB2), error = Color(0xFFCC241D), errorContainer = Color(0xFFEBDBB2), + onError = Color(0xFFEBDBB2), info = Color(0xFF458588), infoContainer = Color(0xFFEBDBB2), + onInfo = Color(0xFFEBDBB2), ) } @@ -365,12 +373,16 @@ fun HermesControlTheme( HermesStatusColors( success = Color(0xFFA6E3A1), successContainer = Color(0xFF313244), + onSuccess = Color(0xFF11111B), warning = Color(0xFFF9E2AF), warningContainer = Color(0xFF313244), + onWarning = Color(0xFF11111B), error = Color(0xFFF38BA8), errorContainer = Color(0xFF313244), + onError = Color(0xFF11111B), info = Color(0xFF89B4FA), infoContainer = Color(0xFF313244), + onInfo = Color(0xFF11111B), ) } @@ -378,12 +390,16 @@ fun HermesControlTheme( HermesStatusColors( success = Color(0xFF40A02B), successContainer = Color(0xFFE6E9EF), + onSuccess = Color(0xFFE6E9EF), warning = Color(0xFFDF8E1D), warningContainer = Color(0xFFE6E9EF), + onWarning = Color(0xFFE6E9EF), error = Color(0xFFD20F39), errorContainer = Color(0xFFE6E9EF), + onError = Color(0xFFE6E9EF), info = Color(0xFF1E66F5), infoContainer = Color(0xFFE6E9EF), + onInfo = Color(0xFFE6E9EF), ) } @@ -391,12 +407,16 @@ fun HermesControlTheme( HermesStatusColors( success = Color(0xFF6FEA8B), successContainer = Color(0xFF0A3A14), + onSuccess = Color(0xFF0F0B1A), warning = Color(0xFFFFB347), warningContainer = Color(0xFF3A2000), + onWarning = Color(0xFF0F0B1A), error = Color(0xFFFF3D68), errorContainer = Color(0xFF3A0A14), + onError = Color(0xFF0F0B1A), info = Color(0xFF60F0FF), infoContainer = Color(0xFF003B40), + onInfo = Color(0xFF0F0B1A), ) } @@ -404,12 +424,16 @@ fun HermesControlTheme( HermesStatusColors( success = Color(0xFF1B873A), successContainer = Color(0xFFD7F5E0), + onSuccess = Color(0xFFFDF8FF), warning = Color(0xFFB87800), warningContainer = Color(0xFFFFEAB3), + onWarning = Color(0xFFFDF8FF), error = Color(0xFFB3261E), errorContainer = Color(0xFFF9DEDC), + onError = Color(0xFFFDF8FF), info = Color(0xFF2E6FBD), infoContainer = Color(0xFFD4E7FF), + onInfo = Color(0xFFFDF8FF), ) } @@ -417,12 +441,16 @@ fun HermesControlTheme( HermesStatusColors( success = StatusGreen, successContainer = StatusGreenContainer, + onSuccess = StatusGreyLight, warning = StatusYellow, warningContainer = StatusYellowContainer, + onWarning = StatusGreyLight, error = StatusRed, errorContainer = StatusRedContainer, + onError = StatusGreyLight, info = StatusBlue, infoContainer = StatusBlueContainer, + onInfo = StatusGreyLight, ) } @@ -430,12 +458,16 @@ fun HermesControlTheme( HermesStatusColors( success = Color(0xFF1B873A), successContainer = Color(0xFFD7F5E0), + onSuccess = Color(0xFFFDF8FF), warning = HermesAmberDark, warningContainer = Color(0xFFFFEAB3), + onWarning = Color(0xFFFDF8FF), error = Color(0xFFB3261E), errorContainer = Color(0xFFF9DEDC), + onError = Color(0xFFFDF8FF), info = Color(0xFF2E6FBD), infoContainer = Color(0xFFD4E7FF), + onInfo = Color(0xFFFDF8FF), ) } } 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 ee5d13c9..09f0ae9f 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 @@ -84,13 +84,15 @@ import com.m57.hermescontrol.data.model.Attachment import com.m57.hermescontrol.data.remote.OkHttpProvider import com.m57.hermescontrol.theme.AssistantBubble import com.m57.hermescontrol.theme.AssistantBubbleLight +import com.m57.hermescontrol.theme.DarkOnSurface +import com.m57.hermescontrol.theme.HermesStatusColors +import com.m57.hermescontrol.theme.LightOnSurface import com.m57.hermescontrol.theme.LocalHermesStatusColors -import com.m57.hermescontrol.theme.StatusGreen -import com.m57.hermescontrol.theme.StatusRed import com.m57.hermescontrol.theme.SystemMessageColor import com.m57.hermescontrol.theme.ToolChipColor import com.m57.hermescontrol.theme.ToolChipColorLight import com.m57.hermescontrol.theme.UserBubble +import com.m57.hermescontrol.theme.onColorFor import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.json.JsonArray @@ -176,10 +178,17 @@ private fun UserBubble( } } + val statusColors = LocalHermesStatusColors.current + val highlightedText = - remember(message.content, searchQuery, isCurrentMatch) { + remember(message.content, searchQuery, isCurrentMatch, statusColors) { if (searchQuery.isNotBlank()) { - buildHighlightedString(message.content, searchQuery, isCurrentMatch) + buildHighlightedString( + message.content, + searchQuery, + isCurrentMatch, + statusColors, + ) } else { AnnotatedString(message.content) } @@ -207,13 +216,13 @@ private fun UserBubble( if (MaterialTheme.colorScheme.onPrimary.luminance() < 0.5f) { MaterialTheme.colorScheme.onPrimary } else { - Color(0xFF1A1A24) + LightOnSurface } } else { if (MaterialTheme.colorScheme.onPrimary.luminance() > 0.5f) { MaterialTheme.colorScheme.onPrimary } else { - Color.White + DarkOnSurface } } Box { @@ -324,13 +333,13 @@ private fun AssistantBubble( if (MaterialTheme.colorScheme.onSurface.luminance() < 0.5f) { MaterialTheme.colorScheme.onSurface } else { - Color(0xFF1A1A24) + LightOnSurface } } else { if (MaterialTheme.colorScheme.onSurface.luminance() > 0.5f) { MaterialTheme.colorScheme.onSurface } else { - Color(0xFFE8E6EE) + DarkOnSurface } } val clipboard = LocalClipboard.current @@ -1719,6 +1728,7 @@ fun parseToolOutput( private fun ExpandedToolContent( parsed: ParsedToolData, contentColor: Color, + statusColors: HermesStatusColors, ) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { // Summary line at top of expanded view @@ -1753,7 +1763,7 @@ private fun ExpandedToolContent( text = stringResource(R.string.chat_tool_stderr, it), style = MaterialTheme.typography.bodySmall.copy( - color = StatusRed.copy(alpha = 0.9f), + color = statusColors.error.copy(alpha = 0.9f), fontFamily = FontFamily.Monospace, fontSize = 11.sp, ), @@ -1764,7 +1774,7 @@ private fun ExpandedToolContent( text = stringResource(R.string.chat_tool_execution_error, it), style = MaterialTheme.typography.bodySmall.copy( - color = StatusRed, + color = statusColors.error, fontWeight = FontWeight.Bold, fontFamily = FontFamily.Monospace, fontSize = 11.sp, @@ -1777,7 +1787,7 @@ private fun ExpandedToolContent( text = stringResource(R.string.chat_tool_exit_code, code), style = MaterialTheme.typography.labelSmall.copy( - color = StatusRed, + color = statusColors.error, fontWeight = FontWeight.Medium, ), ) @@ -1854,6 +1864,7 @@ private fun ToolBubble( var showRawJson by remember { mutableStateOf(false) } val chipColor = if (isDarkTheme) ToolChipColor else ToolChipColorLight val contentColor = if (isDarkTheme) Color.White else Color.Black + val statusColors = LocalHermesStatusColors.current val parsed = remember(message.content, message.toolName, message.toolStatus) { @@ -1892,7 +1903,7 @@ private fun ToolBubble( .padding(horizontal = 10.dp, vertical = 6.dp), ) { // ── Header row: icon + tool name ── - HeaderRow(message, config, contentColor) + HeaderRow(message, config, contentColor, statusColors) // ── Tool progress preview (tool.progress) ── if (message.toolStatus == ToolStatus.RUNNING && !message.progressPreview.isNullOrEmpty()) { @@ -1978,7 +1989,7 @@ private fun ToolBubble( // Clean structured expanded view Box { SelectionContainer { - ExpandedToolContent(parsed, contentColor) + ExpandedToolContent(parsed, contentColor, statusColors) } CopyButton( visible = showCopyButton, @@ -2048,6 +2059,7 @@ private fun HeaderRow( message: ChatMessage, config: ToolDisplayConfig, contentColor: Color, + statusColors: HermesStatusColors, ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -2069,8 +2081,8 @@ private fun HeaderRow( } val tint = when (message.toolStatus) { - ToolStatus.COMPLETED -> StatusGreen - ToolStatus.FAILED -> StatusRed + ToolStatus.COMPLETED -> statusColors.success + ToolStatus.FAILED -> statusColors.error else -> contentColor.copy(alpha = 0.6f) } Icon( @@ -2200,8 +2212,14 @@ private fun buildHighlightedString( text: String, query: String, isCurrentMatch: Boolean = false, + statusColors: HermesStatusColors, ): AnnotatedString { - val highlightColor = if (isCurrentMatch) Color(0xFFF57C00) else Color(0xFFFFF176).copy(alpha = 0.9f) + val (highlightColor, highlightText) = + if (isCurrentMatch) { + statusColors.warning to statusColors.onWarning + } else { + statusColors.warningContainer to onColorFor(statusColors.warningContainer) + } return buildAnnotatedString { var i = 0 while (i < text.length) { @@ -2216,7 +2234,7 @@ private fun buildHighlightedString( append(text.substring(i, matchEnd)) } // Append the match highlighted - withStyle(SpanStyle(background = highlightColor, color = Color(0xFF1A1A24))) { + withStyle(SpanStyle(background = highlightColor, color = highlightText)) { append(text.substring(matchEnd, matchEnd + query.length)) } i = matchEnd + query.length 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 42dac059..03435407 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 @@ -131,7 +131,6 @@ import com.m57.hermescontrol.data.ws.ConnectionStatus import com.m57.hermescontrol.data.ws.HermesWsClient import com.m57.hermescontrol.notification.NotificationHelper import com.m57.hermescontrol.theme.LocalHermesStatusColors -import com.m57.hermescontrol.theme.StatusRed import com.m57.hermescontrol.ui.common.CredentialWarningBanner import com.m57.hermescontrol.ui.common.EmptyState import com.m57.hermescontrol.ui.common.HermesScaffold @@ -358,7 +357,7 @@ fun ChatScreen( Modifier .size(10.dp) .clip(CircleShape) - .background(StatusRed), + .background(LocalHermesStatusColors.current.error), ) } } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/MarkdownText.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/MarkdownText.kt index 940d4b19..fc8bb53f 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/MarkdownText.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/MarkdownText.kt @@ -57,11 +57,13 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.m57.hermescontrol.R +import com.m57.hermescontrol.theme.LocalHermesStatusColors +import com.m57.hermescontrol.theme.SearchHighlightColors +import com.m57.hermescontrol.theme.searchHighlightColors import kotlinx.coroutines.delay import kotlinx.coroutines.launch -private val URL_PATTERN = Regex("""https?://[^\s)>\[\]"‘’]+""") -private val HIGHLIGHT_BG = Color(0xFFFFFF00).copy(alpha = 0.3f) +private val URL_PATTERN = Regex("""https?://[^\s)>\[\]"'‘’]+""") private val TABLE_COL_WIDTH = 160.dp private val FN_DEF_RE = Regex("""^\[\^([^\]]+)\]:\s*(.*)$""") @@ -84,6 +86,8 @@ fun MarkdownText( isCurrentMatch: Boolean = false, modifier: Modifier = Modifier, ) { + val statusColors = LocalHermesStatusColors.current + val highlights = searchHighlightColors(statusColors) if (isStreaming) { Text( text = text, @@ -121,7 +125,7 @@ fun MarkdownText( Text( text = remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) { - parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor) + parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor, highlights) }, color = textColor, style = @@ -145,7 +149,14 @@ fun MarkdownText( Text( text = remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) { - parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor) + parseInline( + block.text, + textColor, + searchQuery, + isCurrentMatch, + linkColor, + highlights, + ) }, color = textColor, style = MaterialTheme.typography.bodyMedium, @@ -180,7 +191,14 @@ fun MarkdownText( Text( text = remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) { - parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor) + parseInline( + block.text, + textColor, + searchQuery, + isCurrentMatch, + linkColor, + highlights, + ) }, color = textColor, style = MaterialTheme.typography.bodyMedium, @@ -203,7 +221,14 @@ fun MarkdownText( Text( text = remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) { - parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor) + parseInline( + block.text, + textColor, + searchQuery, + isCurrentMatch, + linkColor, + highlights, + ) }, color = textColor, style = MaterialTheme.typography.bodyMedium, @@ -229,7 +254,14 @@ fun MarkdownText( Text( text = remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) { - parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor) + parseInline( + block.text, + textColor, + searchQuery, + isCurrentMatch, + linkColor, + highlights, + ) }, color = textColor, style = MaterialTheme.typography.bodyMedium.copy(fontStyle = FontStyle.Italic), @@ -283,7 +315,14 @@ fun MarkdownText( Text( text = remember(note.text, searchQuery, isCurrentMatch, textColor, linkColor) { - parseInline(note.text, textColor, searchQuery, isCurrentMatch, linkColor) + parseInline( + note.text, + textColor, + searchQuery, + isCurrentMatch, + linkColor, + highlights, + ) }, color = textColor, style = MaterialTheme.typography.bodySmall, @@ -298,7 +337,7 @@ fun MarkdownText( Text( text = remember(block.text, searchQuery, isCurrentMatch, textColor, linkColor) { - parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor) + parseInline(block.text, textColor, searchQuery, isCurrentMatch, linkColor, highlights) }, color = textColor, style = MaterialTheme.typography.bodyMedium, @@ -663,9 +702,14 @@ internal fun parseInline( searchQuery: String, isCurrentMatch: Boolean, linkColor: Color, + highlights: SearchHighlightColors, ): AnnotatedString { val searchHighlightColor = - if (isCurrentMatch) Color(0xFFF57C00) else Color(0xFFFFF176).copy(alpha = 0.9f) + if (isCurrentMatch) { + highlights.currentSearchBackground to highlights.currentSearchForeground + } else { + highlights.searchBackground to highlights.searchForeground + } return buildAnnotatedString { var i = 0 @@ -732,7 +776,7 @@ internal fun parseInline( src.startsWith("==", i) -> { val end = src.indexOf("==", i + 2) if (end != -1) { - withStyle(SpanStyle(background = HIGHLIGHT_BG)) { + withStyle(SpanStyle(background = highlights.markupBackground)) { append(src.substring(i + 2, end)) } i = end + 2 @@ -881,8 +925,8 @@ internal fun parseInline( src.regionMatches(i, searchQuery, 0, searchQuery.length, ignoreCase = true) -> { withStyle( SpanStyle( - background = searchHighlightColor, - color = Color(0xFF1A1A24), + background = searchHighlightColor.first, + color = searchHighlightColor.second, ), ) { append(src.substring(i, i + searchQuery.length)) diff --git a/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt index 5cb20a03..fb00c01c 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/gateway/GatewayScreen.kt @@ -29,13 +29,13 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.m57.hermescontrol.R +import com.m57.hermescontrol.theme.LocalHermesStatusColors import com.m57.hermescontrol.ui.common.ErrorState import com.m57.hermescontrol.ui.common.HermesScaffold import com.m57.hermescontrol.ui.common.LoadingState @@ -50,6 +50,7 @@ fun GatewayScreen( viewModel: GatewayViewModel = viewModel { GatewayViewModel() }, ) { val state by viewModel.uiState.collectAsStateWithLifecycle() + val statusColors = LocalHermesStatusColors.current LaunchedEffect(Unit) { viewModel.loadStatus() @@ -188,8 +189,8 @@ fun GatewayScreen( enabled = !isRunning && !state.isActionRunning, colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF4CAF50), // Green for start - contentColor = Color.White, + containerColor = statusColors.success, + contentColor = statusColors.onSuccess, ), ) { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) @@ -203,8 +204,8 @@ fun GatewayScreen( enabled = isRunning && !state.isActionRunning, colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFFF44336), // Red for stop - contentColor = Color.White, + containerColor = statusColors.error, + contentColor = statusColors.onError, ), ) { Text(stringResource(R.string.gateway_action_stop)) @@ -270,9 +271,9 @@ fun GatewayScreen( val stateText = pStatus?.state?.uppercase() ?: "UNKNOWN" val badgeColor = when (stateText) { - "RUNNING" -> Color(0xFF4CAF50) - "STOPPED" -> Color(0xFFF44336) - "ERROR" -> Color(0xFFFF9800) + "RUNNING" -> statusColors.success + "STOPPED" -> statusColors.error + "ERROR" -> statusColors.warning else -> MaterialTheme.colorScheme.onSurfaceVariant } Text( diff --git a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt index 262d296c..0ab1f3f6 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/sessions/SessionsScreen.kt @@ -575,7 +575,7 @@ fun SessionsScreen( CircularProgressIndicator( modifier = Modifier.size(18.dp), strokeWidth = 2.dp, - color = Color.White, + color = statusColors.onError, ) } else { Icon( diff --git a/app/src/test/java/com/m57/hermescontrol/ui/chat/MarkdownTextFeatureTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/chat/MarkdownTextFeatureTest.kt index b43b9c03..bfa0ca75 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/chat/MarkdownTextFeatureTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/chat/MarkdownTextFeatureTest.kt @@ -1,11 +1,39 @@ package com.m57.hermescontrol.ui.chat import androidx.compose.ui.graphics.Color +import com.m57.hermescontrol.theme.HermesStatusColors +import com.m57.hermescontrol.theme.StatusBlue +import com.m57.hermescontrol.theme.StatusBlueContainer +import com.m57.hermescontrol.theme.StatusGreen +import com.m57.hermescontrol.theme.StatusGreenContainer +import com.m57.hermescontrol.theme.StatusRed +import com.m57.hermescontrol.theme.StatusRedContainer +import com.m57.hermescontrol.theme.StatusYellow +import com.m57.hermescontrol.theme.StatusYellowContainer +import com.m57.hermescontrol.theme.searchHighlightColors import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test +private val DEFAULT_HIGHLIGHTS = + searchHighlightColors( + HermesStatusColors( + success = StatusGreen, + successContainer = StatusGreenContainer, + onSuccess = Color.White, + warning = StatusYellow, + warningContainer = StatusYellowContainer, + onWarning = Color.White, + error = StatusRed, + errorContainer = StatusRedContainer, + onError = Color.White, + info = StatusBlue, + infoContainer = StatusBlueContainer, + onInfo = Color.White, + ), + ) + /** * Verifies the hand-rolled Markdown parser covers the feature set requested for issue #572 * (math excluded by decision). Each test asserts the block/inline structure actually parses — @@ -47,7 +75,7 @@ class MarkdownTextFeatureTest { // 3. STRIKETHROUGH @Test fun testStrikethrough_parses() { - val an = parseInline("~~gone~~", Color.Black, "", false, Color.Blue) + val an = parseInline("~~gone~~", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS) assertEquals("gone", an.toString()) assertTrue(an.spanStyles.any { it.item.textDecoration != null }) } @@ -55,7 +83,7 @@ class MarkdownTextFeatureTest { // 4. BOLD + ITALIC in same word (***bolditalic***) @Test fun testBoldItalic_combined() { - val an = parseInline("***both***", Color.Black, "", false, Color.Blue) + val an = parseInline("***both***", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS) assertEquals("both", an.toString()) assertTrue(an.spanStyles.isNotEmpty()) } @@ -128,7 +156,7 @@ class MarkdownTextFeatureTest { // 9. HIGHLIGHT @Test fun testHighlight_parses() { - val an = parseInline("==mark==", Color.Black, "", false, Color.Blue) + val an = parseInline("==mark==", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS) assertEquals("mark", an.toString()) assertTrue(an.spanStyles.any { it.item.background != null }) } @@ -136,16 +164,17 @@ class MarkdownTextFeatureTest { // 10. SUPERSCRIPT / SUBSCRIPT @Test fun testSuperscriptAndSubscript_parse() { - val sup = parseInline("x^2^", Color.Black, "", false, Color.Blue) + val sup = parseInline("x^2^", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS) assertTrue(sup.spanStyles.any { it.item.baselineShift != null }) - val sub = parseInline("H~2~O", Color.Black, "", false, Color.Blue) + val sub = parseInline("H~2~O", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS) assertTrue(sub.spanStyles.any { it.item.baselineShift != null }) } // 11. KEYBOARD KEYS @Test fun testKbd_parses() { - val an = parseInline("Press Ctrl+C", Color.Black, "", false, Color.Blue) + val an = + parseInline("Press Ctrl+C", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS) assertTrue(an.toString().contains("Ctrl")) assertTrue(an.toString().contains("C")) assertTrue(an.spanStyles.any { it.item.fontFamily != null }) @@ -161,7 +190,7 @@ class MarkdownTextFeatureTest { // --- regression: streaming gate handled in composable, parser must not split inline code --- @Test fun testInlineCode_preserved() { - val an = parseInline("use `val x = 1` here", Color.Black, "", false, Color.Blue) + val an = parseInline("use `val x = 1` here", Color.Black, "", false, Color.Blue, DEFAULT_HIGHLIGHTS) assertEquals("use val x = 1 here", an.toString()) assertTrue(an.spanStyles.any { it.item.fontFamily != null }) }