WIP: feat(ui): Migrate to new UI implementation

This commit is contained in:
2026-06-22 06:47:16 +04:00
parent 873aa8d7b5
commit bb4334f68d
109 changed files with 13499 additions and 1257 deletions
+308
View File
@@ -0,0 +1,308 @@
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);
}
}
-22
View File
@@ -1,22 +0,0 @@
part of 'mw_theme.dart';
/// Core palette
class MWColors {
// Surface & background
static const Color abyss = Color(0xFF090F2B); // page bg
static const Color deepNavy = Color(0xFF111840); // surfaces
static const Color stormNavy = Color(0xFF1A2759); // elevated surfaces
// Primary & secondary
static const Color primary = Color(0xFF5460A2);
static const Color secondary = Color(0xFF5F6BD2);
static const Color tertiary = Color(0xFF6494EB);
// Lines & states
static const Color outline = Color(0xFF4B4E6E);
// Semantic
static const Color success = Color(0xFF3DDC97);
static const Color warning = Color(0xFFF0B429);
static const Color error = Color(0xFFD86A8A);
}
-40
View File
@@ -1,40 +0,0 @@
part of 'mw_theme.dart';
class MoonWellDecorations extends ThemeExtension<MoonWellDecorations> {
final Gradient goldBevel; // for gilded headers/buttons
final Shadow textGlow; // subtle moonlight glow
final List<BoxShadow> cardGlow;
const MoonWellDecorations({
required this.goldBevel,
required this.textGlow,
required this.cardGlow,
});
@override
MoonWellDecorations copyWith({
Gradient? goldBevel,
Shadow? textGlow,
List<BoxShadow>? cardGlow,
}) => MoonWellDecorations(
goldBevel: goldBevel ?? this.goldBevel,
textGlow: textGlow ?? this.textGlow,
cardGlow: cardGlow ?? this.cardGlow,
);
@override
ThemeExtension<MoonWellDecorations> lerp(
ThemeExtension<MoonWellDecorations>? other,
double t,
) {
if (other is! MoonWellDecorations) return this;
return MoonWellDecorations(
goldBevel: Gradient.lerp(goldBevel, other.goldBevel, t)!,
textGlow: Shadow.lerp(textGlow, other.textGlow, t)!,
cardGlow: [
for (int i = 0; i < cardGlow.length; i++)
BoxShadow.lerp(cardGlow[i], other.cardGlow[i], t)!,
],
);
}
}
-220
View File
@@ -1,220 +0,0 @@
import 'package:flutter/material.dart';
part 'mw_colors.dart';
part 'mw_decorations.dart';
ThemeData moonWellTheme() {
const cs = ColorScheme.dark(
brightness: Brightness.dark,
primary: Color(0xFF5460A2),
onPrimary: Color(0xFFDDE0FB),
secondary: Color(0xFF5F6BD2),
onSecondary: Color(0xFFDDE0FB),
tertiary: Color(0xFF6494EB),
onTertiary: Color(0xFF090F2B),
error: Color(0xFFD86A8A),
onError: Color(0xFF090F2B),
surface: Color(0xFF1A2759),
onSurface: Color(0xFFDDE0FB),
outline: Color(0xFF4B4E6E),
shadow: Color(0xFF000000),
scrim: Color(0xCC000000),
);
final base = ThemeData(
useMaterial3: true,
colorScheme: cs,
scaffoldBackgroundColor: cs.surface,
canvasColor: cs.surface,
);
final text = base.textTheme
.apply(
fontFamily: 'Cinzel',
bodyColor: cs.onSurface,
displayColor: cs.onSurface,
)
.copyWith(
displayLarge: base.textTheme.displayLarge?.copyWith(
letterSpacing: 0.5,
fontWeight: FontWeight.w700,
shadows: const [Shadow(blurRadius: 10, color: Color(0x336BA3FF))],
),
headlineMedium: base.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.w700,
),
titleLarge: base.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w700,
),
);
return base.copyWith(
textTheme: text,
appBarTheme: AppBarTheme(
backgroundColor: Colors.transparent,
surfaceTintColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
foregroundColor: cs.onSurface,
centerTitle: true,
titleTextStyle: text.titleLarge,
toolbarHeight: 64,
),
cardTheme: CardThemeData(
color: cs.surface.withAlpha(50),
elevation: 0,
margin: const EdgeInsets.all(12),
shape: RoundedRectangleBorder(
side: BorderSide(color: MWColors.outline.withAlpha(150)),
borderRadius: BorderRadius.circular(20),
),
shadowColor: MWColors.tertiary.withAlpha(63),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(horizontal: 18, vertical: 14),
),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
),
textStyle: WidgetStatePropertyAll(
TextStyle(fontWeight: FontWeight.bold, fontFamily: 'Cinzel'),
),
elevation: const WidgetStatePropertyAll(6),
shadowColor: WidgetStatePropertyAll(MWColors.tertiary.withAlpha(89)),
backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.disabled)) {
return MWColors.primary.withAlpha(115);
}
return cs.primary;
}),
foregroundColor: const WidgetStatePropertyAll(Color(0xFFDDE0FB)),
),
),
textButtonTheme: TextButtonThemeData(
style: ButtonStyle(
foregroundColor: WidgetStatePropertyAll(cs.tertiary),
overlayColor: WidgetStatePropertyAll(cs.tertiary.withAlpha(10)),
),
),
filledButtonTheme: FilledButtonThemeData(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(cs.tertiaryContainer),
foregroundColor: WidgetStatePropertyAll(cs.onTertiaryContainer),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: ButtonStyle(
side: WidgetStatePropertyAll(
BorderSide(color: MWColors.outline.withAlpha(230)),
),
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
),
foregroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.disabled)) {
return cs.primary.withAlpha(100);
}
return cs.primary;
}),
backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.disabled)) {
return MWColors.stormNavy.withAlpha(100);
}
return MWColors.stormNavy;
}),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: cs.surfaceContainerHighest,
hintStyle: TextStyle(color: cs.onSurfaceVariant.withAlpha(179)),
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: MWColors.outline),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: cs.primary, width: 1.6),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: cs.error),
),
prefixIconColor: cs.onSurfaceVariant,
suffixIconColor: cs.onSurfaceVariant,
),
chipTheme: base.chipTheme.copyWith(
backgroundColor: cs.surfaceContainerHighest,
side: BorderSide(color: MWColors.outline),
selectedColor: cs.primaryContainer,
labelStyle: TextStyle(color: cs.onSurface),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
sliderTheme: base.sliderTheme.copyWith(
activeTrackColor: cs.primary,
inactiveTrackColor: cs.primary.withAlpha(63),
thumbColor: cs.primary,
),
dividerTheme: DividerThemeData(
color: MWColors.outline,
thickness: 1,
space: 24,
),
bottomNavigationBarTheme: BottomNavigationBarThemeData(
backgroundColor: cs.surface,
selectedItemColor: cs.primary,
unselectedItemColor: cs.onSurface.withAlpha(153),
elevation: 8,
type: BottomNavigationBarType.fixed,
),
extensions: <ThemeExtension<dynamic>>[
const MoonWellDecorations(
goldBevel: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: <Color>[
Color(0xFF7A85CC), // highlight
Color(0xFF5460A2), // body
Color(0xFF3E4880), // edge
],
stops: [0.0, 0.55, 1.0],
),
textGlow: Shadow(
color: Color(0x446494EB),
blurRadius: 14,
offset: Offset(0, 0),
),
cardGlow: [
BoxShadow(
color: Color(0x33143666), // cool rim light
blurRadius: 28,
spreadRadius: 2,
offset: Offset(0, 8),
),
],
),
],
);
}