From 84d50d5a902ab8e41ec6ecfb96510c6511fadcd9 Mon Sep 17 00:00:00 2001 From: Pete Gregory Date: Thu, 11 Dec 2025 20:15:06 +0000 Subject: [PATCH] set trip to null, not 0 --- lib/components/pages/new_entry.dart | 323 ++++++++++++++-------------- lib/main.dart | 3 +- lib/objects/objects.dart | 109 +++++----- pubspec.yaml | 2 +- 4 files changed, 215 insertions(+), 222 deletions(-) diff --git a/lib/components/pages/new_entry.dart b/lib/components/pages/new_entry.dart index 11c1b6b..f64c481 100644 --- a/lib/components/pages/new_entry.dart +++ b/lib/components/pages/new_entry.dart @@ -67,15 +67,10 @@ class _NewEntryPageState extends State { border: OutlineInputBorder(), ), items: [ - const DropdownMenuItem( - value: null, - child: Text('No trip'), - ), + const DropdownMenuItem(value: null, child: Text('No trip')), ...sorted.map( - (t) => DropdownMenuItem( - value: t.tripId, - child: Text(t.tripName), - ), + (t) => + DropdownMenuItem(value: t.tripId, child: Text(t.tripName)), ), ], onChanged: (val) => setState(() => _selectedTripId = val), @@ -99,9 +94,7 @@ class _NewEntryPageState extends State { title: const Text('New Trip'), content: TextField( controller: controller, - decoration: const InputDecoration( - labelText: 'Trip name', - ), + decoration: const InputDecoration(labelText: 'Trip name'), autofocus: true, ), actions: [ @@ -128,7 +121,9 @@ class _NewEntryPageState extends State { final trips = data.tripList; final match = trips.firstWhere( (t) => t.tripName == result, - orElse: () => trips.isNotEmpty ? trips.first : TripSummary(tripId: 0, tripName: result, tripMileage: 0), + orElse: () => trips.isNotEmpty + ? trips.first + : TripSummary(tripId: 0, tripName: result, tripMileage: 0), ); setState(() => _selectedTripId = match.tripId); } catch (e) { @@ -168,12 +163,17 @@ class _NewEntryPageState extends State { selectionMode: true, selectedKeys: selectedKeys, onSelect: (loco) { - final markerIndex = - _tractionItems.indexWhere((element) => element.isMarker); + final markerIndex = _tractionItems.indexWhere( + (element) => element.isMarker, + ); final key = '${loco.locoClass}-${loco.number}'; setState(() { final existingIndex = _tractionItems.indexWhere( - (e) => !e.isMarker && e.loco != null && '${e.loco!.locoClass}-${e.loco!.number}' == key); + (e) => + !e.isMarker && + e.loco != null && + '${e.loco!.locoClass}-${e.loco!.number}' == key, + ); if (existingIndex != -1) { _tractionItems.removeAt(existingIndex); } else { @@ -208,16 +208,17 @@ class _NewEntryPageState extends State { } DateTime get _legDateTime => DateTime( - _selectedDate.year, - _selectedDate.month, - _selectedDate.day, - _selectedTime.hour, - _selectedTime.minute, - ); + _selectedDate.year, + _selectedDate.month, + _selectedDate.day, + _selectedTime.hour, + _selectedTime.minute, + ); List> _buildTractionPayload() { - final markerIndex = - _tractionItems.indexWhere((element) => element.isMarker); + final markerIndex = _tractionItems.indexWhere( + (element) => element.isMarker, + ); final payload = >[]; for (var i = 0; i < _tractionItems.length; i++) { final item = _tractionItems[i]; @@ -262,7 +263,7 @@ class _NewEntryPageState extends State { if (_useManualMileage) { final body = { - "leg_trip": _selectedTripId ?? 0, + "leg_trip": _selectedTripId ?? null, "leg_start": startVal, "leg_end": endVal, "leg_begin_time": _legDateTime.toIso8601String(), @@ -276,12 +277,9 @@ class _NewEntryPageState extends State { await api.post('/add/manual', body); } else { final body = { - "leg_trip": _selectedTripId ?? 0, - "leg_start": startVal, - "leg_end": endVal, + "leg_trip": _selectedTripId ?? null, "leg_begin_time": _legDateTime.toIso8601String(), "leg_route": routeStations, - "leg_mileage": mileageVal, "leg_notes": _notesController.text.trim(), "leg_headcode": _headcodeController.text.trim(), "leg_network": _networkController.text.trim(), @@ -294,15 +292,15 @@ class _NewEntryPageState extends State { } try { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Entry submitted')), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Entry submitted'))); _formKey.currentState!.reset(); } catch (e) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Failed to submit: $e')), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Failed to submit: $e'))); } finally { if (mounted) setState(() => _submitting = false); } @@ -319,141 +317,133 @@ class _NewEntryPageState extends State { builder: (context, constraints) { final twoCol = !isMobile && constraints.maxWidth > 1000; - final detailPanel = _section( - 'Details', - [ - _buildTripSelector(context), + final detailPanel = _section('Details', [ + _buildTripSelector(context), + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: _pickDate, + icon: const Icon(Icons.calendar_today), + label: Text(DateFormat.yMMMd().format(_selectedDate)), + ), + ), + const SizedBox(width: 12), + Expanded( + child: OutlinedButton.icon( + onPressed: _pickTime, + icon: const Icon(Icons.schedule), + label: Text(_selectedTime.format(context)), + ), + ), + ], + ), + if (_useManualMileage) Row( children: [ Expanded( - child: OutlinedButton.icon( - onPressed: _pickDate, - icon: const Icon(Icons.calendar_today), - label: Text(DateFormat.yMMMd().format(_selectedDate)), + child: TextFormField( + controller: _startController, + decoration: const InputDecoration( + labelText: 'From', + border: OutlineInputBorder(), + ), + validator: (v) => !_useManualMileage + ? null + : (v == null || v.isEmpty ? 'Required' : null), ), ), const SizedBox(width: 12), Expanded( - child: OutlinedButton.icon( - onPressed: _pickTime, - icon: const Icon(Icons.schedule), - label: Text(_selectedTime.format(context)), + child: TextFormField( + controller: _endController, + decoration: const InputDecoration( + labelText: 'To', + border: OutlineInputBorder(), + ), + validator: (v) => !_useManualMileage + ? null + : (v == null || v.isEmpty ? 'Required' : null), ), ), ], ), - if (_useManualMileage) - Row( - children: [ - Expanded( - child: TextFormField( - controller: _startController, - decoration: const InputDecoration( - labelText: 'From', - border: OutlineInputBorder(), - ), - validator: (v) => !_useManualMileage - ? null - : (v == null || v.isEmpty ? 'Required' : null), - ), - ), - const SizedBox(width: 12), - Expanded( - child: TextFormField( - controller: _endController, - decoration: const InputDecoration( - labelText: 'To', - border: OutlineInputBorder(), - ), - validator: (v) => !_useManualMileage - ? null - : (v == null || v.isEmpty ? 'Required' : null), - ), - ), - ], - ), - TextFormField( - controller: _headcodeController, - decoration: const InputDecoration( - labelText: 'Headcode', - border: OutlineInputBorder(), - ), + TextFormField( + controller: _headcodeController, + decoration: const InputDecoration( + labelText: 'Headcode', + border: OutlineInputBorder(), ), - TextFormField( - controller: _networkController, - decoration: const InputDecoration( - labelText: 'Network', - border: OutlineInputBorder(), - ), + ), + TextFormField( + controller: _networkController, + decoration: const InputDecoration( + labelText: 'Network', + border: OutlineInputBorder(), ), - TextFormField( - controller: _notesController, - maxLines: 3, - decoration: const InputDecoration( - labelText: 'Notes', - border: OutlineInputBorder(), - ), + ), + TextFormField( + controller: _notesController, + maxLines: 3, + decoration: const InputDecoration( + labelText: 'Notes', + border: OutlineInputBorder(), ), - ], - ); + ), + ]); - final tractionPanel = _section( - 'Traction', - [ + final tractionPanel = _section('Traction', [ + Align( + alignment: Alignment.centerLeft, + child: ElevatedButton.icon( + onPressed: _openTractionPicker, + icon: const Icon(Icons.search), + label: const Text('Search traction'), + ), + ), + _buildTractionList(), + ]); + + final mileagePanel = _section('Mileage', [ + SwitchListTile( + title: const Text('Use manual mileage'), + subtitle: const Text('Turn on to enter mileage manually'), + value: _useManualMileage, + onChanged: (val) { + setState(() { + _useManualMileage = val; + }); + }, + ), + if (_useManualMileage) + TextFormField( + controller: _mileageController, + keyboardType: const TextInputType.numberWithOptions( + decimal: true, + ), + decoration: const InputDecoration( + labelText: 'Mileage (mi)', + border: OutlineInputBorder(), + ), + ) + else if (_routeResult != null) + ListTile( + contentPadding: EdgeInsets.zero, + title: const Text('Calculated mileage'), + subtitle: Text( + '${_routeResult!.distance.toStringAsFixed(2)} mi', + ), + ), + if (!_useManualMileage) Align( alignment: Alignment.centerLeft, child: ElevatedButton.icon( - onPressed: _openTractionPicker, - icon: const Icon(Icons.search), - label: const Text('Search traction'), + onPressed: _openCalculator, + icon: const Icon(Icons.calculate), + label: const Text('Open mileage calculator'), ), ), - _buildTractionList(), - ], - ); - - final mileagePanel = _section( - 'Mileage', - [ - SwitchListTile( - title: const Text('Use manual mileage'), - subtitle: const Text( - 'Turn off to calculate mileage automatically'), - value: _useManualMileage, - onChanged: (val) { - setState(() { - _useManualMileage = val; - }); - }, - ), - if (_useManualMileage) - TextFormField( - controller: _mileageController, - keyboardType: - const TextInputType.numberWithOptions(decimal: true), - decoration: const InputDecoration( - labelText: 'Mileage (mi)', - border: OutlineInputBorder(), - ), - ) - else if (_routeResult != null) - ListTile( - contentPadding: EdgeInsets.zero, - title: const Text('Calculated mileage'), - subtitle: - Text('${_routeResult!.distance.toStringAsFixed(2)} mi'), - ), - if (!_useManualMileage) - Align( - alignment: Alignment.centerLeft, - child: ElevatedButton.icon( - onPressed: _openCalculator, - icon: const Icon(Icons.calculate), - label: const Text('Open mileage calculator'), - ), - ), - ], - ); + ]); return SingleChildScrollView( padding: const EdgeInsets.all(16), @@ -528,14 +518,18 @@ class _NewEntryPageState extends State { leading: Icon(Icons.train), title: Text('Rolling stock marker'), subtitle: Text( - 'Place locomotives above/below. Positions set relative to this.'), + 'Place locomotives above/below. Positions set relative to this.', + ), ), ); } final loco = item.loco!; - final markerIndex = - _tractionItems.indexWhere((element) => element.isMarker); - final pos = index > markerIndex ? -(index - markerIndex) : (markerIndex - 1) - index; + final markerIndex = _tractionItems.indexWhere( + (element) => element.isMarker, + ); + final pos = index > markerIndex + ? -(index - markerIndex) + : (markerIndex - 1) - index; return Card( key: ValueKey('${loco.locoClass}-${loco.number}-$index'), child: ListTile( @@ -553,8 +547,7 @@ class _NewEntryPageState extends State { value: item.powering, onChanged: (v) { setState(() { - _tractionItems[index] = - item.copyWith(powering: v); + _tractionItems[index] = item.copyWith(powering: v); }); }, ), @@ -583,10 +576,9 @@ class _NewEntryPageState extends State { children: [ Text( title, - style: Theme.of(context) - .textTheme - .titleMedium - ?.copyWith(fontWeight: FontWeight.bold), + style: Theme.of( + context, + ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), ...children.map( @@ -616,9 +608,7 @@ class _CalculatorPickerPage extends StatelessWidget { ), title: const Text('Mileage calculator'), ), - body: RouteCalculator( - onApplyRoute: onResult, - ), + body: RouteCalculator(onApplyRoute: onResult), ); } } @@ -628,9 +618,14 @@ class _TractionItem { final bool powering; final bool isMarker; - _TractionItem({required this.loco, this.powering = true, this.isMarker = false}); + _TractionItem({ + required this.loco, + this.powering = true, + this.isMarker = false, + }); - factory _TractionItem.marker() => _TractionItem(loco: null, powering: false, isMarker: true); + factory _TractionItem.marker() => + _TractionItem(loco: null, powering: false, isMarker: true); _TractionItem copyWith({LocoSummary? loco, bool? powering, bool? isMarker}) { return _TractionItem( diff --git a/lib/main.dart b/lib/main.dart index 69b8a4c..9875fcf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -171,8 +171,9 @@ class _MyHomePageState extends State { } void _onItemTapped(int index, int currentIndex) { - if (index < 0 || index >= contentPages.length || index == currentIndex) + if (index < 0 || index >= contentPages.length || index == currentIndex) { return; + } context.push(contentPages[index]); _getIndexFromLocation(contentPages[index]); } diff --git a/lib/objects/objects.dart b/lib/objects/objects.dart index 8254a20..1d4cfba 100644 --- a/lib/objects/objects.dart +++ b/lib/objects/objects.dart @@ -140,7 +140,7 @@ class LocoSummary extends Loco { required String locoType, required String locoNumber, required String locoName, - required String locoClass, + required super.locoClass, required String locoOperator, String? locoNotes, String? locoEvn, @@ -153,43 +153,41 @@ class LocoSummary extends Loco { this.livery, this.location, Map? extra, - }) : extra = extra ?? const {}, - super( + }) : extra = extra ?? const {}, + super( id: locoId, type: locoType, number: locoNumber, name: locoName, - locoClass: locoClass, operator: locoOperator, notes: locoNotes, evn: locoEvn, ); factory LocoSummary.fromJson(Map json) => LocoSummary( - locoId: json['loco_id'] ?? json['id'] ?? 0, - locoType: json['type'] ?? json['loco_type'] ?? '', - locoNumber: json['number'] ?? json['loco_number'] ?? '', - locoName: json['name'] ?? json['loco_name'] ?? "", - locoClass: json['class'] ?? json['loco_class'] ?? '', - locoOperator: json['operator'] ?? json['loco_operator'] ?? '', - locoNotes: json['notes'], - locoEvn: json['evn'] ?? json['loco_evn'], - mileage: ((json['loco_mileage'] ?? json['mileage']) as num?) - ?.toDouble() ?? - 0, - journeys: (json['loco_journeys'] ?? json['journeys'] ?? 0) is num - ? (json['loco_journeys'] ?? json['journeys'] ?? 0).toInt() - : 0, - trips: (json['loco_trips'] ?? json['trips']) is num - ? (json['loco_trips'] ?? json['trips']).toInt() - : null, - status: json['status'] ?? json['loco_status'], - domain: json['domain'], - owner: json['owner'] ?? json['loco_owner'], - livery: json['livery'], - location: json['location'], - extra: Map.from(json), - ); + locoId: json['loco_id'] ?? json['id'] ?? 0, + locoType: json['type'] ?? json['loco_type'] ?? '', + locoNumber: json['number'] ?? json['loco_number'] ?? '', + locoName: json['name'] ?? json['loco_name'] ?? "", + locoClass: json['class'] ?? json['loco_class'] ?? '', + locoOperator: json['operator'] ?? json['loco_operator'] ?? '', + locoNotes: json['notes'], + locoEvn: json['evn'] ?? json['loco_evn'], + mileage: + ((json['loco_mileage'] ?? json['mileage']) as num?)?.toDouble() ?? 0, + journeys: (json['loco_journeys'] ?? json['journeys'] ?? 0) is num + ? (json['loco_journeys'] ?? json['journeys'] ?? 0).toInt() + : 0, + trips: (json['loco_trips'] ?? json['trips']) is num + ? (json['loco_trips'] ?? json['trips']).toInt() + : null, + status: json['status'] ?? json['loco_status'], + domain: json['domain'], + owner: json['owner'] ?? json['loco_owner'], + livery: json['livery'], + location: json['location'], + extra: Map.from(json), + ); } class LeaderboardEntry { @@ -353,24 +351,23 @@ class TripLeg { }); factory TripLeg.fromJson(Map json) => TripLeg( - id: json['leg_id'], - start: json['leg_start'] ?? '', - end: json['leg_end'] ?? '', - beginTime: - json['leg_begin_time'] != null && json['leg_begin_time'] is String - ? DateTime.tryParse(json['leg_begin_time']) - : (json['leg_begin_time'] is DateTime - ? json['leg_begin_time'] - : null), - network: json['leg_network'], - route: json['leg_route'], - mileage: (json['leg_mileage'] as num?)?.toDouble(), - notes: json['leg_notes'], - locos: (json['locos'] as List?) - ?.map((e) => Loco.fromJson(e as Map)) - .toList() ?? - [], - ); + id: json['leg_id'], + start: json['leg_start'] ?? '', + end: json['leg_end'] ?? '', + beginTime: + json['leg_begin_time'] != null && json['leg_begin_time'] is String + ? DateTime.tryParse(json['leg_begin_time']) + : (json['leg_begin_time'] is DateTime ? json['leg_begin_time'] : null), + network: json['leg_network'], + route: json['leg_route'], + mileage: (json['leg_mileage'] as num?)?.toDouble(), + notes: json['leg_notes'], + locos: + (json['locos'] as List?) + ?.map((e) => Loco.fromJson(e as Map)) + .toList() ?? + [], + ); } class TripDetail { @@ -389,14 +386,14 @@ class TripDetail { }); factory TripDetail.fromJson(Map json) => TripDetail( - id: json['trip_id'] ?? 0, - name: json['trip_name'] ?? '', - mileage: (json['trip_mileage'] as num?)?.toDouble() ?? 0, - legCount: json['leg_count'] ?? - ((json['trip_legs'] as List?)?.length ?? 0), - legs: (json['trip_legs'] as List?) - ?.map((e) => TripLeg.fromJson(e as Map)) - .toList() ?? - [], - ); + id: json['trip_id'] ?? 0, + name: json['trip_name'] ?? '', + mileage: (json['trip_mileage'] as num?)?.toDouble() ?? 0, + legCount: json['leg_count'] ?? ((json['trip_legs'] as List?)?.length ?? 0), + legs: + (json['trip_legs'] as List?) + ?.map((e) => TripLeg.fromJson(e as Map)) + .toList() ?? + [], + ); } diff --git a/pubspec.yaml b/pubspec.yaml index eada652..9b865f4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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.0+1 +version: 0.1.1+1 environment: sdk: ^3.8.1