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` 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 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 show( BuildContext context, { required bool goingOnline, required Future Function() onConfirm, }) { return showMilerSheet( 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, ), ), ), ); } }