Enhance API clients with auto-logout on unauthorized responses; improve navigation and schedule handling with better error management and UI updates

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Sven laptop
2026-05-07 16:55:18 +02:00
parent 12e7698d98
commit bd96fc032e
6 changed files with 124 additions and 26 deletions
@@ -73,8 +73,6 @@ class _NavigationScreenState extends State<NavigationScreen> {
// Defensive: filter out invalid stops (missing/NaN coordinates) and deduplicate
if (_stops != null) {
_stops = _stops!
.where((s) => s != null)
.where((s) => s.latitude != null && s.longitude != null)
.where((s) => s.latitude.isFinite && s.longitude.isFinite)
.toList();
@@ -107,7 +105,7 @@ class _NavigationScreenState extends State<NavigationScreen> {
_isLoading = false;
});
_fitMapToRoute(optimized.route);
WidgetsBinding.instance.addPostFrameCallback((_) => _fitMapToRoute(optimized.route));
}
return;
@@ -157,12 +155,12 @@ class _NavigationScreenState extends State<NavigationScreen> {
if (mounted) {
setState(() {
_currentRoute = navRoute;
_alternativeRoutes = (alternatives ?? []).take(2).toList();
_alternativeRoutes = alternatives.take(2).toList();
_isLoading = false;
});
// Fit map to route bounds
_fitMapToRoute(navRoute);
// Fit map to route bounds after first frame so MapController is attached
WidgetsBinding.instance.addPostFrameCallback((_) => _fitMapToRoute(navRoute));
}
} catch (e) {
if (mounted) {
@@ -265,7 +263,7 @@ class _NavigationScreenState extends State<NavigationScreen> {
});
if (navRoute != null) {
_fitMapToRoute(navRoute);
WidgetsBinding.instance.addPostFrameCallback((_) => _fitMapToRoute(navRoute));
}
}
} catch (e) {
@@ -776,7 +774,7 @@ class _NavigationScreenState extends State<NavigationScreen> {
)
: ListView.separated(
itemCount: _stops!.length,
separatorBuilder: (_, __) => const Divider(),
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
final s = _stops![index];
final color = _stopColor(index);
@@ -116,8 +116,8 @@ class _ScheduleOverviewScreenState extends State<ScheduleOverviewScreen> {
// Close the sheet and start navigation with provided stops
Navigator.of(sheetContext).pop();
final routeObj = sched.Route(
id: (svc.id is num) ? (svc.id as num).toInt() : int.tryParse(svc.id.toString()) ?? 0,
final routeObj = sched.Route(
id: svc.id,
busLine: sched.BusLine(
id: svc.busLineId,
lineNumber: svc.lineNumber,
@@ -38,6 +38,13 @@ class NavigationService {
required NavigationPoint end,
}) async {
try {
bool validCoord(double lat, double lon) {
return lat.isFinite && lon.isFinite && lat >= -90 && lat <= 90 && lon >= -180 && lon <= 180;
}
if (!validCoord(start.latitude, start.longitude) || !validCoord(end.latitude, end.longitude)) {
throw Exception('Invalid coordinates for routing: start=(${start.latitude},${start.longitude}) end=(${end.latitude},${end.longitude})');
}
// OSRM format: /route/v1/car/lon1,lat1;lon2,lat2
final coordinates =
'${start.longitude},${start.latitude};${end.longitude},${end.latitude}';
@@ -67,6 +74,16 @@ class NavigationService {
start: start,
end: end,
);
} else if (response.statusCode == 400) {
// Bad request: OSRM couldn't parse coordinates. Fall back to a simple straight-line route.
try {
final distanceMeters = calculateDistance(start, end);
final durationSec = (distanceMeters / 11.111111).toInt(); // assume ~40 km/h -> 11.11 m/s
final leg = RouteLeg.fromPoints(points: [start, end], distance: distanceMeters, duration: durationSec, summary: 'Direct');
return NavigationRoute(start: start, end: end, legs: [leg], totalDistance: distanceMeters, totalDuration: durationSec);
} catch (e) {
throw Exception('Failed to get route (400) and fallback failed: ${e.toString()}');
}
} else {
throw Exception('Failed to get route: ${response.statusCode}');
}
@@ -140,6 +157,14 @@ class NavigationService {
final response = await http.get(Uri.parse(url));
if (response.statusCode != 200) {
if (response.statusCode == 400) {
// fallback: return simple ordered stops route
final distanceMeters = _sumDistancesBetween(stops);
final durationSec = (distanceMeters / 11.111111).toInt();
final leg = RouteLeg.fromPoints(points: stops, distance: distanceMeters, duration: durationSec, summary: 'Fallback optimized trip');
final route = NavigationRoute(start: stops.first, end: stops.last, legs: [leg], totalDistance: distanceMeters, totalDuration: durationSec);
return OptimizedRouteResult(route: route, orderedStops: stops);
}
throw Exception('Failed to get optimized route: ${response.statusCode}');
}
@@ -255,6 +280,12 @@ class NavigationService {
.toList();
return routes;
} else if (response.statusCode == 400) {
// fallback: single straight-line route
final distanceMeters = calculateDistance(start, end);
final durationSec = (distanceMeters / 11.111111).toInt();
final leg = RouteLeg.fromPoints(points: [start, end], distance: distanceMeters, duration: durationSec, summary: 'Direct');
return [NavigationRoute(start: start, end: end, legs: [leg], totalDistance: distanceMeters, totalDuration: durationSec)];
} else {
throw Exception('Failed to get routes: ${response.statusCode}');
}
@@ -263,6 +294,14 @@ class NavigationService {
}
}
double _sumDistancesBetween(List<NavigationPoint> points) {
var total = 0.0;
for (var i = 0; i < points.length - 1; i++) {
total += calculateDistance(points[i], points[i + 1]);
}
return total;
}
/// Calculate straight-line distance between two points
double calculateDistance(NavigationPoint p1, NavigationPoint p2) {
const distance = Distance();