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 saveMessages( String contactKeyHex, List 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> 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; return jsonList.map((json) => _messageFromJson(json)).toList(); } catch (e) { return []; } } Future 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 _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 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?)?.map( (key, value) => MapEntry(key, value as int), ) ?? {}, reactionStatuses: (json['reactionStatuses'] as Map?)?.map( (key, value) => MapEntry(key, MessageStatus.values[value as int]), ) ?? {}, fourByteRoomContactKey: json['fourByteRoomContactKey'] != null ? Uint8List.fromList( base64Decode(json['fourByteRoomContactKey'] as String), ) : null, ); } }