docs(theme): explain the Material and Mornye theme layer

Add lib/theme/README.md (UI-73): what Mornye is, which files form the theme layer, how DynamicColorWrapper builds each style, how Mornye maps onto Material 3 ColorScheme roles and AppTokens, and how widgets branch with context.isMornye and context.adaptiveIcon.

Derived from every file in lib/theme/, lib/models/theme_settings.dart, lib/providers/theme_provider.dart, lib/providers/runtime_profile_provider.dart, lib/widgets/mornye_chrome.dart and test/design_system_test.dart.
This commit is contained in:
zarzet committed 2026-10-19 05:27:36 +07:00
1 parent 44cbd4532b
commit c4cf56fb9b
1 file changed
+88
+88
View File
@@ -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.