first commit

This commit is contained in:
2026-08-11 13:16:33 +05:30
parent 18531bf46b
commit c8350563d9
388 changed files with 69656 additions and 0 deletions

View File

@@ -0,0 +1,271 @@
import 'package:flutter/material.dart';
/// Every colour in the app.
class ColorConstants {
// --- Rider Operations System Design System ---
/// The page itself. Every other surface is measured against this.
static const Color surface = Color(0xFFFCF9F8);
static const Color surfaceDim = Color(0xFFDCD9D9);
static const Color surfaceBright = Color(0xFFFCF9F8);
/// The lightest container — a white card.
static const Color surfaceContainerLowest = Color(0xFFFFFFFF);
static const Color surfaceContainerLow = Color(0xFFF6F3F2);
static const Color surfaceContainer = Color(0xFFF0EDED);
static const Color surfaceContainerHigh = Color(0xFFEAE7E7);
static const Color surfaceContainerHighest = Color(0xFFE4E2E1);
static const Color onSurface = Color(0xFF1B1C1C);
/// Body text on a surface. **Was #5C403E — a brown**, the M3 tonal derivation
/// of the red brand seed, on 56 call sites and in the app's `TextTheme`. Its
/// contrast was fine (9.30:1); it simply was not the same app as the slate
/// dashboard. Now a slate of matched luminance (9.53:1), so nothing gets
/// lighter and nothing is brown.
static const Color onSurfaceVariant = Color(0xFF3D4654);
static const Color inverseSurface = Color(0xFF303030);
static const Color inverseOnSurface = Color(0xFFFCF9F8);
static const Color outline = Color(0xFF906F6D);
/// Default outline. **Was #E5BDBB — a pink**, again derived from the red seed,
/// and it fed `ColorScheme.outlineVariant` plus every `OutlinedButton` side and
/// `TextField` border in [AppTheme]. So every input in the app had a pink edge.
/// Now the same slate hairline the dashboard uses for container edges.
static const Color outlineVariant = Color(0xFFCBD5E1);
static const Color surfaceTint = Color(0xFF960019);
// Primary Red
static const Color primary = Color(0xFF960019);
static const Color onPrimary = Color(0xFFFFFFFF);
static const Color primaryContainer = Color(0xFFC01227);
static const Color onPrimaryContainer = Color(0xFFFFD1CE);
static const Color inversePrimary = Color(0xFFFFB3AF);
// Secondary Dark
static const Color secondary = Color(0xFF5F5E5E);
static const Color onSecondary = Color(0xFFFFFFFF);
static const Color secondaryContainer = Color(0xFFE2DFDE);
static const Color onSecondaryContainer = Color(0xFF636262);
// Tertiary (Info Blue)
static const Color tertiary = Color(0xFF004E6F);
static const Color onTertiary = Color(0xFFFFFFFF);
static const Color tertiaryContainer = Color(0xFF006791);
static const Color onTertiaryContainer = Color(0xFFB8E1FF);
// Error
static const Color error = Color(0xFFBA1A1A);
static const Color onError = Color(0xFFFFFFFF);
static const Color errorContainer = Color(0xFFFFDAD6);
static const Color onErrorContainer = Color(0xFF93000A);
// Success Green
static const Color success = Color(0xFF006A46);
static const Color onSuccess = Color(0xFFFFFFFF);
static const Color successContainer = Color(0xFF008659);
/// The "go" green: Accept, accept-all, slide-to-duty, and the tick that marks
/// a finished stop.
///
/// **Was #12B76A, which failed WCAG outright as a button fill.** White text on
/// it measured **2.62:1** — under even the 3:1 floor for large text and
/// graphics — which means the label on the single most-pressed button in the
/// app washed out in exactly the daylight this app is used in.
///
/// Now 5.48:1 behind white, and ≥4.5:1 on all four surfaces the app draws on.
static const Color acceptGreen = Color(0xFF047857);
/// Kept as a name for the cash figure, now that [acceptGreen] is dark enough
/// to be read as text. Same value; the two greens collapsed into one.
static const Color moneyGreen = acceptGreen;
/// Reward points — the app's third currency, after cash ([moneyGreen]) and
/// completed work ([acceptGreen]).
///
/// Deliberately *not* [warning]: amber means "attention, something is
/// unfinished" everywhere else in this app, and a points balance is good news.
static const Color rewardAmber = Color(0xFF92400E);
// Warning Amber
//
// #B45309 → #AE5008. A three-percent darkening, for one reason: it cleared
// 4.5:1 on white (5.02) and on the page (4.79) but landed at **4.36:1 on
// `cardSurface`** — and the card fill is where warnings actually appear, on
// the skip sheet and the short-payment notice. A token that passes on the
// surface nobody uses it on has not passed.
static const Color warning = Color(0xFFAE5008);
static const Color onWarning = Color(0xFFFFFFFF);
static const Color warningContainer = Color(0xFFFEF3C7);
// Extended Color Palette for Professional UI
/// Captions, labels and secondary body text — the second most-used colour in
/// the app after [slateText], on 89 call sites across every screen.
///
/// **Was #8E8E93, which fails WCAG AA.** It measured 3.26:1 on white, against
/// a 4.5:1 floor — so on a phone clamped to a handlebar in daylight, every
/// caption in the app was the one thing a rider could not read. It is now
/// 6.00:1 on white.
static const Color secondaryText = Color(0xFF5A6472);
/// ── A fill, never a text colour ──
///
/// #C7C7CC measures **1.68:1 on white** — below even the 3:1 floor for large
/// text. It was being used for readable captions and hints on seven screens,
/// including the helper line under the phone field on the first screen a new
/// rider ever sees.
///
/// Renamed from `disabledText` so the name cannot invite the mistake again.
/// A genuinely disabled *control* may be filled with this; the words on it
/// use [secondaryText].
static const Color disabledFill = Color(0xFFC7C7CC);
static const Color dividerColor = Color(0xFFE9EBF0);
static const Color upiPurple = Color(0xFF7C3AED);
static const Color successLight = Color(0xFFF0FFF4);
static const Color errorLight = Color(0xFFFFF0F0);
static const Color neutralLight = Color(0xFFF2F4F8);
/// Reject, and inline validation errors.
static const Color errorRed = Color(0xFFB91C1C);
// ── Five tokens retired here ──
//
// `successGreen` #34C759 (2.22:1), `infoBlue` #007AFF (4.02:1),
// `warningOrange` #FF9500 (2.20:1), `cardBlue` #0EA5E9 (2.77:1) and
// `warningYellow` #FFB800 (1.73:1) were a leftover iOS-derived set living
// alongside the tuned palette. Every one of them failed WCAG AA as text, and
// `warningOrange` was carrying the "you skipped this, this is the retry"
// notice *as text* — the one class of message a rider must not miss was the
// one that disappeared in sunlight.
//
// They are gone rather than fixed, because the tuned equivalents already
// exist and already pass: [warning] 5.02:1, [acceptGreen] 5.48:1,
// [tertiary], [deliveryAccent] 5.17:1. Two palettes for one set of meanings
// was the actual defect.
// ── Stop-type accents ────────────────────────────────────────────────
// One accent per operational mode so a rider never confuses what he is
// supposed to DO at a stop. These are the single source — `stop_type.dart`
// reads them, nothing else should hardcode these hex values.
static const Color pickupAccent = primary;
// #2563EB → #2462E8, for the same reason as [warning]: 4.49:1 on
// `cardSurface`, a hair under the floor, on the accent that names every
// delivery leg in the app.
static const Color deliveryAccent = Color(0xFF2462E8);
static const Color combinedAccent = Color(0xFF5B21B6);
// ── Stop-type CHIP fills ─────────────────────────────────────────────
// Darkened siblings of the accents above, for the small filled tag that names
// a stop's mode. A tag is a mark, not a surface.
static const Color pickupChip = Color(0xFF7F0014);
static const Color deliveryChip = Color(0xFF1E40AF);
static const Color combinedChip = Color(0xFF4C1D95);
/// Text drawn *on* a chip fill.
static const Color onChip = Color(0xFFFFFFFF);
/// ── Borderless card fill ─────────────────────────────────────────────
///
/// For cards that carry no border and no shadow, and rely on a fill to read as
/// a distinct surface — the pattern most large apps use for list cards.
///
/// Measured at a 9.4% relative-luminance drop from the page: riders reported
/// being unable to tell cards apart when a card sat ~3% from its background,
/// and with no border and no shadow the fill carries that alone, outdoors.
static const Color cardSurface = Color(0xFFEDEFF4);
/// ── The white a surface is actually made of ─────────────────────────
///
/// The panel *inside* a card — the stat strip on an Activity row, the fact
/// tiles on the verification review.
static const Color pureSurface = Color(0xFFFFFFFF);
/// ── The neutral action colour ──
///
/// The colour Uber uses for anything that is *chrome* rather than brand: the
/// selected tab, a switch that is on, the fill behind a chosen option.
///
/// Those were all the brand red at 10–14% alpha, which reads as a faint blush
/// and is not a brand moment. The red is still the red where it means
/// something: the primary button, the stop-type chips, a destructive action.
///
/// The rule Uber follows, and this now does: colour marks meaning, neutral
/// marks state.
static const Color emphasis = Color(0xFF0F172A);
/// The wash behind a selected row or tab — [emphasis] at the weight that
/// reads on the page without becoming a surface of its own.
static const Color emphasisWash = Color(0x140F172A);
/// The same wash in the brand hue, for the one place the selected state is
/// deliberately branded rather than neutral: the bottom nav's travelling
/// pill. Kept at [emphasisWash]'s exact 8% so swapping the hue cannot also
/// change how heavy the pill reads.
static const Color primaryWash = Color(0x14960019);
/// Text and icons drawn on a filled accent — brand red, accept green, a dark
/// snackbar.
static const Color onAccent = Color(0xFFFFFFFF);
/// ── The frosted brand wash on every app bar ───────────────────────────
///
/// A translucent maroon laid over the page rather than a solid slab of it.
/// Painted behind a blur (see `milerGlassSurface`), so what shows through is
/// the page's own colour softened and tinted.
///
/// Deliberately weak. At 7% the surface stays light enough for near-black
/// type, which is what keeps a title readable through a windscreen mount in
/// sun.
static const Color glassRed = Color(0x12960019);
/// ── The fill on a frosted sheet ──
///
/// A sheet over a map has something worth seeing underneath it, and a solid
/// slab throws all of it away. Frosted, the map stays legible as context while
/// the sheet stays legible as content.
///
/// Deliberately high at 88%. This is not decoration: the text over it is an
/// address and an ETA, read one-handed at a doorstep in daylight, and every
/// point of transparency spent on the effect is contrast taken off the words.
static const Color glassSheet = Color(0xE0FDFDFE);
/// The specular rim along a glass edge — what makes frost read as a pane
/// rather than as a translucent rectangle.
static const Color glassRim = Color(0x8CFFFFFF);
// ── Daylight surfaces ────────────────────────────────────────────────
// Tuned for a phone clamped to a handlebar in direct sun. Slate-tinted grey
// separates from white far more strongly outdoors than the old warm surface.
static const Color daylightSurface = Color(0xFFF8FAFC);
/// The app's near-black body colour.
static const Color slateText = Color(0xFF0F172A);
/// Card/section borders. `borderStrong` is the default for anything that
/// must read as a distinct container in sunlight; `borderSubtle` is only
/// for internal dividers inside an already-bordered card.
static const Color borderStrong = Color(0xFFCBD5E1);
static const Color borderSubtle = Color(0xFFE2E8F0);
// Task categories
static Color parcelColor = Colors.amber[100]!;
static Color groceriesColor = Colors.purple[100]!;
static Color shoppingColor = Colors.orange[100]!;
static Color healthColor = Colors.cyan[100]!;
static Color handymanColor = Colors.red[100]!;
}
extension ColorExtension on String {
Color? toColor() {
var hexColor = replaceAll("#", "");
if (hexColor.length == 6) {
hexColor = "FF$hexColor";
}
if (hexColor.length == 8) {
return Color(int.parse("0x$hexColor"));
}
return null;
}
}

