Files
doormile_milderapp/lib/views/offline/offline_banner.dart
2026-08-11 13:16:33 +05:30

111 lines
4.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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),
],
);
}
}