import 'package:flutter/material.dart'; import 'package:moonwell_launcher/core/moonwell_theme_variant.dart'; import 'package:moonwell_launcher/features/preferences/domain/repositories/preferences_repository.dart'; export 'package:moonwell_launcher/core/moonwell_theme_variant.dart'; @immutable class MoonWellDesignTokens extends ThemeExtension { const MoonWellDesignTokens({ required this.background, required this.surfaceLow, required this.surface, required this.surfaceHigh, required this.accent, required this.accentMuted, required this.success, required this.warning, required this.border, required this.focusRing, required this.spacing, required this.radiusSmall, required this.radiusMedium, required this.radiusLarge, required this.panelShadow, required this.motionFast, required this.motionStandard, required this.brandFontFamily, required this.monospaceFontFamily, }); final Color background; final Color surfaceLow; final Color surface; final Color surfaceHigh; final Color accent; final Color accentMuted; final Color success; final Color warning; final BorderSide border; final Color focusRing; final double spacing; final double radiusSmall; final double radiusMedium; final double radiusLarge; final List panelShadow; final Duration motionFast; final Duration motionStandard; final String brandFontFamily; final String monospaceFontFamily; static MoonWellDesignTokens of(BuildContext context) => Theme.of(context).extension()!; @override MoonWellDesignTokens copyWith({ Color? background, Color? surfaceLow, Color? surface, Color? surfaceHigh, Color? accent, Color? accentMuted, Color? success, Color? warning, BorderSide? border, Color? focusRing, double? spacing, double? radiusSmall, double? radiusMedium, double? radiusLarge, List? panelShadow, Duration? motionFast, Duration? motionStandard, String? brandFontFamily, String? monospaceFontFamily, }) { return MoonWellDesignTokens( background: background ?? this.background, surfaceLow: surfaceLow ?? this.surfaceLow, surface: surface ?? this.surface, surfaceHigh: surfaceHigh ?? this.surfaceHigh, accent: accent ?? this.accent, accentMuted: accentMuted ?? this.accentMuted, success: success ?? this.success, warning: warning ?? this.warning, border: border ?? this.border, focusRing: focusRing ?? this.focusRing, spacing: spacing ?? this.spacing, radiusSmall: radiusSmall ?? this.radiusSmall, radiusMedium: radiusMedium ?? this.radiusMedium, radiusLarge: radiusLarge ?? this.radiusLarge, panelShadow: panelShadow ?? this.panelShadow, motionFast: motionFast ?? this.motionFast, motionStandard: motionStandard ?? this.motionStandard, brandFontFamily: brandFontFamily ?? this.brandFontFamily, monospaceFontFamily: monospaceFontFamily ?? this.monospaceFontFamily, ); } @override MoonWellDesignTokens lerp(covariant MoonWellDesignTokens? other, double t) { if (other == null) return this; return MoonWellDesignTokens( background: Color.lerp(background, other.background, t)!, surfaceLow: Color.lerp(surfaceLow, other.surfaceLow, t)!, surface: Color.lerp(surface, other.surface, t)!, surfaceHigh: Color.lerp(surfaceHigh, other.surfaceHigh, t)!, accent: Color.lerp(accent, other.accent, t)!, accentMuted: Color.lerp(accentMuted, other.accentMuted, t)!, success: Color.lerp(success, other.success, t)!, warning: Color.lerp(warning, other.warning, t)!, border: BorderSide.lerp(border, other.border, t), focusRing: Color.lerp(focusRing, other.focusRing, t)!, spacing: lerpDouble(spacing, other.spacing, t), radiusSmall: lerpDouble(radiusSmall, other.radiusSmall, t), radiusMedium: lerpDouble(radiusMedium, other.radiusMedium, t), radiusLarge: lerpDouble(radiusLarge, other.radiusLarge, t), panelShadow: t < .5 ? panelShadow : other.panelShadow, motionFast: t < .5 ? motionFast : other.motionFast, motionStandard: t < .5 ? motionStandard : other.motionStandard, brandFontFamily: t < .5 ? brandFontFamily : other.brandFontFamily, monospaceFontFamily: t < .5 ? monospaceFontFamily : other.monospaceFontFamily, ); } static double lerpDouble(double a, double b, double t) => a + (b - a) * t; } abstract final class MoonWellTheme { static ThemeData create(MoonWellThemeVariant variant, {String? fontPackage}) { final isForest = variant == MoonWellThemeVariant.forest; const background = Color(0xFF070A18); const surfaceLow = Color(0xFF0A0D1F); const surface = Color(0xFF10142A); const surfaceHigh = Color(0xFF1A1F3A); final accent = isForest ? const Color(0xFF7FB8D4) : const Color(0xFF8A5CC8); const accentMuted = Color(0xFF5B3A8A); const outline = Color(0xFF34455E); const foreground = Color(0xFFECE4D0); const error = Color(0xFFFF8A86); const success = Color(0xFF70D7A5); const warning = Color(0xFFFFC865); final scheme = ColorScheme.dark( primary: accent, onPrimary: background, secondary: accentMuted, onSecondary: foreground, tertiary: const Color(0xFF91C7D0), onTertiary: background, error: error, onError: background, surface: surface, onSurface: foreground, outline: outline, shadow: Colors.black, scrim: Colors.black87, ); String font(String family) => fontPackage == null ? family : 'packages/$fontPackage/$family'; final base = ThemeData( useMaterial3: true, colorScheme: scheme, scaffoldBackgroundColor: background, canvasColor: background, fontFamily: font('Inter'), focusColor: accent.withAlpha(60), visualDensity: VisualDensity.standard, ); final radius = BorderRadius.circular(10); final textTheme = base.textTheme .apply(bodyColor: foreground, displayColor: foreground) .copyWith( displayLarge: base.textTheme.displayLarge?.copyWith( fontFamily: font('Cormorant Garamond'), fontWeight: FontWeight.w600, ), displayMedium: base.textTheme.displayMedium?.copyWith( fontFamily: font('Cormorant Garamond'), fontWeight: FontWeight.w600, ), headlineLarge: base.textTheme.headlineLarge?.copyWith( fontFamily: font('Cormorant Garamond'), fontWeight: FontWeight.w600, ), headlineMedium: base.textTheme.headlineMedium?.copyWith( fontFamily: font('Cormorant Garamond'), fontWeight: FontWeight.w600, ), titleLarge: base.textTheme.titleLarge?.copyWith( fontFamily: font('Cormorant Garamond'), fontWeight: FontWeight.w700, ), labelLarge: base.textTheme.labelLarge?.copyWith( fontFamily: font('Inter'), fontWeight: FontWeight.w700, ), ); return base.copyWith( textTheme: textTheme, inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: surfaceHigh, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), enabledBorder: OutlineInputBorder( borderRadius: radius, borderSide: BorderSide(color: outline), ), focusedBorder: OutlineInputBorder( borderRadius: radius, borderSide: BorderSide(color: accent, width: 2), ), errorBorder: OutlineInputBorder( borderRadius: radius, borderSide: const BorderSide(color: error), ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( minimumSize: const Size(44, 44), backgroundColor: accent, foregroundColor: background, disabledBackgroundColor: outline.withAlpha(90), disabledForegroundColor: foreground.withAlpha(120), shape: RoundedRectangleBorder(borderRadius: radius), textStyle: TextStyle( fontFamily: font('Inter'), fontWeight: FontWeight.w700, ), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( minimumSize: const Size(44, 44), foregroundColor: foreground, side: BorderSide(color: outline), shape: RoundedRectangleBorder(borderRadius: radius), ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( minimumSize: const Size(44, 44), foregroundColor: accent, ), ), checkboxTheme: CheckboxThemeData( side: BorderSide(color: outline), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), extensions: [ MoonWellDesignTokens( background: background, surfaceLow: surfaceLow, surface: surface, surfaceHigh: surfaceHigh, accent: accent, accentMuted: accentMuted, success: success, warning: warning, border: BorderSide(color: outline.withAlpha(190)), focusRing: accent, spacing: 8, radiusSmall: 6, radiusMedium: 10, radiusLarge: 16, panelShadow: const [ BoxShadow( color: Color(0x66000000), blurRadius: 24, offset: Offset(0, 12), ), ], motionFast: const Duration(milliseconds: 120), motionStandard: const Duration(milliseconds: 220), brandFontFamily: font('Cinzel'), monospaceFontFamily: font('JetBrains Mono'), ), ], ); } } class LauncherThemeController extends ChangeNotifier { LauncherThemeController(this._preferencesRepository); final PreferencesRepository _preferencesRepository; MoonWellThemeVariant _variant = MoonWellThemeVariant.forest; MoonWellThemeVariant get variant => _variant; Future load() async { _variant = await _preferencesRepository.getThemeVariant(); notifyListeners(); } Future setVariant(MoonWellThemeVariant variant) async { if (_variant == variant) return; _variant = variant; notifyListeners(); await _preferencesRepository.setThemeVariant(variant); } }