new pipeline
Some checks failed
Release / meta (push) Successful in 2s
Release / android-build (push) Failing after 15s
Release / linux-build (push) Failing after 34s
Release / windows-build (push) Has been cancelled
Release / release-dev (push) Has been cancelled
Release / release-master (push) Has been cancelled

This commit is contained in:
2025-12-11 01:24:44 +00:00
parent 40ee16d2d5
commit e34c689ed9
4 changed files with 232 additions and 155 deletions

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:mileograph_flutter/components/calculator/calculator.dart';
@@ -25,21 +27,19 @@ class _NewEntryPageState extends State<NewEntryPage> {
final _mileageController = TextEditingController();
final _networkController = TextEditingController();
bool _submitting = false;
bool _initialised = false;
bool _useManualMileage = false;
RouteResult? _routeResult;
final List<_TractionItem> _tractionItems = [_TractionItem.marker()];
int? _selectedTripId;
bool _tripsRequested = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!_tripsRequested) {
_tripsRequested = true;
context.read<DataService>().fetchTrips();
}
Future.microtask(() {
if (!mounted) return;
final data = context.read<DataService>();
data.fetchClassList();
data.fetchTrips();
});
}
@@ -140,21 +140,6 @@ class _NewEntryPageState extends State<NewEntryPage> {
}
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_initialised) {
_initialised = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<DataService>().fetchClassList();
if (!_tripsRequested) {
_tripsRequested = true;
context.read<DataService>().fetchTrips();
}
});
}
}
Future<void> _openCalculator() async {
final result = await Navigator.of(context).push<RouteResult>(
MaterialPageRoute(
@@ -304,6 +289,9 @@ class _NewEntryPageState extends State<NewEntryPage> {
};
await api.post('/add', body);
}
if (mounted) {
context.read<DataService>().refreshLegs();
}
try {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(

View File

@@ -150,11 +150,12 @@ class _TractionPageState extends State<TractionPage> {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Fleet',
style: Theme.of(context).textTheme.labelMedium),
Text('Fleet', style: Theme.of(context).textTheme.labelMedium),
const SizedBox(height: 2),
Text('Traction',
style: Theme.of(context).textTheme.headlineSmall),
Text(
'Traction',
style: Theme.of(context).textTheme.headlineSmall,
),
],
),
IconButton(
@@ -174,8 +175,10 @@ class _TractionPageState extends State<TractionPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Filters',
style: Theme.of(context).textTheme.titleMedium),
Text(
'Filters',
style: Theme.of(context).textTheme.titleMedium,
),
TextButton(
onPressed: _clearFilters,
child: const Text('Clear'),
@@ -199,24 +202,30 @@ class _TractionPageState extends State<TractionPage> {
(c) => c.toLowerCase().contains(query),
);
},
initialValue:
TextEditingValue(text: _classController.text),
fieldViewBuilder: (context, controller, focusNode,
onFieldSubmitted) {
controller.value = _classController.value;
return TextField(
controller: controller,
focusNode: focusNode,
decoration: const InputDecoration(
labelText: 'Class',
border: OutlineInputBorder(),
),
onChanged: (val) {
_classController.text = val;
initialValue: TextEditingValue(
text: _classController.text,
),
fieldViewBuilder:
(
context,
controller,
focusNode,
onFieldSubmitted,
) {
controller.value = _classController.value;
return TextField(
controller: controller,
focusNode: focusNode,
decoration: const InputDecoration(
labelText: 'Class',
border: OutlineInputBorder(),
),
onChanged: (val) {
_classController.text = val;
},
onSubmitted: (_) => _refreshTraction(),
);
},
onSubmitted: (_) => _refreshTraction(),
);
},
onSelected: (String selection) {
setState(() {
_selectedClass = selection;
@@ -249,7 +258,7 @@ class _TractionPageState extends State<TractionPage> {
),
),
FilterChip(
label: const Text('Had only'),
label: const Text('Had first'),
selected: _hadOnly,
onSelected: (v) {
setState(() => _hadOnly = v);
@@ -258,13 +267,18 @@ class _TractionPageState extends State<TractionPage> {
),
TextButton.icon(
onPressed: () => setState(
() => _showAdvancedFilters = !_showAdvancedFilters),
icon: Icon(_showAdvancedFilters
? Icons.expand_less
: Icons.expand_more),
label: Text(_showAdvancedFilters
? 'Hide filters'
: 'More filters'),
() => _showAdvancedFilters = !_showAdvancedFilters,
),
icon: Icon(
_showAdvancedFilters
? Icons.expand_less
: Icons.expand_more,
),
label: Text(
_showAdvancedFilters
? 'Hide filters'
: 'More filters',
),
),
ElevatedButton.icon(
onPressed: _refreshTraction,
@@ -398,10 +412,9 @@ class _TractionPageState extends State<TractionPage> {
children: [
Text(
'No traction found',
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('Try relaxing the filters or sync again.'),
@@ -449,8 +462,10 @@ class _TractionPageState extends State<TractionPage> {
icon: const Icon(Icons.arrow_back),
label: const Text('Back'),
style: TextButton.styleFrom(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
foregroundColor: Theme.of(context).colorScheme.onSurface,
),
),
@@ -484,25 +499,24 @@ class _TractionPageState extends State<TractionPage> {
children: [
Text(
'${loco.locoClass} ${loco.number}',
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(fontWeight: FontWeight.w700),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
),
),
if ((loco.name ?? '').isNotEmpty)
Text(
loco.name ?? '',
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(fontStyle: FontStyle.italic),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontStyle: FontStyle.italic,
),
),
],
),
Chip(
label: Text(status),
backgroundColor:
Theme.of(context).colorScheme.surfaceContainerHighest,
backgroundColor: Theme.of(
context,
).colorScheme.surfaceContainerHighest,
),
],
),
@@ -529,9 +543,11 @@ class _TractionPageState extends State<TractionPage> {
}
});
},
icon: Icon(isSelected
? Icons.remove_circle_outline
: Icons.add_circle_outline),
icon: Icon(
isSelected
? Icons.remove_circle_outline
: Icons.add_circle_outline,
),
label: Text(isSelected ? 'Remove' : 'Add to entry'),
),
],
@@ -551,17 +567,9 @@ class _TractionPageState extends State<TractionPage> {
value: (loco.trips ?? loco.journeys ?? 0).toString(),
),
if (operatorName.isNotEmpty)
_statPill(
context,
label: 'Operator',
value: operatorName,
),
_statPill(context, label: 'Operator', value: operatorName),
if (domain.isNotEmpty)
_statPill(
context,
label: 'Domain',
value: domain,
),
_statPill(context, label: 'Domain', value: domain),
],
),
],
@@ -570,8 +578,11 @@ class _TractionPageState extends State<TractionPage> {
);
}
Widget _statPill(BuildContext context,
{required String label, required String value}) {
Widget _statPill(
BuildContext context, {
required String label,
required String value,
}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
@@ -581,16 +592,12 @@ class _TractionPageState extends State<TractionPage> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'$label: ',
style: Theme.of(context).textTheme.labelSmall,
),
Text('$label: ', style: Theme.of(context).textTheme.labelSmall),
Text(
value,
style: Theme.of(context)
.textTheme
.labelSmall
?.copyWith(fontWeight: FontWeight.w700),
style: Theme.of(
context,
).textTheme.labelSmall?.copyWith(fontWeight: FontWeight.w700),
),
],
),
@@ -621,10 +628,9 @@ class _TractionPageState extends State<TractionPage> {
const SizedBox(width: 8),
Text(
'${loco.locoClass} ${loco.number}',
style: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(fontWeight: FontWeight.w700),
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w700,
),
),
],
),
@@ -647,10 +653,11 @@ class _TractionPageState extends State<TractionPage> {
_detailRow('Owner', loco.owner ?? ''),
_detailRow('Livery', loco.livery ?? ''),
_detailRow('Location', loco.location ?? ''),
_detailRow('Mileage', _formatNumber(loco.mileage ?? 0)),
_detailRow(
'Mileage', _formatNumber(loco.mileage ?? 0)),
_detailRow('Trips',
(loco.trips ?? loco.journeys ?? 0).toString()),
'Trips',
(loco.trips ?? loco.journeys ?? 0).toString(),
),
_detailRow('EVN', loco.evn ?? ''),
if (loco.notes != null && loco.notes!.isNotEmpty)
_detailRow('Notes', loco.notes!),
@@ -676,17 +683,13 @@ class _TractionPageState extends State<TractionPage> {
width: 110,
child: Text(
label,
style: Theme.of(context)
.textTheme
.labelMedium
?.copyWith(fontWeight: FontWeight.w600),
style: Theme.of(
context,
).textTheme.labelMedium?.copyWith(fontWeight: FontWeight.w600),
),
),
Expanded(
child: Text(
value,
style: Theme.of(context).textTheme.bodyMedium,
),
child: Text(value, style: Theme.of(context).textTheme.bodyMedium),
),
],
),

