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
}
// Higher SNR gets higher rank and recency within maxAgeMinutes breaks ties.
final snrOffset = snr + 31.75;
final ageMs =
DateTime.now().millisecondsSinceEpoch -
lastUpdated.millisecondsSinceEpoch;
final maxAgeMs = maxAgeMinutes * 60 * 1000;
final recencyScore = (maxAgeMs - ageMs).clamp(0, maxAgeMs);
return (snrOffset * 1000).round() + recencyScore;
return (snr * snr).round() + recencyScore;
}
bool isStale() {
+2 -1
View File
@@ -901,7 +901,8 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
Navigator.push(
context,
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 {
final ChannelMessage message;
const ChannelMessagePathScreen({super.key, required this.message});
final bool channelMessage;
const ChannelMessagePathScreen({
super.key,
required this.message,
this.channelMessage = false,
});
@override
Widget build(BuildContext context) {
return Consumer<MeshCoreConnector>(
builder: (context, connector, _) {
final l10n = context.l10n;
final primaryPath = _selectPrimaryPath(
final primaryPathTmp = _selectPrimaryPath(
message.pathBytes,
message.pathVariants,
);
final primaryPath = !channelMessage && !message.isOutgoing
? Uint8List.fromList(primaryPathTmp.reversed.toList())
: primaryPathTmp;
final hops = _buildPathHops(primaryPath, connector.contacts, l10n);
final hasHopDetails = primaryPath.isNotEmpty;
final observedLabel = _formatObservedHops(
@@ -37,7 +46,6 @@ class ChannelMessagePathScreen extends StatelessWidget {
l10n,
);
final extraPaths = _otherPaths(primaryPath, message.pathVariants);
return Scaffold(
appBar: AppBar(
title: Text(l10n.channelPath_title),
@@ -50,9 +58,9 @@ class ChannelMessagePathScreen extends StatelessWidget {
MaterialPageRoute(
builder: (context) => PathTraceMapScreen(
title: context.l10n.contacts_repeaterPathTrace,
path: Uint8List.fromList(primaryPath),
path: primaryPath,
flipPathRound: true,
reversePathRound: true,
reversePathRound: !message.isOutgoing,
),
),
),
@@ -62,7 +70,7 @@ class ChannelMessagePathScreen extends StatelessWidget {
tooltip: l10n.channelPath_viewMap,
onPressed: hasHopDetails
? () {
_openPathMap(context);
_openPathMap(context, channelMessage: channelMessage);
}
: 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(
context,
MaterialPageRoute(
builder: (context) => ChannelMessagePathMapScreen(
message: message,
initialPath: initialPath,
channelMessage: channelMessage,
),
),
);
@@ -264,11 +277,13 @@ class ChannelMessagePathScreen extends StatelessWidget {
class ChannelMessagePathMapScreen extends StatefulWidget {
final ChannelMessage message;
final Uint8List? initialPath;
final bool channelMessage;
const ChannelMessagePathMapScreen({
super.key,
required this.message,
this.initialPath,
this.channelMessage = false,
});
@override
@@ -323,11 +338,16 @@ class _ChannelMessagePathMapScreenState
primaryPath,
widget.message.pathVariants,
);
final selectedPath = _resolveSelectedPath(
final selectedPathTmp = _resolveSelectedPath(
_selectedPath,
observedPaths,
primaryPath,
);
final selectedPath = !widget.channelMessage && widget.message.isOutgoing
? Uint8List.fromList(selectedPathTmp.reversed.toList())
: selectedPathTmp;
final selectedIndex = _indexForPath(selectedPath, observedPaths);
final hops = _buildPathHops(
selectedPath,
+3 -3
View File
@@ -105,7 +105,7 @@ class _MapScreenState extends State<MapScreen> {
double _zoomFromStdDev(double latStdDev, double lonStdDev) {
final maxSpread = max(latStdDev, lonStdDev);
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
final zoom = 10.0 - log(maxSpread * 10 + 1) / ln10 * 3;
return zoom.clamp(4.0, 15.0);
@@ -825,7 +825,7 @@ class _MapScreenState extends State<MapScreen> {
color: _getNodeColor(contact.type),
),
const SizedBox(width: 8),
Expanded(child: Text(contact.name)),
Expanded(child: SelectableText(contact.name)),
],
),
content: Column(
@@ -996,7 +996,7 @@ class _MapScreenState extends State<MapScreen> {
),
),
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 {
final String title;
final Uint8List path;
final int? repeaterId;
final bool flipPathRound;
final bool reversePathRound;
@@ -53,6 +54,7 @@ class PathTraceMapScreen extends StatefulWidget {
super.key,
required this.title,
required this.path,
this.repeaterId,
this.flipPathRound = false,
this.reversePathRound = false,
});
@@ -97,7 +99,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
super.dispose();
}
Uint8List addReturnpath(Uint8List pathBytes) {
Uint8List addReturnPath(Uint8List pathBytes) {
Uint8List? traceBytes;
final len = (pathBytes.length + pathBytes.length - 1);
traceBytes = Uint8List(len);
@@ -125,11 +127,13 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
: widget.path;
if (widget.flipPathRound) {
path = addReturnpath(pathTmp);
path = addReturnPath(pathTmp);
} else {
path = pathTmp;
}
print('Initiating path trace with path: ${_formatPathPrefixes(path)}');
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final frame = buildTraceReq(
DateTime.now().millisecondsSinceEpoch ~/ 1000,