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/design_constants.dart'; import 'package:miler/views/helpers/constants/miler_type.dart'; /// ───────────────────────────────────────────────────────────────────────── /// OFFLINE, WITHOUT TAKING THE JOB AWAY /// /// ── What this replaces ── /// /// Losing signal used to lay an opaque full-screen page over the entire app: /// `Stack(children: [baseChild, const OfflinePage()])`. Not a warning — a /// replacement. /// /// A rider standing at a doorstep in a basement, a lift, a stairwell or a rural /// lane — which is where Indian first-mile delivery actually happens — lost the /// address, the customer's name, their phone number, the parcel count and the /// OTP field. He was holding a parcel and could not see who it was for. The app /// then told him it was "retrying automatically every 5 seconds", which is not /// something he can act on while a customer waits. /// /// ── Why a strip is the right shape ── /// /// Everything the rider needs at the door was already fetched. Connectivity is /// needed to *report* the stop, not to work it. So the honest message is not /// "you are offline" but "keep going, this will sync" — and that is a line of /// text, not a screen. /// /// The full-screen page survives for the one case where it is truthful: a cold /// start with no cached session, where there genuinely is nothing to show. /// /// ── Sizing ── /// /// Deliberately thin, and it does **not** overlay the content — [OfflineScope] /// gives it its own row above the app, so it can never cover a field the rider /// is typing into. The one thing worse than a blocker is a floating bar sitting /// on top of the OTP box. /// ───────────────────────────────────────────────────────────────────────── class OfflineBanner extends StatelessWidget { const OfflineBanner({super.key}); @override Widget build(BuildContext context) { return Semantics( liveRegion: true, label: 'Offline. Your changes will sync when you are back online.', excludeSemantics: true, child: Material( color: ColorConstants.warning, child: SafeArea( bottom: false, child: Padding( padding: EdgeInsets.fromLTRB(16.w, 7.h, 16.w, 7.h), child: Row( children: [ Icon( Icons.cloud_off_rounded, size: 15.sp, color: ColorConstants.onAccent, ), SizedBox(width: 9.w), Expanded( child: Text( // States what is true and what happens next. "You're // offline" alone tells a rider mid-stop nothing he can use. 'Offline · Changes will sync when you’re back online', maxLines: 2, overflow: TextOverflow.ellipsis, style: MilerType.micro .on(ColorConstants.onAccent) .semibold, ), ), ], ), ), ), ), ); } } /// Puts [OfflineBanner] above the app rather than over it. /// /// A `Column` rather than a `Stack` on purpose: the strip takes its own height /// out of the layout, so nothing underneath is ever covered. The app simply /// starts a few points lower while the connection is down. class OfflineScope extends StatelessWidget { final Widget child; final bool offline; const OfflineScope({super.key, required this.child, required this.offline}); @override Widget build(BuildContext context) { return Column( children: [ AnimatedSize( duration: DesignConstants.motionState, curve: Curves.easeOutCubic, alignment: Alignment.bottomCenter, child: offline ? const OfflineBanner() : const SizedBox(width: double.infinity), ), Expanded(child: child), ], ); } }