import 'package:flutter/material.dart'; /// Design tokens for fin-tracker's visual language (`docs/ai/design-system.md`): graphite-violet /// surfaces, one bright sky-blue accent, flat rounded cards with no outline, filled inputs and /// pill-shaped navigation — the look of Snowball. /// /// Both brightnesses share this accent and this shape language — only the surface tones /// differ — so the dark theme is not a special case bolted onto a default light Material /// theme, and switching `themeModeProvider` never changes which widgets exist. class AppTheme { const AppTheme._(); /// The accent: buttons, active tabs, links and the primary chart series. /// Matches `ChartColors.slot1Blue`. static const seed = Color(0xFF14AFFF); static ThemeData light() => _build(Brightness.light); static ThemeData dark() => _build(Brightness.dark); static ColorScheme _scheme(Brightness brightness) { final base = ColorScheme.fromSeed(seedColor: seed, brightness: brightness); if (brightness == Brightness.light) { return base.copyWith( primary: const Color(0xFF0A98E6), onPrimary: Colors.white, surface: const Color(0xFFF2F2F7), surfaceContainerLowest: Colors.white, surfaceContainerLow: const Color(0xFFF7F7FB), surfaceContainer: Colors.white, surfaceContainerHigh: const Color(0xFFE8E8F0), surfaceContainerHighest: const Color(0xFFDCDCE6), outlineVariant: const Color(0xFFD8D8E2), ); } return base.copyWith( primary: seed, onPrimary: const Color(0xFF04283B), primaryContainer: const Color(0xFF0E4B6E), onPrimaryContainer: const Color(0xFFD3F0FF), surface: const Color(0xFF25252F), surfaceContainerLowest: const Color(0xFF1F1F28), surfaceContainerLow: const Color(0xFF2A2A35), surfaceContainer: const Color(0xFF2F2F3C), surfaceContainerHigh: const Color(0xFF3A3A49), surfaceContainerHighest: const Color(0xFF474757), onSurface: const Color(0xFFEDEDF3), onSurfaceVariant: const Color(0xFFB4B4C3), outline: const Color(0xFF74748A), outlineVariant: const Color(0xFF3F3F4F), error: const Color(0xFFF26B6B), ); } static ThemeData _build(Brightness brightness) { final scheme = _scheme(brightness); final dark = brightness == Brightness.dark; final outline = scheme.outlineVariant; final radius = BorderRadius.circular(12); final fieldRadius = BorderRadius.circular(8); OutlineInputBorder field([Color? color, double width = 1]) => OutlineInputBorder( borderRadius: fieldRadius, borderSide: color == null ? BorderSide.none : BorderSide(color: color, width: width), ); return ThemeData( useMaterial3: true, brightness: brightness, colorScheme: scheme, scaffoldBackgroundColor: scheme.surface, appBarTheme: AppBarTheme( backgroundColor: scheme.surface, surfaceTintColor: Colors.transparent, elevation: 0, scrolledUnderElevation: 0, ), cardTheme: CardThemeData( elevation: 0, color: scheme.surfaceContainer, surfaceTintColor: Colors.transparent, clipBehavior: Clip.antiAlias, margin: EdgeInsets.zero, shape: RoundedRectangleBorder( borderRadius: radius, side: dark ? BorderSide.none : BorderSide(color: outline), ), ), dividerTheme: DividerThemeData(color: outline, space: 1, thickness: 1), dialogTheme: DialogThemeData( backgroundColor: scheme.surfaceContainer, surfaceTintColor: Colors.transparent, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), ), bottomSheetTheme: BottomSheetThemeData( backgroundColor: scheme.surfaceContainer, surfaceTintColor: Colors.transparent, ), inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: scheme.surfaceContainerHigh, border: field(), enabledBorder: field(), focusedBorder: field(scheme.primary, 1.5), errorBorder: field(scheme.error), focusedErrorBorder: field(scheme.error, 1.5), ), filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom( shape: RoundedRectangleBorder(borderRadius: fieldRadius), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( shape: RoundedRectangleBorder(borderRadius: fieldRadius), side: BorderSide(color: outline), ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( shape: RoundedRectangleBorder(borderRadius: fieldRadius), ), ), chipTheme: ChipThemeData( backgroundColor: scheme.surfaceContainerHigh, side: BorderSide.none, shape: RoundedRectangleBorder(borderRadius: fieldRadius), ), tabBarTheme: TabBarThemeData( indicatorColor: scheme.primary, labelColor: scheme.onSurface, unselectedLabelColor: scheme.onSurfaceVariant, dividerColor: outline, labelStyle: const TextStyle(fontWeight: FontWeight.w600), ), popupMenuTheme: PopupMenuThemeData( color: scheme.surfaceContainerHigh, surfaceTintColor: Colors.transparent, shape: RoundedRectangleBorder(borderRadius: radius), ), navigationRailTheme: NavigationRailThemeData( backgroundColor: scheme.surfaceContainerLow, indicatorColor: scheme.primaryContainer, useIndicator: true, selectedIconTheme: IconThemeData(color: scheme.primary), unselectedIconTheme: IconThemeData(color: scheme.onSurfaceVariant), selectedLabelTextStyle: TextStyle( color: scheme.primary, fontWeight: FontWeight.w600, ), unselectedLabelTextStyle: TextStyle(color: scheme.onSurfaceVariant), ), navigationBarTheme: NavigationBarThemeData( backgroundColor: scheme.surfaceContainerLowest, indicatorColor: scheme.primaryContainer, elevation: 0, iconTheme: WidgetStateProperty.resolveWith( (states) => IconThemeData( color: states.contains(WidgetState.selected) ? scheme.primary : scheme.onSurfaceVariant, ), ), labelTextStyle: WidgetStateProperty.resolveWith( (states) => TextStyle( fontSize: 12, fontWeight: states.contains(WidgetState.selected) ? FontWeight.w600 : FontWeight.w400, color: states.contains(WidgetState.selected) ? scheme.primary : scheme.onSurfaceVariant, ), ), ), ); } }