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>
133 lines
5.0 KiB
Dart
133 lines
5.0 KiB
Dart
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(),
|
||
);
|
||
}
|
||
}
|