import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @immutable class JzPalette extends ThemeExtension { final Color text; final Color text2; final Color text3; final Color background; final Color card; final Color line; final Color primaryBackground; final Color aiBackground; final Color expenseBackground; final Color warningBackground; const JzPalette({ required this.text, required this.text2, required this.text3, required this.background, required this.card, required this.line, required this.primaryBackground, required this.aiBackground, required this.expenseBackground, required this.warningBackground, }); @override JzPalette copyWith({ Color? text, Color? text2, Color? text3, Color? background, Color? card, Color? line, Color? primaryBackground, Color? aiBackground, Color? expenseBackground, Color? warningBackground, }) => JzPalette( text: text ?? this.text, text2: text2 ?? this.text2, text3: text3 ?? this.text3, background: background ?? this.background, card: card ?? this.card, line: line ?? this.line, primaryBackground: primaryBackground ?? this.primaryBackground, aiBackground: aiBackground ?? this.aiBackground, expenseBackground: expenseBackground ?? this.expenseBackground, warningBackground: warningBackground ?? this.warningBackground, ); @override JzPalette lerp(covariant JzPalette? other, double t) { if (other == null) return this; return JzPalette( text: Color.lerp(text, other.text, t)!, text2: Color.lerp(text2, other.text2, t)!, text3: Color.lerp(text3, other.text3, t)!, background: Color.lerp(background, other.background, t)!, card: Color.lerp(card, other.card, t)!, line: Color.lerp(line, other.line, t)!, primaryBackground: Color.lerp( primaryBackground, other.primaryBackground, t, )!, aiBackground: Color.lerp(aiBackground, other.aiBackground, t)!, expenseBackground: Color.lerp( expenseBackground, other.expenseBackground, t, )!, warningBackground: Color.lerp( warningBackground, other.warningBackground, t, )!, ); } } extension JzThemeContext on BuildContext { JzPalette get jz => Theme.of(this).extension()!; } class AppTheme { AppTheme._(); static const Color primary = Color(0xFF00B386); static const Color primaryDeep = Color(0xFF009973); static const Color primaryBg = Color(0xFFE6F7F1); static const Color primaryMid = Color(0xFF4DA6FF); static const Color primaryLight = Color(0xFF80DCC2); static const Color ai = Color(0xFF5B6BF5); static const Color aiBg = Color(0xFFEEF0FE); static const Color red = Color(0xFFF0642D); static const Color redBg = Color(0xFFFFF3EC); static const Color orange = Color(0xFFF5A623); static const Color orangeBg = Color(0xFFFFF7E8); static const Color text = Color(0xFF191F26); static const Color text2 = Color(0xFF5E6772); static const Color text3 = Color(0xFF9AA3AD); static const Color bg = Color(0xFFF6F7F9); static const Color card = Colors.white; static const Color line = Color(0xFFEEF0F3); static const _lightPalette = JzPalette( text: text, text2: text2, text3: text3, background: bg, card: card, line: line, primaryBackground: primaryBg, aiBackground: aiBg, expenseBackground: redBg, warningBackground: orangeBg, ); static const _darkPalette = JzPalette( text: Color(0xFFE8EFED), text2: Color(0xFFAAB6B2), text3: Color(0xFF74817D), background: Color(0xFF111615), card: Color(0xFF1B2220), line: Color(0xFF2B3532), primaryBackground: Color(0xFF143A31), aiBackground: Color(0xFF272B4A), expenseBackground: Color(0xFF40251F), warningBackground: Color(0xFF3B2E1B), ); static ThemeData get light => _build(Brightness.light, _lightPalette); static ThemeData get dark => _build(Brightness.dark, _darkPalette); static ThemeData _build(Brightness brightness, JzPalette p) { final dark = brightness == Brightness.dark; final scheme = dark ? ColorScheme.dark( primary: primary, secondary: ai, surface: p.card, error: red, onPrimary: Colors.white, onSecondary: Colors.white, onSurface: p.text, primaryContainer: p.primaryBackground, onPrimaryContainer: const Color(0xFF9BE7D1), secondaryContainer: p.aiBackground, onSecondaryContainer: const Color(0xFFC5CAFF), outline: p.line, outlineVariant: p.line, ) : ColorScheme.light( primary: primary, secondary: ai, surface: p.card, error: red, onPrimary: Colors.white, onSecondary: Colors.white, onSurface: p.text, primaryContainer: p.primaryBackground, onPrimaryContainer: primaryDeep, secondaryContainer: p.aiBackground, onSecondaryContainer: ai, outline: p.line, outlineVariant: p.line, ); final baseText = ThemeData( brightness: brightness, ).textTheme.apply(bodyColor: p.text, displayColor: p.text); return ThemeData( useMaterial3: true, brightness: brightness, colorScheme: scheme, scaffoldBackgroundColor: p.background, fontFamily: 'PingFang SC', textTheme: baseText, extensions: [p], dividerColor: p.line, disabledColor: p.text3, appBarTheme: AppBarTheme( backgroundColor: Colors.transparent, foregroundColor: p.text, elevation: 0, centerTitle: true, titleTextStyle: TextStyle( color: p.text, fontSize: 16, fontWeight: FontWeight.w600, ), systemOverlayStyle: dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark, ), bottomNavigationBarTheme: BottomNavigationBarThemeData( backgroundColor: p.card, selectedItemColor: primary, unselectedItemColor: p.text3, type: BottomNavigationBarType.fixed, elevation: 0, ), cardTheme: CardThemeData( color: p.card, surfaceTintColor: Colors.transparent, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), side: BorderSide(color: p.line, width: 0.5), ), ), inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: p.card, border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: BorderSide(color: p.line), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: BorderSide(color: p.line), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: primary, width: 1.4), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: red), ), hintStyle: TextStyle(color: p.text3, fontSize: 13.5), labelStyle: TextStyle(color: p.text2, fontSize: 13), contentPadding: const EdgeInsets.symmetric( horizontal: 14, vertical: 13, ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: primary, foregroundColor: Colors.white, disabledBackgroundColor: p.line, disabledForegroundColor: p.text3, elevation: 0, minimumSize: const Size(0, 46), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), padding: const EdgeInsets.symmetric(vertical: 14), textStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600), ), ), filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( backgroundColor: primary, foregroundColor: Colors.white, disabledBackgroundColor: p.line, disabledForegroundColor: p.text3, elevation: 0, minimumSize: const Size(0, 46), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w700), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: p.text, side: BorderSide(color: p.line), minimumSize: const Size(0, 46), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( foregroundColor: dark ? const Color(0xFF77D7BD) : primaryDeep, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), textStyle: const TextStyle( fontSize: 13.5, fontWeight: FontWeight.w600, ), ), ), iconButtonTheme: IconButtonThemeData( style: IconButton.styleFrom( foregroundColor: p.text2, highlightColor: p.primaryBackground, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), checkboxTheme: CheckboxThemeData( fillColor: WidgetStateProperty.resolveWith( (states) => states.contains(WidgetState.selected) ? primary : p.card, ), side: BorderSide(color: p.text3), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), ), switchTheme: SwitchThemeData( trackColor: WidgetStateProperty.resolveWith( (states) => states.contains(WidgetState.selected) ? p.primaryBackground : p.line, ), thumbColor: WidgetStateProperty.resolveWith( (states) => states.contains(WidgetState.selected) ? primary : p.text3, ), trackOutlineColor: const WidgetStatePropertyAll(Colors.transparent), ), dialogTheme: DialogThemeData( backgroundColor: p.card, surfaceTintColor: Colors.transparent, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(22)), titleTextStyle: TextStyle( color: p.text, fontSize: 18, fontWeight: FontWeight.w800, ), contentTextStyle: TextStyle( color: p.text2, fontSize: 13.5, height: 1.6, ), ), bottomSheetTheme: BottomSheetThemeData( backgroundColor: p.card, modalBackgroundColor: p.card, surfaceTintColor: Colors.transparent, showDragHandle: false, ), snackBarTheme: SnackBarThemeData( backgroundColor: dark ? const Color(0xFFE8EFED) : p.text, contentTextStyle: TextStyle( color: dark ? const Color(0xFF111615) : Colors.white, fontSize: 13, ), behavior: SnackBarBehavior.floating, elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), navigationBarTheme: NavigationBarThemeData( backgroundColor: p.card, indicatorColor: p.primaryBackground, ), ); } }