Files
buzz/mobile/lib/shared/theme/theme_pairs.dart
klopez4212 ed11c8d8bf Refresh mobile utility surfaces and theme picker (#6944)
## Summary

- invert mobile utility surfaces so pages and sheets use the softer page
background with raised containers
- rebuild the community theme picker around swipeable Home/Chat
previews, accent and appearance controls, and native iOS glass
interactions
- align shared sheet/profile spacing and radii, and move Theme into the
Community settings card

### Related issue

None found.

### Testing

- `just mobile-check`
- `just mobile-test` (1,871 tests)
- signed iOS Release build installed on a physical iPhone

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
2026-08-28 18:56:42 +01:00

123 lines
3.9 KiB
Dart

import 'buzz_theme.dart';
import 'theme_catalog.dart';
/// Light → dark theme counterparts, ported from the desktop app's `THEME_PAIRS`
/// (`desktop/src/shared/theme/theme-loader.ts`) so both clients offer the same
/// System-mode pairings.
///
/// Buzz leads the map the way it leads desktop's, so the first-party pair sorts
/// ahead of the borrowed syntax themes wherever insertion order is preserved.
const themePairs = <String, String>{
'buzz': 'buzz-dark',
'catppuccin-latte': 'catppuccin-mocha',
'everforest-light': 'everforest-dark',
'github-light': 'github-dark',
'github-light-default': 'github-dark-default',
'github-light-high-contrast': 'github-dark-high-contrast',
'gruvbox-light-hard': 'gruvbox-dark-hard',
'gruvbox-light-medium': 'gruvbox-dark-medium',
'gruvbox-light-soft': 'gruvbox-dark-soft',
'kanagawa-lotus': 'kanagawa-wave',
'light-plus': 'dark-plus',
'material-theme-lighter': 'material-theme',
'min-light': 'min-dark',
'one-light': 'one-dark-pro',
'rose-pine-dawn': 'rose-pine',
'slack-ochin': 'slack-dark',
'solarized-light': 'solarized-dark',
'vitesse-light': 'vitesse-dark',
};
final Map<String, String> _darkToLight = {
for (final entry in themePairs.entries) entry.value: entry.key,
};
/// The counterpart of [name] in the opposite brightness, or null when the theme
/// has no pair. Resolves in both directions.
String? themePairFor(String name) => themePairs[name] ?? _darkToLight[name];
/// Whether [name] participates in a light/dark pair, and so can follow the OS.
bool isPairedTheme(String name) => themePairFor(name) != null;
/// [themeCatalog] partitioned the way the appearance picker offers it.
class ThemeGroups {
/// Light members of light/dark pairs — the System-mode options. Each stands
/// in for both halves of its pair.
final List<ThemeColors> paired;
/// Every light theme, paired or not — the Light-mode options.
final List<ThemeColors> light;
/// Every dark theme, paired or not — the Dark-mode options.
final List<ThemeColors> dark;
const ThemeGroups({
required this.paired,
required this.light,
required this.dark,
});
}
ThemeGroups? _groups;
/// [themeCatalog] grouped for the appearance picker. Buzz is always first;
/// borrowed themes follow in display-name order. Computed once — the catalog
/// is a compile-time constant.
ThemeGroups themeGroups() {
final cached = _groups;
if (cached != null) return cached;
final paired = <ThemeColors>[];
final light = <ThemeColors>[];
final dark = <ThemeColors>[];
for (final theme in themeCatalog) {
if (theme.isDark) {
dark.add(theme);
continue;
}
light.add(theme);
if (themePairs.containsKey(theme.name)) paired.add(theme);
}
int byPickerOrder(ThemeColors a, ThemeColors b) {
final aIsBuzz = isBuzzTheme(a.name);
final bIsBuzz = isBuzzTheme(b.name);
if (aIsBuzz != bIsBuzz) return aIsBuzz ? -1 : 1;
return a.displayName.compareTo(b.displayName);
}
paired.sort(byPickerOrder);
light.sort(byPickerOrder);
dark.sort(byPickerOrder);
return _groups = ThemeGroups(paired: paired, light: light, dark: dark);
}
/// Tokens that only describe a theme's brightness, stripped from paired labels.
const _modeTokens = <String>{
'light',
'latte',
'dawn',
'lotus',
'ochin',
'lighter',
'plus',
};
/// Display label for a pair, derived from its light member: strips the
/// mode-specific tokens so `github-light` reads as "Github" and stands for both
/// halves. Mirrors desktop's `pairedThemeLabel`.
String pairedThemeLabel(String lightName) {
final stripped = lightName
.split('-')
.where((token) => !_modeTokens.contains(token))
.toList();
// Stripping can remove everything (e.g. "light-plus") — fall back to the raw
// name so the row is never blank.
final parts = stripped.isEmpty ? lightName.split('-') : stripped;
return parts
.map((w) => w.isEmpty ? w : '${w[0].toUpperCase()}${w.substring(1)}')
.join(' ');
}