Files
doormile_milderapp/lib/views/helpers/constants/design_constants.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

197 lines
10 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/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)),
];
/// A surface that floats **over** the list rather than in it: the selection
/// bar, a snackbar. Wider and deeper than [shadowGlass] because it has to
/// read as being on a different plane from the cards it covers — but still
/// slate-tinted and still soft, because a hard drop shadow under a white bar
/// reads as a grey stripe, which is exactly what it looked like.
static const List<BoxShadow> shadowFloat = [
BoxShadow(color: Color(0x1F0F172A), blurRadius: 28, offset: Offset(0, 12)),
BoxShadow(color: Color(0x140F172A), blurRadius: 4, offset: Offset(0, 2)),
];
/// ── What holds a borderless card off the page ──
///
/// Two shadows, not one, and that is the whole trick: a wide soft pool gives
/// the card its lift, and a tight near-opaque line under the bottom edge
/// gives it a *defined* edge — without it a single blurred shadow reads as a
/// smudge and the card looks unfinished rather than raised.
///
/// Tinted with the page's slate rather than pure black: a neutral-black
/// shadow on a blue-grey ground goes muddy, which is what makes a card look
/// cheap next to the same card in a design file.
static const List<BoxShadow> shadowGlass = [
BoxShadow(color: Color(0x0F0F172A), blurRadius: 18, offset: Offset(0, 6)),
BoxShadow(color: Color(0x0A0F172A), blurRadius: 2, offset: Offset(0, 1)),
];
}
/// ─────────────────────────────────────────────────────────────────────────
/// 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;