fix(theme): complete color and component contrast contracts (#1417)
This commit is contained in:
22 files changed
+1013
-318
No files matched your search
@@ -40,6 +40,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.theme.HermesStatusColors
|
||||
import com.m57.hermescontrol.theme.LocalHermesStatusColors
|
||||
import com.m57.hermescontrol.theme.LocalSpacing
|
||||
|
||||
@@ -81,28 +82,8 @@ fun StatusBadge(
|
||||
val spacing = LocalSpacing.current
|
||||
val statusColors = LocalHermesStatusColors.current
|
||||
val (bgColor, fgColor) =
|
||||
when (status) {
|
||||
StatusBadgeType.SUCCESS -> {
|
||||
statusColors.successContainer to statusColors.success
|
||||
}
|
||||
|
||||
StatusBadgeType.WARNING -> {
|
||||
statusColors.warningContainer to statusColors.warning
|
||||
}
|
||||
|
||||
StatusBadgeType.ERROR -> {
|
||||
statusColors.errorContainer to statusColors.error
|
||||
}
|
||||
|
||||
StatusBadgeType.INFO -> {
|
||||
statusColors.infoContainer to statusColors.info
|
||||
}
|
||||
|
||||
StatusBadgeType.NEUTRAL -> {
|
||||
MaterialTheme.colorScheme.surfaceContainerHigh to
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
}
|
||||
}
|
||||
statusBadgeColors(status, statusColors)
|
||||
?: (MaterialTheme.colorScheme.surfaceContainerHigh to MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
@@ -135,6 +116,20 @@ fun StatusBadge(
|
||||
|
||||
enum class StatusBadgeType { SUCCESS, WARNING, ERROR, INFO, NEUTRAL }
|
||||
|
||||
/** Exact fill/text pair shared by status badges and live-session indicators. */
|
||||
internal fun statusBadgeColors(
|
||||
status: StatusBadgeType,
|
||||
colors: HermesStatusColors,
|
||||
): Pair<Color, Color>? =
|
||||
// PR #1417: on-status tokens belong on the status fill, not its container.
|
||||
when (status) {
|
||||
StatusBadgeType.SUCCESS -> colors.success to colors.onSuccess
|
||||
StatusBadgeType.WARNING -> colors.warning to colors.onWarning
|
||||
StatusBadgeType.ERROR -> colors.error to colors.onError
|
||||
StatusBadgeType.INFO -> colors.info to colors.onInfo
|
||||
StatusBadgeType.NEUTRAL -> null // Neutral badges use the active Material scheme.
|
||||
}
|
||||
|
||||
// ── ToggleRow — switch row with label + description ────────────────────
|
||||
|
||||
@Composable
|
||||
|
||||
+8
-14
@@ -25,6 +25,8 @@ import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.model.SessionLiveStatus
|
||||
import com.m57.hermescontrol.theme.LocalHermesStatusColors
|
||||
import com.m57.hermescontrol.theme.LocalSpacing
|
||||
import com.m57.hermescontrol.ui.common.StatusBadgeType
|
||||
import com.m57.hermescontrol.ui.common.statusBadgeColors
|
||||
|
||||
@Composable
|
||||
fun SessionLiveStatusIndicator(
|
||||
@@ -35,27 +37,26 @@ fun SessionLiveStatusIndicator(
|
||||
val spacing = LocalSpacing.current
|
||||
val statusColors = LocalHermesStatusColors.current
|
||||
|
||||
val (bgColor, fgColor, textRes, descRes) =
|
||||
val (badgeType, textRes, descRes) =
|
||||
when (liveStatus) {
|
||||
SessionLiveStatus.WORKING -> {
|
||||
Tuple4(
|
||||
statusColors.successContainer,
|
||||
statusColors.success,
|
||||
Triple(
|
||||
StatusBadgeType.SUCCESS,
|
||||
R.string.sessions_live_status_running,
|
||||
R.string.sessions_live_status_running_desc,
|
||||
)
|
||||
}
|
||||
|
||||
SessionLiveStatus.WAITING -> {
|
||||
Tuple4(
|
||||
statusColors.warningContainer,
|
||||
statusColors.warning,
|
||||
Triple(
|
||||
StatusBadgeType.WARNING,
|
||||
R.string.sessions_live_status_waiting,
|
||||
R.string.sessions_live_status_waiting_desc,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val (bgColor, fgColor) = requireNotNull(statusBadgeColors(badgeType, statusColors))
|
||||
val text = stringResource(textRes)
|
||||
val description = stringResource(descRes)
|
||||
|
||||
@@ -103,10 +104,3 @@ fun SessionLiveStatusIndicator(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class Tuple4<A, B, C, D>(
|
||||
val first: A,
|
||||
val second: B,
|
||||
val third: C,
|
||||
val fourth: D,
|
||||
)
|
||||
@@ -14,7 +14,6 @@ import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -270,11 +269,6 @@ private fun ReleaseCandidateRow(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
enabled = enabled,
|
||||
colors =
|
||||
SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.primary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
+27
-69
@@ -1,5 +1,6 @@
|
||||
package com.m57.hermescontrol.ui.settings.components
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -21,7 +22,6 @@ import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
@@ -41,6 +41,7 @@ import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.model.BusySendMode
|
||||
import com.m57.hermescontrol.theme.ThemePreference
|
||||
import com.m57.hermescontrol.theme.ThemePreset
|
||||
import com.m57.hermescontrol.theme.ThemeRegistry
|
||||
import com.m57.hermescontrol.ui.chat.label
|
||||
import com.m57.hermescontrol.ui.settings.SectionCard
|
||||
import kotlin.math.abs
|
||||
@@ -55,6 +56,7 @@ internal fun AppearanceSection(
|
||||
themePreset: ThemePreset,
|
||||
onThemePresetChange: (ThemePreset) -> Unit,
|
||||
) {
|
||||
val dynamicAvailable = useDynamicColors && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||
SectionCard {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_item_theme),
|
||||
@@ -106,11 +108,6 @@ internal fun AppearanceSection(
|
||||
Switch(
|
||||
checked = useDynamicColors,
|
||||
onCheckedChange = onUseDynamicColorsChange,
|
||||
colors =
|
||||
SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.primary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -122,72 +119,30 @@ internal fun AppearanceSection(
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
if (dynamicAvailable) {
|
||||
Text(
|
||||
text = stringResource(R.string.settings_desc_theme_preset_dynamic),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
var presetsExpanded by remember { mutableStateOf(false) }
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
OutlinedButton(
|
||||
onClick = { presetsExpanded = true },
|
||||
enabled = !useDynamicColors,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
when (themePreset) {
|
||||
ThemePreset.DEFAULT -> stringResource(R.string.theme_preset_default)
|
||||
ThemePreset.MONOCHROME -> stringResource(R.string.theme_preset_monochrome)
|
||||
ThemePreset.GRUVBOX -> stringResource(R.string.theme_preset_gruvbox)
|
||||
ThemePreset.CATPPUCCIN -> stringResource(R.string.theme_preset_catppuccin)
|
||||
ThemePreset.AMOLED -> stringResource(R.string.theme_preset_amoled)
|
||||
ThemePreset.NORD -> stringResource(R.string.theme_preset_nord)
|
||||
},
|
||||
)
|
||||
Text(themePreset.label())
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = presetsExpanded,
|
||||
onDismissRequest = { presetsExpanded = false },
|
||||
modifier = Modifier.fillMaxWidth(0.85f),
|
||||
) {
|
||||
ThemePreset.entries.forEach { preset ->
|
||||
ThemeRegistry.forEach { (preset, _) ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(
|
||||
when (preset) {
|
||||
ThemePreset.DEFAULT -> {
|
||||
stringResource(
|
||||
R.string.theme_preset_default,
|
||||
)
|
||||
}
|
||||
|
||||
ThemePreset.MONOCHROME -> {
|
||||
stringResource(
|
||||
R.string.theme_preset_monochrome,
|
||||
)
|
||||
}
|
||||
|
||||
ThemePreset.GRUVBOX -> {
|
||||
stringResource(
|
||||
R.string.theme_preset_gruvbox,
|
||||
)
|
||||
}
|
||||
|
||||
ThemePreset.CATPPUCCIN -> {
|
||||
stringResource(
|
||||
R.string.theme_preset_catppuccin,
|
||||
)
|
||||
}
|
||||
|
||||
ThemePreset.AMOLED -> {
|
||||
stringResource(
|
||||
R.string.theme_preset_amoled,
|
||||
)
|
||||
}
|
||||
|
||||
ThemePreset.NORD -> {
|
||||
stringResource(
|
||||
R.string.theme_preset_nord,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
text = { Text(preset.label()) },
|
||||
onClick = {
|
||||
onThemePresetChange(preset)
|
||||
presetsExpanded = false
|
||||
@@ -199,6 +154,19 @@ internal fun AppearanceSection(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ThemePreset.label(): String =
|
||||
stringResource(
|
||||
when (this) {
|
||||
ThemePreset.DEFAULT -> R.string.theme_preset_default
|
||||
ThemePreset.MONOCHROME -> R.string.theme_preset_monochrome
|
||||
ThemePreset.GRUVBOX -> R.string.theme_preset_gruvbox
|
||||
ThemePreset.CATPPUCCIN -> R.string.theme_preset_catppuccin
|
||||
ThemePreset.AMOLED -> R.string.theme_preset_amoled
|
||||
ThemePreset.NORD -> R.string.theme_preset_nord
|
||||
},
|
||||
)
|
||||
|
||||
@Composable
|
||||
internal fun ChatSection(
|
||||
typingEffectEnabled: Boolean,
|
||||
@@ -485,11 +453,6 @@ internal fun ChatSection(
|
||||
checked = showModelProvider,
|
||||
onCheckedChange = onShowModelProviderChange,
|
||||
modifier = Modifier.testTag("settings_show_model_provider"),
|
||||
colors =
|
||||
SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.primary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -516,11 +479,6 @@ internal fun ChatSection(
|
||||
Switch(
|
||||
checked = typingEffectEnabled,
|
||||
onCheckedChange = onTypingEffectEnabledChange,
|
||||
colors =
|
||||
SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.primary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -50,11 +49,6 @@ internal fun BehaviorSection(
|
||||
Switch(
|
||||
checked = autoReconnect,
|
||||
onCheckedChange = onAutoReconnectChange,
|
||||
colors =
|
||||
SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.primary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -83,11 +77,6 @@ internal fun BehaviorSection(
|
||||
Switch(
|
||||
checked = keepConnectedInBackground,
|
||||
onCheckedChange = onKeepConnectedInBackgroundChange,
|
||||
colors =
|
||||
SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.primary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -116,11 +105,6 @@ internal fun BehaviorSection(
|
||||
Switch(
|
||||
checked = restoreLastSession,
|
||||
onCheckedChange = onRestoreLastSessionChange,
|
||||
colors =
|
||||
SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.primary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user