diff --git a/README.md b/README.md index 8babc4e..7ead598 100644 --- a/README.md +++ b/README.md @@ -98,8 +98,6 @@ cp lib/keys.dart.example lib/keys.dart ## Roadmap ### Needed Bugfixes -- Disallow new/edit nodes below zoom 10 (15?) (don't allow zooming out beyond that either) -- Disallow large downloads or otherwise fix performance issue calculating number of tiles - Update node cache to reflect cleared queue entries - Improve/retune tile fetching backoff/retry - Are offline areas preferred for fast loading even when online? Check working. diff --git a/assets/changelog.json b/assets/changelog.json index e989d53..d78b8dd 100644 --- a/assets/changelog.json +++ b/assets/changelog.json @@ -1,6 +1,12 @@ { "1.4.5": { "content": [ + "• NEW: Minimum zoom level (Z15) enforced for adding and editing surveillance nodes to ensure precise positioning", + "• UX: Snackbar explains why add/edit sheets cannot open when zoomed out too far", + "• UX: Prevents zooming out below minimum level when add/edit sheets are open", + "• Node tags sheet continues to work at all zoom levels for viewing device information", + "• NEW: Minimum zoom level (Z10) enforced for offline area downloads to prevent app crashes from large areas", + "• UX: Download button shows explanatory message when zoomed out too far instead of hanging", "• IMPROVED: Offline area download confirmation now shows as popup with 'View Progress in Settings' button instead of simple notification", "• UX: Download failures now display as dialog instead of snackbar for better visibility" ] diff --git a/lib/dev_config.dart b/lib/dev_config.dart index 7549dbe..8175d8f 100644 --- a/lib/dev_config.dart +++ b/lib/dev_config.dart @@ -80,6 +80,8 @@ bool enableNavigationFeatures({required bool offlineMode}) { // Marker/node interaction const int kNodeMinZoomLevel = 10; // Minimum zoom to show nodes (Overpass) const int kOsmApiMinZoomLevel = 13; // Minimum zoom for OSM API bbox queries (sandbox mode) +const int kMinZoomForNodeEditingSheets = 15; // Minimum zoom to open add/edit node sheets +const int kMinZoomForOfflineDownload = 10; // Minimum zoom to download offline areas (prevents large area crashes) const Duration kMarkerTapTimeout = Duration(milliseconds: 250); const Duration kDebounceCameraRefresh = Duration(milliseconds: 500); diff --git a/lib/localizations/de.json b/lib/localizations/de.json index 18c0dac..1bcf8b9 100644 --- a/lib/localizations/de.json +++ b/lib/localizations/de.json @@ -118,6 +118,7 @@ "enableSubmittableProfile": "Aktivieren Sie ein übertragbares Profil in den Einstellungen, um Knoten zu bearbeiten.", "profileViewOnlyWarning": "Dieses Profil ist nur zum Anzeigen der Karte gedacht. Bitte wählen Sie ein übertragbares Profil aus, um Knoten zu bearbeiten.", "cannotMoveConstrainedNode": "Kann diese Kamera nicht verschieben - sie ist mit einem anderen Kartenelement verbunden (OSM-Weg/Relation). Sie können trotzdem ihre Tags und Richtung bearbeiten.", + "zoomInRequiredMessage": "Zoomen Sie auf mindestens Stufe {} heran, um Überwachungsknoten hinzuzufügen oder zu bearbeiten. Dies gewährleistet eine präzise Positionierung für genaues Kartieren.", "extractFromWay": "Knoten aus Weg/Relation extrahieren", "extractFromWaySubtitle": "Neuen Knoten mit gleichen Tags erstellen, Verschieben an neuen Ort ermöglichen", "refineTags": "Tags Verfeinern", @@ -133,6 +134,7 @@ "withinTileLimit": "Innerhalb {} Kachel-Limit", "exceedsTileLimit": "Aktuelle Auswahl überschreitet {} Kachel-Limit", "offlineModeWarning": "Downloads im Offline-Modus deaktiviert. Deaktivieren Sie den Offline-Modus, um neue Bereiche herunterzuladen.", + "areaTooBigMessage": "Zoomen Sie auf mindestens Stufe {} heran, um Offline-Bereiche herunterzuladen. Downloads großer Gebiete können die App zum Absturz bringen.", "downloadStarted": "Download gestartet! Lade Kacheln und Knoten...", "downloadFailed": "Download konnte nicht gestartet werden: {}" }, diff --git a/lib/localizations/en.json b/lib/localizations/en.json index 9ec3210..4ec930e 100644 --- a/lib/localizations/en.json +++ b/lib/localizations/en.json @@ -136,6 +136,7 @@ "enableSubmittableProfile": "Enable a submittable profile in Settings to edit nodes.", "profileViewOnlyWarning": "This profile is for map viewing only. Please select a submittable profile to edit nodes.", "cannotMoveConstrainedNode": "Cannot move this camera - it's connected to another map element (OSM way/relation). You can still edit its tags and direction.", + "zoomInRequiredMessage": "Zoom in to at least level {} to add or edit surveillance nodes. This ensures precise positioning for accurate mapping.", "extractFromWay": "Extract node from way/relation", "extractFromWaySubtitle": "Create new node with same tags, allow moving to new location", "refineTags": "Refine Tags", @@ -151,6 +152,7 @@ "withinTileLimit": "Within {} tile limit", "exceedsTileLimit": "Current selection exceeds {} tile limit", "offlineModeWarning": "Downloads disabled while in offline mode. Disable offline mode to download new areas.", + "areaTooBigMessage": "Zoom in to at least level {} to download offline areas. Large area downloads can cause the app to become unresponsive.", "downloadStarted": "Download started! Fetching tiles and nodes...", "downloadFailed": "Failed to start download: {}" }, diff --git a/lib/localizations/es.json b/lib/localizations/es.json index 06748e9..3acaff6 100644 --- a/lib/localizations/es.json +++ b/lib/localizations/es.json @@ -136,6 +136,7 @@ "enableSubmittableProfile": "Habilite un perfil envíable en Configuración para editar nodos.", "profileViewOnlyWarning": "Este perfil es solo para visualización del mapa. Por favor, seleccione un perfil envíable para editar nodos.", "cannotMoveConstrainedNode": "No se puede mover esta cámara - está conectada a otro elemento del mapa (OSM way/relation). Aún puede editar sus etiquetas y dirección.", + "zoomInRequiredMessage": "Amplíe al menos al nivel {} para agregar o editar nodos de vigilancia. Esto garantiza un posicionamiento preciso para un mapeo exacto.", "extractFromWay": "Extraer nodo de way/relation", "extractFromWaySubtitle": "Crear nuevo nodo con las mismas etiquetas, permitir mover a nueva ubicación", "refineTags": "Refinar Etiquetas", @@ -151,6 +152,7 @@ "withinTileLimit": "Dentro del límite de {} mosaicos", "exceedsTileLimit": "La selección actual excede el límite de {} mosaicos", "offlineModeWarning": "Descargas deshabilitadas en modo sin conexión. Deshabilite el modo sin conexión para descargar nuevas áreas.", + "areaTooBigMessage": "Amplíe al menos al nivel {} para descargar áreas sin conexión. Las descargas de áreas grandes pueden hacer que la aplicación deje de responder.", "downloadStarted": "¡Descarga iniciada! Obteniendo mosaicos y nodos...", "downloadFailed": "Error al iniciar la descarga: {}" }, diff --git a/lib/localizations/fr.json b/lib/localizations/fr.json index b51d29a..f4b63df 100644 --- a/lib/localizations/fr.json +++ b/lib/localizations/fr.json @@ -136,6 +136,7 @@ "enableSubmittableProfile": "Activez un profil soumissible dans les Paramètres pour modifier les nœuds.", "profileViewOnlyWarning": "Ce profil est uniquement pour la visualisation de la carte. Veuillez sélectionner un profil soumissible pour modifier les nœuds.", "cannotMoveConstrainedNode": "Impossible de déplacer cette caméra - elle est connectée à un autre élément de carte (OSM way/relation). Vous pouvez toujours modifier ses balises et sa direction.", + "zoomInRequiredMessage": "Zoomez au moins au niveau {} pour ajouter ou modifier des nœuds de surveillance. Cela garantit un positionnement précis pour une cartographie exacte.", "extractFromWay": "Extraire le nœud du way/relation", "extractFromWaySubtitle": "Créer un nouveau nœud avec les mêmes balises, permettre le déplacement vers un nouvel emplacement", "refineTags": "Affiner Balises", @@ -151,7 +152,8 @@ "withinTileLimit": "Dans la limite de {} tuiles", "exceedsTileLimit": "La sélection actuelle dépasse la limite de {} tuiles", "offlineModeWarning": "Téléchargements désactivés en mode hors ligne. Désactivez le mode hors ligne pour télécharger de nouvelles zones.", - "downloadStarted": "Téléchargement démarré! Récupération des tuiles et nœuds...", + "areaTooBigMessage": "Zoomez au moins au niveau {} pour télécharger des zones hors ligne. Les téléchargements de grandes zones peuvent rendre l'application non réactive.", + "downloadStarted": "Téléchargement démarré ! Récupération des tuiles et nœuds...", "downloadFailed": "Échec du démarrage du téléchargement: {}" }, "downloadStarted": { diff --git a/lib/localizations/it.json b/lib/localizations/it.json index c462ee9..0386666 100644 --- a/lib/localizations/it.json +++ b/lib/localizations/it.json @@ -136,6 +136,7 @@ "enableSubmittableProfile": "Abilita un profilo inviabile nelle Impostazioni per modificare i nodi.", "profileViewOnlyWarning": "Questo profilo è solo per la visualizzazione della mappa. Per favore seleziona un profilo inviabile per modificare i nodi.", "cannotMoveConstrainedNode": "Impossibile spostare questa telecamera - è collegata a un altro elemento della mappa (OSM way/relation). Puoi ancora modificare i suoi tag e direzione.", + "zoomInRequiredMessage": "Ingrandisci almeno al livello {} per aggiungere o modificare nodi di sorveglianza. Questo garantisce un posizionamento preciso per una mappatura accurata.", "extractFromWay": "Estrai nodo da way/relation", "extractFromWaySubtitle": "Crea nuovo nodo con gli stessi tag, consenti spostamento in nuova posizione", "refineTags": "Affina Tag", @@ -151,6 +152,7 @@ "withinTileLimit": "Entro il limite di {} tile", "exceedsTileLimit": "La selezione corrente supera il limite di {} tile", "offlineModeWarning": "Download disabilitati in modalità offline. Disabilita la modalità offline per scaricare nuove aree.", + "areaTooBigMessage": "Ingrandisci almeno al livello {} per scaricare aree offline. I download di aree grandi possono rendere l'app non reattiva.", "downloadStarted": "Download avviato! Recupero tile e nodi...", "downloadFailed": "Impossibile avviare il download: {}" }, diff --git a/lib/localizations/pt.json b/lib/localizations/pt.json index 0e7d7d2..7c4e967 100644 --- a/lib/localizations/pt.json +++ b/lib/localizations/pt.json @@ -136,6 +136,7 @@ "enableSubmittableProfile": "Ative um perfil enviável nas Configurações para editar nós.", "profileViewOnlyWarning": "Este perfil é apenas para visualização do mapa. Por favor, selecione um perfil enviável para editar nós.", "cannotMoveConstrainedNode": "Não é possível mover esta câmera - ela está conectada a outro elemento do mapa (OSM way/relation). Você ainda pode editar suas tags e direção.", + "zoomInRequiredMessage": "Amplie para pelo menos o nível {} para adicionar ou editar nós de vigilância. Isto garante um posicionamento preciso para mapeamento exato.", "extractFromWay": "Extrair nó do way/relation", "extractFromWaySubtitle": "Criar novo nó com as mesmas tags, permitir mover para nova localização", "refineTags": "Refinar Tags", @@ -151,6 +152,7 @@ "withinTileLimit": "Dentro do limite de {} tiles", "exceedsTileLimit": "A seleção atual excede o limite de {} tiles", "offlineModeWarning": "Downloads desabilitados no modo offline. Desative o modo offline para baixar novas áreas.", + "areaTooBigMessage": "Amplie para pelo menos o nível {} para baixar áreas offline. Downloads de áreas grandes podem tornar o aplicativo não responsivo.", "downloadStarted": "Download iniciado! Buscando tiles e nós...", "downloadFailed": "Falha ao iniciar o download: {}" }, diff --git a/lib/localizations/zh.json b/lib/localizations/zh.json index 9933b83..0bedaf3 100644 --- a/lib/localizations/zh.json +++ b/lib/localizations/zh.json @@ -136,6 +136,7 @@ "enableSubmittableProfile": "在设置中启用可提交的配置文件以编辑节点。", "profileViewOnlyWarning": "此配置文件仅用于地图查看。请选择可提交的配置文件来编辑节点。", "cannotMoveConstrainedNode": "无法移动此相机 - 它连接到另一个地图元素(OSM way/relation)。您仍可以编辑其标签和方向。", + "zoomInRequiredMessage": "请放大至至少第{}级来添加或编辑监控节点。这确保精确定位以便准确制图。", "extractFromWay": "从way/relation中提取节点", "extractFromWaySubtitle": "创建具有相同标签的新节点,允许移动到新位置", "refineTags": "细化标签", @@ -151,6 +152,7 @@ "withinTileLimit": "在 {} 瓦片限制内", "exceedsTileLimit": "当前选择超出 {} 瓦片限制", "offlineModeWarning": "离线模式下禁用下载。禁用离线模式以下载新区域。", + "areaTooBigMessage": "请放大至至少第{}级来下载离线区域。下载大区域可能导致应用程序无响应。", "downloadStarted": "下载已开始!正在获取瓦片和节点...", "downloadFailed": "启动下载失败:{}" }, diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index b81fde6..27741fd 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -103,6 +103,21 @@ class _HomeScreenState extends State with TickerProviderStateMixin { void _openAddNodeSheet() { final appState = context.read(); + + // Check minimum zoom level before opening sheet + final currentZoom = _mapController.mapController.camera.zoom; + if (currentZoom < kMinZoomForNodeEditingSheets) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + LocalizationService.instance.t('editNode.zoomInRequiredMessage', + params: [kMinZoomForNodeEditingSheets.toString()]) + ), + ), + ); + return; + } + // Disable follow-me when adding a camera so the map doesn't jump around appState.setFollowMeMode(FollowMeMode.off); @@ -532,6 +547,20 @@ class _HomeScreenState extends State with TickerProviderStateMixin { child: NodeTagSheet( node: node, onEditPressed: () { + // Check minimum zoom level before starting edit session + final currentZoom = _mapController.mapController.camera.zoom; + if (currentZoom < kMinZoomForNodeEditingSheets) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + LocalizationService.instance.t('editNode.zoomInRequiredMessage', + params: [kMinZoomForNodeEditingSheets.toString()]) + ), + ), + ); + return; + } + final appState = context.read(); appState.startEditSession(node); // This will trigger _openEditNodeSheet via the existing auto-show logic @@ -760,10 +789,26 @@ class _HomeScreenState extends State with TickerProviderStateMixin { child: ElevatedButton.icon( icon: Icon(Icons.download_for_offline), label: Text(LocalizationService.instance.download), - onPressed: () => showDialog( - context: context, - builder: (ctx) => DownloadAreaDialog(controller: _mapController.mapController), - ), + onPressed: () { + // Check minimum zoom level before opening download dialog + final currentZoom = _mapController.mapController.camera.zoom; + if (currentZoom < kMinZoomForOfflineDownload) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + LocalizationService.instance.t('download.areaTooBigMessage', + params: [kMinZoomForOfflineDownload.toString()]) + ), + ), + ); + return; + } + + showDialog( + context: context, + builder: (ctx) => DownloadAreaDialog(controller: _mapController.mapController), + ); + }, style: ElevatedButton.styleFrom( minimumSize: Size(0, 48), textStyle: TextStyle(fontSize: 16), diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index b4f1919..57a665d 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -597,6 +597,18 @@ class MapViewState extends State { 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( + dest: pos.center, + zoom: kMinZoomForNodeEditingSheets.toDouble(), + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + return; // Don't process other position updates + } + if (session != null) { appState.updateSession(target: pos.center); }