import 'package:shared_preferences/shared_preferences.dart'; import 'package:miler/data/accepted_store.dart'; import 'package:miler/data/api_config.dart'; import 'package:miler/data/geofence.dart'; import 'package:miler/data/mutation_guard.dart'; import 'package:miler/data/proof_store.dart'; /// ───────────────────────────────────────────────────────────────────────── /// ENDING A SESSION, IN ONE PLACE /// /// A rider's session ends two ways, and until now only one of them was /// implemented: /// /// * **He presses Log out.** The Account screen tore everything down — /// scoped stores, doorstep photos, bearer token, in-flight guard, cached /// fix — raised `logged_out` and sent him to sign-in. /// * **The server stops accepting his token.** `MilerApi` drops the token on /// any 401 and calls `onUnauthorized`, which **nothing ever assigned**. So /// the credential vanished and nothing else did. /// /// The second case left a state that looks signed in and cannot work: the /// profile is still in SharedPreferences, `logged_out` is still `false`, so /// the app draws the rider's own name over a dashboard whose every call comes /// back `401 authorization header is required`. Observed on a real handset — /// Karthikeyan CBE Rider, rider 23, name and tenant on screen, `authtoken` /// absent from disk, Home / Deliveries / Activity and the background heartbeat /// all failing in a loop. /// /// What that costs is not a blank screen. A rider opens the app, sees himself /// signed in and sees no jobs, and concludes there is no work today — there is /// no prompt anywhere telling him to sign in again. He waits; the stops go /// undelivered; support cannot tell over the phone. /// /// So the teardown lives here, both callers use it, and neither can drift from /// the other. The rule it encodes: **the credential and the appearance of /// being signed in end together, always.** /// ───────────────────────────────────────────────────────────────────────── /// Tears down everything this device holds about the signed-in rider. /// /// Safe to call twice — every step is idempotent — which matters because a 401 /// rarely arrives alone: the home poll, the deliveries queue and the heartbeat /// can all get one within the same second. /// /// Deliberately knows nothing about navigation. Where the rider goes next is a /// UI decision and belongs to the caller; this is the part that must happen /// identically whether he chose to leave or was shown the door. Future endSession() async { // Finished work, skipped stops, carried bags and bag labels are scoped per // rider/tenant/line — this drops *this* scope so nothing survives into the // next rider's session on a shared handset. await clearScopedStores(); // Doorstep photos are exactly the kind of record that must not outlive the // session that took them. await ProofStore.clearScope(); // The credential goes with the session, not at the next sign-in. Anything // that reads the token without checking a flag — a background isolate, the // notification handler, a heartbeat that outlives a route change — must not // be able to keep calling as the rider who has left. await ApiConfig.clearToken(); // A stale in-flight mutation must not find a key still held from the session // being closed. MutationGuard.reset(); // A cached position is a fact about a rider and must not outlive him: the // next person to sign in would have his first proximity check measured from // wherever the last rider was standing. Geofence.resetCache(); // The flag the launch path reads to decide between Home and sign-in. final prefs = await SharedPreferences.getInstance(); await prefs.setBool('logged_out', true); }