View File

@@ -0,0 +1,136 @@
import 'package:flutter/material.dart';
class FontConstants {
// Miler Design System: Manrope typography (variable font) — a free,
// OFL-licensed geometric sans chosen as the closest legal stand-in for
// Uber Move (which is proprietary and cannot be bundled).
static const String fontFamily = 'Manrope';
static String fontFamilyFallback = 'PlusJakartaSans';
// Typography scale
static const double displayLg = 32.0;
static const double headlineMd = 24.0;
static const double headlineSm = 20.0;
static const double bodyLg = 18.0;
static const double bodyMd = 16.0;
static const double labelBold = 14.0;
static const double labelSm = 12.0;
static TextStyle displayLgStyle = const TextStyle(
fontSize: 32,
fontWeight: FontWeight.w700,
letterSpacing: -0.64,
fontFamily: fontFamily,
);
static TextStyle headlineMdStyle = const TextStyle(
fontSize: 24,
fontWeight: FontWeight.w700,
letterSpacing: -0.24,
fontFamily: fontFamily,
);
static TextStyle headlineSmStyle = const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
letterSpacing: -0.2,
fontFamily: fontFamily,
);
static TextStyle bodyLgStyle = const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
);
static TextStyle bodyMdStyle = const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
fontFamily: fontFamily,
);
static TextStyle labelBoldStyle = const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
fontFamily: fontFamily,
);
static TextStyle labelSmStyle = const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
);
static double getResponsiveFontSize(BuildContext context, double baseSize) {
return baseSize;
}
static double extraSmall(BuildContext context) =>
getResponsiveFontSize(context, 10);
static double small(BuildContext context) =>
getResponsiveFontSize(context, labelSm);
static double medium(BuildContext context) =>
getResponsiveFontSize(context, bodyMd);
static double regular(BuildContext context) =>
getResponsiveFontSize(context, bodyLg);
static double large(BuildContext context) =>
getResponsiveFontSize(context, headlineSm);
static double xLarge(BuildContext context) =>
getResponsiveFontSize(context, headlineMd);
static double xxLarge(BuildContext context) =>
getResponsiveFontSize(context, 28);
static double xxxLarge(BuildContext context) =>
getResponsiveFontSize(context, 30);
static double huge(BuildContext context) =>
getResponsiveFontSize(context, displayLg);
}
class ReusableTextWidget extends StatelessWidget {
final String text;
final double? fontSize;
final double? textHeight;
final String? fontFamily;
final FontWeight? fontWeight;
final FontStyle? fontStyle;
final Color? color;
final TextAlign? textAlign;
final int? maxLines;
final TextDecoration? isUnderText;
const ReusableTextWidget({
super.key,
required this.text,
this.fontSize,
this.textHeight,
this.fontFamily,
this.fontWeight,
this.fontStyle,
this.color,
this.textAlign,
this.maxLines,
this.isUnderText,
});
@override
Widget build(BuildContext context) {
return Text(
text,
softWrap: true,
style: TextStyle(
fontSize: fontSize ?? FontConstants.medium(context),
decoration: isUnderText,
fontFamily: fontFamily ?? FontConstants.fontFamily,
decorationColor: color,
fontWeight: fontWeight ?? FontWeight.normal,
fontStyle: fontStyle ?? FontStyle.normal,
color: color ?? Colors.grey.shade900,
overflow: TextOverflow.ellipsis,
decorationStyle: TextDecorationStyle.solid,
decorationThickness: 1,
height: textHeight,
),
maxLines: maxLines,
textAlign: textAlign ?? TextAlign.start,
);
}
}

