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>
151 lines
6.5 KiB
Dart
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,
|
|
);
|
|
}
|
|
}
|