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