Merge branch 'main' into chrome/main

This commit is contained in:
Ben Allfree
2026-02-24 22:51:51 -08:00
39 changed files with 1395 additions and 2221 deletions
+68
View File
@@ -669,6 +669,7 @@ class MeshCoreConnector extends ChangeNotifier {
publicKey: contact.publicKey,
name: contact.name,
type: contact.type,
flags: contact.flags,
pathLength: selection.hopCount >= 0
? selection.hopCount
: contact.pathLength,
@@ -1181,11 +1182,78 @@ class MeshCoreConnector extends ChangeNotifier {
customPath,
pathLen,
type: contact.type,
flags: contact.flags,
name: contact.name,
),
);
}
Future<void> setContactFavorite(Contact contact, bool isFavorite) async {
if (!isConnected) return;
final latestContact =
await _fetchContactSnapshotFromDevice(contact.publicKey) ?? contact;
final updatedFlags = isFavorite
? (latestContact.flags | contactFlagFavorite)
: (latestContact.flags & ~contactFlagFavorite);
await sendFrame(
buildUpdateContactPathFrame(
latestContact.publicKey,
latestContact.path,
latestContact.pathLength,
type: latestContact.type,
flags: updatedFlags,
name: latestContact.name,
),
);
final index = _contacts.indexWhere(
(c) => c.publicKeyHex == contact.publicKeyHex,
);
if (index >= 0) {
_contacts[index] = _contacts[index].copyWith(
type: latestContact.type,
name: latestContact.name,
pathLength: latestContact.pathLength,
path: latestContact.path,
flags: updatedFlags,
);
notifyListeners();
unawaited(_persistContacts());
}
}
Future<Contact?> _fetchContactSnapshotFromDevice(
Uint8List pubKey, {
Duration timeout = const Duration(seconds: 3),
}) async {
if (!isConnected) return null;
final expectedKeyHex = pubKeyToHex(pubKey);
final completer = Completer<Contact?>();
void finish(Contact? result) {
if (!completer.isCompleted) {
completer.complete(result);
}
}
final subscription = receivedFrames.listen((frame) {
if (frame.isEmpty || frame[0] != respCodeContact) return;
final parsed = Contact.fromFrame(frame);
if (parsed == null || parsed.publicKeyHex != expectedKeyHex) return;
finish(parsed);
});
final timer = Timer(timeout, () => finish(null));
try {
await getContactByKey(pubKey);
return await completer.future;
} finally {
timer.cancel();
await subscription.cancel();
}
}
/// Set path override for a contact (persists across contact refreshes)
/// pathLen: -1 = force flood, null = auto (use device path), >= 0 = specific path
Future<void> setPathOverride(
+1
View File
@@ -290,6 +290,7 @@ int _minPositive(int a, int b) {
const int contactPubKeyOffset = 1;
const int contactTypeOffset = 33;
const int contactFlagsOffset = 34;
const int contactFlagFavorite = 0x01;
const int contactPathLenOffset = 35;
const int contactPathOffset = 36;
const int contactNameOffset = 100;