import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:latlong2/latlong.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../models/map_drawing.dart'; import '../utils/drawing_message_parser.dart'; /// Drawing mode state enum DrawingMode { none, line, rectangle } /// Provider for managing map drawings class DrawingProvider with ChangeNotifier { static const String _storageKey = 'map_drawings'; // Drawing state DrawingMode _drawingMode = DrawingMode.none; Color _selectedColor = DrawingColors.palette[0]; bool _showReceivedDrawings = true; bool _showSarMarkers = true; // Completed drawings final List _drawings = []; // In-progress drawing MapDrawing? _currentDrawing; List _currentLinePoints = []; LatLng? _rectangleStartPoint; // Getters DrawingMode get drawingMode => _drawingMode; Color get selectedColor => _selectedColor; bool get showReceivedDrawings => _showReceivedDrawings; bool get showSarMarkers => _showSarMarkers; List get drawings => _showReceivedDrawings ? List.unmodifiable(_drawings) : List.unmodifiable(_drawings.where((d) => !d.isReceived).toList()); MapDrawing? get currentDrawing => _currentDrawing; List get currentLinePoints => List.unmodifiable(_currentLinePoints); LatLng? get rectangleStartPoint => _rectangleStartPoint; bool get isDrawing => _drawingMode != DrawingMode.none; /// Initialize and load saved drawings Future initialize() async { await _loadDrawings(); } /// Set drawing mode void setDrawingMode(DrawingMode mode) { if (_drawingMode != mode) { // Cancel any in-progress drawing when switching modes _cancelCurrentDrawing(); _drawingMode = mode; notifyListeners(); } } /// Set selected color void setColor(Color color) { _selectedColor = color; notifyListeners(); } /// Toggle visibility of received drawings void toggleReceivedDrawings() { _showReceivedDrawings = !_showReceivedDrawings; notifyListeners(); } /// Toggle visibility of SAR markers void toggleSarMarkers() { _showSarMarkers = !_showSarMarkers; notifyListeners(); } /// Start drawing a line void startLine(LatLng point) { if (_drawingMode != DrawingMode.line) return; _currentLinePoints = [point]; notifyListeners(); } /// Add point to current line void addLinePoint(LatLng point) { if (_drawingMode != DrawingMode.line || _currentLinePoints.isEmpty) return; _currentLinePoints.add(point); notifyListeners(); } /// Complete current line drawing void completeLine() { if (_drawingMode != DrawingMode.line || _currentLinePoints.length < 2) { _cancelCurrentDrawing(); return; } final drawing = LineDrawing( id: DateTime.now().millisecondsSinceEpoch.toString(), color: _selectedColor, createdAt: DateTime.now(), points: List.from(_currentLinePoints), ); _drawings.add(drawing); _currentLinePoints = []; _saveDrawings(); notifyListeners(); } /// Start drawing a rectangle void startRectangle(LatLng point) { if (_drawingMode != DrawingMode.rectangle) return; _rectangleStartPoint = point; notifyListeners(); } /// Update rectangle end point (for preview) void updateRectangleEndPoint(LatLng endPoint) { if (_drawingMode != DrawingMode.rectangle || _rectangleStartPoint == null) return; // Create preview rectangle _currentDrawing = RectangleDrawing( id: 'preview', color: _selectedColor, createdAt: DateTime.now(), topLeft: LatLng( _rectangleStartPoint!.latitude > endPoint.latitude ? endPoint.latitude : _rectangleStartPoint!.latitude, _rectangleStartPoint!.longitude < endPoint.longitude ? _rectangleStartPoint!.longitude : endPoint.longitude, ), bottomRight: LatLng( _rectangleStartPoint!.latitude < endPoint.latitude ? endPoint.latitude : _rectangleStartPoint!.latitude, _rectangleStartPoint!.longitude > endPoint.longitude ? _rectangleStartPoint!.longitude : endPoint.longitude, ), ); notifyListeners(); } /// Complete current rectangle drawing void completeRectangle(LatLng endPoint) { if (_drawingMode != DrawingMode.rectangle || _rectangleStartPoint == null) { _cancelCurrentDrawing(); return; } // Calculate top-left and bottom-right corners final topLeft = LatLng( _rectangleStartPoint!.latitude > endPoint.latitude ? endPoint.latitude : _rectangleStartPoint!.latitude, _rectangleStartPoint!.longitude < endPoint.longitude ? _rectangleStartPoint!.longitude : endPoint.longitude, ); final bottomRight = LatLng( _rectangleStartPoint!.latitude < endPoint.latitude ? endPoint.latitude : _rectangleStartPoint!.latitude, _rectangleStartPoint!.longitude > endPoint.longitude ? _rectangleStartPoint!.longitude : endPoint.longitude, ); final drawing = RectangleDrawing( id: DateTime.now().millisecondsSinceEpoch.toString(), color: _selectedColor, createdAt: DateTime.now(), topLeft: topLeft, bottomRight: bottomRight, ); _drawings.add(drawing); _rectangleStartPoint = null; _currentDrawing = null; _saveDrawings(); notifyListeners(); } /// Cancel current drawing in progress void _cancelCurrentDrawing() { _currentLinePoints = []; _rectangleStartPoint = null; _currentDrawing = null; } /// Clear current drawing (public method) void cancelCurrentDrawing() { _cancelCurrentDrawing(); notifyListeners(); } /// Remove a specific drawing void removeDrawing(String id) { _drawings.removeWhere((d) => d.id == id); _saveDrawings(); notifyListeners(); } /// Clear all drawings void clearAllDrawings() { _drawings.clear(); _cancelCurrentDrawing(); _saveDrawings(); notifyListeners(); } /// Exit drawing mode void exitDrawingMode() { _cancelCurrentDrawing(); _drawingMode = DrawingMode.none; notifyListeners(); } /// Save drawings to persistent storage Future _saveDrawings() async { try { final prefs = await SharedPreferences.getInstance(); final jsonList = _drawings.map((d) => d.toJson()).toList(); final jsonString = jsonEncode(jsonList); await prefs.setString(_storageKey, jsonString); } catch (e) { debugPrint('Error saving drawings: $e'); } } /// Load drawings from persistent storage Future _loadDrawings() async { try { final prefs = await SharedPreferences.getInstance(); final jsonString = prefs.getString(_storageKey); if (jsonString == null) return; final jsonList = jsonDecode(jsonString) as List; _drawings.clear(); for (final json in jsonList) { final drawing = MapDrawing.fromJson(json as Map); if (drawing != null) { _drawings.add(drawing); } } notifyListeners(); } catch (e) { debugPrint('Error loading drawings: $e'); } } /// Get the current preview drawing for rendering MapDrawing? getPreviewDrawing() { if (_drawingMode == DrawingMode.line && _currentLinePoints.length >= 2) { return LineDrawing( id: 'preview', color: _selectedColor, createdAt: DateTime.now(), points: _currentLinePoints, ); } else if (_drawingMode == DrawingMode.rectangle && _currentDrawing != null) { return _currentDrawing; } return null; } /// Add received drawing from another node void addReceivedDrawing(MapDrawing drawing) { // Check if drawing with this ID already exists if (_drawings.any((d) => d.id == drawing.id)) { debugPrint('Drawing ${drawing.id} already exists, skipping'); return; } _drawings.add(drawing); _saveDrawings(); notifyListeners(); } /// Broadcast a drawing to contacts /// Returns the formatted message string ready to send /// Sender will be determined from packet metadata on receiving end String createDrawingBroadcastMessage(MapDrawing drawing) { return DrawingMessageParser.createDrawingMessage(drawing); } }