1 Commits

Author SHA1 Message Date
292163bda6 Fix trips on entry page, correct order of trips on dashboard and trip page, move to prod api
Some checks failed
Release / meta (push) Successful in 17s
Release / linux-build (push) Successful in 1m33s
Release / android-build (push) Successful in 15m28s
Release / release-dev (push) Failing after 4s
Release / release-master (push) Successful in 29s
2025-12-12 09:17:18 +00:00
6 changed files with 180 additions and 110 deletions

View File

@@ -39,11 +39,7 @@ class Dashboard extends StatelessWidget {
children: [
_buildHeader(context, auth, stats, data.isHomepageLoading),
const SizedBox(height: 12),
Wrap(
spacing: 12,
runSpacing: 12,
children: metricChips,
),
Wrap(spacing: 12, runSpacing: 12, children: metricChips),
const SizedBox(height: 16),
isWide
? Row(
@@ -71,8 +67,12 @@ class Dashboard extends StatelessWidget {
);
}
Widget _buildHeader(BuildContext context, AuthService auth,
HomepageStats? stats, bool loading) {
Widget _buildHeader(
BuildContext context,
AuthService auth,
HomepageStats? stats,
bool loading,
) {
final greetingName =
stats?.user?.full_name ?? auth.fullName ?? auth.username ?? 'there';
return Row(
@@ -82,10 +82,7 @@ class Dashboard extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Dashboard',
style: Theme.of(context).textTheme.labelMedium,
),
Text('Dashboard', style: Theme.of(context).textTheme.labelMedium),
const SizedBox(height: 2),
Text(
'Welcome back, $greetingName',
@@ -93,7 +90,8 @@ class Dashboard extends StatelessWidget {
),
],
),
if (loading) const Padding(
if (loading)
const Padding(
padding: EdgeInsets.only(right: 8.0),
child: SizedBox(
height: 24,
@@ -121,11 +119,13 @@ class Dashboard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(label.toUpperCase(),
Text(
label.toUpperCase(),
style: textTheme.labelSmall?.copyWith(
letterSpacing: 0.7,
color: textTheme.bodySmall?.color?.withOpacity(0.7),
)),
),
),
const SizedBox(height: 4),
Text(
value,
@@ -203,10 +203,9 @@ class Dashboard extends StatelessWidget {
children: [
Text(
title,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.w700),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
),
),
Row(
mainAxisSize: MainAxisSize.min,
@@ -234,10 +233,7 @@ class Dashboard extends StatelessWidget {
required String emptyMessage,
}) {
if (legs.isEmpty) {
return Text(
emptyMessage,
style: Theme.of(context).textTheme.bodyMedium,
);
return Text(emptyMessage, style: Theme.of(context).textTheme.bodyMedium);
}
return Column(
children: legs.take(5).map((leg) {
@@ -257,10 +253,9 @@ class Dashboard extends StatelessWidget {
if (leg.headcode.isNotEmpty)
Text(
leg.headcode,
style: Theme.of(context)
.textTheme
.labelSmall
?.copyWith(color: Theme.of(context).hintColor),
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).hintColor,
),
),
],
),
@@ -286,7 +281,11 @@ class Dashboard extends StatelessWidget {
}
Widget _buildTripsCard(BuildContext context, DataService data) {
final trips = data.trips;
final trips_unsorted = data.trips;
List trips = [];
if (trips_unsorted.isNotEmpty) {
trips = [...trips_unsorted]..sort((a, b) => b.tripId.compareTo(a.tripId));
}
return _buildCard(
context,
title: 'Trips',

View File

@@ -38,6 +38,8 @@ class _TractionPageState extends State<TractionPage> {
final _domainController = TextEditingController();
final _typeController = TextEditingController();
int offset = 0;
@override
void initState() {
super.initState();
@@ -239,8 +241,7 @@ class _TractionPageState extends State<TractionPage> {
onSubmitted: (_) => _refreshTraction(),
);
},
optionsViewBuilder:
(context, onSelected, options) {
optionsViewBuilder: (context, onSelected, options) {
final optionList = options.toList();
if (optionList.isEmpty) {
return const SizedBox.shrink();
@@ -306,7 +307,7 @@ class _TractionPageState extends State<TractionPage> {
),
FilterChip(
label: Text(
_mileageFirst ? 'Mileage first' : 'Had first',
_mileageFirst ? 'Mileage first' : 'Number order',
),
selected: _mileageFirst,
onSelected: (v) {

View File

@@ -46,11 +46,15 @@ class _TripsPageState extends State<TripsPage> {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Journeys',
style: Theme.of(context).textTheme.labelMedium),
Text(
'Journeys',
style: Theme.of(context).textTheme.labelMedium,
),
const SizedBox(height: 2),
Text('Trips',
style: Theme.of(context).textTheme.headlineSmall),
Text(
'Trips',
style: Theme.of(context).textTheme.headlineSmall,
),
],
),
Row(
@@ -81,10 +85,9 @@ class _TripsPageState extends State<TripsPage> {
children: [
Text(
'No trips yet',
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.w700),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 8),
const Text(
@@ -101,8 +104,9 @@ class _TripsPageState extends State<TripsPage> {
(trip) => Card(
child: ListTile(
title: Text(trip.tripName),
subtitle:
Text('${trip.tripMileage.toStringAsFixed(1)} mi'),
subtitle: Text(
'${trip.tripMileage.toStringAsFixed(1)} mi',
),
),
),
)
@@ -110,8 +114,9 @@ class _TripsPageState extends State<TripsPage> {
)
else
Column(
children:
tripDetails.map((trip) => _buildTripCard(context, trip, isMobile)).toList(),
children: tripDetails
.map((trip) => _buildTripCard(context, trip, isMobile))
.toList(),
),
],
),
@@ -134,10 +139,9 @@ class _TripsPageState extends State<TripsPage> {
children: [
Text(
trip.name,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.w700),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
),
),
Text(
'${trip.mileage.toStringAsFixed(1)} mi · ${trip.legCount} legs',
@@ -145,6 +149,14 @@ class _TripsPageState extends State<TripsPage> {
),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IconButton(
icon: const Icon(Icons.train),
tooltip: 'Traction',
onPressed: () => _showTripWinners(context, trip),
),
IconButton(
icon: const Icon(Icons.open_in_new),
tooltip: 'Details',
@@ -152,6 +164,8 @@ class _TripsPageState extends State<TripsPage> {
),
],
),
],
),
const SizedBox(height: 8),
if (legs.isNotEmpty)
Column(
@@ -168,10 +182,9 @@ class _TripsPageState extends State<TripsPage> {
),
trailing: Text(
leg.mileage?.toStringAsFixed(1) ?? '-',
style: Theme.of(context)
.textTheme
.labelLarge
?.copyWith(fontWeight: FontWeight.bold),
style: Theme.of(context).textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
);
}).toList(),
@@ -215,10 +228,9 @@ class _TripsPageState extends State<TripsPage> {
),
Text(
trip.name,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const Spacer(),
Text('${trip.mileage.toStringAsFixed(1)} mi'),
@@ -237,9 +249,63 @@ class _TripsPageState extends State<TripsPage> {
subtitle: Text(_formatDate(leg.beginTime)),
trailing: Text(
leg.mileage?.toStringAsFixed(1) ?? '-',
style: Theme.of(context)
.textTheme
.labelLarge
style: Theme.of(context).textTheme.labelLarge
?.copyWith(fontWeight: FontWeight.bold),
),
);
},
),
),
],
),
),
);
},
);
}
void _showTripWinners(BuildContext context, TripDetail trip) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
Text(
trip.name,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const Spacer(),
Text('${trip.mileage.toStringAsFixed(1)} mi'),
],
),
const SizedBox(height: 8),
SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: ListView.builder(
itemCount: trip.legs.length,
itemBuilder: (context, index) {
final leg = trip.legs[index];
return ListTile(
leading: const Icon(Icons.train),
title: Text('${leg.start}${leg.end}'),
subtitle: Text(_formatDate(leg.beginTime)),
trailing: Text(
leg.mileage?.toStringAsFixed(1) ?? '-',
style: Theme.of(context).textTheme.labelLarge
?.copyWith(fontWeight: FontWeight.bold),
),
);

View File

@@ -24,7 +24,7 @@ void main() {
providers: [
Provider<ApiService>(
create: (_) {
api = ApiService(baseUrl: 'https://dev.mileograph.co.uk/api/v1');
api = ApiService(baseUrl: 'https://mileograph.co.uk/api/v1');
return api;
},
),

View File

@@ -117,7 +117,8 @@ class DataService extends ChangeNotifier {
);
}
final buffer = StringBuffer(
'?sort_direction=$sortDirection&sort_by=$sortBy&offset=$offset&limit=$limit');
'?sort_direction=$sortDirection&sort_by=$sortBy&offset=$offset&limit=$limit',
);
if (dateRangeStart != null && dateRangeStart.isNotEmpty) {
buffer.write('&date_range_start=$dateRangeStart');
}
@@ -178,7 +179,7 @@ class DataService extends ChangeNotifier {
try {
final params = StringBuffer('?limit=$limit&offset=$offset');
if (hadOnly) params.write('&had_only=true');
if (mileageFirst) params.write('&mileage_first=true');
if (!mileageFirst) params.write('&mileage_first=false');
final payload = <String, dynamic>{};
if (locoClass != null && locoClass.isNotEmpty) {
@@ -203,7 +204,7 @@ class DataService extends ChangeNotifier {
if (json is List) {
final newItems = json.map((e) => LocoSummary.fromJson(e)).toList();
_traction = append ? [..._traction, ...newItems] : newItems;
_tractionHasMore = newItems.length >= limit;
_tractionHasMore = newItems.length >= limit - 1;
} else {
throw Exception('Unexpected traction response: $json');
}
@@ -245,12 +246,13 @@ class DataService extends ChangeNotifier {
try {
final json = await api.get('/trips/legs-and-stats');
if (json is List) {
_tripDetails = json.map((e) => TripDetail.fromJson(e)).toList();
final trip_map = json.map((e) => TripDetail.fromJson(e)).toList();
_tripDetails = [...trip_map]..sort((a, b) => b.id.compareTo(a.id));
} else {
_tripDetails = [];
}
} catch (e) {
debugPrint('Failed to fetch trips: $e');
debugPrint('Failed to fetch trip_map: $e');
_tripDetails = [];
} finally {
_isTripDetailsLoading = false;
@@ -260,7 +262,7 @@ class DataService extends ChangeNotifier {
Future<void> fetchTrips() async {
try {
final json = await api.get('/trips');
final json = await api.get('/trips/mileage');
Iterable<dynamic>? raw;
if (json is List) {
raw = json;
@@ -274,10 +276,12 @@ class DataService extends ChangeNotifier {
}
}
if (raw != null) {
_tripList = raw
final trip_map = raw
.whereType<Map<String, dynamic>>()
.map((e) => TripSummary.fromJson(e))
.toList();
_tripList = [...trip_map]..sort((a, b) => b.tripId.compareTo(a.tripId));
} else {
debugPrint('Unexpected trip list response: $json');
_tripList = [];

View File

@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 0.1.1+1
version: 0.1.2+1
environment:
sdk: ^3.8.1