View File

@@ -0,0 +1,90 @@
class ApiConstants {
static String mainDev = "dev";
static String mainRoute = "live";
// ── There is no maps key any more ──
//
// A `googleMapsApiKey` used to live here, feeding `flutter_polyline_points`
// and both native projects. It was revoked, so every tile request and every
// Directions call came back `REQUEST_DENIED` and the app looked broken rather
// than unconfigured.
//
// The maps no longer need one: tiles come from OpenStreetMap and routes from
// OSRM, both keyless. See `lib/views/helpers/widgets/miler_map.dart` and
// `lib/helpers/miler_router.dart` — including the note on what those free
// services are and are not suitable for.
//Collection Queue - v2
static String pickupQueueDev =
"https://jupiter.doormile.app/$mainDev/api/v2/pickups/getpickupqueues";
static String pickupQueueLive =
"https://jupiter.doormile.app/$mainRoute/api/v2/pickups/getpickupqueues";
//Current Collection - v1
static String currentPickupDev =
"https://jupiter.doormile.app/$mainDev/api/v1/pickups/getpickups";
static String currentPickupLive =
"https://jupiter.doormile.app/$mainRoute/api/v1/pickups/getpickups";
//Current Collection V3 - v3 (date-bounded)
static String currentPickupV3Dev =
"https://jupiter.doormile.app/$mainDev/api/v3/pickups/getpickups";
static String currentPickupV3Live =
"https://jupiter.doormile.app/$mainRoute/api/v3/pickups/getpickups";
//Update Collection - v1
static String updatePickupDev =
"https://queue.workolik.com/live/api/v1/pickups/updatepickup";
static String updatePickupLive =
"https://queue.workolik.com/live/api/v1/pickups/updatepickup";
//Get Rider Log - v1
static String getRiderLogDev =
"https://jupiter.doormile.app/$mainDev/api/v1/partners/getriderlog";
static String getRiderLogLive =
"https://jupiter.doormile.app/$mainRoute/api/v1/partners/getriderlog";
//Create Rider Log - v2
static String createRiderLogDev =
"https://queue.workolik.com/live/api/v2/partners/createriderlog";
static String createRiderLogLive =
"https://queue.workolik.com/live/api/v2/partners/createriderlog";
//Update Rider Log - v1
static String updateRiderLogDev =
"https://jupiter.doormile.app/$mainDev/api/v1/partners/updateriderlog";
static String updateRiderLogLive =
"https://jupiter.doormile.app/$mainRoute/api/v1/partners/updateriderlog";
//Get Rider Count - v1
static String getRiderCountDev =
"https://jupiter.doormile.app/$mainDev/api/v1/partners/getridercount";
static String getRiderCountLive =
"https://jupiter.doormile.app/$mainRoute/api/v1/partners/getridercount";
//Create Break Rider Log - v2
static String createBreakRiderLogDev =
"https://queue.workolik.com/live/api/v2/partners/createbreaklog";
static String createBreakRiderLogLive =
"https://queue.workolik.com/live/api/v2/partners/createbreaklog";
//Update Break Rider Log - v2
static String updateBreakRiderLogDev =
"https://queue.workolik.com/live/api/v2/partners/updatebreaklog";
static String updateBreakRiderLogLive =
"https://queue.workolik.com/live/api/v2/partners/updatebreaklog";
//Create Collection Log - v2
static String createPickupLogDev =
"https://queue.workolik.com/live/api/v2/pickups/createpickuplog";
static String createPickupLogLive =
"https://queue.workolik.com/live/api/v2/pickups/createpickuplog";
//Summary API - v2
static String summaryApiLive =
'https://jupiter.doormile.app/$mainRoute/api/v2/partners';
//Summary Rider Weekly KMs - v1
static String summaryriderkmLive =
'https://jupiter.doormile.app/$mainRoute/api/v1/partners';
}

