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