Enable profiles switch UI

This commit is contained in:
Janez T
2026-03-16 11:25:51 +01:00
parent fedd7b9a2b
commit 420690d683
30 changed files with 2858 additions and 269 deletions

View File

@@ -18,6 +18,7 @@ import '../services/messaging_route_preferences.dart';
import '../services/nearest_router_selector.dart';
import '../services/packet_capture_storage_service.dart';
import '../services/path_history_service.dart';
import '../services/profiles_feature_service.dart';
import '../services/route_hash_preferences.dart';
import '../services/notification_service.dart';
import '../models/contact.dart';
@@ -253,6 +254,10 @@ class AppProvider with ChangeNotifier {
_isInitialized = true;
}
String _scopedKey(String baseKey) {
return ProfileStorageScope.scopedKey(baseKey);
}
void _startPacketCapturePersistence() {
_packetCaptureFlushTimer?.cancel();
_packetCaptureFlushTimer = Timer.periodic(const Duration(seconds: 2), (_) {
@@ -516,7 +521,7 @@ class AppProvider with ChangeNotifier {
Future<void> _loadMapEnabled() async {
try {
final prefs = await SharedPreferences.getInstance();
_isMapEnabled = prefs.getBool('map_enabled') ?? true;
_isMapEnabled = prefs.getBool(_scopedKey('map_enabled')) ?? true;
notifyListeners();
} catch (e) {
debugPrint('Error loading map enabled setting: $e');
@@ -528,7 +533,7 @@ class AppProvider with ChangeNotifier {
try {
_isMapEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('map_enabled', enabled);
await prefs.setBool(_scopedKey('map_enabled'), enabled);
notifyListeners();
} catch (e) {
debugPrint('Error saving map enabled setting: $e');
@@ -539,7 +544,8 @@ class AppProvider with ChangeNotifier {
Future<void> _loadContactsEnabled() async {
try {
final prefs = await SharedPreferences.getInstance();
_isContactsEnabled = prefs.getBool('contacts_enabled') ?? true;
_isContactsEnabled =
prefs.getBool(_scopedKey('contacts_enabled')) ?? true;
notifyListeners();
} catch (e) {
debugPrint('Error loading contacts enabled setting: $e');
@@ -551,7 +557,7 @@ class AppProvider with ChangeNotifier {
try {
_isContactsEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('contacts_enabled', enabled);
await prefs.setBool(_scopedKey('contacts_enabled'), enabled);
notifyListeners();
} catch (e) {
debugPrint('Error saving contacts enabled setting: $e');
@@ -562,7 +568,7 @@ class AppProvider with ChangeNotifier {
Future<void> _loadSensorsEnabled() async {
try {
final prefs = await SharedPreferences.getInstance();
_isSensorsEnabled = prefs.getBool('sensors_enabled') ?? true;
_isSensorsEnabled = prefs.getBool(_scopedKey('sensors_enabled')) ?? true;
notifyListeners();
} catch (e) {
debugPrint('Error loading sensors enabled setting: $e');
@@ -574,7 +580,7 @@ class AppProvider with ChangeNotifier {
try {
_isSensorsEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('sensors_enabled', enabled);
await prefs.setBool(_scopedKey('sensors_enabled'), enabled);
notifyListeners();
} catch (e) {
debugPrint('Error saving sensors enabled setting: $e');
@@ -586,7 +592,7 @@ class AppProvider with ChangeNotifier {
try {
final prefs = await SharedPreferences.getInstance();
_isVoiceSilenceTrimmingEnabled =
prefs.getBool('voice_silence_trimming_enabled') ?? true;
prefs.getBool(_scopedKey('voice_silence_trimming_enabled')) ?? true;
notifyListeners();
} catch (e) {
debugPrint('Error loading voice silence trimming setting: $e');
@@ -598,7 +604,10 @@ class AppProvider with ChangeNotifier {
try {
_isVoiceSilenceTrimmingEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('voice_silence_trimming_enabled', enabled);
await prefs.setBool(
_scopedKey('voice_silence_trimming_enabled'),
enabled,
);
notifyListeners();
} catch (e) {
debugPrint('Error saving voice silence trimming setting: $e');
@@ -610,7 +619,7 @@ class AppProvider with ChangeNotifier {
try {
final prefs = await SharedPreferences.getInstance();
_isVoiceBandPassFilterEnabled =
prefs.getBool('voice_band_pass_filter_enabled') ?? true;
prefs.getBool(_scopedKey('voice_band_pass_filter_enabled')) ?? true;
notifyListeners();
} catch (e) {
debugPrint('Error loading voice band-pass filter setting: $e');
@@ -622,7 +631,10 @@ class AppProvider with ChangeNotifier {
try {
_isVoiceBandPassFilterEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('voice_band_pass_filter_enabled', enabled);
await prefs.setBool(
_scopedKey('voice_band_pass_filter_enabled'),
enabled,
);
notifyListeners();
} catch (e) {
debugPrint('Error saving voice band-pass filter setting: $e');
@@ -634,7 +646,7 @@ class AppProvider with ChangeNotifier {
try {
final prefs = await SharedPreferences.getInstance();
_isVoiceCompressorEnabled =
prefs.getBool('voice_compressor_enabled') ?? true;
prefs.getBool(_scopedKey('voice_compressor_enabled')) ?? true;
notifyListeners();
} catch (e) {
debugPrint('Error loading voice compressor setting: $e');
@@ -646,7 +658,7 @@ class AppProvider with ChangeNotifier {
try {
_isVoiceCompressorEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('voice_compressor_enabled', enabled);
await prefs.setBool(_scopedKey('voice_compressor_enabled'), enabled);
notifyListeners();
} catch (e) {
debugPrint('Error saving voice compressor setting: $e');
@@ -657,7 +669,8 @@ class AppProvider with ChangeNotifier {
Future<void> _loadVoiceLimiterEnabled() async {
try {
final prefs = await SharedPreferences.getInstance();
_isVoiceLimiterEnabled = prefs.getBool('voice_limiter_enabled') ?? true;
_isVoiceLimiterEnabled =
prefs.getBool(_scopedKey('voice_limiter_enabled')) ?? true;
notifyListeners();
} catch (e) {
debugPrint('Error loading voice limiter setting: $e');
@@ -669,7 +682,7 @@ class AppProvider with ChangeNotifier {
try {
_isVoiceLimiterEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('voice_limiter_enabled', enabled);
await prefs.setBool(_scopedKey('voice_limiter_enabled'), enabled);
notifyListeners();
} catch (e) {
debugPrint('Error saving voice limiter setting: $e');
@@ -680,7 +693,7 @@ class AppProvider with ChangeNotifier {
try {
final prefs = await SharedPreferences.getInstance();
_isVoiceAutoGainEnabled =
prefs.getBool('voice_auto_gain_enabled') ?? false;
prefs.getBool(_scopedKey('voice_auto_gain_enabled')) ?? false;
notifyListeners();
} catch (e) {
debugPrint('Error loading voice auto gain setting: $e');
@@ -691,7 +704,7 @@ class AppProvider with ChangeNotifier {
try {
_isVoiceAutoGainEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('voice_auto_gain_enabled', enabled);
await prefs.setBool(_scopedKey('voice_auto_gain_enabled'), enabled);
notifyListeners();
} catch (e) {
debugPrint('Error saving voice auto gain setting: $e');
@@ -702,7 +715,7 @@ class AppProvider with ChangeNotifier {
try {
final prefs = await SharedPreferences.getInstance();
_isVoiceEchoCancellationEnabled =
prefs.getBool('voice_echo_cancellation_enabled') ?? false;
prefs.getBool(_scopedKey('voice_echo_cancellation_enabled')) ?? false;
notifyListeners();
} catch (e) {
debugPrint('Error loading voice echo cancellation setting: $e');
@@ -713,7 +726,10 @@ class AppProvider with ChangeNotifier {
try {
_isVoiceEchoCancellationEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('voice_echo_cancellation_enabled', enabled);
await prefs.setBool(
_scopedKey('voice_echo_cancellation_enabled'),
enabled,
);
notifyListeners();
} catch (e) {
debugPrint('Error saving voice echo cancellation setting: $e');
@@ -724,7 +740,7 @@ class AppProvider with ChangeNotifier {
try {
final prefs = await SharedPreferences.getInstance();
_isVoiceNoiseSuppressionEnabled =
prefs.getBool('voice_noise_suppression_enabled') ?? false;
prefs.getBool(_scopedKey('voice_noise_suppression_enabled')) ?? false;
notifyListeners();
} catch (e) {
debugPrint('Error loading voice noise suppression setting: $e');
@@ -735,7 +751,10 @@ class AppProvider with ChangeNotifier {
try {
_isVoiceNoiseSuppressionEnabled = enabled;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('voice_noise_suppression_enabled', enabled);
await prefs.setBool(
_scopedKey('voice_noise_suppression_enabled'),
enabled,
);
notifyListeners();
} catch (e) {
debugPrint('Error saving voice noise suppression setting: $e');
@@ -745,10 +764,11 @@ class AppProvider with ChangeNotifier {
Future<void> _loadMessageFontScale() async {
try {
final prefs = await SharedPreferences.getInstance();
_messageFontScale = (prefs.getDouble('message_font_scale') ?? 1.0).clamp(
0.85,
1.4,
);
_messageFontScale =
(prefs.getDouble(_scopedKey('message_font_scale')) ?? 1.0).clamp(
0.85,
1.4,
);
notifyListeners();
} catch (e) {
debugPrint('Error loading message font scale setting: $e');
@@ -759,7 +779,10 @@ class AppProvider with ChangeNotifier {
try {
_messageFontScale = scale.clamp(0.85, 1.4);
final prefs = await SharedPreferences.getInstance();
await prefs.setDouble('message_font_scale', _messageFontScale);
await prefs.setDouble(
_scopedKey('message_font_scale'),
_messageFontScale,
);
notifyListeners();
} catch (e) {
debugPrint('Error saving message font scale setting: $e');
@@ -2600,6 +2623,24 @@ class AppProvider with ChangeNotifier {
locationTrackingService.setFastLocationActiveUse(isActive);
}
Future<void> reloadProfileScopedSettings() async {
await Future.wait([
_loadMapEnabled(),
_loadContactsEnabled(),
_loadSensorsEnabled(),
_loadVoiceSilenceTrimmingEnabled(),
_loadVoiceBandPassFilterEnabled(),
_loadVoiceCompressorEnabled(),
_loadVoiceLimiterEnabled(),
_loadVoiceAutoGainEnabled(),
_loadVoiceEchoCancellationEnabled(),
_loadVoiceNoiseSuppressionEnabled(),
_loadMessageFontScale(),
_loadMessagingRouteSettings(),
locationTrackingService.loadSettings(),
]);
}
Future<void> _sendFastLocationUpdate(
dynamic position, {
required String reason,

View File

@@ -1325,6 +1325,31 @@ class ConnectionProvider with ChangeNotifier {
}
}
Future<void> setChannelSlot({
required int channelIdx,
required String channelName,
required Uint8List secret,
}) async {
if (!_activeService.isConnected) {
_error = 'Not connected to device';
notifyListeners();
return;
}
try {
await _activeService.setChannel(
channelIdx: channelIdx,
channelName: channelName,
secret: secret,
);
await _activeService.getChannel(channelIdx);
} catch (e) {
_error = 'Failed to set channel: $e';
notifyListeners();
rethrow;
}
}
/// Generate secret for hash channel using SHA256
/// Same algorithm as Channel model for consistency
/// Python equivalent: hashlib.sha256(channel_name.encode()).digest()[0:16]

View File

@@ -134,6 +134,7 @@ class ContactsProvider with ChangeNotifier {
bool _persistRequested = false;
bool _isPersistingPendingAdverts = false;
bool _persistPendingAdvertsRequested = false;
String? _storageNamespace;
// Add default public channel on initialization
ContactsProvider() {
@@ -141,19 +142,74 @@ class ContactsProvider with ChangeNotifier {
}
bool get isInitialized => _isInitialized;
String? get storageNamespace => _storageNamespace;
/// Initialize and load persisted contacts at app startup
/// This loads contacts without filtering, allowing offline viewing
Future<void> initializeEarly() async {
if (_isInitialized) return;
await _loadFromStorage();
}
Future<void> reloadFromStorage({
String? namespace,
Uint8List? devicePublicKey,
}) async {
_storageNamespace = namespace;
await _loadFromStorage(force: true, devicePublicKey: devicePublicKey);
}
Future<void> persistNow() async {
await _storageService.saveContacts(
_contactsForStorage(),
namespace: _storageNamespace,
);
await _storageService.saveContactGroups(
_savedContactGroups,
namespace: _storageNamespace,
);
await _storageService.savePendingAdverts(
_pendingAdverts.values.map(_pendingAdvertToJson).toList(),
namespace: _storageNamespace,
);
}
Future<void> cloneCurrentStorageTo(String? namespace) async {
await _storageService.saveContacts(
_contactsForStorage(),
namespace: namespace,
);
await _storageService.saveContactGroups(
_savedContactGroups,
namespace: namespace,
);
await _storageService.savePendingAdverts(
_pendingAdverts.values.map(_pendingAdvertToJson).toList(),
namespace: namespace,
);
}
Future<void> _loadFromStorage({
bool force = false,
Uint8List? devicePublicKey,
}) async {
if (_isInitialized && !force) return;
try {
_resetInMemoryState();
debugPrint(
'📦 [ContactsProvider] Early loading persisted contacts (no filtering)...',
);
final storedContacts = await _storageService.loadContacts();
final storedGroups = await _storageService.loadContactGroups();
final storedPendingAdverts = await _storageService.loadPendingAdverts();
final storedContacts = await _storageService.loadContacts(
namespace: _storageNamespace,
);
final storedGroups = await _storageService.loadContactGroups(
namespace: _storageNamespace,
);
final storedPendingAdverts = await _storageService.loadPendingAdverts(
namespace: _storageNamespace,
);
// Add stored contacts (excluding any with all-zeros public key)
const publicChannelKey =
@@ -170,7 +226,10 @@ class ContactsProvider with ChangeNotifier {
_savedContactGroups
..clear()
..addAll(storedGroups);
_restorePendingAdverts(storedPendingAdverts);
_restorePendingAdverts(
storedPendingAdverts,
devicePublicKey: devicePublicKey,
);
debugPrint(
'✅ [ContactsProvider] Early loaded ${storedContacts.length} persisted contacts and ${storedGroups.length} groups',
);
@@ -197,45 +256,9 @@ class ContactsProvider with ChangeNotifier {
return;
}
try {
debugPrint('📦 [ContactsProvider] Loading persisted contacts...');
final storedContacts = await _storageService.loadContacts(
excludePublicKey: devicePublicKey,
);
final storedGroups = await _storageService.loadContactGroups();
final storedPendingAdverts = await _storageService.loadPendingAdverts();
// Add stored contacts (excluding any with all-zeros public key)
const publicChannelKey =
'0000000000000000000000000000000000000000000000000000000000000000';
for (final contact in storedContacts) {
// Skip any contacts with all-zeros public key (shouldn't happen, but safety check)
if (contact.publicKeyHex == publicChannelKey) {
continue;
}
_contacts[contact.publicKeyHex] = contact;
}
_isInitialized = true;
_savedContactGroups
..clear()
..addAll(storedGroups);
_restorePendingAdverts(
storedPendingAdverts,
devicePublicKey: devicePublicKey,
);
debugPrint(
'✅ [ContactsProvider] Loaded ${storedContacts.length} persisted contacts and ${storedGroups.length} groups',
);
// Ensure public channel exists after loading
_ensurePublicChannelExists();
notifyListeners();
} catch (e) {
debugPrint('❌ [ContactsProvider] Error initializing: $e');
_isInitialized = true; // Mark as initialized even on error
_ensurePublicChannelExists();
await _loadFromStorage(force: true, devicePublicKey: devicePublicKey);
if (devicePublicKey != null) {
_removeSelfContact(devicePublicKey);
}
}
@@ -287,7 +310,10 @@ class ContactsProvider with ChangeNotifier {
try {
while (_persistRequested) {
_persistRequested = false;
await _storageService.saveContacts(_contactsForStorage());
await _storageService.saveContacts(
_contactsForStorage(),
namespace: _storageNamespace,
);
}
} catch (e) {
debugPrint('❌ [ContactsProvider] Error persisting contacts: $e');
@@ -305,6 +331,7 @@ class ContactsProvider with ChangeNotifier {
_persistPendingAdvertsRequested = false;
await _storageService.savePendingAdverts(
_pendingAdverts.values.map(_pendingAdvertToJson).toList(),
namespace: _storageNamespace,
);
}
} catch (e) {
@@ -436,7 +463,10 @@ class ContactsProvider with ChangeNotifier {
Future<void> _persistSavedGroups() async {
try {
await _storageService.saveContactGroups(_savedContactGroups);
await _storageService.saveContactGroups(
_savedContactGroups,
namespace: _storageNamespace,
);
} catch (e) {
debugPrint('❌ [ContactsProvider] Error persisting contact groups: $e');
}
@@ -1442,11 +1472,7 @@ class ContactsProvider with ChangeNotifier {
/// Get storage statistics
Future<Map<String, dynamic>> getStorageStats() async {
return await _storageService.getStorageStats();
}
Future<void> persistNow() async {
await _storageService.saveContacts(_contactsForStorage());
return await _storageService.getStorageStats(namespace: _storageNamespace);
}
Future<void> clearPendingAdverts() async {
@@ -1454,7 +1480,7 @@ class ContactsProvider with ChangeNotifier {
return;
}
_pendingAdverts.clear();
await _storageService.clearPendingAdverts();
await _storageService.clearPendingAdverts(namespace: _storageNamespace);
notifyListeners();
}
@@ -1517,6 +1543,12 @@ class ContactsProvider with ChangeNotifier {
}
}
void _resetInMemoryState() {
_contacts.clear();
_savedContactGroups.clear();
_pendingAdverts.clear();
}
Map<String, dynamic> _pendingAdvertToJson(PendingAdvert advert) {
return {
'publicKey': base64Encode(advert.publicKey),

View File

@@ -5,6 +5,7 @@ import 'package:latlong2/latlong.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/map_drawing.dart';
import '../models/map_coordinate_space.dart';
import '../services/profiles_feature_service.dart';
import '../utils/drawing_message_parser.dart';
/// Drawing mode state
@@ -85,6 +86,12 @@ class DrawingProvider with ChangeNotifier {
_isInitialized = true;
}
Future<void> reloadProfileScopedState() async {
await _loadPreferences();
await _loadDrawings();
notifyListeners();
}
void setMapContext({
required MapCoordinateSpace coordinateSpace,
String? mapId,
@@ -140,15 +147,19 @@ class DrawingProvider with ChangeNotifier {
Future<void> _loadPreferences() async {
final prefs = await SharedPreferences.getInstance();
_showReceivedDrawings = prefs.getBool(_showReceivedDrawingsKey) ?? true;
_showSarMarkers = prefs.getBool(_showSarMarkersKey) ?? true;
_showReceivedDrawings =
prefs.getBool(_scopedKey(_showReceivedDrawingsKey)) ?? true;
_showSarMarkers = prefs.getBool(_scopedKey(_showSarMarkersKey)) ?? true;
notifyListeners();
}
Future<void> _savePreferences() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_showReceivedDrawingsKey, _showReceivedDrawings);
await prefs.setBool(_showSarMarkersKey, _showSarMarkers);
await prefs.setBool(
_scopedKey(_showReceivedDrawingsKey),
_showReceivedDrawings,
);
await prefs.setBool(_scopedKey(_showSarMarkersKey), _showSarMarkers);
}
/// Start drawing a line
@@ -365,7 +376,7 @@ class DrawingProvider with ChangeNotifier {
final prefs = await SharedPreferences.getInstance();
final jsonList = _drawings.map((d) => d.toJson()).toList();
final jsonString = jsonEncode(jsonList);
await prefs.setString(_storageKey, jsonString);
await prefs.setString(_scopedKey(_storageKey), jsonString);
} catch (e) {
debugPrint('Error saving drawings: $e');
}
@@ -375,8 +386,11 @@ class DrawingProvider with ChangeNotifier {
Future<void> _loadDrawings() async {
try {
final prefs = await SharedPreferences.getInstance();
final jsonString = prefs.getString(_storageKey);
if (jsonString == null) return;
final jsonString = prefs.getString(_scopedKey(_storageKey));
if (jsonString == null || jsonString.isEmpty) {
_drawings.clear();
return;
}
final jsonList = jsonDecode(jsonString) as List<dynamic>;
_drawings.clear();
@@ -475,6 +489,24 @@ class DrawingProvider with ChangeNotifier {
}
}
List<Map<String, dynamic>> exportDrawingsJson() {
return _drawings.map((drawing) => drawing.toJson()).toList();
}
Future<void> replaceDrawingsFromJson(
List<Map<String, dynamic>> jsonList,
) async {
_drawings
..clear()
..addAll(jsonList.map(MapDrawing.fromJson).whereType<MapDrawing>());
await _saveDrawings();
notifyListeners();
}
String _scopedKey(String baseKey) {
return ProfileStorageScope.scopedKey(baseKey);
}
/// Get all unshared drawings (local drawings not yet sent)
List<MapDrawing> getUnsharedDrawings() {
return drawings.where((d) => !d.isShared && !d.isReceived).toList();

View File

@@ -16,6 +16,7 @@ import '../models/location_trail.dart';
import '../models/map_coordinate_space.dart';
import '../models/map_drawing.dart';
import '../models/sar_marker.dart';
import '../services/profiles_feature_service.dart';
import '../utils/custom_map_id.dart';
class MapProvider with ChangeNotifier {
@@ -105,6 +106,10 @@ class MapProvider with ChangeNotifier {
LatLngBounds? get customMapBounds => _customMapConfig?.bounds;
Future<void> reloadProfileScopedState() async {
await _loadInitialState();
}
bool matchesActiveCustomMap(String? mapId) {
return hasCustomMap &&
normalizeCustomMapId(_customMapConfig!.mapId) ==
@@ -522,27 +527,29 @@ class MapProvider with ChangeNotifier {
Future<void> loadOverlayState() async {
final prefs = await SharedPreferences.getInstance();
_showCadastralOverlay =
prefs.getBool('map_show_cadastral_overlay') ?? false;
prefs.getBool(_scopedKey('map_show_cadastral_overlay')) ?? false;
_showForestRoadsOverlay =
prefs.getBool('map_show_forest_roads_overlay') ?? false;
prefs.getBool(_scopedKey('map_show_forest_roads_overlay')) ?? false;
_showHikingTrailsOverlay =
prefs.getBool('map_show_hiking_trails_overlay') ?? false;
prefs.getBool(_scopedKey('map_show_hiking_trails_overlay')) ?? false;
_showMainRoadsOverlay =
prefs.getBool('map_show_main_roads_overlay') ?? false;
prefs.getBool(_scopedKey('map_show_main_roads_overlay')) ?? false;
_showHouseNumbersOverlay =
prefs.getBool('map_show_house_numbers_overlay') ?? false;
prefs.getBool(_scopedKey('map_show_house_numbers_overlay')) ?? false;
_showFireHazardZonesOverlay =
prefs.getBool('map_show_fire_hazard_zones_overlay') ?? false;
prefs.getBool(_scopedKey('map_show_fire_hazard_zones_overlay')) ??
false;
_showHistoricalFiresOverlay =
prefs.getBool('map_show_historical_fires_overlay') ?? false;
prefs.getBool(_scopedKey('map_show_historical_fires_overlay')) ?? false;
_showFirebreaksOverlay =
prefs.getBool('map_show_firebreaks_overlay') ?? false;
prefs.getBool(_scopedKey('map_show_firebreaks_overlay')) ?? false;
_showKrasFireZonesOverlay =
prefs.getBool('map_show_kras_fire_zones_overlay') ?? false;
prefs.getBool(_scopedKey('map_show_kras_fire_zones_overlay')) ?? false;
_showPlaceNamesOverlay =
prefs.getBool('map_show_place_names_overlay') ?? false;
prefs.getBool(_scopedKey('map_show_place_names_overlay')) ?? false;
_showMunicipalityBordersOverlay =
prefs.getBool('map_show_municipality_borders_overlay') ?? false;
prefs.getBool(_scopedKey('map_show_municipality_borders_overlay')) ??
false;
notifyListeners();
}
@@ -557,36 +564,48 @@ class MapProvider with ChangeNotifier {
Future<void> _saveOverlayState() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('map_show_cadastral_overlay', _showCadastralOverlay);
await prefs.setBool(
'map_show_forest_roads_overlay',
_scopedKey('map_show_cadastral_overlay'),
_showCadastralOverlay,
);
await prefs.setBool(
_scopedKey('map_show_forest_roads_overlay'),
_showForestRoadsOverlay,
);
await prefs.setBool(
'map_show_hiking_trails_overlay',
_scopedKey('map_show_hiking_trails_overlay'),
_showHikingTrailsOverlay,
);
await prefs.setBool('map_show_main_roads_overlay', _showMainRoadsOverlay);
await prefs.setBool(
'map_show_house_numbers_overlay',
_scopedKey('map_show_main_roads_overlay'),
_showMainRoadsOverlay,
);
await prefs.setBool(
_scopedKey('map_show_house_numbers_overlay'),
_showHouseNumbersOverlay,
);
await prefs.setBool(
'map_show_fire_hazard_zones_overlay',
_scopedKey('map_show_fire_hazard_zones_overlay'),
_showFireHazardZonesOverlay,
);
await prefs.setBool(
'map_show_historical_fires_overlay',
_scopedKey('map_show_historical_fires_overlay'),
_showHistoricalFiresOverlay,
);
await prefs.setBool('map_show_firebreaks_overlay', _showFirebreaksOverlay);
await prefs.setBool(
'map_show_kras_fire_zones_overlay',
_scopedKey('map_show_firebreaks_overlay'),
_showFirebreaksOverlay,
);
await prefs.setBool(
_scopedKey('map_show_kras_fire_zones_overlay'),
_showKrasFireZonesOverlay,
);
await prefs.setBool('map_show_place_names_overlay', _showPlaceNamesOverlay);
await prefs.setBool(
'map_show_municipality_borders_overlay',
_scopedKey('map_show_place_names_overlay'),
_showPlaceNamesOverlay,
);
await prefs.setBool(
_scopedKey('map_show_municipality_borders_overlay'),
_showMunicipalityBordersOverlay,
);
}
@@ -600,13 +619,16 @@ class MapProvider with ChangeNotifier {
Future<void> loadTrailSettings() async {
final prefs = await SharedPreferences.getInstance();
_showAllContactTrails =
prefs.getBool('map_show_all_contact_trails') ?? true;
prefs.getBool(_scopedKey('map_show_all_contact_trails')) ?? true;
notifyListeners();
}
Future<void> _saveTrailSettings() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('map_show_all_contact_trails', _showAllContactTrails);
await prefs.setBool(
_scopedKey('map_show_all_contact_trails'),
_showAllContactTrails,
);
}
Future<void> setHideRepeatersOnMap(bool hide) async {
@@ -614,12 +636,13 @@ class MapProvider with ChangeNotifier {
_hideRepeatersOnMap = hide;
notifyListeners();
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('map_hide_repeaters', _hideRepeatersOnMap);
await prefs.setBool(_scopedKey('map_hide_repeaters'), _hideRepeatersOnMap);
}
Future<void> loadRepeaterVisibilitySettings() async {
final prefs = await SharedPreferences.getInstance();
_hideRepeatersOnMap = prefs.getBool('map_hide_repeaters') ?? false;
_hideRepeatersOnMap =
prefs.getBool(_scopedKey('map_hide_repeaters')) ?? false;
notifyListeners();
}
@@ -661,7 +684,7 @@ class MapProvider with ChangeNotifier {
Future<void> _loadCustomMapState() async {
final prefs = await SharedPreferences.getInstance();
final configJson = prefs.getString(_customMapConfigKey);
final configJson = prefs.getString(_scopedKey(_customMapConfigKey));
if (configJson != null && configJson.isNotEmpty) {
final decoded = jsonDecode(configJson);
if (decoded is Map<String, dynamic>) {
@@ -672,23 +695,26 @@ class MapProvider with ChangeNotifier {
_customMapConfig = null;
}
}
} else {
_customMapConfig = null;
}
_isUsingCustomMap =
(prefs.getBool(_customMapModeKey) ?? false) && _customMapConfig != null;
(prefs.getBool(_scopedKey(_customMapModeKey)) ?? false) &&
_customMapConfig != null;
notifyListeners();
}
Future<void> _saveCustomMapState() async {
final prefs = await SharedPreferences.getInstance();
if (_customMapConfig == null) {
await prefs.remove(_customMapConfigKey);
await prefs.remove(_scopedKey(_customMapConfigKey));
} else {
await prefs.setString(
_customMapConfigKey,
_scopedKey(_customMapConfigKey),
jsonEncode(_customMapConfig!.toJson()),
);
}
await prefs.setBool(_customMapModeKey, _isUsingCustomMap);
await prefs.setBool(_scopedKey(_customMapModeKey), _isUsingCustomMap);
}
Future<(int, int)> _decodeImageSize(Uint8List bytes) async {
@@ -711,4 +737,67 @@ class MapProvider with ChangeNotifier {
await file.delete();
}
}
Map<String, dynamic> exportWorkspaceJson() {
return {
'currentTrail': _currentTrail?.toJson(),
'trailHistory': _trailHistory.map((trail) => trail.toJson()).toList(),
'importedTrail': _importedTrail?.toJson(),
'isTrailVisible': _isTrailVisible,
'showCadastralOverlay': _showCadastralOverlay,
'showForestRoadsOverlay': _showForestRoadsOverlay,
'showHikingTrailsOverlay': _showHikingTrailsOverlay,
'showMainRoadsOverlay': _showMainRoadsOverlay,
'showHouseNumbersOverlay': _showHouseNumbersOverlay,
'showFireHazardZonesOverlay': _showFireHazardZonesOverlay,
'showHistoricalFiresOverlay': _showHistoricalFiresOverlay,
'showFirebreaksOverlay': _showFirebreaksOverlay,
'showKrasFireZonesOverlay': _showKrasFireZonesOverlay,
'showPlaceNamesOverlay': _showPlaceNamesOverlay,
'showMunicipalityBordersOverlay': _showMunicipalityBordersOverlay,
'showAllContactTrails': _showAllContactTrails,
'hideRepeatersOnMap': _hideRepeatersOnMap,
};
}
void applyWorkspaceJson(Map<String, dynamic> json) {
_currentTrail = json['currentTrail'] is Map<String, dynamic>
? LocationTrail.fromJson(json['currentTrail'] as Map<String, dynamic>)
: null;
_trailHistory
..clear()
..addAll(
(json['trailHistory'] as List<dynamic>? ?? const [])
.whereType<Map<String, dynamic>>()
.map(LocationTrail.fromJson),
);
_importedTrail = json['importedTrail'] is Map<String, dynamic>
? LocationTrail.fromJson(json['importedTrail'] as Map<String, dynamic>)
: null;
_isTrailVisible = json['isTrailVisible'] as bool? ?? true;
_showCadastralOverlay = json['showCadastralOverlay'] as bool? ?? false;
_showForestRoadsOverlay = json['showForestRoadsOverlay'] as bool? ?? false;
_showHikingTrailsOverlay =
json['showHikingTrailsOverlay'] as bool? ?? false;
_showMainRoadsOverlay = json['showMainRoadsOverlay'] as bool? ?? false;
_showHouseNumbersOverlay =
json['showHouseNumbersOverlay'] as bool? ?? false;
_showFireHazardZonesOverlay =
json['showFireHazardZonesOverlay'] as bool? ?? false;
_showHistoricalFiresOverlay =
json['showHistoricalFiresOverlay'] as bool? ?? false;
_showFirebreaksOverlay = json['showFirebreaksOverlay'] as bool? ?? false;
_showKrasFireZonesOverlay =
json['showKrasFireZonesOverlay'] as bool? ?? false;
_showPlaceNamesOverlay = json['showPlaceNamesOverlay'] as bool? ?? false;
_showMunicipalityBordersOverlay =
json['showMunicipalityBordersOverlay'] as bool? ?? false;
_showAllContactTrails = json['showAllContactTrails'] as bool? ?? true;
_hideRepeatersOnMap = json['hideRepeatersOnMap'] as bool? ?? false;
notifyListeners();
}
String _scopedKey(String baseKey) {
return ProfileStorageScope.scopedKey(baseKey);
}
}

View File

@@ -37,6 +37,7 @@ class MessagesProvider with ChangeNotifier {
final Map<String, MessageReceptionDetails> _messageReceptionDetails = {};
final Map<String, MessageTransferDetails> _messageTransferDetails = {};
final Map<String, MessageRouteMetadata> _messageRouteMetadata = {};
String? _storageNamespace;
// Track pending sent messages by expected ACK/TAG
final Map<int, Message> _pendingSentMessages = {};
@@ -149,6 +150,7 @@ class MessagesProvider with ChangeNotifier {
sarMarkers.where((m) => m.type == SarMarkerType.object).toList();
bool get isInitialized => _isInitialized;
String? get storageNamespace => _storageNamespace;
String? get targetMessageId => _targetMessageId;
@@ -311,19 +313,64 @@ class MessagesProvider with ChangeNotifier {
Future<void> initialize() async {
if (_isInitialized) return;
await _loadFromStorage();
}
Future<void> reloadFromStorage({String? namespace}) async {
_storageNamespace = namespace;
await _loadFromStorage(force: true);
}
Future<void> persistNow() async {
await _storageService.saveMessages(
_messages,
messageContactLocations: _messageContactLocations,
messageReceptionDetails: _messageReceptionDetails,
messageTransferDetails: _messageTransferDetails,
messageRouteMetadata: _messageRouteMetadata,
namespace: _storageNamespace,
);
await _storageService.saveRemovedSarMarkerIds(
_removedSarMarkerIds,
namespace: _storageNamespace,
);
}
Future<void> cloneCurrentStorageTo(String? namespace) async {
await _storageService.saveMessages(
_messages,
messageContactLocations: _messageContactLocations,
messageReceptionDetails: _messageReceptionDetails,
messageTransferDetails: _messageTransferDetails,
messageRouteMetadata: _messageRouteMetadata,
namespace: namespace,
);
await _storageService.saveRemovedSarMarkerIds(
_removedSarMarkerIds,
namespace: namespace,
);
}
Future<void> _loadFromStorage({bool force = false}) async {
if (_isInitialized && !force) return;
try {
_cancelAllTimers();
_resetInMemoryState();
debugPrint('📦 [MessagesProvider] Loading persisted messages...');
final storedMessages = await _storageService.loadMessages();
final storedMessages = await _storageService.loadMessages(
namespace: _storageNamespace,
);
final storedContactLocations = await _storageService
.loadMessageContactLocations();
.loadMessageContactLocations(namespace: _storageNamespace);
final storedReceptionDetails = await _storageService
.loadMessageReceptionDetails();
.loadMessageReceptionDetails(namespace: _storageNamespace);
final storedTransferDetails = await _storageService
.loadMessageTransferDetails();
.loadMessageTransferDetails(namespace: _storageNamespace);
final storedRouteMetadata = await _storageService
.loadMessageRouteMetadata();
.loadMessageRouteMetadata(namespace: _storageNamespace);
final storedRemovedSarMarkerIds = await _storageService
.loadRemovedSarMarkerIds();
.loadRemovedSarMarkerIds(namespace: _storageNamespace);
_messageContactLocations
..clear()
..addAll(storedContactLocations);
@@ -1051,6 +1098,7 @@ class MessagesProvider with ChangeNotifier {
messageReceptionDetails: _messageReceptionDetails,
messageTransferDetails: _messageTransferDetails,
messageRouteMetadata: _messageRouteMetadata,
namespace: _storageNamespace,
);
}
} catch (e) {
@@ -1199,7 +1247,10 @@ class MessagesProvider with ChangeNotifier {
Future<void> removeSarMarker(String id) async {
_sarMarkers.remove(id);
_removedSarMarkerIds.add(id);
await _storageService.saveRemovedSarMarkerIds(_removedSarMarkerIds);
await _storageService.saveRemovedSarMarkerIds(
_removedSarMarkerIds,
namespace: _storageNamespace,
);
notifyListeners();
}
@@ -1324,7 +1375,12 @@ class MessagesProvider with ChangeNotifier {
debugPrint('🗑️ [MessagesProvider] Message $messageId deleted');
_persistMessages();
unawaited(_storageService.saveRemovedSarMarkerIds(_removedSarMarkerIds));
unawaited(
_storageService.saveRemovedSarMarkerIds(
_removedSarMarkerIds,
namespace: _storageNamespace,
),
);
notifyListeners();
}
}
@@ -1348,23 +1404,15 @@ class MessagesProvider with ChangeNotifier {
/// Clear all messages
void clearMessages() {
_cancelAllTimers();
_messages.clear();
_sarMarkers.clear();
_removedSarMarkerIds.clear();
_messageContactLocations.clear();
_messageReceptionDetails.clear();
_messageTransferDetails.clear();
_messageRouteMetadata.clear();
_pendingSentMessages.clear();
_messageContactMap.clear();
_groupedMessageMapping.clear();
_ackTagToRecipients.clear();
_messageAckHistory.clear();
_ackHistoryLookup.clear();
_completedAckHistory.clear();
_resetInMemoryState();
_retryManager.clearAll();
_persistMessages();
unawaited(_storageService.saveRemovedSarMarkerIds(_removedSarMarkerIds));
unawaited(
_storageService.saveRemovedSarMarkerIds(
_removedSarMarkerIds,
namespace: _storageNamespace,
),
);
notifyListeners();
}
@@ -1372,13 +1420,29 @@ class MessagesProvider with ChangeNotifier {
Future<void> clearSarMarkers() async {
_removedSarMarkerIds.addAll(_sarMarkers.keys);
_sarMarkers.clear();
await _storageService.saveRemovedSarMarkerIds(_removedSarMarkerIds);
await _storageService.saveRemovedSarMarkerIds(
_removedSarMarkerIds,
namespace: _storageNamespace,
);
notifyListeners();
}
/// Clear all data
void clearAll() {
_cancelAllTimers();
_resetInMemoryState();
_retryManager.clearAll();
_persistMessages();
unawaited(
_storageService.saveRemovedSarMarkerIds(
_removedSarMarkerIds,
namespace: _storageNamespace,
),
);
notifyListeners();
}
void _resetInMemoryState() {
_messages.clear();
_sarMarkers.clear();
_removedSarMarkerIds.clear();
@@ -1393,10 +1457,6 @@ class MessagesProvider with ChangeNotifier {
_messageAckHistory.clear();
_ackHistoryLookup.clear();
_completedAckHistory.clear();
_retryManager.clearAll();
_persistMessages();
unawaited(_storageService.saveRemovedSarMarkerIds(_removedSarMarkerIds));
notifyListeners();
}
int transferCountForSession({