production

This commit is contained in:
2026-08-28 11:13:15 +05:30
parent d7348e253f
commit 5723d373b2
162 changed files with 17924 additions and 7026 deletions

View File

@@ -232,57 +232,59 @@ class ColorConstants {
/// snackbar.
static const Color onAccent = Color(0xFFFFFFFF);
/// ── The frosted brand wash on every app bar ───────────────────────────
/// ── A tint, resolved rather than laid over ──
///
/// 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.
/// `accent.withValues(alpha: 0.10)` is how nearly every tinted chip, pill and
/// panel in this app was drawn, and it is a *translucent* surface: what the
/// rider actually sees is the accent composited against whatever happens to
/// be behind it. That is fine on a white page and wrong the moment the same
/// chip appears on a tinted panel, a photo or a map — the same "10% green"
/// comes out four different colours across four screens, which is the drift
/// the surface system exists to stop.
///
/// 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);
/// This resolves the composite once, against the surface the caller names, so
/// what comes back is an ordinary opaque colour. Same appearance where the
/// old code was already over white; predictable everywhere else.
static Color tint(Color accent, double amount, {Color on = pureSurface}) =>
Color.alphaBlend(accent.withValues(alpha: amount), on);
/// ── The fill on a frosted sheet ──
/// ── The fill on a bottom 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);
/// It was 88% white — a frosted pane that let the map read through it. It is
/// opaque now, because every point of transparency spent on the effect was
/// contrast taken off the words, and the words here are an address and an ETA
/// read one-handed at a doorstep in daylight. The map is not lost: the sheet
/// covers 45% of the screen and the other 55% is still the map.
static const Color glassSheet = Color(0xFFFFFFFF);
/// 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);
/// The hairline along a sheet's or a card's edge. It was a specular white
/// highlight that only read as an edge because the surface under it was
/// translucent; on an opaque surface the same job needs a real line.
static const Color glassRim = Color(0xFFE5E7EB);
/// ── The pane a list card is made of ──
/// ── The surface a list card is made of ──
///
/// Every card the rider scrolls — a stop on Home, a job on Deliveries — is
/// this fill with a soft, wide shadow under it ([DesignConstants.shadowGlass])
/// and **no border at all**.
/// this fill, a hairline and a soft, wide shadow.
///
/// Not a solid white: at 92% the page's slate tint comes through just enough
/// that the card reads as a pane laid over the screen rather than a white
/// rectangle cut into it, which is the whole difference between "glass" and
/// "box". It stays high enough for near-black type at any brightness — a
/// rider reads addresses off this surface in direct sun.
/// ── 0xEB → 0xFF ──
///
/// No `BackdropFilter`. A real blur re-samples everything behind it every
/// frame, and a scrolling list of twenty of them is the single most reliable
/// way to drop a mid-range Android phone to 30fps. The blur is reserved for
/// the two surfaces that genuinely sit over live content — the app bar and
/// the bottom sheets ([milerGlassSheet]).
static const Color glassCard = Color(0xEBFFFFFF);
/// It was 92% white, so the page's slate tint came through and the card read
/// as a pane laid *over* the screen. That is a nice effect on a design file
/// and a cost on a handlebar: a translucent card takes its contrast from
/// whatever happens to be behind it, so the same address is crisp over the
/// canvas and grey over a map. Opaque, it reads the same everywhere, which is
/// the only property that matters on a surface a rider takes an address off
/// in direct sun. Depth comes from the hairline and the lift instead.
static const Color glassCard = Color(0xFFFFFFFF);
/// The same pane, warmed by the brand, for the one stop that is under way.
/// The same surface, warmed by the brand, for the one stop that is under way.
///
/// A whisper — 5% — because a card the rider has to *read* is the wrong place
/// for a saturated fill: what marks the live stop is the LIVE chip and the
/// button on it, not a red panel behind its address.
static const Color glassCardLive = Color(0x0D960019);
/// A whisper — the brand at 5%, flattened onto white rather than laid over
/// the card as a translucent wash — because a card the rider has to *read* is
/// the wrong place for a saturated fill: what marks the live stop is the LIVE
/// chip and the button on it, not a red panel behind its address.
static const Color glassCardLive = Color(0xFFFAF2F3);
// ── Daylight surfaces ────────────────────────────────────────────────
// Tuned for a phone clamped to a handlebar in direct sun. Slate-tinted grey

View File

@@ -71,12 +71,27 @@ class DesignConstants {
// `radius2xl` (20) is retired: it was the third-most-used value and had no
// job the container radius could not do.
// ── 12/16 → 14/20 ──
//
// Both steps went up one notch, together, after four screens were rebuilt
// against reference designs and every one of them came back reading harder
// than the picture it was drawn from. The shapes were right and the corners
// were not: at 16 a full-width card on a phone is a rectangle with the edges
// taken off, and the difference between that and something that reads as a
// *soft object* is about four points.
//
// Moved as a pair so the ladder holds — an inner tile has to stay visibly
// tighter than the surface it sits on, or the two corners fight — and moved
// in the tokens rather than at call sites, because a radius that is softer
// on the screens somebody redesigned last is exactly how an app ends up with
// 26 of them again.
/// **Inner** — inputs, chips, small tiles, panels *inside* a container.
static const double radiusLg = 12.0;
static const double radiusLg = 16.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;
static const double radiusXl = 24.0;
/// **Pill** — fully rounded: pills, circular tracks, avatars.
static const double radiusFull = 999.0;

View File

@@ -1,6 +1,7 @@
import 'package:flutter/widgets.dart';
import 'package:miler/views/helpers/constants/Colorconstants.dart';
import 'package:miler/views/helpers/constants/narrative.dart';
import 'package:miler/views/helpers/constants/design_constants.dart';
/// ─────────────────────────────────────────────────────────────────────────
@@ -46,7 +47,15 @@ abstract final class MilerSurface {
///
/// 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;
///
/// ── #DEE3EA → #F8F9FA ──
///
/// It was a heavy slate, chosen when a card was a bare white fill and the
/// tonal step was the *only* thing separating the two. Cards carry a hairline
/// and a lift now (see [MilerPanel]), so the ground no longer has to do that
/// work alone — and a page this dark under bordered cards reads as heavy
/// rather than as layered. The brief's canvas is the value.
static const Color canvas = Narrative.canvas;
/// **Layer 1.** A region the rider reads or works in — a sheet's body, a
/// details page, a grouped operational block.
@@ -88,10 +97,18 @@ abstract final class MilerSurface {
/// 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.
///
/// 12 → 20. The brief keeps a generous margin off the screen's edge so
/// nothing interactive touches it and the panels read as floating; its own
/// figure is 24, and 20 is where that lands once the panel's internal padding
/// (below, also raised) is added to it — 40 of inset before a word on a
/// 390pt phone is as far as this can go and still hold a full address.
static const double panelGutter = 12;
/// The breathing room *inside* a layer-1 panel.
static const double panelPad = 14;
///
/// 14 → 20, the brief's figure for the padding inside a card.
static const double panelPad = 16;
}
/// ── Layer 1, as a widget ──
@@ -140,10 +157,17 @@ class MilerPanel extends StatelessWidget {
vertical: MilerSurface.panelPad,
)
: EdgeInsets.zero,
decoration: BoxDecoration(
color: MilerSurface.working,
borderRadius: BorderRadius.circular(DesignConstants.radiusXl),
),
// ── Level 1, as the Narrative brief draws it ──
//
// It was a bare white fill on a dark canvas: separation bought with a
// 1.29:1 tonal step, no border and no shadow, on the rule that an
// outline over a step that already works is a line to read past.
//
// The brief inverts that trade — a *light* canvas, and cards that carry
// a hairline and a soft lift. Both work; what does not is half of each,
// so the panel takes the brief's whole recipe and the ground goes light
// with it. See [Narrative].
decoration: Narrative.cardBox(),
child: child,
);
}

View File

@@ -0,0 +1,174 @@
import 'package:flutter/material.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// PREMIUM LOGISTICS NARRATIVE — the design system the rebuilt screens follow
///
/// A supplied spec, transcribed rather than interpreted: the values below are
/// the ones in the brief. It is a different recipe from [MilerSurface]'s, and
/// the difference is the whole point, so it is worth stating plainly:
///
/// ```
/// MilerSurface dark canvas, white cards, NO border and NO shadow
/// — separation bought with a 1.29:1 tonal step
/// Narrative light canvas, white cards, 1px border AND a soft shadow
/// — separation bought with an edge and a lift
/// ```
///
/// Both work; what does not work is half of each. So a screen belongs to one
/// system or the other, and the ones rebuilt against this brief take this one
/// whole — canvas, card, border, shadow, radius and rhythm together.
///
/// ── No glass ──
///
/// The brief's own Level 2 is a frosted, 70%-white overlay. It is deliberately
/// **not** transcribed here: translucency was cut from these screens on the
/// instruction that came with the spec. A blurred surface is also the single
/// most reliable way to drop a mid-range Android below 60fps, which is the
/// phone this app ships to — so nothing here is see-through, and depth comes
/// from tone, edge and shadow instead.
/// ─────────────────────────────────────────────────────────────────────────
class Narrative {
Narrative._();
// ── Surfaces ──────────────────────────────────────────────────────────
/// **Level 0.** The page. A soft, cool grey — light enough that a white card
/// on it needs its edge and its shadow to read, which is exactly what this
/// system gives them.
static const Color canvas = Color(0xFFF8F9FA);
/// **Level 1.** A card: pure white, over [cardBorder] and [cardShadow].
static const Color card = Color(0xFFFFFFFF);
/// The hairline around a card. 1.13:1 against white on its own — it is not
/// asked to separate anything by itself, it is asked to *finish* an edge the
/// shadow has already softened.
static const Color cardBorder = Color(0xFFE5E7EB);
/// A tonal block *inside* a card — an address, a fact tile, a task row.
static const Color inset = Color(0xFFF3F4F5);
/// One step down from [inset], for a control's resting state.
static const Color insetDeep = Color(0xFFEDEEEF);
// ── Ink ───────────────────────────────────────────────────────────────
/// Headings and any figure the screen is about. 15.9:1 on [card].
static const Color ink = Color(0xFF191C1D);
/// Supporting copy, captions, the quiet half of a pair. 8.0:1 on [card].
static const Color inkSoft = Color(0xFF584141);
/// Labels in data-dense places — timestamps, counts, eyebrows. 4.9:1.
static const Color inkMuted = Color(0xFF6B6B6B);
// ── Accents ───────────────────────────────────────────────────────────
/// **Deep burgundy.** The brand, spent sparingly: primary actions, the
/// selected state, and the one figure a screen is answering with.
///
/// The brief names #800020 in prose and #570013 in its token table. The
/// prose value is the one used, because it is the one the brief calls "the
/// palette is centred around" — and it is the value the mock-ups were drawn
/// with.
static const Color burgundy = Color(0xFF800020);
/// The burgundy at the weight a tint reads on white without becoming a
/// surface of its own — the brief's "10% opacity burgundy" ghost fill.
static const Color burgundyWash = Color(0x1A800020);
/// **Refined emerald.** Completion, and only completion.
static const Color emerald = Color(0xFF00472C);
/// The emerald as a chip fill, with [emerald] as its text — the brief's
/// "light tint of the status colour, darker text of the same hue".
static const Color emeraldWash = Color(0x1A00472C);
/// Amber, for the one row that still owes somebody something.
static const Color amber = Color(0xFF8E5A00);
static const Color amberWash = Color(0x1A8E5A00);
// ── Shape ─────────────────────────────────────────────────────────────
//
// "Architectural curvature": large, intentional radii, nested so a child is
// always visibly tighter than the surface holding it.
/// A card, a sheet, a panel — anything that is a surface in its own right.
static const double radiusCard = 28;
/// A block inside a card: a tonal well, a task row, a fact tile.
static const double radiusInner = 18;
/// A chip, an icon container, a small tile.
static const double radiusChip = 14;
/// Fully rounded — pills, segmented tracks, avatars.
static const double radiusPill = 999;
// ── Elevation ─────────────────────────────────────────────────────────
/// A card's lift: wide, diffuse and almost invisible. Offset 0,4 · blur 20 ·
/// 4% black. It is not there to be seen; it is there so the edge above it
/// does not have to carry the separation alone.
static const List<BoxShadow> cardShadow = [
BoxShadow(color: Color(0x0A000000), blurRadius: 20, offset: Offset(0, 4)),
];
/// A surface that floats over the page rather than sitting in it.
static const List<BoxShadow> floatShadow = [
BoxShadow(color: Color(0x14000000), blurRadius: 28, offset: Offset(0, 8)),
];
/// The glow under a primary action — the brand's own colour, so the button
/// reads as lit rather than as merely raised.
static const List<BoxShadow> burgundyGlow = [
BoxShadow(color: Color(0x33800020), blurRadius: 20, offset: Offset(0, 8)),
];
// ── Rhythm ────────────────────────────────────────────────────────────
//
// Base-8. Two sections are 32 apart, two blocks 16, two lines 8.
/// The margin the page keeps off the screen's edge, so nothing interactive
/// touches it and the cards read as floating.
///
/// ── 24 → 14 ──
///
/// The brief's figure is 24, and it is written for a page of prose. These
/// pages are lists of stops read at arm's length: at 24 of margin plus 20 of
/// card padding, a customer's name started 44pt in on a 390pt phone and the
/// screen held two fewer rows for it. The reference mock-ups the brief came
/// with are drawn at about half its own number, which is the answer — the
/// margin exists so the cards read as floating, and 14 does that.
///
/// ── 14 → 12 ──
///
/// One more point off each side, measured on device rather than reasoned:
/// with a 1px border and a 20-blur shadow the card's *visual* edge already
/// sits a point or two inside its box, so 14 of margin reads as 16 and the
/// page looked like it had a frame. 12 is where the gap stops being noticed
/// and starts just being air.
static const double gutter = 12;
/// The padding inside a card. The brief says 20; 16 is what leaves the
/// content room once [gutter] is added to it on a phone.
static const double cardPad = 16;
/// Between two logical sections.
static const double gapSection = 32;
/// Between two blocks in a section.
static const double gapBlock = 16;
/// Between two lines of one block.
static const double gapLine = 8;
/// Decoration for a Level-1 card, so no screen has to re-declare the three
/// values that make one.
static BoxDecoration cardBox({double? radius}) => BoxDecoration(
color: card,
borderRadius: BorderRadius.circular(radius ?? radiusCard),
border: Border.all(color: cardBorder, width: 1),
boxShadow: cardShadow,
);
}