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

@@ -16,7 +16,8 @@ import 'package:miler/views/helpers/constants/design_constants.dart';
import 'package:miler/views/helpers/constants/miler_type.dart';
import 'package:miler/views/helpers/constants/Font_constant.dart';
import 'package:miler/Models/stop_status.dart';
import 'package:miler/data/bag_manifest.dart';
import 'package:miler/data/order_manifest.dart';
import 'package:miler/data/stop_area.dart';
import 'package:miler/data/milk_run.dart';
import 'package:miler/data/service_profile.dart';
@@ -258,6 +259,10 @@ class TripCard extends StatelessWidget {
// on the same footing. See [Trip.outstanding].
remaining: trip.outstanding(
collectedIds: collectedIds,
// Work he has declined is not work in front of him — see the note on
// [Trip.outstanding]. Without this the Distance cell measured a route
// through stops the rider had refused.
rejectedIds: rejectedIds,
originLat: riderLat,
originLng: riderLng,
),
@@ -311,7 +316,7 @@ class TripCard extends StatelessWidget {
for (var i = 0; i < trip.stops.length; i++) {
if (_stateOf(trip.stops[i]) != rung) continue;
final stop = trip.stops[i];
final key = MilkRun.sourceKeyOf(stop);
final key = MilkRun.sourceKeyOf(stop, within: trip.stops);
final grouped = ServiceProfile.active.sourceIsKitchen && key.isNotEmpty;
final stops = grouped
? [
@@ -430,13 +435,22 @@ class TripCard extends StatelessWidget {
// the screen. See [RouteGroup.allStops].
for (var i = 0; i < trip.stops.length; i++) {
final stop = trip.stops[i];
// Key on the id where there is one, falling back to the name: the id is
// what the hub controls, but a payload that only carries a name still has
// to group rather than collapsing every stop into one nameless pile.
// ── One reader for "which counter is this", not two ──
//
// This built its own key — `stopSourceId`, falling back to
// `stopSourceName` — while [MilkRun.sourceKeyOf] is the function that
// exists to answer exactly this, and is what the bulk collect, the
// per-kitchen gate and `sameSourceAs` all key on.
//
// They did not agree. `stopSourceId` reads `kitchenid` and `sourceid`;
// `sourceKeyOf` reads those **and `pickuplocationid`** — so a row
// identified only by its location id was one place according to the
// collect and a different one according to the card that offers it. That
// is the same two-readers-one-question failure `sourceNameOf` documents,
// repeated on the id, and it splits a kitchen's orders across two groups
// on the screen while the collect still treats them as one.
final key = grouped
? (stopSourceId(stop).isNotEmpty
? stopSourceId(stop)
: stopSourceName(stop))
? MilkRun.sourceKeyOf(stop, within: trip.stops)
: 'stop:${_idOf(stop)}';
if (!buckets.containsKey(key)) {
buckets[key] = <int>[];
@@ -473,17 +487,56 @@ class TripCard extends StatelessWidget {
groups.add(() {
final stops = visible;
final first = all.first;
// ── An unnamed counter is named by where it is ──
//
// A grouped place with no source name fell through to
// `MilkRun.navigationLabel`, which answers with the **customer** — so
// the heading over a dropdown read as one more order rather than as the
// place the orders under it are collected from.
//
// The address is what names a place the payload did not name, and it is
// what the rider wants off that line anyway: he is deciding where to
// ride, not who to hand a bag to.
final place = compactAddress(pickupAddressOf(first));
// ── When a lone stop is a *place*, and when it is the stop ──
//
// `flat` means the header **is** the stop: no counter above it worth
// naming, so the card is titled with the customer and opens the detail
// sheet instead of a list. That is always right on a parcel route,
// where the rider goes to one door for one consignment.
//
// On a kitchen line it was reached by a different route and gave the
// wrong answer. A lone stop was flat whenever the payload carried **no
// source name** — and a missing name is a gap in the data, not evidence
// that there is no counter. So an order whose row lost its kitchen name
// was drawn as a bare header with the customer's name on it, sitting
// above the dropdown its seven siblings were in: seven bags in a list
// and one stranded over it, off the same shelf.
//
// [MilkRun.sourceKeyOf] now groups such a row back in by its address,
// which fixes that case at the root. This is the same correction one
// level down, for a place that genuinely does hold a single order: on a
// kitchen line a lone stop is only flat when there is **nothing at all**
// to name its place with — no counter name and no address. With an
// address it is a place holding one order, headed by where it is, with
// the order in the list under it.
final flat =
all.length == 1 && (!grouped || stopSourceName(first).isEmpty);
// A grouped place is named by its counter. A flat one *is* the stop, so
// it is named by the customer — `MilkRun.navigationLabel` answers
// "pickup" for a parcel that has not been collected yet, which is a
// description of the leg rather than a name the rider can read off a
// gate.
all.length == 1 &&
(!grouped || (stopSourceName(first).isEmpty && place.isEmpty));
// A grouped place is named by its counter, or failing that by where it
// is. A flat one *is* the stop, so it is named by the customer —
// `MilkRun.navigationLabel` answers "pickup" for a parcel that has not
// been collected yet, which is a description of the leg rather than a
// name the rider can read off a gate.
final name = flat
? BagManifest.customerOf(first)
? OrderManifest.customerOf(first)
: (grouped
? stopSourceName(first)
? (stopSourceName(first).trim().isNotEmpty
? stopSourceName(first)
: place)
: MilkRun.navigationLabel(first));
return RouteGroup(
key: key,
@@ -493,6 +546,20 @@ class TripCard extends StatelessWidget {
name: name.trim().isEmpty
? MilkRun.navigationLabel(first)
: name.trim(),
// ── The counter's street, under its name ──
//
// Grouped places only. A flat node *is* the stop and is already named
// by its customer or its own address, so an address line under it
// would print the same place twice; a kitchen is a name the rider has
// to convert into a location, and until now the only thing that could
// do that conversion was the preview sheet.
//
// Trimmed through [compactAddress] — the geocoder appends
// `Tamil Nadu, 641002, India` to every row, which is three
// components that are identical on every stop of every route and
// would push the part that names the street off the end of the line.
// Not repeated when it is already the heading — see [name] above.
location: flat || name.trim() == place ? '' : place,
stops: stops,
allStops: all,
firstIndex: indices.first,
@@ -706,13 +773,12 @@ class TripCard extends StatelessWidget {
for (var i = 0; i < trip.stops.length; i++) {
if (!_showsOnHome(_stateOf(trip.stops[i]))) continue;
// Key on the id where there is one, falling back to the name: the id is
// what the hub controls, but a payload that only carries a name still has
// to group rather than collapsing every stop into one nameless pile.
// [MilkRun.sourceKeyOf], the same reader [routeGroups] uses and the same
// one the bulk collect keys on. This had its own id-then-name expression,
// which misses `pickuplocationid` and therefore split a kitchen the
// collect still treats as one — see the note in [routeGroups].
final stop = trip.stops[i];
final key = stopSourceId(stop).isNotEmpty
? stopSourceId(stop)
: stopSourceName(stop);
final key = MilkRun.sourceKeyOf(stop);
if (!groups.containsKey(key)) {
groups[key] = <int>[];
order.add(key);
@@ -804,11 +870,11 @@ class TripCard extends StatelessWidget {
// him comparing "5 meals" on the phone against a shelf of bags.
// The count he can *check* is orders against bags, and under
// this workflow they are the same number by definition. See
// [BagManifest].
// [OrderManifest].
Text(
pending > 0
? '${BagManifest.countLabel(indices.length)} · $pending to accept'
: BagManifest.countLabel(indices.length),
? '${OrderManifest.countLabel(indices.length)} · $pending to accept'
: OrderManifest.countLabel(indices.length),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
@@ -1204,7 +1270,7 @@ class TripCard extends StatelessWidget {
state: state,
address: address,
service: roundTripDuration(Trip.serviceTimeFor(stop)),
bag: _bagOf(stop),
label: _labelOf(stop),
// The kitchen heading above already names where this came from.
groupedUnderSource: ServiceProfile.active.sourceIsKitchen,
selected: selectedIds.contains(id),
@@ -1258,21 +1324,19 @@ class TripCard extends StatelessWidget {
// `_tag` is gone — it built the tinted Deliver / Collect / ₹ chips. See
// [_MetaLine], which says the same three things on one uncoloured line.
/// Which bag this order travels in.
/// The label the counter printed on this order, or `''`.
///
/// Derived from its position in **its own pickup group**, not from the whole
/// route: the rider counts bags off one counter at a time, so the third order
/// from Kitchen 2 is *its* Bag 3 and not the route's ninth. A label the
/// kitchen actually printed always wins — see [BagManifest].
String _bagOf(Map<String, dynamic> stop) {
if (!ServiceProfile.active.sourceIsKitchen) return stopBagLabel(stop);
final key = MilkRun.sourceKeyOf(stop);
final group = [
for (final s in trip.stops)
if (MilkRun.sourceKeyOf(s) == key) s,
];
return BagManifest.bagFor(stop, group);
}
/// ── It used to be derived, and that was the problem ──
///
/// This computed the order's position in its own pickup group and returned
/// `Bag 3` — the app numbering a list it had built and presenting the result
/// as something stencilled on an object. On a route where no counter prints
/// labels that was every row, and it sat two columns from a rail numbering
/// the same rows.
///
/// Only what the payload actually carries survives. See
/// [OrderManifest.labelFor].
String _labelOf(Map<String, dynamic> stop) => OrderManifest.labelFor(stop);
// ── Footer: accept remaining, or go work the trip ──────────────────────
Widget _footerBar(List<String> pendingIds) {