meshcore-open/lib/storage/unread_store.dart
Winston Lowe 81758adc61
Dev discovery (#291)
* Refactor contact handling: replace DiscoveryContact with Contact, update related methods and settings

* Enhance contact handling: include latitude, longitude, and last modified timestamp in contact updates; refactor path handling to accommodate discovered contacts across multiple screens

* Enhance SNRIndicator: include discovered contacts in name resolution for repeaters

* Refactor path handling: replace addReturnPath with buildPath to improve path construction logic and handle target contact types

* Update lib/screens/map_screen.dart

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add localization for "Show Discovery Contacts" in multiple languages and refactor location plausibility check in map screen

* Enhance contact management: update discovered contacts' active status and improve contact handling with flags and raw packet data

* Refactor ChannelsScreen: pass ChannelMessageStore to buildExpandedContent and ensure messages are cleared after channel creation

* Update MapScreen: adjust label zoom threshold and refactor guessed marker building to include labels

* Refactor ChannelsScreen: change channelMessageStore to a private getter and update its usage in buildExpandedContent calls

* Enhance location plausibility check: add latitude and longitude bounds to ensure valid coordinates

* Update lib/connector/meshcore_connector.dart

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor MeshCoreConnector and related stores: update discovered contacts handling, migrate legacy keys, and set public key in community store

* Refactor MeshCoreConnector and ChannelsScreen: update discovered contacts handling and set public key in community store; enhance location plausibility check in MapScreen

* Update CMD_ADD_UPDATE_CONTACT frame format to include optional latitude and longitude fields

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-12 23:08:46 -07:00

93 lines
2.8 KiB
Dart

import 'dart:async';
import 'dart:convert';
import '../utils/app_logger.dart';
import 'prefs_manager.dart';
/// Storage for unread message tracking with debounced writes to reduce I/O.
class UnreadStore {
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
Timer? _contactUnreadSaveTimer;
static const Duration _saveDebounceDuration = Duration(milliseconds: 500);
// Pending write data
Map<String, int>? _pendingContactUnreadCount;
/// Dispose timers when no longer needed
void dispose() {
_contactUnreadSaveTimer?.cancel();
}
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;
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 channel messages 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 json = jsonDecode(jsonString) as Map<String, dynamic>;
return json.map((key, value) => MapEntry(key, value as int));
} catch (_) {
return {};
}
}
void saveContactUnreadCount(Map<String, int> counts) {
if (publicKeyHex.isEmpty) {
appLogger.warn('Public key hex is not set. Cannot save unread counts.');
return;
}
_pendingContactUnreadCount = counts;
_contactUnreadSaveTimer?.cancel();
_contactUnreadSaveTimer = Timer(_saveDebounceDuration, () async {
if (_pendingContactUnreadCount != null) {
await _flushContactUnreadCount();
}
});
}
Future<void> _flushContactUnreadCount() async {
if (_pendingContactUnreadCount == null) return;
final prefs = PrefsManager.instance;
final jsonStr = jsonEncode(_pendingContactUnreadCount);
await prefs.setString(keyFor, jsonStr);
_pendingContactUnreadCount = null;
}
/// Immediately flush pending writes (call before app termination or disposal)
Future<void> flush() async {
_contactUnreadSaveTimer?.cancel();
await _flushContactUnreadCount();
}
}