Custom Theme

Building a Custom Theme for BookNest

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:

kotlin.mjs: generating both color schemes from the seedJavaScript
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(")");
}
Running it (the package's ES modules need tsx to resolve their imports)Shell
npm install @material/material-color-utilities tsx
npx tsx kotlin.mjs "#2E7D32" > colors.txt
head -4 colors.txt
Output
internal 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.