Co-authored-by: M57 <m57@hermes-control.app>
This commit is contained in:
1 parent
1870e4bb01
commit
29b630a2ec
9 files changed
+207
-48
No files matched your search
@@ -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) ───────
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -575,7 +575,7 @@ fun SessionsScreen(
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(18.dp),
|
||||
strokeWidth = 2.dp,
|
||||
color = Color.White,
|
||||
color = statusColors.onError,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
|
||||
Reference in new issue
Block a user