From 3664ae34cd32e9cf89b6474f277db33420c7132e Mon Sep 17 00:00:00 2001 From: ericz Date: Sun, 15 Mar 2026 11:42:46 +0100 Subject: [PATCH 1/3] reimplement location aware snr-indikator after alpha7 --- lib/utils/contact_search.dart | 55 +++++++++++++++++++ lib/widgets/snr_indicator.dart | 24 ++++++-- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/lib/utils/contact_search.dart b/lib/utils/contact_search.dart index 7a82c53..8aa75b0 100644 --- a/lib/utils/contact_search.dart +++ b/lib/utils/contact_search.dart @@ -1,3 +1,6 @@ +import 'package:latlong2/latlong.dart'; + +import '../connector/meshcore_protocol.dart'; import '../models/contact.dart'; export 'contact_filter_types.dart'; @@ -43,3 +46,55 @@ String? _extractHexPrefix(String query) { if (!RegExp(r'^[0-9a-f]+$').hasMatch(cleaned)) return null; return cleaned; } + +Contact? getRepeaterPrefixMatchNearLocation( + List contacts, + int pubkeyFirstByte, { + LatLng? searchPoint, + bool preferFavorites = false, +}) { + final candidates = contacts + .where( + (c) => + c.publicKey.isNotEmpty && + c.publicKey.first == pubkeyFirstByte && + (c.type == advTypeRepeater || c.type == advTypeRoom), + ) + .toList(); + + if (candidates.isEmpty) return null; + + candidates.sort((a, b) { + if (preferFavorites) { + final favA = a.isFavorite ? 1 : 0; + final favB = b.isFavorite ? 1 : 0; + final favCompare = favB.compareTo(favA); + if (favCompare != 0) return favCompare; + } + + final seenCompare = b.lastSeen.compareTo(a.lastSeen); + if (seenCompare != 0) return seenCompare; + + return a.publicKeyHex.compareTo(b.publicKeyHex); + }); + + if (searchPoint == null) { + return candidates.first; + } + + final distance = Distance(); + Contact best = candidates.first; + var bestDistance = double.infinity; + + for (final c in candidates) { + if (c.hasLocation) { + final d = distance(searchPoint, LatLng(c.latitude!, c.longitude!)); + if (d < bestDistance) { + bestDistance = d; + best = c; + } + } + } + + return best; +} diff --git a/lib/widgets/snr_indicator.dart b/lib/widgets/snr_indicator.dart index 30956e2..cf3c275 100644 --- a/lib/widgets/snr_indicator.dart +++ b/lib/widgets/snr_indicator.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:latlong2/latlong.dart'; + import '../connector/meshcore_connector.dart'; +import '../utils/contact_search.dart'; import '../l10n/l10n.dart'; import 'signal_ui.dart'; @@ -158,10 +161,23 @@ class _SNRIndicatorState extends State { widget.connector.currentSf, ); final allContacts = widget.connector.allContacts; - final name = allContacts - .where((c) => c.publicKey.first == repeater.pubkeyFirstByte) - .map((c) => c.name) - .firstOrNull; + + final selfLat = widget.connector.selfLatitude; + final selfLon = widget.connector.selfLongitude; + + LatLng? selfPoint; + if (selfLat != null && selfLon != null) { + selfPoint = LatLng(selfLat, selfLon); + } + + final contact = getRepeaterPrefixMatchNearLocation( + allContacts, + repeater.pubkeyFirstByte, + searchPoint: selfPoint, + preferFavorites: true, + ); + + final name = contact?.name; return Column( children: [ diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index d2ea57e..4084d9b 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,6 +9,7 @@ import flutter_blue_plus_darwin import flutter_local_notifications import mobile_scanner import package_info_plus +import path_provider_foundation import share_plus import shared_preferences_foundation import sqflite_darwin @@ -20,6 +21,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) From 0ef2194fb02a95cabf39516205329abb7c482285 Mon Sep 17 00:00:00 2001 From: ericszimmermann Date: Sun, 15 Mar 2026 12:10:47 +0100 Subject: [PATCH 2/3] codex suggested fix: explicit check if contact location is not null Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/utils/contact_search.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/contact_search.dart b/lib/utils/contact_search.dart index 8aa75b0..6a708e8 100644 --- a/lib/utils/contact_search.dart +++ b/lib/utils/contact_search.dart @@ -87,7 +87,7 @@ Contact? getRepeaterPrefixMatchNearLocation( var bestDistance = double.infinity; for (final c in candidates) { - if (c.hasLocation) { + if (c.hasLocation && c.latitude != null && c.longitude != null) { final d = distance(searchPoint, LatLng(c.latitude!, c.longitude!)); if (d < bestDistance) { bestDistance = d; From d1e45fc2ba82ef2274ae73acacb2865dc432b8a2 Mon Sep 17 00:00:00 2001 From: ericz Date: Sat, 28 Mar 2026 17:08:59 +0100 Subject: [PATCH 3/3] moved _getRepeaterPrefixMatchNearLocation since I don't need the function anywhere else anymore. --- lib/utils/contact_search.dart | 55 -------------------------- lib/widgets/snr_indicator.dart | 70 ++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 58 deletions(-) diff --git a/lib/utils/contact_search.dart b/lib/utils/contact_search.dart index 6a708e8..7a82c53 100644 --- a/lib/utils/contact_search.dart +++ b/lib/utils/contact_search.dart @@ -1,6 +1,3 @@ -import 'package:latlong2/latlong.dart'; - -import '../connector/meshcore_protocol.dart'; import '../models/contact.dart'; export 'contact_filter_types.dart'; @@ -46,55 +43,3 @@ String? _extractHexPrefix(String query) { if (!RegExp(r'^[0-9a-f]+$').hasMatch(cleaned)) return null; return cleaned; } - -Contact? getRepeaterPrefixMatchNearLocation( - List contacts, - int pubkeyFirstByte, { - LatLng? searchPoint, - bool preferFavorites = false, -}) { - final candidates = contacts - .where( - (c) => - c.publicKey.isNotEmpty && - c.publicKey.first == pubkeyFirstByte && - (c.type == advTypeRepeater || c.type == advTypeRoom), - ) - .toList(); - - if (candidates.isEmpty) return null; - - candidates.sort((a, b) { - if (preferFavorites) { - final favA = a.isFavorite ? 1 : 0; - final favB = b.isFavorite ? 1 : 0; - final favCompare = favB.compareTo(favA); - if (favCompare != 0) return favCompare; - } - - final seenCompare = b.lastSeen.compareTo(a.lastSeen); - if (seenCompare != 0) return seenCompare; - - return a.publicKeyHex.compareTo(b.publicKeyHex); - }); - - if (searchPoint == null) { - return candidates.first; - } - - final distance = Distance(); - Contact best = candidates.first; - var bestDistance = double.infinity; - - for (final c in candidates) { - if (c.hasLocation && c.latitude != null && c.longitude != null) { - final d = distance(searchPoint, LatLng(c.latitude!, c.longitude!)); - if (d < bestDistance) { - bestDistance = d; - best = c; - } - } - } - - return best; -} diff --git a/lib/widgets/snr_indicator.dart b/lib/widgets/snr_indicator.dart index cf3c275..99f2053 100644 --- a/lib/widgets/snr_indicator.dart +++ b/lib/widgets/snr_indicator.dart @@ -2,10 +2,63 @@ import 'package:flutter/material.dart'; import 'package:latlong2/latlong.dart'; import '../connector/meshcore_connector.dart'; -import '../utils/contact_search.dart'; +import '../connector/meshcore_protocol.dart'; import '../l10n/l10n.dart'; +import '../models/contact.dart'; import 'signal_ui.dart'; +Contact? _getRepeaterPrefixMatchNearLocation( + List contacts, + int pubkeyFirstByte, { + LatLng? searchPoint, + bool preferFavorites = false, +}) { + final candidates = contacts + .where( + (c) => + c.publicKey.isNotEmpty && + c.publicKey.first == pubkeyFirstByte && + (c.type == advTypeRepeater || c.type == advTypeRoom), + ) + .toList(); + + if (candidates.isEmpty) return null; + + candidates.sort((a, b) { + if (preferFavorites) { + final favA = a.isFavorite ? 1 : 0; + final favB = b.isFavorite ? 1 : 0; + final favCompare = favB.compareTo(favA); + if (favCompare != 0) return favCompare; + } + + final seenCompare = b.lastSeen.compareTo(a.lastSeen); + if (seenCompare != 0) return seenCompare; + + return a.publicKeyHex.compareTo(b.publicKeyHex); + }); + + if (searchPoint == null) { + return candidates.first; + } + + final distance = Distance(); + Contact best = candidates.first; + var bestDistance = double.infinity; + + for (final c in candidates) { + if (c.hasLocation && c.latitude != null && c.longitude != null) { + final d = distance(searchPoint, LatLng(c.latitude!, c.longitude!)); + if (d < bestDistance) { + bestDistance = d; + best = c; + } + } + } + + return best; +} + class SNRUi { final IconData icon; final Color color; @@ -67,6 +120,15 @@ class SNRIndicator extends StatefulWidget { } class _SNRIndicatorState extends State { + bool _isValidSelfLocation(double lat, double lon) { + const double epsilon = 1e-6; + return (lat.abs() > epsilon || lon.abs() > epsilon) && + lat >= -90.0 && + lat <= 90.0 && + lon >= -180.0 && + lon <= 180.0; + } + @override Widget build(BuildContext context) { final directRepeaters = widget.connector.directRepeaters; @@ -166,11 +228,13 @@ class _SNRIndicatorState extends State { final selfLon = widget.connector.selfLongitude; LatLng? selfPoint; - if (selfLat != null && selfLon != null) { + if (selfLat != null && + selfLon != null && + _isValidSelfLocation(selfLat, selfLon)) { selfPoint = LatLng(selfLat, selfLon); } - final contact = getRepeaterPrefixMatchNearLocation( + final contact = _getRepeaterPrefixMatchNearLocation( allContacts, repeater.pubkeyFirstByte, searchPoint: selfPoint,