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:
2026-08-22 05:40:35 +05:30
parent c8350563d9
commit d7348e253f
387 changed files with 80693 additions and 12272 deletions

178
test/sheet_kit_test.dart Normal file
View File

@@ -0,0 +1,178 @@
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';
import 'package:miler/Models/stop_status.dart';
import 'package:miler/data/bag_manifest.dart';
import 'package:miler/data/service_profile.dart';
import 'package:miler/views/Dashboard/home/duty_sheet.dart';
import 'package:miler/views/Dashboard/home/stop_action_sheet.dart';
import 'package:miler/views/helpers/constants/Colorconstants.dart';
import 'package:miler/views/helpers/widgets/miler_sheet_kit.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// THE SHEET KIT — the rules, held
///
/// The kit exists because six presentation styles had grown around one kind
/// of surface (see the audit in `miler_sheet_kit.dart`). What stops a seventh
/// growing back is not the kit's existence but these tests: the shared handle
/// on the sheets a rider actually uses, and the one inset rule that prevents
/// the double-SafeArea CTA drift from returning.
/// ─────────────────────────────────────────────────────────────────────────
void main() {
tearDown(() => ServiceProfile.setActive(ServiceProfile.parcel));
Future<void> pump(
WidgetTester tester,
Widget child, {
EdgeInsets viewInsets = EdgeInsets.zero,
EdgeInsets viewPadding = EdgeInsets.zero,
}) async {
tester.view.physicalSize = const Size(1170, 2532);
tester.view.devicePixelRatio = 3.0;
addTearDown(tester.view.reset);
await tester.pumpWidget(
ScreenUtilInit(
designSize: const Size(390, 844),
builder: (_, _) => MaterialApp(
home: MediaQuery(
data: MediaQueryData(
size: const Size(390, 844),
viewInsets: viewInsets,
viewPadding: viewPadding,
),
// NOT a Scaffold: `resizeToAvoidBottomInset` consumes viewInsets
// before the sheet sees them, which is exactly the environment a
// modal route does NOT provide — the sheet must read the keyboard
// itself, which is the behaviour under test.
child: Material(
color: Colors.black,
child: Align(alignment: Alignment.bottomCenter, child: child),
),
),
),
),
);
await tester.pump();
}
group('the scaffold', () {
testWidgets('pays the larger of keyboard and gesture bar, never both', (
tester,
) async {
// A gesture-bar phone with the keyboard up. The old double-SafeArea bug
// paid 300 + 34; the rule is max(300, 34).
await pump(
tester,
const MilerSheetScaffold(child: SizedBox(height: 40)),
viewInsets: const EdgeInsets.only(bottom: 300),
viewPadding: const EdgeInsets.only(bottom: 34),
);
final padding = tester.widget<AnimatedPadding>(
find.byType(AnimatedPadding).first,
);
expect(
padding.padding,
const EdgeInsets.only(bottom: 300),
reason:
'the keyboard replaces the gesture bar — summing them floats '
'the CTA a bar height above the keyboard',
);
});
testWidgets('with no keyboard the gesture bar is still paid', (
tester,
) async {
await pump(
tester,
const MilerSheetScaffold(child: SizedBox(height: 40)),
viewPadding: const EdgeInsets.only(bottom: 34),
);
final padding = tester.widget<AnimatedPadding>(
find.byType(AnimatedPadding).first,
);
expect(padding.padding, const EdgeInsets.only(bottom: 34));
});
testWidgets('draws the shared handle once', (tester) async {
await pump(tester, const MilerSheetScaffold(child: SizedBox(height: 40)));
expect(find.byType(MilerSheetHandle), findsOneWidget);
});
});
group('the migrated sheets carry the kit', () {
testWidgets('duty', (tester) async {
await pump(
tester,
DutySheet(goingOnline: true, onConfirm: () async => false),
);
expect(find.byType(MilerSheetScaffold), findsOneWidget);
expect(find.byType(MilerSheetHandle), findsOneWidget);
});
testWidgets('the rung confirmation', (tester) async {
ServiceProfile.setActive(ServiceProfile.milkMan);
await pump(
tester,
StopActionSheet(
stage: StopStatus.accepted,
placeName: 'Vidhya Kitchen',
count: 2,
lines: const [
BagLine(stop: {}, orderId: 'A', customer: 'Joe', bag: 'Bag 1'),
BagLine(stop: {}, orderId: 'B', customer: 'Arun', bag: 'Bag 2'),
],
onConfirm: (_) async => null,
),
);
expect(find.byType(MilerSheetScaffold), findsOneWidget);
expect(find.byType(MilerSheetHandle), findsOneWidget);
// The manifest is still the sheet's content — the kit wraps, it does
// not swallow.
expect(find.text('Joe'), findsOneWidget);
expect(find.text('Bag 2'), findsOneWidget);
});
});
group('the choice row', () {
testWidgets('selection is a soft fill and a tick, not a slab', (
tester,
) async {
await pump(
tester,
Material(
child: Column(
mainAxisSize: MainAxisSize.min,
children: const [
MilerSheetChoiceRow(label: 'Wrong address', selected: true),
MilerSheetChoiceRow(label: 'Out of area', selected: false),
],
),
),
);
// The tick marks the chosen answer…
expect(find.byIcon(LucideIcons.check), findsNWidgets(2));
final ticks = tester
.widgetList<AnimatedOpacity>(find.byType(AnimatedOpacity))
.map((w) => w.opacity)
.toList();
expect(ticks, [
1.0,
0.0,
], reason: 'the tick shows on the selected row and only there');
// …and no row is ever a full-strength primary slab. The fill that broke
// this rule outshouted the destructive CTA under it.
final fills = tester
.widgetList<AnimatedContainer>(find.byType(AnimatedContainer))
.map((c) => (c.decoration as BoxDecoration?)?.color)
.whereType<Color>();
expect(fills, isNot(contains(ColorConstants.primary)));
});
});
}