Refactor contact filtering and improve localization strings; enhance path trace handling

This commit is contained in:
Winston Lowe 2026-03-24 17:45:54 -07:00
parent 411cd3f8d2
commit bde9a029c1
4 changed files with 59 additions and 35 deletions

View file

@ -323,8 +323,11 @@ class MeshCoreConnector extends ChangeNotifier {
List<Contact> get allContacts => List.unmodifiable([
..._contacts,
..._discoveredContacts.where((c) => !c.isActive),
..._discoveredContacts.where(
(c) => !c.isActive && c.publicKeyHex != selfPublicKeyHex,
),
]);
List<Contact> get discoveredContacts {
return List.unmodifiable(_discoveredContacts);
}

View file

@ -1240,9 +1240,7 @@ class _ContactsScreenState extends State<ContactsScreen>
if (isRepeater) ...[
ListTile(
leading: const Icon(Icons.radar, color: Colors.green),
title: contact.pathBytesForDisplay.isNotEmpty
? Text(context.l10n.contacts_pathTrace)
: Text(context.l10n.contacts_ping),
title: Text(context.l10n.contacts_ping),
onTap: () {
final hw = context
.read<MeshCoreConnector>()
@ -1251,11 +1249,8 @@ class _ContactsScreenState extends State<ContactsScreen>
context,
MaterialPageRoute(
builder: (context) => PathTraceMapScreen(
title: contact.pathBytesForDisplay.isNotEmpty
? context.l10n.contacts_repeaterPathTrace
: context.l10n.contacts_repeaterPing,
path: contact.pathBytesForDisplay,
flipPathAround: true,
title: context.l10n.contacts_repeaterPing,
path: Uint8List.fromList([contact.publicKey.first]),
targetContact: contact,
pathHashByteWidth: hw,
),
@ -1274,9 +1269,7 @@ class _ContactsScreenState extends State<ContactsScreen>
] else if (isRoom) ...[
ListTile(
leading: const Icon(Icons.radar, color: Colors.green),
title: contact.pathLength > 0
? Text(context.l10n.contacts_pathTrace)
: Text(context.l10n.contacts_ping),
title: Text(context.l10n.contacts_pathTrace),
onTap: () {
final hw = context
.read<MeshCoreConnector>()
@ -1288,7 +1281,9 @@ class _ContactsScreenState extends State<ContactsScreen>
title: contact.pathBytesForDisplay.isNotEmpty
? context.l10n.contacts_roomPathTrace
: context.l10n.contacts_roomPing,
path: contact.pathBytesForDisplay,
path: contact.pathBytesForDisplay.isNotEmpty
? contact.pathBytesForDisplay
: Uint8List.fromList([contact.publicKey.first]),
flipPathAround: contact.pathBytesForDisplay.isNotEmpty,
targetContact: contact,
pathHashByteWidth: hw,

View file

@ -64,6 +64,7 @@ class _MapScreenState extends State<MapScreen> {
bool _hasInitializedMap = false;
bool _removedMarkersLoaded = false;
final List<int> _pathTrace = [];
final List<Contact> _pathTraceContacts = [];
final List<LatLng> _points = [];
final List<Polyline> _polylines = [];
bool _legendExpanded = false;
@ -488,11 +489,10 @@ class _MapScreenState extends State<MapScreen> {
),
),
),
if (!_isBuildingPathTrace)
..._buildGuessedMarker(
guessedLocations,
showLabels: _showNodeLabels,
),
..._buildGuessedMarker(
guessedLocations,
showLabels: _showNodeLabels,
),
..._buildMarkers(
contactsWithLocation,
settings,
@ -788,17 +788,26 @@ class _MapScreenState extends State<MapScreen> {
final markers = <Marker>[];
for (final guess in guessed) {
if (guess.contact.type == advTypeChat && _isBuildingPathTrace) {
continue;
}
final color = _getNodeColor(guess.contact.type);
final marker = Marker(
point: guess.position,
width: 35,
height: 35,
child: GestureDetector(
onTap: () => _showNodeInfo(
context,
guess.contact,
guessedPosition: guess.position,
),
onLongPress: () => _isBuildingPathTrace
? _showNodeInfo(context, guess.contact)
: null,
onTap: () => _isBuildingPathTrace
? _addToPath(context, guess.contact, position: guess.position)
: _showNodeInfo(
context,
guess.contact,
guessedPosition: guess.position,
),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
@ -2121,12 +2130,18 @@ class _MapScreenState extends State<MapScreen> {
}
}
void _addToPath(BuildContext context, Contact contact) {
void _addToPath(BuildContext context, Contact contact, {LatLng? position}) {
setState(() {
_pathTrace.add(
contact.publicKey[0],
); // Add first 16 bytes of public key to path trace
_points.add(LatLng(contact.latitude!, contact.longitude!));
_pathTraceContacts.add(
contact.copyWith(
latitude: position?.latitude ?? contact.latitude,
longitude: position?.longitude ?? contact.longitude,
),
); // Add contact to path trace contacts
_points.add(position ?? LatLng(contact.latitude!, contact.longitude!));
});
}
@ -2134,6 +2149,7 @@ class _MapScreenState extends State<MapScreen> {
setState(() {
_isBuildingPathTrace = true;
_pathTrace.clear();
_pathTraceContacts.clear();
_points.clear();
_polylines.clear();
_points.add(position);
@ -2143,6 +2159,9 @@ class _MapScreenState extends State<MapScreen> {
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<MapScreen> {
title: l10n.contacts_pathTrace,
path: Uint8List.fromList(_pathTrace),
pathHashByteWidth: hashW,
pathContacts: _pathTraceContacts,
),
),
);

View file

@ -56,6 +56,7 @@ class PathTraceMapScreen extends StatefulWidget {
final bool reversePathAround;
final Contact? targetContact;
final int pathHashByteWidth;
final List<Contact>? pathContacts;
const PathTraceMapScreen({
super.key,
@ -66,6 +67,7 @@ class PathTraceMapScreen extends StatefulWidget {
this.reversePathAround = false,
this.targetContact,
this.pathHashByteWidth = pathHashSize,
this.pathContacts,
});
@override
@ -266,17 +268,21 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
.toList();
Map<int, Contact> pathContacts = {};
final contacts = connector.allContacts;
contacts.where((c) => c.type != advTypeChat).forEach((repeater) {
for (var repeaterData in pathData) {
if (listEquals(
repeater.publicKey.sublist(0, 1),
Uint8List.fromList([repeaterData]),
)) {
pathContacts[repeaterData] = repeater;
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.