mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-13 17:30:28 +00:00
initial commit
This commit is contained in:
121
lib/providers/app_provider.dart
Normal file
121
lib/providers/app_provider.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'connection_provider.dart';
|
||||
import 'contacts_provider.dart';
|
||||
import 'messages_provider.dart';
|
||||
import '../services/tile_cache_service.dart';
|
||||
|
||||
/// Main App Provider - coordinates all other providers
|
||||
class AppProvider with ChangeNotifier {
|
||||
final ConnectionProvider connectionProvider;
|
||||
final ContactsProvider contactsProvider;
|
||||
final MessagesProvider messagesProvider;
|
||||
final TileCacheService tileCacheService;
|
||||
|
||||
bool _isInitialized = false;
|
||||
bool get isInitialized => _isInitialized;
|
||||
|
||||
AppProvider({
|
||||
required this.connectionProvider,
|
||||
required this.contactsProvider,
|
||||
required this.messagesProvider,
|
||||
required this.tileCacheService,
|
||||
}) {
|
||||
_setupCallbacks();
|
||||
_initializeTileCache();
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
/// Initialize tile cache service
|
||||
Future<void> _initializeTileCache() async {
|
||||
try {
|
||||
await tileCacheService.initialize();
|
||||
debugPrint('Tile cache initialized');
|
||||
} catch (e) {
|
||||
debugPrint('Error initializing tile cache: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Setup callbacks between providers
|
||||
void _setupCallbacks() {
|
||||
// When a contact is received from BLE
|
||||
connectionProvider.onContactReceived = (contact) {
|
||||
contactsProvider.addOrUpdateContact(contact);
|
||||
};
|
||||
|
||||
// When all contacts are received
|
||||
connectionProvider.onContactsComplete = (contacts) {
|
||||
contactsProvider.addContacts(contacts);
|
||||
debugPrint('Received ${contacts.length} contacts');
|
||||
};
|
||||
|
||||
// When a message is received
|
||||
connectionProvider.onMessageReceived = (message) {
|
||||
messagesProvider.addMessage(message);
|
||||
|
||||
// Optionally update sender name from contacts
|
||||
if (message.senderPublicKeyPrefix != null) {
|
||||
final contact = contactsProvider
|
||||
.findContactByKey(message.senderPublicKeyPrefix!);
|
||||
if (contact != null) {
|
||||
final updatedMessage = message.copyWith(senderName: contact.advName);
|
||||
// Note: You might want to update the message in the list
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// When telemetry is received
|
||||
connectionProvider.onTelemetryReceived = (publicKey, lppData) {
|
||||
contactsProvider.updateTelemetry(publicKey, lppData);
|
||||
};
|
||||
}
|
||||
|
||||
/// Initialize the app (load contacts, sync time, etc.)
|
||||
Future<void> initialize() async {
|
||||
if (!connectionProvider.deviceInfo.isConnected) return;
|
||||
|
||||
try {
|
||||
// Sync device time
|
||||
await connectionProvider.syncDeviceTime();
|
||||
|
||||
// Load contacts
|
||||
await connectionProvider.getContacts();
|
||||
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Initialization error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh data (contacts, messages)
|
||||
Future<void> refresh() async {
|
||||
if (!connectionProvider.deviceInfo.isConnected) return;
|
||||
|
||||
try {
|
||||
await connectionProvider.getContacts();
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Refresh error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all data
|
||||
void clearAllData() {
|
||||
contactsProvider.clearContacts();
|
||||
messagesProvider.clearAll();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Get app statistics
|
||||
Map<String, dynamic> get statistics {
|
||||
return {
|
||||
'connection': {
|
||||
'isConnected': connectionProvider.deviceInfo.isConnected,
|
||||
'deviceName': connectionProvider.deviceInfo.deviceName,
|
||||
'battery': connectionProvider.deviceInfo.batteryPercent,
|
||||
},
|
||||
'contacts': contactsProvider.contactCounts,
|
||||
'messages': messagesProvider.messageStats,
|
||||
'sarMarkers': messagesProvider.sarMarkerStats,
|
||||
};
|
||||
}
|
||||
}
|
||||
240
lib/providers/connection_provider.dart
Normal file
240
lib/providers/connection_provider.dart
Normal file
@@ -0,0 +1,240 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
import '../models/device_info.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../models/message.dart';
|
||||
import '../services/meshcore_ble_service.dart';
|
||||
import '../services/cayenne_lpp_parser.dart';
|
||||
import '../utils/sar_message_parser.dart';
|
||||
|
||||
/// Connection Provider - manages MeshCore BLE connection
|
||||
class ConnectionProvider with ChangeNotifier {
|
||||
final MeshCoreBleService _bleService = MeshCoreBleService();
|
||||
|
||||
DeviceInfo _deviceInfo = DeviceInfo();
|
||||
DeviceInfo get deviceInfo => _deviceInfo;
|
||||
|
||||
List<BluetoothDevice> _scannedDevices = [];
|
||||
List<BluetoothDevice> get scannedDevices => _scannedDevices;
|
||||
|
||||
bool _isScanning = false;
|
||||
bool get isScanning => _isScanning;
|
||||
|
||||
String? _error;
|
||||
String? get error => _error;
|
||||
|
||||
// Callbacks for other providers
|
||||
Function(Contact)? onContactReceived;
|
||||
Function(List<Contact>)? onContactsComplete;
|
||||
Function(Message)? onMessageReceived;
|
||||
Function(Uint8List publicKey, Uint8List lppData)? onTelemetryReceived;
|
||||
|
||||
ConnectionProvider() {
|
||||
_initializeBleService();
|
||||
}
|
||||
|
||||
void _initializeBleService() {
|
||||
_bleService.onConnectionStateChanged = (isConnected) {
|
||||
_deviceInfo = _deviceInfo.copyWith(
|
||||
connectionState: isConnected
|
||||
? ConnectionState.connected
|
||||
: ConnectionState.disconnected,
|
||||
lastUpdate: DateTime.now(),
|
||||
);
|
||||
notifyListeners();
|
||||
};
|
||||
|
||||
_bleService.onError = (error) {
|
||||
_error = error;
|
||||
_deviceInfo = _deviceInfo.copyWith(
|
||||
connectionState: ConnectionState.error,
|
||||
);
|
||||
notifyListeners();
|
||||
};
|
||||
|
||||
_bleService.onContactReceived = (contact) {
|
||||
onContactReceived?.call(contact);
|
||||
};
|
||||
|
||||
_bleService.onContactsComplete = (contacts) {
|
||||
onContactsComplete?.call(contacts);
|
||||
};
|
||||
|
||||
_bleService.onMessageReceived = (message) {
|
||||
// Parse SAR markers
|
||||
final enhancedMessage = SarMessageParser.enhanceMessage(message);
|
||||
onMessageReceived?.call(enhancedMessage);
|
||||
};
|
||||
|
||||
_bleService.onTelemetryReceived = (publicKey, lppData) {
|
||||
onTelemetryReceived?.call(publicKey, lppData);
|
||||
};
|
||||
}
|
||||
|
||||
/// Start scanning for MeshCore devices
|
||||
Future<void> startScan() async {
|
||||
_isScanning = true;
|
||||
_scannedDevices.clear();
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
await for (final device
|
||||
in _bleService.scanForDevices(timeout: const Duration(seconds: 10))) {
|
||||
if (!_scannedDevices.any((d) => d.remoteId == device.remoteId)) {
|
||||
_scannedDevices.add(device);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
_error = 'Scan error: $e';
|
||||
} finally {
|
||||
_isScanning = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop scanning
|
||||
Future<void> stopScan() async {
|
||||
await FlutterBluePlus.stopScan();
|
||||
_isScanning = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Connect to a device
|
||||
Future<bool> connect(BluetoothDevice device) async {
|
||||
_deviceInfo = _deviceInfo.copyWith(
|
||||
deviceId: device.remoteId.toString(),
|
||||
deviceName: device.platformName.isNotEmpty ? device.platformName : 'Unknown',
|
||||
connectionState: ConnectionState.connecting,
|
||||
);
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
|
||||
final success = await _bleService.connect(device);
|
||||
if (!success) {
|
||||
_deviceInfo = _deviceInfo.copyWith(
|
||||
connectionState: ConnectionState.error,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/// Disconnect from device
|
||||
Future<void> disconnect() async {
|
||||
_deviceInfo = _deviceInfo.copyWith(
|
||||
connectionState: ConnectionState.disconnecting,
|
||||
);
|
||||
notifyListeners();
|
||||
|
||||
await _bleService.disconnect();
|
||||
|
||||
_deviceInfo = DeviceInfo(
|
||||
connectionState: ConnectionState.disconnected,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Get contacts from device
|
||||
Future<void> getContacts() async {
|
||||
if (!_bleService.isConnected) {
|
||||
_error = 'Not connected to device';
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await _bleService.getContacts();
|
||||
} catch (e) {
|
||||
_error = 'Failed to get contacts: $e';
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Send text message to contact
|
||||
Future<void> sendTextMessage({
|
||||
required Uint8List contactPublicKey,
|
||||
required String text,
|
||||
}) async {
|
||||
if (!_bleService.isConnected) {
|
||||
_error = 'Not connected to device';
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await _bleService.sendTextMessage(
|
||||
contactPublicKey: contactPublicKey,
|
||||
text: text,
|
||||
);
|
||||
} catch (e) {
|
||||
_error = 'Failed to send message: $e';
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Send channel message
|
||||
Future<void> sendChannelMessage({
|
||||
required int channelIdx,
|
||||
required String text,
|
||||
}) async {
|
||||
if (!_bleService.isConnected) {
|
||||
_error = 'Not connected to device';
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await _bleService.sendChannelMessage(
|
||||
channelIdx: channelIdx,
|
||||
text: text,
|
||||
);
|
||||
} catch (e) {
|
||||
_error = 'Failed to send channel message: $e';
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Request telemetry from contact
|
||||
Future<void> requestTelemetry(Uint8List contactPublicKey) async {
|
||||
if (!_bleService.isConnected) {
|
||||
_error = 'Not connected to device';
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await _bleService.requestTelemetry(contactPublicKey);
|
||||
} catch (e) {
|
||||
_error = 'Failed to request telemetry: $e';
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set device time to current time
|
||||
Future<void> syncDeviceTime() async {
|
||||
if (!_bleService.isConnected) return;
|
||||
|
||||
try {
|
||||
await _bleService.setDeviceTime();
|
||||
} catch (e) {
|
||||
_error = 'Failed to sync time: $e';
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear error message
|
||||
void clearError() {
|
||||
_error = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_bleService.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
135
lib/providers/contacts_provider.dart
Normal file
135
lib/providers/contacts_provider.dart
Normal file
@@ -0,0 +1,135 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../models/contact_telemetry.dart';
|
||||
import '../services/cayenne_lpp_parser.dart';
|
||||
|
||||
/// Contacts Provider - manages contact list and telemetry
|
||||
class ContactsProvider with ChangeNotifier {
|
||||
final Map<String, Contact> _contacts = {};
|
||||
|
||||
List<Contact> get contacts => _contacts.values.toList();
|
||||
|
||||
List<Contact> get chatContacts =>
|
||||
contacts.where((c) => c.isChat).toList()..sort(_sortByLastSeen);
|
||||
|
||||
List<Contact> get repeaters =>
|
||||
contacts.where((c) => c.isRepeater).toList()..sort(_sortByLastSeen);
|
||||
|
||||
List<Contact> get rooms =>
|
||||
contacts.where((c) => c.isRoom).toList()..sort(_sortByLastSeen);
|
||||
|
||||
/// Get contacts with location (for map display)
|
||||
List<Contact> get contactsWithLocation =>
|
||||
contacts.where((c) => c.displayLocation != null).toList();
|
||||
|
||||
/// Get chat contacts with location (team members on map)
|
||||
List<Contact> get chatContactsWithLocation =>
|
||||
chatContacts.where((c) => c.displayLocation != null).toList();
|
||||
|
||||
/// Sort contacts by last seen (most recent first)
|
||||
int _sortByLastSeen(Contact a, Contact b) {
|
||||
return b.lastSeenTime.compareTo(a.lastSeenTime);
|
||||
}
|
||||
|
||||
/// Add or update a contact
|
||||
void addOrUpdateContact(Contact contact) {
|
||||
_contacts[contact.publicKeyHex] = contact;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Add multiple contacts
|
||||
void addContacts(List<Contact> contacts) {
|
||||
for (final contact in contacts) {
|
||||
_contacts[contact.publicKeyHex] = contact;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Update contact telemetry
|
||||
void updateTelemetry(Uint8List publicKeyPrefix, Uint8List lppData) {
|
||||
// Find contact by public key prefix
|
||||
final contact = _findContactByPrefix(publicKeyPrefix);
|
||||
if (contact == null) return;
|
||||
|
||||
try {
|
||||
// Parse Cayenne LPP data
|
||||
final telemetry = CayenneLppParser.parse(lppData);
|
||||
|
||||
// Update contact with new telemetry
|
||||
final updatedContact = contact.copyWith(telemetry: telemetry);
|
||||
_contacts[contact.publicKeyHex] = updatedContact;
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Failed to parse telemetry: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Find contact by public key prefix (6 bytes)
|
||||
Contact? _findContactByPrefix(Uint8List prefix) {
|
||||
if (prefix.length < 6) return null;
|
||||
|
||||
final prefixHex = prefix
|
||||
.sublist(0, 6)
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||
.join('');
|
||||
|
||||
for (final contact in contacts) {
|
||||
if (contact.publicKeyHex.startsWith(prefixHex)) {
|
||||
return contact;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Find contact by public key
|
||||
Contact? findContactByKey(Uint8List publicKey) {
|
||||
final keyHex =
|
||||
publicKey.map((b) => b.toRadixString(16).padLeft(2, '0')).join('');
|
||||
return _contacts[keyHex];
|
||||
}
|
||||
|
||||
/// Find contact by name
|
||||
Contact? findContactByName(String name) {
|
||||
return contacts.firstWhere(
|
||||
(c) => c.advName == name,
|
||||
orElse: () => contacts.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get contacts with low battery
|
||||
List<Contact> get lowBatteryContacts {
|
||||
return contacts.where((c) {
|
||||
final battery = c.displayBattery;
|
||||
return battery != null && battery < 20.0;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// Get recently seen contacts (within last 10 minutes)
|
||||
List<Contact> get recentlySeenContacts {
|
||||
return contacts.where((c) => c.isRecentlySeen).toList();
|
||||
}
|
||||
|
||||
/// Clear all contacts
|
||||
void clearContacts() {
|
||||
_contacts.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Remove a contact
|
||||
void removeContact(String publicKeyHex) {
|
||||
_contacts.remove(publicKeyHex);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Get contact count by type
|
||||
Map<String, int> get contactCounts {
|
||||
return {
|
||||
'chat': chatContacts.length,
|
||||
'repeater': repeaters.length,
|
||||
'room': rooms.length,
|
||||
'total': contacts.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
35
lib/providers/map_provider.dart
Normal file
35
lib/providers/map_provider.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
class MapProvider with ChangeNotifier {
|
||||
LatLng? _targetLocation;
|
||||
double? _targetZoom;
|
||||
bool _shouldAnimate = false;
|
||||
|
||||
LatLng? get targetLocation => _targetLocation;
|
||||
double? get targetZoom => _targetZoom;
|
||||
bool get shouldAnimate => _shouldAnimate;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
void updateZoom(double zoom) {
|
||||
_targetZoom = zoom;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
156
lib/providers/messages_provider.dart
Normal file
156
lib/providers/messages_provider.dart
Normal file
@@ -0,0 +1,156 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../models/message.dart';
|
||||
import '../models/sar_marker.dart';
|
||||
|
||||
/// Messages Provider - manages message history and SAR markers
|
||||
class MessagesProvider with ChangeNotifier {
|
||||
final List<Message> _messages = [];
|
||||
final Map<String, SarMarker> _sarMarkers = {};
|
||||
|
||||
List<Message> get messages => List.unmodifiable(_messages);
|
||||
|
||||
List<Message> get contactMessages =>
|
||||
_messages.where((m) => m.isContactMessage).toList();
|
||||
|
||||
List<Message> get channelMessages =>
|
||||
_messages.where((m) => m.isChannelMessage).toList();
|
||||
|
||||
List<Message> get sarMarkerMessages =>
|
||||
_messages.where((m) => m.isSarMarker).toList();
|
||||
|
||||
List<SarMarker> get sarMarkers => _sarMarkers.values.toList();
|
||||
|
||||
List<SarMarker> get foundPersonMarkers =>
|
||||
sarMarkers.where((m) => m.type == SarMarkerType.foundPerson).toList();
|
||||
|
||||
List<SarMarker> get fireMarkers =>
|
||||
sarMarkers.where((m) => m.type == SarMarkerType.fire).toList();
|
||||
|
||||
List<SarMarker> get stagingAreaMarkers =>
|
||||
sarMarkers.where((m) => m.type == SarMarkerType.stagingArea).toList();
|
||||
|
||||
/// Add a message
|
||||
void addMessage(Message message) {
|
||||
_messages.add(message);
|
||||
|
||||
// If it's a SAR marker message, extract and store the marker
|
||||
if (message.isSarMarker) {
|
||||
final marker = message.toSarMarker();
|
||||
if (marker != null) {
|
||||
_sarMarkers[marker.id] = marker;
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Add multiple messages
|
||||
void addMessages(List<Message> messages) {
|
||||
for (final message in messages) {
|
||||
_messages.add(message);
|
||||
|
||||
if (message.isSarMarker) {
|
||||
final marker = message.toSarMarker();
|
||||
if (marker != null) {
|
||||
_sarMarkers[marker.id] = marker;
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Get messages for a specific contact
|
||||
List<Message> getMessagesForContact(String senderKeyShort) {
|
||||
return _messages
|
||||
.where((m) =>
|
||||
m.isContactMessage &&
|
||||
m.senderKeyShort != null &&
|
||||
m.senderKeyShort!.startsWith(senderKeyShort))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Get messages for a specific channel
|
||||
List<Message> getMessagesForChannel(int channelIdx) {
|
||||
return _messages
|
||||
.where((m) => m.isChannelMessage && m.channelIdx == channelIdx)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Get recent messages (last N messages)
|
||||
List<Message> getRecentMessages({int count = 50}) {
|
||||
final sorted = List<Message>.from(_messages)
|
||||
..sort((a, b) => b.sentAt.compareTo(a.sentAt));
|
||||
return sorted.take(count).toList();
|
||||
}
|
||||
|
||||
/// Get messages from last N hours
|
||||
List<Message> getMessagesSince(Duration duration) {
|
||||
final cutoff = DateTime.now().subtract(duration);
|
||||
return _messages.where((m) => m.sentAt.isAfter(cutoff)).toList();
|
||||
}
|
||||
|
||||
/// Search messages by text
|
||||
List<Message> searchMessages(String query) {
|
||||
if (query.isEmpty) return [];
|
||||
final lowerQuery = query.toLowerCase();
|
||||
return _messages
|
||||
.where((m) => m.text.toLowerCase().contains(lowerQuery))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Get SAR marker by ID
|
||||
SarMarker? getSarMarker(String id) {
|
||||
return _sarMarkers[id];
|
||||
}
|
||||
|
||||
/// Get recent SAR markers (within last hour)
|
||||
List<SarMarker> getRecentSarMarkers() {
|
||||
return sarMarkers.where((m) => m.isRecent).toList();
|
||||
}
|
||||
|
||||
/// Remove a SAR marker
|
||||
void removeSarMarker(String id) {
|
||||
_sarMarkers.remove(id);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clear all messages
|
||||
void clearMessages() {
|
||||
_messages.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clear all SAR markers
|
||||
void clearSarMarkers() {
|
||||
_sarMarkers.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clear all data
|
||||
void clearAll() {
|
||||
_messages.clear();
|
||||
_sarMarkers.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Get message statistics
|
||||
Map<String, int> get messageStats {
|
||||
return {
|
||||
'total': _messages.length,
|
||||
'contact': contactMessages.length,
|
||||
'channel': channelMessages.length,
|
||||
'sar': sarMarkerMessages.length,
|
||||
'sarMarkers': sarMarkers.length,
|
||||
};
|
||||
}
|
||||
|
||||
/// Get SAR marker statistics
|
||||
Map<String, int> get sarMarkerStats {
|
||||
return {
|
||||
'total': sarMarkers.length,
|
||||
'foundPerson': foundPersonMarkers.length,
|
||||
'fire': fireMarkers.length,
|
||||
'stagingArea': stagingAreaMarkers.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user