Files
doormile_milderapp/lib/helpers/app_bootstrap.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

133 lines
5.0 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:miler/views/helpers/constants/Colorconstants.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:miler/controllers/auth.dart';
import 'package:miler/views/introscreens/introscreen.dart';
import 'package:miler/views/onboardscreens/Mpin.dart';
import 'package:miler/views/onboardscreens/Sign_in.dart';
import 'package:miler/helpers/rider_shell.dart';
/// Decides which screen the app opens on, and nothing else.
///
/// This replaces the old animated splash screen. The splash held two very
/// different jobs: a four-second brand animation, and the routing rules that
/// decide login vs. intro vs. home. Only the second one is load-bearing.
///
/// The four seconds were pure cost for a rider who opens this app 30–50 times
/// a shift — the native launch image already covers the cold-start gap, so the
/// animation was time added on top of it, every single time. Removing it is
/// the single biggest latency win on the whole flow.
///
/// What survives is the routing, unchanged in behaviour:
/// • app version changed since last run → force re-verification (MPIN if the
/// rider was signed in, otherwise Sign In)
/// • signed in + on duty → straight to the dashboard
/// • signed in + off duty → the go-on-duty banner
/// • signed out, intro already seen → Sign In
/// • first ever launch → Introscreen
class AppBootstrap extends StatefulWidget {
const AppBootstrap({super.key});
@override
State<AppBootstrap> createState() => _AppBootstrapState();
}
class _AppBootstrapState extends State<AppBootstrap> {
bool _routed = false;
@override
void initState() {
super.initState();
// Route on the first frame — GetX needs a built navigator before `offAll`.
WidgetsBinding.instance.addPostFrameCallback((_) => _resolveStartScreen());
}
Future<void> _resolveStartScreen() async {
if (_routed) return;
_routed = true;
final prefs = await SharedPreferences.getInstance();
final bool isLoggedOut = prefs.getBool('logged_out') ?? false;
final bool hasSeenIntro = prefs.getBool('has_seen_intro') ?? false;
final int? savedUserId = prefs.getInt('userid');
// ── App update → force re-verification ──
try {
final packageInfo = await PackageInfo.fromPlatform();
final currentVersion = packageInfo.version;
final lastRunVersion = prefs.getString('last_run_version');
if (lastRunVersion != null && lastRunVersion != currentVersion) {
debugPrint(
'[BOOTSTRAP] App update detected: $lastRunVersion -> $currentVersion. '
'Forcing re-verification.',
);
final String? savedPhone = prefs.getString('contactno');
await prefs.setString('last_run_version', currentVersion);
if (savedUserId != null &&
savedPhone != null &&
savedPhone.isNotEmpty) {
debugPrint(
'[BOOTSTRAP] User was logged in. Redirecting to MPIN page.',
);
final auth = Get.put(AuthController());
auth.currentPhone = savedPhone;
await prefs.remove('userid');
await prefs.remove('partnerid');
await prefs.remove('onduty');
await prefs.setBool('logged_out', true);
if (mounted) Get.offAll(() => Mpin());
return;
}
debugPrint(
'[BOOTSTRAP] User was not logged in. Redirecting to Sign In.',
);
if (mounted) Get.offAll(() => const SignIn());
return;
}
await prefs.setString('last_run_version', currentVersion);
} catch (e) {
debugPrint('[BOOTSTRAP] Version check error: $e');
}
if (!mounted) return;
// ── Normal launch ──
//
// A signed-in rider lands on the tabs whether or not he is on duty. The
// duty gate that used to sit here made "open the app" and "start working"
// the same decision, so a rider checking his earnings between shifts had to
// pass a slider that put him back on the clock. Duty is a switch in Home's
// app bar, on the screen where the work is.
if (!isLoggedOut && savedUserId != null && savedUserId > 0) {
// Which of the two dashboards this rider works in — parcel or the ported
// delivery flow — decided by his tenant. `main()` awaits
// `TenantController.load()` before the first frame, so the answer is
// already on hand here. See [riderShell].
Get.offAll(() => riderShell());
} else {
Get.offAll(() => hasSeenIntro ? const SignIn() : Introscreen());
}
}
@override
Widget build(BuildContext context) {
// Held for the few milliseconds the prefs read takes. Plain white so it is
// indistinguishable from the native launch image — the rider should never
// perceive a screen here at all.
return Scaffold(
backgroundColor: ColorConstants.pureSurface,
body: SizedBox.expand(),
);
}
}