production

This commit is contained in:
2026-08-28 11:13:15 +05:30
parent d7348e253f
commit 5723d373b2
162 changed files with 17924 additions and 7026 deletions

View File

@@ -79,6 +79,10 @@ class TripCard extends StatelessWidget {
/// Opens a single stop's detail sheet (map + full order details).
final void Function(Map<String, dynamic> stop)? onStopTap;
/// One order's sheet, from a row inside an expanded place — see
/// [RouteTimeline.onOrderTap]. Falls back to [onStopTap] when unset.
final void Function(Map<String, dynamic> stop)? onOrderTap;
/// Rings the customer for this stop.
final void Function(Map<String, dynamic> stop)? onCallStop;
@@ -145,6 +149,7 @@ class TripCard extends StatelessWidget {
this.onSelectAll,
this.onUnrejectStop,
this.onStopTap,
this.onOrderTap,
this.onCallStop,
this.onViewRoute,
this.onContinueStop,
@@ -175,7 +180,7 @@ class TripCard extends StatelessWidget {
static bool _isChoosable(StopState state) =>
state == StopState.pending ||
(ServiceProfile.active.handsOffAtCollection &&
(state == StopState.accepted || state == StopState.active));
(state == StopState.accepted || state.isAtSource));
@override
Widget build(BuildContext context) {
@@ -247,6 +252,15 @@ class TripCard extends StatelessWidget {
/// four surfaces it replaced were the wrong price for the information.
Widget buildSummary() => TripBriefStrip(
trip: trip,
// What the four figures describe: the collections still owed, measured
// from where the rider is standing — the same origin the page used to
// build the trip in the first place, so the recount and the original are
// on the same footing. See [Trip.outstanding].
remaining: trip.outstanding(
collectedIds: collectedIds,
originLat: riderLat,
originLng: riderLng,
),
brief: brief ?? RouteBrief.empty,
percent: trip.completionPercent(
acceptedIds: acceptedIds,
@@ -287,7 +301,13 @@ class TripCard extends StatelessWidget {
int index,
})?
_nextGroup() {
for (final rung in const [StopState.active, StopState.accepted]) {
// Arrived outranks active: he is at that counter now, and a stop he has
// only been routed to can wait.
for (final rung in const [
StopState.arrived,
StopState.active,
StopState.accepted,
]) {
for (var i = 0; i < trip.stops.length; i++) {
if (_stateOf(trip.stops[i]) != rung) continue;
final stop = trip.stops[i];
@@ -351,6 +371,7 @@ class TripCard extends StatelessWidget {
onSelectAll: onSelectAll,
onToggleSelect: onToggleSelect,
onStopTap: onStopTap,
onOrderTap: onOrderTap ?? onStopTap,
// One journey action per place. Five Navigate buttons pointing at
// one counter was the largest single source of clutter on the old
// screen, and the rider had to work out whether they differed.
@@ -477,6 +498,10 @@ class TripCard extends StatelessWidget {
firstIndex: indices.first,
state: stops.isEmpty ? StopState.collected : _groupRung(stops),
stopStates: [for (final s in stops) _stateOf(s)],
// The whole bucket's states, so the card can tell an order that is
// still this counter's business from one already delivered — see
// [RouteGroup.groupSize].
allStopStates: [for (final s in all) _stateOf(s)],
travel: trip.travelTimeToStop(indices.first),
// ── Measured, or absent ──
//
@@ -530,7 +555,7 @@ class TripCard extends StatelessWidget {
// give. `trip_brief_layout_test.dart` sweeps for exactly this.
Flexible(
child: Text(
ServiceProfile.active.endsAtHub ? 'RETURN · HUB' : 'END · HOME',
ServiceProfile.active.endsAtHub ? 'RETURN · BASE' : 'END · HOME',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: MilerType.eyebrow,
@@ -901,17 +926,17 @@ class TripCard extends StatelessWidget {
isStart
? (ServiceProfile.active.sourceIsKitchen
? 'START · KITCHEN'
: 'START · HUB')
: 'START · BASE')
// ── A milk run does not return anything ──
//
// He loads at the kitchen and his last address is a
// customer's door: there is no hub at the end of
// customer's door: there is no base at the end of
// his day and nothing to carry back to one. The
// logistics rider genuinely does return collected
// shipments to the depot, so the label follows the
// capability rather than the screen position.
: (ServiceProfile.active.endsAtHub
? 'RETURN · HUB'
? 'RETURN · BASE'
: 'END · HOME'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -1076,7 +1101,9 @@ class TripCard extends StatelessWidget {
/// what kept the row here in the first place.
static bool _showsOnHome(StopState state) =>
state == StopState.pending ||
state == StopState.active ||
// Arrived belongs here for the same reason active does, and it is the
// reason this reads as a predicate: see [StopStateX.isAtSource].
state.isAtSource ||
// Accepted stays only where the rider works that rung from this screen;
// on a parcel route it moves to Bookings the moment he takes it.
(state == StopState.accepted &&
@@ -1089,7 +1116,9 @@ class TripCard extends StatelessWidget {
/// heading cannot end up describing different rungs.
StopStatus _rungOf(StopState state) => switch (state) {
StopState.accepted => StopStatus.accepted,
StopState.active => StopStatus.arrived,
// Both: this answers *which rung is the stop on*, and it is the same for
// either — he is at the counter and the next thing he does is collect.
StopState.arrived || StopState.active => StopStatus.arrived,
_ => StopStatus.unknown,
};
@@ -1161,7 +1190,7 @@ class TripCard extends StatelessWidget {
// An **arrived** stop keeps a card-level rung, because by then the batch
// question is settled: he is at the counter, and the sheet it opens acts
// on every order from that counter anyway ([_advanceStopWithSheet]).
final advance = state == StopState.active
final advance = state.isAtSource
? (MilkRun.nextActionLabel(_rungOf(state)) == null
? null
: 'Mark as picked')
@@ -1281,7 +1310,7 @@ class TripCard extends StatelessWidget {
// shrinking.
Flexible(
child: Text(
'Trip complete · head back to the hub',
'Trip complete · head back to base',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(