Files
doormile_milderapp/lib/views/offline/offline_banner.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

112 lines
4.4 KiB
Dart
Raw 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:lucide_icons_flutter/lucide_icons.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(
LucideIcons.cloudOff,
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),
],
);
}
}