import 'dart:convert'; import 'dart:typed_data'; import '../models/contact.dart'; import '../utils/app_logger.dart'; import 'prefs_manager.dart'; class ContactStore { static const String _keyPrefix = 'contacts'; String publicKeyHex = ''; set setPublicKeyHex(String value) => publicKeyHex = value.length > 10 ? value.substring(0, 10) : ''; String get keyFor => '$_keyPrefix$publicKeyHex'; Future> loadContacts() async { if (publicKeyHex.isEmpty) { appLogger.warn('Public key hex is not set. Cannot load contacts.'); return []; } final prefs = PrefsManager.instance; String? jsonString = prefs.getString(keyFor); if (jsonString == null || jsonString.isEmpty) { // Attempt migration from legacy unscoped key on first load final legacyJsonString = prefs.getString(_keyPrefix); prefs.remove(_keyPrefix); if (legacyJsonString != null && legacyJsonString.isNotEmpty) { appLogger.info( 'Migrating contacts from legacy key $_keyPrefix to scoped key $keyFor', ); await prefs.setString(keyFor, 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((entry) => _fromJson(entry as Map)) .toList(); } catch (_) { return []; } } Future saveContacts(List contacts) async { if (publicKeyHex.isEmpty) { appLogger.warn('Public key hex is not set. Cannot save contacts.'); return; } final prefs = PrefsManager.instance; final jsonList = contacts.map(_toJson).toList(); await prefs.setString(keyFor, jsonEncode(jsonList)); } Map _toJson(Contact contact) { return { 'publicKey': base64Encode(contact.publicKey), 'name': contact.name, 'type': contact.type, 'flags': contact.flags, 'pathLength': contact.pathLength, 'path': base64Encode(contact.path), 'pathOverride': contact.pathOverride, 'pathOverrideBytes': contact.pathOverrideBytes != null ? base64Encode(contact.pathOverrideBytes!) : null, 'latitude': contact.latitude, 'longitude': contact.longitude, 'lastSeen': contact.lastSeen.millisecondsSinceEpoch, 'lastMessageAt': contact.lastMessageAt.millisecondsSinceEpoch, 'isActive': contact.isActive, 'rawPacket': contact.rawPacket != null ? base64Encode(contact.rawPacket!) : null, }; } Contact _fromJson(Map json) { final lastSeenMs = json['lastSeen'] as int? ?? 0; final lastMessageMs = json['lastMessageAt'] as int?; return Contact( publicKey: Uint8List.fromList(base64Decode(json['publicKey'] as String)), name: json['name'] as String? ?? 'Unknown', type: json['type'] as int? ?? 0, flags: json['flags'] as int? ?? 0, pathLength: json['pathLength'] as int? ?? -1, path: json['path'] != null ? Uint8List.fromList(base64Decode(json['path'] as String)) : Uint8List(0), pathOverride: json['pathOverride'] as int?, pathOverrideBytes: json['pathOverrideBytes'] != null ? Uint8List.fromList( base64Decode(json['pathOverrideBytes'] as String), ) : null, latitude: (json['latitude'] as num?)?.toDouble(), longitude: (json['longitude'] as num?)?.toDouble(), lastSeen: DateTime.fromMillisecondsSinceEpoch(lastSeenMs), lastMessageAt: DateTime.fromMillisecondsSinceEpoch( lastMessageMs ?? lastSeenMs, ), isActive: json['isActive'] as bool? ?? true, rawPacket: json['rawPacket'] != null ? Uint8List.fromList(base64Decode(json['rawPacket'] as String)) : null, ); } }