device infos

This commit is contained in:
2026-08-28 15:07:30 +05:30
parent 5723d373b2
commit 69f4f3e909
84 changed files with 4337 additions and 2339 deletions

View File

@@ -71,7 +71,7 @@ Future<void> migrateLegacyStores() async {
_kConsignmentIdsKeyBase,
_kOutForDeliveryKeyBase,
_kNotLoadedKeyBase,
_kBagLabelsKeyBase,
_kOrderLabelsKeyBase,
]) {
await _drainLegacy(base, scope);
}
@@ -117,7 +117,7 @@ Future<void> clearScopedStores() async {
_kConsignmentIdsKeyBase,
_kOutForDeliveryKeyBase,
_kNotLoadedKeyBase,
_kBagLabelsKeyBase,
_kOrderLabelsKeyBase,
]) {
await prefs.remove(scope.scoped(base));
await prefs.remove(base);
@@ -296,6 +296,26 @@ String kStopStartedKey(Object pickupId) => 'stop_started_$pickupId';
/// Written by the stop map screen when it starts the pickup.
String kStopArrivedKey(Object pickupId) => 'pickup_start_$pickupId';
/// Prefs key holding the kilometres the app measured for a stop as it closed.
///
/// ── The app measured this, posted it, and kept no copy ──
///
/// `PickupsController.updateToPicked` works out how far the rider came —
/// `Geolocator.distanceBetween`, rider fix to pickup point — and sends it as
/// `actualkms`. Then it dropped it. The number existed for the length of one
/// HTTP request.
///
/// Which is why Activity's `km ridden` read an em dash on a day the rider had
/// plainly ridden: [StopCompliance] looks for `actualkms` on the row, the row
/// never carried one, and `GET /miler/bookings` does not reliably send the
/// distance back. The app was the only thing that knew, and it forgot.
///
/// So it is written here as it is posted, and [addCompletedBookings] merges it
/// onto the record it files — the same rescue it already performs for the two
/// clocks beside it, for the same reason: the moment the fact is true is the
/// moment to write it down.
String kStopKmKey(Object pickupId) => 'stop_km_$pickupId';
/// Record a finished stop. [cancelled] separates "could not complete" from a
/// clean pickup/delivery — Activity shows both, worded differently.
///
@@ -362,10 +382,18 @@ Future<void> addCompletedBookings(
final arrived = prefs.getString(kStopArrivedKey(pickupId));
if (started != null && started.isNotEmpty) copy['startedat'] = started;
if (arrived != null && arrived.isNotEmpty) copy['arrivedat'] = arrived;
// The distance the app measured as it closed this stop — see
// [kStopKmKey]. A figure already on the row wins: that one came from the
// hub, and this is the app's own measurement standing in for it.
final km = prefs.getString(kStopKmKey(pickupId));
if (km != null && km.isNotEmpty && (copy['actualkms'] ?? '') == '') {
copy['actualkms'] = km;
}
// Cleared, or a stop worked twice in a day (a resumed skip) reports the
// first attempt's clock against the second attempt's completion.
await prefs.remove(kStopStartedKey(pickupId));
await prefs.remove(kStopArrivedKey(pickupId));
await prefs.remove(kStopKmKey(pickupId));
}
// Stamped with the session that produced it, so a row read back later
@@ -795,26 +823,36 @@ Future<void> addNotLoadedOrderIds(List<String> ids) async {
}
// ─────────────────────────────────────────────────────────────────────────
// BAG IDENTITY — which bag an order travels in, for as long as it travels
// THE COUNTER'S OWN LABEL — carried for as long as the order travels
//
// ── Why this is stored rather than recomputed ──
//
// A bag number is the order's position in the load it came off — Joe is Bag 1
// of the five that left Vidhya Kitchen. Recomputing that later from whatever
// list happens to be on screen is how identity breaks: deliver Joe, and a
// position-derived Arun silently becomes Bag 1. The rider is then looking for a
// bag labelled 1 that is in a customer's hallway.
// A kitchen that prints labels prints them once, at the counter, and the label
// is what the rider matches against the object in his hands for the rest of the
// day. Recomputing anything from whatever list happens to be on screen is how
// identity breaks — deliver one order and a position-derived label silently
// moves to another, sending the rider looking for something that is already in
// a customer's hallway.
//
// So the pairing is fixed once, at the counter, at the moment the manifest is
// confirmed — and read back unchanged through delivery, skip, completion and a
// day of intermittent signal. See [BagManifest], which derives it; this only
// remembers what it derived.
// So the pairing is fixed once, at the moment the manifest is confirmed, and
// read back unchanged through delivery, skip, completion and a day of
// intermittent signal. See [OrderManifest.labelFor], which reads it off the
// payload; this only remembers what was read.
//
// ── It is empty on most routes now, and that is correct ──
//
// This used to hold `Bag 1 … Bag n`, manufactured from each order's position
// when the payload printed nothing — so every route stored a set of references
// no counter had ever issued. Only a label the kitchen actually printed is
// stored now, which on a route that prints none means nothing is stored at all
// and every screen simply shows no tag.
// ─────────────────────────────────────────────────────────────────────────
const String _kBagLabelsKeyBase = 'order_bag_labels';
const String _kOrderLabelsKeyBase = 'order_bag_labels';
/// Remembers which bag each order travels in. Merges, never replaces: a rider
/// works two kitchens and the second load must not erase the first.
Future<void> saveBagLabels(Map<String, String> byOrderId) async {
/// Remembers the label a counter printed on each order. Merges, never
/// replaces: a rider works two kitchens and the second load must not erase the
/// first. Orders with no printed label contribute nothing.
Future<void> saveOrderLabels(Map<String, String> byOrderId) async {
final clean = {
for (final e in byOrderId.entries)
if (e.key.trim().isNotEmpty && e.value.trim().isNotEmpty)
@@ -823,19 +861,20 @@ Future<void> saveBagLabels(Map<String, String> byOrderId) async {
if (clean.isEmpty) return;
final prefs = await SharedPreferences.getInstance();
final merged = {...await getBagLabels(), ...clean};
final merged = {...await getOrderLabels(), ...clean};
// Stored as `id\u0000label` rows: SharedPreferences has no map type, and a
// NUL separator cannot collide with an order id or a bag label.
await prefs.setStringList(await _scopedKey(_kBagLabelsKeyBase), [
// NUL separator cannot collide with an order id or a printed label.
await prefs.setStringList(await _scopedKey(_kOrderLabelsKeyBase), [
for (final e in merged.entries) '${e.key}\u0000${e.value}',
]);
}
/// The bag each order is in, as recorded at pickup. Empty before any load.
Future<Map<String, String>> getBagLabels() async {
/// The label each order carries, as recorded at pickup. Empty before any load,
/// and empty all day on a route whose counters print nothing.
Future<Map<String, String>> getOrderLabels() async {
final prefs = await SharedPreferences.getInstance();
final rows =
prefs.getStringList(await _scopedKey(_kBagLabelsKeyBase)) ??
prefs.getStringList(await _scopedKey(_kOrderLabelsKeyBase)) ??
const <String>[];
return {
for (final row in rows)
@@ -854,7 +893,7 @@ Future<void> clearServiceRunState() async {
await prefs.remove(await _scopedKey(_kCollectedOrderIdsKeyBase));
await prefs.remove(await _scopedKey(_kOutForDeliveryKeyBase));
await prefs.remove(await _scopedKey(_kNotLoadedKeyBase));
await prefs.remove(await _scopedKey(_kBagLabelsKeyBase));
await prefs.remove(await _scopedKey(_kOrderLabelsKeyBase));
}
// ─────────────────────────────────────────────────────────────────────────
@@ -933,16 +972,16 @@ Future<int> purgeDemoRecords() async {
// Harmless on its own, but this store exists to be the one place that knows
// which bag an order is in, and a stale entry is exactly the kind of thing
// that is trusted later precisely because it is stored.
final bags =
prefs.getStringList(await _scopedKey(_kBagLabelsKeyBase)) ??
final labels =
prefs.getStringList(await _scopedKey(_kOrderLabelsKeyBase)) ??
const <String>[];
if (bags.isNotEmpty) {
final kept = bags
if (labels.isNotEmpty) {
final kept = labels
.where((row) => !_looksLikeDemoId(row.split('\u0000').first))
.toList();
if (kept.length != bags.length) {
dropped += bags.length - kept.length;
await prefs.setStringList(await _scopedKey(_kBagLabelsKeyBase), kept);
if (kept.length != labels.length) {
dropped += labels.length - kept.length;
await prefs.setStringList(await _scopedKey(_kOrderLabelsKeyBase), kept);
}
}