309 lines
10 KiB
Dart
309 lines
10 KiB
Dart
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<MoonWellDesignTokens> {
|
|
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<BoxShadow> panelShadow;
|
|
final Duration motionFast;
|
|
final Duration motionStandard;
|
|
final String brandFontFamily;
|
|
final String monospaceFontFamily;
|
|
|
|
static MoonWellDesignTokens of(BuildContext context) =>
|
|
Theme.of(context).extension<MoonWellDesignTokens>()!;
|
|
|
|
@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<BoxShadow>? 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<void> load() async {
|
|
_variant = await _preferencesRepository.getThemeVariant();
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> setVariant(MoonWellThemeVariant variant) async {
|
|
if (_variant == variant) return;
|
|
_variant = variant;
|
|
notifyListeners();
|
|
await _preferencesRepository.setThemeVariant(variant);
|
|
}
|
|
}
|