View File

@@ -0,0 +1,206 @@
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,
),
),
);
}

View File

@@ -0,0 +1,171 @@
import 'package:flutter/material.dart';
/// Miler Professional Design System Constants
/// Centralized design tokens for spacing, radius, shadows, and layout
/// ─────────────────────────────────────────────────────────────────────────
/// SHARED RADII AND ELEVATION
///
/// ── Why this used to be much bigger ──
///
/// This class shipped 43 members: a nine-step spacing scale, eleven `EdgeInsets`
/// presets, six radii, five shadows, a `cardDecoration` helper and three named
/// border radii. An audit found **six of them used, 17 times in total.** The
/// spacing scale had never been referenced once; every screen writes `16.w` or
/// `12.h` inline, which is both more readable at the call site and correct for
/// this app, because those need ScreenUtil's per-device scaling and a `const
/// double` cannot have it.
///
/// The 37 dead members were not harmless. A token nobody uses still has to be
/// read, still looks authoritative, and is the reason someone reaches for
/// `Colors.grey.shade100` instead — a design system that is mostly aspirational
/// teaches people to ignore it. So the unused members are gone and what is left
/// is only what the app actually reaches for.
///
/// The audit metric that flagged this file was itself misleading, which is worth
/// recording: 13 files import `design_constants.dart` and only 5 mention
/// `DesignConstants`. The other 8 import it for [ButtonSizes], which lives in
/// this same file and is used 72 times. Colocating a thriving class with a dead
/// one hid both facts.
///
/// **Adding to this class:** don't, until a second call site needs the value.
/// Two uses make a token; one use is a local constant.
/// ─────────────────────────────────────────────────────────────────────────
class DesignConstants {
DesignConstants._();
// ── Motion ────────────────────────────────────────────────────────────
//
// The app was using 35 distinct durations. No shared vocabulary means every
// transition is a fresh guess, and some of the guesses sat directly in the
// rider's path between two stops — an 800ms entrance and several 500ms
// cross-fades on screens he passes through 40 times a shift.
//
// Motion on a driver app is a tax paid all day. It earns its place only when
// it explains a state change; anything else is time spent standing still.
/// Press and release. Fast enough to feel like the surface responded rather
/// than animated.
static const Duration motionPress = Duration(milliseconds: 120);
/// A state change in place: a chip selecting, a tick landing, a row opening.
static const Duration motionState = Duration(milliseconds: 200);
/// A page or a sheet arriving. The ceiling for anything on the critical path.
static const Duration motionPage = Duration(milliseconds: 300);
/// Celebration, and only celebration — once per completed route, never on a
/// screen the rider is trying to get through.
static const Duration motionCelebrate = Duration(milliseconds: 600);
// ── Border radius ─────────────────────────────────────────────────────
//
// ── Three, because 26 communicated nothing ──
//
// An audit counted 26 distinct radii in use against the four declared here,
// and found the most common value in the app was 14 — `ButtonSizes.radius`,
// a *button* token, being applied to cards and sheets. Radius is one of the
// strongest cues that two things belong to the same family; at 26 values it
// says nothing, and two elements side by side at 12 and 14 read as a
// rendering bug rather than a decision.
//
// `radius2xl` (20) is retired: it was the third-most-used value and had no
// job the container radius could not do.
/// **Inner** — inputs, chips, small tiles, panels *inside* a container.
static const double radiusLg = 12.0;
/// **Container** — cards, sheets, and anything that is a surface in its own
/// right. Buttons use this too, so a button and the card it sits on agree.
static const double radiusXl = 16.0;
/// **Pill** — fully rounded: pills, circular tracks, avatars.
static const double radiusFull = 999.0;
// ── Elevation ─────────────────────────────────────────────────────────
//
// Only two survive, and both are used sparingly. Most cards in the app are
// now borderless fills with no shadow at all — see
// `ColorConstants.cardSurface` — so elevation is the exception, not the
// default it once was.
/// A card lifted just off the page.
static const List<BoxShadow> shadowSm = [
BoxShadow(color: Color(0x1A000000), blurRadius: 4, offset: Offset(0, 2)),
];
/// Something floating over content: a menu, a dragged element.
static const List<BoxShadow> shadowLg = [
BoxShadow(color: Color(0x2A000000), blurRadius: 12, offset: Offset(0, 8)),
];
}
/// ─────────────────────────────────────────────────────────────────────────
/// BUTTON SIZING — one scale for the whole app.
///
/// Buttons had drifted to a dozen different heights (44, 48, 52, 54, 56, 58)
/// across the pickup flow, Home, auth and the sheets. On a screen the rider
/// uses 30–50 times a day that reads as sloppiness, and worse, it breaks the
/// muscle memory that lets him hit a control without looking: if "the green
/// one at the bottom" is a different size on every screen, he has to aim.
///
/// Three sizes, and only three. Every button in the app must pick one.
/// ─────────────────────────────────────────────────────────────────────────
class ButtonSizes {
ButtonSizes._();
/// **Primary** — the one action a screen exists for. Full-width CTAs:
/// Accept Trip, Start Pickup, Confirm, Navigate.
static const double primary = 56.0;
/// **Secondary** — supporting actions that sit beside or below a primary:
/// Reject, Skip, Details, filter chips with a tap target.
static const double secondary = 48.0;
/// **Compact** — dense rows where several actions share a line, e.g. the
/// per-stop Accept/Reject pair inside a trip card. Still above the 44dp
/// accessibility floor.
static const double compact = 44.0;
/// Square icon-only buttons (call, overflow) match [primary] so they line up
/// with the CTA they sit next to.
static const double icon = 56.0;
/// Corner radius, shared by every button size so they read as one family.
///
/// 14 → 16 so it matches [DesignConstants.radiusXl]. At 14 a button sitting
/// on a 16pt card had a visibly tighter corner than the card holding it,
/// which is the kind of two-point difference nobody can name and everybody
/// sees.
static const double radius = DesignConstants.radiusXl;
/// Absolute floor for any tappable target, per accessibility guidance.
static const double minTapTarget = 44.0;
}
/// ─────────────────────────────────────────────────────────────────────────
/// DESIGN TRIAL — full addresses on stop cards
///
/// Turn on to see every stop card carrying its **complete** address rather than
/// the trimmed one, over [kFullAddressMaxLines] lines instead of two.
///
/// Two separate things are suppressed normally, and both are lifted together
/// here or the trial would not show what it claims to:
///
/// • **Home shortens the text.** `Trip.shortAddress` strips the tail every stop
/// on the route shares — the city, the state, the pincode — because repeating
/// "Coimbatore, Tamil Nadu 641006" on all six stops tells the rider nothing
/// about which one he is looking at. With the trial on, the raw address is
/// used instead. Bookings already shows the raw value.
///
/// • **Both screens cap the lines.** Two, so a card's height does not depend on
/// how verbose a customer was — a list of cards that step unevenly down the
/// screen is harder to scan and fits fewer stops.
///
/// Both caps exist for measured reasons, so this is a switch rather than a
/// deletion. **Set to `false` to restore the shipped behaviour.**
const bool kFullAddressTrial = true;
/// Line cap while [kFullAddressTrial] is on. Four fits a full Indian address at
/// 13sp in a stop card's column without letting one pathological record run the
/// card off the screen.
const int kFullAddressMaxLines = 4;

