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>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
@@ -33,7 +34,10 @@ void main() {
|
||||
|
||||
group('MilerAppBar', () {
|
||||
testWidgets('shows the page name and nothing beneath it', (tester) async {
|
||||
await pump(tester, const Scaffold(appBar: MilerAppBar(title: 'Activity')));
|
||||
await pump(
|
||||
tester,
|
||||
const Scaffold(appBar: MilerAppBar(title: 'Activity')),
|
||||
);
|
||||
|
||||
expect(find.text('Activity'), findsOneWidget);
|
||||
expect(
|
||||
@@ -43,20 +47,64 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('wears the frosted brand wash, not a solid fill',
|
||||
(tester) async {
|
||||
await pump(tester, const Scaffold(appBar: MilerAppBar(title: 'Activity')));
|
||||
// ── The wash gave way to the brand ──
|
||||
//
|
||||
// The four tabs wore a 7% frosted brand wash flattened against white while
|
||||
// Home wore solid brand. Switching tab therefore changed the colour of the
|
||||
// top 76pt of the phone AND the colour of the status-bar glyphs, which
|
||||
// reads as arriving in a different app rather than a different page.
|
||||
//
|
||||
// One ground now, and everything on it inverts. The wash survives as
|
||||
// `milerGlassSurface()` for the sheets, which sit over content and still
|
||||
// want it.
|
||||
testWidgets('is solid brand, so every tab shares one chrome', (
|
||||
tester,
|
||||
) async {
|
||||
await pump(
|
||||
tester,
|
||||
const Scaffold(appBar: MilerAppBar(title: 'Activity')),
|
||||
);
|
||||
|
||||
final bar = tester.widget<AppBar>(find.byType(AppBar));
|
||||
expect(bar.backgroundColor, Colors.transparent);
|
||||
expect(bar.flexibleSpace, isNotNull);
|
||||
// A blur is the difference between glass and a tinted box: content
|
||||
// passing under the bar softens instead of sliding under a lid.
|
||||
expect(find.byType(BackdropFilter), findsOneWidget);
|
||||
expect(bar.backgroundColor, ColorConstants.primary);
|
||||
expect(
|
||||
bar.flexibleSpace,
|
||||
isNull,
|
||||
reason: 'a solid ground needs no surface layered over it',
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('inverts everything drawn on it', (tester) async {
|
||||
await pump(
|
||||
tester,
|
||||
const Scaffold(appBar: MilerAppBar(title: 'Activity', showBack: true)),
|
||||
);
|
||||
|
||||
final title = tester.widget<Text>(find.text('Activity'));
|
||||
expect(
|
||||
title.style?.color,
|
||||
Colors.white,
|
||||
reason: 'white on #960019 is the only combination that clears AA here',
|
||||
);
|
||||
|
||||
final back = tester.widget<Icon>(find.byIcon(LucideIcons.arrowLeft));
|
||||
expect(back.color, Colors.white);
|
||||
|
||||
final bar = tester.widget<AppBar>(find.byType(AppBar));
|
||||
expect(
|
||||
bar.systemOverlayStyle?.statusBarIconBrightness,
|
||||
Brightness.light,
|
||||
reason:
|
||||
'dark glyphs on maroon is the one contrast failure a rider '
|
||||
'cannot work around by tilting the phone',
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('closes with no rule under it', (tester) async {
|
||||
await pump(tester, const Scaffold(appBar: MilerAppBar(title: 'Activity')));
|
||||
await pump(
|
||||
tester,
|
||||
const Scaffold(appBar: MilerAppBar(title: 'Activity')),
|
||||
);
|
||||
|
||||
// The wash is a surface, and a surface against an untinted page is
|
||||
// already an edge. A hairline under it drew that boundary a second time —
|
||||
@@ -65,8 +113,9 @@ void main() {
|
||||
expect(bar.bottom, isNull);
|
||||
});
|
||||
|
||||
testWidgets('keeps a trailing slot for the pages that need one',
|
||||
(tester) async {
|
||||
testWidgets('keeps a trailing slot for the pages that need one', (
|
||||
tester,
|
||||
) async {
|
||||
await pump(
|
||||
tester,
|
||||
Scaffold(
|
||||
@@ -80,26 +129,36 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('appTopBar', () {
|
||||
testWidgets('uses the same wash as the tab bars', (tester) async {
|
||||
await pump(tester, Scaffold(appBar: milerPageBar('Rewards', onBack: () {})));
|
||||
group('the pushed-page bar', () {
|
||||
// The rule is unchanged and is the whole point of this file: a pushed page
|
||||
// and a tab must be the same object at the top of the phone. Only the
|
||||
// answer changed — both are solid brand now, rather than both being a wash.
|
||||
testWidgets('is the same ground as the tab bars', (tester) async {
|
||||
await pump(
|
||||
tester,
|
||||
Scaffold(appBar: milerPageBar('Rewards', onBack: () {})),
|
||||
);
|
||||
|
||||
final bar = tester.widget<AppBar>(find.byType(AppBar));
|
||||
expect(bar.backgroundColor, Colors.transparent);
|
||||
expect(find.byType(BackdropFilter), findsOneWidget);
|
||||
expect(bar.bottom, isNull, reason: 'no rule under the glass here either');
|
||||
expect(bar.backgroundColor, ColorConstants.primary);
|
||||
expect(bar.flexibleSpace, isNull);
|
||||
expect(bar.bottom, isNull, reason: 'no rule under the bar here either');
|
||||
});
|
||||
});
|
||||
|
||||
// The wash survives as `milerGlassSurface()`, which the SHEETS use — they sit
|
||||
// over scrolling content and genuinely want a translucent surface, where a
|
||||
// bar sits over nothing and does not. This still pins what the wash has to
|
||||
// be, wherever it is used.
|
||||
test('the wash is a brand tint light enough to carry near-black type', () {
|
||||
// Flattened against white, which is what both the real bars and Home's
|
||||
// pinned header composite it over.
|
||||
// Flattened against white, which is what the sheets composite it over.
|
||||
final flat = Color.alphaBlend(ColorConstants.glassRed, Colors.white);
|
||||
expect(
|
||||
flat.computeLuminance(),
|
||||
greaterThan(0.85),
|
||||
reason: 'a saturated red bar would force white text onto a mid-tone red, '
|
||||
'the weakest contrast pairing in the palette',
|
||||
reason:
|
||||
'a saturated red surface would force white text onto a mid-tone '
|
||||
'red, the weakest contrast pairing in the palette',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user