diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 4af872c..b096005 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -54,6 +54,19 @@ class DirectRepeater { lastUpdated = DateTime.now(); } + int get ranking { + if (isStale()) { + return -1; // Stale repeaters get lowest rank + } + // Higher SNR gets higher rank and recency within maxAgeMinutes breaks ties. + final ageMs = + DateTime.now().millisecondsSinceEpoch - + lastUpdated.millisecondsSinceEpoch; + final maxAgeMs = maxAgeMinutes * 60 * 1000; + final recencyScore = (maxAgeMs - ageMs).clamp(0, maxAgeMs); + return (snr * 1000).round() + recencyScore; + } + bool isStale() { return DateTime.now().difference(lastUpdated) > const Duration(minutes: maxAgeMinutes); @@ -3466,8 +3479,7 @@ class MeshCoreConnector extends ChangeNotifier { return; } - if (publicKey == _selfPublicKey) { - appLogger.info('Ignoring advert from self', tag: 'Connector'); + if (listEquals(publicKey, _selfPublicKey)) { return; } @@ -3480,7 +3492,9 @@ class MeshCoreConnector extends ChangeNotifier { name: name, type: type, pathLength: path.length, - path: path, + path: Uint8List.fromList( + path.reversed.toList(), + ), // Store path in reverse for easier use in outgoing messages latitude: latitude, longitude: longitude, lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000), @@ -3510,7 +3524,7 @@ class MeshCoreConnector extends ChangeNotifier { latitude: hasLocation ? latitude : existing.latitude, longitude: hasLocation ? longitude : existing.longitude, name: hasName ? name : existing.name, - path: path, + path: Uint8List.fromList(path.reversed.toList()), pathLength: path.length, lastMessageAt: mergedLastMessageAt, lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000), @@ -3518,6 +3532,12 @@ class MeshCoreConnector extends ChangeNotifier { pathOverrideBytes: existing.pathOverrideBytes, ); + // Add path to history if we have a valid path + if (_pathHistoryService != null && + _contacts[existingIndex].pathLength >= 0) { + _pathHistoryService!.handlePathUpdated(_contacts[existingIndex]); + } + _updateDirectRepeater(_contacts[existingIndex], snr, path); appLogger.info( diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index f00f242..327447b 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -437,6 +437,20 @@ class _ChatScreenState extends State { builder: (context) => Consumer( builder: (context, pathService, _) { final paths = pathService.getRecentPaths(widget.contact.publicKeyHex); + + final repeatersList = List.of(connector.directRepeaters) + ..sort((a, b) => b.ranking.compareTo(a.ranking)); + + final directRepeater = repeatersList.isEmpty + ? null + : repeatersList.first; + final secondDirectRepeater = repeatersList.length < 2 + ? null + : repeatersList.elementAt(1); + final thirdDirectRepeater = repeatersList.length < 3 + ? null + : repeatersList.elementAt(2); + return AlertDialog( title: Row( children: [ @@ -478,15 +492,38 @@ class _ChatScreenState extends State { ], const SizedBox(height: 8), ...paths.map((path) { + final isDirectRepeater = + directRepeater != null && + path.pathBytes.isNotEmpty && + directRepeater.pubkeyFirstByte == + path.pathBytes.first; + final isSecoundDirectRepeater = + secondDirectRepeater != null && + path.pathBytes.isNotEmpty && + secondDirectRepeater.pubkeyFirstByte == + path.pathBytes.first; + final isThirdDirectRepeater = + thirdDirectRepeater != null && + path.pathBytes.isNotEmpty && + thirdDirectRepeater.pubkeyFirstByte == + path.pathBytes.first; + Color color = Colors.grey; + if (isDirectRepeater) { + color = Colors.green; + } else if (isSecoundDirectRepeater) { + color = Colors.yellow; + } else if (isThirdDirectRepeater) { + color = Colors.red; + } else if (path.wasFloodDiscovery) { + color = Colors.blue; + } return Card( margin: const EdgeInsets.symmetric(vertical: 4), child: ListTile( dense: true, leading: CircleAvatar( radius: 16, - backgroundColor: path.wasFloodDiscovery - ? Colors.blue - : Colors.green, + backgroundColor: color, child: Text( '${path.hopCount}', style: const TextStyle(fontSize: 12), diff --git a/lib/widgets/path_management_dialog.dart b/lib/widgets/path_management_dialog.dart index f2201b9..3a56e5d 100644 --- a/lib/widgets/path_management_dialog.dart +++ b/lib/widgets/path_management_dialog.dart @@ -134,23 +134,18 @@ class _PathManagementDialog extends StatelessWidget { final currentContact = _resolveContact(connector); final paths = pathService.getRecentPaths(currentContact.publicKeyHex); - final RepeatersList = List.of(connector.directRepeaters) - ..sort((a, b) => b.lastUpdated.compareTo(a.lastUpdated)); + final repeatersList = List.of(connector.directRepeaters) + ..sort((a, b) => b.ranking.compareTo(a.ranking)); - final topSNRRepeaters = List.of(RepeatersList) - ..sort((a, b) => b.snr.compareTo(a.snr)); - - final topThreeRepeaters = topSNRRepeaters.take(3).toList(); - - final directRepeater = topThreeRepeaters.isEmpty + final directRepeater = repeatersList.isEmpty ? null - : topThreeRepeaters.first; - final secondDirectRepeater = topThreeRepeaters.length < 2 + : repeatersList.first; + final secondDirectRepeater = repeatersList.length < 2 ? null - : topThreeRepeaters.elementAt(1); - final thirdDirectRepeater = topThreeRepeaters.length < 3 + : repeatersList.elementAt(1); + final thirdDirectRepeater = repeatersList.length < 3 ? null - : topThreeRepeaters.elementAt(2); + : repeatersList.elementAt(2); return AlertDialog( title: Text(l10n.chat_pathManagement), @@ -206,6 +201,7 @@ class _PathManagementDialog extends StatelessWidget { path.pathBytes.isNotEmpty && thirdDirectRepeater.pubkeyFirstByte == path.pathBytes.first; + Color color = Colors.grey; if (isDirectRepeater) { color = Colors.green; diff --git a/lib/widgets/snr_indicator.dart b/lib/widgets/snr_indicator.dart index 8a07f02..f3f89be 100644 --- a/lib/widgets/snr_indicator.dart +++ b/lib/widgets/snr_indicator.dart @@ -76,7 +76,7 @@ class _SNRIndicatorState extends State { Widget build(BuildContext context) { final directRepeaters = widget.connector.directRepeaters; final directBestRepeaters = List.of(directRepeaters) - ..sort((a, b) => (b.snr).compareTo(a.snr)); + ..sort((a, b) => (b.ranking).compareTo(a.ranking)); final directRepeater = directBestRepeaters.isEmpty ? null : directBestRepeaters.first;