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>
195 lines
7.6 KiB
Dart
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,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|