Add accepted leg edit notification and class leaderboard
All checks were successful
Release / meta (push) Successful in 12s
Release / linux-build (push) Successful in 1m0s
Release / web-build (push) Successful in 2m6s
Release / android-build (push) Successful in 6m8s
Release / release-master (push) Successful in 16s
Release / release-dev (push) Successful in 19s
All checks were successful
Release / meta (push) Successful in 12s
Release / linux-build (push) Successful in 1m0s
Release / web-build (push) Successful in 2m6s
Release / android-build (push) Successful in 6m8s
Release / release-master (push) Successful in 16s
Release / release-dev (push) Successful in 19s
This commit is contained in:
@@ -55,26 +55,19 @@ class _StationAutocompleteState extends State<StationAutocomplete> {
|
||||
},
|
||||
fieldViewBuilder:
|
||||
(context, textEditingController, focusNode, onFieldSubmitted) {
|
||||
textEditingController.value = _controller.value;
|
||||
textEditingController.value = _controller.value;
|
||||
|
||||
return TextField(
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
textInputAction: TextInputAction.done,
|
||||
onSubmitted: (_) {
|
||||
final matches = _findTopMatches(textEditingController.text);
|
||||
final firstMatch = matches.isEmpty ? null : matches.first;
|
||||
if (firstMatch == null) return;
|
||||
_controller.text = firstMatch;
|
||||
widget.onChanged(firstMatch);
|
||||
focusNode.unfocus(); // optionally close keyboard
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Select station',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
);
|
||||
},
|
||||
return TextField(
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
textInputAction: TextInputAction.done,
|
||||
onSubmitted: (_) => onFieldSubmitted(),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Select station',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -181,6 +174,14 @@ class _RouteCalculatorState extends State<RouteCalculator> {
|
||||
}
|
||||
|
||||
Future<void> _calculateRoute(List<String> stations) async {
|
||||
final cleaned = stations.where((s) => s.trim().isNotEmpty).toList();
|
||||
if (cleaned.length < 2) {
|
||||
setState(() {
|
||||
_routeResult = null;
|
||||
_errorMessage = 'Add at least two stations before calculating.';
|
||||
});
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_errorMessage = null;
|
||||
_routeResult = null;
|
||||
@@ -188,7 +189,7 @@ class _RouteCalculatorState extends State<RouteCalculator> {
|
||||
final api = context.read<ApiService>(); // context is valid here
|
||||
try {
|
||||
final res = await api.post('/route/distance2', {
|
||||
'route': stations.where((s) => s.trim().isNotEmpty).toList(),
|
||||
'route': cleaned,
|
||||
});
|
||||
|
||||
if (res is Map && res['error'] == false) {
|
||||
@@ -232,11 +233,28 @@ class _RouteCalculatorState extends State<RouteCalculator> {
|
||||
});
|
||||
}
|
||||
|
||||
void _clearCalculator() {
|
||||
final data = context.read<DataService>();
|
||||
setState(() {
|
||||
data.stations = [''];
|
||||
_routeResult = null;
|
||||
_errorMessage = null;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final data = context.watch<DataService>();
|
||||
return Column(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: IconButton(
|
||||
tooltip: 'Clear calculator',
|
||||
icon: const Icon(Icons.clear_all),
|
||||
onPressed: _clearCalculator,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: Wrap(
|
||||
@@ -366,28 +384,34 @@ class _RouteCalculatorState extends State<RouteCalculator> {
|
||||
spacing: 12,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.swap_horiz),
|
||||
label: const Text('Reverse route'),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
data.stations = data.stations.reversed.toList();
|
||||
});
|
||||
await _calculateRoute(data.stations);
|
||||
},
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Add Station'),
|
||||
onPressed: _addStation,
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(Icons.route),
|
||||
label: const Text('Calculate Route'),
|
||||
onPressed: () async {
|
||||
await _calculateRoute(data.stations);
|
||||
},
|
||||
),
|
||||
...(() {
|
||||
final reverseButton = ElevatedButton.icon(
|
||||
icon: const Icon(Icons.swap_horiz),
|
||||
label: const Text('Reverse route'),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
data.stations = data.stations.reversed.toList();
|
||||
});
|
||||
await _calculateRoute(data.stations);
|
||||
},
|
||||
);
|
||||
final addButton = ElevatedButton.icon(
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('Add Station'),
|
||||
onPressed: _addStation,
|
||||
);
|
||||
final calculateButton = ElevatedButton.icon(
|
||||
icon: const Icon(Icons.route),
|
||||
label: const Text('Calculate Route'),
|
||||
onPressed: () async {
|
||||
await _calculateRoute(data.stations);
|
||||
},
|
||||
);
|
||||
final isMobile = MediaQuery.of(context).size.width < 600;
|
||||
return isMobile
|
||||
? [addButton, reverseButton, calculateButton]
|
||||
: [reverseButton, addButton, calculateButton];
|
||||
})(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user