support new fields in adding
All checks were successful
Release / meta (push) Successful in 9s
Release / linux-build (push) Successful in 6m54s
Release / android-build (push) Successful in 23m30s
Release / release-master (push) Successful in 30s
Release / release-dev (push) Successful in 32s

This commit is contained in:
2025-12-31 18:23:37 +00:00
parent e1ad1ea685
commit 1c15546b66
9 changed files with 1302 additions and 252 deletions

View File

@@ -27,10 +27,6 @@ class _TripsPageState extends State<TripsPage> {
_tripLocoStatsFutures.clear();
final data = context.read<DataService>();
await data.fetchTripDetails();
if (!mounted) return;
for (final trip in data.tripDetails) {
_tripStatsFuture(trip.id);
}
}
Future<void> _renameTrip(TripDetail trip, String newName) async {
@@ -42,10 +38,7 @@ class _TripsPageState extends State<TripsPage> {
"trip_id": trip.id,
"trip_name": newName,
});
await Future.wait([
data.fetchTripDetails(),
data.fetchTrips(),
]);
await data.fetchTripDetails();
} catch (e) {
messenger?.showSnackBar(
SnackBar(content: Text('Failed to rename trip: $e')),
@@ -54,10 +47,24 @@ class _TripsPageState extends State<TripsPage> {
}
}
Future<List<TripLocoStat>> _tripStatsFuture(int tripId) {
List<TripLocoStat> _cachedTripStats(
TripDetail trip,
TripSummary? summary,
) {
if (trip.locoStats.isNotEmpty) return trip.locoStats;
if (summary?.locoStats.isNotEmpty == true) return summary!.locoStats;
return const [];
}
Future<List<TripLocoStat>> _loadTripStats(
TripDetail trip,
TripSummary? summary,
) {
final cached = _cachedTripStats(trip, summary);
if (cached.isNotEmpty) return Future.value(cached);
return _tripLocoStatsFutures.putIfAbsent(
tripId,
() => context.read<DataService>().fetchTripLocoStats(tripId),
trip.id,
() => context.read<DataService>().fetchTripLocoStats(trip.id),
);
}
@@ -93,7 +100,10 @@ class _TripsPageState extends State<TripsPage> {
Widget build(BuildContext context) {
final data = context.watch<DataService>();
final tripDetails = data.tripDetails;
final tripSummaries = data.trips;
final tripSummaries = data.tripList;
final summaryById = {
for (final summary in tripSummaries) summary.tripId: summary,
};
final showLoading = data.isTripDetailsLoading && tripDetails.isEmpty;
return RefreshIndicator(
@@ -184,18 +194,25 @@ class _TripsPageState extends State<TripsPage> {
}
final trip = tripDetails[index - 1];
return _buildTripCard(context, trip);
final summary = summaryById[trip.id];
return _buildTripCard(context, trip, summary);
},
),
);
}
Widget _buildTripCard(BuildContext context, TripDetail trip) {
Widget _buildTripCard(
BuildContext context,
TripDetail trip,
TripSummary? summary,
) {
final legs = trip.legs;
final legCount = trip.legCount > 0 ? trip.legCount : legs.length;
final legCount =
trip.legCount > 0 ? trip.legCount : summary?.legCount ?? legs.length;
final dateRange = _formatDateRange(legs);
final endpoints = _formatEndpoints(legs);
final statsFuture = _tripStatsFuture(trip.id);
final stats = _cachedTripStats(trip, summary);
final winnerCount = stats.where((e) => e.won).length;
return Card(
child: Padding(
@@ -245,50 +262,25 @@ class _TripsPageState extends State<TripsPage> {
],
),
const SizedBox(height: 12),
FutureBuilder<List<TripLocoStat>>(
future: statsFuture,
builder: (context, snapshot) {
final chips = <Widget>[
_buildMetaChip(context, Icons.timeline, '$legCount legs'),
if (dateRange != null)
_buildMetaChip(context, Icons.calendar_month, dateRange),
if (endpoints != null)
_buildMetaChip(context, Icons.route, endpoints),
];
final stats = snapshot.data ?? const [];
final hasStats = stats.isNotEmpty;
final loading =
snapshot.connectionState == ConnectionState.waiting;
if (loading && !hasStats) {
chips.add(
_buildMetaChip(context, Icons.train, 'Loading traction...'),
);
} else if (hasStats) {
final winnerCount = stats.where((e) => e.won).length;
chips.add(
_buildMetaChip(context, Icons.train, '${stats.length} had'),
);
chips.add(
_buildMetaChip(
context,
Icons.emoji_events_outlined,
'$winnerCount winners',
),
);
} else if (snapshot.connectionState == ConnectionState.done) {
chips.add(
_buildMetaChip(context, Icons.train, 'No traction yet'),
);
}
return Wrap(
spacing: 8,
runSpacing: 8,
children: chips,
);
},
Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildMetaChip(context, Icons.timeline, '$legCount legs'),
if (dateRange != null)
_buildMetaChip(context, Icons.calendar_month, dateRange),
if (endpoints != null)
_buildMetaChip(context, Icons.route, endpoints),
if (stats.isNotEmpty) ...[
_buildMetaChip(context, Icons.train, '${stats.length} had'),
_buildMetaChip(
context,
Icons.emoji_events_outlined,
'$winnerCount winners',
),
] else
_buildMetaChip(context, Icons.train, 'No traction yet'),
],
),
const SizedBox(height: 12),
Align(
@@ -301,7 +293,7 @@ class _TripsPageState extends State<TripsPage> {
OutlinedButton.icon(
icon: const Icon(Icons.train),
label: const Text('Locos'),
onPressed: () => _showTripWinners(context, trip),
onPressed: () => _showTripWinners(context, trip, summary),
),
FilledButton.icon(
icon: const Icon(Icons.open_in_new),
@@ -532,14 +524,19 @@ class _TripsPageState extends State<TripsPage> {
);
}
void _showTripWinners(BuildContext context, TripDetail trip) {
void _showTripWinners(
BuildContext context,
TripDetail trip,
TripSummary? summary,
) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) {
return SafeArea(
child: FutureBuilder<List<TripLocoStat>>(
future: _tripStatsFuture(trip.id),
future: _loadTripStats(trip, summary),
initialData: _cachedTripStats(trip, summary),
builder: (ctx, snapshot) {
final items = snapshot.data ?? [];
final loading =