Refactor ranking calculation for direct repeaters and update path handling in channel message screens

This commit is contained in:
Winston Lowe
2026-02-18 09:28:25 -08:00
parent 0a01ecde38
commit 17a9db0f0e
5 changed files with 41 additions and 17 deletions
+1 -2
View File
@@ -59,13 +59,12 @@ class DirectRepeater {
return -1; // Stale repeaters get lowest rank return -1; // Stale repeaters get lowest rank
} }
// Higher SNR gets higher rank and recency within maxAgeMinutes breaks ties. // Higher SNR gets higher rank and recency within maxAgeMinutes breaks ties.
final snrOffset = snr + 31.75;
final ageMs = final ageMs =
DateTime.now().millisecondsSinceEpoch - DateTime.now().millisecondsSinceEpoch -
lastUpdated.millisecondsSinceEpoch; lastUpdated.millisecondsSinceEpoch;
final maxAgeMs = maxAgeMinutes * 60 * 1000; final maxAgeMs = maxAgeMinutes * 60 * 1000;
final recencyScore = (maxAgeMs - ageMs).clamp(0, maxAgeMs); final recencyScore = (maxAgeMs - ageMs).clamp(0, maxAgeMs);
return (snrOffset * 1000).round() + recencyScore; return (snr * snr).round() + recencyScore;
} }
bool isStale() { bool isStale() {
+2 -1
View File
@@ -901,7 +901,8 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChannelMessagePathScreen(message: message), builder: (context) =>
ChannelMessagePathScreen(message: message, channelMessage: true),
), ),
); );
} }
+29 -9
View File
@@ -17,18 +17,27 @@ import '../models/contact.dart';
class ChannelMessagePathScreen extends StatelessWidget { class ChannelMessagePathScreen extends StatelessWidget {
final ChannelMessage message; final ChannelMessage message;
final bool channelMessage;
const ChannelMessagePathScreen({super.key, required this.message}); const ChannelMessagePathScreen({
super.key,
required this.message,
this.channelMessage = false,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<MeshCoreConnector>( return Consumer<MeshCoreConnector>(
builder: (context, connector, _) { builder: (context, connector, _) {
final l10n = context.l10n; final l10n = context.l10n;
final primaryPath = _selectPrimaryPath( final primaryPathTmp = _selectPrimaryPath(
message.pathBytes, message.pathBytes,
message.pathVariants, message.pathVariants,
); );
final primaryPath = !channelMessage && !message.isOutgoing
? Uint8List.fromList(primaryPathTmp.reversed.toList())
: primaryPathTmp;
final hops = _buildPathHops(primaryPath, connector.contacts, l10n); final hops = _buildPathHops(primaryPath, connector.contacts, l10n);
final hasHopDetails = primaryPath.isNotEmpty; final hasHopDetails = primaryPath.isNotEmpty;
final observedLabel = _formatObservedHops( final observedLabel = _formatObservedHops(
@@ -37,7 +46,6 @@ class ChannelMessagePathScreen extends StatelessWidget {
l10n, l10n,
); );
final extraPaths = _otherPaths(primaryPath, message.pathVariants); final extraPaths = _otherPaths(primaryPath, message.pathVariants);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(l10n.channelPath_title), title: Text(l10n.channelPath_title),
@@ -50,9 +58,9 @@ class ChannelMessagePathScreen extends StatelessWidget {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => PathTraceMapScreen( builder: (context) => PathTraceMapScreen(
title: context.l10n.contacts_repeaterPathTrace, title: context.l10n.contacts_repeaterPathTrace,
path: Uint8List.fromList(primaryPath), path: primaryPath,
flipPathRound: true, flipPathRound: true,
reversePathRound: true, reversePathRound: !message.isOutgoing,
), ),
), ),
), ),
@@ -62,7 +70,7 @@ class ChannelMessagePathScreen extends StatelessWidget {
tooltip: l10n.channelPath_viewMap, tooltip: l10n.channelPath_viewMap,
onPressed: hasHopDetails onPressed: hasHopDetails
? () { ? () {
_openPathMap(context); _openPathMap(context, channelMessage: channelMessage);
} }
: null, : null,
), ),
@@ -248,13 +256,18 @@ class ChannelMessagePathScreen extends StatelessWidget {
); );
} }
void _openPathMap(BuildContext context, {Uint8List? initialPath}) { void _openPathMap(
BuildContext context, {
Uint8List? initialPath,
bool channelMessage = false,
}) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChannelMessagePathMapScreen( builder: (context) => ChannelMessagePathMapScreen(
message: message, message: message,
initialPath: initialPath, initialPath: initialPath,
channelMessage: channelMessage,
), ),
), ),
); );
@@ -264,11 +277,13 @@ class ChannelMessagePathScreen extends StatelessWidget {
class ChannelMessagePathMapScreen extends StatefulWidget { class ChannelMessagePathMapScreen extends StatefulWidget {
final ChannelMessage message; final ChannelMessage message;
final Uint8List? initialPath; final Uint8List? initialPath;
final bool channelMessage;
const ChannelMessagePathMapScreen({ const ChannelMessagePathMapScreen({
super.key, super.key,
required this.message, required this.message,
this.initialPath, this.initialPath,
this.channelMessage = false,
}); });
@override @override
@@ -323,11 +338,16 @@ class _ChannelMessagePathMapScreenState
primaryPath, primaryPath,
widget.message.pathVariants, widget.message.pathVariants,
); );
final selectedPath = _resolveSelectedPath( final selectedPathTmp = _resolveSelectedPath(
_selectedPath, _selectedPath,
observedPaths, observedPaths,
primaryPath, primaryPath,
); );
final selectedPath = !widget.channelMessage && widget.message.isOutgoing
? Uint8List.fromList(selectedPathTmp.reversed.toList())
: selectedPathTmp;
final selectedIndex = _indexForPath(selectedPath, observedPaths); final selectedIndex = _indexForPath(selectedPath, observedPaths);
final hops = _buildPathHops( final hops = _buildPathHops(
selectedPath, selectedPath,
+3 -3
View File
@@ -105,7 +105,7 @@ class _MapScreenState extends State<MapScreen> {
double _zoomFromStdDev(double latStdDev, double lonStdDev) { double _zoomFromStdDev(double latStdDev, double lonStdDev) {
final maxSpread = max(latStdDev, lonStdDev); final maxSpread = max(latStdDev, lonStdDev);
if (maxSpread <= 0) return 13.0; if (maxSpread <= 0) return 13.0;
// Approzimate: each zoom level halves the visible area // Approximate: each zoom level halves the visible area
// ~0.01 degrees spread -> zoom 13, ~0.1 -> zoom 10, ~1.0 -> zoom 7 // ~0.01 degrees spread -> zoom 13, ~0.1 -> zoom 10, ~1.0 -> zoom 7
final zoom = 10.0 - log(maxSpread * 10 + 1) / ln10 * 3; final zoom = 10.0 - log(maxSpread * 10 + 1) / ln10 * 3;
return zoom.clamp(4.0, 15.0); return zoom.clamp(4.0, 15.0);
@@ -825,7 +825,7 @@ class _MapScreenState extends State<MapScreen> {
color: _getNodeColor(contact.type), color: _getNodeColor(contact.type),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded(child: Text(contact.name)), Expanded(child: SelectableText(contact.name)),
], ],
), ),
content: Column( content: Column(
@@ -996,7 +996,7 @@ class _MapScreenState extends State<MapScreen> {
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text(value, style: const TextStyle(fontSize: 14)), SelectableText(value, style: const TextStyle(fontSize: 14)),
], ],
), ),
); );
+6 -2
View File
@@ -46,6 +46,7 @@ class PathTraceData {
class PathTraceMapScreen extends StatefulWidget { class PathTraceMapScreen extends StatefulWidget {
final String title; final String title;
final Uint8List path; final Uint8List path;
final int? repeaterId;
final bool flipPathRound; final bool flipPathRound;
final bool reversePathRound; final bool reversePathRound;
@@ -53,6 +54,7 @@ class PathTraceMapScreen extends StatefulWidget {
super.key, super.key,
required this.title, required this.title,
required this.path, required this.path,
this.repeaterId,
this.flipPathRound = false, this.flipPathRound = false,
this.reversePathRound = false, this.reversePathRound = false,
}); });
@@ -97,7 +99,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
super.dispose(); super.dispose();
} }
Uint8List addReturnpath(Uint8List pathBytes) { Uint8List addReturnPath(Uint8List pathBytes) {
Uint8List? traceBytes; Uint8List? traceBytes;
final len = (pathBytes.length + pathBytes.length - 1); final len = (pathBytes.length + pathBytes.length - 1);
traceBytes = Uint8List(len); traceBytes = Uint8List(len);
@@ -125,11 +127,13 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
: widget.path; : widget.path;
if (widget.flipPathRound) { if (widget.flipPathRound) {
path = addReturnpath(pathTmp); path = addReturnPath(pathTmp);
} else { } else {
path = pathTmp; path = pathTmp;
} }
print('Initiating path trace with path: ${_formatPathPrefixes(path)}');
final connector = Provider.of<MeshCoreConnector>(context, listen: false); final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final frame = buildTraceReq( final frame = buildTraceReq(
DateTime.now().millisecondsSinceEpoch ~/ 1000, DateTime.now().millisecondsSinceEpoch ~/ 1000,