Files
2026-08-11 13:16:33 +05:30

207 lines
8.5 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
),
),
);
}