mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-19 00:45:33 +10:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba4fd3eff5 | |||
| beb3e1996d | |||
| 3f4e6f4e13 | |||
| e1cc285c8a | |||
| f4b32e8a8a | |||
| 01bf95c98e | |||
| fa044dd204 | |||
| 72fea3fc32 | |||
| f0bd61144c | |||
| 61c897630c | |||
| a270e2e6d1 | |||
| 247db6a36d | |||
| 78d08afb47 | |||
| c77264cc81 | |||
| d6ed8c5f13 | |||
| 209fee48ca | |||
| 8892823337 | |||
| e738664f89 | |||
| e37616fa15 | |||
| 2763d83fe4 | |||
| 77018dc358 | |||
| 21c58d4e13 | |||
| 3af97ff6dd | |||
| 703d5a1ec4 | |||
| e801a497f8 | |||
| d3c7d8e43a | |||
| 0c1e163b88 | |||
| d0d6a34fb5 | |||
| 16ce1359d7 | |||
| 9fe4a3710d | |||
| 8611adab1f | |||
| 7d457cb863 | |||
| 297516fc80 | |||
| 1b94442ab6 | |||
| 3ae14781f0 | |||
| ecc496f2af | |||
| 87b25655d0 | |||
| c47a4cb622 | |||
| a30fc439f3 | |||
| afcc4db405 | |||
| 87bcb6a6a3 | |||
| 68bb031bb6 | |||
| c4f5c7b171 | |||
| 2bce14224d | |||
| fd305fd55b | |||
| d0dd805244 | |||
| 8668564464 |
@@ -193,6 +193,7 @@ Devices are discovered by scanning for BLE advertisements with known MeshCore de
|
|||||||
- `WisCore-`
|
- `WisCore-`
|
||||||
- `HT-`
|
- `HT-`
|
||||||
- `LowMesh_MC_`
|
- `LowMesh_MC_`
|
||||||
|
- `NRF52`
|
||||||
|
|
||||||
New device prefixes can be added in `lib/connector/meshcore_uuids.dart`.
|
New device prefixes can be added in `lib/connector/meshcore_uuids.dart`.
|
||||||
|
|
||||||
|
|||||||
@@ -305,6 +305,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
final Map<String, int> _contactUnreadCount = {};
|
final Map<String, int> _contactUnreadCount = {};
|
||||||
final Map<String, RepeaterBatterySnapshot> _repeaterBatterySnapshots = {};
|
final Map<String, RepeaterBatterySnapshot> _repeaterBatterySnapshots = {};
|
||||||
bool _unreadStateLoaded = false;
|
bool _unreadStateLoaded = false;
|
||||||
|
int _cachedContactsUnreadTotal = 0;
|
||||||
|
int _cachedChannelsUnreadTotal = 0;
|
||||||
final Map<String, _RepeaterAckContext> _pendingRepeaterAcks = {};
|
final Map<String, _RepeaterAckContext> _pendingRepeaterAcks = {};
|
||||||
String? _activeContactKey;
|
String? _activeContactKey;
|
||||||
int? _activeChannelIndex;
|
int? _activeChannelIndex;
|
||||||
@@ -612,16 +614,42 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
|
|
||||||
int getTotalUnreadCount() {
|
int getTotalUnreadCount() {
|
||||||
if (!_unreadStateLoaded) return 0;
|
if (!_unreadStateLoaded) return 0;
|
||||||
var total = 0;
|
return getTotalContactsUnreadCount() + getTotalChannelsUnreadCount();
|
||||||
// Count unread contact messages
|
}
|
||||||
for (final contact in _contacts) {
|
|
||||||
total += getUnreadCountForContact(contact);
|
int getTotalContactsUnreadCount() {
|
||||||
}
|
if (!_unreadStateLoaded) return 0;
|
||||||
// Count unread channel messages
|
return _cachedContactsUnreadTotal;
|
||||||
for (final channelIndex in _channelMessages.keys) {
|
}
|
||||||
total += getUnreadCountForChannelIndex(channelIndex);
|
|
||||||
}
|
int getTotalChannelsUnreadCount() {
|
||||||
return total;
|
if (!_unreadStateLoaded) return 0;
|
||||||
|
return _cachedChannelsUnreadTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recalculates both cached unread totals from scratch.
|
||||||
|
/// Called when unread state is first loaded.
|
||||||
|
void _recalculateCachedUnreadTotals() {
|
||||||
|
_recalculateCachedContactsUnreadTotal();
|
||||||
|
_recalculateCachedChannelsUnreadTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _recalculateCachedContactsUnreadTotal() {
|
||||||
|
int total = 0;
|
||||||
|
_contactUnreadCount.forEach((contactKeyHex, count) {
|
||||||
|
if (_shouldTrackUnreadForContactKey(contactKeyHex)) {
|
||||||
|
total += count;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_cachedContactsUnreadTotal = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _recalculateCachedChannelsUnreadTotal() {
|
||||||
|
final allChannels = _channels.isNotEmpty ? _channels : _cachedChannels;
|
||||||
|
_cachedChannelsUnreadTotal = allChannels.fold(
|
||||||
|
0,
|
||||||
|
(total, ch) => total + ch.unreadCount,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isChannelSmazEnabled(int channelIndex) {
|
bool isChannelSmazEnabled(int channelIndex) {
|
||||||
@@ -655,6 +683,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
..clear()
|
..clear()
|
||||||
..addAll(await _unreadStore.loadContactUnreadCount());
|
..addAll(await _unreadStore.loadContactUnreadCount());
|
||||||
_unreadStateLoaded = true;
|
_unreadStateLoaded = true;
|
||||||
|
_recalculateCachedUnreadTotals();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,6 +722,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
final previousCount = _contactUnreadCount[contactKeyHex] ?? 0;
|
final previousCount = _contactUnreadCount[contactKeyHex] ?? 0;
|
||||||
if (previousCount > 0) {
|
if (previousCount > 0) {
|
||||||
_contactUnreadCount[contactKeyHex] = 0;
|
_contactUnreadCount[contactKeyHex] = 0;
|
||||||
|
_cachedContactsUnreadTotal = (_cachedContactsUnreadTotal - previousCount)
|
||||||
|
.clamp(0, _cachedContactsUnreadTotal);
|
||||||
_appDebugLogService?.info(
|
_appDebugLogService?.info(
|
||||||
'Contact $contactKeyHex marked as read (was $previousCount unread)',
|
'Contact $contactKeyHex marked as read (was $previousCount unread)',
|
||||||
tag: 'Unread',
|
tag: 'Unread',
|
||||||
@@ -734,6 +765,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
if (channel != null && channel.unreadCount > 0) {
|
if (channel != null && channel.unreadCount > 0) {
|
||||||
final previousCount = channel.unreadCount;
|
final previousCount = channel.unreadCount;
|
||||||
channel.unreadCount = 0;
|
channel.unreadCount = 0;
|
||||||
|
_cachedChannelsUnreadTotal = (_cachedChannelsUnreadTotal - previousCount)
|
||||||
|
.clamp(0, _cachedChannelsUnreadTotal);
|
||||||
_appDebugLogService?.info(
|
_appDebugLogService?.info(
|
||||||
'Channel ${channel.name.isNotEmpty ? channel.name : channelIndex} marked as read (was $previousCount unread)',
|
'Channel ${channel.name.isNotEmpty ? channel.name : channelIndex} marked as read (was $previousCount unread)',
|
||||||
tag: 'Unread',
|
tag: 'Unread',
|
||||||
@@ -940,11 +973,18 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
final lastCompanionPublicKeyHex = prefs.getString(
|
final lastCompanionPublicKeyHex = prefs.getString(
|
||||||
_lastCompanionPublicKeyPref,
|
_lastCompanionPublicKeyPref,
|
||||||
);
|
);
|
||||||
if (lastCompanionPublicKeyHex == null ||
|
try {
|
||||||
lastCompanionPublicKeyHex.trim().isEmpty) {
|
if (lastCompanionPublicKeyHex == null ||
|
||||||
return;
|
lastCompanionPublicKeyHex.trim().isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_selfPublicKey = hexToPubKey(lastCompanionPublicKeyHex);
|
||||||
|
_setScopedStorePublicKey(lastCompanionPublicKeyHex);
|
||||||
|
} catch (e) {
|
||||||
|
_appDebugLogService?.error(
|
||||||
|
'Failed to restore last companion scope with public key hex: $lastCompanionPublicKeyHex, error: $e',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_setScopedStorePublicKey(lastCompanionPublicKeyHex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadDiscoveredContactCache() => _loadDiscoveredContactCache();
|
Future<void> loadDiscoveredContactCache() => _loadDiscoveredContactCache();
|
||||||
@@ -966,7 +1006,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
_conversations.clear();
|
_conversations.clear();
|
||||||
_loadedConversationKeys.clear();
|
_loadedConversationKeys.clear();
|
||||||
_channelMessages.clear();
|
_channelMessages.clear();
|
||||||
|
_channels.clear();
|
||||||
_cachedChannels.clear();
|
_cachedChannels.clear();
|
||||||
|
_previousChannelsCache.clear();
|
||||||
_knownContactKeys.clear();
|
_knownContactKeys.clear();
|
||||||
_contactUnreadCount.clear();
|
_contactUnreadCount.clear();
|
||||||
_unreadStateLoaded = false;
|
_unreadStateLoaded = false;
|
||||||
@@ -987,6 +1029,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
await restoreLastCompanionScope();
|
await restoreLastCompanionScope();
|
||||||
await loadContactCache();
|
await loadContactCache();
|
||||||
await _loadDiscoveredContactCache();
|
await _loadDiscoveredContactCache();
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadDiscoveredContactCache() async {
|
Future<void> _loadDiscoveredContactCache() async {
|
||||||
@@ -3203,6 +3246,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
unawaited(_persistContacts());
|
unawaited(_persistContacts());
|
||||||
_conversations.remove(contact.publicKeyHex);
|
_conversations.remove(contact.publicKeyHex);
|
||||||
_loadedConversationKeys.remove(contact.publicKeyHex);
|
_loadedConversationKeys.remove(contact.publicKeyHex);
|
||||||
|
final removedCount = _contactUnreadCount[contact.publicKeyHex] ?? 0;
|
||||||
|
_cachedContactsUnreadTotal = (_cachedContactsUnreadTotal - removedCount)
|
||||||
|
.clamp(0, _cachedContactsUnreadTotal);
|
||||||
_contactUnreadCount.remove(contact.publicKeyHex);
|
_contactUnreadCount.remove(contact.publicKeyHex);
|
||||||
_unreadStore.saveContactUnreadCount(
|
_unreadStore.saveContactUnreadCount(
|
||||||
Map<String, int>.from(_contactUnreadCount),
|
Map<String, int>.from(_contactUnreadCount),
|
||||||
@@ -3596,6 +3642,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
// Cache channels for offline use
|
// Cache channels for offline use
|
||||||
_cachedChannels = List<Channel>.from(_channels);
|
_cachedChannels = List<Channel>.from(_channels);
|
||||||
unawaited(_channelStore.saveChannels(_channels));
|
unawaited(_channelStore.saveChannels(_channels));
|
||||||
|
_recalculateCachedChannelsUnreadTotal();
|
||||||
|
|
||||||
// Apply ordering and notify UI
|
// Apply ordering and notify UI
|
||||||
_applyChannelOrder();
|
_applyChannelOrder();
|
||||||
@@ -4134,6 +4181,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
_handleDiscovery(contact, frame, noNotify: true, addActive: true);
|
_handleDiscovery(contact, frame, noNotify: true, addActive: true);
|
||||||
|
|
||||||
if (contact.type == advTypeRepeater) {
|
if (contact.type == advTypeRepeater) {
|
||||||
|
final removedCount = _contactUnreadCount[contact.publicKeyHex] ?? 0;
|
||||||
|
_cachedContactsUnreadTotal = (_cachedContactsUnreadTotal - removedCount)
|
||||||
|
.clamp(0, _cachedContactsUnreadTotal);
|
||||||
_contactUnreadCount.remove(contact.publicKeyHex);
|
_contactUnreadCount.remove(contact.publicKeyHex);
|
||||||
_unreadStore.saveContactUnreadCount(
|
_unreadStore.saveContactUnreadCount(
|
||||||
Map<String, int>.from(_contactUnreadCount),
|
Map<String, int>.from(_contactUnreadCount),
|
||||||
@@ -4224,6 +4274,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (contact.type == advTypeRepeater) {
|
if (contact.type == advTypeRepeater) {
|
||||||
|
final removedCount = _contactUnreadCount[contact.publicKeyHex] ?? 0;
|
||||||
|
_cachedContactsUnreadTotal = (_cachedContactsUnreadTotal - removedCount)
|
||||||
|
.clamp(0, _cachedContactsUnreadTotal);
|
||||||
_contactUnreadCount.remove(contact.publicKeyHex);
|
_contactUnreadCount.remove(contact.publicKeyHex);
|
||||||
_unreadStore.saveContactUnreadCount(
|
_unreadStore.saveContactUnreadCount(
|
||||||
Map<String, int>.from(_contactUnreadCount),
|
Map<String, int>.from(_contactUnreadCount),
|
||||||
@@ -5104,7 +5157,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
_channelSyncRetries = 0; // Reset retry counter on success
|
_channelSyncRetries = 0; // Reset retry counter on success
|
||||||
|
|
||||||
// Only add non-empty channels
|
// Only add non-empty channels
|
||||||
if (!channel.isEmpty) {
|
if (!channel.isEmpty &&
|
||||||
|
!_channels.any((c) => c.pskHex == channel.pskHex)) {
|
||||||
_channels.add(channel);
|
_channels.add(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5236,6 +5290,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
final channel = _findChannelByIndex(channelIndex);
|
final channel = _findChannelByIndex(channelIndex);
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
channel.unreadCount++;
|
channel.unreadCount++;
|
||||||
|
_cachedChannelsUnreadTotal++;
|
||||||
_appDebugLogService?.info(
|
_appDebugLogService?.info(
|
||||||
'Channel ${channel.name.isNotEmpty ? channel.name : channelIndex} unread count incremented to ${channel.unreadCount}',
|
'Channel ${channel.name.isNotEmpty ? channel.name : channelIndex} unread count incremented to ${channel.unreadCount}',
|
||||||
tag: 'Unread',
|
tag: 'Unread',
|
||||||
@@ -5280,6 +5335,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
|
|
||||||
final currentCount = _contactUnreadCount[contactKey] ?? 0;
|
final currentCount = _contactUnreadCount[contactKey] ?? 0;
|
||||||
_contactUnreadCount[contactKey] = currentCount + 1;
|
_contactUnreadCount[contactKey] = currentCount + 1;
|
||||||
|
_cachedContactsUnreadTotal++;
|
||||||
_appDebugLogService?.info(
|
_appDebugLogService?.info(
|
||||||
'Contact $contactKey unread count incremented to ${currentCount + 1}',
|
'Contact $contactKey unread count incremented to ${currentCount + 1}',
|
||||||
tag: 'Unread',
|
tag: 'Unread',
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ class MeshCoreUuids {
|
|||||||
"Lilygo",
|
"Lilygo",
|
||||||
"HT-",
|
"HT-",
|
||||||
"LowMesh_MC_",
|
"LowMesh_MC_",
|
||||||
|
"NRF52",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2312,5 +2312,8 @@
|
|||||||
"chat_newMessages": "Нови съобщения",
|
"chat_newMessages": "Нови съобщения",
|
||||||
"settings_companionDebugLog": "Лог за отстраняване на грешки (за съпътстваща програма)",
|
"settings_companionDebugLog": "Лог за отстраняване на грешки (за съпътстваща програма)",
|
||||||
"repeater_chanUtil": "Използване на канала",
|
"repeater_chanUtil": "Използване на канала",
|
||||||
"contact_connectCompanion": "Свържете се с придружител, за да получите достъп до функциите на ретранслатора и сървъра за стаи."
|
"dialog_connectCompanion": "Свържете се с придружител, за да получите достъп до функциите на ретранслатора и сървъра за стаи.",
|
||||||
|
"dialog_disconnectedTitle": "Прекъснато",
|
||||||
|
"dialog_disconnectedMessage": "Свързването ви с вашия спътник е прекъснато.",
|
||||||
|
"contact_connectCompanion": "Свържете се с спътник, за да получите достъп до функциите на repeater и room server."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2340,5 +2340,8 @@
|
|||||||
"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."
|
"dialog_connectCompanion": "Verbinden Sie sich mit einem Companion, um auf die Funktionen des Repeaters und des Raumservers zuzugreifen.",
|
||||||
|
"dialog_disconnectedTitle": "Getrennt",
|
||||||
|
"dialog_disconnectedMessage": "Du wurdest von deinem Begleiter getrennt.",
|
||||||
|
"contact_connectCompanion": "Mit einem Companion verbinden, um auf Repeater- und Raumserver-Funktionen zuzugreifen."
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -1062,6 +1062,9 @@
|
|||||||
"time_allTime": "All Time",
|
"time_allTime": "All Time",
|
||||||
"dialog_disconnect": "Disconnect",
|
"dialog_disconnect": "Disconnect",
|
||||||
"dialog_disconnectConfirm": "Are you sure you want to disconnect from this device?",
|
"dialog_disconnectConfirm": "Are you sure you want to disconnect from this device?",
|
||||||
|
"dialog_disconnectedTitle": "Disconnected",
|
||||||
|
"dialog_disconnectedMessage": "You have been disconnected from your companion.",
|
||||||
|
"dialog_connectCompanion": "Connect to a companion to access repeater and room server features.",
|
||||||
"login_repeaterLogin": "Repeater Login",
|
"login_repeaterLogin": "Repeater Login",
|
||||||
"login_roomLogin": "Room Server Login",
|
"login_roomLogin": "Room Server Login",
|
||||||
"login_password": "Password",
|
"login_password": "Password",
|
||||||
@@ -2366,6 +2369,5 @@
|
|||||||
"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."
|
}
|
||||||
}
|
|
||||||
+4
-1
@@ -2340,5 +2340,8 @@
|
|||||||
"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."
|
"dialog_connectCompanion": "Conéctate a un compañero para acceder a las funciones de repetidor y servidor de sala.",
|
||||||
|
"dialog_disconnectedTitle": "Desconectado",
|
||||||
|
"dialog_disconnectedMessage": "Te has desconectado de tu compañero.",
|
||||||
|
"contact_connectCompanion": "Conéctate a un compañero para acceder a las funciones del repetidor y del servidor de la sala."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2319,5 +2319,8 @@
|
|||||||
"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."
|
"dialog_connectCompanion": "Connectez-vous à un compagnon pour accéder aux fonctionnalités de répéteur et de serveur de salle.",
|
||||||
|
"dialog_disconnectedTitle": "Déconnecté",
|
||||||
|
"dialog_disconnectedMessage": "Vous avez été déconnecté de votre compagnon.",
|
||||||
|
"contact_connectCompanion": "Connectez-vous à un compagnon pour accéder aux fonctionnalités du répéteur et du serveur de salle."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2350,5 +2350,8 @@
|
|||||||
"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."
|
"dialog_connectCompanion": "Csatlakozz egy kísérőhöz az ismétlő- és szobaszerver-funkciók eléréséhez.",
|
||||||
|
"dialog_disconnectedTitle": "Kapcsolat megszakadt",
|
||||||
|
"dialog_disconnectedMessage": "A kapcsolat megszakadt a kísérővel.",
|
||||||
|
"contact_connectCompanion": "Csatlakozz egy kísérőhöz az ismétlő- és szobaszerver-funkciók eléréséhez."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2312,5 +2312,8 @@
|
|||||||
"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."
|
"dialog_connectCompanion": "Connettiti a un dispositivo companion per accedere alle funzionalità di ripetitore e server stanza.",
|
||||||
|
"dialog_disconnectedTitle": "Disconnesso",
|
||||||
|
"dialog_disconnectedMessage": "Sei stato disconnesso dal tuo compagno.",
|
||||||
|
"contact_connectCompanion": "Connettiti a un companion per accedere alle funzioni del repeater e del server di stanza."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2350,5 +2350,8 @@
|
|||||||
"chat_newMessages": "新しいメッセージ",
|
"chat_newMessages": "新しいメッセージ",
|
||||||
"chat_markAsUnread": "未読としてマークする",
|
"chat_markAsUnread": "未読としてマークする",
|
||||||
"repeater_chanUtil": "チャンネルの利用状況",
|
"repeater_chanUtil": "チャンネルの利用状況",
|
||||||
"contact_connectCompanion": "コネクトしてリピーターとルームサーバー機能にアクセス"
|
"dialog_connectCompanion": "コネクトしてリピーターとルームサーバー機能にアクセス",
|
||||||
|
"dialog_disconnectedTitle": "切断済み",
|
||||||
|
"dialog_disconnectedMessage": "コンパニオンとの接続が切れました。",
|
||||||
|
"contact_connectCompanion": "リピーターおよびルームサーバー機能にアクセスするには、コンパニオンに接続してください。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2350,5 +2350,8 @@
|
|||||||
"settings_companionDebugLogSubtitle": "BLE/TCP/USB 명령어, 응답 및 원시 데이터",
|
"settings_companionDebugLogSubtitle": "BLE/TCP/USB 명령어, 응답 및 원시 데이터",
|
||||||
"chat_markAsUnread": "미리 읽지 않음으로 표시",
|
"chat_markAsUnread": "미리 읽지 않음으로 표시",
|
||||||
"repeater_chanUtil": "채널 활용도",
|
"repeater_chanUtil": "채널 활용도",
|
||||||
|
"dialog_connectCompanion": "리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요.",
|
||||||
|
"dialog_disconnectedTitle": "연결 끊김",
|
||||||
|
"dialog_disconnectedMessage": "컴패니언과의 연결이 끊어졌습니다.",
|
||||||
"contact_connectCompanion": "리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요."
|
"contact_connectCompanion": "리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3561,6 +3561,24 @@ abstract class AppLocalizations {
|
|||||||
/// **'Are you sure you want to disconnect from this device?'**
|
/// **'Are you sure you want to disconnect from this device?'**
|
||||||
String get dialog_disconnectConfirm;
|
String get dialog_disconnectConfirm;
|
||||||
|
|
||||||
|
/// No description provided for @dialog_disconnectedTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Disconnected'**
|
||||||
|
String get dialog_disconnectedTitle;
|
||||||
|
|
||||||
|
/// No description provided for @dialog_disconnectedMessage.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'You have been disconnected from your companion.'**
|
||||||
|
String get dialog_disconnectedMessage;
|
||||||
|
|
||||||
|
/// No description provided for @dialog_connectCompanion.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Connect to a companion to access repeater and room server features.'**
|
||||||
|
String get dialog_connectCompanion;
|
||||||
|
|
||||||
/// No description provided for @login_repeaterLogin.
|
/// No description provided for @login_repeaterLogin.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -7335,12 +7353,6 @@ 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
|
||||||
|
|||||||
@@ -1979,6 +1979,17 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Сигурни ли сте, че искате да се откъснете от това устройство?';
|
'Сигурни ли сте, че искате да се откъснете от това устройство?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Прекъснато';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Свързването ви с вашия спътник е прекъснато.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Свържете се с придружител, за да получите достъп до функциите на ретранслатора и сървъра за стаи.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Повторител Вход';
|
String get login_repeaterLogin => 'Повторител Вход';
|
||||||
|
|
||||||
@@ -4288,8 +4299,4 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get contact_typeUnknown => 'Unknown';
|
String get contact_typeUnknown => 'Unknown';
|
||||||
|
|
||||||
@override
|
|
||||||
String get contact_connectCompanion =>
|
|
||||||
'Свържете се с придружител, за да получите достъп до функциите на ретранслатора и сървъра за стаи.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1977,6 +1977,17 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Sind Sie sicher, dass Sie sich von diesem Gerät trennen möchten?';
|
'Sind Sie sicher, dass Sie sich von diesem Gerät trennen möchten?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Getrennt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Du wurdest von deinem Begleiter getrennt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Verbinden Sie sich mit einem Companion, um auf die Funktionen des Repeaters und des Raumservers zuzugreifen.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Beim Repeater anmelden';
|
String get login_repeaterLogin => 'Beim Repeater anmelden';
|
||||||
|
|
||||||
@@ -4305,8 +4316,4 @@ 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.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1938,6 +1938,17 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Are you sure you want to disconnect from this device?';
|
'Are you sure you want to disconnect from this device?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Disconnected';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'You have been disconnected from your companion.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Connect to a companion to access repeater and room server features.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Repeater Login';
|
String get login_repeaterLogin => 'Repeater Login';
|
||||||
|
|
||||||
@@ -4210,8 +4221,4 @@ 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.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1974,6 +1974,17 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'¿Está seguro de que desea desconectarse de este dispositivo?';
|
'¿Está seguro de que desea desconectarse de este dispositivo?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Desconectado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Te has desconectado de tu compañero.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Conéctate a un compañero para acceder a las funciones de repetidor y servidor de sala.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Iniciar sesión en el Repetidor';
|
String get login_repeaterLogin => 'Iniciar sesión en el Repetidor';
|
||||||
|
|
||||||
@@ -4292,8 +4303,4 @@ 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.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1985,6 +1985,17 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Êtes-vous sûr de vouloir vous déconnecter de cet appareil ?';
|
'Êtes-vous sûr de vouloir vous déconnecter de cet appareil ?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Déconnecté';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Vous avez été déconnecté de votre compagnon.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Connectez-vous à un compagnon pour accéder aux fonctionnalités de répéteur et de serveur de salle.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Connexion au répéteur';
|
String get login_repeaterLogin => 'Connexion au répéteur';
|
||||||
|
|
||||||
@@ -4321,8 +4332,4 @@ 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.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1986,6 +1986,16 @@ class AppLocalizationsHu extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Biztosan szeretné kiírni ezt a készüléket?';
|
'Biztosan szeretné kiírni ezt a készüléket?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Lejárat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage => 'Lehentetőtől megszakadtál.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Csatlakozzon egy kísérőhöz a ismétlő és szobaszerver funkciók eléréséhez.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Ismételt bejelentkezés';
|
String get login_repeaterLogin => 'Ismételt bejelentkezés';
|
||||||
|
|
||||||
@@ -4309,8 +4319,4 @@ 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.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1976,6 +1976,17 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Sei sicuro di voler disconnetterti da questo dispositivo?';
|
'Sei sicuro di voler disconnetterti da questo dispositivo?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Disconnesso';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Sei stato disconnesso dal tuo compagno.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Connettiti a un dispositivo companion per accedere alle funzionalità di ripetitore e server stanza.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Login Ripetitore';
|
String get login_repeaterLogin => 'Login Ripetitore';
|
||||||
|
|
||||||
@@ -4297,8 +4308,4 @@ 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.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1894,6 +1894,15 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get dialog_disconnectConfirm => '本当にこのデバイスとの接続を解除したいですか?';
|
String get dialog_disconnectConfirm => '本当にこのデバイスとの接続を解除したいですか?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => '切断済み';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage => 'コンパニオンとの接続が切れました。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion => 'コネクトしてリピーターとルームサーバー機能にアクセス';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => '再ログイン';
|
String get login_repeaterLogin => '再ログイン';
|
||||||
|
|
||||||
@@ -4063,7 +4072,4 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get contact_typeUnknown => 'Unknown';
|
String get contact_typeUnknown => 'Unknown';
|
||||||
|
|
||||||
@override
|
|
||||||
String get contact_connectCompanion => 'コネクトしてリピーターとルームサーバー機能にアクセス';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1890,6 +1890,15 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get dialog_disconnectConfirm => '이 장치와의 연결을 해제하시겠습니까?';
|
String get dialog_disconnectConfirm => '이 장치와의 연결을 해제하시겠습니까?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => '연결 끊김';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage => '컴패니언과의 연결이 끊어졌습니다.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion => '리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => '다시 로그인';
|
String get login_repeaterLogin => '다시 로그인';
|
||||||
|
|
||||||
@@ -4064,7 +4073,4 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get contact_typeUnknown => 'Unknown';
|
String get contact_typeUnknown => 'Unknown';
|
||||||
|
|
||||||
@override
|
|
||||||
String get contact_connectCompanion => '리피터 및 룸 서버 기능에 액세스하려면 컴패니언에 연결하세요.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1963,6 +1963,17 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Ben je er zeker van dat je verbinding met dit apparaat wilt verbreken?';
|
'Ben je er zeker van dat je verbinding met dit apparaat wilt verbreken?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Verbroken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Je bent losgekoppeld van je companion.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Maak verbinding met een companion om repeater- en kamerserverfuncties te gebruiken.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Inloggen Repeater';
|
String get login_repeaterLogin => 'Inloggen Repeater';
|
||||||
|
|
||||||
@@ -4273,8 +4284,4 @@ 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.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1990,6 +1990,17 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Czy na pewno chcesz się odłączyć od tego urządzenia?';
|
'Czy na pewno chcesz się odłączyć od tego urządzenia?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Rozłączono';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Zostałeś rozłączony ze swoim towarzyszem.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Połącz się z towarzyszem, aby uzyskać dostęp do funkcji powtarzacza i serwera pokoi.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Logowanie do przekaźnika';
|
String get login_repeaterLogin => 'Logowanie do przekaźnika';
|
||||||
|
|
||||||
@@ -4309,8 +4320,4 @@ 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.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1973,6 +1973,17 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Tem certeza de que deseja desconectar deste dispositivo?';
|
'Tem certeza de que deseja desconectar deste dispositivo?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Desconectado';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Você foi desconectado do seu companheiro.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Conecte-se a um dispositivo companion para acessar as funcionalidades de repetidor e servidor de salas.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Login ao Repetidor';
|
String get login_repeaterLogin => 'Login ao Repetidor';
|
||||||
|
|
||||||
@@ -4285,8 +4296,4 @@ 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.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1977,6 +1977,17 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Вы уверены, что хотите отключиться от этого устройства?';
|
'Вы уверены, что хотите отключиться от этого устройства?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Отключено';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Вы были отключены от вашего компаньона.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Подключитесь к компаньону, чтобы получить доступ к функциям ретранслятора и сервера комнат.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Вход в репитер';
|
String get login_repeaterLogin => 'Вход в репитер';
|
||||||
|
|
||||||
@@ -4303,8 +4314,4 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get contact_typeUnknown => 'Неизвестно';
|
String get contact_typeUnknown => 'Неизвестно';
|
||||||
|
|
||||||
@override
|
|
||||||
String get contact_connectCompanion =>
|
|
||||||
'Подключитесь к компаньону, чтобы получить доступ к функциям ретранслятора и сервера комнат.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1963,6 +1963,17 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Ste si istý/á, že chcete odpojiť od tohto zariadenia?';
|
'Ste si istý/á, že chcete odpojiť od tohto zariadenia?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Odpojené';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Od vášho spoločníka ste boli odpojený.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Pripojte sa k sprievodcovi a získajte prístup k funkciám opakovača a serveru miestností.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Opätovné prihlásenie';
|
String get login_repeaterLogin => 'Opätovné prihlásenie';
|
||||||
|
|
||||||
@@ -4269,8 +4280,4 @@ 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í.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1961,6 +1961,17 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Ste prepričani, da želite se odklopiti s tega naprave?';
|
'Ste prepričani, da želite se odklopiti s tega naprave?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Prekinjeno';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Prekinjena povezava s vašim spre伴ovalcem.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Povežite se s spremljevalnikom za dostop do funkcij ponavljalnika in strežnika sob.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Ponovni vnos';
|
String get login_repeaterLogin => 'Ponovni vnos';
|
||||||
|
|
||||||
@@ -4267,8 +4278,4 @@ 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.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1950,6 +1950,17 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Är du säker på att du vill koppla från enheten?';
|
'Är du säker på att du vill koppla från enheten?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Ansluten ej';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Du har kopplats från din companion.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Anslut till en sällskapstjänst för att komma åt upprepning och rumsserverfunktioner.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Återuppta Inloggning';
|
String get login_repeaterLogin => 'Återuppta Inloggning';
|
||||||
|
|
||||||
@@ -4241,8 +4252,4 @@ 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.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1972,6 +1972,17 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
String get dialog_disconnectConfirm =>
|
String get dialog_disconnectConfirm =>
|
||||||
'Ви впевнені, що хочете відключитись від цього пристрою?';
|
'Ви впевнені, що хочете відключитись від цього пристрою?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => 'Від’єднано';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage =>
|
||||||
|
'Вас від’єднано від вашого супутника.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion =>
|
||||||
|
'Підключіться до супутнього пристрою, щоб отримати доступ до функцій ретранслятора та сервера кімнат.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => 'Вхід у ретранслятор';
|
String get login_repeaterLogin => 'Вхід у ретранслятор';
|
||||||
|
|
||||||
@@ -4304,8 +4315,4 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get contact_typeUnknown => 'Невідомо';
|
String get contact_typeUnknown => 'Невідомо';
|
||||||
|
|
||||||
@override
|
|
||||||
String get contact_connectCompanion =>
|
|
||||||
'Підключіться до супутнього пристрою, щоб отримати доступ до функцій ретранслятора та сервера кімнат.';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1861,6 +1861,15 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get dialog_disconnectConfirm => '确定要断开与此设备的连接吗?';
|
String get dialog_disconnectConfirm => '确定要断开与此设备的连接吗?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedTitle => '已断开连接';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_disconnectedMessage => '你已与你的伙伴断开连接。';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialog_connectCompanion => '连接伴机以访问中继器和房间服务器功能。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get login_repeaterLogin => '转发节点登录';
|
String get login_repeaterLogin => '转发节点登录';
|
||||||
|
|
||||||
@@ -3938,7 +3947,4 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get contact_typeUnknown => 'Unknown';
|
String get contact_typeUnknown => 'Unknown';
|
||||||
|
|
||||||
@override
|
|
||||||
String get contact_connectCompanion => '连接伴机以访问中继器和房间服务器功能。';
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2312,5 +2312,8 @@
|
|||||||
"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."
|
"dialog_connectCompanion": "Maak verbinding met een companion om repeater- en kamerserverfuncties te gebruiken.",
|
||||||
|
"dialog_disconnectedTitle": "Verbroken",
|
||||||
|
"dialog_disconnectedMessage": "Je bent losgekoppeld van je companion.",
|
||||||
|
"contact_connectCompanion": "Maak verbinding met een companion om toegang te krijgen tot repeater- en roomserverfuncties."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2350,5 +2350,8 @@
|
|||||||
"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."
|
"dialog_connectCompanion": "Połącz się z towarzyszem, aby uzyskać dostęp do funkcji powtarzacza i serwera pokoi.",
|
||||||
|
"dialog_disconnectedTitle": "Rozłączono",
|
||||||
|
"dialog_disconnectedMessage": "Zostałeś rozłączony ze swoim towarzyszem.",
|
||||||
|
"contact_connectCompanion": "Połącz się z towarzyszem, aby uzyskać dostęp do funkcji repeatera i serwera pokojowego."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2312,5 +2312,8 @@
|
|||||||
"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."
|
"dialog_connectCompanion": "Conecte-se a um dispositivo companion para acessar as funcionalidades de repetidor e servidor de salas.",
|
||||||
|
"dialog_disconnectedTitle": "Desconectado",
|
||||||
|
"dialog_disconnectedMessage": "Você foi desconectado do seu companheiro.",
|
||||||
|
"contact_connectCompanion": "Conecte-se a um companheiro para acessar recursos de repetidor e servidor de sala."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -1615,5 +1615,8 @@
|
|||||||
"settings_companionDebugLogSubtitle": "Команды, ответы и необработанные данные, используемые для протоколов BLE, TCP и USB.",
|
"settings_companionDebugLogSubtitle": "Команды, ответы и необработанные данные, используемые для протоколов BLE, TCP и USB.",
|
||||||
"repeater_chanUtil": "Использование канала",
|
"repeater_chanUtil": "Использование канала",
|
||||||
"settings_companionDebugLog": "Журнал отладки (для сопутствующего приложения)",
|
"settings_companionDebugLog": "Журнал отладки (для сопутствующего приложения)",
|
||||||
"contact_connectCompanion": "Подключитесь к компаньону, чтобы получить доступ к функциям ретранслятора и сервера комнат."
|
"dialog_connectCompanion": "Подключитесь к компаньону, чтобы получить доступ к функциям ретранслятора и сервера комнат.",
|
||||||
|
"dialog_disconnectedTitle": "Отключено",
|
||||||
|
"dialog_disconnectedMessage": "Вы были отключены от вашего компаньона.",
|
||||||
|
"contact_connectCompanion": "Подключитесь к компаньону, чтобы получить доступ к функциям репитера и серверу комнаты."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2312,5 +2312,8 @@
|
|||||||
"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í."
|
"dialog_connectCompanion": "Pripojte sa k sprievodcovi a získajte prístup k funkciám opakovača a serveru miestností.",
|
||||||
|
"dialog_disconnectedTitle": "Odpojené",
|
||||||
|
"dialog_disconnectedMessage": "Od vášho spoločníka ste boli odpojený.",
|
||||||
|
"contact_connectCompanion": "Pripojte sa k spoločníkovi pre prístup k funkciám opakovača a miestneho servera."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2312,5 +2312,8 @@
|
|||||||
"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."
|
"dialog_connectCompanion": "Povežite se s spremljevalnikom za dostop do funkcij ponavljalnika in strežnika sob.",
|
||||||
|
"dialog_disconnectedTitle": "Prekinjeno",
|
||||||
|
"dialog_disconnectedMessage": "Prekinjena povezava s vašim spre伴ovalcem.",
|
||||||
|
"contact_connectCompanion": "Povežite se s ponсоbnikom za dostop do funkcij pon 반복nika in strežnika prostorov."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2312,5 +2312,8 @@
|
|||||||
"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."
|
"dialog_connectCompanion": "Anslut till en sällskapstjänst för att komma åt upprepning och rumsserverfunktioner.",
|
||||||
|
"dialog_disconnectedTitle": "Ansluten ej",
|
||||||
|
"dialog_disconnectedMessage": "Du har kopplats från din companion.",
|
||||||
|
"contact_connectCompanion": "Anslut till en companion för att få tillgång till repeater- och rumsserverfunktioner."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2292,5 +2292,8 @@
|
|||||||
"chat_newMessages": "Нові повідомлення",
|
"chat_newMessages": "Нові повідомлення",
|
||||||
"chat_markAsUnread": "Позначити як непрочитане",
|
"chat_markAsUnread": "Позначити як непрочитане",
|
||||||
"repeater_chanUtil": "Використання каналу",
|
"repeater_chanUtil": "Використання каналу",
|
||||||
"contact_connectCompanion": "Підключіться до супутнього пристрою, щоб отримати доступ до функцій ретранслятора та сервера кімнат."
|
"dialog_connectCompanion": "Підключіться до супутнього пристрою, щоб отримати доступ до функцій ретранслятора та сервера кімнат.",
|
||||||
|
"dialog_disconnectedTitle": "Від’єднано",
|
||||||
|
"dialog_disconnectedMessage": "Вас від’єднано від вашого супутника.",
|
||||||
|
"contact_connectCompanion": "Підключіться до супутника, щоб отримати доступ до функцій репітера та серверів кімнат."
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2317,5 +2317,8 @@
|
|||||||
"chat_newMessages": "新的消息",
|
"chat_newMessages": "新的消息",
|
||||||
"settings_companionDebugLogSubtitle": "BLE/TCP/USB 协议、响应和原始数据",
|
"settings_companionDebugLogSubtitle": "BLE/TCP/USB 协议、响应和原始数据",
|
||||||
"repeater_chanUtil": "频道利用率",
|
"repeater_chanUtil": "频道利用率",
|
||||||
"contact_connectCompanion": "连接伴机以访问中继器和房间服务器功能。"
|
"dialog_connectCompanion": "连接伴机以访问中继器和房间服务器功能。",
|
||||||
|
"dialog_disconnectedTitle": "已断开连接",
|
||||||
|
"dialog_disconnectedMessage": "你已与你的伙伴断开连接。",
|
||||||
|
"contact_connectCompanion": "连接至伴侣设备以访问中继器和房间服务器功能。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import 'dart:typed_data';
|
|||||||
import 'package:crypto/crypto.dart' as crypto;
|
import 'package:crypto/crypto.dart' as crypto;
|
||||||
|
|
||||||
import '../connector/meshcore_protocol.dart';
|
import '../connector/meshcore_protocol.dart';
|
||||||
|
import 'community.dart';
|
||||||
|
|
||||||
|
enum ChannelType { public, private, hashtag, communityPublic, communityHashtag }
|
||||||
|
|
||||||
class Channel {
|
class Channel {
|
||||||
final int index;
|
final int index;
|
||||||
@@ -111,5 +114,36 @@ class Channel {
|
|||||||
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isCommunityChannel(ChannelType channelType) {
|
||||||
|
switch (channelType) {
|
||||||
|
case ChannelType.communityPublic:
|
||||||
|
case ChannelType.communityHashtag:
|
||||||
|
return true;
|
||||||
|
case ChannelType.public:
|
||||||
|
case ChannelType.private:
|
||||||
|
case ChannelType.hashtag:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ChannelType getChannelType(
|
||||||
|
Channel channel,
|
||||||
|
CommunityPskIndex communityIndex,
|
||||||
|
) {
|
||||||
|
Community? community = communityIndex.getCommunityForChannel(channel);
|
||||||
|
if (community != null) {
|
||||||
|
if (Community.isCommunityPublicChannel(channel, community)) {
|
||||||
|
return ChannelType.communityPublic;
|
||||||
|
}
|
||||||
|
return ChannelType.communityHashtag;
|
||||||
|
}
|
||||||
|
if (channel.isPublicChannel) {
|
||||||
|
return ChannelType.public;
|
||||||
|
} else if (channel.name.startsWith('#')) {
|
||||||
|
return ChannelType.hashtag;
|
||||||
|
}
|
||||||
|
return ChannelType.private;
|
||||||
|
}
|
||||||
|
|
||||||
static const String publicChannelPsk = '8b3387e9c5cdea6ac9e5edbaa115cd72';
|
static const String publicChannelPsk = '8b3387e9c5cdea6ac9e5edbaa115cd72';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:crypto/crypto.dart' as crypto;
|
import 'package:crypto/crypto.dart' as crypto;
|
||||||
|
|
||||||
|
import 'channel.dart';
|
||||||
|
|
||||||
/// Represents a community with a shared secret for deriving channel PSKs.
|
/// Represents a community with a shared secret for deriving channel PSKs.
|
||||||
///
|
///
|
||||||
/// A Community is a namespace with a shared secret K (32 random bytes),
|
/// A Community is a namespace with a shared secret K (32 random bytes),
|
||||||
@@ -162,6 +164,12 @@ class Community {
|
|||||||
return hashtag.replaceFirst(RegExp(r'^#'), '').toLowerCase().trim();
|
return hashtag.replaceFirst(RegExp(r'^#'), '').toLowerCase().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this is the community's public channel
|
||||||
|
static bool isCommunityPublicChannel(Channel channel, Community community) {
|
||||||
|
final publicPsk = community.deriveCommunityPublicPsk();
|
||||||
|
return channel.pskHex == Channel.formatPskHex(publicPsk);
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a hashtag channel to this community's list
|
/// Add a hashtag channel to this community's list
|
||||||
Community addHashtagChannel(String hashtag) {
|
Community addHashtagChannel(String hashtag) {
|
||||||
final normalized = _normalizeCommunityHashtag(hashtag);
|
final normalized = _normalizeCommunityHashtag(hashtag);
|
||||||
@@ -237,3 +245,28 @@ class Community {
|
|||||||
@override
|
@override
|
||||||
int get hashCode => id.hashCode;
|
int get hashCode => id.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CommunityPskIndex {
|
||||||
|
// Cache of PSK hex -> Community for quick lookup
|
||||||
|
final Map<String, Community> _pskToCommunity = {};
|
||||||
|
|
||||||
|
void initialize(List<Community> communities) {
|
||||||
|
_pskToCommunity.clear();
|
||||||
|
for (final community in communities) {
|
||||||
|
// Map the community public channel PSK
|
||||||
|
final publicPsk = community.deriveCommunityPublicPsk();
|
||||||
|
_pskToCommunity[Channel.formatPskHex(publicPsk)] = community;
|
||||||
|
|
||||||
|
// Map all known hashtag channel PSKs
|
||||||
|
for (final hashtag in community.hashtagChannels) {
|
||||||
|
final hashtagPsk = community.deriveCommunityHashtagPsk(hashtag);
|
||||||
|
_pskToCommunity[Channel.formatPskHex(hashtagPsk)] = community;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the community this channel belongs to, or null if not a community channel
|
||||||
|
Community? getCommunityForChannel(Channel channel) {
|
||||||
|
return _pskToCommunity[channel.pskHex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class PathRecord {
|
|||||||
successCount: json['success_count'] as int? ?? 0,
|
successCount: json['success_count'] as int? ?? 0,
|
||||||
failureCount: json['failure_count'] as int? ?? 0,
|
failureCount: json['failure_count'] as int? ?? 0,
|
||||||
routeWeight: (json['route_weight'] as num?)?.toDouble() ?? 1.0,
|
routeWeight: (json['route_weight'] as num?)?.toDouble() ?? 1.0,
|
||||||
|
byteCount: json['byte_count'] as int? ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../connector/meshcore_connector.dart';
|
import '../connector/meshcore_connector.dart';
|
||||||
|
import '../models/community.dart';
|
||||||
|
import '../storage/community_store.dart';
|
||||||
import '../utils/platform_info.dart';
|
import '../utils/platform_info.dart';
|
||||||
import '../helpers/chat_scroll_controller.dart';
|
import '../helpers/chat_scroll_controller.dart';
|
||||||
import '../connector/meshcore_protocol.dart';
|
import '../connector/meshcore_protocol.dart';
|
||||||
@@ -56,8 +58,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
final ChatScrollController _scrollController = ChatScrollController();
|
final ChatScrollController _scrollController = ChatScrollController();
|
||||||
final FocusNode _textFieldFocusNode = FocusNode();
|
final FocusNode _textFieldFocusNode = FocusNode();
|
||||||
ChannelMessage? _replyingToMessage;
|
ChannelMessage? _replyingToMessage;
|
||||||
|
final CommunityStore _communityStore = CommunityStore();
|
||||||
|
final CommunityPskIndex _communityIndex = CommunityPskIndex();
|
||||||
final Map<String, GlobalKey> _messageKeys = {};
|
final Map<String, GlobalKey> _messageKeys = {};
|
||||||
bool _isLoadingOlder = false;
|
bool _isLoadingOlder = false;
|
||||||
|
bool _communitiesLoaded = false;
|
||||||
|
|
||||||
MeshCoreConnector? _connector;
|
MeshCoreConnector? _connector;
|
||||||
DateTime? _lastChannelSendAt;
|
DateTime? _lastChannelSendAt;
|
||||||
@@ -81,6 +86,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
final idx = widget.channel.index;
|
final idx = widget.channel.index;
|
||||||
final unread = widget.initialUnreadCount;
|
final unread = widget.initialUnreadCount;
|
||||||
final messages = connector.getChannelMessages(widget.channel);
|
final messages = connector.getChannelMessages(widget.channel);
|
||||||
|
_loadCommunities();
|
||||||
ChannelMessage? anchor;
|
ChannelMessage? anchor;
|
||||||
if (unread > 0) {
|
if (unread > 0) {
|
||||||
anchor = _findOldestUnreadChannelAnchor(messages, unread);
|
anchor = _findOldestUnreadChannelAnchor(messages, unread);
|
||||||
@@ -107,6 +113,19 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Reload communities when returning from another screen
|
||||||
|
Future<void> _loadCommunities() async {
|
||||||
|
final connector = context.read<MeshCoreConnector>();
|
||||||
|
_communityStore.setPublicKeyHex = connector.selfPublicKeyHex;
|
||||||
|
final communities = await _communityStore.loadCommunities();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_communityIndex.initialize(communities);
|
||||||
|
_communitiesLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ChannelMessage? _findOldestUnreadChannelAnchor(
|
ChannelMessage? _findOldestUnreadChannelAnchor(
|
||||||
List<ChannelMessage> messages,
|
List<ChannelMessage> messages,
|
||||||
int unreadCount,
|
int unreadCount,
|
||||||
@@ -193,16 +212,63 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _channelIcon(Channel channel) {
|
||||||
|
// Determine icon based on channel type
|
||||||
|
final ChannelType channelType = Channel.getChannelType(
|
||||||
|
channel,
|
||||||
|
_communityIndex,
|
||||||
|
);
|
||||||
|
final bool isCommunityChannel = Channel.isCommunityChannel(channelType);
|
||||||
|
IconData icon;
|
||||||
|
switch (channelType) {
|
||||||
|
case ChannelType.communityPublic:
|
||||||
|
icon = Icons.groups;
|
||||||
|
case ChannelType.communityHashtag:
|
||||||
|
icon = Icons.tag;
|
||||||
|
case ChannelType.public:
|
||||||
|
icon = Icons.public;
|
||||||
|
case ChannelType.hashtag:
|
||||||
|
icon = Icons.tag;
|
||||||
|
case ChannelType.private:
|
||||||
|
icon = Icons.lock;
|
||||||
|
}
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 3),
|
||||||
|
child: _communitiesLoaded
|
||||||
|
? Icon(icon, size: 20)
|
||||||
|
: SizedBox.square(dimension: 20),
|
||||||
|
),
|
||||||
|
if (isCommunityChannel)
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: Container(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.purple,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).cardColor,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.people, size: 8, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
_channelIcon(widget.channel),
|
||||||
widget.channel.isPublicChannel ? Icons.public : Icons.tag,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -311,6 +377,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
final reversedMessages = messages.reversed.toList();
|
final reversedMessages = messages.reversed.toList();
|
||||||
final itemCount =
|
final itemCount =
|
||||||
reversedMessages.length + (_isLoadingOlder ? 1 : 0);
|
reversedMessages.length + (_isLoadingOlder ? 1 : 0);
|
||||||
|
final keyedMessageIds = <String>{};
|
||||||
|
|
||||||
// Auto-scroll to bottom if user is already at bottom
|
// Auto-scroll to bottom if user is already at bottom
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
@@ -347,14 +414,20 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
}
|
}
|
||||||
final messageIndex = index;
|
final messageIndex = index;
|
||||||
final message = reversedMessages[messageIndex];
|
final message = reversedMessages[messageIndex];
|
||||||
if (!_messageKeys.containsKey(message.messageId)) {
|
final shouldAttachMessageKey = keyedMessageIds.add(
|
||||||
|
message.messageId,
|
||||||
|
);
|
||||||
|
if (shouldAttachMessageKey &&
|
||||||
|
!_messageKeys.containsKey(message.messageId)) {
|
||||||
_messageKeys[message.messageId] = GlobalKey();
|
_messageKeys[message.messageId] = GlobalKey();
|
||||||
}
|
}
|
||||||
final isUnreadAnchor =
|
final isUnreadAnchor =
|
||||||
_unreadDividerMessageId != null &&
|
_unreadDividerMessageId != null &&
|
||||||
message.messageId == _unreadDividerMessageId;
|
message.messageId == _unreadDividerMessageId;
|
||||||
return Container(
|
return Container(
|
||||||
key: _messageKeys[message.messageId]!,
|
key: shouldAttachMessageKey
|
||||||
|
? _messageKeys[message.messageId]
|
||||||
|
: null,
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final textScale = context
|
final textScale = context
|
||||||
|
|||||||
@@ -40,15 +40,12 @@ class ChannelsScreen extends StatefulWidget {
|
|||||||
State<ChannelsScreen> createState() => _ChannelsScreenState();
|
State<ChannelsScreen> createState() => _ChannelsScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelsScreenState extends State<ChannelsScreen>
|
class _ChannelsScreenState extends State<ChannelsScreen> {
|
||||||
{
|
|
||||||
final TextEditingController _searchController = TextEditingController();
|
final TextEditingController _searchController = TextEditingController();
|
||||||
final CommunityStore _communityStore = CommunityStore();
|
final CommunityStore _communityStore = CommunityStore();
|
||||||
Timer? _searchDebounce;
|
final CommunityPskIndex _communityIndex = CommunityPskIndex();
|
||||||
List<Community> _communities = [];
|
List<Community> _communities = [];
|
||||||
|
Timer? _searchDebounce;
|
||||||
// Cache of PSK hex -> Community for quick lookup
|
|
||||||
final Map<String, Community> _pskToCommunity = {};
|
|
||||||
|
|
||||||
ChannelMessageStore get _channelMessageStore => ChannelMessageStore();
|
ChannelMessageStore get _channelMessageStore => ChannelMessageStore();
|
||||||
|
|
||||||
@@ -71,37 +68,11 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_communities = communities;
|
_communities = communities;
|
||||||
_buildPskCommunityMap();
|
_communityIndex.initialize(communities);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _buildPskCommunityMap() {
|
|
||||||
_pskToCommunity.clear();
|
|
||||||
for (final community in _communities) {
|
|
||||||
// Map the community public channel PSK
|
|
||||||
final publicPsk = community.deriveCommunityPublicPsk();
|
|
||||||
_pskToCommunity[Channel.formatPskHex(publicPsk)] = community;
|
|
||||||
|
|
||||||
// Map all known hashtag channel PSKs
|
|
||||||
for (final hashtag in community.hashtagChannels) {
|
|
||||||
final hashtagPsk = community.deriveCommunityHashtagPsk(hashtag);
|
|
||||||
_pskToCommunity[Channel.formatPskHex(hashtagPsk)] = community;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the community this channel belongs to, or null if not a community channel
|
|
||||||
Community? _getCommunityForChannel(Channel channel) {
|
|
||||||
return _pskToCommunity[channel.pskHex];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if this is the community's public channel
|
|
||||||
bool _isCommunityPublicChannel(Channel channel, Community community) {
|
|
||||||
final publicPsk = community.deriveCommunityPublicPsk();
|
|
||||||
return channel.pskHex == Channel.formatPskHex(publicPsk);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_searchDebounce?.cancel();
|
_searchDebounce?.cancel();
|
||||||
@@ -372,6 +343,8 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
selectedIndex: 1,
|
selectedIndex: 1,
|
||||||
onDestinationSelected: (index) =>
|
onDestinationSelected: (index) =>
|
||||||
_handleQuickSwitch(index, context),
|
_handleQuickSwitch(index, context),
|
||||||
|
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
|
||||||
|
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -387,41 +360,41 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||||||
int? dragIndex,
|
int? dragIndex,
|
||||||
}) {
|
}) {
|
||||||
final unreadCount = connector.getUnreadCountForChannel(channel);
|
final unreadCount = connector.getUnreadCountForChannel(channel);
|
||||||
final community = _getCommunityForChannel(channel);
|
|
||||||
final isCommunityChannel = community != null;
|
|
||||||
final isCommunityPublic =
|
|
||||||
isCommunityChannel && _isCommunityPublicChannel(channel, community);
|
|
||||||
|
|
||||||
// Determine icon and colors based on channel type
|
// Determine icon and colors based on channel type
|
||||||
IconData icon;
|
IconData icon;
|
||||||
Color iconColor;
|
Color iconColor;
|
||||||
Color bgColor;
|
Color bgColor;
|
||||||
|
final ChannelType channelType = Channel.getChannelType(
|
||||||
if (isCommunityChannel) {
|
channel,
|
||||||
// Community channel styling
|
_communityIndex,
|
||||||
iconColor = Colors.purple;
|
);
|
||||||
bgColor = Colors.purple.withValues(alpha: 0.2);
|
final bool isCommunityChannel = Channel.isCommunityChannel(channelType);
|
||||||
if (isCommunityPublic) {
|
switch (channelType) {
|
||||||
|
case ChannelType.communityPublic:
|
||||||
icon = Icons.groups;
|
icon = Icons.groups;
|
||||||
} else {
|
iconColor = Colors.purple;
|
||||||
|
bgColor = Colors.purple.withValues(alpha: 0.2);
|
||||||
|
case ChannelType.communityHashtag:
|
||||||
icon = Icons.tag;
|
icon = Icons.tag;
|
||||||
}
|
iconColor = Colors.purple;
|
||||||
} else if (channel.isPublicChannel) {
|
bgColor = Colors.purple.withValues(alpha: 0.2);
|
||||||
icon = Icons.public;
|
case ChannelType.public:
|
||||||
iconColor = Colors.green;
|
icon = Icons.public;
|
||||||
bgColor = Colors.green.withValues(alpha: 0.2);
|
iconColor = Colors.green;
|
||||||
} else if (channel.name.startsWith('#')) {
|
bgColor = Colors.green.withValues(alpha: 0.2);
|
||||||
icon = Icons.tag;
|
case ChannelType.hashtag:
|
||||||
iconColor = Colors.blue;
|
icon = Icons.tag;
|
||||||
bgColor = Colors.blue.withValues(alpha: 0.2);
|
iconColor = Colors.blue;
|
||||||
} else {
|
bgColor = Colors.blue.withValues(alpha: 0.2);
|
||||||
icon = Icons.lock;
|
case ChannelType.private:
|
||||||
iconColor = Colors.blue;
|
icon = Icons.lock;
|
||||||
bgColor = Colors.blue.withValues(alpha: 0.2);
|
iconColor = Colors.blue;
|
||||||
|
bgColor = Colors.blue.withValues(alpha: 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
key: ValueKey('channel_${channel.index}'),
|
key: ValueKey('${channel.index}_${channel.pskHex}_${channel.name}'),
|
||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onSecondaryTapUp: PlatformInfo.isDesktop
|
onSecondaryTapUp: PlatformInfo.isDesktop
|
||||||
|
|||||||
@@ -451,6 +451,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
) {
|
) {
|
||||||
// Reverse messages so newest appear at bottom with reverse: true
|
// Reverse messages so newest appear at bottom with reverse: true
|
||||||
final reversedMessages = messages.reversed.toList();
|
final reversedMessages = messages.reversed.toList();
|
||||||
|
var unreadAnchorKeyAssigned = false;
|
||||||
final itemCount = reversedMessages.length + (_isLoadingOlder ? 1 : 0);
|
final itemCount = reversedMessages.length + (_isLoadingOlder ? 1 : 0);
|
||||||
|
|
||||||
// Auto-scroll to bottom if user is already at bottom
|
// Auto-scroll to bottom if user is already at bottom
|
||||||
@@ -525,7 +526,11 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
children: [const UnreadDivider(), bubble],
|
children: [const UnreadDivider(), bubble],
|
||||||
)
|
)
|
||||||
: bubble;
|
: bubble;
|
||||||
if (identical(message, _pendingUnreadScrollTarget)) {
|
final shouldAttachUnreadScrollKey =
|
||||||
|
!unreadAnchorKeyAssigned &&
|
||||||
|
identical(message, _pendingUnreadScrollTarget);
|
||||||
|
if (shouldAttachUnreadScrollKey) {
|
||||||
|
unreadAnchorKeyAssigned = true;
|
||||||
return KeyedSubtree(key: _unreadScrollKey, child: child);
|
return KeyedSubtree(key: _unreadScrollKey, child: child);
|
||||||
}
|
}
|
||||||
return child;
|
return child;
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ class ContactsScreen extends StatefulWidget {
|
|||||||
State<ContactsScreen> createState() => _ContactsScreenState();
|
State<ContactsScreen> createState() => _ContactsScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ContactsScreenState extends State<ContactsScreen>
|
class _ContactsScreenState extends State<ContactsScreen> {
|
||||||
{
|
|
||||||
final TextEditingController _searchController = TextEditingController();
|
final TextEditingController _searchController = TextEditingController();
|
||||||
final ContactGroupStore _groupStore = ContactGroupStore();
|
final ContactGroupStore _groupStore = ContactGroupStore();
|
||||||
MeshCoreConnector? _scopeSyncConnector;
|
MeshCoreConnector? _scopeSyncConnector;
|
||||||
@@ -314,63 +313,64 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
title: AppBarTitle(context.l10n.contacts_title),
|
title: AppBarTitle(context.l10n.contacts_title),
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
actions: [
|
actions: [
|
||||||
PopupMenuButton(
|
if (connector.isConnected)
|
||||||
itemBuilder: (context) => [
|
PopupMenuButton(
|
||||||
PopupMenuItem(
|
itemBuilder: (context) => [
|
||||||
child: Row(
|
PopupMenuItem(
|
||||||
children: [
|
child: Row(
|
||||||
const Icon(Icons.connect_without_contact),
|
children: [
|
||||||
const SizedBox(width: 8),
|
const Icon(Icons.connect_without_contact),
|
||||||
Text(context.l10n.contacts_zeroHopAdvert),
|
const SizedBox(width: 8),
|
||||||
],
|
Text(context.l10n.contacts_zeroHopAdvert),
|
||||||
),
|
],
|
||||||
onTap: () => {
|
|
||||||
connector.sendSelfAdvert(flood: false),
|
|
||||||
showDismissibleSnackBar(
|
|
||||||
context,
|
|
||||||
content: Text(context.l10n.settings_advertisementSent),
|
|
||||||
),
|
),
|
||||||
},
|
onTap: () async {
|
||||||
),
|
await connector.sendSelfAdvert(flood: false);
|
||||||
PopupMenuItem(
|
showDismissibleSnackBar(
|
||||||
child: Row(
|
context,
|
||||||
children: [
|
content: Text(context.l10n.settings_advertisementSent),
|
||||||
const Icon(Icons.cell_tower),
|
);
|
||||||
const SizedBox(width: 8),
|
},
|
||||||
Text(context.l10n.contacts_floodAdvert),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
onTap: () => {
|
PopupMenuItem(
|
||||||
connector.sendSelfAdvert(flood: true),
|
child: Row(
|
||||||
showDismissibleSnackBar(
|
children: [
|
||||||
context,
|
const Icon(Icons.cell_tower),
|
||||||
content: Text(context.l10n.settings_advertisementSent),
|
const SizedBox(width: 8),
|
||||||
|
Text(context.l10n.contacts_floodAdvert),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
},
|
onTap: () async {
|
||||||
),
|
await connector.sendSelfAdvert(flood: true);
|
||||||
PopupMenuItem(
|
showDismissibleSnackBar(
|
||||||
child: Row(
|
context,
|
||||||
children: [
|
content: Text(context.l10n.settings_advertisementSent),
|
||||||
const Icon(Icons.copy),
|
);
|
||||||
const SizedBox(width: 8),
|
},
|
||||||
Text(context.l10n.contacts_copyAdvertToClipboard),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
onTap: () => _contactExport(Uint8List.fromList([])),
|
PopupMenuItem(
|
||||||
),
|
child: Row(
|
||||||
PopupMenuItem(
|
children: [
|
||||||
child: Row(
|
const Icon(Icons.copy),
|
||||||
children: [
|
const SizedBox(width: 8),
|
||||||
const Icon(Icons.paste),
|
Text(context.l10n.contacts_copyAdvertToClipboard),
|
||||||
const SizedBox(width: 8),
|
],
|
||||||
Text(context.l10n.contacts_addContactFromClipboard),
|
),
|
||||||
],
|
onTap: () => _contactExport(Uint8List.fromList([])),
|
||||||
),
|
),
|
||||||
onTap: () => _contactImport(),
|
PopupMenuItem(
|
||||||
),
|
child: Row(
|
||||||
],
|
children: [
|
||||||
icon: const Icon(Icons.connect_without_contact),
|
const Icon(Icons.paste),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
|
Text(context.l10n.contacts_addContactFromClipboard),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => _contactImport(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
icon: const Icon(Icons.connect_without_contact),
|
||||||
|
),
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
if (connector.isConnected)
|
if (connector.isConnected)
|
||||||
@@ -442,6 +442,8 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
selectedIndex: 0,
|
selectedIndex: 0,
|
||||||
onDestinationSelected: (index) =>
|
onDestinationSelected: (index) =>
|
||||||
_handleQuickSwitch(index, context),
|
_handleQuickSwitch(index, context),
|
||||||
|
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
|
||||||
|
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -826,6 +828,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
contact,
|
contact,
|
||||||
);
|
);
|
||||||
return _ContactTile(
|
return _ContactTile(
|
||||||
|
key: ValueKey(contact.publicKeyHex),
|
||||||
contact: contact,
|
contact: contact,
|
||||||
lastSeen: _resolveLastSeen(contact),
|
lastSeen: _resolveLastSeen(contact),
|
||||||
unreadCount: unreadCount,
|
unreadCount: unreadCount,
|
||||||
@@ -1046,7 +1049,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) => AlertDialog(
|
builder: (dialogContext) => AlertDialog(
|
||||||
title: Text(context.l10n.scanner_notConnected),
|
title: Text(context.l10n.scanner_notConnected),
|
||||||
content: Text(context.l10n.contact_connectCompanion),
|
content: Text(context.l10n.dialog_connectCompanion),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(dialogContext),
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
@@ -1494,6 +1497,7 @@ class _ContactTile extends StatelessWidget {
|
|||||||
final VoidCallback onLongPress;
|
final VoidCallback onLongPress;
|
||||||
|
|
||||||
const _ContactTile({
|
const _ContactTile({
|
||||||
|
super.key,
|
||||||
required this.contact,
|
required this.contact,
|
||||||
required this.lastSeen,
|
required this.lastSeen,
|
||||||
required this.unreadCount,
|
required this.unreadCount,
|
||||||
|
|||||||
@@ -539,6 +539,12 @@ class _LineOfSightMapScreenState extends State<LineOfSightMapScreen> {
|
|||||||
child: QuickSwitchBar(
|
child: QuickSwitchBar(
|
||||||
selectedIndex: 2,
|
selectedIndex: 2,
|
||||||
onDestinationSelected: (index) => _handleQuickSwitch(index, context),
|
onDestinationSelected: (index) => _handleQuickSwitch(index, context),
|
||||||
|
contactsUnreadCount: context
|
||||||
|
.watch<MeshCoreConnector>()
|
||||||
|
.getTotalContactsUnreadCount(),
|
||||||
|
channelsUnreadCount: context
|
||||||
|
.watch<MeshCoreConnector>()
|
||||||
|
.getTotalChannelsUnreadCount(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -416,7 +416,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
actions: [
|
actions: [
|
||||||
if (!_isBuildingPathTrace)
|
if (!_isBuildingPathTrace && connector.isConnected)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.radar),
|
icon: const Icon(Icons.radar),
|
||||||
onPressed: () => _startPath(
|
onPressed: () => _startPath(
|
||||||
@@ -694,6 +694,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
selectedIndex: 2,
|
selectedIndex: 2,
|
||||||
onDestinationSelected: (index) =>
|
onDestinationSelected: (index) =>
|
||||||
_handleQuickSwitch(index, context),
|
_handleQuickSwitch(index, context),
|
||||||
|
contactsUnreadCount: connector.getTotalContactsUnreadCount(),
|
||||||
|
channelsUnreadCount: connector.getTotalChannelsUnreadCount(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
@@ -1498,7 +1500,28 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showCompanionRequiredDialog(BuildContext context) {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (dialogContext) => AlertDialog(
|
||||||
|
title: Text(context.l10n.scanner_notConnected),
|
||||||
|
content: Text(context.l10n.dialog_connectCompanion),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(dialogContext),
|
||||||
|
child: Text(context.l10n.common_ok),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
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(
|
||||||
@@ -1521,6 +1544,11 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showRoomLogin(BuildContext context, Contact room) {
|
void _showRoomLogin(BuildContext context, Contact room) {
|
||||||
|
final connector = context.read<MeshCoreConnector>();
|
||||||
|
if (!connector.isConnected) {
|
||||||
|
_showCompanionRequiredDialog(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => RoomLoginDialog(
|
builder: (context) => RoomLoginDialog(
|
||||||
|
|||||||
@@ -42,9 +42,18 @@ class ChannelStore {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||||
return jsonList
|
final channels = jsonList
|
||||||
.map((entry) => _fromJson(entry as Map<String, dynamic>))
|
.map((entry) => _fromJson(entry as Map<String, dynamic>))
|
||||||
.toList();
|
.toList();
|
||||||
|
// Deduplicate: keep the last entry per channel index
|
||||||
|
final seen = <int>{};
|
||||||
|
final deduped = <Channel>[];
|
||||||
|
for (final channel in channels.reversed) {
|
||||||
|
if (seen.add(channel.index)) {
|
||||||
|
deduped.add(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deduped.reversed.toList();
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,21 @@ Future<bool> showDisconnectDialog(
|
|||||||
if (confirmed == true) {
|
if (confirmed == true) {
|
||||||
appLogger.info('Disconnect confirmed from popup', tag: 'Connection');
|
appLogger.info('Disconnect confirmed from popup', tag: 'Connection');
|
||||||
await connector.disconnect();
|
await connector.disconnect();
|
||||||
|
if (context.mounted) {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text(context.l10n.dialog_disconnectedTitle),
|
||||||
|
content: Text(context.l10n.dialog_disconnectedMessage),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(context.l10n.common_ok),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ class AppBarTitle extends StatelessWidget {
|
|||||||
if (showBattery) BatteryIndicator(connector: connector),
|
if (showBattery) BatteryIndicator(connector: connector),
|
||||||
if (showSnr) SNRIndicator(connector: connector),
|
if (showSnr) SNRIndicator(connector: connector),
|
||||||
if (connector.supportsCompanionRadioStats)
|
if (connector.supportsCompanionRadioStats)
|
||||||
const RadioStatsIconButton(compact: true),
|
if (connector.isConnected)
|
||||||
|
const RadioStatsIconButton(compact: true),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
trailing ?? const SizedBox.shrink(),
|
trailing ?? const SizedBox.shrink(),
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ import '../l10n/l10n.dart';
|
|||||||
class QuickSwitchBar extends StatelessWidget {
|
class QuickSwitchBar extends StatelessWidget {
|
||||||
final int selectedIndex;
|
final int selectedIndex;
|
||||||
final ValueChanged<int> onDestinationSelected;
|
final ValueChanged<int> onDestinationSelected;
|
||||||
|
final int contactsUnreadCount;
|
||||||
|
final int channelsUnreadCount;
|
||||||
|
|
||||||
const QuickSwitchBar({
|
const QuickSwitchBar({
|
||||||
super.key,
|
super.key,
|
||||||
required this.selectedIndex,
|
required this.selectedIndex,
|
||||||
required this.onDestinationSelected,
|
required this.onDestinationSelected,
|
||||||
|
this.contactsUnreadCount = 0,
|
||||||
|
this.channelsUnreadCount = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -62,15 +66,30 @@ class QuickSwitchBar extends StatelessWidget {
|
|||||||
onDestinationSelected: onDestinationSelected,
|
onDestinationSelected: onDestinationSelected,
|
||||||
destinations: [
|
destinations: [
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: const Icon(Icons.people_outline),
|
icon: _buildIconWithBadge(
|
||||||
|
const Icon(Icons.people_outline),
|
||||||
|
contactsUnreadCount,
|
||||||
|
),
|
||||||
|
selectedIcon: _buildIconWithBadge(
|
||||||
|
const Icon(Icons.people),
|
||||||
|
contactsUnreadCount,
|
||||||
|
),
|
||||||
label: context.l10n.nav_contacts,
|
label: context.l10n.nav_contacts,
|
||||||
),
|
),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: const Icon(Icons.tag),
|
icon: _buildIconWithBadge(
|
||||||
|
const Icon(Icons.tag),
|
||||||
|
channelsUnreadCount,
|
||||||
|
),
|
||||||
|
selectedIcon: _buildIconWithBadge(
|
||||||
|
const Icon(Icons.tag),
|
||||||
|
channelsUnreadCount,
|
||||||
|
),
|
||||||
label: context.l10n.nav_channels,
|
label: context.l10n.nav_channels,
|
||||||
),
|
),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: const Icon(Icons.map_outlined),
|
icon: const Icon(Icons.map_outlined),
|
||||||
|
selectedIcon: const Icon(Icons.map),
|
||||||
label: context.l10n.nav_map,
|
label: context.l10n.nav_map,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -81,4 +100,27 @@ class QuickSwitchBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildIconWithBadge(Icon icon, int count) {
|
||||||
|
if (count <= 0) return icon;
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
icon,
|
||||||
|
Positioned(
|
||||||
|
right: -2,
|
||||||
|
top: -2,
|
||||||
|
child: Container(
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.redAccent,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ class _FakeMeshCoreConnector extends MeshCoreConnector {
|
|||||||
lastHost = host;
|
lastHost = host;
|
||||||
lastPort = port;
|
lastPort = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> disconnect({
|
||||||
|
bool manual = true,
|
||||||
|
bool skipBleDeviceDisconnect = false,
|
||||||
|
}) async {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTestApp({
|
Widget _buildTestApp({
|
||||||
|
|||||||
@@ -73,6 +73,12 @@ class _FakeMeshCoreConnector extends MeshCoreConnector {
|
|||||||
void setUsbFallbackDeviceName(String label) {
|
void setUsbFallbackDeviceName(String label) {
|
||||||
fallbackDeviceName = label;
|
fallbackDeviceName = label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> disconnect({
|
||||||
|
bool manual = true,
|
||||||
|
bool skipBleDeviceDisconnect = false,
|
||||||
|
}) async {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTestApp({
|
Widget _buildTestApp({
|
||||||
|
|||||||
+422
-930
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user