mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: Add send mode localization #123
This commit is contained in:
@@ -660,6 +660,30 @@
|
|||||||
"@flood": {
|
"@flood": {
|
||||||
"description": "Flood routing indicator"
|
"description": "Flood routing indicator"
|
||||||
},
|
},
|
||||||
|
"autoSend": "Auto Send",
|
||||||
|
"@autoSend": {
|
||||||
|
"description": "Auto send mode - uses current path"
|
||||||
|
},
|
||||||
|
"autoSendDescription": "Send via current path.",
|
||||||
|
"@autoSendDescription": {
|
||||||
|
"description": "Description for auto send mode"
|
||||||
|
},
|
||||||
|
"sendDirect": "Send Direct",
|
||||||
|
"@sendDirect": {
|
||||||
|
"description": "Send direct mode - zero-hop to contact"
|
||||||
|
},
|
||||||
|
"sendDirectDescription": "Send directly to this contact.",
|
||||||
|
"@sendDirectDescription": {
|
||||||
|
"description": "Description for send direct mode"
|
||||||
|
},
|
||||||
|
"sendFlood": "Send Flood",
|
||||||
|
"@sendFlood": {
|
||||||
|
"description": "Send flood mode - via all repeaters"
|
||||||
|
},
|
||||||
|
"sendFloodDescription": "Send via all repeaters.",
|
||||||
|
"@sendFloodDescription": {
|
||||||
|
"description": "Description for send flood mode"
|
||||||
|
},
|
||||||
"loggedIn": "Logged In",
|
"loggedIn": "Logged In",
|
||||||
"@loggedIn": {
|
"@loggedIn": {
|
||||||
"description": "Logged in status badge"
|
"description": "Logged in status badge"
|
||||||
|
|||||||
@@ -961,6 +961,42 @@ abstract class AppLocalizations {
|
|||||||
/// **'Flood'**
|
/// **'Flood'**
|
||||||
String get flood;
|
String get flood;
|
||||||
|
|
||||||
|
/// Auto send mode - uses current path
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Auto Send'**
|
||||||
|
String get autoSend;
|
||||||
|
|
||||||
|
/// Description for auto send mode
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Send via current path.'**
|
||||||
|
String get autoSendDescription;
|
||||||
|
|
||||||
|
/// Send direct mode - zero-hop to contact
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Send Direct'**
|
||||||
|
String get sendDirect;
|
||||||
|
|
||||||
|
/// Description for send direct mode
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Send directly to this contact.'**
|
||||||
|
String get sendDirectDescription;
|
||||||
|
|
||||||
|
/// Send flood mode - via all repeaters
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Send Flood'**
|
||||||
|
String get sendFlood;
|
||||||
|
|
||||||
|
/// Description for send flood mode
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Send via all repeaters.'**
|
||||||
|
String get sendFloodDescription;
|
||||||
|
|
||||||
/// Logged in status badge
|
/// Logged in status badge
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -481,6 +481,24 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Flut';
|
String get flood => 'Flut';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Angemeldet';
|
String get loggedIn => 'Angemeldet';
|
||||||
|
|
||||||
|
|||||||
@@ -483,6 +483,24 @@ class AppLocalizationsEl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Πλημμυρικό';
|
String get flood => 'Πλημμυρικό';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Συνδεδεμένος';
|
String get loggedIn => 'Συνδεδεμένος';
|
||||||
|
|
||||||
|
|||||||
@@ -478,6 +478,24 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Flood';
|
String get flood => 'Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Logged In';
|
String get loggedIn => 'Logged In';
|
||||||
|
|
||||||
|
|||||||
@@ -481,6 +481,24 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Inundación';
|
String get flood => 'Inundación';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Sesión iniciada';
|
String get loggedIn => 'Sesión iniciada';
|
||||||
|
|
||||||
|
|||||||
@@ -483,6 +483,24 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Inondation';
|
String get flood => 'Inondation';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Connecté';
|
String get loggedIn => 'Connecté';
|
||||||
|
|
||||||
|
|||||||
@@ -476,6 +476,24 @@ class AppLocalizationsHr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Preplavljanje';
|
String get flood => 'Preplavljanje';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Prijavljen';
|
String get loggedIn => 'Prijavljen';
|
||||||
|
|
||||||
|
|||||||
@@ -480,6 +480,24 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Flood';
|
String get flood => 'Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Connesso';
|
String get loggedIn => 'Connesso';
|
||||||
|
|
||||||
|
|||||||
@@ -481,6 +481,24 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Rozgłoszeniowo';
|
String get flood => 'Rozgłoszeniowo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Zalogowano';
|
String get loggedIn => 'Zalogowano';
|
||||||
|
|
||||||
|
|||||||
@@ -482,6 +482,24 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Inundação';
|
String get flood => 'Inundação';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Conectado';
|
String get loggedIn => 'Conectado';
|
||||||
|
|
||||||
|
|||||||
@@ -480,6 +480,24 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Широковещательно';
|
String get flood => 'Широковещательно';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Вход выполнен';
|
String get loggedIn => 'Вход выполнен';
|
||||||
|
|
||||||
|
|||||||
@@ -477,6 +477,24 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Razpršitev';
|
String get flood => 'Razpršitev';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Prijavljen';
|
String get loggedIn => 'Prijavljen';
|
||||||
|
|
||||||
|
|||||||
@@ -480,6 +480,24 @@ class AppLocalizationsTr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Yayılım';
|
String get flood => 'Yayılım';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Giriş yapıldı';
|
String get loggedIn => 'Giriş yapıldı';
|
||||||
|
|
||||||
|
|||||||
@@ -480,6 +480,24 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => 'Широкомовно';
|
String get flood => 'Широкомовно';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => 'Увійшли';
|
String get loggedIn => 'Увійшли';
|
||||||
|
|
||||||
|
|||||||
@@ -464,6 +464,24 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get flood => '泛洪';
|
String get flood => '泛洪';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSend => 'Auto Send';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get autoSendDescription => 'Send via current path.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirect => 'Send Direct';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendDirectDescription => 'Send directly to this contact.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFlood => 'Send Flood';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sendFloodDescription => 'Send via all repeaters.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get loggedIn => '已登录';
|
String get loggedIn => '已登录';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
{
|
{
|
||||||
"de": [
|
"de": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -16,6 +22,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"el": [
|
"el": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -32,6 +44,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -48,6 +66,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -64,6 +88,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"hr": [
|
"hr": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -80,6 +110,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -96,6 +132,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"pl": [
|
"pl": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -112,6 +154,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -128,6 +176,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -144,6 +198,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"sl": [
|
"sl": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -160,6 +220,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"tr": [
|
"tr": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -176,6 +242,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"uk": [
|
"uk": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
@@ -192,6 +264,12 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
|
"autoSend",
|
||||||
|
"autoSendDescription",
|
||||||
|
"sendDirect",
|
||||||
|
"sendDirectDescription",
|
||||||
|
"sendFlood",
|
||||||
|
"sendFloodDescription",
|
||||||
"postConnectDiscoveryTitle",
|
"postConnectDiscoveryTitle",
|
||||||
"postConnectDiscoveryDescription",
|
"postConnectDiscoveryDescription",
|
||||||
"setRegionScope",
|
"setRegionScope",
|
||||||
|
|||||||
@@ -2880,6 +2880,32 @@ class ConnectionProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a contact to zero-hop direct mode (no mesh forwarding).
|
||||||
|
///
|
||||||
|
/// This forces the firmware to send directly to this contact without
|
||||||
|
/// using any repeaters, matching the "Send Direct" option in the
|
||||||
|
/// official MeshCore client.
|
||||||
|
Future<void> setContactDirect(Contact contact) async {
|
||||||
|
if (!_activeService.isConnected) {
|
||||||
|
_error = 'Not connected to device';
|
||||||
|
notifyListeners();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_error = null;
|
||||||
|
final updatedContact = contact.copyWith(
|
||||||
|
outPathLen: 0, // descriptor: hashSize=1, hopCount=0 → zero-hop direct
|
||||||
|
outPath: Uint8List(64),
|
||||||
|
);
|
||||||
|
await _activeService.addOrUpdateContact(updatedContact);
|
||||||
|
} catch (e) {
|
||||||
|
_error = 'Failed to set direct path: $e';
|
||||||
|
notifyListeners();
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove a contact from the companion radio
|
/// Remove a contact from the companion radio
|
||||||
///
|
///
|
||||||
/// Deletes the contact from the device's internal contact table.
|
/// Deletes the contact from the device's internal contact table.
|
||||||
|
|||||||
@@ -943,6 +943,69 @@ class _MessagesTabState extends State<MessagesTab> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isContactDestination() {
|
||||||
|
return _destinationType ==
|
||||||
|
MessageDestinationPreferences.destinationTypeContact &&
|
||||||
|
_selectedRecipient != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showSendModeSheet() {
|
||||||
|
if (!mounted) return;
|
||||||
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (sheetContext) {
|
||||||
|
return SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.route),
|
||||||
|
title: Text(l10n.autoSend),
|
||||||
|
subtitle: Text(l10n.autoSendDescription),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(sheetContext);
|
||||||
|
_sendMessage();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.near_me),
|
||||||
|
title: Text(l10n.sendDirect),
|
||||||
|
subtitle: Text(l10n.sendDirectDescription),
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.pop(sheetContext);
|
||||||
|
final connectionProvider = context.read<ConnectionProvider>();
|
||||||
|
if (_selectedRecipient != null &&
|
||||||
|
connectionProvider.deviceInfo.isConnected) {
|
||||||
|
await connectionProvider
|
||||||
|
.setContactDirect(_selectedRecipient!);
|
||||||
|
}
|
||||||
|
await _sendMessage();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.cell_tower),
|
||||||
|
title: Text(l10n.sendFlood),
|
||||||
|
subtitle: Text(l10n.sendFloodDescription),
|
||||||
|
onTap: () async {
|
||||||
|
Navigator.pop(sheetContext);
|
||||||
|
final connectionProvider = context.read<ConnectionProvider>();
|
||||||
|
if (_selectedRecipient != null &&
|
||||||
|
connectionProvider.deviceInfo.isConnected) {
|
||||||
|
await connectionProvider
|
||||||
|
.resetPath(_selectedRecipient!.publicKey);
|
||||||
|
}
|
||||||
|
await _sendMessage();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _startTicTacToeGame() async {
|
Future<void> _startTicTacToeGame() async {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (_destinationType !=
|
if (_destinationType !=
|
||||||
@@ -2387,6 +2450,9 @@ class _MessagesTabState extends State<MessagesTab> {
|
|||||||
onStartVoiceRecording: _startVoiceRecording,
|
onStartVoiceRecording: _startVoiceRecording,
|
||||||
onStopAndSendVoice: _stopAndSendVoice,
|
onStopAndSendVoice: _stopAndSendVoice,
|
||||||
onSendMessage: _sendMessage,
|
onSendMessage: _sendMessage,
|
||||||
|
onLongPressSend: _isContactDestination()
|
||||||
|
? _showSendModeSheet
|
||||||
|
: null,
|
||||||
regionScopeName: _channelRegionScopeName,
|
regionScopeName: _channelRegionScopeName,
|
||||||
onRegionScopeTap: _channelRegionScopeName != null
|
onRegionScopeTap: _channelRegionScopeName != null
|
||||||
? _showRegionScopeSheet
|
? _showRegionScopeSheet
|
||||||
|
|||||||
@@ -290,6 +290,17 @@ class PathHistoryService {
|
|||||||
ContactPathHistory.empty(contactPublicKeyHex);
|
ContactPathHistory.empty(contactPublicKeyHex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> clearHistoryFor(String contactPublicKeyHex) async {
|
||||||
|
await initialize();
|
||||||
|
_cache.remove(contactPublicKeyHex);
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final payload = <String, dynamic>{};
|
||||||
|
for (final entry in _cache.entries) {
|
||||||
|
payload[entry.key] = entry.value.toJson();
|
||||||
|
}
|
||||||
|
await prefs.setString(_storageKey, jsonEncode(payload));
|
||||||
|
}
|
||||||
|
|
||||||
ContactPathHistory _historyFor(String contactPublicKeyHex) {
|
ContactPathHistory _historyFor(String contactPublicKeyHex) {
|
||||||
return _cache.putIfAbsent(
|
return _cache.putIfAbsent(
|
||||||
contactPublicKeyHex,
|
contactPublicKeyHex,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import '../../models/path_history.dart';
|
|||||||
import '../../l10n/app_localizations.dart';
|
import '../../l10n/app_localizations.dart';
|
||||||
import '../../providers/app_provider.dart';
|
import '../../providers/app_provider.dart';
|
||||||
import '../../providers/connection_provider.dart';
|
import '../../providers/connection_provider.dart';
|
||||||
import '../../services/contact_route_resolver.dart';
|
|
||||||
import '../../services/path_history_service.dart';
|
import '../../services/path_history_service.dart';
|
||||||
import '../../services/relay_candidate_sorter.dart';
|
import '../../services/relay_candidate_sorter.dart';
|
||||||
import '../../services/route_hash_preferences.dart';
|
import '../../services/route_hash_preferences.dart';
|
||||||
@@ -80,7 +79,6 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
ParsedContactRoute? _parsedRoute;
|
ParsedContactRoute? _parsedRoute;
|
||||||
String? _errorText;
|
String? _errorText;
|
||||||
bool _showRoutingInfo = false;
|
bool _showRoutingInfo = false;
|
||||||
bool _showManualEditor = false;
|
|
||||||
List<Contact> _selectedMapHops = const [];
|
List<Contact> _selectedMapHops = const [];
|
||||||
ContactPathHistory? _pathHistory;
|
ContactPathHistory? _pathHistory;
|
||||||
|
|
||||||
@@ -92,7 +90,6 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
);
|
);
|
||||||
_relaySearchController = TextEditingController();
|
_relaySearchController = TextEditingController();
|
||||||
_controller.addListener(_reparse);
|
_controller.addListener(_reparse);
|
||||||
_showManualEditor = widget.contact.routeCanonicalText.isNotEmpty;
|
|
||||||
_loadHashSizePreference();
|
_loadHashSizePreference();
|
||||||
_loadPathHistory();
|
_loadPathHistory();
|
||||||
_reparse();
|
_reparse();
|
||||||
@@ -240,19 +237,6 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _applyResolvedPlan(ResolvedContactRoutePlan plan) {
|
|
||||||
setState(() {
|
|
||||||
_selectedMapHops = plan.selectedContacts;
|
|
||||||
_controller.text = plan.canonicalText;
|
|
||||||
_controller.selection = TextSelection.fromPosition(
|
|
||||||
TextPosition(offset: _controller.text.length),
|
|
||||||
);
|
|
||||||
_errorText = null;
|
|
||||||
_showManualEditor = false;
|
|
||||||
});
|
|
||||||
_reparse();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _applyHistoryRecord(PathRecord record) {
|
void _applyHistoryRecord(PathRecord record) {
|
||||||
final canonicalText = _canonicalRouteFromBytes(
|
final canonicalText = _canonicalRouteFromBytes(
|
||||||
record.pathBytes,
|
record.pathBytes,
|
||||||
@@ -264,7 +248,6 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
TextPosition(offset: _controller.text.length),
|
TextPosition(offset: _controller.text.length),
|
||||||
);
|
);
|
||||||
_errorText = null;
|
_errorText = null;
|
||||||
_showManualEditor = true;
|
|
||||||
});
|
});
|
||||||
_reparse();
|
_reparse();
|
||||||
}
|
}
|
||||||
@@ -319,39 +302,6 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resolvePathAutomatically() {
|
|
||||||
final connectionProvider = context.read<ConnectionProvider>();
|
|
||||||
final advLat = connectionProvider.deviceInfo.advLat;
|
|
||||||
final advLon = connectionProvider.deviceInfo.advLon;
|
|
||||||
final recipientLocation = widget.contact.displayLocation;
|
|
||||||
if (advLat == null ||
|
|
||||||
advLon == null ||
|
|
||||||
(advLat == 0 && advLon == 0) ||
|
|
||||||
recipientLocation == null) {
|
|
||||||
setState(() {
|
|
||||||
_errorText =
|
|
||||||
'Automatic resolve needs both your advertised location and the contact location.';
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final plan = ContactRouteResolver.resolveAutomaticRoute(
|
|
||||||
senderLocation: LatLng(advLat / 1e6, advLon / 1e6),
|
|
||||||
recipient: widget.contact,
|
|
||||||
availableContacts: widget.availableContacts,
|
|
||||||
hashSize: _selectedHashSize,
|
|
||||||
);
|
|
||||||
if (plan == null) {
|
|
||||||
setState(() {
|
|
||||||
_errorText =
|
|
||||||
'Could not resolve a route from available repeater locations.';
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_applyResolvedPlan(plan);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _canonicalRouteFromBytes(
|
String _canonicalRouteFromBytes(
|
||||||
List<int> pathBytes, {
|
List<int> pathBytes, {
|
||||||
required int hashSize,
|
required int hashSize,
|
||||||
@@ -434,32 +384,32 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
|
|
||||||
Widget _buildPreviewSection() {
|
Widget _buildPreviewSection() {
|
||||||
final previewRoute = _effectiveRoute;
|
final previewRoute = _effectiveRoute;
|
||||||
|
if (previewRoute == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: colorScheme.outlineVariant),
|
border: Border.all(color: colorScheme.outlineVariant),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
previewRoute == null ? 'Route preview' : previewRoute.summary,
|
previewRoute.summary,
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
previewRoute == null
|
'${previewRoute.byteLength}B • 0x${previewRoute.encodedPathLen.toRadixString(16).padLeft(2, '0').toUpperCase()}',
|
||||||
? 'Pick relays from the list below or open manual edit if you need exact hop tokens.'
|
|
||||||
: '${previewRoute.byteLength} bytes • descriptor 0x${previewRoute.encodedPathLen.toRadixString(16).padLeft(2, '0').toUpperCase()}',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
if (previewRoute != null &&
|
if (previewRoute.canonicalText.isNotEmpty) ...[
|
||||||
previewRoute.canonicalText.isNotEmpty) ...[
|
const SizedBox(height: 8),
|
||||||
const SizedBox(height: 10),
|
|
||||||
SelectableText(
|
SelectableText(
|
||||||
previewRoute.canonicalText,
|
previewRoute.canonicalText,
|
||||||
style: Theme.of(
|
style: Theme.of(
|
||||||
@@ -474,18 +424,7 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
|
|
||||||
Widget _buildSelectedHopSection() {
|
Widget _buildSelectedHopSection() {
|
||||||
if (_selectedMapHops.isEmpty) {
|
if (_selectedMapHops.isEmpty) {
|
||||||
return Container(
|
return const SizedBox.shrink();
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(14),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(color: Theme.of(context).dividerColor),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'No relays selected. Save now to use a direct path, or add repeaters below.',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@@ -493,28 +432,21 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
children: [
|
children: [
|
||||||
Text('Selected relays', style: Theme.of(context).textTheme.titleSmall),
|
Text('Selected relays', style: Theme.of(context).textTheme.titleSmall),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
..._selectedMapHops.asMap().entries.map((entry) {
|
Wrap(
|
||||||
final index = entry.key;
|
spacing: 8,
|
||||||
final contact = entry.value;
|
runSpacing: 8,
|
||||||
return Card(
|
children: _selectedMapHops.asMap().entries.map((entry) {
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
final index = entry.key;
|
||||||
child: ListTile(
|
final contact = entry.value;
|
||||||
leading: CircleAvatar(child: Text('${index + 1}')),
|
return InputChip(
|
||||||
title: Text(contact.displayName),
|
label: Text('${index + 1}. ${contact.displayName}'),
|
||||||
subtitle: Text(
|
deleteIcon: const Icon(Icons.close),
|
||||||
_tokenFor(contact, _selectedHashSize),
|
onDeleted: () => _toggleHop(contact),
|
||||||
style: Theme.of(
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
context,
|
visualDensity: VisualDensity.compact,
|
||||||
).textTheme.bodySmall?.copyWith(fontFamily: 'monospace'),
|
);
|
||||||
),
|
}).toList(),
|
||||||
trailing: IconButton(
|
),
|
||||||
tooltip: 'Remove relay',
|
|
||||||
onPressed: () => _toggleHop(contact),
|
|
||||||
icon: const Icon(Icons.close),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -563,6 +495,12 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
dense: true,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
isSelected
|
isSelected
|
||||||
? Icons.check_circle
|
? Icons.check_circle
|
||||||
@@ -593,41 +531,6 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildManualEditor() {
|
|
||||||
return ExpansionTile(
|
|
||||||
tilePadding: EdgeInsets.zero,
|
|
||||||
childrenPadding: EdgeInsets.zero,
|
|
||||||
initiallyExpanded: _showManualEditor,
|
|
||||||
onExpansionChanged: (expanded) {
|
|
||||||
setState(() {
|
|
||||||
_showManualEditor = expanded;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
title: Text(AppLocalizations.of(context)!.manualRouteEdit),
|
|
||||||
subtitle: const Text(
|
|
||||||
'Use this when you need to paste or tweak hop tokens directly.',
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
TextField(
|
|
||||||
controller: _controller,
|
|
||||||
textCapitalization: TextCapitalization.characters,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Route',
|
|
||||||
hintText: _selectedHashSize == 1
|
|
||||||
? 'AA,BB,CC'
|
|
||||||
: _selectedHashSize == 2
|
|
||||||
? 'AABB,CCDD'
|
|
||||||
: 'AABBCC,DDEEFF',
|
|
||||||
helperText:
|
|
||||||
'Comma-separated hops using the selected path size. Colon form like AA:BB is also accepted.',
|
|
||||||
errorText: _errorText,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMapPreview({
|
Widget _buildMapPreview({
|
||||||
required List<Contact> routeCandidates,
|
required List<Contact> routeCandidates,
|
||||||
required List<LatLng> mapPoints,
|
required List<LatLng> mapPoints,
|
||||||
@@ -709,15 +612,14 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
|
|
||||||
Widget _buildBuilderTab({
|
Widget _buildBuilderTab({
|
||||||
required List<Contact> routeCandidates,
|
required List<Contact> routeCandidates,
|
||||||
required List<LatLng> mapPoints,
|
|
||||||
required List<LatLng> routePoints,
|
|
||||||
}) {
|
}) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text('Path Size', style: Theme.of(context).textTheme.labelLarge),
|
Text('Path size', style: Theme.of(context).textTheme.labelLarge),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
SegmentedButton<int>(
|
SegmentedButton<int>(
|
||||||
segments: [
|
segments: [
|
||||||
@@ -734,36 +636,32 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (_errorText != null) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
_errorText!,
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (_selectedMapHops.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildSelectedHopSection(),
|
||||||
|
],
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: [
|
|
||||||
FilledButton.tonalIcon(
|
|
||||||
onPressed: _resolvePathAutomatically,
|
|
||||||
icon: Icon(Icons.auto_fix_high),
|
|
||||||
label: Text(AppLocalizations.of(context)!.autoResolve),
|
|
||||||
),
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: _selectedMapHops.isEmpty
|
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
setState(() {
|
|
||||||
_selectedMapHops = const [];
|
|
||||||
_syncControllerFromSelectedHops();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
icon: Icon(Icons.clear_all),
|
|
||||||
label: Text(AppLocalizations.of(context)!.clearRelays),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildPreviewSection(),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildSelectedHopSection(),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildRelayPicker(routeCandidates),
|
_buildRelayPicker(routeCandidates),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoTab({
|
||||||
|
required AppProvider appProvider,
|
||||||
|
required List<Contact> routeCandidates,
|
||||||
|
required List<LatLng> mapPoints,
|
||||||
|
required List<LatLng> routePoints,
|
||||||
|
}) {
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
_buildPreviewSection(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildMapPreview(
|
_buildMapPreview(
|
||||||
routeCandidates: routeCandidates,
|
routeCandidates: routeCandidates,
|
||||||
@@ -771,7 +669,18 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
routePoints: routePoints,
|
routePoints: routePoints,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildManualEditor(),
|
_AutomationRoutingInfo(
|
||||||
|
isExpanded: _showRoutingInfo,
|
||||||
|
onToggle: () {
|
||||||
|
setState(() {
|
||||||
|
_showRoutingInfo = !_showRoutingInfo;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
autoRouteRotationEnabled: appProvider.autoRouteRotationEnabled,
|
||||||
|
nearestRelayFallbackEnabled: appProvider.nearestRelayFallbackEnabled,
|
||||||
|
clearPathOnMaxRetry: appProvider.clearPathOnMaxRetry,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -808,6 +717,24 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await _pathHistoryService.clearHistoryFor(
|
||||||
|
widget.contact.publicKeyHex,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_pathHistory = _pathHistoryService.historyFor(
|
||||||
|
widget.contact.publicKeyHex,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Text('Clear history'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
if (observedRecord != null) ...[
|
if (observedRecord != null) ...[
|
||||||
_buildHistoryRecordTile(observedRecord, title: AppLocalizations.of(context)!.observedMeshRoute),
|
_buildHistoryRecordTile(observedRecord, title: AppLocalizations.of(context)!.observedMeshRoute),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -877,7 +804,7 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: 2,
|
length: 3,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Set Path for ${widget.contact.displayName}'),
|
title: Text('Set Path for ${widget.contact.displayName}'),
|
||||||
@@ -885,56 +812,34 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
|||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: 'Build'),
|
Tab(text: 'Build'),
|
||||||
Tab(text: 'History'),
|
Tab(text: 'History'),
|
||||||
|
Tab(text: 'Info'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||||
child: Column(
|
child: TabBarView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
ListView(
|
||||||
'Plan the route on its own screen, then save it once the preview looks right.',
|
children: [
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
_buildBuilderTab(
|
||||||
|
routeCandidates: routeCandidates,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
ListView(
|
||||||
Expanded(
|
children: [
|
||||||
child: TabBarView(
|
_buildHistoryTab(),
|
||||||
children: [
|
const SizedBox(height: 24),
|
||||||
ListView(
|
],
|
||||||
children: [
|
),
|
||||||
_buildBuilderTab(
|
_buildInfoTab(
|
||||||
routeCandidates: routeCandidates,
|
appProvider: appProvider,
|
||||||
mapPoints: mapPoints,
|
routeCandidates: routeCandidates,
|
||||||
routePoints: routePoints,
|
mapPoints: mapPoints,
|
||||||
),
|
routePoints: routePoints,
|
||||||
const SizedBox(height: 16),
|
|
||||||
_AutomationRoutingInfo(
|
|
||||||
isExpanded: _showRoutingInfo,
|
|
||||||
onToggle: () {
|
|
||||||
setState(() {
|
|
||||||
_showRoutingInfo = !_showRoutingInfo;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
autoRouteRotationEnabled:
|
|
||||||
appProvider.autoRouteRotationEnabled,
|
|
||||||
nearestRelayFallbackEnabled:
|
|
||||||
appProvider.nearestRelayFallbackEnabled,
|
|
||||||
clearPathOnMaxRetry:
|
|
||||||
appProvider.clearPathOnMaxRetry,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
ListView(
|
|
||||||
children: [
|
|
||||||
_buildHistoryTab(),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class MessagesComposer extends StatelessWidget {
|
|||||||
final Future<void> Function() onStartVoiceRecording;
|
final Future<void> Function() onStartVoiceRecording;
|
||||||
final Future<void> Function() onStopAndSendVoice;
|
final Future<void> Function() onStopAndSendVoice;
|
||||||
final Future<void> Function() onSendMessage;
|
final Future<void> Function() onSendMessage;
|
||||||
|
final VoidCallback? onLongPressSend;
|
||||||
final String? regionScopeName;
|
final String? regionScopeName;
|
||||||
final VoidCallback? onRegionScopeTap;
|
final VoidCallback? onRegionScopeTap;
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ class MessagesComposer extends StatelessWidget {
|
|||||||
required this.onStartVoiceRecording,
|
required this.onStartVoiceRecording,
|
||||||
required this.onStopAndSendVoice,
|
required this.onStopAndSendVoice,
|
||||||
required this.onSendMessage,
|
required this.onSendMessage,
|
||||||
|
this.onLongPressSend,
|
||||||
this.regionScopeName,
|
this.regionScopeName,
|
||||||
this.onRegionScopeTap,
|
this.onRegionScopeTap,
|
||||||
});
|
});
|
||||||
@@ -163,6 +165,7 @@ class MessagesComposer extends StatelessWidget {
|
|||||||
messageByteCount: messageByteCount,
|
messageByteCount: messageByteCount,
|
||||||
maxMessageBytes: maxMessageBytes,
|
maxMessageBytes: maxMessageBytes,
|
||||||
onSendMessage: onSendMessage,
|
onSendMessage: onSendMessage,
|
||||||
|
onLongPressSend: onLongPressSend,
|
||||||
onStartVoiceRecording: onStartVoiceRecording,
|
onStartVoiceRecording: onStartVoiceRecording,
|
||||||
onStopAndSendVoice: onStopAndSendVoice,
|
onStopAndSendVoice: onStopAndSendVoice,
|
||||||
),
|
),
|
||||||
@@ -433,6 +436,7 @@ class _SendButton extends StatelessWidget {
|
|||||||
final int messageByteCount;
|
final int messageByteCount;
|
||||||
final int maxMessageBytes;
|
final int maxMessageBytes;
|
||||||
final Future<void> Function() onSendMessage;
|
final Future<void> Function() onSendMessage;
|
||||||
|
final VoidCallback? onLongPressSend;
|
||||||
final Future<void> Function() onStartVoiceRecording;
|
final Future<void> Function() onStartVoiceRecording;
|
||||||
final Future<void> Function() onStopAndSendVoice;
|
final Future<void> Function() onStopAndSendVoice;
|
||||||
|
|
||||||
@@ -445,6 +449,7 @@ class _SendButton extends StatelessWidget {
|
|||||||
required this.messageByteCount,
|
required this.messageByteCount,
|
||||||
required this.maxMessageBytes,
|
required this.maxMessageBytes,
|
||||||
required this.onSendMessage,
|
required this.onSendMessage,
|
||||||
|
this.onLongPressSend,
|
||||||
required this.onStartVoiceRecording,
|
required this.onStartVoiceRecording,
|
||||||
required this.onStopAndSendVoice,
|
required this.onStopAndSendVoice,
|
||||||
});
|
});
|
||||||
@@ -456,28 +461,32 @@ class _SendButton extends StatelessWidget {
|
|||||||
enabled: canSendText || (voiceSupported && !isSendingVoice),
|
enabled: canSendText || (voiceSupported && !isSendingVoice),
|
||||||
label: semanticsLabel,
|
label: semanticsLabel,
|
||||||
onTap: canSendText ? onSendMessage : null,
|
onTap: canSendText ? onSendMessage : null,
|
||||||
onLongPress: (voiceSupported && !isSendingVoice)
|
onLongPress: canSendText && onLongPressSend != null
|
||||||
? () {
|
? onLongPressSend
|
||||||
if (isRecording) {
|
: (voiceSupported && !isSendingVoice)
|
||||||
onStopAndSendVoice();
|
? () {
|
||||||
return;
|
if (isRecording) {
|
||||||
}
|
onStopAndSendVoice();
|
||||||
onStartVoiceRecording();
|
return;
|
||||||
}
|
}
|
||||||
: null,
|
onStartVoiceRecording();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: semanticsLabel,
|
message: semanticsLabel,
|
||||||
excludeFromSemantics: true,
|
excludeFromSemantics: true,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
excludeFromSemantics: true,
|
excludeFromSemantics: true,
|
||||||
onTap: canSendText ? onSendMessage : null,
|
onTap: canSendText ? onSendMessage : null,
|
||||||
onLongPressStart: (voiceSupported && !isSendingVoice)
|
onLongPressStart: canSendText && onLongPressSend != null
|
||||||
? (_) => onStartVoiceRecording()
|
? (_) => onLongPressSend!()
|
||||||
: null,
|
: (voiceSupported && !isSendingVoice)
|
||||||
onLongPressEnd: (voiceSupported && isRecording)
|
? (_) => onStartVoiceRecording()
|
||||||
|
: null,
|
||||||
|
onLongPressEnd: (!canSendText && voiceSupported && isRecording)
|
||||||
? (_) => onStopAndSendVoice()
|
? (_) => onStopAndSendVoice()
|
||||||
: null,
|
: null,
|
||||||
onLongPressCancel: (voiceSupported && isRecording)
|
onLongPressCancel: (!canSendText && voiceSupported && isRecording)
|
||||||
? onStopAndSendVoice
|
? onStopAndSendVoice
|
||||||
: null,
|
: null,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -207,6 +207,22 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test('clear history removes stored direct paths for one contact', () async {
|
||||||
|
final service = PathHistoryService();
|
||||||
|
|
||||||
|
await service.initialize();
|
||||||
|
await service.recordReceivedBytePath('abc123', [0x01, 0x02], 1);
|
||||||
|
await service.recordReceivedBytePath('def456', [0x03, 0x04], 1);
|
||||||
|
|
||||||
|
expect(service.historyFor('abc123').directPaths, hasLength(1));
|
||||||
|
expect(service.historyFor('def456').directPaths, hasLength(1));
|
||||||
|
|
||||||
|
await service.clearHistoryFor('abc123');
|
||||||
|
|
||||||
|
expect(service.historyFor('abc123').directPaths, isEmpty);
|
||||||
|
expect(service.historyFor('def456').directPaths, hasLength(1));
|
||||||
|
});
|
||||||
|
|
||||||
test('last successful direct path is chosen by location fit', () async {
|
test('last successful direct path is chosen by location fit', () async {
|
||||||
final service = PathHistoryService();
|
final service = PathHistoryService();
|
||||||
final contact = _buildContact(
|
final contact = _buildContact(
|
||||||
|
|||||||
@@ -415,8 +415,11 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final scaffoldKey = GlobalKey<ScaffoldMessengerState>();
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
|
scaffoldMessengerKey: scaffoldKey,
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
@@ -431,15 +434,43 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await tester.tap(find.byIcon(Icons.more_vert));
|
await tester.tap(find.byIcon(Icons.more_vert));
|
||||||
await tester.pumpAndSettle();
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
expect(find.text('Copy raw response'), findsOneWidget);
|
expect(find.text('Copy raw response'), findsOneWidget);
|
||||||
|
|
||||||
|
// Capture clipboard writes via the test platform channel mock.
|
||||||
|
String? clipboardText;
|
||||||
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
|
SystemChannels.platform,
|
||||||
|
(MethodCall call) async {
|
||||||
|
if (call.method == 'Clipboard.setData') {
|
||||||
|
final args = call.arguments as Map<dynamic, dynamic>;
|
||||||
|
clipboardText = args['text'] as String?;
|
||||||
|
}
|
||||||
|
if (call.method == 'Clipboard.getData') {
|
||||||
|
return <String, dynamic>{'text': clipboardText};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
addTearDown(() {
|
||||||
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
|
SystemChannels.platform,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
await tester.tap(find.text('Copy raw response'));
|
await tester.tap(find.text('Copy raw response'));
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
|
expect(clipboardText, '01 67 00 d7');
|
||||||
expect(clipboardData?.text, '01 67 00 d7');
|
|
||||||
expect(find.text('Raw response copied'), findsOneWidget);
|
expect(find.text('Raw response copied'), findsOneWidget);
|
||||||
|
|
||||||
|
// Clear the SnackBar to prevent its timer from blocking teardown.
|
||||||
|
scaffoldKey.currentState?.clearSnackBars();
|
||||||
|
await tester.pump(const Duration(seconds: 5));
|
||||||
|
await tester.pump(const Duration(seconds: 5));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user