import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/location_trail.dart'; import '../models/map_drawing.dart'; class MapProvider with ChangeNotifier { MapProvider() { unawaited(_loadInitialState()); } LatLng? _targetLocation; double? _targetZoom; bool _shouldAnimate = false; // Track which contact paths are currently visible final Set _visibleContactPaths = {}; // Location trail tracking LocationTrail? _currentTrail; bool _isTrailVisible = true; final List _trailHistory = []; // WMS overlay toggles bool _showCadastralOverlay = false; bool _showForestRoadsOverlay = false; bool _showHikingTrailsOverlay = false; bool _showMainRoadsOverlay = false; bool _showHouseNumbersOverlay = false; bool _showFireHazardZonesOverlay = false; bool _showHistoricalFiresOverlay = false; bool _showFirebreaksOverlay = false; bool _showKrasFireZonesOverlay = false; bool _showPlaceNamesOverlay = false; bool _showMunicipalityBordersOverlay = false; // Contact trail toggles bool _showAllContactTrails = true; // Default to showing all contact trails bool _hideRepeatersOnMap = false; // Imported trail (from GPX) LocationTrail? _importedTrail; // Download area selection bool _isSelectingDownloadArea = false; LatLngBounds? _downloadAreaBounds; LatLng? get targetLocation => _targetLocation; double? get targetZoom => _targetZoom; bool get shouldAnimate => _shouldAnimate; Set get visibleContactPaths => Set.unmodifiable(_visibleContactPaths); // Trail getters LocationTrail? get currentTrail => _currentTrail; bool get isTrailVisible => _isTrailVisible; List get trailHistory => List.unmodifiable(_trailHistory); bool get isTrailActive => _currentTrail?.isActive ?? false; // WMS overlay getters bool get showCadastralOverlay => _showCadastralOverlay; bool get showForestRoadsOverlay => _showForestRoadsOverlay; bool get showHikingTrailsOverlay => _showHikingTrailsOverlay; bool get showMainRoadsOverlay => _showMainRoadsOverlay; bool get showHouseNumbersOverlay => _showHouseNumbersOverlay; bool get showFireHazardZonesOverlay => _showFireHazardZonesOverlay; bool get showHistoricalFiresOverlay => _showHistoricalFiresOverlay; bool get showFirebreaksOverlay => _showFirebreaksOverlay; bool get showKrasFireZonesOverlay => _showKrasFireZonesOverlay; bool get showPlaceNamesOverlay => _showPlaceNamesOverlay; bool get showMunicipalityBordersOverlay => _showMunicipalityBordersOverlay; // Contact trail getters bool get showAllContactTrails => _showAllContactTrails; bool get hideRepeatersOnMap => _hideRepeatersOnMap; // Imported trail getters LocationTrail? get importedTrail => _importedTrail; // Download area getters bool get isSelectingDownloadArea => _isSelectingDownloadArea; LatLngBounds? get downloadAreaBounds => _downloadAreaBounds; void navigateToLocation({ required LatLng location, double zoom = 15.0, bool animate = true, }) { _targetLocation = location; _targetZoom = zoom; _shouldAnimate = animate; notifyListeners(); } void clearNavigation() { _targetLocation = null; _targetZoom = null; _shouldAnimate = false; // Don't notify listeners to avoid rebuilds } /// Navigate to a drawing by its ID void navigateToDrawing(String drawingId, dynamic drawingProvider) { debugPrint('🗺️ [MapProvider] navigateToDrawing called with ID: $drawingId'); // Find the drawing in the provider final drawings = drawingProvider.drawings as List; debugPrint('🗺️ [MapProvider] Total drawings in provider: ${drawings.length}'); final drawing = drawings.cast().firstWhere( (d) => d.id == drawingId, orElse: () => null, ); if (drawing == null) { debugPrint('⚠️ [MapProvider] Drawing $drawingId not found'); debugPrint('⚠️ [MapProvider] Available drawing IDs: ${drawings.map((d) => d.id).toList()}'); return; } // Use MapDrawing's built-in getCenter and getBounds methods final center = drawing.getCenter(); final bounds = drawing.getBounds(); // Calculate appropriate zoom level based on bounds // For larger drawings, use lower zoom to fit the whole drawing // For smaller drawings, use higher zoom for better detail final latDiff = (bounds.north - bounds.south).abs(); final lonDiff = (bounds.east - bounds.west).abs(); final maxDiff = latDiff > lonDiff ? latDiff : lonDiff; // Zoom scale: smaller drawings get higher zoom // 0.001 degrees (~100m) -> zoom 17 // 0.005 degrees (~500m) -> zoom 16 // 0.01 degrees (~1km) -> zoom 15 // 0.05 degrees (~5km) -> zoom 13 // 0.1 degrees (~10km) -> zoom 12 double zoom = 15.0; if (maxDiff < 0.001) { zoom = 17.0; } else if (maxDiff < 0.005) { zoom = 16.0; } else if (maxDiff < 0.01) { zoom = 15.0; } else if (maxDiff < 0.05) { zoom = 13.0; } else if (maxDiff < 0.1) { zoom = 12.0; } else { zoom = 10.0; } final typeStr = drawing is LineDrawing ? 'line' : 'rectangle'; debugPrint('🗺️ [MapProvider] Navigating to drawing: $typeStr, zoom: $zoom'); navigateToLocation(location: center, zoom: zoom, animate: true); } void updateZoom(double zoom) { _targetZoom = zoom; notifyListeners(); } /// Toggle path visibility for a contact void toggleContactPath(String publicKeyHex) { if (_visibleContactPaths.contains(publicKeyHex)) { _visibleContactPaths.remove(publicKeyHex); } else { _visibleContactPaths.add(publicKeyHex); } notifyListeners(); } /// Check if a contact's path is visible bool isContactPathVisible(String publicKeyHex) { return _visibleContactPaths.contains(publicKeyHex); } /// Hide all contact paths void hideAllPaths() { _visibleContactPaths.clear(); notifyListeners(); } /// Show path for specific contact (hide all others) void showOnlyPath(String publicKeyHex) { _visibleContactPaths.clear(); _visibleContactPaths.add(publicKeyHex); notifyListeners(); } /// Start a new location trail void startTrail() { // End current trail if active if (_currentTrail != null && _currentTrail!.isActive) { endTrail(); } _currentTrail = LocationTrail( id: DateTime.now().millisecondsSinceEpoch.toString(), startTime: DateTime.now(), ); _isTrailVisible = true; notifyListeners(); } /// Add a point to the current trail void addTrailPoint(LatLng position, {double? accuracy, double? speed}) { if (_currentTrail == null || !_currentTrail!.isActive) { startTrail(); } _currentTrail!.addPoint(TrailPoint( position: position, timestamp: DateTime.now(), accuracy: accuracy, speed: speed, )); notifyListeners(); } /// End the current trail void endTrail() { if (_currentTrail != null) { _currentTrail!.isActive = false; _currentTrail!.endTime = DateTime.now(); if (_currentTrail!.points.isNotEmpty) { _trailHistory.add(_currentTrail!); } _currentTrail = null; notifyListeners(); } } /// Toggle trail visibility void toggleTrailVisibility() { _isTrailVisible = !_isTrailVisible; notifyListeners(); } /// Clear the current trail void clearCurrentTrail() { if (_currentTrail != null) { _currentTrail = null; notifyListeners(); } } /// Clear all trail history void clearAllTrails() { _currentTrail = null; _trailHistory.clear(); notifyListeners(); } /// Get total trail distance in meters double get totalTrailDistance { if (_currentTrail == null) return 0; return _currentTrail!.totalDistance; } /// Get trail duration Duration get trailDuration { if (_currentTrail == null) return Duration.zero; return _currentTrail!.duration; } /// Toggle cadastral parcels overlay Future toggleCadastralOverlay() async { _showCadastralOverlay = !_showCadastralOverlay; notifyListeners(); await _saveOverlayState(); } /// Toggle forest roads overlay Future toggleForestRoadsOverlay() async { _showForestRoadsOverlay = !_showForestRoadsOverlay; notifyListeners(); await _saveOverlayState(); } /// Toggle hiking trails overlay Future toggleHikingTrailsOverlay() async { _showHikingTrailsOverlay = !_showHikingTrailsOverlay; notifyListeners(); await _saveOverlayState(); } /// Toggle main roads overlay Future toggleMainRoadsOverlay() async { _showMainRoadsOverlay = !_showMainRoadsOverlay; notifyListeners(); await _saveOverlayState(); } /// Toggle house numbers overlay Future toggleHouseNumbersOverlay() async { _showHouseNumbersOverlay = !_showHouseNumbersOverlay; notifyListeners(); await _saveOverlayState(); } /// Toggle fire hazard zones overlay Future toggleFireHazardZonesOverlay() async { _showFireHazardZonesOverlay = !_showFireHazardZonesOverlay; notifyListeners(); await _saveOverlayState(); } /// Toggle historical fires overlay Future toggleHistoricalFiresOverlay() async { _showHistoricalFiresOverlay = !_showHistoricalFiresOverlay; notifyListeners(); await _saveOverlayState(); } /// Toggle firebreaks overlay Future toggleFirebreaksOverlay() async { _showFirebreaksOverlay = !_showFirebreaksOverlay; notifyListeners(); await _saveOverlayState(); } /// Toggle Kras fire zones overlay Future toggleKrasFireZonesOverlay() async { _showKrasFireZonesOverlay = !_showKrasFireZonesOverlay; notifyListeners(); await _saveOverlayState(); } /// Toggle place names overlay Future togglePlaceNamesOverlay() async { _showPlaceNamesOverlay = !_showPlaceNamesOverlay; notifyListeners(); await _saveOverlayState(); } /// Toggle municipality borders overlay Future toggleMunicipalityBordersOverlay() async { _showMunicipalityBordersOverlay = !_showMunicipalityBordersOverlay; notifyListeners(); await _saveOverlayState(); } /// Load overlay state from SharedPreferences Future loadOverlayState() async { final prefs = await SharedPreferences.getInstance(); _showCadastralOverlay = prefs.getBool('map_show_cadastral_overlay') ?? false; _showForestRoadsOverlay = prefs.getBool('map_show_forest_roads_overlay') ?? false; _showHikingTrailsOverlay = prefs.getBool('map_show_hiking_trails_overlay') ?? false; _showMainRoadsOverlay = prefs.getBool('map_show_main_roads_overlay') ?? false; _showHouseNumbersOverlay = prefs.getBool('map_show_house_numbers_overlay') ?? false; _showFireHazardZonesOverlay = prefs.getBool('map_show_fire_hazard_zones_overlay') ?? false; _showHistoricalFiresOverlay = prefs.getBool('map_show_historical_fires_overlay') ?? false; _showFirebreaksOverlay = prefs.getBool('map_show_firebreaks_overlay') ?? false; _showKrasFireZonesOverlay = prefs.getBool('map_show_kras_fire_zones_overlay') ?? false; _showPlaceNamesOverlay = prefs.getBool('map_show_place_names_overlay') ?? false; _showMunicipalityBordersOverlay = prefs.getBool('map_show_municipality_borders_overlay') ?? false; notifyListeners(); } Future _loadInitialState() async { await Future.wait([loadOverlayState(), loadTrailSettings()]); await loadRepeaterVisibilitySettings(); } /// Save overlay state to SharedPreferences Future _saveOverlayState() async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool('map_show_cadastral_overlay', _showCadastralOverlay); await prefs.setBool('map_show_forest_roads_overlay', _showForestRoadsOverlay); await prefs.setBool('map_show_hiking_trails_overlay', _showHikingTrailsOverlay); await prefs.setBool('map_show_main_roads_overlay', _showMainRoadsOverlay); await prefs.setBool('map_show_house_numbers_overlay', _showHouseNumbersOverlay); await prefs.setBool('map_show_fire_hazard_zones_overlay', _showFireHazardZonesOverlay); await prefs.setBool('map_show_historical_fires_overlay', _showHistoricalFiresOverlay); await prefs.setBool('map_show_firebreaks_overlay', _showFirebreaksOverlay); await prefs.setBool('map_show_kras_fire_zones_overlay', _showKrasFireZonesOverlay); await prefs.setBool('map_show_place_names_overlay', _showPlaceNamesOverlay); await prefs.setBool('map_show_municipality_borders_overlay', _showMunicipalityBordersOverlay); } /// Toggle all contact trails on/off Future toggleAllContactTrails() async { _showAllContactTrails = !_showAllContactTrails; notifyListeners(); await _saveTrailSettings(); } /// Load trail settings from SharedPreferences Future loadTrailSettings() async { final prefs = await SharedPreferences.getInstance(); _showAllContactTrails = prefs.getBool('map_show_all_contact_trails') ?? true; // Default to true (show all) notifyListeners(); } /// Save trail settings to SharedPreferences Future _saveTrailSettings() async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool('map_show_all_contact_trails', _showAllContactTrails); } Future setHideRepeatersOnMap(bool hide) async { if (_hideRepeatersOnMap == hide) return; _hideRepeatersOnMap = hide; notifyListeners(); final prefs = await SharedPreferences.getInstance(); await prefs.setBool('map_hide_repeaters', _hideRepeatersOnMap); } Future loadRepeaterVisibilitySettings() async { final prefs = await SharedPreferences.getInstance(); _hideRepeatersOnMap = prefs.getBool('map_hide_repeaters') ?? false; notifyListeners(); } /// Set imported trail (from GPX import) void setImportedTrail(LocationTrail trail) { _importedTrail = trail; notifyListeners(); } /// Clear imported trail void clearImportedTrail() { _importedTrail = null; notifyListeners(); } /// Replace current trail with imported trail void replaceCurrentTrailWithImport(LocationTrail importedTrail) { // End current trail if active if (_currentTrail != null && _currentTrail!.isActive) { endTrail(); } // Set imported trail as current trail _currentTrail = importedTrail; _isTrailVisible = true; notifyListeners(); } /// Enter download area selection mode with initial bounds void enterDownloadAreaMode(LatLngBounds initialBounds) { _isSelectingDownloadArea = true; _downloadAreaBounds = initialBounds; notifyListeners(); } /// Exit download area selection mode void exitDownloadAreaMode() { _isSelectingDownloadArea = false; _downloadAreaBounds = null; notifyListeners(); } /// Update the download area bounds (while dragging/resizing) void updateDownloadAreaBounds(LatLngBounds bounds) { _downloadAreaBounds = bounds; notifyListeners(); } }