feat: Enhance companion connection features and UI updates

- Added functionality to load and restore the last companion's scope on app startup.
- Implemented caching mechanisms for contacts, channels, and messages related to the current companion.
- Updated UI to reflect connection status, including disabling message input when disconnected.
- Introduced new dialog prompts to inform users when they need to connect to a companion for accessing features.
- Refactored navigation logic to improve user experience when disconnected, directing users to the scanner screen.
- Added localization strings for new companion connection prompts in multiple languages.
This commit is contained in:
Winston Lowe
2026-05-10 16:02:58 -07:00
parent 209fee48ca
commit d6ed8c5f13
45 changed files with 320 additions and 113 deletions
+86 -21
View File
@@ -40,6 +40,7 @@ import '../storage/contact_discovery_store.dart';
import '../storage/contact_settings_store.dart';
import '../storage/contact_store.dart';
import '../storage/message_store.dart';
import '../storage/prefs_manager.dart';
import '../storage/unread_store.dart';
import '../utils/app_logger.dart';
import '../utils/battery_utils.dart';
@@ -124,6 +125,8 @@ class MeshCoreRadioStateSnapshot {
class MeshCoreConnector extends ChangeNotifier {
// Message windowing to limit memory usage
static const int _messageWindowSize = 200;
static const String _lastCompanionPublicKeyPref =
'last_companion_public_key_hex';
MeshCoreConnectionState _state = MeshCoreConnectionState.disconnected;
BluetoothDevice? _device;
@@ -478,6 +481,9 @@ class MeshCoreConnector extends ChangeNotifier {
}
List<Message> getMessages(Contact contact) {
if (!_loadedConversationKeys.contains(contact.publicKeyHex)) {
unawaited(_loadMessagesForContact(contact.publicKeyHex));
}
return _conversations[contact.publicKeyHex] ?? [];
}
@@ -682,8 +688,14 @@ class MeshCoreConnector extends ChangeNotifier {
}
Future<void> loadCachedChannels() async {
_cachedChannels = await _channelStore.loadChannels();
_recalculateCachedChannelsUnreadTotal();
final loaded = await _channelStore.loadChannels();
_cachedChannels = loaded;
if (_channels.isEmpty && loaded.isNotEmpty) {
_channels
..clear()
..addAll(loaded);
notifyListeners();
}
}
void setActiveContact(String? contactKeyHex) {
@@ -947,6 +959,69 @@ class MeshCoreConnector extends ChangeNotifier {
}
}
Future<void> loadAllCachedDataForCurrentCompanion() async {
await loadContactCache();
await _loadDiscoveredContactCache();
await loadChannelSettings();
await loadCachedChannels();
await loadAllChannelMessages();
await loadUnreadState();
}
Future<void> restoreLastCompanionScope() async {
final prefs = PrefsManager.instance;
final lastCompanionPublicKeyHex = prefs.getString(
_lastCompanionPublicKeyPref,
);
if (lastCompanionPublicKeyHex == null ||
lastCompanionPublicKeyHex.trim().isEmpty) {
return;
}
_setScopedStorePublicKey(lastCompanionPublicKeyHex);
}
Future<void> loadDiscoveredContactCache() => _loadDiscoveredContactCache();
void _setScopedStorePublicKey(String publicKeyHex) {
_channelMessageStore.setPublicKeyHex = publicKeyHex;
_messageStore.setPublicKeyHex = publicKeyHex;
_channelOrderStore.setPublicKeyHex = publicKeyHex;
_channelSettingsStore.setPublicKeyHex = publicKeyHex;
_contactSettingsStore.setPublicKeyHex = publicKeyHex;
_contactStore.setPublicKeyHex = publicKeyHex;
_channelStore.setPublicKeyHex = publicKeyHex;
_unreadStore.setPublicKeyHex = publicKeyHex;
}
void _clearCachedCompanionData() {
_contacts.clear();
_discoveredContacts.clear();
_conversations.clear();
_loadedConversationKeys.clear();
_channelMessages.clear();
_cachedChannels.clear();
_knownContactKeys.clear();
_contactUnreadCount.clear();
_unreadStateLoaded = false;
notifyListeners();
}
Future<void> _persistLastCompanionScope() async {
final keyHex = selfPublicKeyHex;
if (keyHex.isEmpty) return;
final prefs = PrefsManager.instance;
await prefs.setString(_lastCompanionPublicKeyPref, keyHex);
}
Future<void> _reloadOfflineCachesForLastCompanion() async {
if (_state != MeshCoreConnectionState.disconnected) {
return;
}
await restoreLastCompanionScope();
await loadContactCache();
await _loadDiscoveredContactCache();
}
Future<void> _loadDiscoveredContactCache() async {
final cached = await _discoveryContactStore.loadContacts();
_discoveredContacts
@@ -1484,6 +1559,7 @@ class MeshCoreConnector extends ChangeNotifier {
_cancelReconnectTimer();
_manualDisconnect = false;
_resetConnectionHandshakeState();
_clearCachedCompanionData();
_activeTransport = MeshCoreTransportType.usb;
_setState(MeshCoreConnectionState.connecting);
@@ -1565,6 +1641,7 @@ class MeshCoreConnector extends ChangeNotifier {
_cancelReconnectTimer();
_manualDisconnect = false;
_resetConnectionHandshakeState();
_clearCachedCompanionData();
_activeTransport = MeshCoreTransportType.tcp;
_setState(MeshCoreConnectionState.connecting);
@@ -1699,6 +1776,7 @@ class MeshCoreConnector extends ChangeNotifier {
_activeTransport = MeshCoreTransportType.bluetooth;
await stopScan();
_clearCachedCompanionData();
_setState(MeshCoreConnectionState.connecting);
_device = device;
_deviceId = device.remoteId.toString();
@@ -2450,6 +2528,7 @@ class MeshCoreConnector extends ChangeNotifier {
'Disconnect complete transport=$transportLabel manual=$manual',
tag: 'Connection',
);
unawaited(_reloadOfflineCachesForLastCompanion());
if (!manual && transportAtDisconnect == MeshCoreTransportType.bluetooth) {
_scheduleReconnect();
}
@@ -3834,26 +3913,12 @@ class MeshCoreConnector extends ChangeNotifier {
return;
}
//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;
// Set scoped stores to this companion and remember it for next launch.
_setScopedStorePublicKey(selfPublicKeyHex);
unawaited(_persistLastCompanionScope());
// 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();
_loadDiscoveredContactCache();
// Now that we have self info, we can load all the persisted data for this node.
unawaited(loadAllCachedDataForCurrentCompanion());
_awaitingSelfInfo = false;
_selfInfoRetryTimer?.cancel();