mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-08-10 21:20:18 +02:00
catch and correct possible bad values for lat/lon, add tests
This commit is contained in:
@@ -26,9 +26,12 @@ import 'node_limit_indicator.dart';
|
||||
import 'proximity_alert_banner.dart';
|
||||
import '../dev_config.dart';
|
||||
import '../services/proximity_alert_service.dart';
|
||||
import '../services/coordinate_validation.dart';
|
||||
import 'sheet_aware_map.dart';
|
||||
|
||||
import 'custom_scale_bar.dart';
|
||||
|
||||
|
||||
class MapView extends StatefulWidget {
|
||||
final AnimatedMapController controller;
|
||||
const MapView({
|
||||
@@ -80,6 +83,15 @@ class MapViewState extends State<MapView> {
|
||||
|
||||
// Track map center to clear queue on significant panning
|
||||
LatLng? _lastCenter;
|
||||
|
||||
// Track the last known-good (finite, in-range) camera center/zoom so we
|
||||
// can self-heal if the camera ever gets pushed into a degenerate state
|
||||
// (e.g. by an edge case in flutter_map's internal gesture-to-camera
|
||||
// pipeline). This mirrors the validation used for persisted map position
|
||||
// in MapPositionManager, applied to the *live* camera as well.
|
||||
LatLng? _lastValidCenter;
|
||||
double? _lastValidZoom;
|
||||
|
||||
|
||||
// State for proximity alert banner
|
||||
bool _showProximityBanner = false;
|
||||
@@ -275,6 +287,29 @@ class MapViewState extends State<MapView> {
|
||||
return (!appState.offlineMode && appState.isInSearchMode) ? 60.0 : 0.0;
|
||||
}
|
||||
|
||||
/// Get a known-good fallback location for [initialCenter], preferring a
|
||||
/// validated GPS location, then a validated persisted position, then a
|
||||
/// hardcoded default. GpsController already validates incoming fixes, but
|
||||
/// this extra check keeps launch resilient even if that guard is ever
|
||||
/// bypassed (e.g. future refactor).
|
||||
LatLng get _safeInitialCenter {
|
||||
final gpsLocation = _gpsController.currentLocation;
|
||||
if (gpsLocation != null &&
|
||||
isValidCoordinate(gpsLocation.latitude) &&
|
||||
isValidCoordinate(gpsLocation.longitude)) {
|
||||
return gpsLocation;
|
||||
}
|
||||
final persisted = _positionManager.initialLocation;
|
||||
if (persisted != null &&
|
||||
isValidCoordinate(persisted.latitude) &&
|
||||
isValidCoordinate(persisted.longitude)) {
|
||||
return persisted;
|
||||
}
|
||||
return LatLng(37.7749, -122.4194);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant MapView oldWidget) {
|
||||
@@ -414,18 +449,55 @@ class MapViewState extends State<MapView> {
|
||||
key: ValueKey('map_${appState.selectedTileProvider?.id ?? 'none'}_${appState.selectedTileType?.id ?? 'none'}_${appState.offlineMode}_${_tileManager.mapRebuildKey}'),
|
||||
mapController: _controller.mapController,
|
||||
options: MapOptions(
|
||||
initialCenter: _gpsController.currentLocation ?? _positionManager.initialLocation ?? LatLng(37.7749, -122.4194),
|
||||
initialCenter: _safeInitialCenter,
|
||||
initialZoom: _positionManager.initialZoom ?? 15,
|
||||
|
||||
minZoom: 1.0,
|
||||
maxZoom: (appState.selectedTileType?.maxZoom ?? 18).toDouble(),
|
||||
interactionOptions: _interactionManager.getInteractionOptions(editSession),
|
||||
onPositionChanged: (pos, gesture) {
|
||||
// Self-heal if the camera has been pushed into a degenerate
|
||||
// state (non-finite or wildly out-of-range center/zoom). This
|
||||
// has been observed to occur silently during ordinary panning
|
||||
// (no crash, no console output) — likely an edge case in
|
||||
// flutter_map's internal gesture-to-camera math — leaving the
|
||||
// map showing a blank grey view with no tiles or markers,
|
||||
// since every subsequent gesture computes its new position as
|
||||
// a delta from the already-broken center. Snap back to the
|
||||
// last known-good position the moment this is detected rather
|
||||
// than requiring the user to discover the GPS button "fixes"
|
||||
// it (which works only because it supplies a fresh valid
|
||||
// destination).
|
||||
if (!isValidCoordinate(pos.center.latitude) ||
|
||||
!isValidCoordinate(pos.center.longitude) ||
|
||||
!isValidZoom(pos.zoom)) {
|
||||
|
||||
debugPrint(
|
||||
'[MapView] Detected invalid camera position '
|
||||
'(lat=${pos.center.latitude}, lng=${pos.center.longitude}, zoom=${pos.zoom}) '
|
||||
'- snapping back to last known-good position',
|
||||
);
|
||||
final recoveryCenter = _lastValidCenter ?? _safeInitialCenter;
|
||||
final recoveryZoom = _lastValidZoom ?? (_positionManager.initialZoom ?? 15.0);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
try {
|
||||
_controller.mapController.move(recoveryCenter, recoveryZoom);
|
||||
} catch (e) {
|
||||
debugPrint('[MapView] Failed to recover camera position: $e');
|
||||
}
|
||||
});
|
||||
return; // Don't process this invalid position further
|
||||
}
|
||||
_lastValidCenter = pos.center;
|
||||
_lastValidZoom = pos.zoom;
|
||||
|
||||
setState(() {}); // Instant UI update for zoom, etc.
|
||||
if (gesture) {
|
||||
widget.onUserGesture();
|
||||
}
|
||||
|
||||
// Enforce minimum zoom level for add/edit node sheets (but not tag sheet)
|
||||
|
||||
if ((session != null || editSession != null) && pos.zoom < kMinZoomForNodeEditingSheets) {
|
||||
// User tried to zoom out below minimum - snap back to minimum zoom
|
||||
_controller.animateTo(
|
||||
|
||||
Reference in New Issue
Block a user