diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 3236bcb..ea0522a 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -65,9 +65,6 @@ PODS:
- ObjectBox (= 4.4.1)
- package_info_plus (0.4.5):
- Flutter
- - path_provider_foundation (0.0.1):
- - Flutter
- - FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- record_ios (1.2.0):
@@ -105,7 +102,6 @@ DEPENDENCIES:
- nsd_ios (from `.symlinks/plugins/nsd_ios/ios`)
- objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- record_ios (from `.symlinks/plugins/record_ios/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
@@ -153,8 +149,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/objectbox_flutter_libs/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
- path_provider_foundation:
- :path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
record_ios:
@@ -182,14 +176,13 @@ SPEC CHECKSUMS:
flutter_background_service_ios: 00d31bdff7b4bfe06d32375df358abe0329cf87e
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
flutter_compass: b236ab69b61545cce89fd58527f401a7587d5cc1
- flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
+ flutter_local_notifications: 643a3eda1ce1c0599413ca31672536d423dee214
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
nsd_ios: 596ad79109ddd3e52d665f650f36428049e3653e
ObjectBox: 7da4aceb5013d041bfafdbc6d744a26918b09757
objectbox_flutter_libs: 09b1dec1b4cd27bf1a5f9bae7ccaa7e43588bf31
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
- path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
record_ios: 412daca2350b228e698fffcd08f1f94ceb1e3844
SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index f8d79fc..8e57aed 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 = 100;
+ CURRENT_PROJECT_VERSION = 101;
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 = 100;
+ CURRENT_PROJECT_VERSION = 101;
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 = 100;
+ CURRENT_PROJECT_VERSION = 101;
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 = 100;
+ CURRENT_PROJECT_VERSION = 101;
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 = 100;
+ CURRENT_PROJECT_VERSION = 101;
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 = 100;
+ CURRENT_PROJECT_VERSION = 101;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index a371671..81f26d0 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -43,7 +43,7 @@
CFBundleSignature
????
CFBundleVersion
- 100
+ 101
LSRequiresIPhoneOS
ITSAppUsesNonExemptEncryption
diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml
index fed4b04..ea05d7b 100644
--- a/ios/fastlane/report.xml
+++ b/ios/fastlane/report.xml
@@ -5,17 +5,17 @@
-
+
-
+
-
+
diff --git a/lib/models/contact.dart b/lib/models/contact.dart
index 41e4f19..47895a2 100644
--- a/lib/models/contact.dart
+++ b/lib/models/contact.dart
@@ -231,10 +231,10 @@ extension ContactLocalization on Contact {
}
String get routeSummary {
- if (routeIsUnknown || !routeHasPath) {
- return 'Flood/Unknown';
+ if (routeIsUnknown) {
+ return 'Unknown';
}
- if (routeHopCount == 0) {
+ if (!routeHasPath || routeHopCount == 0) {
return 'Direct';
}
return '$routeHopCount hop${routeHopCount == 1 ? '' : 's'} via $routeHashSize-byte hashes';
diff --git a/lib/providers/connection_provider.dart b/lib/providers/connection_provider.dart
index fa50f3c..d251082 100644
--- a/lib/providers/connection_provider.dart
+++ b/lib/providers/connection_provider.dart
@@ -2015,6 +2015,7 @@ class ConnectionProvider with ChangeNotifier {
}
try {
+ _error = null;
await _activeService.resetPath(contactPublicKey);
} catch (e) {
_error = 'Failed to reset path: $e';
@@ -2034,6 +2035,7 @@ class ConnectionProvider with ChangeNotifier {
}
try {
+ _error = null;
final updatedContact = contact.copyWith(
outPathLen: signedEncodedPathLen,
outPath: Uint8List.fromList(paddedPathBytes),
diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart
index 8b28e10..6350ddd 100644
--- a/lib/screens/home_screen.dart
+++ b/lib/screens/home_screen.dart
@@ -715,36 +715,33 @@ class _HomeScreenState extends State with TickerProviderStateMixin {
],
),
),
- // Settings cog - hidden in simple mode
- if (!context.watch().isSimpleMode) ...[
- const SizedBox(width: 8),
- GestureDetector(
- onTap: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => const DeviceConfigScreen(),
+ const SizedBox(width: 8),
+ GestureDetector(
+ onTap: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const DeviceConfigScreen(),
+ ),
+ );
+ },
+ onLongPress: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => PacketLogScreen(
+ bleService: provider.bleService,
),
- );
- },
- onLongPress: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => PacketLogScreen(
- bleService: provider.bleService,
- ),
- ),
- );
- },
- child: Container(
- width: 32,
- height: 32,
- alignment: Alignment.center,
- child: const Icon(Icons.settings, size: 18),
- ),
+ ),
+ );
+ },
+ child: Container(
+ width: 32,
+ height: 32,
+ alignment: Alignment.center,
+ child: const Icon(Icons.settings, size: 18),
),
- ],
+ ),
],
),
),
diff --git a/lib/screens/map_tab.dart b/lib/screens/map_tab.dart
index be80c18..992b747 100644
--- a/lib/screens/map_tab.dart
+++ b/lib/screens/map_tab.dart
@@ -59,7 +59,7 @@ class MapTab extends StatefulWidget {
class _MapTabState extends State with AutomaticKeepAliveClientMixin {
final MapController _mapController = MapController();
- final TileCacheService _tileCache = TileCacheService();
+ late final TileCacheService _tileCache;
// DO NOT create a new LocationTrackingService instance here
// Use the singleton from AppProvider instead via _locationService getter
final MapMarkerService _markerService = MapMarkerService();
@@ -117,6 +117,7 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin {
@override
void initState() {
super.initState();
+ _tileCache = context.read();
// Initialize Slovenian WMS layers with CRS
_slovenianAerialLayer = MapLayer.getSlovenianAerial2024(slovenianCrs);
_dtk25Layer = MapLayer.getDTK25(slovenianCrs);
@@ -446,7 +447,6 @@ class _MapTabState extends State with AutomaticKeepAliveClientMixin {
// Restore the original callback instead of setting to null
_locationService.onPositionUpdate = _originalLocationCallback;
_mapController.dispose();
- _tileCache.dispose();
super.dispose();
}
diff --git a/lib/services/tile_cache_service.dart b/lib/services/tile_cache_service.dart
index 0aecd68..888abba 100644
--- a/lib/services/tile_cache_service.dart
+++ b/lib/services/tile_cache_service.dart
@@ -10,47 +10,66 @@ class TileCacheService {
// Global flag to ensure ObjectBox is only initialized once
static bool _objectBoxInitialized = false;
- static final _initLock = >{};
+ static Future? _objectBoxInitialization;
- late final FMTCStore _store;
+ Future? _initializeFuture;
+ FMTCStore? _store;
bool _isInitialized = false;
bool _isDownloading = false;
Future initialize() async {
if (_isInitialized) return;
+ _initializeFuture ??= _initializeInternal();
+ await _initializeFuture;
+ }
- // Ensure we only initialize ObjectBox once globally
- if (!_objectBoxInitialized) {
- // Use a lock to prevent concurrent initialization attempts
- final initFuture = _initLock.putIfAbsent('objectbox', () async {
- try {
- await FMTCObjectBoxBackend().initialise();
- _objectBoxInitialized = true;
- } catch (e) {
- // Already initialized or error - that's okay
- _objectBoxInitialized = true;
- }
- });
- await initFuture;
- }
-
+ Future _initializeInternal() async {
try {
- _store = FMTCStore(_storeName);
- await _store.manage.create();
- _isInitialized = true;
- } catch (e) {
- // Store might already exist
- _store = FMTCStore(_storeName);
+ await _ensureObjectBoxInitialized();
+
+ final store = FMTCStore(_storeName);
+ try {
+ await store.manage.create();
+ } catch (_) {
+ // The store may already exist from a prior initialization.
+ }
+
+ _store = store;
_isInitialized = true;
+ } catch (_) {
+ _initializeFuture = null;
+ rethrow;
}
}
- FMTCTileProvider getTileProvider(MapLayer layer) {
- if (!_isInitialized) {
+ Future _ensureObjectBoxInitialized() async {
+ if (_objectBoxInitialized) return;
+
+ _objectBoxInitialization ??= () async {
+ try {
+ await FMTCObjectBoxBackend().initialise();
+ } catch (_) {
+ // Treat repeated backend initialization as a no-op.
+ } finally {
+ _objectBoxInitialized = true;
+ }
+ }();
+
+ await _objectBoxInitialization;
+ }
+
+ FMTCStore _requireStore() {
+ final store = _store;
+ if (!_isInitialized || store == null) {
throw StateError(
'TileCacheService not initialized. Call initialize() first.',
);
}
+ return store;
+ }
+
+ TileProvider getTileProvider(MapLayer layer) {
+ _requireStore();
return FMTCTileProvider(
stores: {_storeName: BrowseStoreStrategy.readUpdateCreate},
loadingStrategy: BrowseLoadingStrategy.cacheFirst,
@@ -60,12 +79,8 @@ class TileCacheService {
/// Get tile provider for WMS layers with caching support
/// WMS layers require special handling because they use WMSTileLayerOptions
- FMTCTileProvider getTileProviderForWms(MapLayer layer) {
- if (!_isInitialized) {
- throw StateError(
- 'TileCacheService not initialized. Call initialize() first.',
- );
- }
+ TileProvider getTileProviderForWms(MapLayer layer) {
+ _requireStore();
if (!layer.isWms) {
throw ArgumentError('Layer must be a WMS layer');
}
@@ -97,6 +112,7 @@ class TileCacheService {
}
_isDownloading = true;
+ final store = _requireStore();
try {
final region = RectangleRegion(bounds);
@@ -107,7 +123,7 @@ class TileCacheService {
options: TileLayer(urlTemplate: layer.urlTemplate),
);
- final download = _store.download.startForeground(region: downloadable);
+ final download = store.download.startForeground(region: downloadable);
await for (final progress in download.downloadProgress) {
if (onProgress != null && progress.maxTilesCount > 0) {
@@ -127,24 +143,25 @@ class TileCacheService {
Future cancelDownload() async {
if (!_isInitialized) return;
- await _store.download.cancel();
+ await _requireStore().download.cancel();
}
Future clearCache() async {
if (!_isInitialized) return;
- await _store.manage.delete();
- await _store.manage.create();
+ final store = _requireStore();
+ await store.manage.delete();
+ await store.manage.create();
}
Future getCachedTileCount() async {
if (!_isInitialized) return 0;
- final stats = await _store.stats.length;
+ final stats = await _requireStore().stats.length;
return stats;
}
Future getCacheSizeMB() async {
if (!_isInitialized) return 0.0;
- final stats = await _store.stats.size;
+ final stats = await _requireStore().stats.size;
return stats / (1024 * 1024);
}
@@ -162,8 +179,9 @@ class TileCacheService {
Future