mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 09:13:23 +00:00
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVwY2Qzth2EREWg8qLhyzF
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user