mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-17 16:06:28 +10:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c8a15538e | |||
| 68eeefa04e | |||
| 2da8995d0b | |||
| 1c376b0056 | |||
| da70d5fc08 | |||
| f63bc4b787 | |||
| 9b1f1e1994 | |||
| 5f475fce4d | |||
| 7eff1df6e2 | |||
| bd030153c1 | |||
| 5140ff383d | |||
| dc57f9b9c0 | |||
| 53cd3f4461 | |||
| 35e296f1cd | |||
| 532401cc94 | |||
| 5321974cbb | |||
| 7c16dde989 | |||
| 9a75c912af | |||
| 4f609f160f | |||
| e313bea3fc | |||
| 77be2b8e6f | |||
| c81c3efe7c | |||
| cac0cc15eb | |||
| b88e5e647a | |||
| 87d11c2e6b | |||
| 7b3c099736 | |||
| 11cb14a925 | |||
| d2df2b0bed | |||
| 723bf7293c |
@@ -58,6 +58,7 @@ secrets.dart
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
.AppleDouble
|
||||||
.LSOverride
|
.LSOverride
|
||||||
|
macos/Flutter/GeneratedPluginRegistrant.swift
|
||||||
|
|
||||||
# iOS
|
# iOS
|
||||||
**/ios/Pods/
|
**/ios/Pods/
|
||||||
|
|||||||
@@ -4943,6 +4943,17 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasValidLocation(double? latitude, double? longitude) {
|
||||||
|
const double epsilon = 1e-6;
|
||||||
|
final lat = latitude ?? 0.0;
|
||||||
|
final lon = longitude ?? 0.0;
|
||||||
|
return (lat.abs() > epsilon || lon.abs() > epsilon) &&
|
||||||
|
lat >= -90.0 &&
|
||||||
|
lat <= 90.0 &&
|
||||||
|
lon >= -180.0 &&
|
||||||
|
lon <= 180.0;
|
||||||
|
}
|
||||||
|
|
||||||
void _handlePayloadAdvertReceived(
|
void _handlePayloadAdvertReceived(
|
||||||
Uint8List rawPacket,
|
Uint8List rawPacket,
|
||||||
Uint8List payload,
|
Uint8List payload,
|
||||||
@@ -4980,6 +4991,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
latitude = advert.readInt32LE() / 1e6;
|
latitude = advert.readInt32LE() / 1e6;
|
||||||
longitude = advert.readInt32LE() / 1e6;
|
longitude = advert.readInt32LE() / 1e6;
|
||||||
}
|
}
|
||||||
|
// Validate location values if present
|
||||||
|
hasLocation = hasValidLocation(latitude, longitude);
|
||||||
|
|
||||||
if (hasName && advert.remaining > 0) {
|
if (hasName && advert.remaining > 0) {
|
||||||
name = advert.readCString();
|
name = advert.readCString();
|
||||||
}
|
}
|
||||||
@@ -5045,20 +5059,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
|
|
||||||
// CRITICAL: Preserve user's path override when contact is refreshed from device
|
// CRITICAL: Preserve user's path override when contact is refreshed from device
|
||||||
_contacts[existingIndex] = existing.copyWith(
|
_contacts[existingIndex] = existing.copyWith(
|
||||||
latitude:
|
latitude: hasLocation ? latitude : existing.latitude,
|
||||||
hasLocation &&
|
longitude: hasLocation ? longitude : existing.longitude,
|
||||||
latitude != null &&
|
|
||||||
latitude.abs() <= 90 &&
|
|
||||||
(latitude != 0 || longitude != 0)
|
|
||||||
? latitude
|
|
||||||
: existing.latitude,
|
|
||||||
longitude:
|
|
||||||
hasLocation &&
|
|
||||||
longitude != null &&
|
|
||||||
longitude.abs() <= 180 &&
|
|
||||||
(latitude != 0 || longitude != 0)
|
|
||||||
? longitude
|
|
||||||
: existing.longitude,
|
|
||||||
name: hasName ? name : existing.name,
|
name: hasName ? name : existing.name,
|
||||||
path: Uint8List.fromList(path.reversed.toList()),
|
path: Uint8List.fromList(path.reversed.toList()),
|
||||||
pathLength: path.length,
|
pathLength: path.length,
|
||||||
@@ -5226,6 +5228,10 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||||||
markChannelRead(channelIndex);
|
markChannelRead(channelIndex);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void deleteAllPaths() {
|
||||||
|
_pathHistoryService?.clearAllHistories();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const int _phRouteMask = 0x03;
|
const int _phRouteMask = 0x03;
|
||||||
|
|||||||
+3
-1
@@ -1941,5 +1941,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.",
|
"appSettings_maxMessageRetriesSubtitle": "Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_multiAck": "Мулти-потвърди: {value}",
|
"settings_multiAck": "Мулти-потвърди: {value}",
|
||||||
"settings_telemetryModeUpdated": "Режим на телеметрията е обновен"
|
"settings_telemetryModeUpdated": "Режим на телеметрията е обновен",
|
||||||
|
"map_showOverlaps": "Покриване на ключа на повтаряча",
|
||||||
|
"map_runTraceWithReturnPath": "Върни се по същия път."
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1969,5 +1969,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Anzahl der Versuche, eine Nachricht erneut zu senden, bevor sie als fehlgeschlagen markiert wird.",
|
"appSettings_maxMessageRetriesSubtitle": "Anzahl der Versuche, eine Nachricht erneut zu senden, bevor sie als fehlgeschlagen markiert wird.",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_telemetryModeUpdated": "Telemetriemodus aktualisiert",
|
"settings_telemetryModeUpdated": "Telemetriemodus aktualisiert",
|
||||||
"settings_multiAck": "Mehrfach-Bestätigungen: {value}"
|
"settings_multiAck": "Mehrfach-Bestätigungen: {value}",
|
||||||
|
"map_showOverlaps": "Überlappungen der Repeater-Taste",
|
||||||
|
"map_runTraceWithReturnPath": "Auf dem gleichen Pfad zurückkehren."
|
||||||
}
|
}
|
||||||
+3
-1
@@ -878,6 +878,7 @@
|
|||||||
"map_chatNodes": "Chat Nodes",
|
"map_chatNodes": "Chat Nodes",
|
||||||
"map_repeaters": "Repeaters",
|
"map_repeaters": "Repeaters",
|
||||||
"map_otherNodes": "Other Nodes",
|
"map_otherNodes": "Other Nodes",
|
||||||
|
"map_showOverlaps": "Repeater Key Overlaps",
|
||||||
"map_keyPrefix": "Key Prefix",
|
"map_keyPrefix": "Key Prefix",
|
||||||
"map_filterByKeyPrefix": "Filter by key prefix",
|
"map_filterByKeyPrefix": "Filter by key prefix",
|
||||||
"map_publicKeyPrefix": "Public key prefix",
|
"map_publicKeyPrefix": "Public key prefix",
|
||||||
@@ -891,7 +892,8 @@
|
|||||||
"map_joinRoom": "Join Room",
|
"map_joinRoom": "Join Room",
|
||||||
"map_manageRepeater": "Manage Repeater",
|
"map_manageRepeater": "Manage Repeater",
|
||||||
"map_tapToAdd": "Tap on nodes to add them to the path.",
|
"map_tapToAdd": "Tap on nodes to add them to the path.",
|
||||||
"map_runTrace": "Run Path Trace",
|
"map_runTrace": "Run path trace",
|
||||||
|
"map_runTraceWithReturnPath": "Return back on the same path.",
|
||||||
"map_removeLast": "Remove Last",
|
"map_removeLast": "Remove Last",
|
||||||
"map_pathTraceCancelled": "Path trace cancelled.",
|
"map_pathTraceCancelled": "Path trace cancelled.",
|
||||||
"mapCache_title": "Offline Map Cache",
|
"mapCache_title": "Offline Map Cache",
|
||||||
|
|||||||
+3
-1
@@ -1969,5 +1969,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Número de intentos de reintento antes de marcar un mensaje como fallido.",
|
"appSettings_maxMessageRetriesSubtitle": "Número de intentos de reintento antes de marcar un mensaje como fallido.",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_telemetryModeUpdated": "Modo de telemetría actualizado",
|
"settings_telemetryModeUpdated": "Modo de telemetría actualizado",
|
||||||
"settings_multiAck": "Multi-ACKs: {value}"
|
"settings_multiAck": "Multi-ACKs: {value}",
|
||||||
|
"map_showOverlaps": "Superposiciones de tecla repetidora",
|
||||||
|
"map_runTraceWithReturnPath": "Volver atrás por el mismo camino."
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1941,5 +1941,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Nombre de tentatives de relance avant de marquer un message comme ayant échoué.",
|
"appSettings_maxMessageRetriesSubtitle": "Nombre de tentatives de relance avant de marquer un message comme ayant échoué.",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_multiAck": "Multi-ACKs : {value}",
|
"settings_multiAck": "Multi-ACKs : {value}",
|
||||||
"settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour"
|
"settings_telemetryModeUpdated": "Le mode télémétrie a été mis à jour",
|
||||||
|
"map_showOverlaps": "Chevauchement de la touche répétitive",
|
||||||
|
"map_runTraceWithReturnPath": "Revenir sur le même chemin."
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1941,5 +1941,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Numero di tentativi di riprova prima di considerare un messaggio come fallito.",
|
"appSettings_maxMessageRetriesSubtitle": "Numero di tentativi di riprova prima di considerare un messaggio come fallito.",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_telemetryModeUpdated": "Modalità telemetria aggiornata",
|
"settings_telemetryModeUpdated": "Modalità telemetria aggiornata",
|
||||||
"settings_multiAck": "Multi-ACKs: {value}"
|
"settings_multiAck": "Multi-ACKs: {value}",
|
||||||
|
"map_showOverlaps": "Sovrapposizioni della chiave ripetitore",
|
||||||
|
"map_runTraceWithReturnPath": "Tornare indietro sullo stesso percorso"
|
||||||
}
|
}
|
||||||
@@ -3052,6 +3052,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Other Nodes'**
|
/// **'Other Nodes'**
|
||||||
String get map_otherNodes;
|
String get map_otherNodes;
|
||||||
|
|
||||||
|
/// No description provided for @map_showOverlaps.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Repeater Key Overlaps'**
|
||||||
|
String get map_showOverlaps;
|
||||||
|
|
||||||
/// No description provided for @map_keyPrefix.
|
/// No description provided for @map_keyPrefix.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -3133,9 +3139,15 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @map_runTrace.
|
/// No description provided for @map_runTrace.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Run Path Trace'**
|
/// **'Run path trace'**
|
||||||
String get map_runTrace;
|
String get map_runTrace;
|
||||||
|
|
||||||
|
/// No description provided for @map_runTraceWithReturnPath.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Return back on the same path.'**
|
||||||
|
String get map_runTraceWithReturnPath;
|
||||||
|
|
||||||
/// No description provided for @map_removeLast.
|
/// No description provided for @map_removeLast.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -1689,6 +1689,9 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Други възли';
|
String get map_otherNodes => 'Други възли';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Покриване на ключа на повтаряча';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Префикс на ключа';
|
String get map_keyPrefix => 'Префикс на ключа';
|
||||||
|
|
||||||
@@ -1733,6 +1736,9 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Изпълни Път на Следване';
|
String get map_runTrace => 'Изпълни Път на Следване';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => 'Върни се по същия път.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Премахни Последно';
|
String get map_removeLast => 'Премахни Последно';
|
||||||
|
|
||||||
|
|||||||
@@ -1686,6 +1686,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Andere Knoten';
|
String get map_otherNodes => 'Andere Knoten';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Überlappungen der Repeater-Taste';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Schlüsselpräfix';
|
String get map_keyPrefix => 'Schlüsselpräfix';
|
||||||
|
|
||||||
@@ -1730,6 +1733,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Pfadverlauf ausführen';
|
String get map_runTrace => 'Pfadverlauf ausführen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath =>
|
||||||
|
'Auf dem gleichen Pfad zurückkehren.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Letztes Entfernen';
|
String get map_removeLast => 'Letztes Entfernen';
|
||||||
|
|
||||||
|
|||||||
@@ -1656,6 +1656,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Other Nodes';
|
String get map_otherNodes => 'Other Nodes';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Repeater Key Overlaps';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Key Prefix';
|
String get map_keyPrefix => 'Key Prefix';
|
||||||
|
|
||||||
@@ -1696,7 +1699,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get map_tapToAdd => 'Tap on nodes to add them to the path.';
|
String get map_tapToAdd => 'Tap on nodes to add them to the path.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Run Path Trace';
|
String get map_runTrace => 'Run path trace';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => 'Return back on the same path.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Remove Last';
|
String get map_removeLast => 'Remove Last';
|
||||||
|
|||||||
@@ -1685,6 +1685,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Otros Nodos';
|
String get map_otherNodes => 'Otros Nodos';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Superposiciones de tecla repetidora';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Prefijo de clave';
|
String get map_keyPrefix => 'Prefijo de clave';
|
||||||
|
|
||||||
@@ -1728,6 +1731,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Ejecutar Rastreo de Ruta';
|
String get map_runTrace => 'Ejecutar Rastreo de Ruta';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => 'Volver atrás por el mismo camino.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Eliminar último';
|
String get map_removeLast => 'Eliminar último';
|
||||||
|
|
||||||
|
|||||||
@@ -1695,6 +1695,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Autres nœuds';
|
String get map_otherNodes => 'Autres nœuds';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Chevauchement de la touche répétitive';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Préfixe clé';
|
String get map_keyPrefix => 'Préfixe clé';
|
||||||
|
|
||||||
@@ -1739,6 +1742,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Exécuter la traçage de chemin';
|
String get map_runTrace => 'Exécuter la traçage de chemin';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => 'Revenir sur le même chemin.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Supprimer le dernier';
|
String get map_removeLast => 'Supprimer le dernier';
|
||||||
|
|
||||||
|
|||||||
@@ -1687,6 +1687,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Altri Nodi';
|
String get map_otherNodes => 'Altri Nodi';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Sovrapposizioni della chiave ripetitore';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Prefisso Chiave';
|
String get map_keyPrefix => 'Prefisso Chiave';
|
||||||
|
|
||||||
@@ -1729,6 +1732,10 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Esegui Path Trace';
|
String get map_runTrace => 'Esegui Path Trace';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath =>
|
||||||
|
'Tornare indietro sullo stesso percorso';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Rimuovi ultimo';
|
String get map_removeLast => 'Rimuovi ultimo';
|
||||||
|
|
||||||
|
|||||||
@@ -1674,6 +1674,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Andere Nodes';
|
String get map_otherNodes => 'Andere Nodes';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Herhalingssleutel overlapt';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Prefix sleutel';
|
String get map_keyPrefix => 'Prefix sleutel';
|
||||||
|
|
||||||
@@ -1718,6 +1721,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Padeshulp traceren';
|
String get map_runTrace => 'Padeshulp traceren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => 'Terugkeren op hetzelfde pad.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Verwijder Laatste';
|
String get map_removeLast => 'Verwijder Laatste';
|
||||||
|
|
||||||
|
|||||||
+258
-239
File diff suppressed because it is too large
Load Diff
@@ -1686,6 +1686,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Outros Nós';
|
String get map_otherNodes => 'Outros Nós';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Sobreposições da Chave Repeater';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Prefixo Chave';
|
String get map_keyPrefix => 'Prefixo Chave';
|
||||||
|
|
||||||
@@ -1729,6 +1732,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Executar Traçado de Caminho';
|
String get map_runTrace => 'Executar Traçado de Caminho';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => 'Retornar ao mesmo caminho.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Remover Último';
|
String get map_removeLast => 'Remover Último';
|
||||||
|
|
||||||
|
|||||||
@@ -1689,6 +1689,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Другие ноды';
|
String get map_otherNodes => 'Другие ноды';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Перекрытия ключа повтора';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Префикс ключа';
|
String get map_keyPrefix => 'Префикс ключа';
|
||||||
|
|
||||||
@@ -1732,6 +1735,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Запустить трассировку пути';
|
String get map_runTrace => 'Запустить трассировку пути';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => 'Вернуться обратно по тому же пути';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Удалить последний';
|
String get map_removeLast => 'Удалить последний';
|
||||||
|
|
||||||
|
|||||||
@@ -1675,6 +1675,9 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Ostatné uzly';
|
String get map_otherNodes => 'Ostatné uzly';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Prekrývanie opakovača kľúča';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Päťciferné predpona';
|
String get map_keyPrefix => 'Päťciferné predpona';
|
||||||
|
|
||||||
@@ -1718,6 +1721,9 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Spustiť trasovaním cesty';
|
String get map_runTrace => 'Spustiť trasovaním cesty';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => 'Vráťte sa späť po tej istej ceste.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Odstrániť posledný';
|
String get map_removeLast => 'Odstrániť posledný';
|
||||||
|
|
||||||
|
|||||||
@@ -1671,6 +1671,9 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Druge vozlišča';
|
String get map_otherNodes => 'Druge vozlišča';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Prekrivanje ključa ponovnega predvajanja';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Predpona ključa';
|
String get map_keyPrefix => 'Predpona ključa';
|
||||||
|
|
||||||
@@ -1713,6 +1716,9 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Zaženi sledenje poti';
|
String get map_runTrace => 'Zaženi sledenje poti';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => 'Vrni se nazaj po isti poti.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Odstrani Zadnji';
|
String get map_removeLast => 'Odstrani Zadnji';
|
||||||
|
|
||||||
|
|||||||
@@ -1664,6 +1664,9 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Andra noder';
|
String get map_otherNodes => 'Andra noder';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Repeater-nyckelöverlappningar';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Nyckelprefix';
|
String get map_keyPrefix => 'Nyckelprefix';
|
||||||
|
|
||||||
@@ -1707,6 +1710,9 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Kör spårsökning';
|
String get map_runTrace => 'Kör spårsökning';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => 'Gå tillbaka på samma väg';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Ta bort sista';
|
String get map_removeLast => 'Ta bort sista';
|
||||||
|
|
||||||
|
|||||||
@@ -1684,6 +1684,9 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => 'Інші вузли';
|
String get map_otherNodes => 'Інші вузли';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => 'Перекриття ключа повторювача';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => 'Префікс ключа';
|
String get map_keyPrefix => 'Префікс ключа';
|
||||||
|
|
||||||
@@ -1727,6 +1730,9 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => 'Виконати трасування шляху';
|
String get map_runTrace => 'Виконати трасування шляху';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => 'Повернутися назад тим же шляхом';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => 'Видалити останній';
|
String get map_removeLast => 'Видалити останній';
|
||||||
|
|
||||||
|
|||||||
@@ -1582,6 +1582,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_otherNodes => '其他节点';
|
String get map_otherNodes => '其他节点';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_showOverlaps => '重复键重叠';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_keyPrefix => '关键字前缀';
|
String get map_keyPrefix => '关键字前缀';
|
||||||
|
|
||||||
@@ -1624,6 +1627,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get map_runTrace => '运行路径追踪';
|
String get map_runTrace => '运行路径追踪';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get map_runTraceWithReturnPath => '沿着相同的路径返回';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get map_removeLast => '移除最后一个';
|
String get map_removeLast => '移除最后一个';
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -1941,5 +1941,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Aantal pogingen om een bericht opnieuw te versturen voordat het als mislukt wordt gemarkeerd",
|
"appSettings_maxMessageRetriesSubtitle": "Aantal pogingen om een bericht opnieuw te versturen voordat het als mislukt wordt gemarkeerd",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt",
|
"settings_telemetryModeUpdated": "Telemetrie-modus bijgewerkt",
|
||||||
"settings_multiAck": "Multi-ACKs: {value}"
|
"settings_multiAck": "Multi-ACKs: {value}",
|
||||||
|
"map_showOverlaps": "Herhalingssleutel overlapt",
|
||||||
|
"map_runTraceWithReturnPath": "Terugkeren op hetzelfde pad."
|
||||||
}
|
}
|
||||||
+265
-225
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -1941,5 +1941,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Número de tentativas de reenvio antes de classificar uma mensagem como falha.",
|
"appSettings_maxMessageRetriesSubtitle": "Número de tentativas de reenvio antes de classificar uma mensagem como falha.",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_telemetryModeUpdated": "Modo de telemetria atualizado",
|
"settings_telemetryModeUpdated": "Modo de telemetria atualizado",
|
||||||
"settings_multiAck": "Multi-ACKs: {value}"
|
"settings_multiAck": "Multi-ACKs: {value}",
|
||||||
|
"map_showOverlaps": "Sobreposições da Chave Repeater",
|
||||||
|
"map_runTraceWithReturnPath": "Retornar ao mesmo caminho."
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1181,5 +1181,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.",
|
"appSettings_maxMessageRetriesSubtitle": "Количество попыток повторной отправки сообщения перед тем, как пометить его как неудачное.",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_telemetryModeUpdated": "Режим телеметрии обновлен",
|
"settings_telemetryModeUpdated": "Режим телеметрии обновлен",
|
||||||
"settings_multiAck": "Мульти-ACK: {value}"
|
"settings_multiAck": "Мульти-ACK: {value}",
|
||||||
|
"map_showOverlaps": "Перекрытия ключа повтора",
|
||||||
|
"map_runTraceWithReturnPath": "Вернуться обратно по тому же пути"
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1941,5 +1941,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Počet pokusov o odošleť pred označením správy ako neúspešnej",
|
"appSettings_maxMessageRetriesSubtitle": "Počet pokusov o odošleť pred označením správy ako neúspešnej",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný",
|
"settings_telemetryModeUpdated": "Režim telemetrie bol aktualizovaný",
|
||||||
"settings_multiAck": "Viaceré ACK: {value}"
|
"settings_multiAck": "Viaceré ACK: {value}",
|
||||||
|
"map_showOverlaps": "Prekrývanie opakovača kľúča",
|
||||||
|
"map_runTraceWithReturnPath": "Vráťte sa späť po tej istej ceste."
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1941,5 +1941,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno",
|
"appSettings_maxMessageRetriesSubtitle": "Število poskusov ponovnega poslanja, preden se sporočilo označuje kot neuspešno",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_multiAck": "Večkratni potrditvi: {value}",
|
"settings_multiAck": "Večkratni potrditvi: {value}",
|
||||||
"settings_telemetryModeUpdated": "Način telemetrije posodobljen"
|
"settings_telemetryModeUpdated": "Način telemetrije posodobljen",
|
||||||
|
"map_showOverlaps": "Prekrivanje ključa ponovnega predvajanja",
|
||||||
|
"map_runTraceWithReturnPath": "Vrni se nazaj po isti poti."
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1941,5 +1941,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Antal försök att skicka om ett meddelande innan det markeras som misslyckat.",
|
"appSettings_maxMessageRetriesSubtitle": "Antal försök att skicka om ett meddelande innan det markeras som misslyckat.",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_telemetryModeUpdated": "Telemetri-läge uppdaterat",
|
"settings_telemetryModeUpdated": "Telemetri-läge uppdaterat",
|
||||||
"settings_multiAck": "Multi-ACKs: {value}"
|
"settings_multiAck": "Multi-ACKs: {value}",
|
||||||
|
"map_showOverlaps": "Repeater-nyckelöverlappningar",
|
||||||
|
"map_runTraceWithReturnPath": "Gå tillbaka på samma väg"
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1941,5 +1941,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале",
|
"appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_telemetryModeUpdated": "Режим телеметрії оновлено",
|
"settings_telemetryModeUpdated": "Режим телеметрії оновлено",
|
||||||
"settings_multiAck": "Багатократне підтвердження: {value}"
|
"settings_multiAck": "Багатократне підтвердження: {value}",
|
||||||
|
"map_showOverlaps": "Перекриття ключа повторювача",
|
||||||
|
"map_runTraceWithReturnPath": "Повернутися назад тим же шляхом"
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1946,5 +1946,7 @@
|
|||||||
"appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数",
|
"appSettings_maxMessageRetriesSubtitle": "在将消息标记为失败之前,允许尝试的次数",
|
||||||
"path_routeWeight": "{weight}/{max}",
|
"path_routeWeight": "{weight}/{max}",
|
||||||
"settings_multiAck": "多重ACK:{value}",
|
"settings_multiAck": "多重ACK:{value}",
|
||||||
"settings_telemetryModeUpdated": "遥测模式已更新"
|
"settings_telemetryModeUpdated": "遥测模式已更新",
|
||||||
|
"map_showOverlaps": "重复键重叠",
|
||||||
|
"map_runTraceWithReturnPath": "沿着相同的路径返回"
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,7 @@ class AppSettings {
|
|||||||
final bool mapShowRepeaters;
|
final bool mapShowRepeaters;
|
||||||
final bool mapShowChatNodes;
|
final bool mapShowChatNodes;
|
||||||
final bool mapShowOtherNodes;
|
final bool mapShowOtherNodes;
|
||||||
|
final bool mapShowOverlaps;
|
||||||
final double mapTimeFilterHours; // 0 = all time
|
final double mapTimeFilterHours; // 0 = all time
|
||||||
final bool mapKeyPrefixEnabled;
|
final bool mapKeyPrefixEnabled;
|
||||||
final String mapKeyPrefix;
|
final String mapKeyPrefix;
|
||||||
@@ -53,6 +54,7 @@ class AppSettings {
|
|||||||
this.mapShowRepeaters = true,
|
this.mapShowRepeaters = true,
|
||||||
this.mapShowChatNodes = true,
|
this.mapShowChatNodes = true,
|
||||||
this.mapShowOtherNodes = true,
|
this.mapShowOtherNodes = true,
|
||||||
|
this.mapShowOverlaps = false,
|
||||||
this.mapTimeFilterHours = 0, // Default to all time
|
this.mapTimeFilterHours = 0, // Default to all time
|
||||||
this.mapKeyPrefixEnabled = false,
|
this.mapKeyPrefixEnabled = false,
|
||||||
this.mapKeyPrefix = '',
|
this.mapKeyPrefix = '',
|
||||||
@@ -92,6 +94,7 @@ class AppSettings {
|
|||||||
'map_show_repeaters': mapShowRepeaters,
|
'map_show_repeaters': mapShowRepeaters,
|
||||||
'map_show_chat_nodes': mapShowChatNodes,
|
'map_show_chat_nodes': mapShowChatNodes,
|
||||||
'map_show_other_nodes': mapShowOtherNodes,
|
'map_show_other_nodes': mapShowOtherNodes,
|
||||||
|
'map_show_overlaps': mapShowOverlaps,
|
||||||
'map_time_filter_hours': mapTimeFilterHours,
|
'map_time_filter_hours': mapTimeFilterHours,
|
||||||
'map_key_prefix_enabled': mapKeyPrefixEnabled,
|
'map_key_prefix_enabled': mapKeyPrefixEnabled,
|
||||||
'map_key_prefix': mapKeyPrefix,
|
'map_key_prefix': mapKeyPrefix,
|
||||||
@@ -137,6 +140,7 @@ class AppSettings {
|
|||||||
mapShowRepeaters: json['map_show_repeaters'] as bool? ?? true,
|
mapShowRepeaters: json['map_show_repeaters'] as bool? ?? true,
|
||||||
mapShowChatNodes: json['map_show_chat_nodes'] as bool? ?? true,
|
mapShowChatNodes: json['map_show_chat_nodes'] as bool? ?? true,
|
||||||
mapShowOtherNodes: json['map_show_other_nodes'] as bool? ?? true,
|
mapShowOtherNodes: json['map_show_other_nodes'] as bool? ?? true,
|
||||||
|
mapShowOverlaps: json['map_show_overlaps'] as bool? ?? false,
|
||||||
mapTimeFilterHours:
|
mapTimeFilterHours:
|
||||||
(json['map_time_filter_hours'] as num?)?.toDouble() ?? 0,
|
(json['map_time_filter_hours'] as num?)?.toDouble() ?? 0,
|
||||||
mapKeyPrefixEnabled: json['map_key_prefix_enabled'] as bool? ?? false,
|
mapKeyPrefixEnabled: json['map_key_prefix_enabled'] as bool? ?? false,
|
||||||
@@ -196,6 +200,7 @@ class AppSettings {
|
|||||||
bool? mapShowRepeaters,
|
bool? mapShowRepeaters,
|
||||||
bool? mapShowChatNodes,
|
bool? mapShowChatNodes,
|
||||||
bool? mapShowOtherNodes,
|
bool? mapShowOtherNodes,
|
||||||
|
bool? mapShowOverlaps,
|
||||||
double? mapTimeFilterHours,
|
double? mapTimeFilterHours,
|
||||||
bool? mapKeyPrefixEnabled,
|
bool? mapKeyPrefixEnabled,
|
||||||
String? mapKeyPrefix,
|
String? mapKeyPrefix,
|
||||||
@@ -231,6 +236,7 @@ class AppSettings {
|
|||||||
mapShowRepeaters: mapShowRepeaters ?? this.mapShowRepeaters,
|
mapShowRepeaters: mapShowRepeaters ?? this.mapShowRepeaters,
|
||||||
mapShowChatNodes: mapShowChatNodes ?? this.mapShowChatNodes,
|
mapShowChatNodes: mapShowChatNodes ?? this.mapShowChatNodes,
|
||||||
mapShowOtherNodes: mapShowOtherNodes ?? this.mapShowOtherNodes,
|
mapShowOtherNodes: mapShowOtherNodes ?? this.mapShowOtherNodes,
|
||||||
|
mapShowOverlaps: mapShowOverlaps ?? this.mapShowOverlaps,
|
||||||
mapTimeFilterHours: mapTimeFilterHours ?? this.mapTimeFilterHours,
|
mapTimeFilterHours: mapTimeFilterHours ?? this.mapTimeFilterHours,
|
||||||
mapKeyPrefixEnabled: mapKeyPrefixEnabled ?? this.mapKeyPrefixEnabled,
|
mapKeyPrefixEnabled: mapKeyPrefixEnabled ?? this.mapKeyPrefixEnabled,
|
||||||
mapKeyPrefix: mapKeyPrefix ?? this.mapKeyPrefix,
|
mapKeyPrefix: mapKeyPrefix ?? this.mapKeyPrefix,
|
||||||
|
|||||||
@@ -183,13 +183,14 @@ class Contact {
|
|||||||
final lastMod = reader.readUInt32LE();
|
final lastMod = reader.readUInt32LE();
|
||||||
|
|
||||||
double? lat, lon;
|
double? lat, lon;
|
||||||
|
if (reader.remaining >= 8) {
|
||||||
final latRaw = reader.readInt32LE();
|
final latRaw = reader.readInt32LE();
|
||||||
final lonRaw = reader.readInt32LE();
|
final lonRaw = reader.readInt32LE();
|
||||||
|
|
||||||
if (latRaw != 0 || lonRaw != 0) {
|
if (latRaw != 0 || lonRaw != 0) {
|
||||||
lat = latRaw / 1e6;
|
lat = latRaw / 1e6;
|
||||||
lon = lonRaw / 1e6;
|
lon = lonRaw / 1e6;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Contact(
|
return Contact(
|
||||||
publicKey: pubKey,
|
publicKey: pubKey,
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ class ChannelMessagePathScreen extends StatelessWidget {
|
|||||||
final primaryPath = !channelMessage && !message.isOutgoing
|
final primaryPath = !channelMessage && !message.isOutgoing
|
||||||
? Uint8List.fromList(primaryPathTmp.reversed.toList())
|
? Uint8List.fromList(primaryPathTmp.reversed.toList())
|
||||||
: primaryPathTmp;
|
: primaryPathTmp;
|
||||||
final contacts = connector.allContacts;
|
final hops = _buildPathHops(primaryPath, connector, l10n);
|
||||||
final hops = _buildPathHops(primaryPath, contacts, l10n);
|
|
||||||
final hasHopDetails = primaryPath.isNotEmpty;
|
final hasHopDetails = primaryPath.isNotEmpty;
|
||||||
final observedLabel = _formatObservedHops(
|
final observedLabel = _formatObservedHops(
|
||||||
primaryPath.length,
|
primaryPath.length,
|
||||||
@@ -303,10 +302,12 @@ class _ChannelMessagePathMapScreenState
|
|||||||
extends State<ChannelMessagePathMapScreen> {
|
extends State<ChannelMessagePathMapScreen> {
|
||||||
static const double _labelZoomThreshold = 8.5;
|
static const double _labelZoomThreshold = 8.5;
|
||||||
|
|
||||||
|
final MapController _mapController = MapController();
|
||||||
Uint8List? _selectedPath;
|
Uint8List? _selectedPath;
|
||||||
double _pathDistance = 0.0;
|
double _pathDistance = 0.0;
|
||||||
bool _showNodeLabels = true;
|
bool _showNodeLabels = true;
|
||||||
bool _didReceivePositionUpdate = false;
|
bool _didReceivePositionUpdate = false;
|
||||||
|
int? _focusedHopIndex;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -337,6 +338,22 @@ class _ChannelMessagePathMapScreenState
|
|||||||
return totalDistance;
|
return totalDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _focusHop(_PathHop hop) {
|
||||||
|
if (!hop.hasLocation) return;
|
||||||
|
final targetZoom = _didReceivePositionUpdate
|
||||||
|
? max(_mapController.camera.zoom, 10.0)
|
||||||
|
: 12.0;
|
||||||
|
_mapController.move(hop.position!, targetZoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onHopTapped(_PathHop hop) {
|
||||||
|
_focusHop(hop);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_focusedHopIndex = hop.index;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<MeshCoreConnector>(
|
return Consumer<MeshCoreConnector>(
|
||||||
@@ -365,8 +382,7 @@ class _ChannelMessagePathMapScreenState
|
|||||||
: selectedPathTmp;
|
: selectedPathTmp;
|
||||||
|
|
||||||
final selectedIndex = _indexForPath(selectedPath, observedPaths);
|
final selectedIndex = _indexForPath(selectedPath, observedPaths);
|
||||||
final contacts = connector.allContacts;
|
final hops = _buildPathHops(selectedPath, connector, context.l10n);
|
||||||
final hops = _buildPathHops(selectedPath, contacts, context.l10n);
|
|
||||||
|
|
||||||
final points = <LatLng>[];
|
final points = <LatLng>[];
|
||||||
|
|
||||||
@@ -421,6 +437,7 @@ class _ChannelMessagePathMapScreenState
|
|||||||
children: [
|
children: [
|
||||||
FlutterMap(
|
FlutterMap(
|
||||||
key: mapKey,
|
key: mapKey,
|
||||||
|
mapController: _mapController,
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
initialCenter: initialCenter,
|
initialCenter: initialCenter,
|
||||||
initialZoom: initialZoom,
|
initialZoom: initialZoom,
|
||||||
@@ -472,6 +489,7 @@ class _ChannelMessagePathMapScreenState
|
|||||||
) {
|
) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedPath = observedPaths[index].pathBytes;
|
_selectedPath = observedPaths[index].pathBytes;
|
||||||
|
_focusedHopIndex = null;
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
if (points.isEmpty)
|
if (points.isEmpty)
|
||||||
@@ -727,8 +745,17 @@ class _ChannelMessagePathMapScreenState
|
|||||||
separatorBuilder: (_, _) => const Divider(height: 1),
|
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final hop = hops[index];
|
final hop = hops[index];
|
||||||
|
final isFocused = _focusedHopIndex == hop.index;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
|
enabled: hop.hasLocation,
|
||||||
|
selected: isFocused,
|
||||||
|
selectedTileColor: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.primary.withValues(alpha: 0.12),
|
||||||
|
onTap: hop.hasLocation
|
||||||
|
? () => _onHopTapped(hop)
|
||||||
|
: null,
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
radius: 14,
|
radius: 14,
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -787,19 +814,71 @@ class _ObservedPath {
|
|||||||
|
|
||||||
List<_PathHop> _buildPathHops(
|
List<_PathHop> _buildPathHops(
|
||||||
Uint8List pathBytes,
|
Uint8List pathBytes,
|
||||||
List<Contact> contacts,
|
MeshCoreConnector connector,
|
||||||
AppLocalizations l10n,
|
AppLocalizations l10n,
|
||||||
) {
|
) {
|
||||||
|
if (pathBytes.isEmpty) return const [];
|
||||||
|
final candidatesByPrefix = <int, List<Contact>>{};
|
||||||
|
for (final contact in connector.allContacts) {
|
||||||
|
if (contact.publicKey.isEmpty) continue;
|
||||||
|
if (contact.type != advTypeRepeater && contact.type != advTypeRoom) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final prefix = contact.publicKey.first;
|
||||||
|
candidatesByPrefix.putIfAbsent(prefix, () => <Contact>[]).add(contact);
|
||||||
|
}
|
||||||
|
for (final candidates in candidatesByPrefix.values) {
|
||||||
|
candidates.sort((a, b) => b.lastSeen.compareTo(a.lastSeen));
|
||||||
|
}
|
||||||
|
final startPoint =
|
||||||
|
(connector.selfLatitude != null && connector.selfLongitude != null)
|
||||||
|
? LatLng(connector.selfLatitude!, connector.selfLongitude!)
|
||||||
|
: null;
|
||||||
|
var previousPosition = startPoint;
|
||||||
|
final distance = Distance();
|
||||||
|
|
||||||
final hops = <_PathHop>[];
|
final hops = <_PathHop>[];
|
||||||
for (var i = 0; i < pathBytes.length; i++) {
|
for (var i = 0; i < pathBytes.length; i++) {
|
||||||
final prefix = pathBytes[i];
|
final searchPoint = i == 0 ? startPoint : previousPosition;
|
||||||
final contact = _matchContactForPrefix(contacts, prefix);
|
final candidates = candidatesByPrefix[pathBytes[i]];
|
||||||
|
Contact? contact;
|
||||||
|
if (candidates != null && candidates.isNotEmpty) {
|
||||||
|
var bestIndex = 0;
|
||||||
|
if (searchPoint != null) {
|
||||||
|
var bestDistance = double.infinity;
|
||||||
|
for (var j = 0; j < candidates.length; j++) {
|
||||||
|
final candidate = candidates[j];
|
||||||
|
if (!candidate.hasLocation ||
|
||||||
|
candidate.latitude == null ||
|
||||||
|
candidate.longitude == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final currentDistance = distance(
|
||||||
|
searchPoint,
|
||||||
|
LatLng(candidate.latitude!, candidate.longitude!),
|
||||||
|
);
|
||||||
|
if (currentDistance < bestDistance) {
|
||||||
|
bestDistance = currentDistance;
|
||||||
|
bestIndex = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contact = candidates.removeAt(bestIndex);
|
||||||
|
if (candidates.isEmpty) {
|
||||||
|
candidatesByPrefix.remove(pathBytes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final resolvedPosition = _resolvePosition(contact);
|
||||||
|
if (resolvedPosition != null) {
|
||||||
|
previousPosition = resolvedPosition;
|
||||||
|
}
|
||||||
hops.add(
|
hops.add(
|
||||||
_PathHop(
|
_PathHop(
|
||||||
index: i + 1,
|
index: i + 1,
|
||||||
prefix: prefix,
|
prefix: pathBytes[i],
|
||||||
contact: contact,
|
contact: contact,
|
||||||
position: _resolvePosition(contact),
|
position: resolvedPosition,
|
||||||
l10n: l10n,
|
l10n: l10n,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -807,42 +886,13 @@ List<_PathHop> _buildPathHops(
|
|||||||
return hops;
|
return hops;
|
||||||
}
|
}
|
||||||
|
|
||||||
Contact? _matchContactForPrefix(List<Contact> contacts, int prefix) {
|
|
||||||
final matches = contacts
|
|
||||||
.where(
|
|
||||||
(contact) =>
|
|
||||||
(contact.type == advTypeRepeater || contact.type == advTypeRoom) &&
|
|
||||||
contact.publicKey.isNotEmpty &&
|
|
||||||
contact.publicKey[0] == prefix,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
if (matches.isEmpty) return null;
|
|
||||||
|
|
||||||
Contact? pickWhere(bool Function(Contact) predicate) {
|
|
||||||
for (final contact in matches) {
|
|
||||||
if (predicate(contact)) return contact;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pickWhere((c) => c.type == advTypeRepeater && _hasValidLocation(c)) ??
|
|
||||||
pickWhere((c) => c.type == advTypeRepeater) ??
|
|
||||||
pickWhere(_hasValidLocation) ??
|
|
||||||
matches.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
LatLng? _resolvePosition(Contact? contact) {
|
LatLng? _resolvePosition(Contact? contact) {
|
||||||
if (contact == null) return null;
|
if (contact == null) return null;
|
||||||
if (!_hasValidLocation(contact)) return null;
|
if (!contact.hasLocation) return null;
|
||||||
return LatLng(contact.latitude!, contact.longitude!);
|
final latitude = contact.latitude;
|
||||||
}
|
final longitude = contact.longitude;
|
||||||
|
if (latitude == null || longitude == null) return null;
|
||||||
bool _hasValidLocation(Contact contact) {
|
return LatLng(latitude, longitude);
|
||||||
final lat = contact.latitude;
|
|
||||||
final lon = contact.longitude;
|
|
||||||
if (lat == null || lon == null) return false;
|
|
||||||
if (lat == 0 && lon == 0) return false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatPrefix(int prefix) {
|
String _formatPrefix(int prefix) {
|
||||||
|
|||||||
+201
-51
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:collection';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ class MapScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _MapScreenState extends State<MapScreen> {
|
class _MapScreenState extends State<MapScreen> {
|
||||||
// Zoom level at which node labels start to appear
|
// Zoom level at which node labels start to appear
|
||||||
static const double _labelZoomThreshold = 12.0;
|
static const double _labelZoomThreshold = 14.0;
|
||||||
|
|
||||||
final MapController _mapController = MapController();
|
final MapController _mapController = MapController();
|
||||||
final MapMarkerService _markerService = MapMarkerService();
|
final MapMarkerService _markerService = MapMarkerService();
|
||||||
@@ -329,7 +330,9 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
if (!_isBuildingPathTrace)
|
if (!_isBuildingPathTrace)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.radar),
|
icon: const Icon(Icons.radar),
|
||||||
onPressed: () => _startPath(),
|
onPressed: () => _startPath(
|
||||||
|
LatLng(connector.selfLatitude!, connector.selfLongitude!),
|
||||||
|
),
|
||||||
tooltip: context.l10n.contacts_pathTrace,
|
tooltip: context.l10n.contacts_pathTrace,
|
||||||
),
|
),
|
||||||
if (!_isBuildingPathTrace)
|
if (!_isBuildingPathTrace)
|
||||||
@@ -477,12 +480,14 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
point: highlightPosition,
|
point: highlightPosition,
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
|
child: IgnorePointer(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.location_on_outlined,
|
Icons.location_on_outlined,
|
||||||
color: Colors.red[600],
|
color: Colors.red[600],
|
||||||
size: 34,
|
size: 34,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (!_isBuildingPathTrace)
|
if (!_isBuildingPathTrace)
|
||||||
..._buildGuessedMarker(
|
..._buildGuessedMarker(
|
||||||
guessedLocations,
|
guessedLocations,
|
||||||
@@ -503,6 +508,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
),
|
),
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
|
child: IgnorePointer(
|
||||||
|
ignoring: true,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -514,7 +521,9 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
),
|
),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black.withValues(alpha: 0.3),
|
color: Colors.black.withValues(
|
||||||
|
alpha: 0.3,
|
||||||
|
),
|
||||||
blurRadius: 4,
|
blurRadius: 4,
|
||||||
offset: const Offset(0, 2),
|
offset: const Offset(0, 2),
|
||||||
),
|
),
|
||||||
@@ -528,6 +537,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (_showNodeLabels &&
|
if (_showNodeLabels &&
|
||||||
connector.selfLatitude != null &&
|
connector.selfLatitude != null &&
|
||||||
connector.selfLongitude != null)
|
connector.selfLongitude != null)
|
||||||
@@ -544,6 +554,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
),
|
),
|
||||||
if (!_isBuildingPathTrace)
|
if (!_isBuildingPathTrace)
|
||||||
_buildLegend(
|
_buildLegend(
|
||||||
|
contacts,
|
||||||
contactsWithLocation,
|
contactsWithLocation,
|
||||||
settings,
|
settings,
|
||||||
sharedMarkers.length,
|
sharedMarkers.length,
|
||||||
@@ -580,6 +591,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
// Index known-location repeaters by their 1-byte hash.
|
// Index known-location repeaters by their 1-byte hash.
|
||||||
// null value = two repeaters share the same hash byte (ambiguous collision).
|
// null value = two repeaters share the same hash byte (ambiguous collision).
|
||||||
final repeaterByHash = <int, Contact?>{};
|
final repeaterByHash = <int, Contact?>{};
|
||||||
|
|
||||||
for (final c in withLocation) {
|
for (final c in withLocation) {
|
||||||
if (c.type == advTypeRepeater) {
|
if (c.type == advTypeRepeater) {
|
||||||
if (repeaterByHash.containsKey(c.publicKey[0])) {
|
if (repeaterByHash.containsKey(c.publicKey[0])) {
|
||||||
@@ -595,6 +607,11 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
|
|
||||||
for (final contact in allContacts) {
|
for (final contact in allContacts) {
|
||||||
if (contact.hasLocation) continue;
|
if (contact.hasLocation) continue;
|
||||||
|
if (contact.lastSeen.isBefore(
|
||||||
|
DateTime.now().subtract(const Duration(hours: 24)),
|
||||||
|
)) {
|
||||||
|
continue; // skip stale contacts
|
||||||
|
}
|
||||||
|
|
||||||
final anchorSet = <LatLng>{};
|
final anchorSet = <LatLng>{};
|
||||||
|
|
||||||
@@ -641,10 +658,19 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
continue; // discard implausible guesses near (0, 0)
|
continue; // discard implausible guesses near (0, 0)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
double lat = 0, lon = 0;
|
double lat = 0, lon = 0, weight = 1.0;
|
||||||
|
int counted = 0;
|
||||||
for (final a in anchors) {
|
for (final a in anchors) {
|
||||||
lat += a.latitude;
|
if (counted == 0) {
|
||||||
lon += a.longitude;
|
lat = a.latitude;
|
||||||
|
lon = a.longitude;
|
||||||
|
} else {
|
||||||
|
lat += a.latitude * weight;
|
||||||
|
lon += a.longitude * weight;
|
||||||
|
}
|
||||||
|
// weight subsequent anchors less to create a bias towards the first (if more than 2)
|
||||||
|
weight = weight / 2;
|
||||||
|
counted++;
|
||||||
}
|
}
|
||||||
position = _offsetGuessedPosition(
|
position = _offsetGuessedPosition(
|
||||||
LatLng(lat / anchors.length, lon / anchors.length),
|
LatLng(lat / anchors.length, lon / anchors.length),
|
||||||
@@ -812,31 +838,70 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
return markers;
|
return markers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Contact> _filterContactsBySettings(
|
||||||
|
List<Contact> contacts,
|
||||||
|
dynamic settings, {
|
||||||
|
bool noLocations = false,
|
||||||
|
}) {
|
||||||
|
List<Contact> filtered = [];
|
||||||
|
bool addContact = false;
|
||||||
|
for (final contact in contacts) {
|
||||||
|
addContact = false;
|
||||||
|
if (!contact.hasLocation && !noLocations) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply node type filters
|
||||||
|
if (contact.type == advTypeRepeater &&
|
||||||
|
(settings.mapShowRepeaters ||
|
||||||
|
_isBuildingPathTrace ||
|
||||||
|
settings.mapShowOverlaps)) {
|
||||||
|
addContact = true;
|
||||||
|
}
|
||||||
|
if (contact.type == advTypeChat &&
|
||||||
|
(settings.mapShowChatNodes || _isBuildingPathTrace)) {
|
||||||
|
addContact = true;
|
||||||
|
}
|
||||||
|
if (contact.type != advTypeChat &&
|
||||||
|
contact.type != advTypeRepeater &&
|
||||||
|
(settings.mapShowOtherNodes ||
|
||||||
|
_isBuildingPathTrace ||
|
||||||
|
settings.mapShowOverlaps)) {
|
||||||
|
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) {
|
||||||
|
addContact = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addContact) {
|
||||||
|
filtered.add(contact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
List<Marker> _buildMarkers(
|
List<Marker> _buildMarkers(
|
||||||
List<Contact> contacts,
|
List<Contact> contacts,
|
||||||
settings, {
|
settings, {
|
||||||
required bool showLabels,
|
required bool showLabels,
|
||||||
}) {
|
}) {
|
||||||
final markers = <Marker>[];
|
final markers = <Marker>[];
|
||||||
|
final filteredContacts = _filterContactsBySettings(contacts, settings);
|
||||||
for (final contact in contacts) {
|
for (final contact in filteredContacts) {
|
||||||
if (!contact.hasLocation) continue;
|
|
||||||
|
|
||||||
// Apply node type filters
|
|
||||||
if (contact.type == advTypeRepeater &&
|
|
||||||
(!settings.mapShowRepeaters && !_isBuildingPathTrace)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (contact.type == advTypeChat &&
|
|
||||||
!(settings.mapShowChatNodes && !_isBuildingPathTrace)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (contact.type != advTypeChat &&
|
|
||||||
contact.type != advTypeRepeater &&
|
|
||||||
(!settings.mapShowOtherNodes && !_isBuildingPathTrace)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final marker = Marker(
|
final marker = Marker(
|
||||||
point: LatLng(contact.latitude!, contact.longitude!),
|
point: LatLng(contact.latitude!, contact.longitude!),
|
||||||
width: 35,
|
width: 35,
|
||||||
@@ -852,7 +917,9 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: _getNodeColor(contact.type),
|
color: settings.mapShowOverlaps && !_isBuildingPathTrace
|
||||||
|
? Colors.red
|
||||||
|
: _getNodeColor(contact.type),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
border: Border.all(color: Colors.white, width: 2),
|
border: Border.all(color: Colors.white, width: 2),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
@@ -879,7 +946,9 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
markers.add(
|
markers.add(
|
||||||
_buildNodeLabelMarker(
|
_buildNodeLabelMarker(
|
||||||
point: LatLng(contact.latitude!, contact.longitude!),
|
point: LatLng(contact.latitude!, contact.longitude!),
|
||||||
label: contact.name,
|
label: settings.mapShowOverlaps && !_isBuildingPathTrace
|
||||||
|
? "${contact.publicKeyHex.substring(0, 2)}:${contact.name}"
|
||||||
|
: contact.name,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -954,25 +1023,25 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLegend(
|
Widget _buildLegend(
|
||||||
|
List<Contact> contacts,
|
||||||
List<Contact> contactsWithLocation,
|
List<Contact> contactsWithLocation,
|
||||||
settings,
|
settings,
|
||||||
int markerCount,
|
int markerCount,
|
||||||
int guessedCount,
|
int guessedCount,
|
||||||
) {
|
) {
|
||||||
int nodeCount = 0;
|
final filteredContacts = _filterContactsBySettings(
|
||||||
for (final contact in contactsWithLocation) {
|
contacts,
|
||||||
// Apply node type filters
|
settings,
|
||||||
if (contact.type == advTypeRepeater && !settings.mapShowRepeaters) {
|
noLocations: false,
|
||||||
continue;
|
);
|
||||||
}
|
final filteredContactsAll = _filterContactsBySettings(
|
||||||
if (contact.type == advTypeChat && !settings.mapShowChatNodes) continue;
|
contacts,
|
||||||
if (contact.type != advTypeChat &&
|
settings,
|
||||||
contact.type != advTypeRepeater &&
|
noLocations: true,
|
||||||
!settings.mapShowOtherNodes) {
|
);
|
||||||
continue;
|
|
||||||
}
|
final nodeCount = filteredContacts.length;
|
||||||
nodeCount++;
|
final nodeCountAll = filteredContactsAll.length;
|
||||||
}
|
|
||||||
|
|
||||||
return Positioned(
|
return Positioned(
|
||||||
top: 16,
|
top: 16,
|
||||||
@@ -1008,6 +1077,54 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.location_on,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
": $nodeCount",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.wrong_location,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
": ${nodeCountAll - nodeCount}",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.add_outlined,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
": $nodeCountAll",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
context.l10n.map_pinsCount(markerCount),
|
context.l10n.map_pinsCount(markerCount),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@@ -1846,6 +1963,15 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
},
|
},
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
title: Text(context.l10n.map_showOverlaps),
|
||||||
|
value: settings.mapShowOverlaps,
|
||||||
|
onChanged: (value) {
|
||||||
|
service.setMapShowOverlaps(value ?? true);
|
||||||
|
},
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
context.l10n.map_keyPrefix,
|
context.l10n.map_keyPrefix,
|
||||||
@@ -2004,12 +2130,13 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startPath() {
|
void _startPath(LatLng position) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isBuildingPathTrace = true;
|
_isBuildingPathTrace = true;
|
||||||
_pathTrace.clear();
|
_pathTrace.clear();
|
||||||
_points.clear();
|
_points.clear();
|
||||||
_polylines.clear();
|
_polylines.clear();
|
||||||
|
_points.add(position);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2055,14 +2182,14 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
.join(','),
|
.join(','),
|
||||||
style: TextStyle(fontSize: 18),
|
style: TextStyle(fontSize: 18),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
// const SizedBox(height: 6),
|
||||||
Wrap(
|
Wrap(
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
spacing: 8,
|
spacing: 1,
|
||||||
runSpacing: 8,
|
runSpacing: 1,
|
||||||
children: [
|
children: [
|
||||||
if (_pathTrace.isNotEmpty)
|
if (_pathTrace.isNotEmpty)
|
||||||
ElevatedButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
@@ -2077,15 +2204,37 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
_isBuildingPathTrace = false;
|
_isBuildingPathTrace = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Text(l10n.map_runTrace),
|
tooltip: l10n.map_runTrace,
|
||||||
|
icon: const Icon(Icons.arrow_forward_outlined),
|
||||||
),
|
),
|
||||||
if (_pathTrace.isNotEmpty)
|
if (_pathTrace.isNotEmpty)
|
||||||
ElevatedButton(
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => PathTraceMapScreen(
|
||||||
|
title: l10n.contacts_pathTrace,
|
||||||
|
path: Uint8List.fromList(_pathTrace),
|
||||||
|
flipPathAround: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
_isBuildingPathTrace = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
tooltip: l10n.map_runTraceWithReturnPath,
|
||||||
|
icon: const Icon(Icons.replay),
|
||||||
|
),
|
||||||
|
if (_pathTrace.isNotEmpty)
|
||||||
|
IconButton(
|
||||||
onPressed: _removePath,
|
onPressed: _removePath,
|
||||||
child: Text(l10n.map_removeLast),
|
tooltip: l10n.map_removeLast,
|
||||||
|
icon: const Icon(Icons.undo),
|
||||||
),
|
),
|
||||||
if (_pathTrace.isEmpty)
|
if (_pathTrace.isEmpty)
|
||||||
ElevatedButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isBuildingPathTrace = false;
|
_isBuildingPathTrace = false;
|
||||||
@@ -2097,7 +2246,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||||||
SnackBar(content: Text(l10n.map_pathTraceCancelled)),
|
SnackBar(content: Text(l10n.map_pathTraceCancelled)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(l10n.common_cancel),
|
tooltip: l10n.common_cancel,
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -311,10 +311,13 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.cell_tower),
|
leading: const Icon(Icons.delete_outline, color: Colors.red),
|
||||||
title: Text(l10n.settings_sendAdvertisement),
|
title: Text("Delete All Paths"),
|
||||||
subtitle: Text(l10n.settings_sendAdvertisementSubtitle),
|
subtitle: Text(
|
||||||
onTap: () => _sendAdvert(context, connector),
|
"Clear all path data from contacts.",
|
||||||
|
style: TextStyle(color: Colors.red[700]),
|
||||||
|
),
|
||||||
|
onTap: () => connector.deleteAllPaths(),
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -657,14 +660,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _sendAdvert(BuildContext context, MeshCoreConnector connector) {
|
|
||||||
final l10n = context.l10n;
|
|
||||||
connector.sendSelfAdvert(flood: true);
|
|
||||||
ScaffoldMessenger.of(
|
|
||||||
context,
|
|
||||||
).showSnackBar(SnackBar(content: Text(l10n.settings_advertisementSent)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _syncTime(BuildContext context, MeshCoreConnector connector) {
|
void _syncTime(BuildContext context, MeshCoreConnector connector) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
connector.syncTime();
|
connector.syncTime();
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ class AppSettingsService extends ChangeNotifier {
|
|||||||
await updateSettings(_settings.copyWith(mapShowOtherNodes: value));
|
await updateSettings(_settings.copyWith(mapShowOtherNodes: value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setMapShowOverlaps(bool value) async {
|
||||||
|
await updateSettings(_settings.copyWith(mapShowOverlaps: value));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> setMapTimeFilterHours(double value) async {
|
Future<void> setMapTimeFilterHours(double value) async {
|
||||||
await updateSettings(_settings.copyWith(mapTimeFilterHours: value));
|
await updateSettings(_settings.copyWith(mapTimeFilterHours: value));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -565,6 +565,16 @@ class PathHistoryService extends ChangeNotifier {
|
|||||||
_floodStats.remove(oldest);
|
_floodStats.remove(oldest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clearAllHistories() {
|
||||||
|
_cache.clear();
|
||||||
|
_cacheAccessOrder.clear();
|
||||||
|
_autoRotationIndex.clear();
|
||||||
|
_floodStats.clear();
|
||||||
|
_storage.clearAllPathHistories();
|
||||||
|
_version = 0;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DeferredPathRecord {
|
class _DeferredPathRecord {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import flutter_blue_plus_darwin
|
|||||||
import flutter_local_notifications
|
import flutter_local_notifications
|
||||||
import mobile_scanner
|
import mobile_scanner
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
|
import path_provider_foundation
|
||||||
import share_plus
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
@@ -20,6 +21,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
|
|||||||
Reference in New Issue
Block a user