From bde9a029c153fcc72c6fc1634f5ee09a5c823695 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Tue, 24 Mar 2026 17:45:54 -0700 Subject: [PATCH] Refactor contact filtering and improve localization strings; enhance path trace handling --- lib/connector/meshcore_connector.dart | 5 ++- lib/screens/contacts_screen.dart | 19 +++++------- lib/screens/map_screen.dart | 44 +++++++++++++++++++-------- lib/screens/path_trace_map.dart | 26 ++++++++++------ 4 files changed, 59 insertions(+), 35 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index b99ecf7..11e2d4d 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -323,8 +323,11 @@ class MeshCoreConnector extends ChangeNotifier { List get allContacts => List.unmodifiable([ ..._contacts, - ..._discoveredContacts.where((c) => !c.isActive), + ..._discoveredContacts.where( + (c) => !c.isActive && c.publicKeyHex != selfPublicKeyHex, + ), ]); + List get discoveredContacts { return List.unmodifiable(_discoveredContacts); } diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index d5b01f2..62a380b 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -1240,9 +1240,7 @@ class _ContactsScreenState extends State if (isRepeater) ...[ ListTile( leading: const Icon(Icons.radar, color: Colors.green), - title: contact.pathBytesForDisplay.isNotEmpty - ? Text(context.l10n.contacts_pathTrace) - : Text(context.l10n.contacts_ping), + title: Text(context.l10n.contacts_ping), onTap: () { final hw = context .read() @@ -1251,11 +1249,8 @@ class _ContactsScreenState extends State context, MaterialPageRoute( builder: (context) => PathTraceMapScreen( - title: contact.pathBytesForDisplay.isNotEmpty - ? context.l10n.contacts_repeaterPathTrace - : context.l10n.contacts_repeaterPing, - path: contact.pathBytesForDisplay, - flipPathAround: true, + title: context.l10n.contacts_repeaterPing, + path: Uint8List.fromList([contact.publicKey.first]), targetContact: contact, pathHashByteWidth: hw, ), @@ -1274,9 +1269,7 @@ class _ContactsScreenState extends State ] else if (isRoom) ...[ ListTile( leading: const Icon(Icons.radar, color: Colors.green), - title: contact.pathLength > 0 - ? Text(context.l10n.contacts_pathTrace) - : Text(context.l10n.contacts_ping), + title: Text(context.l10n.contacts_pathTrace), onTap: () { final hw = context .read() @@ -1288,7 +1281,9 @@ class _ContactsScreenState extends State title: contact.pathBytesForDisplay.isNotEmpty ? context.l10n.contacts_roomPathTrace : context.l10n.contacts_roomPing, - path: contact.pathBytesForDisplay, + path: contact.pathBytesForDisplay.isNotEmpty + ? contact.pathBytesForDisplay + : Uint8List.fromList([contact.publicKey.first]), flipPathAround: contact.pathBytesForDisplay.isNotEmpty, targetContact: contact, pathHashByteWidth: hw, diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 9616d47..700c382 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -64,6 +64,7 @@ class _MapScreenState extends State { bool _hasInitializedMap = false; bool _removedMarkersLoaded = false; final List _pathTrace = []; + final List _pathTraceContacts = []; final List _points = []; final List _polylines = []; bool _legendExpanded = false; @@ -488,11 +489,10 @@ class _MapScreenState extends State { ), ), ), - if (!_isBuildingPathTrace) - ..._buildGuessedMarker( - guessedLocations, - showLabels: _showNodeLabels, - ), + ..._buildGuessedMarker( + guessedLocations, + showLabels: _showNodeLabels, + ), ..._buildMarkers( contactsWithLocation, settings, @@ -788,17 +788,26 @@ class _MapScreenState extends State { final markers = []; for (final guess in guessed) { + if (guess.contact.type == advTypeChat && _isBuildingPathTrace) { + continue; + } + final color = _getNodeColor(guess.contact.type); final marker = Marker( point: guess.position, width: 35, height: 35, child: GestureDetector( - onTap: () => _showNodeInfo( - context, - guess.contact, - guessedPosition: guess.position, - ), + onLongPress: () => _isBuildingPathTrace + ? _showNodeInfo(context, guess.contact) + : null, + onTap: () => _isBuildingPathTrace + ? _addToPath(context, guess.contact, position: guess.position) + : _showNodeInfo( + context, + guess.contact, + guessedPosition: guess.position, + ), child: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( @@ -2121,12 +2130,18 @@ class _MapScreenState extends State { } } - void _addToPath(BuildContext context, Contact contact) { + void _addToPath(BuildContext context, Contact contact, {LatLng? position}) { setState(() { _pathTrace.add( contact.publicKey[0], ); // Add first 16 bytes of public key to path trace - _points.add(LatLng(contact.latitude!, contact.longitude!)); + _pathTraceContacts.add( + contact.copyWith( + latitude: position?.latitude ?? contact.latitude, + longitude: position?.longitude ?? contact.longitude, + ), + ); // Add contact to path trace contacts + _points.add(position ?? LatLng(contact.latitude!, contact.longitude!)); }); } @@ -2134,6 +2149,7 @@ class _MapScreenState extends State { setState(() { _isBuildingPathTrace = true; _pathTrace.clear(); + _pathTraceContacts.clear(); _points.clear(); _polylines.clear(); _points.add(position); @@ -2143,6 +2159,9 @@ class _MapScreenState extends State { void _removePath() { setState(() { _pathTrace.removeLast(); // Remove last node from path trace + _pathTraceContacts.remove( + _pathTrace.last, + ); // Remove last contact from path trace _points.removeLast(); // Remove last point from points list _polylines.clear(); // Clear polylines }); @@ -2201,6 +2220,7 @@ class _MapScreenState extends State { title: l10n.contacts_pathTrace, path: Uint8List.fromList(_pathTrace), pathHashByteWidth: hashW, + pathContacts: _pathTraceContacts, ), ), ); diff --git a/lib/screens/path_trace_map.dart b/lib/screens/path_trace_map.dart index 5b02931..8d6b0e7 100644 --- a/lib/screens/path_trace_map.dart +++ b/lib/screens/path_trace_map.dart @@ -56,6 +56,7 @@ class PathTraceMapScreen extends StatefulWidget { final bool reversePathAround; final Contact? targetContact; final int pathHashByteWidth; + final List? pathContacts; const PathTraceMapScreen({ super.key, @@ -66,6 +67,7 @@ class PathTraceMapScreen extends StatefulWidget { this.reversePathAround = false, this.targetContact, this.pathHashByteWidth = pathHashSize, + this.pathContacts, }); @override @@ -266,17 +268,21 @@ class _PathTraceMapScreenState extends State { .toList(); Map pathContacts = {}; - final contacts = connector.allContacts; - contacts.where((c) => c.type != advTypeChat).forEach((repeater) { - for (var repeaterData in pathData) { - if (listEquals( - repeater.publicKey.sublist(0, 1), - Uint8List.fromList([repeaterData]), - )) { - pathContacts[repeaterData] = repeater; + if (widget.pathContacts != null) { + pathContacts = {for (var c in widget.pathContacts!) c.publicKey[0]: c}; + } else { + final contacts = connector.allContacts; + contacts.where((c) => c.type != advTypeChat).forEach((repeater) { + for (var repeaterData in pathData) { + if (listEquals( + repeater.publicKey.sublist(0, 1), + Uint8List.fromList([repeaterData]), + )) { + pathContacts[repeaterData] = repeater; + } } - } - }); + }); + } // For hops with no GPS contact, infer position from other contacts // with known GPS that share the same last-hop byte.