From 7c2b9ea087e40c8bde0590453a86c94d724fc504 Mon Sep 17 00:00:00 2001 From: stopflock Date: Sun, 16 Nov 2025 18:16:50 -0600 Subject: [PATCH] Configurable max height for node tags box, localizations for new UX strings --- lib/dev_config.dart | 3 + lib/localizations/de.json | 7 +- lib/localizations/en.json | 28 +++++++- lib/localizations/es.json | 7 +- lib/localizations/fr.json | 7 +- lib/localizations/it.json | 7 +- lib/localizations/pt.json | 7 +- lib/localizations/zh.json | 7 +- lib/widgets/advanced_edit_options_sheet.dart | 37 +++++------ lib/widgets/edit_node_sheet.dart | 2 +- lib/widgets/node_tag_sheet.dart | 67 ++++++++++++-------- lib/widgets/suspected_location_sheet.dart | 3 +- 12 files changed, 121 insertions(+), 61 deletions(-) diff --git a/lib/dev_config.dart b/lib/dev_config.dart index b02b94c..19e1467 100644 --- a/lib/dev_config.dart +++ b/lib/dev_config.dart @@ -79,6 +79,9 @@ const int kDataRefreshIntervalSeconds = 60; // Refresh cached data after this ma const Duration kFollowMeAnimationDuration = Duration(milliseconds: 600); const double kMinSpeedForRotationMps = 1.0; // Minimum speed (m/s) to apply rotation +// Sheet content configuration +const double kMaxTagListHeightRatio = 0.4; // Maximum height for tag lists as fraction of screen height + // Proximity alerts configuration const int kProximityAlertDefaultDistance = 200; // meters const int kProximityAlertMinDistance = 50; // meters diff --git a/lib/localizations/de.json b/lib/localizations/de.json index 9356133..01a02ca 100644 --- a/lib/localizations/de.json +++ b/lib/localizations/de.json @@ -16,7 +16,10 @@ "close": "Schließen", "submit": "Senden", "saveEdit": "Bearbeitung Speichern", - "clear": "Löschen" + "clear": "Löschen", + "viewOnOSM": "Auf OSM anzeigen", + "advanced": "Erweitert", + "useAdvancedEditor": "Erweiterten Editor verwenden" }, "followMe": { "off": "Verfolgung aktivieren", @@ -94,7 +97,7 @@ "sandboxModeWarning": "Bearbeitungen von Produktionsknoten können nicht an die Sandbox übertragen werden. Wechseln Sie in den Produktionsmodus in den Einstellungen, um Knoten zu bearbeiten.", "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 einer Straße oder einem Bereich verbunden (OSM-Weg/Relation). Sie können trotzdem ihre Tags und Richtung 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.", "refineTags": "Tags Verfeinern", "refineTagsWithProfile": "Tags Verfeinern ({})" }, diff --git a/lib/localizations/en.json b/lib/localizations/en.json index 871ff28..1e31d9b 100644 --- a/lib/localizations/en.json +++ b/lib/localizations/en.json @@ -34,7 +34,10 @@ "close": "Close", "submit": "Submit", "saveEdit": "Save Edit", - "clear": "Clear" + "clear": "Clear", + "viewOnOSM": "View on OSM", + "advanced": "Advanced", + "useAdvancedEditor": "Use Advanced Editor" }, "followMe": { "off": "Enable follow-me", @@ -112,7 +115,7 @@ "sandboxModeWarning": "Cannot submit edits on production nodes to sandbox. Switch to Production mode in Settings to edit nodes.", "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 a street or area (OSM way/relation). You can still edit its tags and direction.", + "cannotMoveConstrainedNode": "Cannot move this camera - it's connected to another map element (OSM way/relation). You can still edit its tags and direction.", "refineTags": "Refine Tags", "refineTagsWithProfile": "Refine Tags ({})" }, @@ -324,6 +327,27 @@ "selectMapLayer": "Select Map Layer", "noTileProvidersAvailable": "No tile providers available" }, + "advancedEdit": { + "title": "Advanced Editing Options", + "subtitle": "These editors offer more advanced features for complex edits.", + "webEditors": "Web Editors", + "mobileEditors": "Mobile Editors", + "iDEditor": "iD Editor", + "iDEditorSubtitle": "Full-featured web editor - always works", + "rapidEditor": "RapiD Editor", + "rapidEditorSubtitle": "AI-assisted editing with Facebook data", + "vespucci": "Vespucci", + "vespucciSubtitle": "Advanced Android OSM editor", + "streetComplete": "StreetComplete", + "streetCompleteSubtitle": "Survey-based mapping app", + "everyDoor": "EveryDoor", + "everyDoorSubtitle": "Fast POI editing", + "goMap": "Go Map!!", + "goMapSubtitle": "iOS OSM editor", + "couldNotOpenEditor": "Could not open editor - app may not be installed", + "couldNotOpenURL": "Could not open URL", + "couldNotOpenOSMWebsite": "Could not open OSM website" + }, "networkStatus": { "showIndicator": "Show network status indicator", "showIndicatorSubtitle": "Display network loading and error status on the map", diff --git a/lib/localizations/es.json b/lib/localizations/es.json index 5edc157..ac78f5a 100644 --- a/lib/localizations/es.json +++ b/lib/localizations/es.json @@ -34,7 +34,10 @@ "close": "Cerrar", "submit": "Enviar", "saveEdit": "Guardar Edición", - "clear": "Limpiar" + "clear": "Limpiar", + "viewOnOSM": "Ver en OSM", + "advanced": "Avanzado", + "useAdvancedEditor": "Usar Editor Avanzado" }, "followMe": { "off": "Activar seguimiento", @@ -112,7 +115,7 @@ "sandboxModeWarning": "No se pueden enviar ediciones de nodos de producción al sandbox. Cambie al modo Producción en Configuración para editar nodos.", "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 una calle o área (OSM way/relation). Aún puede editar sus etiquetas y dirección.", + "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.", "refineTags": "Refinar Etiquetas", "refineTagsWithProfile": "Refinar Etiquetas ({})" }, diff --git a/lib/localizations/fr.json b/lib/localizations/fr.json index 3d5648a..af46e49 100644 --- a/lib/localizations/fr.json +++ b/lib/localizations/fr.json @@ -34,7 +34,10 @@ "close": "Fermer", "submit": "Soumettre", "saveEdit": "Sauvegarder Modification", - "clear": "Effacer" + "clear": "Effacer", + "viewOnOSM": "Voir sur OSM", + "advanced": "Avancé", + "useAdvancedEditor": "Utiliser l'Éditeur Avancé" }, "followMe": { "off": "Activer le suivi", @@ -112,7 +115,7 @@ "sandboxModeWarning": "Impossible de soumettre des modifications de nœuds de production au sandbox. Passez au mode Production dans les Paramètres pour modifier les nœuds.", "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 à une rue ou une zone (OSM way/relation). Vous pouvez toujours modifier ses balises et sa direction.", + "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.", "refineTags": "Affiner Balises", "refineTagsWithProfile": "Affiner Balises ({})" }, diff --git a/lib/localizations/it.json b/lib/localizations/it.json index a987570..31851a0 100644 --- a/lib/localizations/it.json +++ b/lib/localizations/it.json @@ -34,7 +34,10 @@ "close": "Chiudi", "submit": "Invia", "saveEdit": "Salva Modifica", - "clear": "Pulisci" + "clear": "Pulisci", + "viewOnOSM": "Visualizza su OSM", + "advanced": "Avanzato", + "useAdvancedEditor": "Usa Editor Avanzato" }, "followMe": { "off": "Attiva seguimi", @@ -112,7 +115,7 @@ "sandboxModeWarning": "Impossibile inviare modifiche di nodi di produzione alla sandbox. Passa alla modalità Produzione nelle Impostazioni per modificare i nodi.", "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 una strada o area (OSM way/relation). Puoi ancora modificare i suoi tag e direzione.", + "cannotMoveConstrainedNode": "Impossibile spostare questa telecamera - è collegata a un altro elemento della mappa (OSM way/relation). Puoi ancora modificare i suoi tag e direzione.", "refineTags": "Affina Tag", "refineTagsWithProfile": "Affina Tag ({})" }, diff --git a/lib/localizations/pt.json b/lib/localizations/pt.json index bb96db6..8730981 100644 --- a/lib/localizations/pt.json +++ b/lib/localizations/pt.json @@ -34,7 +34,10 @@ "close": "Fechar", "submit": "Enviar", "saveEdit": "Salvar Edição", - "clear": "Limpar" + "clear": "Limpar", + "viewOnOSM": "Ver no OSM", + "advanced": "Avançado", + "useAdvancedEditor": "Usar Editor Avançado" }, "followMe": { "off": "Ativar seguir-me", @@ -112,7 +115,7 @@ "sandboxModeWarning": "Não é possível enviar edições de nós de produção para o sandbox. Mude para o modo Produção nas Configurações para editar nós.", "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 uma rua ou área (OSM way/relation). Você ainda pode editar suas tags e direção.", + "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.", "refineTags": "Refinar Tags", "refineTagsWithProfile": "Refinar Tags ({})" }, diff --git a/lib/localizations/zh.json b/lib/localizations/zh.json index 3a228fc..912e6f9 100644 --- a/lib/localizations/zh.json +++ b/lib/localizations/zh.json @@ -34,7 +34,10 @@ "close": "关闭", "submit": "提交", "saveEdit": "保存编辑", - "clear": "清空" + "clear": "清空", + "viewOnOSM": "在OSM上查看", + "advanced": "高级", + "useAdvancedEditor": "使用高级编辑器" }, "followMe": { "off": "启用跟随模式", @@ -112,7 +115,7 @@ "sandboxModeWarning": "无法将生产节点的编辑提交到沙盒。在设置中切换到生产模式以编辑节点。", "enableSubmittableProfile": "在设置中启用可提交的配置文件以编辑节点。", "profileViewOnlyWarning": "此配置文件仅用于地图查看。请选择可提交的配置文件来编辑节点。", - "cannotMoveConstrainedNode": "无法移动此相机 - 它连接到街道或区域(OSM way/relation)。您仍可以编辑其标签和方向。", + "cannotMoveConstrainedNode": "无法移动此相机 - 它连接到另一个地图元素(OSM way/relation)。您仍可以编辑其标签和方向。", "refineTags": "细化标签", "refineTagsWithProfile": "细化标签({})" }, diff --git a/lib/widgets/advanced_edit_options_sheet.dart b/lib/widgets/advanced_edit_options_sheet.dart index 6f237aa..aa79cdd 100644 --- a/lib/widgets/advanced_edit_options_sheet.dart +++ b/lib/widgets/advanced_edit_options_sheet.dart @@ -21,12 +21,12 @@ class AdvancedEditOptionsSheet extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Advanced Editing Options', + locService.t('advancedEdit.title'), style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 8), Text( - 'These editors offer more advanced features for complex edits.', + locService.t('advancedEdit.subtitle'), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).textTheme.bodySmall?.color, ), @@ -35,22 +35,22 @@ class AdvancedEditOptionsSheet extends StatelessWidget { // Web Editors Section Text( - 'Web Editors', + locService.t('advancedEdit.webEditors'), style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), _buildEditorTile( context: context, icon: Icons.public, - title: 'iD Editor', - subtitle: 'Full-featured web editor - always works', + title: locService.t('advancedEdit.iDEditor'), + subtitle: locService.t('advancedEdit.iDEditorSubtitle'), onTap: () => _launchEditor(context, 'https://www.openstreetmap.org/edit?editor=id&node=${node.id}'), ), _buildEditorTile( context: context, icon: Icons.speed, - title: 'RapiD Editor', - subtitle: 'AI-assisted editing with Facebook data', + title: locService.t('advancedEdit.rapidEditor'), + subtitle: locService.t('advancedEdit.rapidEditorSubtitle'), onTap: () => _launchEditor(context, 'https://rapideditor.org/edit#map=19/0/0&nodes=${node.id}'), ), @@ -58,7 +58,7 @@ class AdvancedEditOptionsSheet extends StatelessWidget { // Mobile Editors Section Text( - 'Mobile Editors', + locService.t('advancedEdit.mobileEditors'), style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), @@ -67,22 +67,22 @@ class AdvancedEditOptionsSheet extends StatelessWidget { _buildEditorTile( context: context, icon: Icons.android, - title: 'Vespucci', - subtitle: 'Advanced Android OSM editor', + title: locService.t('advancedEdit.vespucci'), + subtitle: locService.t('advancedEdit.vespucciSubtitle'), onTap: () => _launchEditor(context, 'vespucci://edit?node=${node.id}'), ), _buildEditorTile( context: context, icon: Icons.place, - title: 'StreetComplete', - subtitle: 'Survey-based mapping app', + title: locService.t('advancedEdit.streetComplete'), + subtitle: locService.t('advancedEdit.streetCompleteSubtitle'), onTap: () => _launchEditor(context, 'streetcomplete://quest?node=${node.id}'), ), _buildEditorTile( context: context, icon: Icons.map, - title: 'EveryDoor', - subtitle: 'Fast POI editing', + title: locService.t('advancedEdit.everyDoor'), + subtitle: locService.t('advancedEdit.everyDoorSubtitle'), onTap: () => _launchEditor(context, 'everydoor://edit?node=${node.id}'), ), ], @@ -91,8 +91,8 @@ class AdvancedEditOptionsSheet extends StatelessWidget { _buildEditorTile( context: context, icon: Icons.phone_iphone, - title: 'Go Map!!', - subtitle: 'iOS OSM editor', + title: locService.t('advancedEdit.goMap'), + subtitle: locService.t('advancedEdit.goMapSubtitle'), onTap: () => _launchEditor(context, 'gomaposm://edit?node=${node.id}'), ), ], @@ -131,6 +131,7 @@ class AdvancedEditOptionsSheet extends StatelessWidget { } void _launchEditor(BuildContext context, String url) async { + final locService = LocalizationService.instance; Navigator.pop(context); // Close the sheet first try { @@ -140,14 +141,14 @@ class AdvancedEditOptionsSheet extends StatelessWidget { } else { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Could not open $url - app may not be installed')), + SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenEditor'))), ); } } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Could not open editor - app may not be installed')), + SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenEditor'))), ); } } diff --git a/lib/widgets/edit_node_sheet.dart b/lib/widgets/edit_node_sheet.dart index 83d2772..82ca478 100644 --- a/lib/widgets/edit_node_sheet.dart +++ b/lib/widgets/edit_node_sheet.dart @@ -236,7 +236,7 @@ class EditNodeSheet extends StatelessWidget { OutlinedButton.icon( onPressed: () => _openAdvancedEdit(context), icon: const Icon(Icons.open_in_new, size: 16), - label: const Text('Use Advanced Editor'), + label: Text(locService.t('actions.useAdvancedEditor')), style: OutlinedButton.styleFrom( minimumSize: const Size(0, 32), ), diff --git a/lib/widgets/node_tag_sheet.dart b/lib/widgets/node_tag_sheet.dart index 6804d4a..bf1b4b1 100644 --- a/lib/widgets/node_tag_sheet.dart +++ b/lib/widgets/node_tag_sheet.dart @@ -5,6 +5,7 @@ import 'package:flutter_linkify/flutter_linkify.dart'; import '../models/osm_node.dart'; import '../app_state.dart'; import '../services/localization_service.dart'; +import '../dev_config.dart'; import 'advanced_edit_options_sheet.dart'; class NodeTagSheet extends StatelessWidget { @@ -79,14 +80,14 @@ class NodeTagSheet extends StatelessWidget { } else { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Could not open OSM website')), + SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenOSMWebsite'))), ); } } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Could not open OSM website')), + SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenOSMWebsite'))), ); } } @@ -113,21 +114,29 @@ class NodeTagSheet extends StatelessWidget { style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 12), - ...node.tags.entries.map( - (e) => Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, + // Constrain tag list height to keep buttons and map visible + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * kMaxTagListHeightRatio, + ), + child: SingleChildScrollView( + child: Column( children: [ - Text( - e.key, - style: TextStyle( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - const SizedBox(width: 8), - Expanded( + ...node.tags.entries.map( + (e) => Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + e.key, + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + const SizedBox(width: 8), + Expanded( child: Linkify( onOpen: (link) async { final uri = Uri.parse(link.url); @@ -135,19 +144,23 @@ class NodeTagSheet extends StatelessWidget { await launchUrl(uri, mode: LaunchMode.externalApplication); } else if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Could not open URL: ${link.url}')), + SnackBar(content: Text('${LocalizationService.instance.t('advancedEdit.couldNotOpenURL')}: ${link.url}')), ); } }, - text: e.value, - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + text: e.value, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + ), + linkStyle: TextStyle( + color: Theme.of(context).colorScheme.primary, + decoration: TextDecoration.underline, + ), + options: const LinkifyOptions(humanize: false), + ), + ), + ], ), - linkStyle: TextStyle( - color: Theme.of(context).colorScheme.primary, - decoration: TextDecoration.underline, - ), - options: const LinkifyOptions(humanize: false), ), ), ], @@ -162,14 +175,14 @@ class NodeTagSheet extends StatelessWidget { TextButton.icon( onPressed: () => _viewOnOSM(), icon: const Icon(Icons.open_in_new, size: 16), - label: const Text('View on OSM'), + label: Text(locService.t('actions.viewOnOSM')), ), const SizedBox(width: 8), if (isEditable) ...[ OutlinedButton.icon( onPressed: _openAdvancedEdit, icon: const Icon(Icons.open_in_new, size: 18), - label: const Text('Advanced'), + label: Text(locService.t('actions.advanced')), style: OutlinedButton.styleFrom( minimumSize: const Size(0, 36), ), diff --git a/lib/widgets/suspected_location_sheet.dart b/lib/widgets/suspected_location_sheet.dart index ebc8f71..dfa24e7 100644 --- a/lib/widgets/suspected_location_sheet.dart +++ b/lib/widgets/suspected_location_sheet.dart @@ -5,6 +5,7 @@ import 'package:url_launcher/url_launcher.dart'; import '../models/suspected_location.dart'; import '../app_state.dart'; import '../services/localization_service.dart'; +import '../dev_config.dart'; class SuspectedLocationSheet extends StatelessWidget { final SuspectedLocation location; @@ -47,7 +48,7 @@ class SuspectedLocationSheet extends StatelessWidget { // Constrain field list height to keep buttons visible ConstrainedBox( constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.4, // Max 40% of screen height + maxHeight: MediaQuery.of(context).size.height * kMaxTagListHeightRatio, ), child: SingleChildScrollView( child: Column(