mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
434 lines
14 KiB
Dart
434 lines
14 KiB
Dart
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 {
|
|
LatLng? _targetLocation;
|
|
double? _targetZoom;
|
|
bool _shouldAnimate = false;
|
|
|
|
// Track which contact paths are currently visible
|
|
final Set<String> _visibleContactPaths = {};
|
|
|
|
// Location trail tracking
|
|
LocationTrail? _currentTrail;
|
|
bool _isTrailVisible = true;
|
|
final List<LocationTrail> _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
|
|
|
|
// 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<String> get visibleContactPaths => Set.unmodifiable(_visibleContactPaths);
|
|
|
|
// Trail getters
|
|
LocationTrail? get currentTrail => _currentTrail;
|
|
bool get isTrailVisible => _isTrailVisible;
|
|
List<LocationTrail> 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;
|
|
|
|
// 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<dynamic>().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<void> toggleCadastralOverlay() async {
|
|
_showCadastralOverlay = !_showCadastralOverlay;
|
|
notifyListeners();
|
|
await _saveOverlayState();
|
|
}
|
|
|
|
/// Toggle forest roads overlay
|
|
Future<void> toggleForestRoadsOverlay() async {
|
|
_showForestRoadsOverlay = !_showForestRoadsOverlay;
|
|
notifyListeners();
|
|
await _saveOverlayState();
|
|
}
|
|
|
|
/// Toggle hiking trails overlay
|
|
Future<void> toggleHikingTrailsOverlay() async {
|
|
_showHikingTrailsOverlay = !_showHikingTrailsOverlay;
|
|
notifyListeners();
|
|
await _saveOverlayState();
|
|
}
|
|
|
|
/// Toggle main roads overlay
|
|
Future<void> toggleMainRoadsOverlay() async {
|
|
_showMainRoadsOverlay = !_showMainRoadsOverlay;
|
|
notifyListeners();
|
|
await _saveOverlayState();
|
|
}
|
|
|
|
/// Toggle house numbers overlay
|
|
Future<void> toggleHouseNumbersOverlay() async {
|
|
_showHouseNumbersOverlay = !_showHouseNumbersOverlay;
|
|
notifyListeners();
|
|
await _saveOverlayState();
|
|
}
|
|
|
|
/// Toggle fire hazard zones overlay
|
|
Future<void> toggleFireHazardZonesOverlay() async {
|
|
_showFireHazardZonesOverlay = !_showFireHazardZonesOverlay;
|
|
notifyListeners();
|
|
await _saveOverlayState();
|
|
}
|
|
|
|
/// Toggle historical fires overlay
|
|
Future<void> toggleHistoricalFiresOverlay() async {
|
|
_showHistoricalFiresOverlay = !_showHistoricalFiresOverlay;
|
|
notifyListeners();
|
|
await _saveOverlayState();
|
|
}
|
|
|
|
/// Toggle firebreaks overlay
|
|
Future<void> toggleFirebreaksOverlay() async {
|
|
_showFirebreaksOverlay = !_showFirebreaksOverlay;
|
|
notifyListeners();
|
|
await _saveOverlayState();
|
|
}
|
|
|
|
/// Toggle Kras fire zones overlay
|
|
Future<void> toggleKrasFireZonesOverlay() async {
|
|
_showKrasFireZonesOverlay = !_showKrasFireZonesOverlay;
|
|
notifyListeners();
|
|
await _saveOverlayState();
|
|
}
|
|
|
|
/// Toggle place names overlay
|
|
Future<void> togglePlaceNamesOverlay() async {
|
|
_showPlaceNamesOverlay = !_showPlaceNamesOverlay;
|
|
notifyListeners();
|
|
await _saveOverlayState();
|
|
}
|
|
|
|
/// Toggle municipality borders overlay
|
|
Future<void> toggleMunicipalityBordersOverlay() async {
|
|
_showMunicipalityBordersOverlay = !_showMunicipalityBordersOverlay;
|
|
notifyListeners();
|
|
await _saveOverlayState();
|
|
}
|
|
|
|
/// Load overlay state from SharedPreferences
|
|
Future<void> 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();
|
|
}
|
|
|
|
/// Save overlay state to SharedPreferences
|
|
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', _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<void> toggleAllContactTrails() async {
|
|
_showAllContactTrails = !_showAllContactTrails;
|
|
notifyListeners();
|
|
await _saveTrailSettings();
|
|
}
|
|
|
|
/// Load trail settings from SharedPreferences
|
|
Future<void> 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<void> _saveTrailSettings() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool('map_show_all_contact_trails', _showAllContactTrails);
|
|
}
|
|
|
|
/// 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();
|
|
}
|
|
}
|