A brand theme starts from one seed color, for BookNest the green of its launcher icon, #2E7D32 (Adaptive Icons). Don't pick the 48 roles by eye: derive them with the algorithm behind dynamic color, Material Color Utilities 2,276 (github.com/material-foundation/material-color-utilities (https://github.com/material-foundation/material-color-utilities 2,276 ), Apache 2.0). The web tool Material Theme Builder wraps it and exports Kotlin files; the library is also on npm 2,036 , so a short script generates the Kotlin directly:
import { argbFromHex, hexFromArgb, Hct, SchemeTonalSpot, MaterialDynamicColors as M }
from "@material/material-color-utilities";
const roles = `primary onPrimary primaryContainer onPrimaryContainer inversePrimary secondary
onSecondary secondaryContainer onSecondaryContainer tertiary onTertiary tertiaryContainer
onTertiaryContainer background onBackground surface onSurface surfaceVariant onSurfaceVariant
surfaceTint inverseSurface inverseOnSurface error onError errorContainer onErrorContainer
outline outlineVariant scrim surfaceBright surfaceDim surfaceContainerLowest surfaceContainerLow
surfaceContainer surfaceContainerHigh surfaceContainerHighest primaryFixed primaryFixedDim
onPrimaryFixed onPrimaryFixedVariant secondaryFixed secondaryFixedDim onSecondaryFixed
onSecondaryFixedVariant tertiaryFixed tertiaryFixedDim onTertiaryFixed onTertiaryFixedVariant`
.split(/\s+/);
const seed = Hct.fromInt(argbFromHex(process.argv[2] ?? "#2E7D32"));
const kt = (r, s) => `${r} = Color(0xFF${hexFromArgb(M[r].getArgb(s)).slice(1).toUpperCase()})`;
for (const dark of [false, true]) {
const scheme = new SchemeTonalSpot(seed, dark, 0.0); // the same algorithm as Android 12+
const name = dark ? "Dark" : "Light";
console.log(`internal val BookNest${name} = ${name.toLowerCase()}ColorScheme(`);
for (let i = 0; i < roles.length; i += 2)
console.log(" " + roles.slice(i, i + 2).map(r => kt(r, scheme)).join(", ") + ",");
console.log(")");
}npm install @material/material-color-utilities tsx
npx tsx kotlin.mjs "#2E7D32" > colors.txt
head -4 colors.txtinternal val BookNestLight = lightColorScheme( primary = Color(0xFF3C6939), onPrimary = Color(0xFFFFFFFF), primaryContainer = Color(0xFFBCF0B4), onPrimaryContainer = Color(0xFF245024), inversePrimary = Color(0xFFA1D39A), secondary = Color(0xFF52634F),
The full output, 52 lines, is ui/theme/Color.kt. Notice that primary is #3C6939, not the seed: HCT (hue, chroma, tone) keeps the seed's hue and places each role at a fixed tone, 40 for light primary and 80 for dark, which guarantees contrast between every role and its "on" partner.
Theme.kt chooses between the schemes (Dynamic Color and Material You), and MainActivity now wraps every screen in BookNestTheme, so later screens are green. A first attempt that set only twelve roles left the Scaffold background and the cards lavender: lightColorScheme() fills every omitted role from the purple baseline.