fix(chat): tint composer send/stop/mic button with theme primary (#1410)

This commit is contained in:
m57 authored and GitHub committed 2026-09-30 14:33:08 +03:00
1 parent 0e39a71591
commit 3fb99f86ac
3 files changed
+44 -3

No files matched your search

@@ -91,3 +91,6 @@ fun parseProjectColor(raw: String?): Color? {
if (h !in 0f..360f || s !in 0f..1f || l !in 0f..1f) return null
return Color.hsl(h % 360f, s, l)
}
/** Pure white glyph for filled accent controls (composer action button) when it is legible on the preset primary. */
val GlyphWhite = Color(0xFFFFFFFF)
@@ -6,6 +6,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.luminance
import com.m57.hermescontrol.theme.GlyphWhite
/**
* Resolved colors for the chat composer card and its controls.
@@ -15,7 +17,8 @@ import androidx.compose.ui.graphics.compositeOver
* (Nord's High and Highest are the same color), which made the flat controls
* vanish into the card. A fixed onSurface tint keeps them visible in every
* preset and under dynamic color. Text and icon contrast is gated per preset
* in ComposerPaletteTest.
* in ComposerPaletteTest. The send / stop / mic action button is the one
* themed control: it takes the preset's primary so it follows the theme tint.
*/
internal data class ComposerPalette(
val card: Color,
@@ -30,10 +33,30 @@ internal data class ComposerPalette(
)
private const val CONTROL_TINT_ALPHA = 0.12f
private const val ACTION_MIN_CONTRAST = 3f
private fun contrastRatio(
a: Color,
b: Color,
): Float {
val l1 = a.luminance()
val l2 = b.luminance()
return (maxOf(l1, l2) + 0.05f) / (minOf(l1, l2) + 0.05f)
}
internal fun composerPalette(scheme: ColorScheme): ComposerPalette {
val card = scheme.surfaceContainer
val tint = scheme.onSurface.copy(alpha = CONTROL_TINT_ALPHA)
// Pastel primaries (Nord light) vanish on the card; keep the neutral fill there.
val tinted = contrastRatio(scheme.primary, card) >= ACTION_MIN_CONTRAST
val action = if (tinted) scheme.primary else scheme.onSurface
// White glyph wherever it clears 3:1 on the primary; pastel primaries keep their own dark onPrimary.
val onAction =
when {
!tinted -> scheme.surface
contrastRatio(GlyphWhite, scheme.primary) >= ACTION_MIN_CONTRAST -> GlyphWhite
else -> scheme.onPrimary
}
return ComposerPalette(
card = card,
cardBorder = tint.compositeOver(scheme.background),
@@ -42,8 +65,8 @@ internal fun composerPalette(scheme: ColorScheme): ComposerPalette {
control = tint.compositeOver(card),
onControl = scheme.onSurface,
onControlVariant = scheme.onSurfaceVariant,
action = scheme.onSurface,
onAction = scheme.surface,
action = action,
onAction = onAction,
)
}