Dev discovery (#291)

* Refactor contact handling: replace DiscoveryContact with Contact, update related methods and settings

* Enhance contact handling: include latitude, longitude, and last modified timestamp in contact updates; refactor path handling to accommodate discovered contacts across multiple screens

* Enhance SNRIndicator: include discovered contacts in name resolution for repeaters

* Refactor path handling: replace addReturnPath with buildPath to improve path construction logic and handle target contact types

* Update lib/screens/map_screen.dart

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add localization for "Show Discovery Contacts" in multiple languages and refactor location plausibility check in map screen

* Enhance contact management: update discovered contacts' active status and improve contact handling with flags and raw packet data

* Refactor ChannelsScreen: pass ChannelMessageStore to buildExpandedContent and ensure messages are cleared after channel creation

* Update MapScreen: adjust label zoom threshold and refactor guessed marker building to include labels

* Refactor ChannelsScreen: change channelMessageStore to a private getter and update its usage in buildExpandedContent calls

* Enhance location plausibility check: add latitude and longitude bounds to ensure valid coordinates

* Update lib/connector/meshcore_connector.dart

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor MeshCoreConnector and related stores: update discovered contacts handling, migrate legacy keys, and set public key in community store

* Refactor MeshCoreConnector and ChannelsScreen: update discovered contacts handling and set public key in community store; enhance location plausibility check in MapScreen

* Update CMD_ADD_UPDATE_CONTACT frame format to include optional latitude and longitude fields

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Winston Lowe
2026-03-12 23:08:46 -07:00
committed by GitHub
parent c81791cf1e
commit 81758adc61
56 changed files with 476 additions and 241 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ class ChannelMessageStore {
final key = '$keyFor$channelIndex';
final oldKey = '$_keyPrefix$channelIndex';
String? jsonString = prefs.getString(oldKey);
String? jsonString = prefs.getString(key);
if (jsonString == null || jsonString.isEmpty) {
// Attempt migration from legacy unscoped key on first load
final legacyJsonString = prefs.getString(oldKey);
+1 -1
View File
@@ -26,7 +26,7 @@ class ChannelOrderStore {
return [];
}
final prefs = PrefsManager.instance;
String? jsonString = prefs.getString(_keyPrefix);
String? jsonString = prefs.getString(keyFor);
if (jsonString == null || jsonString.isEmpty) {
// Attempt migration from legacy unscoped key on first load
final legacyJsonString = prefs.getString(_keyPrefix);
+1 -1
View File
@@ -32,7 +32,7 @@ class ChannelSettingsStore {
await prefs.setBool(key, enabled);
}
}
return prefs.getBool(key) ?? false;
return enabled ?? false;
}
Future<void> saveSmazEnabled(int channelIndex, bool enabled) async {
+1 -1
View File
@@ -19,7 +19,7 @@ class ChannelStore {
return [];
}
final prefs = PrefsManager.instance;
String? jsonString = prefs.getString(_keyPrefix);
String? jsonString = prefs.getString(keyFor);
if (jsonString == null || jsonString.isEmpty) {
// Attempt migration from legacy unscoped key on first load
final legacyJsonString = prefs.getString(_keyPrefix);
+1 -1
View File
@@ -25,7 +25,7 @@ class CommunityStore {
return [];
}
final prefs = PrefsManager.instance;
String? jsonString = prefs.getString(_keyPrefix);
String? jsonString = prefs.getString(keyFor);
if (jsonString == null || jsonString.isEmpty) {
// Attempt migration from legacy unscoped key on first load
final legacyJsonString = prefs.getString(_keyPrefix);
+30 -8
View File
@@ -1,13 +1,13 @@
import 'dart:convert';
import 'dart:typed_data';
import '../models/discovery_contact.dart';
import '../models/contact.dart';
import 'prefs_manager.dart';
class ContactDiscoveryStore {
static const String _keyPrefix = 'discovered_contacts';
Future<List<DiscoveryContact>> loadContacts() async {
Future<List<Contact>> loadContacts() async {
final prefs = PrefsManager.instance;
final jsonStr = prefs.getString(_keyPrefix);
if (jsonStr == null) return [];
@@ -22,40 +22,62 @@ class ContactDiscoveryStore {
}
}
Future<void> saveContacts(List<DiscoveryContact> contacts) async {
Future<void> saveContacts(List<Contact> contacts) async {
final prefs = PrefsManager.instance;
final jsonList = contacts.map(_toJson).toList();
await prefs.setString(_keyPrefix, jsonEncode(jsonList));
}
Map<String, dynamic> _toJson(DiscoveryContact contact) {
Map<String, dynamic> _toJson(Contact contact) {
return {
'rawPacket': base64Encode(contact.rawPacket),
'publicKey': base64Encode(contact.publicKey),
'name': contact.name,
'type': contact.type,
'flags': contact.flags,
'pathLength': contact.pathLength,
'path': base64Encode(contact.path),
'pathOverride': contact.pathOverride,
'pathOverrideBytes': contact.pathOverrideBytes != null
? base64Encode(contact.pathOverrideBytes!)
: null,
'latitude': contact.latitude,
'longitude': contact.longitude,
'lastSeen': contact.lastSeen.millisecondsSinceEpoch,
'lastMessageAt': contact.lastMessageAt.millisecondsSinceEpoch,
'rawPacket': contact.rawPacket != null
? base64Encode(contact.rawPacket!)
: null,
};
}
DiscoveryContact _fromJson(Map<String, dynamic> json) {
Contact _fromJson(Map<String, dynamic> json) {
final lastSeenMs = json['lastSeen'] as int? ?? 0;
return DiscoveryContact(
rawPacket: Uint8List.fromList(base64Decode(json['rawPacket'] as String)),
final lastMessageMs = json['lastMessageAt'] as int?;
return Contact(
publicKey: Uint8List.fromList(base64Decode(json['publicKey'] as String)),
name: json['name'] as String? ?? 'Unknown',
type: json['type'] as int? ?? 0,
flags: json['flags'] as int? ?? 0,
pathLength: json['pathLength'] as int? ?? -1,
path: json['path'] != null
? Uint8List.fromList(base64Decode(json['path'] as String))
: Uint8List(0),
pathOverride: json['pathOverride'] as int?,
pathOverrideBytes: json['pathOverrideBytes'] != null
? Uint8List.fromList(
base64Decode(json['pathOverrideBytes'] as String),
)
: null,
latitude: (json['latitude'] as num?)?.toDouble(),
longitude: (json['longitude'] as num?)?.toDouble(),
lastSeen: DateTime.fromMillisecondsSinceEpoch(lastSeenMs),
lastMessageAt: DateTime.fromMillisecondsSinceEpoch(
lastMessageMs ?? lastSeenMs,
),
isActive: false,
rawPacket: json['rawPacket'] != null
? Uint8List.fromList(base64Decode(json['rawPacket'] as String))
: null,
);
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ class ContactGroupStore {
return [];
}
final prefs = PrefsManager.instance;
String? jsonString = prefs.getString(_keyPrefix);
String? jsonString = prefs.getString(keyFor);
if (jsonString == null || jsonString.isEmpty) {
// Attempt migration from legacy unscoped key on first load
final legacyJsonString = prefs.getString(_keyPrefix);
+8
View File
@@ -76,6 +76,10 @@ class ContactStore {
'longitude': contact.longitude,
'lastSeen': contact.lastSeen.millisecondsSinceEpoch,
'lastMessageAt': contact.lastMessageAt.millisecondsSinceEpoch,
'isActive': contact.isActive,
'rawPacket': contact.rawPacket != null
? base64Encode(contact.rawPacket!)
: null,
};
}
@@ -103,6 +107,10 @@ class ContactStore {
lastMessageAt: DateTime.fromMillisecondsSinceEpoch(
lastMessageMs ?? lastSeenMs,
),
isActive: json['isActive'] as bool? ?? true,
rawPacket: json['rawPacket'] != null
? Uint8List.fromList(base64Decode(json['rawPacket'] as String))
: null,
);
}
}
+1 -1
View File
@@ -32,7 +32,7 @@ class UnreadStore {
return {};
}
final prefs = PrefsManager.instance;
String? jsonString = prefs.getString(_keyPrefix);
String? jsonString = prefs.getString(keyFor);
if (jsonString == null || jsonString.isEmpty) {
// Attempt migration from legacy unscoped key on first load
final legacyJsonString = prefs.getString(_keyPrefix);