mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
- Implemented PacketLogScreen to display and filter BLE packet logs. - Added functionality to export logs as CSV and text files. - Introduced clipboard copy feature for hex data. - Implemented clear logs functionality with confirmation dialog. - Enhanced MeshCoreBleService to log TX and RX packets with descriptions. - Added BufferReader methods for reading unsigned and signed 16-bit integers (big-endian). - Updated CayenneLppParser to read values as big-endian. - Created MessageStorageService for persisting messages to local storage. - Enhanced map markers to display telemetry data including voltage, humidity, and pressure.
219 lines
6.0 KiB
Dart
219 lines
6.0 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import '../models/message.dart';
|
|
import '../models/sar_marker.dart';
|
|
import '../services/message_storage_service.dart';
|
|
|
|
/// Messages Provider - manages message history and SAR markers
|
|
class MessagesProvider with ChangeNotifier {
|
|
final List<Message> _messages = [];
|
|
final Map<String, SarMarker> _sarMarkers = {};
|
|
final MessageStorageService _storageService = MessageStorageService();
|
|
bool _isInitialized = false;
|
|
|
|
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();
|
|
|
|
List<SarMarker> get objectMarkers =>
|
|
sarMarkers.where((m) => m.type == SarMarkerType.object).toList();
|
|
|
|
bool get isInitialized => _isInitialized;
|
|
|
|
/// Initialize and load persisted messages
|
|
Future<void> initialize() async {
|
|
if (_isInitialized) return;
|
|
|
|
try {
|
|
print('📦 [MessagesProvider] Loading persisted messages...');
|
|
final storedMessages = await _storageService.loadMessages();
|
|
|
|
// Add stored messages
|
|
_messages.addAll(storedMessages);
|
|
|
|
// Extract SAR markers from stored messages
|
|
for (final message in storedMessages) {
|
|
if (message.isSarMarker) {
|
|
final marker = message.toSarMarker();
|
|
if (marker != null) {
|
|
_sarMarkers[marker.id] = marker;
|
|
}
|
|
}
|
|
}
|
|
|
|
_isInitialized = true;
|
|
print('✅ [MessagesProvider] Loaded ${storedMessages.length} persisted messages');
|
|
notifyListeners();
|
|
} catch (e) {
|
|
print('❌ [MessagesProvider] Error initializing: $e');
|
|
_isInitialized = true; // Mark as initialized even on error
|
|
}
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
}
|
|
|
|
// Persist to storage asynchronously
|
|
_persistMessages();
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Persist to storage asynchronously
|
|
_persistMessages();
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Persist messages to storage (async, non-blocking)
|
|
Future<void> _persistMessages() async {
|
|
try {
|
|
await _storageService.saveMessages(_messages);
|
|
} catch (e) {
|
|
print('❌ [MessagesProvider] Error persisting messages: $e');
|
|
}
|
|
}
|
|
|
|
/// 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();
|
|
_persistMessages();
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Clear all SAR markers
|
|
void clearSarMarkers() {
|
|
_sarMarkers.clear();
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Clear all data
|
|
void clearAll() {
|
|
_messages.clear();
|
|
_sarMarkers.clear();
|
|
_persistMessages();
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Get storage statistics
|
|
Future<Map<String, dynamic>> getStorageStats() async {
|
|
return await _storageService.getStorageStats();
|
|
}
|
|
|
|
/// 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,
|
|
'object': objectMarkers.length,
|
|
};
|
|
}
|
|
}
|