diff --git a/lib/dev_config.dart b/lib/dev_config.dart index f94e24d..2b61dc7 100644 --- a/lib/dev_config.dart +++ b/lib/dev_config.dart @@ -37,7 +37,8 @@ const String kClientVersion = '0.9.11'; const bool kEnableDevelopmentModes = true; // Set to false to hide sandbox/simulate modes and force production mode // Marker/node interaction -const int kCameraMinZoomLevel = 10; // Minimum zoom to show nodes or warning +const int kCameraMinZoomLevel = 10; // Minimum zoom to show nodes (Overpass) +const int kOsmApiMinZoomLevel = 13; // Minimum zoom for OSM API bbox queries (sandbox mode) const Duration kMarkerTapTimeout = Duration(milliseconds: 250); const Duration kDebounceCameraRefresh = Duration(milliseconds: 500); diff --git a/lib/widgets/edit_node_sheet.dart b/lib/widgets/edit_node_sheet.dart index 9e1c110..54ea91d 100644 --- a/lib/widgets/edit_node_sheet.dart +++ b/lib/widgets/edit_node_sheet.dart @@ -36,7 +36,7 @@ class EditNodeSheet extends StatelessWidget { final submittableProfiles = appState.enabledProfiles.where((p) => p.isSubmittable).toList(); final isSandboxMode = appState.uploadMode == UploadMode.sandbox; - final allowSubmit = appState.isLoggedIn && submittableProfiles.isNotEmpty && session.profile.isSubmittable && !isSandboxMode; + final allowSubmit = appState.isLoggedIn && submittableProfiles.isNotEmpty && session.profile.isSubmittable; void _openRefineTags() async { final result = await Navigator.push( @@ -130,22 +130,6 @@ class EditNodeSheet extends StatelessWidget { ], ), ) - else if (isSandboxMode) - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), - child: Row( - children: [ - const Icon(Icons.info_outline, color: Colors.blue, size: 20), - const SizedBox(width: 6), - Expanded( - child: Text( - locService.t('editNode.sandboxModeWarning'), - style: const TextStyle(color: Colors.blue, fontSize: 13), - ), - ), - ], - ), - ) else if (submittableProfiles.isEmpty) Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index b2b911d..a9d67b2 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -129,6 +129,42 @@ class MapViewState extends State { static Future clearStoredMapPosition() => MapPositionManager.clearStoredMapPosition(); + /// Get minimum zoom level for camera fetching based on upload mode + int _getMinZoomForCameras(BuildContext context) { + final appState = context.read(); + final uploadMode = appState.uploadMode; + + // OSM API (sandbox mode) needs higher zoom level due to bbox size limits + if (uploadMode == UploadMode.sandbox) { + return kOsmApiMinZoomLevel; + } else { + return kCameraMinZoomLevel; + } + } + + /// Show zoom warning if user is below minimum zoom level + void _showZoomWarningIfNeeded(BuildContext context, double currentZoom, int minZoom) { + // Only show warning once per zoom level to avoid spam + if (currentZoom.floor() == (minZoom - 1)) { + final appState = context.read(); + final uploadMode = appState.uploadMode; + + final message = uploadMode == UploadMode.sandbox + ? 'Zoom to level $minZoom or higher to see nodes in sandbox mode (OSM API bbox limit)' + : 'Zoom to level $minZoom or higher to see surveillance nodes'; + + // Show a brief snackbar + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + duration: const Duration(seconds: 2), + behavior: SnackBarBehavior.floating, + ), + ); + } + } + void _refreshCamerasFromProvider() { @@ -321,11 +357,15 @@ class MapViewState extends State { }); // Request more cameras on any map movement/zoom at valid zoom level (slower debounce) - if (pos.zoom >= 10) { + final minZoom = _getMinZoomForCameras(context); + if (pos.zoom >= minZoom) { _cameraDebounce(_refreshCamerasFromProvider); } else { // Skip nodes at low zoom - report immediate completion (brutalist approach) NetworkStatus.instance.reportNodeComplete(); + + // Show zoom warning if needed + _showZoomWarningIfNeeded(context, pos.zoom, minZoom); } }, ),