mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-04-20 22:13:48 +00:00
Refactor contact handling and other improvments (#317)
* Refactor contact filtering and improve localization strings; enhance path trace handling * Add localization for new CLI commands and update existing strings * Enhance contact handling and UI updates across multiple screens add unfiltered contact access and improve last seen resolution * Add polling interval configuration and improve contact handling * Reorder command constants for better organization and clarity * Refactor contact handling by removing unnecessary mapping and improving clarity across multiple screens * Moved RadioStatsIconButton in chat screen for improved UI consistency * Added indicators to AppBar for channels * Ignore contacts with self public key in contact handling * Simplify path removal logic and clean up unused imports in path management dialog * Enhance path hop resolution by adding distance checks to improve candidate selection accuracy * Remove unnecessary reset of radio stats poll reference count in polling interval setter
This commit is contained in:
parent
89a14c2719
commit
4879b136f8
51 changed files with 488 additions and 109 deletions
|
|
@ -196,6 +196,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
static const int _contactMsgBackoffFallbackMs = 5000;
|
||||
static const int _contactMsgBackoffMinMs = 500;
|
||||
static const int _contactMsgBackoffMaxMs = 15000;
|
||||
int _pollingInterval = 30;
|
||||
bool _batteryRequested = false;
|
||||
bool _awaitingSelfInfo = false;
|
||||
bool _hasReceivedDeviceInfo = false;
|
||||
|
|
@ -326,8 +327,14 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
|
||||
List<Contact> get allContacts => List.unmodifiable([
|
||||
..._contacts,
|
||||
..._discoveredContacts.where((c) => !c.isActive),
|
||||
..._discoveredContacts.where(
|
||||
(c) => !c.isActive && c.publicKeyHex != selfPublicKeyHex,
|
||||
),
|
||||
]);
|
||||
|
||||
List<Contact> get allContactsUnfiltered =>
|
||||
List.unmodifiable([..._contacts, ..._discoveredContacts]);
|
||||
|
||||
List<Contact> get discoveredContacts {
|
||||
return List.unmodifiable(_discoveredContacts);
|
||||
}
|
||||
|
|
@ -2368,9 +2375,18 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
_batteryPollTimer = null;
|
||||
}
|
||||
|
||||
void setPollingInterval(int i) {
|
||||
_pollingInterval = i.clamp(1, 60);
|
||||
if (isConnected) {
|
||||
_startRadioStatsPolling();
|
||||
}
|
||||
}
|
||||
|
||||
void _startRadioStatsPolling() {
|
||||
_radioStatsPollTimer?.cancel();
|
||||
_radioStatsPollTimer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
_radioStatsPollTimer = Timer.periodic(Duration(seconds: _pollingInterval), (
|
||||
_,
|
||||
) {
|
||||
if (!isConnected) {
|
||||
_stopRadioStatsPolling();
|
||||
return;
|
||||
|
|
@ -2495,6 +2511,18 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
});
|
||||
}
|
||||
|
||||
Contact getFromDiscovered(Contact contact) {
|
||||
final tmp = _discoveredContacts.firstWhere(
|
||||
(c) => c.publicKeyHex == contact.publicKeyHex,
|
||||
orElse: () => contact,
|
||||
);
|
||||
return contact.copyWith(
|
||||
rawPacket: tmp.rawPacket,
|
||||
latitude: tmp.latitude,
|
||||
longitude: tmp.longitude,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> getContacts({int? since, bool preserveExisting = false}) async {
|
||||
if (!isConnected) return;
|
||||
|
||||
|
|
@ -3885,8 +3913,17 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
}
|
||||
|
||||
void _handleContact(Uint8List frame, {bool isContact = true}) {
|
||||
final contact = Contact.fromFrame(frame);
|
||||
if (contact != null) {
|
||||
final contactTmp = Contact.fromFrame(frame);
|
||||
if (contactTmp != null) {
|
||||
if (listEquals(contactTmp.publicKey, _selfPublicKey)) {
|
||||
appLogger.info(
|
||||
'Ignoring contact with self public key: ${contactTmp.name}',
|
||||
tag: 'Connector',
|
||||
);
|
||||
removeContact(contactTmp);
|
||||
return;
|
||||
}
|
||||
final contact = getFromDiscovered(contactTmp);
|
||||
_handleDiscovery(contact, frame, noNotify: true, addActive: true);
|
||||
|
||||
if (contact.type == advTypeRepeater) {
|
||||
|
|
|
|||
|
|
@ -202,15 +202,15 @@ const int cmdGetChannel = 31;
|
|||
const int cmdSetChannel = 32;
|
||||
const int cmdSendTracePath = 36;
|
||||
const int cmdSetOtherParams = 38;
|
||||
const int cmdSendAnonReq = 57;
|
||||
const int cmdSendTelemetryReq = 39;
|
||||
const int cmdGetCustomVar = 40;
|
||||
const int cmdSetCustomVar = 41;
|
||||
const int cmdSendBinaryReq = 50;
|
||||
const int cmdGetStats = 56;
|
||||
const int cmdSendAnonReq = 57;
|
||||
const int cmdSetAutoAddConfig = 58;
|
||||
const int cmdGetAutoAddConfig = 59;
|
||||
const int cmdSetPathHashMode = 61;
|
||||
const int cmdGetStats = 56;
|
||||
|
||||
// Text message types
|
||||
const int txtTypePlain = 0;
|
||||
|
|
|
|||
|
|
@ -2059,5 +2059,9 @@
|
|||
"translation_composerEnabledHint": "Съобщенията ще бъдат преведени, преди да бъдат изпратени.",
|
||||
"translation_translateTo": "Превеждане на {language}",
|
||||
"translation_translationOptions": "Опции за превод",
|
||||
"translation_systemLanguage": "Език на системата"
|
||||
}
|
||||
"translation_systemLanguage": "Език на системата",
|
||||
"scanner_linuxPairingPinTitle": "PIN код за сдвояване на Bluetooth",
|
||||
"scanner_linuxPairingPinPrompt": "Въведете ПИН за {deviceName} (оставете празно, ако няма).",
|
||||
"repeater_cliQuickClockSync": "Синхронизация на часовника",
|
||||
"repeater_cliQuickDiscovery": "Открий Съседи"
|
||||
}
|
||||
|
|
@ -2087,5 +2087,10 @@
|
|||
"translation_composerDisabledHint": "Nachrichten in der ursprünglichen, getippten Sprache senden.",
|
||||
"translation_translateTo": "Übersetzen Sie auf {language}",
|
||||
"translation_translationOptions": "Übersetzungsmöglichkeiten",
|
||||
"translation_systemLanguage": "Sprache des Systems"
|
||||
}
|
||||
"translation_systemLanguage": "Sprache des Systems",
|
||||
"scanner_linuxPairingHidePin": "PIN ausblenden",
|
||||
"scanner_linuxPairingPinTitle": "Bluetooth-Paarungs-PIN",
|
||||
"scanner_linuxPairingPinPrompt": "Geben Sie die PIN für {deviceName} ein (leer lassen, falls keine).",
|
||||
"repeater_cliQuickClockSync": "Uhr Synchronisieren",
|
||||
"repeater_cliQuickDiscovery": "Entdecke Nachbarn"
|
||||
}
|
||||
|
|
@ -303,8 +303,12 @@
|
|||
"path_routeWeight": "{weight}/{max}",
|
||||
"@path_routeWeight": {
|
||||
"placeholders": {
|
||||
"weight": { "type": "String" },
|
||||
"max": { "type": "String" }
|
||||
"weight": {
|
||||
"type": "String"
|
||||
},
|
||||
"max": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_battery": "Battery",
|
||||
|
|
@ -1333,6 +1337,8 @@
|
|||
"repeater_cliQuickVersion": "Version",
|
||||
"repeater_cliQuickAdvertise": "Advertise",
|
||||
"repeater_cliQuickClock": "Clock",
|
||||
"repeater_cliQuickClockSync": "Clock Sync",
|
||||
"repeater_cliQuickDiscovery": "Discover Neighbors",
|
||||
"repeater_cliHelpAdvert": "Sends an advertisement packet",
|
||||
"repeater_cliHelpReboot": "Reboots the device. (note, you'll prob get 'Timeout' which is normal)",
|
||||
"repeater_cliHelpClock": "Displays current time per device's clock.",
|
||||
|
|
|
|||
|
|
@ -2087,5 +2087,8 @@
|
|||
"translation_translateBeforeSending": "Traducir antes de enviar",
|
||||
"translation_translateTo": "Traducir a {language}",
|
||||
"translation_translationOptions": "Opciones de traducción",
|
||||
"translation_systemLanguage": "Idioma del sistema"
|
||||
}
|
||||
"translation_systemLanguage": "Idioma del sistema",
|
||||
"scanner_linuxPairingPinPrompt": "Introduzca el PIN para {deviceName} (déjelo en blanco si no hay ninguno).",
|
||||
"repeater_cliQuickDiscovery": "Descubrir Vecinos",
|
||||
"repeater_cliQuickClockSync": "Sincronización del reloj"
|
||||
}
|
||||
|
|
@ -2059,5 +2059,9 @@
|
|||
"translation_messageTranslation": "Traduction du message",
|
||||
"translation_translateTo": "Traduire en {language}",
|
||||
"translation_translationOptions": "Options de traduction",
|
||||
"translation_systemLanguage": "Langue du système"
|
||||
}
|
||||
"translation_systemLanguage": "Langue du système",
|
||||
"scanner_linuxPairingPinTitle": "Code PIN d’appairage Bluetooth",
|
||||
"scanner_linuxPairingPinPrompt": "Entrez le code PIN pour {deviceName} (laissez vide si aucun).",
|
||||
"repeater_cliQuickClockSync": "Synchronisation de l'horloge",
|
||||
"repeater_cliQuickDiscovery": "Découvrir les voisins"
|
||||
}
|
||||
|
|
@ -2097,5 +2097,8 @@
|
|||
"translation_composerDisabledHint": "Küldj üzeneteket az eredeti, nyomtatott nyelven.",
|
||||
"translation_translateTo": "Fordítás {language}-ra",
|
||||
"translation_translationOptions": "Fordítási lehetőségek",
|
||||
"translation_systemLanguage": "Rendszer nyelvé"
|
||||
}
|
||||
"translation_systemLanguage": "Rendszer nyelvé",
|
||||
"scanner_linuxPairingPinPrompt": "Adja meg a(z) {deviceName} PIN-kódját (hagyja üresen, ha nincs).",
|
||||
"repeater_cliQuickClockSync": "Óra szinkronizálás",
|
||||
"repeater_cliQuickDiscovery": "Fedezd fel a szomszédokat"
|
||||
}
|
||||
|
|
@ -2059,5 +2059,10 @@
|
|||
"translation_composerEnabledHint": "I messaggi verranno tradotti prima di essere inviati.",
|
||||
"translation_translateTo": "Tradurre in {language}",
|
||||
"translation_translationOptions": "Opzioni di traduzione",
|
||||
"translation_systemLanguage": "Lingua del sistema"
|
||||
}
|
||||
"translation_systemLanguage": "Lingua del sistema",
|
||||
"scanner_linuxPairingHidePin": "Nascondi PIN",
|
||||
"scanner_linuxPairingPinTitle": "PIN di associazione Bluetooth",
|
||||
"scanner_linuxPairingPinPrompt": "Inserisci il PIN per {deviceName} (lascia vuoto se non ce n'è).",
|
||||
"repeater_cliQuickClockSync": "Sincronizzazione dell'orologio",
|
||||
"repeater_cliQuickDiscovery": "Scopri i Vicini"
|
||||
}
|
||||
|
|
@ -2097,5 +2097,11 @@
|
|||
"translation_composerDisabledHint": "元のタイプされた言語でメッセージを送信してください。",
|
||||
"translation_translateTo": "{language} への翻訳",
|
||||
"translation_translationOptions": "翻訳の選択肢",
|
||||
"translation_systemLanguage": "システム言語"
|
||||
}
|
||||
"translation_systemLanguage": "システム言語",
|
||||
"scanner_linuxPairingShowPin": "PINを表示",
|
||||
"scanner_linuxPairingHidePin": "PINを非表示",
|
||||
"scanner_linuxPairingPinTitle": "Bluetooth ペアリング PIN",
|
||||
"scanner_linuxPairingPinPrompt": "{deviceName}のPINを入力してください(なしの場合は空欄のまま)。",
|
||||
"repeater_cliQuickClockSync": "クロック同期",
|
||||
"repeater_cliQuickDiscovery": "近隣を発見する"
|
||||
}
|
||||
|
|
@ -2097,5 +2097,8 @@
|
|||
"translation_composerDisabledHint": "원래 작성된 언어로 메시지를 보내세요.",
|
||||
"translation_translateTo": "{language} 번역",
|
||||
"translation_translationOptions": "번역 옵션",
|
||||
"translation_systemLanguage": "시스템 언어"
|
||||
}
|
||||
"translation_systemLanguage": "시스템 언어",
|
||||
"scanner_linuxPairingPinPrompt": "{deviceName}에 대한 PIN을 입력하세요 (없으면 비워두세요).",
|
||||
"repeater_cliQuickClockSync": "시계 동기화",
|
||||
"repeater_cliQuickDiscovery": "이웃 발견하기"
|
||||
}
|
||||
|
|
@ -4322,6 +4322,18 @@ abstract class AppLocalizations {
|
|||
/// **'Clock'**
|
||||
String get repeater_cliQuickClock;
|
||||
|
||||
/// No description provided for @repeater_cliQuickClockSync.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Clock Sync'**
|
||||
String get repeater_cliQuickClockSync;
|
||||
|
||||
/// No description provided for @repeater_cliQuickDiscovery.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Discover Neighbors'**
|
||||
String get repeater_cliQuickDiscovery;
|
||||
|
||||
/// No description provided for @repeater_cliHelpAdvert.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -2429,6 +2429,12 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Часовник';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Синхронизация на часовника';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Открий Съседи';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Изпраща рекламен пакет';
|
||||
|
||||
|
|
|
|||
|
|
@ -2429,6 +2429,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Uhr';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Uhr Synchronisieren';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Entdecke Nachbarn';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Sendet eine Ankündigung';
|
||||
|
||||
|
|
|
|||
|
|
@ -2379,6 +2379,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Clock';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Clock Sync';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Discover Neighbors';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Sends an advertisement packet';
|
||||
|
||||
|
|
|
|||
|
|
@ -2423,6 +2423,12 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Reloj';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Sincronización del reloj';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Descubrir Vecinos';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Envía un paquete de publicidad';
|
||||
|
||||
|
|
|
|||
|
|
@ -2442,6 +2442,12 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Horloge';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Synchronisation de l\'horloge';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Découvrir les voisins';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Envoie un paquet d\'annonce';
|
||||
|
||||
|
|
|
|||
|
|
@ -2437,6 +2437,12 @@ class AppLocalizationsHu extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'óra';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Óra szinkronizálás';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Fedezd fel a szomszédokat';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Elküldi egy hirdetési csomagot';
|
||||
|
||||
|
|
|
|||
|
|
@ -2426,6 +2426,12 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Orologio';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Sincronizzazione dell\'orologio';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Scopri i Vicini';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Invia un pacchetto pubblicitario';
|
||||
|
||||
|
|
|
|||
|
|
@ -2322,6 +2322,12 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => '時計';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'クロック同期';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => '近隣を発見する';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => '広告用資料を送る';
|
||||
|
||||
|
|
|
|||
|
|
@ -2319,6 +2319,12 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => '시계';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => '시계 동기화';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => '이웃 발견하기';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => '광고 패킷을 발송';
|
||||
|
||||
|
|
|
|||
|
|
@ -2410,7 +2410,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
String get repeater_cliQuickClock => 'Tijd opvragen';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Advertentie uitzenden';
|
||||
String get repeater_cliQuickClockSync => 'Kloksynchronisatie';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Ontdek Buren';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Verstuurt een advertentiepakket';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpReboot =>
|
||||
|
|
|
|||
|
|
@ -2435,6 +2435,12 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Godzina';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Synchronizacja zegara';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Odkryj Sąsiadów';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Wysyła pakiet rozgłoszeniowy';
|
||||
|
||||
|
|
|
|||
|
|
@ -2423,6 +2423,12 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Relógio';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Sincronização do Relógio';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Descobrir Vizinhos';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Envia um pacote de anúncios';
|
||||
|
||||
|
|
|
|||
|
|
@ -2427,6 +2427,12 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Время';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Синхронизация часов';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Обнаружить Соседей';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Отправляет пакет анонсирования';
|
||||
|
||||
|
|
|
|||
|
|
@ -2406,6 +2406,12 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Hodiny';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Synchronizácia hodin';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Objaviť susedov';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Odosiela reklamnú balíček.';
|
||||
|
||||
|
|
|
|||
|
|
@ -2409,6 +2409,12 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Ura';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Usklajevanje ure';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Odkrijte sosede';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Pošlje paket oglasov';
|
||||
|
||||
|
|
|
|||
|
|
@ -2394,6 +2394,12 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Klocka';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Synkronisera klocka';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Upptäck grannar';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Skickar ett annonspaket';
|
||||
|
||||
|
|
|
|||
|
|
@ -2427,6 +2427,12 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => 'Годинник';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => 'Синхронізація годинника';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => 'Відкрити сусідів';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Надсилає пакет оголошення';
|
||||
|
||||
|
|
|
|||
|
|
@ -2277,6 +2277,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get repeater_cliQuickClock => '时钟';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClockSync => '同步时钟';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickDiscovery => '发现邻居';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => '发送广播包';
|
||||
|
||||
|
|
|
|||
|
|
@ -2059,5 +2059,10 @@
|
|||
"translation_messageTranslation": "Berichtvertaling",
|
||||
"translation_translationOptions": "Opties voor vertaling",
|
||||
"translation_systemLanguage": "Taal van het systeem",
|
||||
"translation_translateTo": "Vertalen naar {language}"
|
||||
}
|
||||
"translation_translateTo": "Vertalen naar {language}",
|
||||
"scanner_linuxPairingHidePin": "PIN verbergen",
|
||||
"scanner_linuxPairingPinPrompt": "Voer PIN in voor {deviceName} (laat leeg als er geen is).",
|
||||
"scanner_linuxPairingPinTitle": "Bluetooth‑koppelings‑PIN",
|
||||
"repeater_cliQuickDiscovery": "Ontdek Buren",
|
||||
"repeater_cliQuickClockSync": "Kloksynchronisatie"
|
||||
}
|
||||
|
|
@ -2097,5 +2097,11 @@
|
|||
"translation_messageTranslation": "Tłumaczenie wiadomości",
|
||||
"translation_translationOptions": "Opcje tłumaczenia",
|
||||
"translation_systemLanguage": "Język systemu",
|
||||
"translation_translateTo": "Tłumacz na {language}"
|
||||
}
|
||||
"translation_translateTo": "Tłumacz na {language}",
|
||||
"scanner_linuxPairingShowPin": "Pokaż PIN",
|
||||
"scanner_linuxPairingHidePin": "Ukryj PIN",
|
||||
"scanner_linuxPairingPinPrompt": "Wprowadź kod PIN dla {deviceName} (pozostaw puste, jeśli brak).",
|
||||
"scanner_linuxPairingPinTitle": "Kod PIN parowania Bluetooth",
|
||||
"repeater_cliQuickClockSync": "Synchronizacja zegara",
|
||||
"repeater_cliQuickDiscovery": "Odkryj Sąsiadów"
|
||||
}
|
||||
|
|
@ -2059,5 +2059,10 @@
|
|||
"translation_composerDisabledHint": "Envie mensagens no idioma original, conforme digitado.",
|
||||
"translation_translateTo": "Traduzir para {language}",
|
||||
"translation_translationOptions": "Opções de tradução",
|
||||
"translation_systemLanguage": "Idioma do sistema"
|
||||
}
|
||||
"translation_systemLanguage": "Idioma do sistema",
|
||||
"scanner_linuxPairingHidePin": "Ocultar PIN",
|
||||
"scanner_linuxPairingPinPrompt": "Insira o PIN para {deviceName} (deixe em branco se não houver).",
|
||||
"scanner_linuxPairingPinTitle": "PIN de emparelhamento Bluetooth",
|
||||
"repeater_cliQuickClockSync": "Sincronização do Relógio",
|
||||
"repeater_cliQuickDiscovery": "Descobrir Vizinhos"
|
||||
}
|
||||
|
|
@ -1299,5 +1299,11 @@
|
|||
"translation_composerDisabledHint": "Отправляйте сообщения на языке, в котором они были изначально набраны.",
|
||||
"translation_translateTo": "Перевести на {language}",
|
||||
"translation_translationOptions": "Варианты перевода",
|
||||
"translation_systemLanguage": "Язык системы"
|
||||
}
|
||||
"translation_systemLanguage": "Язык системы",
|
||||
"scanner_linuxPairingShowPin": "Показать PIN",
|
||||
"scanner_linuxPairingPinPrompt": "Введите PIN‑код для {deviceName} (оставьте пустым, если нет).",
|
||||
"scanner_linuxPairingHidePin": "Скрыть PIN",
|
||||
"scanner_linuxPairingPinTitle": "PIN‑код сопряжения Bluetooth",
|
||||
"repeater_cliQuickDiscovery": "Обнаружить Соседей",
|
||||
"repeater_cliQuickClockSync": "Синхронизация часов"
|
||||
}
|
||||
|
|
@ -2059,5 +2059,8 @@
|
|||
"translation_messageTranslation": "Preklad textu",
|
||||
"translation_translateTo": "Preložte do {language}",
|
||||
"translation_translationOptions": "Možnosti prekladania",
|
||||
"translation_systemLanguage": "Jazyk systému"
|
||||
}
|
||||
"translation_systemLanguage": "Jazyk systému",
|
||||
"scanner_linuxPairingPinTitle": "Bluetooth párovací PIN",
|
||||
"repeater_cliQuickClockSync": "Synchronizácia hodin",
|
||||
"repeater_cliQuickDiscovery": "Objaviť susedov"
|
||||
}
|
||||
|
|
@ -2059,5 +2059,10 @@
|
|||
"translation_messageTranslation": "Prevod sporočila",
|
||||
"translation_translateTo": "Prevesti v {language}",
|
||||
"translation_translationOptions": "Možnosti prevoda",
|
||||
"translation_systemLanguage": "Jezik sistema"
|
||||
}
|
||||
"translation_systemLanguage": "Jezik sistema",
|
||||
"scanner_linuxPairingHidePin": "Skrij PIN",
|
||||
"scanner_linuxPairingPinPrompt": "Vnesite PIN za {deviceName} (pustite prazno, če ga ni).",
|
||||
"scanner_linuxPairingPinTitle": "Bluetooth PIN za seznanjanje",
|
||||
"repeater_cliQuickDiscovery": "Odkrijte sosede",
|
||||
"repeater_cliQuickClockSync": "Usklajevanje ure"
|
||||
}
|
||||
|
|
@ -2059,5 +2059,11 @@
|
|||
"translation_messageTranslation": "Meddelandets översättning",
|
||||
"translation_translateTo": "Översätt till {language}",
|
||||
"translation_translationOptions": "Översättningsalternativ",
|
||||
"translation_systemLanguage": "Språk för systemet"
|
||||
}
|
||||
"translation_systemLanguage": "Språk för systemet",
|
||||
"scanner_linuxPairingShowPin": "Visa PIN",
|
||||
"scanner_linuxPairingPinTitle": "Bluetooth‑parnings‑PIN",
|
||||
"scanner_linuxPairingPinPrompt": "Ange PIN för {deviceName} (lämna tomt om ingen).",
|
||||
"scanner_linuxPairingHidePin": "Dölj PIN",
|
||||
"repeater_cliQuickDiscovery": "Upptäck grannar",
|
||||
"repeater_cliQuickClockSync": "Synkronisera klocka"
|
||||
}
|
||||
|
|
@ -2059,5 +2059,11 @@
|
|||
"translation_translateBeforeSending": "Перекладіть перед відправкою",
|
||||
"translation_translateTo": "Перекласти на {language}",
|
||||
"translation_translationOptions": "Варіанти перекладу",
|
||||
"translation_systemLanguage": "Мова системи"
|
||||
}
|
||||
"translation_systemLanguage": "Мова системи",
|
||||
"scanner_linuxPairingPinTitle": "PIN‑код спарювання Bluetooth",
|
||||
"scanner_linuxPairingShowPin": "Показати PIN",
|
||||
"scanner_linuxPairingPinPrompt": "Введіть PIN для {deviceName} (залиште порожнім, якщо його немає).",
|
||||
"scanner_linuxPairingHidePin": "Приховати PIN",
|
||||
"repeater_cliQuickClockSync": "Синхронізація годинника",
|
||||
"repeater_cliQuickDiscovery": "Відкрити сусідів"
|
||||
}
|
||||
|
|
@ -2064,5 +2064,8 @@
|
|||
"translation_translateBeforeSending": "在发送前进行翻译",
|
||||
"translation_translateTo": "翻译成 {language}",
|
||||
"translation_translationOptions": "翻译选项",
|
||||
"translation_systemLanguage": "系统语言"
|
||||
}
|
||||
"translation_systemLanguage": "系统语言",
|
||||
"scanner_linuxPairingHidePin": "隐藏 PIN",
|
||||
"repeater_cliQuickDiscovery": "发现邻居",
|
||||
"repeater_cliQuickClockSync": "同步时钟"
|
||||
}
|
||||
|
|
@ -822,7 +822,8 @@ List<_PathHop> _buildPathHops(
|
|||
) {
|
||||
if (pathBytes.isEmpty) return const [];
|
||||
final candidatesByPrefix = <int, List<Contact>>{};
|
||||
for (final contact in connector.allContacts) {
|
||||
final allContacts = connector.allContacts;
|
||||
for (final contact in allContacts) {
|
||||
if (contact.publicKey.isEmpty) continue;
|
||||
if (contact.type != advTypeRepeater && contact.type != advTypeRoom) {
|
||||
continue;
|
||||
|
|
@ -839,7 +840,8 @@ List<_PathHop> _buildPathHops(
|
|||
: null;
|
||||
var previousPosition = startPoint;
|
||||
final distance = Distance();
|
||||
|
||||
var lastDistance = 0.0;
|
||||
var bestDistance = 0.0;
|
||||
final hops = <_PathHop>[];
|
||||
for (var i = 0; i < pathBytes.length; i++) {
|
||||
final searchPoint = i == 0 ? startPoint : previousPosition;
|
||||
|
|
@ -848,7 +850,7 @@ List<_PathHop> _buildPathHops(
|
|||
if (candidates != null && candidates.isNotEmpty) {
|
||||
var bestIndex = 0;
|
||||
if (searchPoint != null) {
|
||||
var bestDistance = double.infinity;
|
||||
bestDistance = double.infinity;
|
||||
for (var j = 0; j < candidates.length; j++) {
|
||||
final candidate = candidates[j];
|
||||
if (!candidate.hasLocation ||
|
||||
|
|
@ -876,6 +878,16 @@ List<_PathHop> _buildPathHops(
|
|||
if (resolvedPosition != null) {
|
||||
previousPosition = resolvedPosition;
|
||||
}
|
||||
// If the best candidate is much farther than the previous hop, it's likely not the correct match.
|
||||
if (lastDistance + bestDistance > 70000 &&
|
||||
candidates != null &&
|
||||
candidates.isNotEmpty) {
|
||||
i--;
|
||||
lastDistance = bestDistance;
|
||||
continue;
|
||||
}
|
||||
lastDistance = bestDistance;
|
||||
|
||||
hops.add(
|
||||
_PathHop(
|
||||
index: i + 1,
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ class _ChannelsScreenState extends State<ChannelsScreen>
|
|||
canPop: allowBack,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: AppBarTitle(context.l10n.channels_title, indicators: false),
|
||||
title: AppBarTitle(context.l10n.channels_title),
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
actions: [
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
tooltip: context.l10n.chat_pathManagement,
|
||||
onPressed: () => _showPathHistory(context),
|
||||
),
|
||||
const RadioStatsIconButton(),
|
||||
Consumer<MeshCoreConnector>(
|
||||
builder: (context, connector, _) {
|
||||
return PopupMenuButton<String>(
|
||||
|
|
@ -366,7 +367,6 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
);
|
||||
},
|
||||
),
|
||||
const RadioStatsIconButton(),
|
||||
],
|
||||
),
|
||||
body: Consumer<MeshCoreConnector>(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class _CompanionRadioStatsScreenState extends State<CompanionRadioStatsScreen> {
|
|||
final c = context.read<MeshCoreConnector>();
|
||||
_connector = c;
|
||||
c.acquireRadioStatsPolling();
|
||||
c.setPollingInterval(1);
|
||||
c.radioStatsNotifier.addListener(_onStatsUpdate);
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +45,7 @@ class _CompanionRadioStatsScreenState extends State<CompanionRadioStatsScreen> {
|
|||
void dispose() {
|
||||
_connector?.radioStatsNotifier.removeListener(_onStatsUpdate);
|
||||
_connector?.releaseRadioStatsPolling();
|
||||
_connector?.setPollingInterval(30);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1240,9 +1240,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
if (isRepeater) ...[
|
||||
ListTile(
|
||||
leading: const Icon(Icons.radar, color: Colors.green),
|
||||
title: contact.pathBytesForDisplay.isNotEmpty
|
||||
? Text(context.l10n.contacts_pathTrace)
|
||||
: Text(context.l10n.contacts_ping),
|
||||
title: Text(context.l10n.contacts_ping),
|
||||
onTap: () {
|
||||
final hw = context
|
||||
.read<MeshCoreConnector>()
|
||||
|
|
@ -1251,11 +1249,8 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PathTraceMapScreen(
|
||||
title: contact.pathBytesForDisplay.isNotEmpty
|
||||
? context.l10n.contacts_repeaterPathTrace
|
||||
: context.l10n.contacts_repeaterPing,
|
||||
path: contact.pathBytesForDisplay,
|
||||
flipPathAround: true,
|
||||
title: context.l10n.contacts_repeaterPing,
|
||||
path: Uint8List.fromList([contact.publicKey.first]),
|
||||
targetContact: contact,
|
||||
pathHashByteWidth: hw,
|
||||
),
|
||||
|
|
@ -1274,9 +1269,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
] else if (isRoom) ...[
|
||||
ListTile(
|
||||
leading: const Icon(Icons.radar, color: Colors.green),
|
||||
title: contact.pathLength > 0
|
||||
? Text(context.l10n.contacts_pathTrace)
|
||||
: Text(context.l10n.contacts_ping),
|
||||
title: Text(context.l10n.contacts_pathTrace),
|
||||
onTap: () {
|
||||
final hw = context
|
||||
.read<MeshCoreConnector>()
|
||||
|
|
@ -1288,7 +1281,9 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
title: contact.pathBytesForDisplay.isNotEmpty
|
||||
? context.l10n.contacts_roomPathTrace
|
||||
: context.l10n.contacts_roomPing,
|
||||
path: contact.pathBytesForDisplay,
|
||||
path: contact.pathBytesForDisplay.isNotEmpty
|
||||
? contact.pathBytesForDisplay
|
||||
: Uint8List.fromList([contact.publicKey.first]),
|
||||
flipPathAround: contact.pathBytesForDisplay.isNotEmpty,
|
||||
targetContact: contact,
|
||||
pathHashByteWidth: hw,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,13 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
DateTime _resolveLastSeen(Contact contact) {
|
||||
if (contact.type != advTypeChat) return contact.lastSeen;
|
||||
return contact.lastMessageAt.isAfter(contact.lastSeen)
|
||||
? contact.lastMessageAt
|
||||
: contact.lastSeen;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
|
@ -108,11 +115,56 @@ class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
|||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: Text(
|
||||
_formatLastSeen(context, contact.lastSeen),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
// Clamp text scaling in trailing section to prevent overflow while
|
||||
// maintaining accessibility. Primary content (title/subtitle) scales normally.
|
||||
trailing: MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaler: TextScaler.linear(
|
||||
MediaQuery.textScalerOf(
|
||||
context,
|
||||
).scale(1.0).clamp(1.0, 1.3),
|
||||
),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 120,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
_formatLastSeen(
|
||||
context,
|
||||
_resolveLastSeen(contact),
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (contact.hasLocation)
|
||||
Icon(
|
||||
Icons.location_on,
|
||||
size: 14,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
if (contact.rawPacket != null)
|
||||
const SizedBox(width: 2),
|
||||
if (contact.rawPacket != null)
|
||||
Icon(
|
||||
Icons.cell_tower,
|
||||
size: 14,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||
bool _hasInitializedMap = false;
|
||||
bool _removedMarkersLoaded = false;
|
||||
final List<int> _pathTrace = [];
|
||||
final List<Contact> _pathTraceContacts = [];
|
||||
final List<LatLng> _points = [];
|
||||
final List<Polyline> _polylines = [];
|
||||
bool _legendExpanded = false;
|
||||
|
|
@ -488,7 +489,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (!_isBuildingPathTrace)
|
||||
if (!settings.mapShowOverlaps)
|
||||
..._buildGuessedMarker(
|
||||
guessedLocations,
|
||||
showLabels: _showNodeLabels,
|
||||
|
|
@ -788,17 +789,26 @@ class _MapScreenState extends State<MapScreen> {
|
|||
final markers = <Marker>[];
|
||||
|
||||
for (final guess in guessed) {
|
||||
if (guess.contact.type == advTypeChat && _isBuildingPathTrace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final color = _getNodeColor(guess.contact.type);
|
||||
final marker = Marker(
|
||||
point: guess.position,
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: GestureDetector(
|
||||
onTap: () => _showNodeInfo(
|
||||
context,
|
||||
guess.contact,
|
||||
guessedPosition: guess.position,
|
||||
),
|
||||
onLongPress: () => _isBuildingPathTrace
|
||||
? _showNodeInfo(context, guess.contact)
|
||||
: null,
|
||||
onTap: () => _isBuildingPathTrace
|
||||
? _addToPath(context, guess.contact, position: guess.position)
|
||||
: _showNodeInfo(
|
||||
context,
|
||||
guess.contact,
|
||||
guessedPosition: guess.position,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
|
|
@ -870,23 +880,29 @@ class _MapScreenState extends State<MapScreen> {
|
|||
addContact = true;
|
||||
}
|
||||
|
||||
final hasOverlap = contacts
|
||||
.where(
|
||||
(c) =>
|
||||
c.publicKeyHex != contact.publicKeyHex &&
|
||||
c.publicKey.first == contact.publicKey.first &&
|
||||
(c.type == advTypeRepeater || c.type == advTypeRoom) &&
|
||||
(contact.type == advTypeRepeater ||
|
||||
contact.type == advTypeRoom),
|
||||
)
|
||||
.firstOrNull;
|
||||
|
||||
if (hasOverlap == null &&
|
||||
settings.mapShowOverlaps &&
|
||||
!_isBuildingPathTrace) {
|
||||
if (contact.type == advTypeChat && _isBuildingPathTrace) {
|
||||
addContact = false;
|
||||
}
|
||||
|
||||
if (settings.mapShowOverlaps) {
|
||||
final hasOverlap = contacts
|
||||
.where(
|
||||
(c) =>
|
||||
c.publicKeyHex != contact.publicKeyHex &&
|
||||
c.publicKey.first == contact.publicKey.first &&
|
||||
(c.type == advTypeRepeater || c.type == advTypeRoom) &&
|
||||
(contact.type == advTypeRepeater ||
|
||||
contact.type == advTypeRoom),
|
||||
)
|
||||
.firstOrNull;
|
||||
|
||||
if (hasOverlap == null &&
|
||||
settings.mapShowOverlaps &&
|
||||
!_isBuildingPathTrace) {
|
||||
addContact = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (addContact) {
|
||||
filtered.add(contact);
|
||||
}
|
||||
|
|
@ -2121,12 +2137,18 @@ class _MapScreenState extends State<MapScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
void _addToPath(BuildContext context, Contact contact) {
|
||||
void _addToPath(BuildContext context, Contact contact, {LatLng? position}) {
|
||||
setState(() {
|
||||
_pathTrace.add(
|
||||
contact.publicKey[0],
|
||||
); // Add first 16 bytes of public key to path trace
|
||||
_points.add(LatLng(contact.latitude!, contact.longitude!));
|
||||
_pathTraceContacts.add(
|
||||
contact.copyWith(
|
||||
latitude: position?.latitude ?? contact.latitude,
|
||||
longitude: position?.longitude ?? contact.longitude,
|
||||
),
|
||||
); // Add contact to path trace contacts
|
||||
_points.add(position ?? LatLng(contact.latitude!, contact.longitude!));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2134,6 +2156,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||
setState(() {
|
||||
_isBuildingPathTrace = true;
|
||||
_pathTrace.clear();
|
||||
_pathTraceContacts.clear();
|
||||
_points.clear();
|
||||
_polylines.clear();
|
||||
_points.add(position);
|
||||
|
|
@ -2142,6 +2165,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||
|
||||
void _removePath() {
|
||||
setState(() {
|
||||
_pathTraceContacts.removeLast();
|
||||
_pathTrace.removeLast(); // Remove last node from path trace
|
||||
_points.removeLast(); // Remove last point from points list
|
||||
_polylines.clear(); // Clear polylines
|
||||
|
|
@ -2201,6 +2225,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||
title: l10n.contacts_pathTrace,
|
||||
path: Uint8List.fromList(_pathTrace),
|
||||
pathHashByteWidth: hashW,
|
||||
pathContacts: _pathTraceContacts,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class PathTraceMapScreen extends StatefulWidget {
|
|||
final bool reversePathAround;
|
||||
final Contact? targetContact;
|
||||
final int pathHashByteWidth;
|
||||
final List<Contact>? pathContacts;
|
||||
|
||||
const PathTraceMapScreen({
|
||||
super.key,
|
||||
|
|
@ -66,6 +67,7 @@ class PathTraceMapScreen extends StatefulWidget {
|
|||
this.reversePathAround = false,
|
||||
this.targetContact,
|
||||
this.pathHashByteWidth = pathHashSize,
|
||||
this.pathContacts,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -74,6 +76,8 @@ class PathTraceMapScreen extends StatefulWidget {
|
|||
|
||||
class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
||||
static const double _labelZoomThreshold = 8.5;
|
||||
//miles to meters conversion for filtering out repeaters that are too far from the last known GPS hop to be a likely match, to avoid false matches that throw off the inferred positions of other hops in the path
|
||||
static const double _maxRepeaterMatchDistanceMeters = 40 * 1609.344;
|
||||
|
||||
StreamSubscription<Uint8List>? _frameSubscription;
|
||||
Timer? _timeoutTimer;
|
||||
|
|
@ -266,17 +270,43 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
.toList();
|
||||
|
||||
Map<int, Contact> pathContacts = {};
|
||||
final contacts = connector.allContacts;
|
||||
contacts.where((c) => c.type != advTypeChat).forEach((repeater) {
|
||||
for (var repeaterData in pathData) {
|
||||
if (listEquals(
|
||||
repeater.publicKey.sublist(0, 1),
|
||||
Uint8List.fromList([repeaterData]),
|
||||
)) {
|
||||
pathContacts[repeaterData] = repeater;
|
||||
Contact lastContact = Contact(
|
||||
path: Uint8List(0),
|
||||
pathLength: 0,
|
||||
publicKey: connector.selfPublicKey ?? Uint8List(0),
|
||||
name: context.l10n.pathTrace_you,
|
||||
type: advTypeChat,
|
||||
latitude: connector.selfLatitude,
|
||||
longitude: connector.selfLongitude,
|
||||
lastSeen: DateTime.now(),
|
||||
);
|
||||
if (widget.pathContacts != null) {
|
||||
pathContacts = {for (var c in widget.pathContacts!) c.publicKey[0]: c};
|
||||
} else {
|
||||
final contacts = connector.allContactsUnfiltered;
|
||||
contacts.where((c) => c.type != advTypeChat).forEach((repeater) {
|
||||
if (lastContact.latitude != null &&
|
||||
lastContact.longitude != null &&
|
||||
repeater.hasLocation &&
|
||||
lastContact.hasLocation &&
|
||||
Distance().distance(
|
||||
LatLng(lastContact.latitude!, lastContact.longitude!),
|
||||
LatLng(repeater.latitude!, repeater.longitude!),
|
||||
) >
|
||||
_maxRepeaterMatchDistanceMeters) {
|
||||
return; //skip reapeaters that are far away from the last one with known GPS, to avoid false matches
|
||||
}
|
||||
}
|
||||
});
|
||||
for (var repeaterData in pathData) {
|
||||
if (listEquals(
|
||||
repeater.publicKey.sublist(0, 1),
|
||||
Uint8List.fromList([repeaterData]),
|
||||
)) {
|
||||
pathContacts[repeaterData] = repeater;
|
||||
lastContact = repeater;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// For hops with no GPS contact, infer position from other contacts
|
||||
// with known GPS that share the same last-hop byte.
|
||||
|
|
|
|||
|
|
@ -35,13 +35,15 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
|||
|
||||
// Common commands for quick access
|
||||
late final List<Map<String, String>> _quickCommands = [
|
||||
{'labelKey': 'advertise', 'command': 'advert'},
|
||||
{'labelKey': 'getName', 'command': 'get name'},
|
||||
{'labelKey': 'getRadio', 'command': 'get radio'},
|
||||
{'labelKey': 'getTx', 'command': 'get tx'},
|
||||
{'labelKey': 'discovery', 'command': 'discover.neighbors'},
|
||||
{'labelKey': 'neighbors', 'command': 'neighbors'},
|
||||
{'labelKey': 'version', 'command': 'ver'},
|
||||
{'labelKey': 'advertise', 'command': 'advert'},
|
||||
{'labelKey': 'clock', 'command': 'clock'},
|
||||
{'labelKey': 'clock sync', 'command': 'clock sync'},
|
||||
];
|
||||
|
||||
@override
|
||||
|
|
@ -407,6 +409,10 @@ class _RepeaterCliScreenState extends State<RepeaterCliScreen> {
|
|||
return l10n.repeater_cliQuickAdvertise;
|
||||
case 'clock':
|
||||
return l10n.repeater_cliQuickClock;
|
||||
case 'clock sync':
|
||||
return l10n.repeater_cliQuickClockSync;
|
||||
case 'discovery':
|
||||
return l10n.repeater_cliQuickDiscovery;
|
||||
default:
|
||||
return key;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,13 @@ class ContactExport {
|
|||
final double lon;
|
||||
final String desc;
|
||||
final double? ele;
|
||||
|
||||
final String url;
|
||||
ContactExport({
|
||||
required this.name,
|
||||
required this.lat,
|
||||
required this.lon,
|
||||
required this.desc,
|
||||
required this.url,
|
||||
this.ele,
|
||||
});
|
||||
}
|
||||
|
|
@ -40,6 +41,7 @@ class GpxExport {
|
|||
String name,
|
||||
double lat,
|
||||
double lon,
|
||||
String url,
|
||||
String desc, [
|
||||
double? ele,
|
||||
]) {
|
||||
|
|
@ -50,55 +52,66 @@ class GpxExport {
|
|||
lon: lon,
|
||||
desc: desc.trim(),
|
||||
ele: ele,
|
||||
url: url,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void addRepeaters() {
|
||||
final contacts = _connector.contacts
|
||||
.where((c) => c.type == advTypeRepeater || c.type == advTypeRoom)
|
||||
.toList();
|
||||
final contacts = _connector.allContacts.where(
|
||||
(c) => c.type == advTypeRepeater || c.type == advTypeRoom,
|
||||
);
|
||||
for (var contact in contacts) {
|
||||
if (contact.latitude == null || contact.longitude == null) {
|
||||
continue;
|
||||
}
|
||||
final url = contact.rawPacket != null
|
||||
? "meshcore://${pubKeyToHex(contact.rawPacket!)}"
|
||||
: "";
|
||||
_addContact(
|
||||
contact.name,
|
||||
contact.latitude!,
|
||||
contact.longitude!,
|
||||
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
|
||||
url,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void addContacts() {
|
||||
final contacts = _connector.contacts
|
||||
.where((c) => c.type == advTypeChat)
|
||||
.toList();
|
||||
final contacts = _connector.allContacts.where((c) => c.type == advTypeChat);
|
||||
for (var contact in contacts) {
|
||||
if (contact.latitude == null || contact.longitude == null) {
|
||||
continue;
|
||||
}
|
||||
final url = contact.rawPacket != null
|
||||
? "meshcore://${pubKeyToHex(contact.rawPacket!)}"
|
||||
: "";
|
||||
_addContact(
|
||||
contact.name,
|
||||
contact.latitude!,
|
||||
contact.longitude!,
|
||||
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
|
||||
url,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void addAll() {
|
||||
final contacts = _connector.contacts;
|
||||
for (var contact in contacts.toList()) {
|
||||
final contacts = _connector.allContacts;
|
||||
for (var contact in contacts) {
|
||||
if (contact.latitude == null || contact.longitude == null) {
|
||||
continue;
|
||||
}
|
||||
final url = contact.rawPacket != null
|
||||
? "meshcore://${pubKeyToHex(contact.rawPacket!)}"
|
||||
: "";
|
||||
_addContact(
|
||||
contact.name,
|
||||
contact.latitude ?? 0.0,
|
||||
contact.longitude ?? 0.0,
|
||||
"Type: ${contact.typeLabel}\nPublic Key: ${contact.publicKeyHex}",
|
||||
url,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -138,6 +151,9 @@ class GpxExport {
|
|||
ele: c.ele,
|
||||
name: c.name,
|
||||
desc: c.desc,
|
||||
extensions: {
|
||||
"meshcore": {"url": c.url},
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
|||
messageBytes: responseBytes,
|
||||
);
|
||||
final timeoutSeconds = (timeoutMs / 1000).ceil();
|
||||
final timeout = Duration(milliseconds: timeoutMs);
|
||||
final timeout = Duration(milliseconds: timeoutMs + 2000);
|
||||
final selectionLabel = selection.useFlood
|
||||
? 'flood'
|
||||
: '${selection.hopCount} hops';
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ class _RoomLoginDialogState extends State<RoomLoginDialog> {
|
|||
messageBytes: responseBytes,
|
||||
);
|
||||
final timeoutSeconds = (timeoutMs / 1000).ceil();
|
||||
final timeout = Duration(milliseconds: timeoutMs);
|
||||
final timeout = Duration(milliseconds: timeoutMs + 2000);
|
||||
final selectionLabel = selection.useFlood
|
||||
? 'flood'
|
||||
: '${selection.hopCount} hops';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue