mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-30 14:10:30 +10:00
remove voice code make optimizations. Fix channels race conditions. add reply function
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/channel_message.dart';
|
||||
import '../helpers/smaz.dart';
|
||||
import 'prefs_manager.dart';
|
||||
|
||||
class ChannelMessageStore {
|
||||
static const String _keyPrefix = 'channel_messages_';
|
||||
|
||||
/// Save messages for a specific channel
|
||||
Future<void> saveChannelMessages(int channelIndex, List<ChannelMessage> messages) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final key = '$_keyPrefix$channelIndex';
|
||||
|
||||
// Convert messages to JSON
|
||||
@@ -21,7 +21,7 @@ class ChannelMessageStore {
|
||||
|
||||
/// Load messages for a specific channel
|
||||
Future<List<ChannelMessage>> loadChannelMessages(int channelIndex) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final key = '$_keyPrefix$channelIndex';
|
||||
|
||||
final jsonString = prefs.getString(key);
|
||||
@@ -38,14 +38,14 @@ class ChannelMessageStore {
|
||||
|
||||
/// Clear messages for a specific channel
|
||||
Future<void> clearChannelMessages(int channelIndex) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final key = '$_keyPrefix$channelIndex';
|
||||
await prefs.remove(key);
|
||||
}
|
||||
|
||||
/// Clear all channel messages
|
||||
Future<void> clearAllChannelMessages() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final keys = prefs.getKeys().where((k) => k.startsWith(_keyPrefix));
|
||||
for (var key in keys) {
|
||||
await prefs.remove(key);
|
||||
@@ -67,6 +67,10 @@ class ChannelMessageStore {
|
||||
'pathBytes': base64Encode(msg.pathBytes),
|
||||
'pathVariants': msg.pathVariants.map(base64Encode).toList(),
|
||||
'repeats': msg.repeats.map(_repeatToJson).toList(),
|
||||
'messageId': msg.messageId,
|
||||
'replyToMessageId': msg.replyToMessageId,
|
||||
'replyToSenderName': msg.replyToSenderName,
|
||||
'replyToText': msg.replyToText,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -96,6 +100,10 @@ class ChannelMessageStore {
|
||||
.toList() ??
|
||||
const [],
|
||||
channelIndex: json['channelIndex'] as int?,
|
||||
messageId: json['messageId'] as String?,
|
||||
replyToMessageId: json['replyToMessageId'] as String?,
|
||||
replyToSenderName: json['replyToSenderName'] as String?,
|
||||
replyToText: json['replyToText'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import 'dart:convert';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'prefs_manager.dart';
|
||||
|
||||
class ChannelOrderStore {
|
||||
static const String _key = 'channel_order';
|
||||
|
||||
Future<void> saveChannelOrder(List<int> order) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
await prefs.setString(_key, jsonEncode(order));
|
||||
}
|
||||
|
||||
Future<List<int>> loadChannelOrder() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final raw = prefs.getString(_key);
|
||||
if (raw == null || raw.isEmpty) return [];
|
||||
try {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'prefs_manager.dart';
|
||||
|
||||
class ChannelSettingsStore {
|
||||
static const String _smazKeyPrefix = 'channel_smaz_';
|
||||
|
||||
Future<bool> loadSmazEnabled(int channelIndex) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final key = '$_smazKeyPrefix$channelIndex';
|
||||
return prefs.getBool(key) ?? false;
|
||||
}
|
||||
|
||||
Future<void> saveSmazEnabled(int channelIndex, bool enabled) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final key = '$_smazKeyPrefix$channelIndex';
|
||||
await prefs.setBool(key, enabled);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'dart:convert';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/contact_group.dart';
|
||||
import 'prefs_manager.dart';
|
||||
|
||||
class ContactGroupStore {
|
||||
static const String _key = 'contact_groups';
|
||||
|
||||
Future<List<ContactGroup>> loadGroups() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final raw = prefs.getString(_key);
|
||||
if (raw == null || raw.isEmpty) return [];
|
||||
|
||||
@@ -25,7 +25,7 @@ class ContactGroupStore {
|
||||
}
|
||||
|
||||
Future<void> saveGroups(List<ContactGroup> groups) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final encoded = jsonEncode(groups.map((group) => group.toJson()).toList());
|
||||
await prefs.setString(_key, encoded);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'prefs_manager.dart';
|
||||
|
||||
class ContactSettingsStore {
|
||||
static const String _smazKeyPrefix = 'contact_smaz_';
|
||||
|
||||
Future<bool> loadSmazEnabled(String contactKeyHex) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final key = '$_smazKeyPrefix$contactKeyHex';
|
||||
return prefs.getBool(key) ?? false;
|
||||
}
|
||||
|
||||
Future<void> saveSmazEnabled(String contactKeyHex, bool enabled) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final key = '$_smazKeyPrefix$contactKeyHex';
|
||||
await prefs.setBool(key, enabled);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../models/contact.dart';
|
||||
import 'prefs_manager.dart';
|
||||
|
||||
class ContactStore {
|
||||
static const String _key = 'contacts';
|
||||
|
||||
Future<List<Contact>> loadContacts() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final jsonStr = prefs.getString(_key);
|
||||
if (jsonStr == null) return [];
|
||||
|
||||
@@ -22,7 +21,7 @@ class ContactStore {
|
||||
}
|
||||
|
||||
Future<void> saveContacts(List<Contact> contacts) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final jsonList = contacts.map(_toJson).toList();
|
||||
await prefs.setString(_key, jsonEncode(jsonList));
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/message.dart';
|
||||
import '../helpers/smaz.dart';
|
||||
import 'prefs_manager.dart';
|
||||
|
||||
class MessageStore {
|
||||
static const String _keyPrefix = 'messages_';
|
||||
|
||||
Future<void> saveMessages(String contactKeyHex, List<Message> messages) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final key = '$_keyPrefix$contactKeyHex';
|
||||
final jsonList = messages.map(_messageToJson).toList();
|
||||
await prefs.setString(key, jsonEncode(jsonList));
|
||||
}
|
||||
|
||||
Future<List<Message>> loadMessages(String contactKeyHex) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final key = '$_keyPrefix$contactKeyHex';
|
||||
final jsonString = prefs.getString(key);
|
||||
if (jsonString == null) return [];
|
||||
@@ -29,7 +29,7 @@ class MessageStore {
|
||||
}
|
||||
|
||||
Future<void> clearMessages(String contactKeyHex) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final key = '$_keyPrefix$contactKeyHex';
|
||||
await prefs.remove(key);
|
||||
}
|
||||
@@ -41,10 +41,6 @@ class MessageStore {
|
||||
'timestamp': msg.timestamp.millisecondsSinceEpoch,
|
||||
'isOutgoing': msg.isOutgoing,
|
||||
'isCli': msg.isCli,
|
||||
'isVoice': msg.isVoice,
|
||||
'voicePath': msg.voicePath,
|
||||
'voiceDurationMs': msg.voiceDurationMs,
|
||||
'voiceCodec': msg.voiceCodec,
|
||||
'status': msg.status.index,
|
||||
'messageId': msg.messageId,
|
||||
'retryCount': msg.retryCount,
|
||||
@@ -69,10 +65,6 @@ class MessageStore {
|
||||
timestamp: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int),
|
||||
isOutgoing: json['isOutgoing'] as bool,
|
||||
isCli: isCli,
|
||||
isVoice: json['isVoice'] as bool? ?? false,
|
||||
voicePath: json['voicePath'] as String?,
|
||||
voiceDurationMs: json['voiceDurationMs'] as int?,
|
||||
voiceCodec: json['voiceCodec'] as String?,
|
||||
status: MessageStatus.values[json['status'] as int],
|
||||
messageId: json['messageId'] as String?,
|
||||
retryCount: json['retryCount'] as int? ?? 0,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// Singleton wrapper for SharedPreferences to avoid redundant getInstance() calls.
|
||||
///
|
||||
/// BEFORE: Every storage operation called SharedPreferences.getInstance()
|
||||
/// AFTER: Single getInstance() on app startup, reused throughout lifecycle
|
||||
///
|
||||
/// This eliminates 30+ redundant platform channel calls across the app.
|
||||
class PrefsManager {
|
||||
PrefsManager._();
|
||||
|
||||
static SharedPreferences? _instance;
|
||||
|
||||
/// Initialize the cached instance. Call this once during app startup in main().
|
||||
static Future<void> initialize() async {
|
||||
_instance ??= await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
/// Get the cached SharedPreferences instance.
|
||||
/// Throws StateError if initialize() hasn't been called.
|
||||
static SharedPreferences get instance {
|
||||
if (_instance == null) {
|
||||
throw StateError(
|
||||
'PrefsManager not initialized. Call PrefsManager.initialize() in main() before use.');
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
/// For testing: reset the instance
|
||||
static void reset() {
|
||||
_instance = null;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,30 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'prefs_manager.dart';
|
||||
|
||||
/// Storage for unread message tracking with debounced writes to reduce I/O.
|
||||
class UnreadStore {
|
||||
static const String _contactLastReadKey = 'contact_last_read';
|
||||
static const String _channelLastReadKey = 'channel_last_read';
|
||||
|
||||
// Debounce timers to batch rapid writes
|
||||
Timer? _contactSaveTimer;
|
||||
Timer? _channelSaveTimer;
|
||||
static const Duration _saveDebounceDuration = Duration(milliseconds: 500);
|
||||
|
||||
// Pending write data
|
||||
Map<String, int>? _pendingContactLastRead;
|
||||
Map<int, int>? _pendingChannelLastRead;
|
||||
|
||||
/// Dispose timers when no longer needed
|
||||
void dispose() {
|
||||
_contactSaveTimer?.cancel();
|
||||
_channelSaveTimer?.cancel();
|
||||
}
|
||||
|
||||
Future<Map<String, int>> loadContactLastRead() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final jsonStr = prefs.getString(_contactLastReadKey);
|
||||
if (jsonStr == null) return {};
|
||||
|
||||
@@ -19,14 +36,24 @@ class UnreadStore {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveContactLastRead(Map<String, int> lastReadMs) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonStr = jsonEncode(lastReadMs);
|
||||
await prefs.setString(_contactLastReadKey, jsonStr);
|
||||
/// Save contact last read timestamps with debouncing.
|
||||
/// Writes are delayed by 500ms and batched to reduce I/O operations.
|
||||
void saveContactLastRead(Map<String, int> lastReadMs) {
|
||||
_pendingContactLastRead = lastReadMs;
|
||||
|
||||
// Cancel existing timer
|
||||
_contactSaveTimer?.cancel();
|
||||
|
||||
// Schedule new write
|
||||
_contactSaveTimer = Timer(_saveDebounceDuration, () async {
|
||||
if (_pendingContactLastRead != null) {
|
||||
await _flushContactLastRead();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<Map<int, int>> loadChannelLastRead() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final prefs = PrefsManager.instance;
|
||||
final jsonStr = prefs.getString(_channelLastReadKey);
|
||||
if (jsonStr == null) return {};
|
||||
|
||||
@@ -38,10 +65,48 @@ class UnreadStore {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveChannelLastRead(Map<int, int> lastReadMs) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final asString = lastReadMs.map((key, value) => MapEntry(key.toString(), value));
|
||||
/// Save channel last read timestamps with debouncing.
|
||||
/// Writes are delayed by 500ms and batched to reduce I/O operations.
|
||||
void saveChannelLastRead(Map<int, int> lastReadMs) {
|
||||
_pendingChannelLastRead = lastReadMs;
|
||||
|
||||
_channelSaveTimer?.cancel();
|
||||
|
||||
_channelSaveTimer = Timer(_saveDebounceDuration, () async {
|
||||
if (_pendingChannelLastRead != null) {
|
||||
await _flushChannelLastRead();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _flushContactLastRead() async {
|
||||
if (_pendingContactLastRead == null) return;
|
||||
|
||||
final prefs = PrefsManager.instance;
|
||||
final jsonStr = jsonEncode(_pendingContactLastRead);
|
||||
await prefs.setString(_contactLastReadKey, jsonStr);
|
||||
_pendingContactLastRead = null;
|
||||
}
|
||||
|
||||
Future<void> _flushChannelLastRead() async {
|
||||
if (_pendingChannelLastRead == null) return;
|
||||
|
||||
final prefs = PrefsManager.instance;
|
||||
final asString =
|
||||
_pendingChannelLastRead!.map((key, value) => MapEntry(key.toString(), value));
|
||||
final jsonStr = jsonEncode(asString);
|
||||
await prefs.setString(_channelLastReadKey, jsonStr);
|
||||
_pendingChannelLastRead = null;
|
||||
}
|
||||
|
||||
/// Immediately flush pending writes (call before app termination or disposal)
|
||||
Future<void> flush() async {
|
||||
_contactSaveTimer?.cancel();
|
||||
_channelSaveTimer?.cancel();
|
||||
|
||||
await Future.wait([
|
||||
_flushContactLastRead(),
|
||||
_flushChannelLastRead(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user