mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-04-20 22:13:48 +00:00
- Added storageUsedKb and storageTotalKb properties to MeshCoreConnector. - Updated battery and storage frame parsing with improved error handling. - Refactored log RX data handling to use BufferReader for better readability and error management. - Enhanced message parsing in ChannelMessage and Message classes to utilize BufferReader. - Introduced new text type for signed messages in meshcore_protocol.dart. - Updated BLE debug log screen to use BufferReader for payload parsing. - Refactored message retry service to handle ACK hashes as integers instead of Uint8List. - Improved message storage serialization and deserialization to accommodate new expectedAckHash type. - Added wasPulled property to Contact model for better state management.
149 lines
5.1 KiB
Dart
149 lines
5.1 KiB
Dart
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
import '../models/message.dart';
|
|
import '../helpers/smaz.dart';
|
|
import '../utils/app_logger.dart';
|
|
import 'prefs_manager.dart';
|
|
|
|
class MessageStore {
|
|
static const String _keyPrefix = 'messages_';
|
|
|
|
String publicKeyHex = '';
|
|
set setPublicKeyHex(String value) =>
|
|
publicKeyHex = value.length > 10 ? value.substring(0, 10) : '';
|
|
|
|
String get keyFor => '$_keyPrefix$publicKeyHex';
|
|
|
|
Future<void> saveMessages(
|
|
String contactKeyHex,
|
|
List<Message> messages,
|
|
) async {
|
|
if (publicKeyHex.isEmpty) {
|
|
appLogger.warn('Public key hex is not set. Cannot save messages.');
|
|
return;
|
|
}
|
|
final prefs = PrefsManager.instance;
|
|
final key = '$keyFor$contactKeyHex';
|
|
final jsonList = messages.map(_messageToJson).toList();
|
|
await prefs.setString(key, jsonEncode(jsonList));
|
|
}
|
|
|
|
Future<List<Message>> loadMessages(String contactKeyHex) async {
|
|
if (publicKeyHex.isEmpty) {
|
|
appLogger.warn('Public key hex is not set. Cannot load messages.');
|
|
return [];
|
|
}
|
|
final prefs = PrefsManager.instance;
|
|
final key = '$keyFor$contactKeyHex';
|
|
final oldKey = '$_keyPrefix$contactKeyHex';
|
|
String? jsonString = prefs.getString(key);
|
|
if (jsonString == null || jsonString.isEmpty) {
|
|
// Attempt migration from legacy unscoped key on first load
|
|
final legacyJsonString = prefs.getString(oldKey);
|
|
prefs.remove(oldKey);
|
|
if (legacyJsonString != null && legacyJsonString.isNotEmpty) {
|
|
appLogger.info(
|
|
'Migrating messages from legacy key $oldKey to scoped key $key',
|
|
);
|
|
await prefs.setString(key, legacyJsonString);
|
|
jsonString = legacyJsonString;
|
|
}
|
|
}
|
|
if (jsonString == null || jsonString.isEmpty) {
|
|
jsonString = prefs.getString(keyFor);
|
|
}
|
|
if (jsonString == null || jsonString.isEmpty) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
|
return jsonList.map((json) => _messageFromJson(json)).toList();
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
Future<void> clearMessages(String contactKeyHex) async {
|
|
if (publicKeyHex.isEmpty) {
|
|
appLogger.warn('Public key hex is not set. Cannot clear messages.');
|
|
return;
|
|
}
|
|
final prefs = PrefsManager.instance;
|
|
final key = '$keyFor$contactKeyHex';
|
|
await prefs.remove(key);
|
|
}
|
|
|
|
Map<String, dynamic> _messageToJson(Message msg) {
|
|
return {
|
|
'senderKey': base64Encode(msg.senderKey),
|
|
'text': msg.text,
|
|
'timestamp': msg.timestamp.millisecondsSinceEpoch,
|
|
'isOutgoing': msg.isOutgoing,
|
|
'isCli': msg.isCli,
|
|
'status': msg.status.index,
|
|
'messageId': msg.messageId,
|
|
'retryCount': msg.retryCount,
|
|
'estimatedTimeoutMs': msg.estimatedTimeoutMs,
|
|
'expectedAckHash': msg.expectedAckHash,
|
|
'sentAt': msg.sentAt?.millisecondsSinceEpoch,
|
|
'deliveredAt': msg.deliveredAt?.millisecondsSinceEpoch,
|
|
'tripTimeMs': msg.tripTimeMs,
|
|
'pathLength': msg.pathLength,
|
|
'pathBytes': msg.pathBytes.isNotEmpty
|
|
? base64Encode(msg.pathBytes)
|
|
: null,
|
|
'reactions': msg.reactions,
|
|
'reactionStatuses': msg.reactionStatuses.map(
|
|
(key, value) => MapEntry(key, value.index),
|
|
),
|
|
'fourByteRoomContactKey': base64Encode(msg.fourByteRoomContactKey),
|
|
};
|
|
}
|
|
|
|
Message _messageFromJson(Map<String, dynamic> json) {
|
|
final rawText = json['text'] as String;
|
|
final isCli = json['isCli'] as bool? ?? false;
|
|
final decodedText = isCli
|
|
? rawText
|
|
: (Smaz.tryDecodePrefixed(rawText) ?? rawText);
|
|
return Message(
|
|
senderKey: Uint8List.fromList(base64Decode(json['senderKey'] as String)),
|
|
text: decodedText,
|
|
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
|
isOutgoing: json['isOutgoing'] as bool,
|
|
isCli: isCli,
|
|
status: MessageStatus.values[json['status'] as int],
|
|
messageId: json['messageId'] as String?,
|
|
retryCount: json['retryCount'] as int? ?? 0,
|
|
estimatedTimeoutMs: json['estimatedTimeoutMs'] as int?,
|
|
expectedAckHash: json['expectedAckHash'] as int? ?? 0,
|
|
sentAt: json['sentAt'] != null
|
|
? DateTime.fromMillisecondsSinceEpoch(json['sentAt'] as int)
|
|
: null,
|
|
deliveredAt: json['deliveredAt'] != null
|
|
? DateTime.fromMillisecondsSinceEpoch(json['deliveredAt'] as int)
|
|
: null,
|
|
tripTimeMs: json['tripTimeMs'] as int?,
|
|
pathLength: json['pathLength'] as int?,
|
|
pathBytes: json['pathBytes'] != null
|
|
? Uint8List.fromList(base64Decode(json['pathBytes'] as String))
|
|
: Uint8List(0),
|
|
reactions:
|
|
(json['reactions'] as Map<String, dynamic>?)?.map(
|
|
(key, value) => MapEntry(key, value as int),
|
|
) ??
|
|
{},
|
|
reactionStatuses:
|
|
(json['reactionStatuses'] as Map<String, dynamic>?)?.map(
|
|
(key, value) => MapEntry(key, MessageStatus.values[value as int]),
|
|
) ??
|
|
{},
|
|
fourByteRoomContactKey: json['fourByteRoomContactKey'] != null
|
|
? Uint8List.fromList(
|
|
base64Decode(json['fourByteRoomContactKey'] as String),
|
|
)
|
|
: null,
|
|
);
|
|
}
|
|
}
|