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

151 lines
6.5 KiB
Dart

import 'package:flutter/widgets.dart';
import 'package:miler/views/helpers/constants/Colorconstants.dart';
import 'package:miler/views/helpers/constants/design_constants.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// FOUR LAYERS, AND WHAT EACH ONE MEANS
///
/// The app had **five** near-identical light surfaces — `pureSurface`,
/// `surface`, `neutralLight`, `cardSurface`, `daylightSurface` — and measured
/// against the page ground, three of them were invisible:
///
/// ```
/// neutralLight #F2F4F8 1.021 : 1 ← indistinguishable
/// cardSurface #EDEFF4 1.023 : 1 ← indistinguishable
/// surface #FCF9F8 1.073 : 1 ← and warm, unlike everything else
/// pureSurface #FFFFFF 1.124 : 1 ← the one that mattered, and too close
/// ```
///
/// Five tokens doing the work of two is why the app looked flat *and* why
/// individual screens grew their own borders and shadows: a surface that will
/// not separate on its own has to be outlined, and once one screen outlines
/// something every screen does, in its own way.
///
/// The fix was not more containers. It was giving the ground somewhere to be:
/// the canvas moved down to #DEE3EA, white now separates at 1.290:1, and the
/// layers below are named for **what a surface means** rather than for what
/// colour it happens to be.
///
/// ── The ladder ──
///
/// ```
/// 0 canvas the page itself. Never white.
/// 1 working a region the rider reads or works in. White, no border.
/// 2 raised something that acts: a command bar, a live card. Shadow.
/// 3 floating over everything, with a scrim: sheets, dialogs.
/// ```
///
/// A container is justified when it is one of these layers. It is not
/// justified because the content inside it is a group — grouping is done by
/// whitespace, type and the timeline, which is why a route reads as a route
/// without a box around each stop.
/// ─────────────────────────────────────────────────────────────────────────
abstract final class MilerSurface {
/// **Layer 0.** The page. Every scaffold in the app sits on this.
///
/// Deliberately not white: a canvas that is white leaves nothing for a white
/// surface to be distinguishable *from*, which is the state this app was in.
static const Color canvas = ColorConstants.daylightSurface;
/// **Layer 1.** A region the rider reads or works in — a sheet's body, a
/// details page, a grouped operational block.
///
/// No border. At 1.290:1 against [canvas] it separates on its own, and an
/// outline on top of a step that already works is a line to read past.
static const Color working = ColorConstants.pureSurface;
/// **Layer 2.** Something that *acts*: the floating command bar, the live
/// card, a control that owns the bottom of the screen.
///
/// Same fill as [working] — the difference is [raisedShadow], because what
/// separates an action surface from a reading surface is that it appears to
/// sit above the page rather than in it.
static const Color raised = ColorConstants.pureSurface;
/// **Layer 3.** Over everything, with a scrim beneath: modal sheets and
/// dialogs. Painted by `milerGlassSheet`; this names the layer.
static const Color floating = ColorConstants.glassCard;
/// A quiet tonal block *inside* layer 1 — a fact grid, a folded group.
///
/// The only nesting the system allows, and only when the block is a
/// different **kind** of thing from what surrounds it. Never for grouping.
static const Color inset = ColorConstants.neutralLight;
/// Layer 2's lift. One shadow, used everywhere something is raised, so
/// "floating" looks the same on every screen.
static const List<BoxShadow> raisedShadow = DesignConstants.shadowSm;
/// Layer 3's lift.
static const List<BoxShadow> floatingShadow = DesignConstants.shadowFloat;
/// The dim behind a layer-3 surface. Separation comes from the scrim first
/// and the shadow second — a sheet that needs a heavy shadow to be legible
/// is a sheet whose scrim is too weak.
static const Color scrim = Color(0x66000000);
/// The gutter a layer-1 panel leaves around itself, so the canvas reads as
/// ground rather than as a hairline. One number, because two panels a few
/// points apart is how a page stops looking composed.
static const double panelGutter = 12;
/// The breathing room *inside* a layer-1 panel.
static const double panelPad = 14;
}
/// ── Layer 1, as a widget ──
///
/// The ladder above says a working region is *white, standing on the canvas,
/// with no border*. Home was the screen that proved why that has to be a
/// widget rather than a paragraph: its sheet was painted with the canvas and
/// its content placed straight onto it, so the whole page came out one flat
/// grey with nothing white anywhere on it. Every rule was followed except the
/// one that does the work — something has to actually stand on the ground.
///
/// No border and no shadow, deliberately. White separates from the canvas at
/// 1.290 : 1, which is above the ~1.25 : 1 where two tones start reading as
/// two surfaces, so an outline here would be a line to read past rather than
/// a line that says anything.
class MilerPanel extends StatelessWidget {
final Widget child;
/// Space below this panel before the next one. The canvas showing through
/// the gap is what makes them read as separate regions.
final double gap;
/// Set false where the child already owns its own horizontal padding — a
/// list whose rows need to bleed to the panel edge, for instance.
final bool padded;
const MilerPanel({
super.key,
required this.child,
this.gap = 10,
this.padded = true,
});
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.fromLTRB(
MilerSurface.panelGutter,
0,
MilerSurface.panelGutter,
gap,
),
padding: padded
? EdgeInsets.symmetric(
horizontal: MilerSurface.panelPad,
vertical: MilerSurface.panelPad,
)
: EdgeInsets.zero,
decoration: BoxDecoration(
color: MilerSurface.working,
borderRadius: BorderRadius.circular(DesignConstants.radiusXl),
),
child: child,
);
}
}