import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; /// Melo-Design-Tokens: Zugriff per `Theme.of(context).extension()!` class MeloDesignTokens extends ThemeExtension { // ─── Farben ─── final Color hintergrund; // #0B0B10 final Color oberflaeche; // #14141C final Color karte; // #1C1C26 final Color textPrimaer; // #F5F5F7 final Color textSekundaer; // #9E9EAB final Color erfolg; // #4CAF50 final Color warnung; // #FF9800 final Color fehler; // #F44336 final Color akzent; // Live per ValueNotifier // ─── Radien ─── final double cardRadius; // 20 final double buttonRadius; // 999 (Pill) final double sheetRadius; // 28 final double dialogRadius; // 24 final double inputRadius; // 14 // ─── Abstände / Typo ─── final double abstandXs; final double abstandSm; final double abstandMd; final double abstandLg; const MeloDesignTokens({ required this.hintergrund, required this.oberflaeche, required this.karte, required this.textPrimaer, required this.textSekundaer, required this.erfolg, required this.warnung, required this.fehler, required this.akzent, this.cardRadius = 20, this.buttonRadius = 999, this.sheetRadius = 28, this.dialogRadius = 24, this.inputRadius = 14, this.abstandXs = 4, this.abstandSm = 8, this.abstandMd = 16, this.abstandLg = 24, }); /// Akzent-Gradient (Akzent → dunklere Variante) Gradient get akzentGradient => LinearGradient( colors: [akzent, Color.lerp(akzent, Colors.black, 0.5)!], begin: Alignment.topLeft, end: Alignment.bottomRight, ); /// Rot-Gradient (hartes Rot, unabhängig vom User-Akzent) static const Gradient rotGradient = LinearGradient( colors: [Color(0xFFFF3B30), Color(0xFFC62828)], begin: Alignment.topLeft, end: Alignment.bottomRight, ); /// Glow-Schatten für Akzent-Elemente List get akzentGlow => [ BoxShadow( color: akzent.withValues(alpha: 0.30), blurRadius: 24, offset: const Offset(0, 8), ), ]; @override MeloDesignTokens copyWith({ Color? hintergrund, Color? oberflaeche, Color? karte, Color? textPrimaer, Color? textSekundaer, Color? erfolg, Color? warnung, Color? fehler, Color? akzent, double? cardRadius, double? buttonRadius, double? sheetRadius, double? dialogRadius, double? inputRadius, }) { return MeloDesignTokens( hintergrund: hintergrund ?? this.hintergrund, oberflaeche: oberflaeche ?? this.oberflaeche, karte: karte ?? this.karte, textPrimaer: textPrimaer ?? this.textPrimaer, textSekundaer: textSekundaer ?? this.textSekundaer, erfolg: erfolg ?? this.erfolg, warnung: warnung ?? this.warnung, fehler: fehler ?? this.fehler, akzent: akzent ?? this.akzent, cardRadius: cardRadius ?? this.cardRadius, buttonRadius: buttonRadius ?? this.buttonRadius, sheetRadius: sheetRadius ?? this.sheetRadius, dialogRadius: dialogRadius ?? this.dialogRadius, inputRadius: inputRadius ?? this.inputRadius, ); } @override MeloDesignTokens lerp(ThemeExtension? other, double t) { if (other is! MeloDesignTokens) return this; return MeloDesignTokens( hintergrund: Color.lerp(hintergrund, other.hintergrund, t)!, oberflaeche: Color.lerp(oberflaeche, other.oberflaeche, t)!, karte: Color.lerp(karte, other.karte, t)!, textPrimaer: Color.lerp(textPrimaer, other.textPrimaer, t)!, textSekundaer: Color.lerp(textSekundaer, other.textSekundaer, t)!, erfolg: Color.lerp(erfolg, other.erfolg, t)!, warnung: Color.lerp(warnung, other.warnung, t)!, fehler: Color.lerp(fehler, other.fehler, t)!, akzent: Color.lerp(akzent, other.akzent, t)!, ); } } /// MeloTheme – zentrales Design-System für Melo Dark Fusion. /// Behälter für Akzentfarbe (per User) und das komplette ThemeData. class MeloTheme { /// Legacy-Farb-Konstanten (für Rückwärtskompatibilität mit bestehendem Code) static const Color rot = Color(0xFFCC0000); static const Color rotHell = Color(0x33CC0000); static const Color schwarz = Color(0xFF0B0B10); static const Color oberflaeche = Color(0xFF14141C); static const Color dunkel1 = Color(0xFF14141C); static const Color dunkel2 = Color(0xFF1C1C26); static const Color textPrimaer = Color(0xFFF5F5F7); static const Color textSekundaer = Color(0xFF9E9EAB); /// Akzentfarbe – wechselt pro angemeldeter Person (Login-Effekt). /// UI hängt per ValueListenableBuilder an [akzentNotifier]. static final ValueNotifier akzentNotifier = ValueNotifier(rot); static Color get akzent => akzentNotifier.value; static set akzent(Color farbe) => akzentNotifier.value = farbe; /// Baut das vollständige ThemeData mit allen Komponenten-Themen. static ThemeData themeMitAkzent(Color akzent) { final tokens = MeloDesignTokens( hintergrund: const Color(0xFF0B0B10), oberflaeche: const Color(0xFF14141C), karte: const Color(0xFF1C1C26), textPrimaer: const Color(0xFFF5F5F7), textSekundaer: const Color(0xFF9E9EAB), erfolg: const Color(0xFF4CAF50), warnung: const Color(0xFFFF9800), fehler: const Color(0xFFF44336), akzent: akzent, ); // Outfit-TextTheme final textTheme = GoogleFonts.outfitTextTheme().apply( displayColor: tokens.textPrimaer, bodyColor: tokens.textPrimaer, ); return ThemeData( brightness: Brightness.dark, scaffoldBackgroundColor: tokens.hintergrund, fontFamily: GoogleFonts.outfit().fontFamily, colorScheme: ColorScheme.dark( primary: akzent, secondary: akzent, surface: tokens.oberflaeche, onPrimary: Colors.white, onSecondary: Colors.white, onSurface: tokens.textPrimaer, error: tokens.fehler, ), textTheme: textTheme, // ─── AppBar ─── appBarTheme: AppBarTheme( backgroundColor: tokens.oberflaeche, foregroundColor: tokens.textPrimaer, elevation: 0, titleTextStyle: TextStyle( fontSize: 22, fontWeight: FontWeight.w600, color: tokens.textPrimaer, letterSpacing: -0.5, ), ), // ─── BottomNavigationBar ─── bottomNavigationBarTheme: BottomNavigationBarThemeData( backgroundColor: tokens.oberflaeche, selectedItemColor: akzent, unselectedItemColor: tokens.textSekundaer, type: BottomNavigationBarType.fixed, elevation: 0, selectedLabelStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.w600), unselectedLabelStyle: const TextStyle(fontSize: 11), ), // ─── Card ─── cardTheme: CardThemeData( color: tokens.karte, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(tokens.cardRadius), ), margin: EdgeInsets.zero, ), // ─── Chip ─── chipTheme: ChipThemeData( backgroundColor: tokens.karte, selectedColor: akzent, labelStyle: TextStyle(color: tokens.textPrimaer, fontSize: 13), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(tokens.buttonRadius), ), side: BorderSide.none, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), ), // ─── FilledButton (Pill, Gradient) ─── filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( backgroundColor: akzent, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(tokens.buttonRadius), ), padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 14), textStyle: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), ), ), // ─── ElevatedButton ─── elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: akzent, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(tokens.buttonRadius), ), padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 14), textStyle: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, ), elevation: 0, ), ), // ─── OutlinedButton ─── outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: akzent, side: BorderSide(color: akzent), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(tokens.buttonRadius), ), padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 14), ), ), // ─── InputDecoration ─── inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: tokens.karte, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), border: OutlineInputBorder( borderRadius: BorderRadius.circular(tokens.inputRadius), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(tokens.inputRadius), borderSide: BorderSide(color: akzent, width: 1.5), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(tokens.inputRadius), borderSide: BorderSide(color: tokens.karte), ), labelStyle: TextStyle(color: tokens.textSekundaer, fontSize: 13), hintStyle: TextStyle(color: tokens.textSekundaer.withValues(alpha: 0.6), fontSize: 13), ), // ─── Switch ─── switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) return akzent; return tokens.textSekundaer; }), trackColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return akzent.withValues(alpha: 0.4); } return tokens.karte; }), ), // ─── Slider ─── sliderTheme: SliderThemeData( activeTrackColor: akzent, inactiveTrackColor: tokens.karte, thumbColor: akzent, overlayColor: akzent.withValues(alpha: 0.2), trackHeight: 4, ), // ─── Dialog ─── dialogTheme: DialogThemeData( backgroundColor: tokens.oberflaeche, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(tokens.dialogRadius), ), titleTextStyle: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: tokens.textPrimaer, ), ), // ─── BottomSheet ─── bottomSheetTheme: BottomSheetThemeData( backgroundColor: tokens.oberflaeche, shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(tokens.sheetRadius)), ), ), // ─── NavigationBar ─── navigationBarTheme: NavigationBarThemeData( backgroundColor: tokens.oberflaeche, indicatorColor: akzent.withValues(alpha: 0.25), surfaceTintColor: Colors.transparent, ), // ─── Extensions ─── extensions: [tokens], ); } /// Legacy-Getter (von main.dart genutzt) — nutzt aktuelle Akzentfarbe. static ThemeData get theme => themeMitAkzent(akzent); }