major refactor
This commit is contained in:
@@ -25,9 +25,6 @@ class StationAutocomplete extends StatefulWidget {
|
||||
class _StationAutocompleteState extends State<StationAutocomplete> {
|
||||
late final TextEditingController _controller;
|
||||
|
||||
// Simulated list of over 10,000 stations
|
||||
final List<String> stations = List.generate(10000, (i) => 'Station $i');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -50,15 +47,7 @@ class _StationAutocompleteState extends State<StationAutocomplete> {
|
||||
if (textEditingValue.text.isEmpty) {
|
||||
return const Iterable<String>.empty();
|
||||
}
|
||||
final query = textEditingValue.text.toLowerCase();
|
||||
final matches = widget.allStations
|
||||
.map((s) => s.name)
|
||||
.where((name) => name.toLowerCase().contains(query))
|
||||
.toList();
|
||||
|
||||
matches.sort((a, b) => a.length.compareTo(b.length));
|
||||
|
||||
return matches.take(10);
|
||||
return _findTopMatches(textEditingValue.text);
|
||||
},
|
||||
onSelected: (String selection) {
|
||||
_controller.text = selection;
|
||||
@@ -73,19 +62,12 @@ class _StationAutocompleteState extends State<StationAutocomplete> {
|
||||
focusNode: focusNode,
|
||||
textInputAction: TextInputAction.done,
|
||||
onSubmitted: (_) {
|
||||
final query = textEditingController.text.toLowerCase();
|
||||
final matches = widget.allStations
|
||||
.map((s) => s.name)
|
||||
.where((name) => name.toLowerCase().contains(query))
|
||||
.toList();
|
||||
|
||||
if (matches.isNotEmpty) {
|
||||
matches.sort((a, b) => a.length.compareTo(b.length));
|
||||
final firstMatch = matches.first;
|
||||
_controller.text = firstMatch;
|
||||
widget.onChanged(firstMatch);
|
||||
focusNode.unfocus(); // optionally close keyboard
|
||||
}
|
||||
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',
|
||||
@@ -95,6 +77,42 @@ class _StationAutocompleteState extends State<StationAutocomplete> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Iterable<String> _findTopMatches(String rawQuery) {
|
||||
final query = rawQuery.trim().toLowerCase();
|
||||
if (query.isEmpty) return const <String>[];
|
||||
|
||||
// Keep a bounded, sorted list (by shortest name, then alpha) without
|
||||
// sorting the entire match set.
|
||||
final best = <String>[];
|
||||
for (final station in widget.allStations) {
|
||||
final name = station.name;
|
||||
if (name.isEmpty) continue;
|
||||
if (!name.toLowerCase().contains(query)) continue;
|
||||
|
||||
_insertCandidate(best, name, max: 10);
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
void _insertCandidate(List<String> best, String candidate, {required int max}) {
|
||||
final existingIndex = best.indexOf(candidate);
|
||||
if (existingIndex >= 0) return;
|
||||
|
||||
int insertAt = 0;
|
||||
while (insertAt < best.length &&
|
||||
_candidateCompare(best[insertAt], candidate) <= 0) {
|
||||
insertAt++;
|
||||
}
|
||||
best.insert(insertAt, candidate);
|
||||
if (best.length > max) best.removeLast();
|
||||
}
|
||||
|
||||
int _candidateCompare(String a, String b) {
|
||||
final byLength = a.length.compareTo(b.length);
|
||||
if (byLength != 0) return byLength;
|
||||
return a.compareTo(b);
|
||||
}
|
||||
}
|
||||
|
||||
class RouteCalculator extends StatefulWidget {
|
||||
@@ -146,20 +164,28 @@ class _RouteCalculatorState extends State<RouteCalculator> {
|
||||
_routeResult = null;
|
||||
});
|
||||
final api = context.read<ApiService>(); // context is valid here
|
||||
final res = await api.post('/route/distance2', {
|
||||
'route': stations.where((s) => s.trim().isNotEmpty).toList(),
|
||||
});
|
||||
try {
|
||||
final res = await api.post('/route/distance2', {
|
||||
'route': stations.where((s) => s.trim().isNotEmpty).toList(),
|
||||
});
|
||||
|
||||
if (res['error'] == false) {
|
||||
setState(() {
|
||||
_routeResult = RouteResult.fromJson(res);
|
||||
});
|
||||
final distance = (_routeResult?.distance ?? 0);
|
||||
widget.onDistanceComputed?.call(distance);
|
||||
} else {
|
||||
setState(() {
|
||||
_errorMessage = RouteError.fromJson(res["error_obj"][0]).msg;
|
||||
});
|
||||
if (res is Map && res['error'] == false) {
|
||||
setState(() {
|
||||
_routeResult = RouteResult.fromJson(Map<String, dynamic>.from(res));
|
||||
});
|
||||
final distance = (_routeResult?.distance ?? 0);
|
||||
widget.onDistanceComputed?.call(distance);
|
||||
} else if (res is Map && res['error_obj'] is List && res['error_obj'].isNotEmpty) {
|
||||
setState(() {
|
||||
_errorMessage = RouteError.fromJson(
|
||||
Map<String, dynamic>.from(res['error_obj'][0] as Map),
|
||||
).msg;
|
||||
});
|
||||
} else {
|
||||
setState(() => _errorMessage = 'Failed to calculate route.');
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() => _errorMessage = 'Failed to calculate route: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user