Compare commits

...

3 Commits

Author SHA1 Message Date
Winston Lowe d104edd65c added Groq and openAI APIs 2026-05-10 16:04:39 -07:00
Winston Lowe 094d5d2706 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.
2026-05-10 16:02:58 -07:00
Winston Lowe 396f70c994 Add byteCount field to PathRecord class with default value 2026-05-10 15:57:33 -07:00
47 changed files with 1250 additions and 530 deletions
+86 -20
View File
@@ -40,6 +40,7 @@ import '../storage/contact_discovery_store.dart';
import '../storage/contact_settings_store.dart'; import '../storage/contact_settings_store.dart';
import '../storage/contact_store.dart'; import '../storage/contact_store.dart';
import '../storage/message_store.dart'; import '../storage/message_store.dart';
import '../storage/prefs_manager.dart';
import '../storage/unread_store.dart'; import '../storage/unread_store.dart';
import '../utils/app_logger.dart'; import '../utils/app_logger.dart';
import '../utils/battery_utils.dart'; import '../utils/battery_utils.dart';
@@ -124,6 +125,8 @@ class MeshCoreRadioStateSnapshot {
class MeshCoreConnector extends ChangeNotifier { class MeshCoreConnector extends ChangeNotifier {
// Message windowing to limit memory usage // Message windowing to limit memory usage
static const int _messageWindowSize = 200; static const int _messageWindowSize = 200;
static const String _lastCompanionPublicKeyPref =
'last_companion_public_key_hex';
MeshCoreConnectionState _state = MeshCoreConnectionState.disconnected; MeshCoreConnectionState _state = MeshCoreConnectionState.disconnected;
BluetoothDevice? _device; BluetoothDevice? _device;
@@ -476,6 +479,9 @@ class MeshCoreConnector extends ChangeNotifier {
} }
List<Message> getMessages(Contact contact) { List<Message> getMessages(Contact contact) {
if (!_loadedConversationKeys.contains(contact.publicKeyHex)) {
unawaited(_loadMessagesForContact(contact.publicKeyHex));
}
return _conversations[contact.publicKeyHex] ?? []; return _conversations[contact.publicKeyHex] ?? [];
} }
@@ -653,7 +659,14 @@ class MeshCoreConnector extends ChangeNotifier {
} }
Future<void> loadCachedChannels() async { Future<void> loadCachedChannels() async {
_cachedChannels = await _channelStore.loadChannels(); final loaded = await _channelStore.loadChannels();
_cachedChannels = loaded;
if (_channels.isEmpty && loaded.isNotEmpty) {
_channels
..clear()
..addAll(loaded);
notifyListeners();
}
} }
void setActiveContact(String? contactKeyHex) { void setActiveContact(String? contactKeyHex) {
@@ -913,6 +926,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 { Future<void> _loadDiscoveredContactCache() async {
final cached = await _discoveryContactStore.loadContacts(); final cached = await _discoveryContactStore.loadContacts();
_discoveredContacts _discoveredContacts
@@ -1450,6 +1526,7 @@ class MeshCoreConnector extends ChangeNotifier {
_cancelReconnectTimer(); _cancelReconnectTimer();
_manualDisconnect = false; _manualDisconnect = false;
_resetConnectionHandshakeState(); _resetConnectionHandshakeState();
_clearCachedCompanionData();
_activeTransport = MeshCoreTransportType.usb; _activeTransport = MeshCoreTransportType.usb;
_setState(MeshCoreConnectionState.connecting); _setState(MeshCoreConnectionState.connecting);
@@ -1531,6 +1608,7 @@ class MeshCoreConnector extends ChangeNotifier {
_cancelReconnectTimer(); _cancelReconnectTimer();
_manualDisconnect = false; _manualDisconnect = false;
_resetConnectionHandshakeState(); _resetConnectionHandshakeState();
_clearCachedCompanionData();
_activeTransport = MeshCoreTransportType.tcp; _activeTransport = MeshCoreTransportType.tcp;
_setState(MeshCoreConnectionState.connecting); _setState(MeshCoreConnectionState.connecting);
@@ -1665,6 +1743,7 @@ class MeshCoreConnector extends ChangeNotifier {
_activeTransport = MeshCoreTransportType.bluetooth; _activeTransport = MeshCoreTransportType.bluetooth;
await stopScan(); await stopScan();
_clearCachedCompanionData();
_setState(MeshCoreConnectionState.connecting); _setState(MeshCoreConnectionState.connecting);
_device = device; _device = device;
_deviceId = device.remoteId.toString(); _deviceId = device.remoteId.toString();
@@ -2416,6 +2495,7 @@ class MeshCoreConnector extends ChangeNotifier {
'Disconnect complete transport=$transportLabel manual=$manual', 'Disconnect complete transport=$transportLabel manual=$manual',
tag: 'Connection', tag: 'Connection',
); );
unawaited(_reloadOfflineCachesForLastCompanion());
if (!manual && transportAtDisconnect == MeshCoreTransportType.bluetooth) { if (!manual && transportAtDisconnect == MeshCoreTransportType.bluetooth) {
_scheduleReconnect(); _scheduleReconnect();
} }
@@ -3796,26 +3876,12 @@ class MeshCoreConnector extends ChangeNotifier {
return; return;
} }
//set all the stores' public key so they can load the correct data // Set scoped stores to this companion and remember it for next launch.
_channelMessageStore.setPublicKeyHex = selfPublicKeyHex; _setScopedStorePublicKey(selfPublicKeyHex);
_messageStore.setPublicKeyHex = selfPublicKeyHex; unawaited(_persistLastCompanionScope());
_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 // Now that we have self info, we can load all the persisted data for this node.
_loadChannelOrder(); unawaited(loadAllCachedDataForCurrentCompanion());
loadContactCache();
loadChannelSettings();
loadCachedChannels();
// Load persisted channel messages
loadAllChannelMessages();
loadUnreadState();
_loadDiscoveredContactCache();
_awaitingSelfInfo = false; _awaitingSelfInfo = false;
_selfInfoRetryTimer?.cancel(); _selfInfoRetryTimer?.cancel();
+2 -1
View File
@@ -2311,5 +2311,6 @@
"settings_companionDebugLogSubtitle": "Команди, отговори и сурови данни за протоколите BLE/TCP/USB", "settings_companionDebugLogSubtitle": "Команди, отговори и сурови данни за протоколите BLE/TCP/USB",
"chat_newMessages": "Нови съобщения", "chat_newMessages": "Нови съобщения",
"settings_companionDebugLog": "Лог за отстраняване на грешки (за съпътстваща програма)", "settings_companionDebugLog": "Лог за отстраняване на грешки (за съпътстваща програма)",
"repeater_chanUtil": "Използване на канала" "repeater_chanUtil": "Използване на канала",
"contact_connectCompanion": "Свържете се с придружител, за да получите достъп до функциите на ретранслатора и сървъра за стаи."
} }
+2 -1
View File
@@ -2339,5 +2339,6 @@
"chat_newMessages": "Neue Nachrichten", "chat_newMessages": "Neue Nachrichten",
"settings_companionDebugLog": "Debug-Protokoll für die Begleitsoftware", "settings_companionDebugLog": "Debug-Protokoll für die Begleitsoftware",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB-Befehle, Antworten und Rohdaten", "settings_companionDebugLogSubtitle": "BLE/TCP/USB-Befehle, Antworten und Rohdaten",
"repeater_chanUtil": "Nutzung des Kanals" "repeater_chanUtil": "Nutzung des Kanals",
"contact_connectCompanion": "Verbinden Sie sich mit einem Companion, um auf die Funktionen des Repeaters und des Raumservers zuzugreifen."
} }
+2 -1
View File
@@ -2366,5 +2366,6 @@
"contact_typeRepeater": "Repeater", "contact_typeRepeater": "Repeater",
"contact_typeRoom": "Room", "contact_typeRoom": "Room",
"contact_typeSensor": "Sensor", "contact_typeSensor": "Sensor",
"contact_typeUnknown": "Unknown" "contact_typeUnknown": "Unknown",
"contact_connectCompanion": "Connect to a companion to access repeater and room server features."
} }
+2 -1
View File
@@ -2339,5 +2339,6 @@
"chat_newMessages": "Nuevos mensajes", "chat_newMessages": "Nuevos mensajes",
"settings_companionDebugLogSubtitle": "Comandos, respuestas y datos brutos para protocolos BLE/TCP/USB", "settings_companionDebugLogSubtitle": "Comandos, respuestas y datos brutos para protocolos BLE/TCP/USB",
"chat_markAsUnread": "Marcar como no leído", "chat_markAsUnread": "Marcar como no leído",
"repeater_chanUtil": "Utilización del canal" "repeater_chanUtil": "Utilización del canal",
"contact_connectCompanion": "Conéctate a un compañero para acceder a las funciones de repetidor y servidor de sala."
} }
+2 -1
View File
@@ -2318,5 +2318,6 @@
"chat_markAsUnread": "Signaler comme non lu", "chat_markAsUnread": "Signaler comme non lu",
"chat_newMessages": "Nouveaux messages", "chat_newMessages": "Nouveaux messages",
"settings_companionDebugLogSubtitle": "Commandes, réponses et données brutes pour les protocoles BLE/TCP/USB", "settings_companionDebugLogSubtitle": "Commandes, réponses et données brutes pour les protocoles BLE/TCP/USB",
"repeater_chanUtil": "Utilisation du canal" "repeater_chanUtil": "Utilisation du canal",
"contact_connectCompanion": "Connectez-vous à un compagnon pour accéder aux fonctionnalités de répéteur et de serveur de salle."
} }
+2 -1
View File
@@ -2349,5 +2349,6 @@
"chat_newMessages": "Új üzenetek", "chat_newMessages": "Új üzenetek",
"settings_companionDebugLog": "Párhuzamos hibakeresési napló", "settings_companionDebugLog": "Párhuzamos hibakeresési napló",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB parancsok, válaszok és alapvető adatok", "settings_companionDebugLogSubtitle": "BLE/TCP/USB parancsok, válaszok és alapvető adatok",
"repeater_chanUtil": "Csatorna-használat" "repeater_chanUtil": "Csatorna-használat",
"contact_connectCompanion": "Csatlakozzon egy kísérőhöz a ismétlő és szobaszerver funkciók eléréséhez."
} }
+2 -1
View File
@@ -2311,5 +2311,6 @@
"settings_companionDebugLog": "Registro di debug per il supporto", "settings_companionDebugLog": "Registro di debug per il supporto",
"chat_newMessages": "Nuovi messaggi", "chat_newMessages": "Nuovi messaggi",
"chat_markAsUnread": "Segna come non letto", "chat_markAsUnread": "Segna come non letto",
"repeater_chanUtil": "Utilizzo del canale" "repeater_chanUtil": "Utilizzo del canale",
"contact_connectCompanion": "Connettiti a un dispositivo companion per accedere alle funzionalità di ripetitore e server stanza."
} }
+2 -1
View File
@@ -2349,5 +2349,6 @@
"settings_companionDebugLog": "同伴デバッグログ", "settings_companionDebugLog": "同伴デバッグログ",
"chat_newMessages": "新しいメッセージ", "chat_newMessages": "新しいメッセージ",
"chat_markAsUnread": "未読としてマークする", "chat_markAsUnread": "未読としてマークする",
"repeater_chanUtil": "チャンネルの利用状況" "repeater_chanUtil": "チャンネルの利用状況",
"contact_connectCompanion": "コネクトしてリピーターとルームサーバー機能にアクセス"
} }
+2 -1
View File
@@ -2349,5 +2349,6 @@
"chat_newMessages": "새로운 메시지", "chat_newMessages": "새로운 메시지",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB 명령어, 응답 및 원시 데이터", "settings_companionDebugLogSubtitle": "BLE/TCP/USB 명령어, 응답 및 원시 데이터",
"chat_markAsUnread": "미리 읽지 않음으로 표시", "chat_markAsUnread": "미리 읽지 않음으로 표시",
"repeater_chanUtil": "채널 활용도" "repeater_chanUtil": "채널 활용도",
"contact_connectCompanion": "리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요."
} }
+6
View File
@@ -7335,6 +7335,12 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Unknown'** /// **'Unknown'**
String get contact_typeUnknown; String get contact_typeUnknown;
/// No description provided for @contact_connectCompanion.
///
/// In en, this message translates to:
/// **'Connect to a companion to access repeater and room server features.'**
String get contact_connectCompanion;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate
+4
View File
@@ -4288,4 +4288,8 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Свържете се с придружител, за да получите достъп до функциите на ретранслатора и сървъра за стаи.';
} }
+4
View File
@@ -4305,4 +4305,8 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Verbinden Sie sich mit einem Companion, um auf die Funktionen des Repeaters und des Raumservers zuzugreifen.';
} }
+4
View File
@@ -4210,4 +4210,8 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Connect to a companion to access repeater and room server features.';
} }
+4
View File
@@ -4292,4 +4292,8 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Conéctate a un compañero para acceder a las funciones de repetidor y servidor de sala.';
} }
+4
View File
@@ -4321,4 +4321,8 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Connectez-vous à un compagnon pour accéder aux fonctionnalités de répéteur et de serveur de salle.';
} }
+4
View File
@@ -4309,4 +4309,8 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Csatlakozzon egy kísérőhöz a ismétlő és szobaszerver funkciók eléréséhez.';
} }
+4
View File
@@ -4297,4 +4297,8 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Connettiti a un dispositivo companion per accedere alle funzionalità di ripetitore e server stanza.';
} }
+3
View File
@@ -4063,4 +4063,7 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion => 'コネクトしてリピーターとルームサーバー機能にアクセス';
} }
+3
View File
@@ -4064,4 +4064,7 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion => '리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요.';
} }
+4
View File
@@ -4273,4 +4273,8 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Maak verbinding met een companion om repeater- en kamerserverfuncties te gebruiken.';
} }
+4
View File
@@ -4309,4 +4309,8 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Połącz się z towarzyszem, aby uzyskać dostęp do funkcji powtarzacza i serwera pokoi.';
} }
+4
View File
@@ -4285,4 +4285,8 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Conecte-se a um dispositivo companion para acessar as funcionalidades de repetidor e servidor de salas.';
} }
+4
View File
@@ -4303,4 +4303,8 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Неизвестно'; String get contact_typeUnknown => 'Неизвестно';
@override
String get contact_connectCompanion =>
'Подключитесь к компаньону, чтобы получить доступ к функциям ретранслятора и сервера комнат.';
} }
+4
View File
@@ -4269,4 +4269,8 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Pripojte sa k sprievodcovi a získajte prístup k funkciám opakovača a serveru miestností.';
} }
+4
View File
@@ -4267,4 +4267,8 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Povežite se s spremljevalnikom za dostop do funkcij ponavljalnika in strežnika sob.';
} }
+4
View File
@@ -4241,4 +4241,8 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion =>
'Anslut till en sällskapstjänst för att komma åt upprepning och rumsserverfunktioner.';
} }
+4
View File
@@ -4304,4 +4304,8 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Невідомо'; String get contact_typeUnknown => 'Невідомо';
@override
String get contact_connectCompanion =>
'Підключіться до супутнього пристрою, щоб отримати доступ до функцій ретранслятора та сервера кімнат.';
} }
+3
View File
@@ -3938,4 +3938,7 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get contact_typeUnknown => 'Unknown'; String get contact_typeUnknown => 'Unknown';
@override
String get contact_connectCompanion => '连接伴机以访问中继器和房间服务器功能。';
} }
+2 -1
View File
@@ -2311,5 +2311,6 @@
"chat_newMessages": "Nieuwe berichten", "chat_newMessages": "Nieuwe berichten",
"chat_markAsUnread": "Markeer als ongelezen", "chat_markAsUnread": "Markeer als ongelezen",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB commando's, antwoorden en ruwe data", "settings_companionDebugLogSubtitle": "BLE/TCP/USB commando's, antwoorden en ruwe data",
"repeater_chanUtil": "Gebruik van het kanaal" "repeater_chanUtil": "Gebruik van het kanaal",
"contact_connectCompanion": "Maak verbinding met een companion om repeater- en kamerserverfuncties te gebruiken."
} }
+2 -1
View File
@@ -2349,5 +2349,6 @@
"settings_companionDebugLogSubtitle": "Polecenia, odpowiedzi i surowe dane związane z protokołami BLE/TCP/USB", "settings_companionDebugLogSubtitle": "Polecenia, odpowiedzi i surowe dane związane z protokołami BLE/TCP/USB",
"chat_markAsUnread": "Oznacz jako nieprzeczytane", "chat_markAsUnread": "Oznacz jako nieprzeczytane",
"settings_companionDebugLog": "Log debugowania (dla pomocy w rozwiązywaniu problemów)", "settings_companionDebugLog": "Log debugowania (dla pomocy w rozwiązywaniu problemów)",
"repeater_chanUtil": "Wykorzystanie kanału" "repeater_chanUtil": "Wykorzystanie kanału",
"contact_connectCompanion": "Połącz się z towarzyszem, aby uzyskać dostęp do funkcji powtarzacza i serwera pokoi."
} }
+2 -1
View File
@@ -2311,5 +2311,6 @@
"settings_companionDebugLogSubtitle": "Comandos, respostas e dados brutos para protocolos BLE/TCP/USB", "settings_companionDebugLogSubtitle": "Comandos, respostas e dados brutos para protocolos BLE/TCP/USB",
"chat_markAsUnread": "Marcar como não lido", "chat_markAsUnread": "Marcar como não lido",
"chat_newMessages": "Novas mensagens", "chat_newMessages": "Novas mensagens",
"repeater_chanUtil": "Utilização do canal" "repeater_chanUtil": "Utilização do canal",
"contact_connectCompanion": "Conecte-se a um dispositivo companion para acessar as funcionalidades de repetidor e servidor de salas."
} }
+2 -1
View File
@@ -1614,5 +1614,6 @@
"repeater_cliHelpStatsCore": "(Только для серийного оборудования) Отображает основные статистические данные прошивки.", "repeater_cliHelpStatsCore": "(Только для серийного оборудования) Отображает основные статистические данные прошивки.",
"settings_companionDebugLogSubtitle": "Команды, ответы и необработанные данные, используемые для протоколов BLE, TCP и USB.", "settings_companionDebugLogSubtitle": "Команды, ответы и необработанные данные, используемые для протоколов BLE, TCP и USB.",
"repeater_chanUtil": "Использование канала", "repeater_chanUtil": "Использование канала",
"settings_companionDebugLog": "Журнал отладки (для сопутствующего приложения)" "settings_companionDebugLog": "Журнал отладки (для сопутствующего приложения)",
"contact_connectCompanion": "Подключитесь к компаньону, чтобы получить доступ к функциям ретранслятора и сервера комнат."
} }
+2 -1
View File
@@ -2311,5 +2311,6 @@
"settings_companionDebugLogSubtitle": "Príkazy, odpovede a surové dáta pre protokoly BLE/TCP/USB", "settings_companionDebugLogSubtitle": "Príkazy, odpovede a surové dáta pre protokoly BLE/TCP/USB",
"settings_companionDebugLog": "Logovanie pre ladenie (sprievodný log)", "settings_companionDebugLog": "Logovanie pre ladenie (sprievodný log)",
"chat_newMessages": "Nové správy", "chat_newMessages": "Nové správy",
"repeater_chanUtil": "Využitie kanálu" "repeater_chanUtil": "Využitie kanálu",
"contact_connectCompanion": "Pripojte sa k sprievodcovi a získajte prístup k funkciám opakovača a serveru miestností."
} }
+2 -1
View File
@@ -2311,5 +2311,6 @@
"chat_markAsUnread": "Označiti kot neneobdelano", "chat_markAsUnread": "Označiti kot neneobdelano",
"chat_newMessages": "Nove novice", "chat_newMessages": "Nove novice",
"settings_companionDebugLogSubtitle": "Navodila, odgovori in surova podatka za BLE/TCP/USB.", "settings_companionDebugLogSubtitle": "Navodila, odgovori in surova podatka za BLE/TCP/USB.",
"repeater_chanUtil": "Uporaba kanala" "repeater_chanUtil": "Uporaba kanala",
"contact_connectCompanion": "Povežite se s spremljevalnikom za dostop do funkcij ponavljalnika in strežnika sob."
} }
+2 -1
View File
@@ -2311,5 +2311,6 @@
"settings_companionDebugLog": "Följande felsökningslogg", "settings_companionDebugLog": "Följande felsökningslogg",
"chat_newMessages": "Nya meddelanden", "chat_newMessages": "Nya meddelanden",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB-kommandon, svar och rådata", "settings_companionDebugLogSubtitle": "BLE/TCP/USB-kommandon, svar och rådata",
"repeater_chanUtil": "Användning av kanal" "repeater_chanUtil": "Användning av kanal",
"contact_connectCompanion": "Anslut till en sällskapstjänst för att komma åt upprepning och rumsserverfunktioner."
} }
+2 -1
View File
@@ -2291,5 +2291,6 @@
"settings_companionDebugLogSubtitle": "Команди, відповіді та необроблена інформація для протоколів BLE/TCP/USB", "settings_companionDebugLogSubtitle": "Команди, відповіді та необроблена інформація для протоколів BLE/TCP/USB",
"chat_newMessages": "Нові повідомлення", "chat_newMessages": "Нові повідомлення",
"chat_markAsUnread": "Позначити як непрочитане", "chat_markAsUnread": "Позначити як непрочитане",
"repeater_chanUtil": "Використання каналу" "repeater_chanUtil": "Використання каналу",
"contact_connectCompanion": "Підключіться до супутнього пристрою, щоб отримати доступ до функцій ретранслятора та сервера кімнат."
} }
+2 -1
View File
@@ -2316,5 +2316,6 @@
"settings_companionDebugLog": "调试日志", "settings_companionDebugLog": "调试日志",
"chat_newMessages": "新的消息", "chat_newMessages": "新的消息",
"settings_companionDebugLogSubtitle": "BLE/TCP/USB 协议、响应和原始数据", "settings_companionDebugLogSubtitle": "BLE/TCP/USB 协议、响应和原始数据",
"repeater_chanUtil": "频道利用率" "repeater_chanUtil": "频道利用率",
"contact_connectCompanion": "连接伴机以访问中继器和房间服务器功能。"
} }
+4 -9
View File
@@ -5,10 +5,10 @@ import 'l10n/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'screens/chrome_required_screen.dart'; import 'screens/chrome_required_screen.dart';
import 'screens/contacts_screen.dart';
import 'utils/platform_info.dart'; import 'utils/platform_info.dart';
import 'connector/meshcore_connector.dart'; import 'connector/meshcore_connector.dart';
import 'screens/scanner_screen.dart';
import 'services/storage_service.dart'; import 'services/storage_service.dart';
import 'services/message_retry_service.dart'; import 'services/message_retry_service.dart';
import 'services/path_history_service.dart'; import 'services/path_history_service.dart';
@@ -81,13 +81,8 @@ void main() async {
timeoutPredictionService: timeoutPredictionService, timeoutPredictionService: timeoutPredictionService,
); );
await connector.loadContactCache(); await connector.restoreLastCompanionScope();
await connector.loadChannelSettings(); await connector.loadAllCachedDataForCurrentCompanion();
await connector.loadCachedChannels();
// Load persisted channel messages
await connector.loadAllChannelMessages();
await connector.loadUnreadState();
runApp( runApp(
MeshCoreApp( MeshCoreApp(
@@ -218,7 +213,7 @@ class MeshCoreApp extends StatelessWidget {
}, },
home: (PlatformInfo.isWeb && !PlatformInfo.isChrome) home: (PlatformInfo.isWeb && !PlatformInfo.isChrome)
? const ChromeRequiredScreen() ? const ChromeRequiredScreen()
: const ScannerScreen(), : const ContactsScreen(),
); );
}, },
), ),
+2
View File
@@ -3,6 +3,7 @@ class PathRecord {
final int tripTimeMs; final int tripTimeMs;
final DateTime? timestamp; final DateTime? timestamp;
final bool wasFloodDiscovery; final bool wasFloodDiscovery;
final int byteCount;
final List<int> pathBytes; final List<int> pathBytes;
final int successCount; final int successCount;
final int failureCount; final int failureCount;
@@ -17,6 +18,7 @@ class PathRecord {
required this.successCount, required this.successCount,
required this.failureCount, required this.failureCount,
this.routeWeight = 1.0, this.routeWeight = 1.0,
this.byteCount = 0,
}); });
String get displayText => String get displayText =>
+6 -1
View File
@@ -1057,6 +1057,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
Widget _buildMessageComposer() { Widget _buildMessageComposer() {
final connector = context.watch<MeshCoreConnector>(); final connector = context.watch<MeshCoreConnector>();
if (!connector.isConnected) {
return const SizedBox.shrink();
}
final maxBytes = maxChannelMessageBytes(connector.selfName); final maxBytes = maxChannelMessageBytes(connector.selfName);
final settings = context.watch<AppSettingsService>().settings; final settings = context.watch<AppSettingsService>().settings;
return Column( return Column(
@@ -1208,6 +1211,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
} }
Future<void> _sendMessage() async { Future<void> _sendMessage() async {
final connector = context.read<MeshCoreConnector>();
if (!connector.isConnected) return;
final text = _textController.text.trim(); final text = _textController.text.trim();
if (text.isEmpty) return; if (text.isEmpty) return;
@@ -1222,7 +1228,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
} }
_lastChannelSendAt = now; _lastChannelSendAt = now;
final connector = context.read<MeshCoreConnector>();
final settings = context.read<AppSettingsService>().settings; final settings = context.read<AppSettingsService>().settings;
final translationService = context.read<TranslationService>(); final translationService = context.read<TranslationService>();
+28 -16
View File
@@ -17,7 +17,6 @@ import '../models/channel.dart';
import '../models/community.dart'; import '../models/community.dart';
import '../storage/community_store.dart'; import '../storage/community_store.dart';
import '../utils/dialog_utils.dart'; import '../utils/dialog_utils.dart';
import '../utils/disconnect_navigation_mixin.dart';
import '../utils/route_transitions.dart'; import '../utils/route_transitions.dart';
import '../widgets/list_filter_widget.dart'; import '../widgets/list_filter_widget.dart';
import '../widgets/empty_state.dart'; import '../widgets/empty_state.dart';
@@ -29,6 +28,7 @@ import 'channel_chat_screen.dart';
import 'community_qr_scanner_screen.dart'; import 'community_qr_scanner_screen.dart';
import 'contacts_screen.dart'; import 'contacts_screen.dart';
import 'map_screen.dart'; import 'map_screen.dart';
import 'scanner_screen.dart';
import 'settings_screen.dart'; import 'settings_screen.dart';
class ChannelsScreen extends StatefulWidget { class ChannelsScreen extends StatefulWidget {
@@ -41,7 +41,7 @@ class ChannelsScreen extends StatefulWidget {
} }
class _ChannelsScreenState extends State<ChannelsScreen> class _ChannelsScreenState extends State<ChannelsScreen>
with DisconnectNavigationMixin { {
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
final CommunityStore _communityStore = CommunityStore(); final CommunityStore _communityStore = CommunityStore();
Timer? _searchDebounce; Timer? _searchDebounce;
@@ -117,11 +117,6 @@ class _ChannelsScreenState extends State<ChannelsScreen>
final channelMessageStore = ChannelMessageStore(); final channelMessageStore = ChannelMessageStore();
channelMessageStore.setPublicKeyHex = connector.selfPublicKeyHex; channelMessageStore.setPublicKeyHex = connector.selfPublicKeyHex;
// Auto-navigate back to scanner if disconnected
if (!checkConnectionAndNavigate(connector)) {
return const SizedBox.shrink();
}
final allowBack = !connector.isConnected; final allowBack = !connector.isConnected;
return PopScope( return PopScope(
@@ -134,16 +129,33 @@ class _ChannelsScreenState extends State<ChannelsScreen>
actions: [ actions: [
PopupMenuButton( PopupMenuButton(
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( if (connector.isConnected)
child: Row( PopupMenuItem(
children: [ child: Row(
const Icon(Icons.logout, color: Colors.red), children: [
const SizedBox(width: 8), const Icon(Icons.logout, color: Colors.red),
Text(context.l10n.common_disconnect), const SizedBox(width: 8),
], Text(context.l10n.common_disconnect),
],
),
onTap: () => _disconnect(context),
)
else
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.bluetooth_searching),
const SizedBox(width: 8),
Text(context.l10n.common_connect),
],
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ScannerScreen(),
),
),
), ),
onTap: () => _disconnect(context),
),
if (_communities.isNotEmpty) if (_communities.isNotEmpty)
PopupMenuItem( PopupMenuItem(
child: Row( child: Row(
+4 -1
View File
@@ -414,7 +414,7 @@ class _ChatScreenState extends State<ChatScreen> {
], ],
), ),
), ),
_buildInputBar(connector), if (connector.isConnected) _buildInputBar(connector),
], ],
); );
}, },
@@ -693,6 +693,9 @@ class _ChatScreenState extends State<ChatScreen> {
} }
Future<void> _sendMessage(MeshCoreConnector connector) async { Future<void> _sendMessage(MeshCoreConnector connector) async {
if (!connector.isConnected) {
return;
}
final text = _textController.text.trim(); final text = _textController.text.trim();
if (text.isEmpty) return; if (text.isEmpty) return;
+54 -16
View File
@@ -19,7 +19,6 @@ import '../services/ui_view_state_service.dart';
import '../utils/contact_search.dart'; import '../utils/contact_search.dart';
import '../storage/contact_group_store.dart'; import '../storage/contact_group_store.dart';
import '../utils/dialog_utils.dart'; import '../utils/dialog_utils.dart';
import '../utils/disconnect_navigation_mixin.dart';
import '../utils/emoji_utils.dart'; import '../utils/emoji_utils.dart';
import '../utils/route_transitions.dart'; import '../utils/route_transitions.dart';
import '../widgets/list_filter_widget.dart'; import '../widgets/list_filter_widget.dart';
@@ -34,6 +33,7 @@ import 'chat_screen.dart';
import 'discovery_screen.dart'; import 'discovery_screen.dart';
import 'map_screen.dart'; import 'map_screen.dart';
import 'repeater_hub_screen.dart'; import 'repeater_hub_screen.dart';
import 'scanner_screen.dart';
import 'settings_screen.dart'; import 'settings_screen.dart';
enum RoomLoginDestination { chat, management } enum RoomLoginDestination { chat, management }
@@ -50,7 +50,7 @@ class ContactsScreen extends StatefulWidget {
} }
class _ContactsScreenState extends State<ContactsScreen> class _ContactsScreenState extends State<ContactsScreen>
with DisconnectNavigationMixin { {
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
final ContactGroupStore _groupStore = ContactGroupStore(); final ContactGroupStore _groupStore = ContactGroupStore();
MeshCoreConnector? _scopeSyncConnector; MeshCoreConnector? _scopeSyncConnector;
@@ -306,11 +306,6 @@ class _ContactsScreenState extends State<ContactsScreen>
Widget build(BuildContext context) { Widget build(BuildContext context) {
final connector = context.watch<MeshCoreConnector>(); final connector = context.watch<MeshCoreConnector>();
// Auto-navigate back to scanner if disconnected
if (!checkConnectionAndNavigate(connector)) {
return const SizedBox.shrink();
}
final allowBack = !connector.isConnected; final allowBack = !connector.isConnected;
return PopScope( return PopScope(
canPop: allowBack, canPop: allowBack,
@@ -378,16 +373,33 @@ class _ContactsScreenState extends State<ContactsScreen>
), ),
PopupMenuButton( PopupMenuButton(
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( if (connector.isConnected)
child: Row( PopupMenuItem(
children: [ child: Row(
const Icon(Icons.logout, color: Colors.red), children: [
const SizedBox(width: 8), const Icon(Icons.logout, color: Colors.red),
Text(context.l10n.common_disconnect), const SizedBox(width: 8),
], Text(context.l10n.common_disconnect),
],
),
onTap: () => _disconnect(context, connector),
)
else
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.bluetooth_searching),
const SizedBox(width: 8),
Text(context.l10n.common_connect),
],
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ScannerScreen(),
),
),
), ),
onTap: () => _disconnect(context, connector),
),
PopupMenuItem( PopupMenuItem(
child: Row( child: Row(
children: [ children: [
@@ -965,6 +977,11 @@ class _ContactsScreenState extends State<ContactsScreen>
} }
void _showRepeaterLogin(BuildContext context, Contact repeater) { void _showRepeaterLogin(BuildContext context, Contact repeater) {
final connector = context.read<MeshCoreConnector>();
if (!connector.isConnected) {
_showCompanionRequiredDialog(context);
return;
}
showDialog( showDialog(
context: context, context: context,
builder: (context) => RepeaterLoginDialog( builder: (context) => RepeaterLoginDialog(
@@ -991,6 +1008,11 @@ class _ContactsScreenState extends State<ContactsScreen>
Contact room, Contact room,
RoomLoginDestination destination, RoomLoginDestination destination,
) { ) {
final connector = context.read<MeshCoreConnector>();
if (!connector.isConnected) {
_showCompanionRequiredDialog(context);
return;
}
showDialog( showDialog(
context: context, context: context,
builder: (context) => RoomLoginDialog( builder: (context) => RoomLoginDialog(
@@ -1019,6 +1041,22 @@ class _ContactsScreenState extends State<ContactsScreen>
); );
} }
void _showCompanionRequiredDialog(BuildContext context) {
showDialog<void>(
context: context,
builder: (dialogContext) => AlertDialog(
title: Text(context.l10n.scanner_notConnected),
content: Text(context.l10n.contact_connectCompanion),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: Text(context.l10n.common_ok),
),
],
),
);
}
void _confirmDeleteGroup(BuildContext context, ContactGroup group) { void _confirmDeleteGroup(BuildContext context, ContactGroup group) {
if (!_hasGroupStoreScope(context.read<MeshCoreConnector>())) { if (!_hasGroupStoreScope(context.read<MeshCoreConnector>())) {
_showGroupsUnavailableMessage(context); _showGroupsUnavailableMessage(context);
+27 -9
View File
@@ -31,6 +31,7 @@ import '../widgets/repeater_login_dialog.dart';
import '../widgets/room_login_dialog.dart'; import '../widgets/room_login_dialog.dart';
import '../helpers/snack_bar_builder.dart'; import '../helpers/snack_bar_builder.dart';
import 'repeater_hub_screen.dart'; import 'repeater_hub_screen.dart';
import 'scanner_screen.dart';
import 'settings_screen.dart'; import 'settings_screen.dart';
import 'line_of_sight_map_screen.dart'; import 'line_of_sight_map_screen.dart';
@@ -466,16 +467,33 @@ class _MapScreenState extends State<MapScreen> {
), ),
PopupMenuButton( PopupMenuButton(
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( if (connector.isConnected)
child: Row( PopupMenuItem(
children: [ child: Row(
const Icon(Icons.logout, color: Colors.red), children: [
const SizedBox(width: 8), const Icon(Icons.logout, color: Colors.red),
Text(context.l10n.common_disconnect), const SizedBox(width: 8),
], Text(context.l10n.common_disconnect),
],
),
onTap: () => _disconnect(context, connector),
)
else
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.bluetooth_searching),
const SizedBox(width: 8),
Text(context.l10n.common_connect),
],
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ScannerScreen(),
),
),
), ),
onTap: () => _disconnect(context, connector),
),
PopupMenuItem( PopupMenuItem(
child: Row( child: Row(
children: [ children: [
@@ -1,20 +0,0 @@
import 'package:flutter/material.dart';
import '../connector/meshcore_connector.dart';
/// Mixin that automatically navigates back to scanner when disconnected.
/// Use in State classes for screens that require active connection.
mixin DisconnectNavigationMixin<T extends StatefulWidget> on State<T> {
/// Call this in your Widget build method to enable auto-navigation.
/// Returns true if still connected, false if navigation was triggered.
bool checkConnectionAndNavigate(MeshCoreConnector connector) {
if (!connector.isConnected) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
Navigator.popUntil(context, (route) => route.isFirst);
}
});
return false;
}
return true;
}
}
+928 -420
View File
File diff suppressed because it is too large Load Diff