diff --git a/assets/changelog.json b/assets/changelog.json index 3f5063a..43fb914 100644 --- a/assets/changelog.json +++ b/assets/changelog.json @@ -1,4 +1,17 @@ { + "1.3.4": { + "content": [ + "• NEW: 'Pause Upload Queue' toggle in Offline Settings - stops uploads while keeping live data access", + "• Useful for metered connections or when you want to batch uploads later", + "• Upload queue is now disabled if either full offline mode OR pause queue processing is enabled", + "• FIXED: Sheet buttons now remain visible when rotating from portrait to landscape mode", + "• FIXED: Sheets now properly resize when rotating between orientations without requiring user interaction", + "• IMPROVED: Tag list height adapts automatically for landscape orientation to prevent covering map", + "• IMPROVED: Sheets with few tags now shrink to appropriate size rather than maintaining fixed height", + "• IMPROVED: More reliable sheet layout using proper flexible height constraints", + "• CLEANED: Fixed minor code formatting inconsistencies" + ] + }, "1.3.3": { "content": [ "• NEW: Added builtin surveillance device profiles for Rekor and Axis Communications ALPR cameras", diff --git a/lib/app_state.dart b/lib/app_state.dart index 6f09473..c1983be 100644 --- a/lib/app_state.dart +++ b/lib/app_state.dart @@ -130,6 +130,7 @@ class AppState extends ChangeNotifier { // Settings state bool get offlineMode => _settingsState.offlineMode; + bool get pauseQueueProcessing => _settingsState.pauseQueueProcessing; int get maxCameras => _settingsState.maxCameras; UploadMode get uploadMode => _settingsState.uploadMode; FollowMeMode get followMeMode => _settingsState.followMeMode; @@ -411,6 +412,15 @@ class AppState extends ChangeNotifier { } } + Future setPauseQueueProcessing(bool enabled) async { + await _settingsState.setPauseQueueProcessing(enabled); + if (!enabled) { + _startUploader(); // Resume upload queue processing + } else { + _uploadQueueState.stopUploader(); // Stop uploader when paused + } + } + set maxCameras(int n) { _settingsState.maxCameras = n; } @@ -524,6 +534,7 @@ class AppState extends ChangeNotifier { void _startUploader() { _uploadQueueState.startUploader( offlineMode: offlineMode, + pauseQueueProcessing: pauseQueueProcessing, uploadMode: uploadMode, getAccessToken: _authState.getAccessToken, ); diff --git a/lib/dev_config.dart b/lib/dev_config.dart index 19e1467..ba4018e 100644 --- a/lib/dev_config.dart +++ b/lib/dev_config.dart @@ -80,7 +80,15 @@ 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 +const double kMaxTagListHeightRatioPortrait = 0.4; // Maximum height for tag lists in portrait mode +const double kMaxTagListHeightRatioLandscape = 0.3; // Maximum height for tag lists in landscape mode + +/// Get appropriate tag list height ratio based on screen orientation +double getTagListHeightRatio(BuildContext context) { + final size = MediaQuery.of(context).size; + final isLandscape = size.width > size.height; + return isLandscape ? kMaxTagListHeightRatioLandscape : kMaxTagListHeightRatioPortrait; +} // Proximity alerts configuration const int kProximityAlertDefaultDistance = 200; // meters diff --git a/lib/localizations/de.json b/lib/localizations/de.json index afecd28..db245ed 100644 --- a/lib/localizations/de.json +++ b/lib/localizations/de.json @@ -39,6 +39,8 @@ "maxNodesWarning": "Sie möchten das wahrscheinlich nicht tun, es sei denn, Sie sind absolut sicher, dass Sie einen guten Grund dafür haben.", "offlineMode": "Offline-Modus", "offlineModeSubtitle": "Alle Netzwerkanfragen außer für lokale/Offline-Bereiche deaktivieren.", + "pauseQueueProcessing": "Upload-Warteschlange pausieren", + "pauseQueueProcessingSubtitle": "Upload von wartenden Änderungen stoppen, aber Live-Datenzugriff beibehalten.", "offlineModeWarningTitle": "Aktive Downloads", "offlineModeWarningMessage": "Die Aktivierung des Offline-Modus bricht alle aktiven Bereichsdownloads ab. Möchten Sie fortfahren?", "enableOfflineMode": "Offline-Modus Aktivieren", diff --git a/lib/localizations/en.json b/lib/localizations/en.json index 1e31d9b..d2443aa 100644 --- a/lib/localizations/en.json +++ b/lib/localizations/en.json @@ -57,6 +57,8 @@ "maxNodesWarning": "You probably don't want to do that unless you are absolutely sure you have a good reason for it.", "offlineMode": "Offline Mode", "offlineModeSubtitle": "Disable all network requests except for local/offline areas.", + "pauseQueueProcessing": "Pause Upload Queue", + "pauseQueueProcessingSubtitle": "Stop uploading queued changes while keeping live data access.", "offlineModeWarningTitle": "Active Downloads", "offlineModeWarningMessage": "Enabling offline mode will cancel any active area downloads. Do you want to continue?", "enableOfflineMode": "Enable Offline Mode", diff --git a/lib/localizations/es.json b/lib/localizations/es.json index fc09955..2ba5a25 100644 --- a/lib/localizations/es.json +++ b/lib/localizations/es.json @@ -57,6 +57,8 @@ "maxNodesWarning": "Probablemente no quieras hacer eso a menos que estés absolutamente seguro de que tienes una buena razón para ello.", "offlineMode": "Modo Sin Conexión", "offlineModeSubtitle": "Deshabilitar todas las solicitudes de red excepto para áreas locales/sin conexión.", + "pauseQueueProcessing": "Pausar Cola de Subida", + "pauseQueueProcessingSubtitle": "Detener la subida de cambios en cola manteniendo acceso a datos en vivo.", "offlineModeWarningTitle": "Descargas Activas", "offlineModeWarningMessage": "Habilitar el modo sin conexión cancelará cualquier descarga de área activa. ¿Desea continuar?", "enableOfflineMode": "Habilitar Modo Sin Conexión", diff --git a/lib/localizations/fr.json b/lib/localizations/fr.json index f4eb673..c149391 100644 --- a/lib/localizations/fr.json +++ b/lib/localizations/fr.json @@ -57,6 +57,8 @@ "maxNodesWarning": "Vous ne voulez probablement pas faire cela à moins d'être absolument sûr d'avoir une bonne raison de le faire.", "offlineMode": "Mode Hors Ligne", "offlineModeSubtitle": "Désactiver toutes les requêtes réseau sauf pour les zones locales/hors ligne.", + "pauseQueueProcessing": "Suspendre la File d'Upload", + "pauseQueueProcessingSubtitle": "Arrêter l'upload des modifications en attente tout en gardant l'accès aux données en direct.", "offlineModeWarningTitle": "Téléchargements Actifs", "offlineModeWarningMessage": "L'activation du mode hors ligne annulera tous les téléchargements de zone actifs. Voulez-vous continuer?", "enableOfflineMode": "Activer le Mode Hors Ligne", diff --git a/lib/localizations/it.json b/lib/localizations/it.json index 3a5db4a..e070043 100644 --- a/lib/localizations/it.json +++ b/lib/localizations/it.json @@ -57,6 +57,8 @@ "maxNodesWarning": "Probabilmente non vuoi farlo a meno che non sei assolutamente sicuro di avere una buona ragione per farlo.", "offlineMode": "Modalità Offline", "offlineModeSubtitle": "Disabilita tutte le richieste di rete tranne per aree locali/offline.", + "pauseQueueProcessing": "Pausa Coda Upload", + "pauseQueueProcessingSubtitle": "Ferma l'upload delle modifiche in coda mantenendo l'accesso ai dati dal vivo.", "offlineModeWarningTitle": "Download Attivi", "offlineModeWarningMessage": "L'attivazione della modalità offline cancellerà qualsiasi download di area attivo. Vuoi continuare?", "enableOfflineMode": "Attiva Modalità Offline", diff --git a/lib/localizations/pt.json b/lib/localizations/pt.json index 564185e..548ec9d 100644 --- a/lib/localizations/pt.json +++ b/lib/localizations/pt.json @@ -57,6 +57,8 @@ "maxNodesWarning": "Você provavelmente não quer fazer isso a menos que tenha certeza absoluta de que tem uma boa razão para isso.", "offlineMode": "Modo Offline", "offlineModeSubtitle": "Desabilitar todas as requisições de rede exceto para áreas locais/offline.", + "pauseQueueProcessing": "Pausar Fila de Upload", + "pauseQueueProcessingSubtitle": "Parar upload de alterações na fila mantendo acesso a dados ao vivo.", "offlineModeWarningTitle": "Downloads Ativos", "offlineModeWarningMessage": "Ativar o modo offline cancelará qualquer download de área ativo. Deseja continuar?", "enableOfflineMode": "Ativar Modo Offline", diff --git a/lib/localizations/zh.json b/lib/localizations/zh.json index 66cad4d..c28b3e0 100644 --- a/lib/localizations/zh.json +++ b/lib/localizations/zh.json @@ -57,6 +57,8 @@ "maxNodesWarning": "除非您确定有充分的理由,否则您可能不想这样做。", "offlineMode": "离线模式", "offlineModeSubtitle": "禁用除本地/离线区域外的所有网络请求。", + "pauseQueueProcessing": "暂停上传队列", + "pauseQueueProcessingSubtitle": "停止上传排队的更改,同时保持实时数据访问。", "offlineModeWarningTitle": "活动下载", "offlineModeWarningMessage": "启用离线模式将取消任何活动的区域下载。您要继续吗?", "enableOfflineMode": "启用离线模式", diff --git a/lib/screens/settings/sections/offline_mode_section.dart b/lib/screens/settings/sections/offline_mode_section.dart index e66b2df..051726d 100644 --- a/lib/screens/settings/sections/offline_mode_section.dart +++ b/lib/screens/settings/sections/offline_mode_section.dart @@ -77,6 +77,27 @@ class OfflineModeSection extends StatelessWidget { onChanged: (value) => _handleOfflineModeChange(context, appState, value), ), ), + const SizedBox(height: 8), + ListTile( + leading: Icon( + Icons.pause_circle_outline, + color: appState.offlineMode + ? Theme.of(context).disabledColor + : Theme.of(context).iconTheme.color, + ), + title: Text( + locService.t('settings.pauseQueueProcessingSubtitle'), + style: appState.offlineMode + ? TextStyle(color: Theme.of(context).disabledColor) + : null, + ), + trailing: Switch( + value: appState.pauseQueueProcessing, + onChanged: appState.offlineMode + ? null // Disable when offline mode is on + : (value) => appState.setPauseQueueProcessing(value), + ), + ), ], ); }, diff --git a/lib/state/settings_state.dart b/lib/state/settings_state.dart index 525dafe..9d0a170 100644 --- a/lib/state/settings_state.dart +++ b/lib/state/settings_state.dart @@ -28,8 +28,10 @@ class SettingsState extends ChangeNotifier { static const String _proximityAlertDistancePrefsKey = 'proximity_alert_distance'; static const String _networkStatusIndicatorEnabledPrefsKey = 'network_status_indicator_enabled'; static const String _suspectedLocationMinDistancePrefsKey = 'suspected_location_min_distance'; + static const String _pauseQueueProcessingPrefsKey = 'pause_queue_processing'; bool _offlineMode = false; + bool _pauseQueueProcessing = false; int _maxCameras = 250; UploadMode _uploadMode = kEnableDevelopmentModes ? UploadMode.simulate : UploadMode.production; FollowMeMode _followMeMode = FollowMeMode.follow; @@ -42,6 +44,7 @@ class SettingsState extends ChangeNotifier { // Getters bool get offlineMode => _offlineMode; + bool get pauseQueueProcessing => _pauseQueueProcessing; int get maxCameras => _maxCameras; UploadMode get uploadMode => _uploadMode; FollowMeMode get followMeMode => _followMeMode; @@ -92,6 +95,9 @@ class SettingsState extends ChangeNotifier { // Load offline mode _offlineMode = prefs.getBool(_offlineModePrefsKey) ?? false; + // Load queue processing setting + _pauseQueueProcessing = prefs.getBool(_pauseQueueProcessingPrefsKey) ?? false; + // Load max cameras if (prefs.containsKey(_maxCamerasPrefsKey)) { _maxCameras = prefs.getInt(_maxCamerasPrefsKey) ?? 250; @@ -212,6 +218,13 @@ class SettingsState extends ChangeNotifier { notifyListeners(); } + Future setPauseQueueProcessing(bool enabled) async { + _pauseQueueProcessing = enabled; + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(_pauseQueueProcessingPrefsKey, enabled); + notifyListeners(); + } + set maxCameras(int n) { if (n < 10) n = 10; // minimum _maxCameras = n; diff --git a/lib/state/upload_queue_state.dart b/lib/state/upload_queue_state.dart index 619407d..8aafa63 100644 --- a/lib/state/upload_queue_state.dart +++ b/lib/state/upload_queue_state.dart @@ -163,16 +163,17 @@ class UploadQueueState extends ChangeNotifier { // Start the upload processing loop void startUploader({ required bool offlineMode, + required bool pauseQueueProcessing, required UploadMode uploadMode, required Future Function() getAccessToken, }) { _uploadTimer?.cancel(); - // No uploads without queue, or if offline mode is enabled. - if (_queue.isEmpty || offlineMode) return; + // No uploads if queue is empty, offline mode is enabled, or queue processing is paused + if (_queue.isEmpty || offlineMode || pauseQueueProcessing) return; _uploadTimer = Timer.periodic(const Duration(seconds: 10), (t) async { - if (_queue.isEmpty || offlineMode) { + if (_queue.isEmpty || offlineMode || pauseQueueProcessing) { _uploadTimer?.cancel(); return; } diff --git a/lib/widgets/node_tag_sheet.dart b/lib/widgets/node_tag_sheet.dart index bf1b4b1..7d9740c 100644 --- a/lib/widgets/node_tag_sheet.dart +++ b/lib/widgets/node_tag_sheet.dart @@ -101,132 +101,136 @@ class NodeTagSheet extends StatelessWidget { ); } - return SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - locService.t('node.title').replaceAll('{}', node.id.toString()), - style: Theme.of(context).textTheme.titleLarge, + return LayoutBuilder( + builder: (context, constraints) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + locService.t('node.title').replaceAll('{}', node.id.toString()), + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 12), + + // Tag list with flexible height constraint + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * getTagListHeightRatio(context), ), - const SizedBox(height: 12), - // 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: [ - ...node.tags.entries.map( - (e) => Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - e.key, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ...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); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('${LocalizationService.instance.t('advancedEdit.couldNotOpenURL')}: ${link.url}')), + ); + } + }, + text: e.value, style: TextStyle( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface, + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), - ), - const SizedBox(width: 8), - Expanded( - child: Linkify( - onOpen: (link) async { - final uri = Uri.parse(link.url); - if (await canLaunchUrl(uri)) { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } else if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('${LocalizationService.instance.t('advancedEdit.couldNotOpenURL')}: ${link.url}')), - ); - } - }, - 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), ), - ], - ), + ), + ], ), ), - ], - ), + ), + ], ), ), - const SizedBox(height: 16), - // First row: View and Advanced buttons - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton.icon( - onPressed: () => _viewOnOSM(), - icon: const Icon(Icons.open_in_new, size: 16), - label: Text(locService.t('actions.viewOnOSM')), + ), + const SizedBox(height: 16), + // First row: View and Advanced buttons + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + onPressed: () => _viewOnOSM(), + icon: const Icon(Icons.open_in_new, size: 16), + 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: Text(locService.t('actions.advanced')), + style: OutlinedButton.styleFrom( + minimumSize: const Size(0, 36), + ), + ), + ], + ], + ), + const SizedBox(height: 8), + // Second row: Edit, Delete, and Close buttons + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (isEditable) ...[ + ElevatedButton.icon( + onPressed: _openEditSheet, + icon: const Icon(Icons.edit, size: 18), + label: Text(locService.edit), + style: ElevatedButton.styleFrom( + minimumSize: const Size(0, 36), + ), ), const SizedBox(width: 8), - if (isEditable) ...[ - OutlinedButton.icon( - onPressed: _openAdvancedEdit, - icon: const Icon(Icons.open_in_new, size: 18), - label: Text(locService.t('actions.advanced')), - style: OutlinedButton.styleFrom( - minimumSize: const Size(0, 36), - ), + ElevatedButton.icon( + onPressed: _deleteNode, + icon: const Icon(Icons.delete, size: 18), + label: Text(locService.t('actions.delete')), + style: ElevatedButton.styleFrom( + minimumSize: const Size(0, 36), + foregroundColor: Colors.red, ), - ], - ], - ), - const SizedBox(height: 8), - // Second row: Edit, Delete, and Close buttons - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (isEditable) ...[ - ElevatedButton.icon( - onPressed: _openEditSheet, - icon: const Icon(Icons.edit, size: 18), - label: Text(locService.edit), - style: ElevatedButton.styleFrom( - minimumSize: const Size(0, 36), - ), - ), - const SizedBox(width: 8), - ElevatedButton.icon( - onPressed: _deleteNode, - icon: const Icon(Icons.delete, size: 18), - label: Text(locService.t('actions.delete')), - style: ElevatedButton.styleFrom( - minimumSize: const Size(0, 36), - foregroundColor: Colors.red, - ), - ), - const SizedBox(width: 12), - ], - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(locService.t('actions.close')), ), + const SizedBox(width: 12), ], - ), - ], - ), + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(locService.t('actions.close')), + ), + ], + ), + ], ), ), ); + }, + ); }, ); } diff --git a/lib/widgets/suspected_location_sheet.dart b/lib/widgets/suspected_location_sheet.dart index d739403..8e9d4d0 100644 --- a/lib/widgets/suspected_location_sheet.dart +++ b/lib/widgets/suspected_location_sheet.dart @@ -29,132 +29,135 @@ class SuspectedLocationSheet extends StatelessWidget { } } - return SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - locService.t('suspectedLocation.title', params: [location.ticketNo]), - style: Theme.of(context).textTheme.titleLarge, + return LayoutBuilder( + builder: (context, constraints) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + locService.t('suspectedLocation.title', params: [location.ticketNo]), + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 12), + + // Field list with flexible height constraint + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * getTagListHeightRatio(context), ), - const SizedBox(height: 12), - - // Constrain field list height to keep buttons visible - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * kMaxTagListHeightRatio, - ), - child: SingleChildScrollView( - child: Column( - children: [ - // Display all fields - ...displayData.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, - ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Display all fields + ...displayData.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: e.key.toLowerCase().contains('url') && e.value.isNotEmpty - ? GestureDetector( - onTap: () async { - final uri = Uri.parse(e.value); - if (await canLaunchUrl(uri)) { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } else { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Could not open URL: ${e.value}'), - ), - ); - } + ), + const SizedBox(width: 8), + Expanded( + child: e.key.toLowerCase().contains('url') && e.value.isNotEmpty + ? GestureDetector( + onTap: () async { + final uri = Uri.parse(e.value); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } else { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Could not open URL: ${e.value}'), + ), + ); } - }, - child: Text( - e.value, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - decoration: TextDecoration.underline, - ), - softWrap: true, - ), - ) - : Text( + } + }, + child: Text( e.value, style: TextStyle( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + color: Theme.of(context).colorScheme.primary, + decoration: TextDecoration.underline, ), softWrap: true, ), - ), - ], - ), + ) + : Text( + e.value, + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + ), + softWrap: true, + ), + ), + ], ), ), - ], - ), - ), - ), - - const SizedBox(height: 16), - - // Coordinates info - Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - locService.t('suspectedLocation.coordinates'), - style: TextStyle( - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface, - ), - ), - const SizedBox(width: 8), - Expanded( - child: Text( - '${location.centroid.latitude.toStringAsFixed(6)}, ${location.centroid.longitude.toStringAsFixed(6)}', - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), - ), - softWrap: true, - ), ), ], ), ), - - const SizedBox(height: 16), - - // Close button - Row( - mainAxisAlignment: MainAxisAlignment.end, + ), + + const SizedBox(height: 16), + + // Coordinates info + Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(locService.t('actions.close')), + Text( + locService.t('suspectedLocation.coordinates'), + style: TextStyle( + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + '${location.centroid.latitude.toStringAsFixed(6)}, ${location.centroid.longitude.toStringAsFixed(6)}', + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + ), + softWrap: true, + ), ), ], ), - ], - ), + ), + + const SizedBox(height: 16), + + // Close button + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(locService.t('actions.close')), + ), + ], + ), + ], ), ), ); + }, + ); }, ); } diff --git a/pubspec.yaml b/pubspec.yaml index 7028349..2911015 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: deflockapp description: Map public surveillance infrastructure with OpenStreetMap publish_to: "none" -version: 1.3.3+11 # The thing after the + is the version code, incremented with each release +version: 1.3.4+12 # The thing after the + is the version code, incremented with each release environment: sdk: ">=3.5.0 <4.0.0" # oauth2_client 4.x needs Dart 3.5+