Add show overlaps in public keys of repeaters functionality and localization support

This commit is contained in:
Winston Lowe
2026-02-22 16:00:51 -08:00
parent cac0cc15eb
commit c81c3efe7c
37 changed files with 242 additions and 77 deletions
+4
View File
@@ -5196,6 +5196,10 @@ class MeshCoreConnector extends ChangeNotifier {
markChannelRead(channelIndex); markChannelRead(channelIndex);
notifyListeners(); notifyListeners();
} }
void deleteAllPaths() {
_pathHistoryService?.clearAllHistories();
}
} }
const int _phRouteMask = 0x03; const int _phRouteMask = 0x03;
+2 -1
View File
@@ -1941,5 +1941,6 @@
"appSettings_maxMessageRetriesSubtitle": "Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.", "appSettings_maxMessageRetriesSubtitle": "Брой опити за повторно изпращане, преди съобщението да бъде маркирано като неуспешно.",
"path_routeWeight": "{weight}/{max}", "path_routeWeight": "{weight}/{max}",
"settings_multiAck": "Мулти-потвърди: {value}", "settings_multiAck": "Мулти-потвърди: {value}",
"settings_telemetryModeUpdated": "Режим на телеметрията е обновен" "settings_telemetryModeUpdated": "Режим на телеметрията е обновен",
"map_showOverlaps": "Покриване на ключа на повтаряча"
} }
+2 -1
View File
@@ -1969,5 +1969,6 @@
"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"
} }
+1
View File
@@ -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",
+1 -10
View File
@@ -1918,7 +1918,6 @@
"tcpConnectionFailed": "Error en la conexión TCP: {error}", "tcpConnectionFailed": "Error en la conexión TCP: {error}",
"map_showDiscoveryContacts": "Mostrar Contactos de Descubrimiento", "map_showDiscoveryContacts": "Mostrar Contactos de Descubrimiento",
"map_setAsMyLocation": "Establecer mi ubicación", "map_setAsMyLocation": "Establecer mi ubicación",
<<<<<<< HEAD
"@path_routeWeight": { "@path_routeWeight": {
"placeholders": { "placeholders": {
"weight": { "weight": {
@@ -1929,8 +1928,6 @@
} }
} }
}, },
=======
>>>>>>> c9e3ceb (feat: Enhance privacy settings and telemetry (#308))
"settings_privacySubtitle": "Controlar qué información se comparte.", "settings_privacySubtitle": "Controlar qué información se comparte.",
"settings_allowByContact": "Permitir por banderas de contacto", "settings_allowByContact": "Permitir por banderas de contacto",
"settings_denyAll": "Denegar todo", "settings_denyAll": "Denegar todo",
@@ -1960,7 +1957,6 @@
} }
} }
}, },
<<<<<<< HEAD
"appSettings_initialRouteWeight": "Peso inicial de la ruta", "appSettings_initialRouteWeight": "Peso inicial de la ruta",
"appSettings_maxRouteWeight": "Peso máximo permitido para la ruta", "appSettings_maxRouteWeight": "Peso máximo permitido para la ruta",
"appSettings_initialRouteWeightSubtitle": "Peso inicial para rutas recién descubiertas", "appSettings_initialRouteWeightSubtitle": "Peso inicial para rutas recién descubiertas",
@@ -1974,9 +1970,4 @@
"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}"
} }
=======
"settings_telemetryModeUpdated": "Modo de telemetría actualizado",
"settings_multiAck": "Multi-ACKs: {value}"
}
>>>>>>> c9e3ceb (feat: Enhance privacy settings and telemetry (#308))
+2 -1
View File
@@ -1941,5 +1941,6 @@
"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"
} }
+2 -1
View File
@@ -1941,5 +1941,6 @@
"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"
} }
+6
View File
@@ -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:
+3
View File
@@ -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 => 'Префикс на ключа';
+3
View File
@@ -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';
+3
View File
@@ -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';
+3
View File
@@ -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';
+3
View File
@@ -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é';
+3
View File
@@ -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';
+3
View File
@@ -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';
+3
View File
@@ -1688,6 +1688,9 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get map_otherNodes => 'Inne węzły'; String get map_otherNodes => 'Inne węzły';
@override
String get map_showOverlaps => 'Nakładające się klucze powtarzalne';
@override @override
String get map_keyPrefix => 'Prefiks klucza'; String get map_keyPrefix => 'Prefiks klucza';
+3
View File
@@ -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';
+3
View File
@@ -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 => 'Префикс ключа';
+3
View File
@@ -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';
+3
View File
@@ -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';
+3
View File
@@ -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';
+3
View File
@@ -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 => 'Префікс ключа';
+3
View File
@@ -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 => '关键字前缀';
+2 -1
View File
@@ -1941,5 +1941,6 @@
"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"
} }
+2 -1
View File
@@ -1941,5 +1941,6 @@
"appSettings_maxMessageRetriesSubtitle": "Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej", "appSettings_maxMessageRetriesSubtitle": "Liczba prób ponownego wysłania wiadomości przed oznaczaniem jej jako nieudanej",
"path_routeWeight": "{weight}/{max}", "path_routeWeight": "{weight}/{max}",
"settings_telemetryModeUpdated": "Tryb telemetryczny zaktualizowany", "settings_telemetryModeUpdated": "Tryb telemetryczny zaktualizowany",
"settings_multiAck": "Wiele potwierdzeń: {value}" "settings_multiAck": "Wiele potwierdzeń: {value}",
"map_showOverlaps": "Nakładające się klucze powtarzalne"
} }
+2 -1
View File
@@ -1941,5 +1941,6 @@
"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"
} }
+2 -1
View File
@@ -1181,5 +1181,6 @@
"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": "Перекрытия ключа повтора"
} }
+2 -1
View File
@@ -1941,5 +1941,6 @@
"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"
} }
+2 -1
View File
@@ -1941,5 +1941,6 @@
"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"
} }
+2 -1
View File
@@ -1941,5 +1941,6 @@
"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"
} }
+2 -1
View File
@@ -1941,5 +1941,6 @@
"appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале", "appSettings_maxMessageRetriesSubtitle": "Кількість спроб повторного відправлення повідомлення перед тим, як позначити його як невдале",
"path_routeWeight": "{weight}/{max}", "path_routeWeight": "{weight}/{max}",
"settings_telemetryModeUpdated": "Режим телеметрії оновлено", "settings_telemetryModeUpdated": "Режим телеметрії оновлено",
"settings_multiAck": "Багатократне підтвердження: {value}" "settings_multiAck": "Багатократне підтвердження: {value}",
"map_showOverlaps": "Перекриття ключа повторювача"
} }
+2 -1
View File
@@ -1946,5 +1946,6 @@
"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": "重复键重叠"
} }
+6
View File
@@ -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,
+132 -50
View File
@@ -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)
@@ -580,6 +583,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 +599,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 +650,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 +830,67 @@ class _MapScreenState extends State<MapScreen> {
return markers; return markers;
} }
List<Contact> _filterContactsBySettings(
List<Contact> contacts,
dynamic settings,
) {
List<Contact> filtered = [];
bool addContact = false;
for (final contact in contacts) {
addContact = false;
if (!contact.hasLocation) 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 +906,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 +935,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,
), ),
); );
} }
@@ -959,20 +1017,12 @@ class _MapScreenState extends State<MapScreen> {
int markerCount, int markerCount,
int guessedCount, int guessedCount,
) { ) {
int nodeCount = 0; final filteredContacts = _filterContactsBySettings(
for (final contact in contactsWithLocation) { contactsWithLocation,
// Apply node type filters settings,
if (contact.type == advTypeRepeater && !settings.mapShowRepeaters) { );
continue;
} final nodeCount = filteredContacts.length;
if (contact.type == advTypeChat && !settings.mapShowChatNodes) continue;
if (contact.type != advTypeChat &&
contact.type != advTypeRepeater &&
!settings.mapShowOtherNodes) {
continue;
}
nodeCount++;
}
return Positioned( return Positioned(
top: 16, top: 16,
@@ -1846,6 +1896,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 +2063,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 +2115,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 +2137,37 @@ class _MapScreenState extends State<MapScreen> {
_isBuildingPathTrace = false; _isBuildingPathTrace = false;
}); });
}, },
child: Text(l10n.map_runTrace), tooltip: "Path Trace",
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: "Build Return Path",
icon: const Icon(Icons.replay),
),
if (_pathTrace.isNotEmpty)
IconButton(
onPressed: _removePath, onPressed: _removePath,
child: Text(l10n.map_removeLast), tooltip: "Remove Last Point",
icon: const Icon(Icons.delete),
), ),
if (_pathTrace.isEmpty) if (_pathTrace.isEmpty)
ElevatedButton( IconButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
_isBuildingPathTrace = false; _isBuildingPathTrace = false;
@@ -2097,7 +2179,7 @@ class _MapScreenState extends State<MapScreen> {
SnackBar(content: Text(l10n.map_pathTraceCancelled)), SnackBar(content: Text(l10n.map_pathTraceCancelled)),
); );
}, },
child: Text(l10n.common_cancel), icon: const Icon(Icons.close),
), ),
], ],
), ),
+7 -4
View File
@@ -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(
+4
View File
@@ -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));
} }
+10
View File
@@ -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 {