From 8b3dfafc20c59221bc23abbf899e51c848025ee0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 13:37:32 +0000 Subject: [PATCH] chore(theme): trim color preview to the by-role view, taller Drop the old-vs-new normalization comparison now that the color direction is settled; keep ThemeColorPairsPreview (fg/bg pairs by ColorScheme role, dark | light) and bump it to heightDp=980 so all rows render. Revert darkColors/ lightColors back to private (the removed preview was the only external caller). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01EVwY2Qzth2EREWg8qLhyzF --- .../amethyst/ui/theme/ColorSchemePreview.kt | 267 ++---------------- .../vitorpamplona/amethyst/ui/theme/Theme.kt | 4 +- 2 files changed, 18 insertions(+), 253 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/ColorSchemePreview.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/ColorSchemePreview.kt index 5d094f3682..8c750eb07f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/ColorSchemePreview.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/ColorSchemePreview.kt @@ -34,10 +34,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -51,256 +48,13 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.vitorpamplona.amethyst.model.AccentColorType -import com.vitorpamplona.amethyst.model.ThemeType import kotlin.math.max import kotlin.math.min -// Living reference + side-by-side "before / after" of the mobile theme, rendered with the real -// color schemes so editing Theme.kt re-renders both. Each row is one app surface (Save button, -// checked switch, settings tile, card, unchecked switch, …) shown four ways: -// Old·Dark | New·Dark | Old·Light | New·Light -// so the effect of the accent-normalization (+ the new filledAccent role) is directly comparable -// within each theme. "Old" reconstructs the pre-normalization scheme: purple primary + teal -// secondary with Material's default violet-tinted surfaces/containers and the deep default -// onPrimary — i.e. the theme before container-derivation, surface-neutralization and onPrimary=White. - -// ----- reconstructed pre-normalization scheme (default purple accent) ----- -private fun oldDarkColors(): ColorScheme = - darkColorScheme( - primary = Purple200, - secondary = Teal200, - tertiary = Teal200, - background = Color.Black, - surface = Color.Black, - surfaceDim = Color.Black, - surfaceVariant = Color(0xFF1D1A22), - ) - -private fun oldLightColors(): ColorScheme = - lightColorScheme( - primary = Purple500, - secondary = Teal200, - tertiary = Teal200, - surfaceContainerHighest = Color(0xFFECE6F0), - surfaceVariant = Color(0xFFFAF5FC), - ) - -// One app surface and how to resolve its fill/content from a scheme. `isOld` lets a surface pick a -// different role for the two eras — the Save button + switch used primary/onPrimary before and -// filledAccent/onFilledAccent now; everything else keeps its role and just shows the value drift. -private class StyleEntry( - val name: String, - val usage: String, - val bg: (ColorScheme, Boolean) -> Color, - val fg: (ColorScheme, Boolean) -> Color, - val highlight: Boolean = false, -) - -private val STYLE_ENTRIES = - listOf( - StyleEntry( - "Save button · checked switch", - "Old: primary/onPrimary (washed out). New: the dedicated filledAccent fill.", - bg = { s, old -> if (old) s.primary else s.filledAccent }, - fg = { s, old -> if (old) s.onPrimary else s.onFilledAccent }, - highlight = true, - ), - StyleEntry( - "FAB · filled button", - "primary / onPrimary — unchanged; still the pastel pairing on dark.", - bg = { s, _ -> s.primary }, - fg = { s, _ -> s.onPrimary }, - ), - StyleEntry( - "Settings tile · tonal card", - "primaryContainer / onPrimaryContainer — old = Material violet, new = accent-derived.", - bg = { s, _ -> s.primaryContainer }, - fg = { s, _ -> s.onPrimaryContainer }, - ), - StyleEntry( - "Selected reaction · chip", - "secondaryContainer / onSecondaryContainer.", - bg = { s, _ -> s.secondaryContainer }, - fg = { s, _ -> s.onSecondaryContainer }, - ), - StyleEntry( - "Teal accent", - "secondary / onSecondary (purple theme).", - bg = { s, _ -> s.secondary }, - fg = { s, _ -> s.onSecondary }, - ), - StyleEntry( - "App background · body text", - "surface / onSurface — old carried Material's violet tint, new is neutral grey.", - bg = { s, _ -> s.surface }, - fg = { s, _ -> s.onSurface }, - ), - StyleEntry( - "Secondary text", - "surface / onSurfaceVariant.", - bg = { s, _ -> s.surface }, - fg = { s, _ -> s.onSurfaceVariant }, - ), - StyleEntry( - "Card · sheet", - "surfaceContainer / onSurface — old = Material default, new = neutral ramp.", - bg = { s, _ -> s.surfaceContainer }, - fg = { s, _ -> s.onSurface }, - ), - StyleEntry( - "Unchecked switch", - "surfaceContainerHighest / outline (off-track + thumb).", - bg = { s, _ -> s.surfaceContainerHighest }, - fg = { s, _ -> s.outline }, - ), - StyleEntry( - "Error", - "error / onError — Material baseline in both.", - bg = { s, _ -> s.error }, - fg = { s, _ -> s.onError }, - ), - ) - -private fun Color.hex(): String = "#%06X".format(0xFFFFFF and toArgb()) - -private fun contrastRatio( - a: Color, - b: Color, -): Float { - val la = a.luminance() - val lb = b.luminance() - return (max(la, lb) + 0.05f) / (min(la, lb) + 0.05f) -} - -private const val CELL_WIDTH = 116 -private const val LABEL_WIDTH = 196 - -@Composable -private fun StyleCell( - entry: StyleEntry, - scheme: ColorScheme, - isOld: Boolean, -) { - val bg = entry.bg(scheme, isOld) - val fg = entry.fg(scheme, isOld) - val ratio = contrastRatio(bg, fg) - val label = MaterialTheme.colorScheme.onSurfaceVariant - - Column(modifier = Modifier.width(CELL_WIDTH.dp).padding(horizontal = 4.dp)) { - Box( - modifier = - Modifier - .fillMaxWidth() - .height(48.dp) - .clip(RoundedCornerShape(8.dp)) - .background(bg) - .border(1.dp, MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(8.dp)) - .padding(6.dp), - ) { - Text("Aa", color = fg, fontSize = 15.sp, fontWeight = FontWeight.Bold) - Text( - "%.1f".format(ratio), - color = fg, - fontSize = 8.sp, - fontFamily = FontFamily.Monospace, - modifier = Modifier.align(Alignment.BottomEnd), - ) - } - Text(bg.hex(), color = label, fontSize = 8.5.sp, fontFamily = FontFamily.Monospace) - Text(fg.hex(), color = label, fontSize = 8.5.sp, fontFamily = FontFamily.Monospace) - } -} - -@Composable -private fun StyleRow( - entry: StyleEntry, - oldDark: ColorScheme, - newDark: ColorScheme, - oldLight: ColorScheme, - newLight: ColorScheme, -) { - Row( - modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), - verticalAlignment = Alignment.Top, - ) { - Column(modifier = Modifier.width(LABEL_WIDTH.dp).padding(end = 8.dp)) { - Text( - entry.name, - color = MaterialTheme.colorScheme.onSurface, - fontSize = 11.5.sp, - fontWeight = if (entry.highlight) FontWeight.Bold else FontWeight.Medium, - ) - Text( - entry.usage, - color = MaterialTheme.colorScheme.onSurfaceVariant, - fontSize = 9.5.sp, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - ) - } - StyleCell(entry, oldDark, isOld = true) - StyleCell(entry, newDark, isOld = false) - StyleCell(entry, oldLight, isOld = true) - StyleCell(entry, newLight, isOld = false) - } -} - -@Composable -private fun ColumnHeader(text: String) { - Text( - text, - modifier = Modifier.width(CELL_WIDTH.dp).padding(horizontal = 4.dp), - color = MaterialTheme.colorScheme.onSurface, - fontSize = 10.sp, - fontWeight = FontWeight.Bold, - fontFamily = FontFamily.Monospace, - ) -} - -@Preview(name = "Theme styleguide — old vs new", widthDp = 720, showBackground = true) -@Composable -fun ThemeStyleguideOldVsNewPreview() { - val oldDark = oldDarkColors() - val newDark = darkColors(AccentColorType.PURPLE) - val oldLight = oldLightColors() - val newLight = lightColors(AccentColorType.PURPLE) - - // Neutral dark chrome for the table itself; each cell paints its own scheme color. - AmethystTheme(ThemeType.DARK) { - Surface(color = MaterialTheme.colorScheme.background) { - Column( - modifier = Modifier.padding(12.dp), - verticalArrangement = Arrangement.spacedBy(2.dp), - ) { - Text( - "Theme styleguide · before normalization → current", - color = MaterialTheme.colorScheme.onSurface, - fontSize = 13.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(bottom = 6.dp), - ) - Row { - Spacer(Modifier.width(LABEL_WIDTH.dp)) - ColumnHeader("Old·Dark") - ColumnHeader("New·Dark") - ColumnHeader("Old·Light") - ColumnHeader("New·Light") - } - STYLE_ENTRIES.forEach { - StyleRow(it, oldDark, newDark, oldLight, newLight) - } - } - } - } -} - -// --------------------------------------------------------------------------- -// Role-oriented view: every fg/bg pair listed by its ColorScheme role name (not -// by the component using it), rendered for the CURRENT theme in dark | light. -// Answers "what is primaryContainer right now"; the table above answers "how did -// this surface change". Both read the live scheme, so Theme.kt edits re-render. -// --------------------------------------------------------------------------- +// Living reference of every foreground/background pair the mobile theme uses, listed by its +// ColorScheme role name and rendered with the real AmethystTheme colors (dark | light). Each row +// previews the foreground on its background, the live WCAG contrast, the two hexes, and the app +// surfaces that consume the pair. Reads the live scheme, so editing Theme.kt re-renders it. private class RolePair( val name: String, @@ -387,6 +141,17 @@ private val ROLE_PAIRS = ), ) +private fun Color.hex(): String = "#%06X".format(0xFFFFFF and toArgb()) + +private fun contrastRatio( + a: Color, + b: Color, +): Float { + val la = a.luminance() + val lb = b.luminance() + return (max(la, lb) + 0.05f) / (min(la, lb) + 0.05f) +} + private fun contrastLabel(ratio: Float): String = when { ratio >= 4.5f -> "AA" @@ -471,7 +236,7 @@ private fun RolePairList() { } } -@Preview(name = "Theme color pairs by role · dark | light", widthDp = 820, showBackground = true) +@Preview(name = "Theme color pairs by role · dark | light", widthDp = 820, heightDp = 980, showBackground = true) @Composable fun ThemeColorPairsPreview() { ThemeComparisonRow { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index bf6018aecf..ac9bea39a4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -131,7 +131,7 @@ fun AccentColorType.previewColor(dark: Boolean): Color = accentPrimary(this, dar // Contrast colour (black or white) for content drawn on top of an accent swatch. fun contentColorOnAccent(color: Color): Color = onAccent(color) -internal fun darkColors(accent: AccentColorType): ColorScheme { +private fun darkColors(accent: AccentColorType): ColorScheme { val primary = accentPrimary(accent, dark = true) val secondary = accentSecondary(accent, dark = true) return darkColorScheme( @@ -176,7 +176,7 @@ internal fun darkColors(accent: AccentColorType): ColorScheme { ) } -internal fun lightColors(accent: AccentColorType): ColorScheme { +private fun lightColors(accent: AccentColorType): ColorScheme { val primary = accentPrimary(accent, dark = false) val secondary = accentSecondary(accent, dark = false) return lightColorScheme(