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:
@@ -36,22 +36,24 @@ void main() {
|
||||
double cash = 0,
|
||||
int deliverQty = 0,
|
||||
int pickupQty = 0,
|
||||
}) =>
|
||||
{
|
||||
'orderid': id,
|
||||
'type': type,
|
||||
'pickupcustomer': 'Sri Balaji Stores',
|
||||
'pickupaddress': '19, Thadagam Road, Venkitapuram, Coimbatore',
|
||||
'pickupcontactno': '9876543210',
|
||||
if (deliverQty > 0) 'deliveryqty': '$deliverQty',
|
||||
if (pickupQty > 0) 'pickupqty': '$pickupQty',
|
||||
'Quantity': '3',
|
||||
if (cash > 0) 'collectionamt': '$cash',
|
||||
'expected_pickup_time': DateTime.now().toIso8601String(),
|
||||
};
|
||||
}) => {
|
||||
'orderid': id,
|
||||
'type': type,
|
||||
'pickupcustomer': 'Sri Balaji Stores',
|
||||
'pickupaddress': '19, Thadagam Road, Venkitapuram, Coimbatore',
|
||||
'pickupcontactno': '9876543210',
|
||||
if (deliverQty > 0) 'deliveryqty': '$deliverQty',
|
||||
if (pickupQty > 0) 'pickupqty': '$pickupQty',
|
||||
'Quantity': '3',
|
||||
if (cash > 0) 'collectionamt': '$cash',
|
||||
'expected_pickup_time': DateTime.now().toIso8601String(),
|
||||
};
|
||||
|
||||
Future<void> pumpCard(WidgetTester tester,
|
||||
{Set<String> accepted = const {}, double width = 390}) async {
|
||||
Future<void> pumpCard(
|
||||
WidgetTester tester, {
|
||||
Set<String> accepted = const {},
|
||||
double width = 390,
|
||||
}) async {
|
||||
tester.view.physicalSize = Size(width * 3, 2532);
|
||||
tester.view.devicePixelRatio = 3.0;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
@@ -93,7 +95,8 @@ void main() {
|
||||
void addBorder(String where, BoxBorder? b) {
|
||||
if (b is Border) {
|
||||
for (final side in [b.top, b.right, b.bottom, b.left]) {
|
||||
if (side.style != BorderStyle.none) found.add(('$where.border', side.color));
|
||||
if (side.style != BorderStyle.none)
|
||||
found.add(('$where.border', side.color));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,17 +153,22 @@ void main() {
|
||||
'combinedAccent (deep indigo)': ColorConstants.combinedAccent,
|
||||
};
|
||||
|
||||
testWidgets('the un-darkened type accents are not painted at all',
|
||||
(tester) async {
|
||||
testWidgets('the un-darkened type accents are not painted at all', (
|
||||
tester,
|
||||
) async {
|
||||
await pumpCard(tester);
|
||||
final painted = paintedColours(tester);
|
||||
expect(painted, isNotEmpty, reason: 'sanity: the card painted something');
|
||||
|
||||
for (final entry in bannedOutright.entries) {
|
||||
final hits = painted.where((p) => p.$2 == entry.value).toList();
|
||||
expect(hits, isEmpty,
|
||||
reason: '${entry.key} is the light accent — the chip should use the '
|
||||
'darkened variant. Found at ${hits.map((h) => h.$1).toSet()}');
|
||||
expect(
|
||||
hits,
|
||||
isEmpty,
|
||||
reason:
|
||||
'${entry.key} is the light accent — the chip should use the '
|
||||
'darkened variant. Found at ${hits.map((h) => h.$1).toSet()}',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -168,90 +176,100 @@ void main() {
|
||||
await pumpCard(tester, accepted: {'D', 'C'});
|
||||
final painted = paintedColours(tester);
|
||||
for (final entry in bannedOutright.entries) {
|
||||
expect(painted.where((p) => p.$2 == entry.value), isEmpty,
|
||||
reason: '${entry.key} must not appear in the accepted state either');
|
||||
expect(
|
||||
painted.where((p) => p.$2 == entry.value),
|
||||
isEmpty,
|
||||
reason: '${entry.key} must not appear in the accepted state either',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('type colour reaches the chip and nothing else', (tester) async {
|
||||
// ── Where the type went, and why these two tests changed ──
|
||||
//
|
||||
// The rule this file was written for was "a stop type may be a mark, never a
|
||||
// surface", and it was measured by finding the type *word* on the row and
|
||||
// checking nothing was filled with its colour.
|
||||
//
|
||||
// The route timeline then took the word off the row as well. That was not a
|
||||
// slip: a step is `who` and `which bag`, the two facts a rider matches
|
||||
// against a shelf, and the type went into the detail sheet with the address,
|
||||
// the phone and the parcel counts. So the rule gets **stricter** rather than
|
||||
// looser — a stop type may not tint anything on the route, and it may not
|
||||
// spend a line there either — and that is what is measured now.
|
||||
//
|
||||
// The wording itself is not lost and is not untested: [StopKind.badgeLabel]
|
||||
// and [StopKind.chipLabel] still carry it, the Bookings card still prints it
|
||||
// on a line of its own, and `service_card_test.dart` holds both.
|
||||
|
||||
testWidgets('a stop type tints nothing on the route', (tester) async {
|
||||
await pumpCard(tester);
|
||||
|
||||
// Present: the three darkened chip fills, one per stop kind.
|
||||
final containerFills = tester.allWidgets
|
||||
final containerColours = tester.allWidgets
|
||||
.whereType<Container>()
|
||||
.map((c) => c.decoration)
|
||||
.whereType<BoxDecoration>()
|
||||
.map((d) => d.color)
|
||||
.expand(
|
||||
(d) => [
|
||||
d.color,
|
||||
if (d.border is Border) (d.border as Border).top.color,
|
||||
],
|
||||
)
|
||||
.whereType<Color>()
|
||||
.toList();
|
||||
|
||||
for (final chip in [
|
||||
ColorConstants.pickupChip,
|
||||
ColorConstants.deliveryChip,
|
||||
ColorConstants.combinedChip,
|
||||
]) {
|
||||
expect(containerFills, contains(chip),
|
||||
reason: 'the type chip should be filled with its darkened accent');
|
||||
}
|
||||
|
||||
// Absent: any of those three used as a *border*. A chip is a mark; a border
|
||||
// belongs to a surface, and that is the line being held.
|
||||
final borderColours = tester.allWidgets
|
||||
.whereType<Container>()
|
||||
.map((c) => c.decoration)
|
||||
.whereType<BoxDecoration>()
|
||||
.map((d) => d.border)
|
||||
.whereType<Border>()
|
||||
.expand((b) => [b.top.color, b.right.color, b.bottom.color, b.left.color])
|
||||
.toList();
|
||||
final textColours = tester.allWidgets
|
||||
.whereType<Text>()
|
||||
.map((t) => t.style?.color)
|
||||
.whereType<Color>()
|
||||
.toSet();
|
||||
|
||||
for (final chip in [
|
||||
ColorConstants.pickupChip,
|
||||
ColorConstants.deliveryChip,
|
||||
ColorConstants.combinedChip,
|
||||
]) {
|
||||
expect(borderColours, isNot(contains(chip)),
|
||||
reason: 'stop type must not outline a surface');
|
||||
expect(
|
||||
containerColours.contains(chip),
|
||||
isFalse,
|
||||
reason: 'a stop type may not fill or border anything on the route',
|
||||
);
|
||||
expect(
|
||||
textColours.contains(chip),
|
||||
isFalse,
|
||||
reason:
|
||||
'nor tint type: the route is one accent — the brand, on the live '
|
||||
'node and the journey action — and a per-stop hue competes with it',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('stop cards are borderless, on one fill whatever the type',
|
||||
(tester) async {
|
||||
testWidgets('a step states who it is for, and no stop type', (tester) async {
|
||||
await pumpCard(tester);
|
||||
|
||||
// The stop bodies: a rounded fill with no border at all now.
|
||||
final bodies = tester.allWidgets
|
||||
.whereType<Container>()
|
||||
.map((c) => c.decoration)
|
||||
.whereType<BoxDecoration>()
|
||||
.where((d) => d.color == ColorConstants.cardSurface)
|
||||
.toList();
|
||||
// This is a parcel route, so each stop is its own node and what names it is
|
||||
// the customer plus its position in the run. (The `who` + `which bag` pair
|
||||
// is the kitchen-group shape and is held by `one_bag_per_order_test.dart`.)
|
||||
expect(find.text('Sri Balaji Stores'), findsWidgets);
|
||||
expect(find.text('Stop 1'), findsOneWidget);
|
||||
|
||||
expect(bodies.length, 3, reason: 'one fill per stop, all three identical');
|
||||
for (final d in bodies) {
|
||||
expect(d.border, isNull, reason: 'the card carries no border');
|
||||
expect(d.boxShadow, anyOf(isNull, isEmpty),
|
||||
reason: 'the fill does the separating, not an elevation');
|
||||
// Everything the card used to shout on every row. The type is one tap away
|
||||
// in the sheet, on the badge `StopDetailSheet._header` draws.
|
||||
for (final label in const [
|
||||
'PICKUP',
|
||||
'DELIVERY',
|
||||
'P&D',
|
||||
'PICKUP & DELIVERY',
|
||||
]) {
|
||||
expect(
|
||||
find.text(label),
|
||||
findsNothing,
|
||||
reason:
|
||||
'"$label" is on the stop sheet, not on a row a rider reads forty '
|
||||
'times a shift',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('type is stated in words, not colour', (tester) async {
|
||||
await pumpCard(tester);
|
||||
// The labels still say which kind of stop each one is — that information was
|
||||
// never removed, only moved out of the fill and into the text.
|
||||
//
|
||||
// The combined chip reads "P&D", not "PICKUP & DELIVERY". On a stop row the
|
||||
// chip shares a line with a duration, a call button and a tick box, all of
|
||||
// which have a fixed claim on the width, so 17 characters ellipsised down to
|
||||
// "P…" — a chip that has to truncate is not a chip. The full wording still
|
||||
// appears on the Bookings card, which has a whole line for it.
|
||||
expect(find.text('PICKUP'), findsWidgets);
|
||||
expect(find.text('DELIVERY'), findsWidgets);
|
||||
expect(find.text('P&D'), findsWidgets);
|
||||
expect(find.text('PICKUP & DELIVERY'), findsNothing,
|
||||
reason: 'the long label does not fit a stop-row chip');
|
||||
});
|
||||
|
||||
testWidgets('the type chip is never squeezed out of shape', (tester) async {
|
||||
// The bug: the chip rendered as "P…". Its `Flexible` sat beside a `Spacer`,
|
||||
// and a Spacer is an `Expanded` — so the two split the free width evenly and
|
||||
@@ -269,8 +287,11 @@ void main() {
|
||||
|
||||
for (final width in [320.0, 360.0, 390.0, 430.0]) {
|
||||
await pumpCard(tester, width: width);
|
||||
expect(tester.takeException(), isNull,
|
||||
reason: 'the meta row overflowed at ${width.toInt()}pt');
|
||||
expect(
|
||||
tester.takeException(),
|
||||
isNull,
|
||||
reason: 'the meta row overflowed at ${width.toInt()}pt',
|
||||
);
|
||||
|
||||
for (final label in ['PICKUP', 'DELIVERY', 'P&D']) {
|
||||
final finder = find.text(label);
|
||||
@@ -283,14 +304,17 @@ void main() {
|
||||
style: tester.widget<Text>(finder.first).style,
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout())
|
||||
.width;
|
||||
)..layout()).width;
|
||||
|
||||
final got = box.size.width / natural;
|
||||
expect(got, greaterThan(acceptable),
|
||||
reason: 'at ${width.toInt()}pt the "$label" chip got '
|
||||
'${(got * 100).round()}% of the width its label needs; the '
|
||||
'Spacer-stealing bug gave ${(squeezed * 100).round()}%');
|
||||
expect(
|
||||
got,
|
||||
greaterThan(acceptable),
|
||||
reason:
|
||||
'at ${width.toInt()}pt the "$label" chip got '
|
||||
'${(got * 100).round()}% of the width its label needs; the '
|
||||
'Spacer-stealing bug gave ${(squeezed * 100).round()}%',
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user