Files
doormile_milderapp/lib/views/Dashboard/home/duty_sheet.dart
Thiru-tenext d7348e253f 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>
2026-08-22 05:40:35 +05:30

195 lines
7.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:miler/views/helpers/constants/Colorconstants.dart';
import 'package:miler/views/helpers/constants/Font_constant.dart';
import 'package:miler/views/helpers/constants/design_constants.dart';
import 'package:miler/views/helpers/widgets/miler_sheet_kit.dart';
/// ─────────────────────────────────────────────────────────────────────────
/// GOING ON / OFF DUTY
///
/// One decision, asked once.
///
/// ── What this had accumulated ──
///
/// A grab handle, a tinted icon disc, a title, a subtitle, a **drawn diagram of
/// the switch moving from one state to the other**, three icon-and-text rows
/// naming the consequences, and a slide-to-confirm. Eight blocks for a question
/// with two answers — the shape of a screen assembled from good intentions one
/// at a time rather than designed.
///
/// Each piece was individually defensible and the total was not. The diagram
/// illustrated a state change the title had already stated. The consequence
/// rows were three lines of icons for facts that fit in one sentence. And the
/// slide was borrowed from the payment flows, where the app's own rule is that
/// **slides are reserved for money** — a rider ending his shift is not
/// confirming a transaction, and asking for a drag rather than a tap made the
/// commonest action of his day the most laborious.
///
/// ── What it is ──
///
/// The question, one sentence of consequence, and two answers with an obvious
/// primary. Nothing is illustrated that is also written. Going off duty is the
/// consequential direction, so it carries the brand and states the one thing
/// that genuinely catches riders out — the app will not let him off duty while
/// a booking is open — in the sentence rather than after a failed gesture.
/// ─────────────────────────────────────────────────────────────────────────
class DutySheet extends StatelessWidget {
/// True when this sheet is asking to go **on** duty.
final bool goingOnline;
/// Runs on a completed slide, and owns the work: the pending-booking check,
/// the API call, the polling restart. Returns true when duty actually
/// changed.
///
/// The knob belongs to the sheet, not the caller — it springs back on every
/// outcome, and the sheet closes itself only on a true. That is why this is a
/// `Future<bool>` and not the package's controller: the controller type is
/// not exported from `slide_to_submit_button`, so naming it here would mean
/// reaching into the package's `src/`.
final Future<bool> Function() onConfirm;
const DutySheet({
super.key,
required this.goingOnline,
required this.onConfirm,
});
/// Opens the sheet. The caller keeps the toggle logic; this only presents it.
static Future<void> show(
BuildContext context, {
required bool goingOnline,
required Future<bool> Function() onConfirm,
}) {
return showMilerSheet<void>(
context,
builder: (_) => DutySheet(goingOnline: goingOnline, onConfirm: onConfirm),
);
}
/// Green to start, brand red to stop.
///
/// Green matches the duty switch's own "on" fill, so the sheet is visibly
/// about the control that opened it. Stopping is not grey: grey is what a
/// disabled control looks like, and this is the most consequential button on
/// the screen.
Color get _accent =>
goingOnline ? ColorConstants.success : ColorConstants.primary;
@override
Widget build(BuildContext context) {
// ── The SafeArea used to wrap the GLASS ──
//
// So the frosted surface stopped at the top of the gesture inset and the
// brand-red home indicator area showed through beneath it — a coloured
// stripe under every duty change. The kit pads *inside* the fabric.
return MilerSheetScaffold(
padding: EdgeInsets.fromLTRB(22.w, 0, 22.w, 18.h),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 6.h),
Text(
goingOnline ? 'Go on duty?' : 'Go off duty?',
style: TextStyle(
fontSize: 21.sp,
fontWeight: FontWeight.w700,
letterSpacing: -0.5,
height: 1.2,
fontFamily: FontConstants.fontFamily,
color: ColorConstants.slateText,
),
),
SizedBox(height: 8.h),
// One sentence. The off-duty version carries the fact that used
// to be discovered only after a failed slide.
Text(
goingOnline
? 'Your hub can add you to the next slot, and live '
'tracking runs while you are on.'
: 'Your hub stops assigning you trips and live tracking '
'pauses. Any booking you are holding has to be '
'finished first.',
style: TextStyle(
fontSize: 14.5.sp,
height: 1.45,
fontWeight: FontWeight.w500,
fontFamily: FontConstants.fontFamily,
color: ColorConstants.secondaryText,
),
),
SizedBox(height: 24.h),
_confirm(context),
SizedBox(height: 4.h),
_dismiss(context),
],
),
);
}
/// The answer, as a button. Green to start the day, brand to end it — the
/// same two colours the duty switch itself uses, so the sheet is visibly
/// about the control that opened it.
Widget _confirm(BuildContext context) {
return SizedBox(
height: ButtonSizes.primary,
child: ElevatedButton(
onPressed: () async {
final changed = await onConfirm();
if (changed && context.mounted) Navigator.of(context).maybePop();
},
style: ElevatedButton.styleFrom(
backgroundColor: _accent,
foregroundColor: ColorConstants.onAccent,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(ButtonSizes.radius),
),
),
child: Text(
goingOnline ? 'Go on duty' : 'Go off duty',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w700,
letterSpacing: -0.3,
fontFamily: FontConstants.fontFamily,
color: ColorConstants.onAccent,
),
),
),
);
}
/// The way out, stated as what it leaves him as rather than as "Cancel" — a
/// rider reading two buttons should not have to work out which one is the
/// no-op.
Widget _dismiss(BuildContext context) {
return SizedBox(
height: ButtonSizes.secondary,
child: TextButton(
onPressed: () => Navigator.of(context).maybePop(),
style: TextButton.styleFrom(
foregroundColor: ColorConstants.secondaryText,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(ButtonSizes.radius),
),
),
child: Text(
goingOnline ? 'Not yet' : 'Stay on duty',
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w600,
fontFamily: FontConstants.fontFamily,
color: ColorConstants.secondaryText,
),
),
),
);
}
}