diff --git a/CLAUDE.md b/CLAUDE.md index cbd0b56..ebf25d6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -752,6 +752,14 @@ MapOptions( #### Troubleshooting +**RangeError: Invalid value: Not in inclusive range 0..15: 16**: +- **Cause**: Map zoom level exceeds the number of resolutions defined in Slovenian CRS +- **Fix**: Zoom level is automatically clamped when switching to WMS layers +- **Implementation**: + - Layer switching clamps zoom to `layer.maxZoom` if current zoom exceeds it + - Layer loading from settings clamps `_savedMapZoom` to layer's maximum + - Prevents crash when switching from high-zoom layer (19+) to WMS layer (15 max) + **400 Bad Request Errors**: - **Cause**: Tile grid misalignment (wrong origin, bounds, or resolutions) - **Fix**: Verify values match WMTS GetCapabilities exactly diff --git a/ios/Runner.app.dSYM.zip b/ios/Runner.app.dSYM.zip index e773401..571e481 100644 Binary files a/ios/Runner.app.dSYM.zip and b/ios/Runner.app.dSYM.zip differ diff --git a/ios/Runner.ipa b/ios/Runner.ipa index f6022d2..f20f106 100644 Binary files a/ios/Runner.ipa and b/ios/Runner.ipa differ diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index bc2cc91..ad54059 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -489,7 +489,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = JND55328G8; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -511,7 +511,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = JND55328G8; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; @@ -530,7 +530,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = JND55328G8; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; @@ -547,7 +547,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = JND55328G8; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; @@ -679,7 +679,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = JND55328G8; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -702,7 +702,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 40; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = JND55328G8; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 466d274..13a4ca8 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -43,7 +43,7 @@ CFBundleSignature ???? CFBundleVersion - 40 + 41 LSRequiresIPhoneOS NSBluetoothAlwaysUsageDescription diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml index c245ce0..db372fc 100644 --- a/ios/fastlane/report.xml +++ b/ios/fastlane/report.xml @@ -5,22 +5,24 @@ - + - + - + - + + + diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index f2134ff..3c51eef 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1683,6 +1683,16 @@ "description": "Beschreibung für Karte-drehen-Umschalter" }, + "resetMapRotation": "Drehung zurücksetzen", + "@resetMapRotation": { + "description": "Schaltfläche zum Zurücksetzen der Kartendrehung nach Norden" + }, + + "resetMapRotationTooltip": "Karte nach Norden zurücksetzen", + "@resetMapRotationTooltip": { + "description": "Tooltip für Drehung-zurücksetzen-Schaltfläche" + }, + "showMapDebugInfo": "Karten-Debug-Info anzeigen", "@showMapDebugInfo": { "description": "Umschalter zum Anzeigen von Karten-Debug-Informationen" diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d7f5cec..07bc272 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1683,6 +1683,16 @@ "description": "Description for rotate map toggle" }, + "resetMapRotation": "Reset Rotation", + "@resetMapRotation": { + "description": "Button to reset map rotation to north" + }, + + "resetMapRotationTooltip": "Reset map to north", + "@resetMapRotationTooltip": { + "description": "Tooltip for reset rotation button" + }, + "showMapDebugInfo": "Show Map Debug Info", "@showMapDebugInfo": { "description": "Toggle for showing map debug information" diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 6ebdcdf..4268ab6 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -1683,6 +1683,16 @@ "description": "Descripción del alternar de rotar mapa" }, + "resetMapRotation": "Restablecer rotación", + "@resetMapRotation": { + "description": "Botón para restablecer la rotación del mapa al norte" + }, + + "resetMapRotationTooltip": "Restablecer mapa al norte", + "@resetMapRotationTooltip": { + "description": "Tooltip para botón de restablecer rotación" + }, + "showMapDebugInfo": "Mostrar información de depuración del mapa", "@showMapDebugInfo": { "description": "Alternar para mostrar información de depuración del mapa" diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d494c7b..8012d79 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -1683,6 +1683,16 @@ "description": "Description de l'interrupteur de rotation de la carte" }, + "resetMapRotation": "Réinitialiser la rotation", + "@resetMapRotation": { + "description": "Bouton pour réinitialiser la rotation de la carte vers le nord" + }, + + "resetMapRotationTooltip": "Réinitialiser la carte vers le nord", + "@resetMapRotationTooltip": { + "description": "Infobulle pour le bouton de réinitialisation de rotation" + }, + "showMapDebugInfo": "Afficher les infos de débogage de la carte", "@showMapDebugInfo": { "description": "Interrupteur pour afficher les informations de débogage de la carte" diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb index 223dac0..85f072d 100644 --- a/lib/l10n/app_hr.arb +++ b/lib/l10n/app_hr.arb @@ -567,6 +567,10 @@ "mapFollowsDirection": "Karta slijedi vaš smjer pri kretanju", + "resetMapRotation": "Resetiraj rotaciju", + + "resetMapRotationTooltip": "Vrati kartu na sjever", + "showMapDebugInfo": "Prikaži debug informacije karte", "displayZoomLevelBounds": "Prikaži razinu zumiranja i granice", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 518e0de..bd59c18 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -1683,6 +1683,16 @@ "description": "Descrizione per l'interruttore ruota mappa" }, + "resetMapRotation": "Ripristina Rotazione", + "@resetMapRotation": { + "description": "Pulsante per ripristinare la rotazione della mappa verso nord" + }, + + "resetMapRotationTooltip": "Ripristina mappa verso nord", + "@resetMapRotationTooltip": { + "description": "Tooltip per il pulsante di ripristino rotazione" + }, + "showMapDebugInfo": "Mostra Info Debug Mappa", "@showMapDebugInfo": { "description": "Interruttore per mostrare informazioni di debug della mappa" diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e6608ab..0bf76ef 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1810,6 +1810,18 @@ abstract class AppLocalizations { /// **'Map follows your direction when moving'** String get mapFollowsDirection; + /// Button to reset map rotation to north + /// + /// In en, this message translates to: + /// **'Reset Rotation'** + String get resetMapRotation; + + /// Tooltip for reset rotation button + /// + /// In en, this message translates to: + /// **'Reset map to north'** + String get resetMapRotationTooltip; + /// Toggle for showing map debug information /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 065fc65..68726fc 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -991,6 +991,12 @@ class AppLocalizationsDe extends AppLocalizations { String get mapFollowsDirection => 'Karte folgt Ihrer Richtung während der Bewegung'; + @override + String get resetMapRotation => 'Drehung zurücksetzen'; + + @override + String get resetMapRotationTooltip => 'Karte nach Norden zurücksetzen'; + @override String get showMapDebugInfo => 'Karten-Debug-Info anzeigen'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index f40caaf..84152c5 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -987,6 +987,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get mapFollowsDirection => 'Map follows your direction when moving'; + @override + String get resetMapRotation => 'Reset Rotation'; + + @override + String get resetMapRotationTooltip => 'Reset map to north'; + @override String get showMapDebugInfo => 'Show Map Debug Info'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 67f8420..02926b7 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -993,6 +993,12 @@ class AppLocalizationsEs extends AppLocalizations { String get mapFollowsDirection => 'El mapa sigue tu dirección cuando te mueves'; + @override + String get resetMapRotation => 'Restablecer rotación'; + + @override + String get resetMapRotationTooltip => 'Restablecer mapa al norte'; + @override String get showMapDebugInfo => 'Mostrar información de depuración del mapa'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 651913d..6c1665a 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -995,6 +995,12 @@ class AppLocalizationsFr extends AppLocalizations { String get mapFollowsDirection => 'La carte suit votre direction lorsque vous vous déplacez'; + @override + String get resetMapRotation => 'Réinitialiser la rotation'; + + @override + String get resetMapRotationTooltip => 'Réinitialiser la carte vers le nord'; + @override String get showMapDebugInfo => 'Afficher les infos de débogage de la carte'; diff --git a/lib/l10n/app_localizations_hr.dart b/lib/l10n/app_localizations_hr.dart index 3f7c924..079ecb9 100644 --- a/lib/l10n/app_localizations_hr.dart +++ b/lib/l10n/app_localizations_hr.dart @@ -985,6 +985,12 @@ class AppLocalizationsHr extends AppLocalizations { @override String get mapFollowsDirection => 'Karta slijedi vaš smjer pri kretanju'; + @override + String get resetMapRotation => 'Resetiraj rotaciju'; + + @override + String get resetMapRotationTooltip => 'Vrati kartu na sjever'; + @override String get showMapDebugInfo => 'Prikaži debug informacije karte'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 5104caa..319ab8f 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -992,6 +992,12 @@ class AppLocalizationsIt extends AppLocalizations { String get mapFollowsDirection => 'La mappa segue la tua direzione quando ti muovi'; + @override + String get resetMapRotation => 'Ripristina Rotazione'; + + @override + String get resetMapRotationTooltip => 'Ripristina mappa verso nord'; + @override String get showMapDebugInfo => 'Mostra Info Debug Mappa'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index adfe261..3249ad0 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -985,6 +985,12 @@ class AppLocalizationsSl extends AppLocalizations { @override String get mapFollowsDirection => 'Zemljevid sledi vaši smeri pri gibanju'; + @override + String get resetMapRotation => 'Ponastavi rotacijo'; + + @override + String get resetMapRotationTooltip => 'Ponastavi zemljevid na sever'; + @override String get showMapDebugInfo => 'Prikaži debug informacije zemljevida'; diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index 0121d67..40ac6ad 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -567,6 +567,10 @@ "mapFollowsDirection": "Zemljevid sledi vaši smeri pri gibanju", + "resetMapRotation": "Ponastavi rotacijo", + + "resetMapRotationTooltip": "Ponastavi zemljevid na sever", + "showMapDebugInfo": "Prikaži debug informacije zemljevida", "displayZoomLevelBounds": "Prikaži nivo povečave in meje", diff --git a/lib/screens/map_tab.dart b/lib/screens/map_tab.dart index 03020eb..d252187 100644 --- a/lib/screens/map_tab.dart +++ b/lib/screens/map_tab.dart @@ -291,6 +291,9 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { orElse: () => MapLayer.openStreetMap, ); _currentLayer = mbtilesLayer; + } else if (layerType == MapLayerType.wmsBase) { + // Use Slovenian aerial layer if that's what was saved + _currentLayer = _slovenianAerialLayer; } else { // Use default layer _currentLayer = MapLayer.allLayers.firstWhere( @@ -299,6 +302,12 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { ); } } + + // Clamp saved zoom if it exceeds the current layer's maximum + // For WMS layers, use a middle zoom (11) instead of max zoom to avoid extreme close-up + if (_savedMapZoom != null && _savedMapZoom! > _currentLayer.maxZoom) { + _savedMapZoom = _currentLayer.isWms ? 11.0 : _currentLayer.maxZoom; + } }); } } @@ -436,6 +445,18 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { } } + // Reset map rotation to north (0 degrees) + void _resetMapRotation() { + if (!_isMapReady) return; + try { + final camera = _mapController.camera; + _mapController.moveAndRotate(camera.center, camera.zoom, 0); + } catch (e) { + // Silently fail if map controller not ready + debugPrint('Failed to reset map rotation: $e'); + } + } + LatLng _calculateCenter(List contacts, List sarMarkers) { return _markerService.calculateCenter( contacts: contacts, @@ -540,6 +561,13 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { onTap: () async { setState(() { _currentLayer = layer; + // Clamp zoom level if current zoom exceeds new layer's max + if (_isMapReady && _mapController.camera.zoom > layer.maxZoom) { + _mapController.move( + _mapController.camera.center, + layer.maxZoom, + ); + } }); _saveSettings(); Navigator.pop(context); @@ -557,6 +585,14 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { onTap: () async { setState(() { _currentLayer = _slovenianAerialLayer; + // Clamp zoom level if current zoom exceeds new layer's max + // For WMS layers, use a middle zoom (11) instead of max zoom to avoid extreme close-up + if (_isMapReady && _mapController.camera.zoom > _slovenianAerialLayer.maxZoom) { + _mapController.move( + _mapController.camera.center, + 11.0, // Middle zoom for WMS + ); + } }); _saveSettings(); Navigator.pop(context); @@ -592,6 +628,13 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { setState(() { _currentLayer = layer; + // Clamp zoom level if current zoom exceeds new layer's max + if (_isMapReady && _mapController.camera.zoom > layer.maxZoom) { + _mapController.move( + _mapController.camera.center, + layer.maxZoom, + ); + } }); _saveSettings(); @@ -601,9 +644,10 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { }, )), ], - // WMS Overlays section (only for Slovenian/Croatian regions) - if (AppLocalizations.of(context)!.localeName == 'sl' || - AppLocalizations.of(context)!.localeName == 'hr') ...[ + // WMS Overlays section (only for Slovenian/Croatian regions and when WMS base layer is selected) + if ((AppLocalizations.of(context)!.localeName == 'sl' || + AppLocalizations.of(context)!.localeName == 'hr') && + _currentLayer.isWms) ...[ const Divider(), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), @@ -1156,6 +1200,10 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { if (event is MapEventMoveEnd || event is MapEventScrollWheelZoom) { _saveMapPosition(); } + // Trigger rebuild on rotation change to show/hide reset button + if (event is MapEventRotateEnd || event is MapEventRotateStart) { + setState(() {}); + } }, onLongPress: (tapPosition, point) { // Handle measurement mode - set measurement points @@ -1828,6 +1876,13 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin { FloatingActionButton.small( heroTag: 'center_map', onPressed: !_isMapReady ? null : () async { + // First tap: reset rotation if not 0 + // Second tap (or if rotation is 0): jump to current location + if (_getMapRotation() != 0) { + _resetMapRotation(); + return; + } + // Force update GPS location and jump to it final position = await _locationService.getCurrentPosition(); if (position != null && mounted) { diff --git a/lib/screens/messages_tab.dart b/lib/screens/messages_tab.dart index 1e28495..3890d39 100644 --- a/lib/screens/messages_tab.dart +++ b/lib/screens/messages_tab.dart @@ -8,6 +8,7 @@ import '../providers/contacts_provider.dart'; import '../providers/map_provider.dart'; import '../providers/connection_provider.dart'; import '../providers/drawing_provider.dart'; +import '../providers/app_provider.dart'; import '../models/message.dart'; import '../models/contact.dart'; import '../models/sar_marker.dart'; @@ -564,20 +565,25 @@ class _MessagesTabState extends State { // Get all recent messages final allMessages = messagesProvider.getRecentMessages(count: 100); + // Get simple mode setting from AppProvider + final appProvider = context.read(); + final isSimpleMode = appProvider.isSimpleMode; + + List filteredMessages; + // If public channel is selected, show ALL messages if (_destinationType == MessageDestinationPreferences.destinationTypeChannel && _selectedRecipient == null) { - return allMessages; + filteredMessages = allMessages; } - // If a contact or room is selected, filter by recipient - if ((_destinationType == + else if ((_destinationType == MessageDestinationPreferences.destinationTypeContact || _destinationType == MessageDestinationPreferences.destinationTypeRoom) && _selectedRecipient != null) { - return allMessages.where((message) { + filteredMessages = allMessages.where((message) { // Include messages sent TO this recipient if (message.recipientPublicKey != null && message.recipientPublicKey!.length >= 6 && @@ -603,10 +609,19 @@ class _MessagesTabState extends State { return false; }).toList(); + } else { + // Default: show all messages (fallback case) + filteredMessages = allMessages; } - // Default: show all messages (fallback case) - return allMessages; + // In simple mode, filter out system messages (toast logs) + if (isSimpleMode) { + filteredMessages = filteredMessages + .where((message) => !message.isSystemMessage) + .toList(); + } + + return filteredMessages; } @override