View File

@@ -0,0 +1,198 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:miler/views/helpers/constants/Colorconstants.dart';
import 'package:miler/views/helpers/constants/Font_constant.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// THE TYPE SCALE
///
/// ── Why this exists ──
///
/// An audit counted **51 distinct font sizes** across the app and **448 sites
/// repeating `fontFamily: FontConstants.fontFamily` by hand**. Sizes in use ran
/// 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15, 16, 16.5, 17,
/// 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 30, 46.
///
/// That is not carelessness. There was simply nothing to look up: every screen
/// had to invent its own typography, so screens drifted the moment they were
/// written and a reviewer had nothing to compare a pull request against. It is
/// why the page title is 23sp on three tabs, 22sp on two flow screens and 21sp
/// on another.
///
/// ── Why not `Theme.of(context).textTheme` ──
///
/// The same reason [ColorConstants] is static: the app reads type on hundreds
/// of call sites that have no `BuildContext` anywhere near them, and `AppTheme`
/// already proves the point — it builds a full `TextTheme` that **nothing in
/// the app reads**. A scale nobody can reach is not a scale.
///
/// So these are plain static getters, in the shape the app already reaches for.
///
/// ── The eight roles ──
///
/// | role | size | where |
/// |---------------|------|-------|
/// | [hero] | 28 | the one thing a screen is about — the next stop's name |
/// | [pageTitle] | 22 | app bar titles, sheet titles |
/// | [cardTitle] | 19 | a stop name in a list, a section heading |
/// | [body] | 16 | addresses, running copy, list-row titles |
/// | [caption] | 15 | supporting copy under a title |
/// | [label] | 13 | metadata, timestamps, chip text |
/// | [micro] | 12.5 | the smallest readable size in the app |
/// | [eyebrow] | 11 | letterspaced uppercase group names |
///
/// [figure] and [buttonLabel] are roles rather than sizes — a figure is a
/// number read at a glance and takes the size the context demands, a button
/// label is fixed by [ButtonSizes].
///
/// ── Rules ──
///
/// * **Same role = same size.** Do not add a size; add a role, and only when a
/// second screen needs it.
/// * **No half points** unless the half point is doing real work. `12.5` earns
/// its place as the floor; `13.5`, `14.5` and `16.5` did not and are gone.
/// * Every style already carries the family, so **no call site should name it**.
/// * Colour is a separate axis. Pass `.on(...)` to recolour, or use the named
/// helpers, so a role never hardcodes a token.
/// ─────────────────────────────────────────────────────────────────────────
class MilerType {
MilerType._();
static const String _family = FontConstants.fontFamily;
/// The one thing a screen is about. One per screen, or none.
static TextStyle get hero => TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.w800,
letterSpacing: -0.9,
height: 1.08,
color: ColorConstants.slateText,
fontFamily: _family,
);
/// App bar titles and sheet headings.
static TextStyle get pageTitle => TextStyle(
fontSize: 22.sp,
fontWeight: FontWeight.w800,
letterSpacing: -0.6,
height: 1.15,
color: ColorConstants.slateText,
fontFamily: _family,
);
/// A stop name in a list, a section heading inside a screen.
static TextStyle get cardTitle => TextStyle(
fontSize: 19.sp,
fontWeight: FontWeight.w800,
letterSpacing: -0.4,
height: 1.2,
color: ColorConstants.slateText,
fontFamily: _family,
);
/// Addresses, running copy, the title line of a compact row.
static TextStyle get body => TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
letterSpacing: -0.2,
height: 1.35,
color: ColorConstants.slateText,
fontFamily: _family,
);
/// Supporting copy under a title. Grey by default — this is the role that
/// explains, and explanation is secondary to the thing being explained.
static TextStyle get caption => TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w500,
height: 1.4,
color: ColorConstants.secondaryText,
fontFamily: _family,
);
/// Metadata, timestamps, chip text, the second line of a row.
static TextStyle get label => TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.w600,
height: 1.35,
color: ColorConstants.secondaryText,
fontFamily: _family,
);
/// The floor. Nothing in this app may be smaller than this and still be
/// expected to be read outdoors.
static TextStyle get micro => TextStyle(
fontSize: 12.5.sp,
fontWeight: FontWeight.w600,
height: 1.3,
color: ColorConstants.secondaryText,
fontFamily: _family,
);
/// Letterspaced uppercase group names. The quietest way to name a group, and
/// the reason most groups in this app need no outline.
static TextStyle get eyebrow => TextStyle(
fontSize: 11.sp,
fontWeight: FontWeight.w800,
letterSpacing: 1.2,
height: 1.3,
color: ColorConstants.secondaryText,
fontFamily: _family,
);
/// [pageTitle] without ScreenUtil.
///
/// App bars are built eagerly as a `PreferredSizeWidget` argument, sometimes
/// before `ScreenUtilInit` has run — and `toolbarHeight` is a fixed logical
/// value anyway, so a title that scaled with the device inside a bar that did
/// not would only find new ways to clip. This is the same role at the same
/// number, resolved without `.sp`.
static const TextStyle pageTitleFixed = TextStyle(
fontSize: 22,
fontWeight: FontWeight.w800,
letterSpacing: -0.6,
height: 1.15,
color: ColorConstants.slateText,
fontFamily: _family,
);
/// A number read at a glance: a cash amount, a distance, a duration.
///
/// Sized by its context rather than fixed — a figure in a stat column is not
/// the same size as one in a hero — so this takes the size and supplies only
/// what makes a figure a figure: heavy, tight, and tabular so a column of
/// them lines up.
static TextStyle figure(double size, {Color? color}) => TextStyle(
fontSize: size.sp,
fontWeight: FontWeight.w800,
letterSpacing: -0.3,
height: 1.1,
fontFeatures: const [FontFeature.tabularFigures()],
color: color ?? ColorConstants.slateText,
fontFamily: _family,
);
/// The label inside a button. Sized against the button's own height, so a
/// compact button does not shout like a primary one.
static TextStyle buttonLabel(double height) => TextStyle(
fontSize: (height >= 56 ? 16 : height >= 48 ? 15 : 14).sp,
fontWeight: FontWeight.w700,
letterSpacing: -0.1,
color: ColorConstants.onAccent,
fontFamily: _family,
);
}
/// Recolour and reweight a role without leaving the scale.
///
/// `MilerType.body.on(ColorConstants.secondaryText)` keeps the size, family,
/// tracking and line height of the role and changes only what is asked for —
/// which is the whole point of having roles.
extension MilerTypeTint on TextStyle {
TextStyle on(Color color) => copyWith(color: color);
TextStyle get bold => copyWith(fontWeight: FontWeight.w800);
TextStyle get semibold => copyWith(fontWeight: FontWeight.w700);
}

View File

@@ -0,0 +1,19 @@
class MqttConstants {
static const String brokerHost = '66.116.225.226'; // Updated with VPS IP
static const int brokerPort = 1883;
static const String username = 'admin';
static const String passwordString = 'Package@321#'; // Provided by user
// Topic Structure
static const String topicRiderStatus = 'doormile/riders/{riderId}/status';
static const String topicRiderProfile = 'doormile/riders/{riderId}/profile';
static const String topicRiderLocation = 'doormile/riders/{riderId}/location';
static const String topicRiderTelemetry =
'doormile/riders/{riderId}/telemetry';
static const String topicRiderLogs = 'doormile/riders/{riderId}/logs';
// Status Values
static const String statusOnline = 'Online';
static const String statusOffline = 'Offline';
static const String statusIdle = 'Idle';
}