View File

@@ -5,11 +5,29 @@ import 'package:flutter/scheduler.dart';
import 'package:mileograph_flutter/objects/objects.dart';
import 'package:mileograph_flutter/services/apiService.dart'; // assumes you've moved HomepageStats + submodels to a separate file
class _LegFetchOptions {
final int limit;
final String sortBy;
final int sortDirection;
final String? dateRangeStart;
final String? dateRangeEnd;
const _LegFetchOptions({
this.limit = 100,
this.sortBy = 'date',
this.sortDirection = 0,
this.dateRangeStart,
this.dateRangeEnd,
});
}
class DataService extends ChangeNotifier {
final ApiService api;
DataService({required this.api});
_LegFetchOptions _lastLegsFetch = const _LegFetchOptions();
// Homepage Data
HomepageStats? _homepageStats;
HomepageStats? get homepageStats => _homepageStats;
@@ -89,6 +107,15 @@ class DataService extends ChangeNotifier {
bool append = false,
}) async {
_isLegsLoading = true;
if (!append) {
_lastLegsFetch = _LegFetchOptions(
limit: limit,
sortBy: sortBy,
sortDirection: sortDirection,
dateRangeStart: dateRangeStart,
dateRangeEnd: dateRangeEnd,
);
}
final buffer = StringBuffer(
'?sort_direction=$sortDirection&sort_by=$sortBy&offset=$offset&limit=$limit');
if (dateRangeStart != null && dateRangeStart.isNotEmpty) {
@@ -117,6 +144,16 @@ class DataService extends ChangeNotifier {
}
}
Future<void> refreshLegs() {
return fetchLegs(
limit: _lastLegsFetch.limit,
sortBy: _lastLegsFetch.sortBy,
sortDirection: _lastLegsFetch.sortDirection,
dateRangeStart: _lastLegsFetch.dateRangeStart,
dateRangeEnd: _lastLegsFetch.dateRangeEnd,
);
}
Future<void> fetchHadTraction({int offset = 0, int limit = 100}) async {
await fetchTraction(
hadOnly: true,