Files
doormile_milderapp/lib/views/helpers/constants/app_theme.dart
Thiru-tenext d7348e253f Miler rider app: surface system, visible design language, backend lifecycle
Design system
- MilerSurface ladder (canvas → working → raised → floating) with MilerPanel
  as layer 1; canvas moved to #DEE3EA so white separates at 1.290:1.
- Visible vocabulary applied across Home, Deliveries, Activity, Account and
  the sheets: hero heads (tabular numeral + small caption, clamped at 1.3x),
  canvas wells for anything that opens, small filled tags for shelf labels,
  demoted placeholders. Recorded in DESIGN_SYSTEM.md §6.
- One icon family: 222 Material glyphs migrated to Lucide; none left outside
  lib/xpress.
- Colour semantics corrected: amber only for what is genuinely owed, brand red
  reserved for the live stop, disabled primaries go neutral rather than pale.

Data and lifecycle
- lib/data/lifecycle.dart reads mutations for what they prove; route_order.dart
  makes admin sequence the single ordering authority; service_day.dart, and
  stop_area.dart rewritten against live Coimbatore addresses (digit-token
  stripping, city stoplist, street suffixes, stammer collapse).
- countLabel states the load once, in bags.

Testing
- 1440 tests passing; golden shot harnesses for Home, Deliveries, Activity,
  sheets and verify, with test/failures/ now gitignored (diff debris).
- New pins: home_gutter_test, stop_area_test, plus updated structural bounds.

Note: this commit also carries pre-existing working-tree deletions that were
present before this work (API_SPEC.md, README.md, demo test fixtures).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-22 05:40:35 +05:30

213 lines
8.9 KiB
Dart
Raw 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,
// Manrope first: it is bundled, so its coverage is guaranteed on every
// device, and it is what backs Poppins up for anything Poppins does not
// carry. The platform families behind it are the last resort. Poppins is
// missing U+2192 among others — a glyph the app no longer renders as a
// character, but the net belongs here rather than in a rule nobody
// remembers.
fontFamilyFallback: const ['Manrope', '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.w600,
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.w600,
fontFamily: FontConstants.fontFamily,
),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: _primary,
textStyle: const TextStyle(
fontWeight: FontWeight.w500,
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,
),
),
);
}