import 'package:flutter/cupertino.dart' show CupertinoPageTransitionsBuilder; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:miler/views/helpers/constants/Colorconstants.dart'; import 'package:miler/views/helpers/constants/design_constants.dart'; import 'package:miler/views/helpers/constants/Font_constant.dart'; /// Centralized app theme (single source of truth for colors, type, and /// component styling). Screens should lean on this instead of re-specifying /// `ColorConstants.*` + `FontConstants.fontFamily` on every widget. /// /// Everything below maps to the existing red/maroon brand via [ColorConstants] /// — no colors changed, just consolidated and extended with component themes /// (buttons, inputs, snackbars, sheets, dividers) so inline styling can be /// retired incrementally. class AppTheme { AppTheme._(); static const Color _primary = ColorConstants.primary; /// The app's one theme. static ThemeData get light => _build(); static ThemeData _build() => ThemeData( useMaterial3: true, brightness: Brightness.light, // The floor for page motion. Named routes and any route pushed without // going through `openScreen`/`openSheet` still slide horizontally with // the parallax and edge-swipe-back, instead of Android's default // fade-through zoom — which, on the mid-range phones riders carry, // reads as the new screen simply appearing. pageTransitionsTheme: const PageTransitionsTheme( builders: { TargetPlatform.android: CupertinoPageTransitionsBuilder(), TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), }, ), fontFamily: FontConstants.fontFamily, fontFamilyFallback: const ['Roboto', 'sans-serif'], textTheme: ThemeData.light().textTheme.apply( fontFamily: FontConstants.fontFamily, ), primaryTextTheme: ThemeData.light().primaryTextTheme.apply( fontFamily: FontConstants.fontFamily, ), colorScheme: const ColorScheme( brightness: Brightness.light, primary: ColorConstants.primary, onPrimary: ColorConstants.onPrimary, primaryContainer: ColorConstants.primaryContainer, onPrimaryContainer: ColorConstants.onPrimaryContainer, secondary: ColorConstants.secondary, onSecondary: ColorConstants.onSecondary, secondaryContainer: ColorConstants.secondaryContainer, onSecondaryContainer: ColorConstants.onSurface, tertiary: ColorConstants.tertiary, onTertiary: ColorConstants.onTertiary, tertiaryContainer: ColorConstants.tertiaryContainer, onTertiaryContainer: ColorConstants.onPrimary, error: ColorConstants.error, onError: ColorConstants.onError, errorContainer: ColorConstants.errorContainer, onErrorContainer: ColorConstants.onSurface, surface: ColorConstants.surface, onSurface: ColorConstants.onSurface, surfaceContainerHighest: ColorConstants.surfaceContainerHighest, outline: ColorConstants.outline, outlineVariant: ColorConstants.outlineVariant, ), appBarTheme: const AppBarTheme( backgroundColor: ColorConstants.pureSurface, elevation: 0, foregroundColor: ColorConstants.onSurface, systemOverlayStyle: SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarIconBrightness: Brightness.dark, statusBarBrightness: Brightness.light, ), ), scaffoldBackgroundColor: ColorConstants.surface, progressIndicatorTheme: const ProgressIndicatorThemeData( color: _primary, linearTrackColor: ColorConstants.surfaceContainerHigh, ), dividerTheme: const DividerThemeData( color: ColorConstants.dividerColor, thickness: 1, space: 1, ), cardTheme: CardThemeData( color: Colors.white, surfaceTintColor: Colors.transparent, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(DesignConstants.radiusXl), side: const BorderSide(color: ColorConstants.outlineVariant, width: 1), ), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: _primary, foregroundColor: Colors.white, disabledBackgroundColor: _primary.withValues(alpha: 0.35), disabledForegroundColor: Colors.white.withValues(alpha: 0.9), elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 18), minimumSize: const Size(double.infinity, ButtonSizes.primary), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(ButtonSizes.radius), ), textStyle: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, fontFamily: FontConstants.fontFamily, ), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: _primary, side: const BorderSide(color: _primary, width: 1.4), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), // ── Why this line exists ── // // `ElevatedButton` above pins its height to 56. `OutlinedButton` // used to pin nothing, so it fell back to Material's default 40dp // floor and sized itself from its own padding instead. That is // invisible until the two appear together — which is exactly where // they always appear, as the Cancel/Confirm pair of a dialog. Each // of those rows overrode padding on both buttons and assumed that // settled the height; it did not, because `styleFrom` only replaces // the keys you hand it. Cancel came out 44–49 and Confirm came out // 56, side by side, in three separate dialogs. // // Matching the height here means a plain `OutlinedButton` and a // plain `ElevatedButton` agree by default, and nobody has to know // about the merge order to get a straight row. // // Deliberately `64` wide and not `double.infinity`: outlined // buttons are the ones that share a row or hug their label, so // forcing full width would be wrong more often than right. A // button that must be shorter than 56 has to say so explicitly // with `minimumSize: Size.zero` — see [SecondaryButton]. minimumSize: const Size(64, ButtonSizes.primary), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(ButtonSizes.radius), ), textStyle: const TextStyle( fontSize: 15, fontWeight: FontWeight.w700, fontFamily: FontConstants.fontFamily, ), ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( foregroundColor: _primary, textStyle: const TextStyle( fontWeight: FontWeight.w600, fontFamily: FontConstants.fontFamily, ), ), ), snackBarTheme: SnackBarThemeData( behavior: SnackBarBehavior.floating, backgroundColor: ColorConstants.onSurface, contentTextStyle: const TextStyle( color: Colors.white, fontFamily: FontConstants.fontFamily, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(DesignConstants.radiusLg), ), ), bottomSheetTheme: BottomSheetThemeData( backgroundColor: ColorConstants.pureSurface, surfaceTintColor: Colors.transparent, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), ), inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: ColorConstants.pureSurface, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), border: OutlineInputBorder( borderRadius: BorderRadius.circular(DesignConstants.radiusXl), borderSide: const BorderSide(color: ColorConstants.outlineVariant), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(DesignConstants.radiusXl), borderSide: const BorderSide(color: ColorConstants.outlineVariant), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(DesignConstants.radiusXl), borderSide: const BorderSide(color: _primary, width: 2), ), labelStyle: const TextStyle( color: ColorConstants.onSurfaceVariant, fontFamily: FontConstants.fontFamily, fontSize: 14, fontWeight: FontWeight.w500, ), ), ); }