diff --git a/lib/theme/README.md b/lib/theme/README.md new file mode 100644 index 00000000..0432dc6d --- /dev/null +++ b/lib/theme/README.md @@ -0,0 +1,88 @@ +# Theme Layer + +The app has two visual styles, chosen in Appearance settings and stored as +`AppThemeStyle { material, mornye }` (`lib/models/theme_settings.dart`, +preference key `theme_style`, loaded by `lib/providers/theme_provider.dart`): + +- **Material**: Material 3 with dynamic color or a seed color, optional AMOLED + surfaces, and Material 3 Expressive motion and components. +- **Mornye**: the Flutter counterpart of Mornye's iPhone visual system: iOS + system colors, Cupertino typography and icons, and glass surfaces. Its + palette is kept independent of wallpaper colors. + +Both styles still produce a Flutter `ThemeData` with `useMaterial3: true`, so +widgets read `Theme.of(context).colorScheme` and `textTheme` the same way. +Mornye maps its colors onto Material 3 `ColorScheme` roles instead of +replacing them. + +## Files + +| File | Role | +| --- | --- | +| `lib/theme/dynamic_color_wrapper.dart` | `DynamicColorWrapper`, used by `lib/app.dart`. Builds the light and dark `ThemeData` for the selected style | +| `lib/theme/app_theme.dart` | `AppTheme.light` and `AppTheme.dark`: Material 3 component themes over a `ColorScheme` | +| `lib/theme/app_tokens.dart` | `AppTokens`, a `ThemeExtension` with the shared radii, spacing, cover sizes and header metrics | +| `lib/theme/mornye_theme.dart` | `MornyeTheme`: the Mornye `ThemeExtension`, its `ThemeData` builder, `context.isMornye`, `MornyeSettingsTheme` | +| `lib/theme/mornye_icons.dart` | `mornyeIconFor` (Material icon to Cupertino icon) and `context.adaptiveIcon` | +| `lib/theme/material_symbols.dart` | Material Symbols Rounded counterparts of `Icons.*` for the Material style | +| `lib/theme/material_expressive.dart` | `materialExpressiveEnabled` and `MaterialExpressiveScope`, the bridge to the Material UI and Material 3 Expressive packages | +| `lib/theme/app_motion.dart` | `AppMotionSpec`, `SpringCurve` and `AppMotion` spring presets | +| `lib/theme/cover_palette.dart` | `CoverPalette`, `HeaderPalette` and `CoverPaletteBuilder`: colors extracted from artwork | + +## How a Theme Is Built + +`DynamicColorWrapper` watches `themeProvider` and the custom font provider. + +- Material: `DynamicColorBuilder` supplies the wallpaper scheme when dynamic + color is on; otherwise `ColorScheme.fromSeed` uses the seed color. AMOLED + replaces the dark surfaces with black and near-black. `AppTheme` then adds + component themes and registers `AppTokens.standard`. +- Mornye: `MornyeTheme.build(brightness, ...)` takes the Mornye accent + (`MornyeAccent`, red by default), the system-font and custom-font choices, + glass clarity (default 0.75) and glass outlines. It seeds a `ColorScheme` + from the accent and overrides the roles with iOS grouped colors (for + example `#F2F2F7` and `#1C1C1E`). Text uses Cupertino styles. The font is + the custom font when one is set; otherwise the Cupertino system font + (`CupertinoSystemText`, SF on Apple) on iOS and macOS or when the system + font is chosen, and Inter elsewhere. It registers its own + `MornyeTheme.tokens`, a copy of `AppTokens.standard` with different radii + (smaller for most shapes, larger for sheets) and iOS title sizes, and + removes ink splashes. + +`MornyeTheme.build` caches up to 16 base themes; changing only glass clarity +or outlines reuses the cached theme. Use `MornyeTheme.fromContext` to rebuild +a local surface (another brightness or chrome color) without losing the +user's Mornye preferences. + +## Using the Layer in Widgets + +- Read tokens with `context.tokens`, not `Theme.of(context).extension`. It + falls back to `AppTokens.standard` outside a themed subtree. +- Branch on `context.isMornye`, which is true when the nearest `Theme` carries + a `MornyeTheme` extension. Because it follows the nearest `Theme`, a + subtree can be switched by wrapping it in another theme. +- Keep `Icons.*` at call sites and pass them through `context.adaptiveIcon`; + Mornye then shows the Cupertino glyph from `mornyeIconFor`. +- Gate Material 3 Expressive motion with `materialExpressiveEnabled` (false + for Mornye and when the OS asks for reduced motion). `AppMotion` presets + return the call site's fallback spec in those cases. +- Wrap settings pages in `MornyeSettingsTheme` for iOS inset grouped + surfaces; it does nothing in the Material style. + +## Mornye Outside This Folder + +- Glass rendering level: `MornyeGlassLevel` (`flat`, `frosted`, `liquid`) and + `mornyeGlassLevelProvider` in `lib/providers/runtime_profile_provider.dart` + choose how much glass a device renders. +- Mornye widgets live in `lib/widgets/mornye_*.dart`, for example `MornyeGlass` + in `lib/widgets/mornye_chrome.dart`. Mornye-only screens are + `lib/screens/mornye_library_screen.dart` and + `lib/screens/track_metadata_mornye.dart`; most other screens branch on + `context.isMornye`. + +## Tests + +`test/design_system_test.dart` checks that `AppTokens` is registered on the +Material themes (`AppTokens.standard`) and on Mornye (`MornyeTheme.tokens`), +the Mornye dark accent, and Mornye's font families and letter spacing. Mornye +widgets have their own `test/mornye_*_test.dart` suites.