mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-08-01 16:15:56 +10:00
Refactor storage classes to include companion's public key (#277)
* Refactor storage classes to include public key handling and improve data loading/saving logic * Remove redundant publicKeyHex handling from ContactDiscoveryStore and fix key reference in saveContacts method * Remove unused app_logger import from ContactDiscoveryStore * Add warning log for empty publicKeyHex in saveChannelMessages method * Add warning log for empty publicKeyHex in clearMessages method * Migrate legacy storage keys to scoped keys across multiple stores * Remove legacy unscoped keys during migration in storage classes * Update lib/storage/contact_store.dart Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -291,6 +291,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
bool get isLoadingChannels => _isLoadingChannels;
|
bool get isLoadingChannels => _isLoadingChannels;
|
||||||
Stream<Uint8List> get receivedFrames => _receivedFramesController.stream;
|
Stream<Uint8List> get receivedFrames => _receivedFramesController.stream;
|
||||||
Uint8List? get selfPublicKey => _selfPublicKey;
|
Uint8List? get selfPublicKey => _selfPublicKey;
|
||||||
|
String get selfPublicKeyHex => pubKeyToHex(_selfPublicKey ?? Uint8List(0));
|
||||||
String? get selfName => _selfName;
|
String? get selfName => _selfName;
|
||||||
double? get selfLatitude => _selfLatitude;
|
double? get selfLatitude => _selfLatitude;
|
||||||
double? get selfLongitude => _selfLongitude;
|
double? get selfLongitude => _selfLongitude;
|
||||||
@@ -663,6 +664,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
// Initialize notification service
|
// Initialize notification service
|
||||||
_notificationService.initialize();
|
_notificationService.initialize();
|
||||||
_loadChannelOrder();
|
_loadChannelOrder();
|
||||||
|
_loadDiscoveredContactCache();
|
||||||
|
|
||||||
// Initialize retry service callbacks
|
// Initialize retry service callbacks
|
||||||
_retryService?.initialize(
|
_retryService?.initialize(
|
||||||
@@ -691,7 +693,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadDiscoveredContactCache() async {
|
Future<void> _loadDiscoveredContactCache() async {
|
||||||
final cached = await _discoveryContactStore.loadContacts();
|
final cached = await _discoveryContactStore.loadContacts();
|
||||||
_discoveredContacts
|
_discoveredContacts
|
||||||
..clear()
|
..clear()
|
||||||
@@ -1193,7 +1195,6 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
|
|
||||||
await _requestDeviceInfo();
|
await _requestDeviceInfo();
|
||||||
_startBatteryPolling();
|
_startBatteryPolling();
|
||||||
unawaited(loadDiscoveredContactCache());
|
|
||||||
|
|
||||||
final gotSelfInfo = await _waitForSelfInfo(
|
final gotSelfInfo = await _waitForSelfInfo(
|
||||||
timeout: const Duration(seconds: 3),
|
timeout: const Duration(seconds: 3),
|
||||||
@@ -2489,6 +2490,27 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
selfName.isNotEmpty) {
|
selfName.isNotEmpty) {
|
||||||
_usbManager.updateConnectedLabel(selfName);
|
_usbManager.updateConnectedLabel(selfName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//set all the stores' public key so they can load the correct data
|
||||||
|
_channelMessageStore.setPublicKeyHex = selfPublicKeyHex;
|
||||||
|
_messageStore.setPublicKeyHex = selfPublicKeyHex;
|
||||||
|
_channelOrderStore.setPublicKeyHex = selfPublicKeyHex;
|
||||||
|
_channelSettingsStore.setPublicKeyHex = selfPublicKeyHex;
|
||||||
|
_contactSettingsStore.setPublicKeyHex = selfPublicKeyHex;
|
||||||
|
_contactStore.setPublicKeyHex = selfPublicKeyHex;
|
||||||
|
_channelStore.setPublicKeyHex = selfPublicKeyHex;
|
||||||
|
_unreadStore.setPublicKeyHex = selfPublicKeyHex;
|
||||||
|
|
||||||
|
// Now that we have self info, we can load all the persisted data for this node
|
||||||
|
_loadChannelOrder();
|
||||||
|
loadContactCache();
|
||||||
|
loadChannelSettings();
|
||||||
|
loadCachedChannels();
|
||||||
|
|
||||||
|
// Load persisted channel messages
|
||||||
|
loadAllChannelMessages();
|
||||||
|
loadUnreadState();
|
||||||
|
|
||||||
_awaitingSelfInfo = false;
|
_awaitingSelfInfo = false;
|
||||||
_selfInfoRetryTimer?.cancel();
|
_selfInfoRetryTimer?.cancel();
|
||||||
_selfInfoRetryTimer = null;
|
_selfInfoRetryTimer = null;
|
||||||
|
|||||||
@@ -106,7 +106,9 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final connector = context.watch<MeshCoreConnector>();
|
final connector = context.watch<MeshCoreConnector>();
|
||||||
|
|
||||||
final channelMessageStore = ChannelMessageStore();
|
final channelMessageStore = ChannelMessageStore();
|
||||||
|
channelMessageStore.setPublicKeyHex = connector.selfPublicKeyHex;
|
||||||
|
|
||||||
// Auto-navigate back to scanner if disconnected
|
// Auto-navigate back to scanner if disconnected
|
||||||
if (!checkConnectionAndNavigate(connector)) {
|
if (!checkConnectionAndNavigate(connector)) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:meshcore_open/utils/app_logger.dart';
|
||||||
|
|
||||||
import '../models/channel_message.dart';
|
import '../models/channel_message.dart';
|
||||||
import '../helpers/smaz.dart';
|
import '../helpers/smaz.dart';
|
||||||
import 'prefs_manager.dart';
|
import 'prefs_manager.dart';
|
||||||
@@ -7,13 +9,25 @@ import 'prefs_manager.dart';
|
|||||||
class ChannelMessageStore {
|
class ChannelMessageStore {
|
||||||
static const String _keyPrefix = 'channel_messages_';
|
static const String _keyPrefix = 'channel_messages_';
|
||||||
|
|
||||||
|
String publicKeyHex = '';
|
||||||
|
set setPublicKeyHex(String value) =>
|
||||||
|
publicKeyHex = value.length > 10 ? value.substring(0, 10) : '';
|
||||||
|
|
||||||
|
String get keyFor => '$_keyPrefix$publicKeyHex';
|
||||||
|
|
||||||
/// Save messages for a specific channel
|
/// Save messages for a specific channel
|
||||||
Future<void> saveChannelMessages(
|
Future<void> saveChannelMessages(
|
||||||
int channelIndex,
|
int channelIndex,
|
||||||
List<ChannelMessage> messages,
|
List<ChannelMessage> messages,
|
||||||
) async {
|
) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn(
|
||||||
|
'Public key hex is not set. Cannot save channel messages.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final key = '$_keyPrefix$channelIndex';
|
final key = '$keyFor$channelIndex';
|
||||||
|
|
||||||
// Convert messages to JSON
|
// Convert messages to JSON
|
||||||
final jsonList = messages.map((msg) => _messageToJson(msg)).toList();
|
final jsonList = messages.map((msg) => _messageToJson(msg)).toList();
|
||||||
@@ -24,11 +38,31 @@ class ChannelMessageStore {
|
|||||||
|
|
||||||
/// Load messages for a specific channel
|
/// Load messages for a specific channel
|
||||||
Future<List<ChannelMessage>> loadChannelMessages(int channelIndex) async {
|
Future<List<ChannelMessage>> loadChannelMessages(int channelIndex) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn(
|
||||||
|
'Public key hex is not set. Cannot load channel messages.',
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final key = '$_keyPrefix$channelIndex';
|
final key = '$keyFor$channelIndex';
|
||||||
|
|
||||||
final jsonString = prefs.getString(key);
|
String? jsonString = prefs.getString(_keyPrefix);
|
||||||
if (jsonString == null) return [];
|
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 channel messages from legacy key $_keyPrefix to scoped key $key',
|
||||||
|
);
|
||||||
|
await prefs.setString(key, legacyJsonString);
|
||||||
|
jsonString = legacyJsonString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (jsonString == null || jsonString.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||||
@@ -42,14 +76,14 @@ class ChannelMessageStore {
|
|||||||
/// Clear messages for a specific channel
|
/// Clear messages for a specific channel
|
||||||
Future<void> clearChannelMessages(int channelIndex) async {
|
Future<void> clearChannelMessages(int channelIndex) async {
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final key = '$_keyPrefix$channelIndex';
|
final key = '$keyFor$channelIndex';
|
||||||
await prefs.remove(key);
|
await prefs.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear all channel messages
|
/// Clear all channel messages
|
||||||
Future<void> clearAllChannelMessages() async {
|
Future<void> clearAllChannelMessages() async {
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final keys = prefs.getKeys().where((k) => k.startsWith(_keyPrefix));
|
final keys = prefs.getKeys().where((k) => k.startsWith(keyFor));
|
||||||
for (var key in keys) {
|
for (var key in keys) {
|
||||||
await prefs.remove(key);
|
await prefs.remove(key);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,49 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import '../utils/app_logger.dart';
|
||||||
import 'prefs_manager.dart';
|
import 'prefs_manager.dart';
|
||||||
|
|
||||||
class ChannelOrderStore {
|
class ChannelOrderStore {
|
||||||
static const String _key = 'channel_order';
|
static const String _keyPrefix = 'channel_order_';
|
||||||
|
|
||||||
|
String publicKeyHex = '';
|
||||||
|
set setPublicKeyHex(String value) =>
|
||||||
|
publicKeyHex = value.length > 10 ? value.substring(0, 10) : '';
|
||||||
|
|
||||||
|
String get keyFor => '$_keyPrefix$publicKeyHex';
|
||||||
|
|
||||||
Future<void> saveChannelOrder(List<int> order) async {
|
Future<void> saveChannelOrder(List<int> order) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot save channel order.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
await prefs.setString(_key, jsonEncode(order));
|
await prefs.setString(keyFor, jsonEncode(order));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<int>> loadChannelOrder() async {
|
Future<List<int>> loadChannelOrder() async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot load channel order.');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final raw = prefs.getString(_key);
|
String? jsonString = prefs.getString(_keyPrefix);
|
||||||
if (raw == null || raw.isEmpty) return [];
|
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 channel order from legacy key $_keyPrefix to scoped key $keyFor',
|
||||||
|
);
|
||||||
|
await prefs.setString(keyFor, legacyJsonString);
|
||||||
|
jsonString = legacyJsonString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (jsonString == null || jsonString.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
final decoded = jsonDecode(raw);
|
final decoded = jsonDecode(jsonString);
|
||||||
if (decoded is List) {
|
if (decoded is List) {
|
||||||
return decoded
|
return decoded
|
||||||
.map((value) => value is int ? value : int.tryParse('$value'))
|
.map((value) => value is int ? value : int.tryParse('$value'))
|
||||||
@@ -24,7 +53,7 @@ class ChannelOrderStore {
|
|||||||
} catch (_) {
|
} catch (_) {
|
||||||
// fall through to legacy parse
|
// fall through to legacy parse
|
||||||
}
|
}
|
||||||
return raw
|
return jsonString
|
||||||
.split(',')
|
.split(',')
|
||||||
.map((value) => int.tryParse(value))
|
.map((value) => int.tryParse(value))
|
||||||
.whereType<int>()
|
.whereType<int>()
|
||||||
|
|||||||
@@ -1,17 +1,49 @@
|
|||||||
|
import '../utils/app_logger.dart';
|
||||||
import 'prefs_manager.dart';
|
import 'prefs_manager.dart';
|
||||||
|
|
||||||
class ChannelSettingsStore {
|
class ChannelSettingsStore {
|
||||||
static const String _smazKeyPrefix = 'channel_smaz_';
|
static const String _keyPrefix = 'channel_smaz_';
|
||||||
|
|
||||||
|
String publicKeyHex = '';
|
||||||
|
set setPublicKeyHex(String value) =>
|
||||||
|
publicKeyHex = value.length > 10 ? value.substring(0, 10) : '';
|
||||||
|
|
||||||
|
String get keyFor => '$_keyPrefix$publicKeyHex';
|
||||||
|
|
||||||
Future<bool> loadSmazEnabled(int channelIndex) async {
|
Future<bool> loadSmazEnabled(int channelIndex) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn(
|
||||||
|
'Public key hex is not set. Cannot load channel settings.',
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final key = '$_smazKeyPrefix$channelIndex';
|
final key = '$keyFor$channelIndex';
|
||||||
|
final oldKey = '$_keyPrefix$channelIndex';
|
||||||
|
bool? enabled = prefs.getBool(key);
|
||||||
|
if (enabled == null) {
|
||||||
|
// Attempt migration from legacy unscoped key on first load
|
||||||
|
enabled = prefs.getBool(oldKey);
|
||||||
|
prefs.remove(oldKey);
|
||||||
|
if (enabled != null) {
|
||||||
|
appLogger.info(
|
||||||
|
'Migrating channel settings from legacy key $oldKey to scoped key $key',
|
||||||
|
);
|
||||||
|
await prefs.setBool(key, enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
return prefs.getBool(key) ?? false;
|
return prefs.getBool(key) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveSmazEnabled(int channelIndex, bool enabled) async {
|
Future<void> saveSmazEnabled(int channelIndex, bool enabled) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn(
|
||||||
|
'Public key hex is not set. Cannot save channel settings.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final key = '$_smazKeyPrefix$channelIndex';
|
final key = '$keyFor$channelIndex';
|
||||||
await prefs.setBool(key, enabled);
|
await prefs.setBool(key, enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,42 @@ import 'dart:convert';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import '../models/channel.dart';
|
import '../models/channel.dart';
|
||||||
|
import '../utils/app_logger.dart';
|
||||||
import 'prefs_manager.dart';
|
import 'prefs_manager.dart';
|
||||||
|
|
||||||
class ChannelStore {
|
class ChannelStore {
|
||||||
static const String _key = 'channels';
|
static const String _keyPrefix = 'channels';
|
||||||
|
String publicKeyHex = '';
|
||||||
|
set setPublicKeyHex(String value) =>
|
||||||
|
publicKeyHex = value.length >= 10 ? value.substring(0, 10) : '';
|
||||||
|
|
||||||
|
String get keyFor => '$_keyPrefix$publicKeyHex';
|
||||||
|
|
||||||
Future<List<Channel>> loadChannels() async {
|
Future<List<Channel>> loadChannels() async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot load channels.');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final jsonStr = prefs.getString(_key);
|
String? jsonString = prefs.getString(_keyPrefix);
|
||||||
if (jsonStr == null) return [];
|
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 channel messages from legacy key $_keyPrefix to scoped key $keyFor',
|
||||||
|
);
|
||||||
|
await prefs.setString(keyFor, legacyJsonString);
|
||||||
|
jsonString = legacyJsonString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (jsonString == null || jsonString.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final jsonList = jsonDecode(jsonStr) as List<dynamic>;
|
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||||
return jsonList
|
return jsonList
|
||||||
.map((entry) => _fromJson(entry as Map<String, dynamic>))
|
.map((entry) => _fromJson(entry as Map<String, dynamic>))
|
||||||
.toList();
|
.toList();
|
||||||
@@ -23,9 +47,13 @@ class ChannelStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveChannels(List<Channel> channels) async {
|
Future<void> saveChannels(List<Channel> channels) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot save channels.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final jsonList = channels.map(_toJson).toList();
|
final jsonList = channels.map(_toJson).toList();
|
||||||
await prefs.setString(_key, jsonEncode(jsonList));
|
await prefs.setString(keyFor, jsonEncode(jsonList));
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _toJson(Channel channel) {
|
Map<String, dynamic> _toJson(Channel channel) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import '../models/community.dart';
|
import '../models/community.dart';
|
||||||
|
import '../utils/app_logger.dart';
|
||||||
import 'prefs_manager.dart';
|
import 'prefs_manager.dart';
|
||||||
|
|
||||||
/// Persists communities to local storage using SharedPreferences.
|
/// Persists communities to local storage using SharedPreferences.
|
||||||
@@ -9,12 +10,34 @@ import 'prefs_manager.dart';
|
|||||||
/// Each community contains its secret K, so this data should
|
/// Each community contains its secret K, so this data should
|
||||||
/// be considered sensitive (though device encryption handles security).
|
/// be considered sensitive (though device encryption handles security).
|
||||||
class CommunityStore {
|
class CommunityStore {
|
||||||
static const String _communitiesKey = 'communities_v1';
|
static const String _keyPrefix = 'communities_v1';
|
||||||
|
|
||||||
|
String publicKeyHex = '';
|
||||||
|
set setPublicKeyHex(String value) =>
|
||||||
|
publicKeyHex = value.length > 10 ? value.substring(0, 10) : '';
|
||||||
|
|
||||||
|
String get keyFor => '$_keyPrefix$publicKeyHex';
|
||||||
|
|
||||||
/// Load all communities from storage
|
/// Load all communities from storage
|
||||||
Future<List<Community>> loadCommunities() async {
|
Future<List<Community>> loadCommunities() async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot load communities.');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final jsonString = prefs.getString(_communitiesKey);
|
String? jsonString = prefs.getString(_keyPrefix);
|
||||||
|
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 communities from legacy key $_keyPrefix to scoped key $keyFor',
|
||||||
|
);
|
||||||
|
await prefs.setString(keyFor, legacyJsonString);
|
||||||
|
jsonString = legacyJsonString;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (jsonString == null || jsonString.isEmpty) {
|
if (jsonString == null || jsonString.isEmpty) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -32,9 +55,13 @@ class CommunityStore {
|
|||||||
|
|
||||||
/// Save all communities to storage
|
/// Save all communities to storage
|
||||||
Future<void> saveCommunities(List<Community> communities) async {
|
Future<void> saveCommunities(List<Community> communities) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot save communities.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final jsonList = communities.map((c) => c.toJson()).toList();
|
final jsonList = communities.map((c) => c.toJson()).toList();
|
||||||
await prefs.setString(_communitiesKey, jsonEncode(jsonList));
|
await prefs.setString(keyFor, jsonEncode(jsonList));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a new community
|
/// Add a new community
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import '../models/discovery_contact.dart';
|
|||||||
import 'prefs_manager.dart';
|
import 'prefs_manager.dart';
|
||||||
|
|
||||||
class ContactDiscoveryStore {
|
class ContactDiscoveryStore {
|
||||||
static const String _key = 'discovered_contacts';
|
static const String _keyPrefix = 'discovered_contacts';
|
||||||
|
|
||||||
Future<List<DiscoveryContact>> loadContacts() async {
|
Future<List<DiscoveryContact>> loadContacts() async {
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final jsonStr = prefs.getString(_key);
|
final jsonStr = prefs.getString(_keyPrefix);
|
||||||
if (jsonStr == null) return [];
|
if (jsonStr == null) return [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -25,7 +25,7 @@ class ContactDiscoveryStore {
|
|||||||
Future<void> saveContacts(List<DiscoveryContact> contacts) async {
|
Future<void> saveContacts(List<DiscoveryContact> contacts) async {
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final jsonList = contacts.map(_toJson).toList();
|
final jsonList = contacts.map(_toJson).toList();
|
||||||
await prefs.setString(_key, jsonEncode(jsonList));
|
await prefs.setString(_keyPrefix, jsonEncode(jsonList));
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _toJson(DiscoveryContact contact) {
|
Map<String, dynamic> _toJson(DiscoveryContact contact) {
|
||||||
|
|||||||
@@ -1,17 +1,42 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import '../models/contact_group.dart';
|
import '../models/contact_group.dart';
|
||||||
|
import '../utils/app_logger.dart';
|
||||||
import 'prefs_manager.dart';
|
import 'prefs_manager.dart';
|
||||||
|
|
||||||
class ContactGroupStore {
|
class ContactGroupStore {
|
||||||
static const String _key = 'contact_groups';
|
static const String _keyPrefix = 'contact_groups';
|
||||||
|
|
||||||
|
String publicKeyHex = '';
|
||||||
|
set setPublicKeyHex(String value) =>
|
||||||
|
publicKeyHex = value.length > 10 ? value.substring(0, 10) : '';
|
||||||
|
|
||||||
|
String get keyFor => '$_keyPrefix$publicKeyHex';
|
||||||
|
|
||||||
Future<List<ContactGroup>> loadGroups() async {
|
Future<List<ContactGroup>> loadGroups() async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot load contact groups.');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final raw = prefs.getString(_key);
|
String? jsonString = prefs.getString(_keyPrefix);
|
||||||
if (raw == null || raw.isEmpty) return [];
|
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 channel messages from legacy key $_keyPrefix to scoped key $keyFor',
|
||||||
|
);
|
||||||
|
await prefs.setString(keyFor, legacyJsonString);
|
||||||
|
jsonString = legacyJsonString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (jsonString == null || jsonString.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final decoded = jsonDecode(raw);
|
final decoded = jsonDecode(jsonString);
|
||||||
if (decoded is List) {
|
if (decoded is List) {
|
||||||
return decoded
|
return decoded
|
||||||
.whereType<Map<String, dynamic>>()
|
.whereType<Map<String, dynamic>>()
|
||||||
@@ -25,8 +50,12 @@ class ContactGroupStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveGroups(List<ContactGroup> groups) async {
|
Future<void> saveGroups(List<ContactGroup> groups) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot save contact groups.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final encoded = jsonEncode(groups.map((group) => group.toJson()).toList());
|
final encoded = jsonEncode(groups.map((group) => group.toJson()).toList());
|
||||||
await prefs.setString(_key, encoded);
|
await prefs.setString(keyFor, encoded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,49 @@
|
|||||||
|
import '../utils/app_logger.dart';
|
||||||
import 'prefs_manager.dart';
|
import 'prefs_manager.dart';
|
||||||
|
|
||||||
class ContactSettingsStore {
|
class ContactSettingsStore {
|
||||||
static const String _smazKeyPrefix = 'contact_smaz_';
|
static const String _keyPrefix = 'contact_smaz_';
|
||||||
|
|
||||||
|
String publicKeyHex = '';
|
||||||
|
set setPublicKeyHex(String value) =>
|
||||||
|
publicKeyHex = value.length > 10 ? value.substring(0, 10) : '';
|
||||||
|
|
||||||
|
String get keyFor => '$_keyPrefix$publicKeyHex';
|
||||||
|
|
||||||
Future<bool> loadSmazEnabled(String contactKeyHex) async {
|
Future<bool> loadSmazEnabled(String contactKeyHex) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn(
|
||||||
|
'Public key hex is not set. Cannot load contact settings.',
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final key = '$_smazKeyPrefix$contactKeyHex';
|
final key = '$keyFor$contactKeyHex';
|
||||||
|
final oldKey = '$_keyPrefix$contactKeyHex';
|
||||||
|
bool? enabled = prefs.getBool(key);
|
||||||
|
if (enabled == null) {
|
||||||
|
// Attempt migration from legacy unscoped key on first load
|
||||||
|
enabled = prefs.getBool(oldKey);
|
||||||
|
prefs.remove(oldKey);
|
||||||
|
if (enabled != null) {
|
||||||
|
appLogger.info(
|
||||||
|
'Migrating contact settings from legacy key $oldKey to scoped key $key',
|
||||||
|
);
|
||||||
|
await prefs.setBool(key, enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
return prefs.getBool(key) ?? false;
|
return prefs.getBool(key) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveSmazEnabled(String contactKeyHex, bool enabled) async {
|
Future<void> saveSmazEnabled(String contactKeyHex, bool enabled) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn(
|
||||||
|
'Public key hex is not set. Cannot save contact settings.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final key = '$_smazKeyPrefix$contactKeyHex';
|
final key = '$keyFor$contactKeyHex';
|
||||||
await prefs.setBool(key, enabled);
|
await prefs.setBool(key, enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,43 @@ import 'dart:convert';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import '../models/contact.dart';
|
import '../models/contact.dart';
|
||||||
|
import '../utils/app_logger.dart';
|
||||||
import 'prefs_manager.dart';
|
import 'prefs_manager.dart';
|
||||||
|
|
||||||
class ContactStore {
|
class ContactStore {
|
||||||
static const String _key = 'contacts';
|
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<List<Contact>> loadContacts() async {
|
Future<List<Contact>> loadContacts() async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot load contacts.');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final jsonStr = prefs.getString(_key);
|
String? jsonString = prefs.getString(keyFor);
|
||||||
if (jsonStr == null) return [];
|
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) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final jsonList = jsonDecode(jsonStr) as List<dynamic>;
|
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||||
return jsonList
|
return jsonList
|
||||||
.map((entry) => _fromJson(entry as Map<String, dynamic>))
|
.map((entry) => _fromJson(entry as Map<String, dynamic>))
|
||||||
.toList();
|
.toList();
|
||||||
@@ -23,9 +48,13 @@ class ContactStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveContacts(List<Contact> contacts) async {
|
Future<void> saveContacts(List<Contact> contacts) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot save contacts.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final jsonList = contacts.map(_toJson).toList();
|
final jsonList = contacts.map(_toJson).toList();
|
||||||
await prefs.setString(_key, jsonEncode(jsonList));
|
await prefs.setString(keyFor, jsonEncode(jsonList));
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _toJson(Contact contact) {
|
Map<String, dynamic> _toJson(Contact contact) {
|
||||||
|
|||||||
@@ -2,26 +2,56 @@ import 'dart:convert';
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import '../models/message.dart';
|
import '../models/message.dart';
|
||||||
import '../helpers/smaz.dart';
|
import '../helpers/smaz.dart';
|
||||||
|
import '../utils/app_logger.dart';
|
||||||
import 'prefs_manager.dart';
|
import 'prefs_manager.dart';
|
||||||
|
|
||||||
class MessageStore {
|
class MessageStore {
|
||||||
static const String _keyPrefix = 'messages_';
|
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(
|
Future<void> saveMessages(
|
||||||
String contactKeyHex,
|
String contactKeyHex,
|
||||||
List<Message> messages,
|
List<Message> messages,
|
||||||
) async {
|
) async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot save messages.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final key = '$_keyPrefix$contactKeyHex';
|
final key = '$keyFor$contactKeyHex';
|
||||||
final jsonList = messages.map(_messageToJson).toList();
|
final jsonList = messages.map(_messageToJson).toList();
|
||||||
await prefs.setString(key, jsonEncode(jsonList));
|
await prefs.setString(key, jsonEncode(jsonList));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Message>> loadMessages(String contactKeyHex) async {
|
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 prefs = PrefsManager.instance;
|
||||||
final key = '$_keyPrefix$contactKeyHex';
|
final key = '$keyFor$contactKeyHex';
|
||||||
final jsonString = prefs.getString(key);
|
final oldKey = '$_keyPrefix$contactKeyHex';
|
||||||
if (jsonString == null) return [];
|
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) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||||
@@ -32,8 +62,12 @@ class MessageStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clearMessages(String contactKeyHex) async {
|
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 prefs = PrefsManager.instance;
|
||||||
final key = '$_keyPrefix$contactKeyHex';
|
final key = '$keyFor$contactKeyHex';
|
||||||
await prefs.remove(key);
|
await prefs.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import '../utils/app_logger.dart';
|
||||||
import 'prefs_manager.dart';
|
import 'prefs_manager.dart';
|
||||||
|
|
||||||
/// Storage for unread message tracking with debounced writes to reduce I/O.
|
/// Storage for unread message tracking with debounced writes to reduce I/O.
|
||||||
class UnreadStore {
|
class UnreadStore {
|
||||||
static const String _contactUnreadCountKey = 'contact_unread_count';
|
static const String _keyPrefix = 'contact_unread_count';
|
||||||
|
|
||||||
|
String publicKeyHex = '';
|
||||||
|
set setPublicKeyHex(String value) =>
|
||||||
|
publicKeyHex = value.length >= 10 ? value.substring(0, 10) : '';
|
||||||
|
|
||||||
|
String get keyFor => '$_keyPrefix$publicKeyHex';
|
||||||
|
|
||||||
// Debounce timers to batch rapid writes
|
// Debounce timers to batch rapid writes
|
||||||
Timer? _contactUnreadSaveTimer;
|
Timer? _contactUnreadSaveTimer;
|
||||||
@@ -20,12 +27,30 @@ class UnreadStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, int>> loadContactUnreadCount() async {
|
Future<Map<String, int>> loadContactUnreadCount() async {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot load unread counts.');
|
||||||
|
return {};
|
||||||
|
}
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final jsonStr = prefs.getString(_contactUnreadCountKey);
|
String? jsonString = prefs.getString(_keyPrefix);
|
||||||
if (jsonStr == null) return {};
|
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 channel messages from legacy key $_keyPrefix to scoped key $keyFor',
|
||||||
|
);
|
||||||
|
await prefs.setString(keyFor, legacyJsonString);
|
||||||
|
jsonString = legacyJsonString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (jsonString == null || jsonString.isEmpty) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final json = jsonDecode(jsonStr) as Map<String, dynamic>;
|
final json = jsonDecode(jsonString) as Map<String, dynamic>;
|
||||||
return json.map((key, value) => MapEntry(key, value as int));
|
return json.map((key, value) => MapEntry(key, value as int));
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return {};
|
return {};
|
||||||
@@ -33,6 +58,10 @@ class UnreadStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void saveContactUnreadCount(Map<String, int> counts) {
|
void saveContactUnreadCount(Map<String, int> counts) {
|
||||||
|
if (publicKeyHex.isEmpty) {
|
||||||
|
appLogger.warn('Public key hex is not set. Cannot save unread counts.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
_pendingContactUnreadCount = counts;
|
_pendingContactUnreadCount = counts;
|
||||||
|
|
||||||
_contactUnreadSaveTimer?.cancel();
|
_contactUnreadSaveTimer?.cancel();
|
||||||
@@ -49,7 +78,7 @@ class UnreadStore {
|
|||||||
|
|
||||||
final prefs = PrefsManager.instance;
|
final prefs = PrefsManager.instance;
|
||||||
final jsonStr = jsonEncode(_pendingContactUnreadCount);
|
final jsonStr = jsonEncode(_pendingContactUnreadCount);
|
||||||
await prefs.setString(_contactUnreadCountKey, jsonStr);
|
await prefs.setString(keyFor, jsonStr);
|
||||||
_pendingContactUnreadCount = null;
|
_pendingContactUnreadCount = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user