import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; /// A class that contains all theme configurations for the application. /// Implements "Purposeful Transit Minimalism" design system with high-contrast utility colors. class AppTheme { AppTheme._(); // Core color palette - High-Contrast Utility for Panama's bright outdoor conditions static const Color primaryBlack = Color(0xFF101820); static const Color accentYellow = Color(0xFFFEE715); static const Color surfaceWhite = Color(0xFFFFFFFF); static const Color textSecondary = Color(0xFF6B7280); static const Color successGreen = Color(0xFF10B981); static const Color warningOrange = Color(0xFFF59E0B); static const Color errorRed = Color(0xFFEF4444); static const Color backgroundGray = Color(0xFFF9FAFB); static const Color borderLight = Color(0xFFE5E7EB); static const Color overlayDark = Color(0x1F2937CC); // Text emphasis colors for light theme static const Color textHighEmphasisLight = Color(0xFF101820); // Primary black static const Color textMediumEmphasisLight = Color(0xFF6B7280); // Text secondary static const Color textDisabledLight = Color(0xFFE5E7EB); // Border light // Text emphasis colors for dark theme static const Color textHighEmphasisDark = Color(0xFFFFFFFF); static const Color textMediumEmphasisDark = Color(0xFF9CA3AF); static const Color textDisabledDark = Color(0xFF6B7280); // Shadow colors for subtle elevation static const Color shadowLight = Color(0x33000000); // 20% opacity black for 2-4dp blur static const Color shadowDark = Color(0x33FFFFFF); // 20% opacity white /// Light theme - Optimized for Panama's bright outdoor conditions static ThemeData lightTheme = ThemeData( brightness: Brightness.light, colorScheme: ColorScheme( brightness: Brightness.light, primary: primaryBlack, onPrimary: surfaceWhite, primaryContainer: textSecondary, onPrimaryContainer: surfaceWhite, secondary: accentYellow, onSecondary: primaryBlack, secondaryContainer: warningOrange, onSecondaryContainer: primaryBlack, tertiary: successGreen, onTertiary: surfaceWhite, tertiaryContainer: successGreen, onTertiaryContainer: surfaceWhite, error: errorRed, onError: surfaceWhite, surface: surfaceWhite, onSurface: primaryBlack, onSurfaceVariant: textSecondary, outline: borderLight, outlineVariant: borderLight, shadow: shadowLight, scrim: overlayDark, inverseSurface: primaryBlack, onInverseSurface: surfaceWhite, inversePrimary: accentYellow, ), scaffoldBackgroundColor: backgroundGray, cardColor: surfaceWhite, dividerColor: borderLight, // AppBar theme - Clean, minimal header appBarTheme: AppBarTheme( backgroundColor: surfaceWhite, foregroundColor: primaryBlack, elevation: 0, shadowColor: shadowLight, surfaceTintColor: Colors.transparent, titleTextStyle: GoogleFonts.roboto( fontSize: 20, fontWeight: FontWeight.w500, color: primaryBlack, ), iconTheme: const IconThemeData( color: primaryBlack, size: 24, ), ), // Card theme - Clean separation without borders cardTheme: CardThemeData( color: surfaceWhite, elevation: 2.0, shadowColor: shadowLight, surfaceTintColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), // Bottom navigation - Persistent state with contextual badges bottomNavigationBarTheme: BottomNavigationBarThemeData( backgroundColor: surfaceWhite, selectedItemColor: primaryBlack, unselectedItemColor: textSecondary, type: BottomNavigationBarType.fixed, elevation: 8, selectedLabelStyle: GoogleFonts.openSans( fontSize: 12, fontWeight: FontWeight.w600, ), unselectedLabelStyle: GoogleFonts.openSans( fontSize: 12, fontWeight: FontWeight.w400, ), ), // Floating action button - High contrast accent floatingActionButtonTheme: const FloatingActionButtonThemeData( backgroundColor: accentYellow, foregroundColor: primaryBlack, elevation: 4.0, shape: CircleBorder(), ), // Button themes - Clear action hierarchy elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( foregroundColor: primaryBlack, backgroundColor: accentYellow, elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), textStyle: GoogleFonts.openSans( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: primaryBlack, backgroundColor: Colors.transparent, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), side: const BorderSide(color: primaryBlack, width: 1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), textStyle: GoogleFonts.openSans( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( foregroundColor: primaryBlack, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), textStyle: GoogleFonts.openSans( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), // Typography - Optimized for Spanish character support and mobile readability textTheme: _buildTextTheme(isLight: true), // Input decoration - Clean form elements with clear focus states inputDecorationTheme: InputDecorationTheme( fillColor: surfaceWhite, filled: true, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: const BorderSide(color: borderLight, width: 1), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: const BorderSide(color: borderLight, width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: const BorderSide(color: primaryBlack, width: 2), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: const BorderSide(color: errorRed, width: 1), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: const BorderSide(color: errorRed, width: 2), ), labelStyle: GoogleFonts.openSans( color: textSecondary, fontSize: 16, fontWeight: FontWeight.w400, ), hintStyle: GoogleFonts.openSans( color: textDisabledLight, fontSize: 16, fontWeight: FontWeight.w400, ), ), // Interactive elements switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return accentYellow; } return borderLight; }), trackColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return primaryBlack; } return textSecondary; }), ), checkboxTheme: CheckboxThemeData( fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return primaryBlack; } return Colors.transparent; }), checkColor: WidgetStateProperty.all(accentYellow), side: const BorderSide(color: borderLight, width: 1), ), radioTheme: RadioThemeData( fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return primaryBlack; } return textSecondary; }), ), progressIndicatorTheme: const ProgressIndicatorThemeData( color: primaryBlack, linearTrackColor: borderLight, ), sliderTheme: SliderThemeData( activeTrackColor: primaryBlack, thumbColor: accentYellow, overlayColor: accentYellow.withValues(alpha: 0.2), inactiveTrackColor: borderLight, ), tabBarTheme: TabBarThemeData( labelColor: primaryBlack, unselectedLabelColor: textSecondary, indicatorColor: accentYellow, indicatorSize: TabBarIndicatorSize.tab, labelStyle: GoogleFonts.roboto( fontSize: 16, fontWeight: FontWeight.w500, ), unselectedLabelStyle: GoogleFonts.roboto( fontSize: 16, fontWeight: FontWeight.w400, ), ), tooltipTheme: TooltipThemeData( decoration: BoxDecoration( color: primaryBlack, borderRadius: BorderRadius.circular(8), ), textStyle: GoogleFonts.openSans( color: surfaceWhite, fontSize: 14, fontWeight: FontWeight.w400, ), ), snackBarTheme: SnackBarThemeData( backgroundColor: primaryBlack, contentTextStyle: GoogleFonts.openSans( color: surfaceWhite, fontSize: 16, fontWeight: FontWeight.w400, ), actionTextColor: accentYellow, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), elevation: 4, ), dialogTheme: DialogThemeData(backgroundColor: surfaceWhite), ); /// Dark theme - Maintains high contrast for nighttime use static ThemeData darkTheme = ThemeData( brightness: Brightness.dark, colorScheme: ColorScheme( brightness: Brightness.dark, primary: accentYellow, onPrimary: primaryBlack, primaryContainer: warningOrange, onPrimaryContainer: primaryBlack, secondary: primaryBlack, onSecondary: surfaceWhite, secondaryContainer: textSecondary, onSecondaryContainer: surfaceWhite, tertiary: successGreen, onTertiary: primaryBlack, tertiaryContainer: successGreen, onTertiaryContainer: primaryBlack, error: errorRed, onError: surfaceWhite, surface: primaryBlack, onSurface: surfaceWhite, onSurfaceVariant: textMediumEmphasisDark, outline: textSecondary, outlineVariant: textSecondary, shadow: shadowDark, scrim: overlayDark, inverseSurface: surfaceWhite, onInverseSurface: primaryBlack, inversePrimary: primaryBlack, ), scaffoldBackgroundColor: primaryBlack, cardColor: Color(0xFF1F2937), dividerColor: textSecondary, appBarTheme: AppBarTheme( backgroundColor: primaryBlack, foregroundColor: surfaceWhite, elevation: 0, shadowColor: shadowDark, surfaceTintColor: Colors.transparent, titleTextStyle: GoogleFonts.roboto( fontSize: 20, fontWeight: FontWeight.w500, color: surfaceWhite, ), iconTheme: const IconThemeData( color: surfaceWhite, size: 24, ), ), cardTheme: CardThemeData( color: Color(0xFF1F2937), elevation: 2.0, shadowColor: shadowDark, surfaceTintColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), bottomNavigationBarTheme: BottomNavigationBarThemeData( backgroundColor: primaryBlack, selectedItemColor: accentYellow, unselectedItemColor: textMediumEmphasisDark, type: BottomNavigationBarType.fixed, elevation: 8, selectedLabelStyle: GoogleFonts.openSans( fontSize: 12, fontWeight: FontWeight.w600, ), unselectedLabelStyle: GoogleFonts.openSans( fontSize: 12, fontWeight: FontWeight.w400, ), ), floatingActionButtonTheme: const FloatingActionButtonThemeData( backgroundColor: accentYellow, foregroundColor: primaryBlack, elevation: 4.0, shape: CircleBorder(), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( foregroundColor: primaryBlack, backgroundColor: accentYellow, elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), textStyle: GoogleFonts.openSans( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: accentYellow, backgroundColor: Colors.transparent, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), side: const BorderSide(color: accentYellow, width: 1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), textStyle: GoogleFonts.openSans( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( foregroundColor: accentYellow, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), textStyle: GoogleFonts.openSans( fontSize: 16, fontWeight: FontWeight.w600, ), ), ), textTheme: _buildTextTheme(isLight: false), inputDecorationTheme: InputDecorationTheme( fillColor: Color(0xFF1F2937), filled: true, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: const BorderSide(color: textSecondary, width: 1), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: const BorderSide(color: textSecondary, width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: const BorderSide(color: accentYellow, width: 2), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: const BorderSide(color: errorRed, width: 1), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8.0), borderSide: const BorderSide(color: errorRed, width: 2), ), labelStyle: GoogleFonts.openSans( color: textMediumEmphasisDark, fontSize: 16, fontWeight: FontWeight.w400, ), hintStyle: GoogleFonts.openSans( color: textDisabledDark, fontSize: 16, fontWeight: FontWeight.w400, ), ), switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return accentYellow; } return textSecondary; }), trackColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return primaryBlack; } return textDisabledDark; }), ), checkboxTheme: CheckboxThemeData( fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return accentYellow; } return Colors.transparent; }), checkColor: WidgetStateProperty.all(primaryBlack), side: const BorderSide(color: textSecondary, width: 1), ), radioTheme: RadioThemeData( fillColor: WidgetStateProperty.resolveWith((states) { if (states.contains(WidgetState.selected)) { return accentYellow; } return textMediumEmphasisDark; }), ), progressIndicatorTheme: const ProgressIndicatorThemeData( color: accentYellow, linearTrackColor: textSecondary, ), sliderTheme: SliderThemeData( activeTrackColor: accentYellow, thumbColor: accentYellow, overlayColor: accentYellow.withValues(alpha: 0.2), inactiveTrackColor: textSecondary, ), tabBarTheme: TabBarThemeData( labelColor: accentYellow, unselectedLabelColor: textMediumEmphasisDark, indicatorColor: accentYellow, indicatorSize: TabBarIndicatorSize.tab, labelStyle: GoogleFonts.roboto( fontSize: 16, fontWeight: FontWeight.w500, ), unselectedLabelStyle: GoogleFonts.roboto( fontSize: 16, fontWeight: FontWeight.w400, ), ), tooltipTheme: TooltipThemeData( decoration: BoxDecoration( color: surfaceWhite, borderRadius: BorderRadius.circular(8), ), textStyle: GoogleFonts.openSans( color: primaryBlack, fontSize: 14, fontWeight: FontWeight.w400, ), ), snackBarTheme: SnackBarThemeData( backgroundColor: surfaceWhite, contentTextStyle: GoogleFonts.openSans( color: primaryBlack, fontSize: 16, fontWeight: FontWeight.w400, ), actionTextColor: primaryBlack, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), elevation: 4, ), dialogTheme: DialogThemeData(backgroundColor: Color(0xFF1F2937)), ); /// Helper method to build text theme based on brightness /// Uses Google Fonts for optimal Spanish character support and mobile readability static TextTheme _buildTextTheme({required bool isLight}) { final Color textHighEmphasis = isLight ? textHighEmphasisLight : textHighEmphasisDark; final Color textMediumEmphasis = isLight ? textMediumEmphasisLight : textMediumEmphasisDark; final Color textDisabled = isLight ? textDisabledLight : textDisabledDark; return TextTheme( // Display styles - Roboto for strong presence displayLarge: GoogleFonts.roboto( fontSize: 57, fontWeight: FontWeight.w700, color: textHighEmphasis, letterSpacing: -0.25, ), displayMedium: GoogleFonts.roboto( fontSize: 45, fontWeight: FontWeight.w700, color: textHighEmphasis, ), displaySmall: GoogleFonts.roboto( fontSize: 36, fontWeight: FontWeight.w500, color: textHighEmphasis, ), // Headline styles - Roboto for bus route names and times headlineLarge: GoogleFonts.roboto( fontSize: 32, fontWeight: FontWeight.w500, color: textHighEmphasis, ), headlineMedium: GoogleFonts.roboto( fontSize: 28, fontWeight: FontWeight.w500, color: textHighEmphasis, ), headlineSmall: GoogleFonts.roboto( fontSize: 24, fontWeight: FontWeight.w500, color: textHighEmphasis, ), // Title styles - Roboto for section headers titleLarge: GoogleFonts.roboto( fontSize: 22, fontWeight: FontWeight.w500, color: textHighEmphasis, letterSpacing: 0, ), titleMedium: GoogleFonts.roboto( fontSize: 16, fontWeight: FontWeight.w500, color: textHighEmphasis, letterSpacing: 0.15, ), titleSmall: GoogleFonts.roboto( fontSize: 14, fontWeight: FontWeight.w500, color: textHighEmphasis, letterSpacing: 0.1, ), // Body styles - Open Sans for extended reading bodyLarge: GoogleFonts.openSans( fontSize: 16, fontWeight: FontWeight.w400, color: textHighEmphasis, letterSpacing: 0.5, ), bodyMedium: GoogleFonts.openSans( fontSize: 14, fontWeight: FontWeight.w400, color: textHighEmphasis, letterSpacing: 0.25, ), bodySmall: GoogleFonts.openSans( fontSize: 12, fontWeight: FontWeight.w400, color: textMediumEmphasis, letterSpacing: 0.4, ), // Label styles - Inter for captions and small text labelLarge: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w500, color: textHighEmphasis, letterSpacing: 0.1, ), labelMedium: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w500, color: textMediumEmphasis, letterSpacing: 0.5, ), labelSmall: GoogleFonts.inter( fontSize: 11, fontWeight: FontWeight.w400, color: textDisabled, letterSpacing: 0.5, ), ); } }