mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 08:20:36 +00:00
feat: UX polish, localization, advert relocation, and map filter fixes
- i18n: translate connection screen + chat tab keys into 13 locales; add "Send my contact" (advert) strings in all locales - feat: move self-advert from home header into composer "+" action menu (new lib/utils/advert_helper.dart, always shows Flood/Direct sheet) - fix: hide-repeaters map toggle was bypassed in simple mode - fix: simple mode map now shows only favourite chat contacts - fix: wrap ListTiles in Material (contact_tile secondary actions, inferred contact group card) to satisfy Flutter ink assertions - device-authoritative contact/channel sync and UX review fixes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -303,6 +303,45 @@ class ContactsProvider with ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
/// Finish a device contact sync started with [prepareForDeviceContactSync].
|
||||
///
|
||||
/// The device contact list is authoritative: when [deviceListComplete] is
|
||||
/// true, any contact still left in the retained snapshot (i.e. not sent by
|
||||
/// the device during this sync) is deleted locally. Channel pseudo-contacts
|
||||
/// are excluded — they mirror device channel slots and are reconciled by the
|
||||
/// channel sync instead. When the sync was partial (timeout/disconnect),
|
||||
/// nothing is deleted so a flaky link can't wipe valid contacts.
|
||||
void finalizeDeviceContactSync({required bool deviceListComplete}) {
|
||||
if (!deviceListComplete) {
|
||||
if (_retainedContactsForSync.isNotEmpty) {
|
||||
debugPrint(
|
||||
'ℹ️ [ContactsProvider] Partial contact sync - keeping '
|
||||
'${_retainedContactsForSync.length} unconfirmed contact(s)',
|
||||
);
|
||||
}
|
||||
_retainedContactsForSync.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final staleKeys = _retainedContactsForSync.keys
|
||||
.where((key) => _contacts[key]?.type != ContactType.channel)
|
||||
.where(_contacts.containsKey)
|
||||
.toList();
|
||||
for (final key in staleKeys) {
|
||||
final removed = _contacts.remove(key);
|
||||
debugPrint(
|
||||
'🗑️ [ContactsProvider] Removing contact no longer on device: '
|
||||
'${removed?.advName} (${key.substring(0, 8)}...)',
|
||||
);
|
||||
}
|
||||
_retainedContactsForSync.clear();
|
||||
|
||||
if (staleKeys.isNotEmpty) {
|
||||
_persistContacts();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove self-contact from loaded contacts (called after BLE connection established)
|
||||
void _removeSelfContact(Uint8List devicePublicKey) {
|
||||
final selfKeyHex = devicePublicKey
|
||||
@@ -386,6 +425,10 @@ class ContactsProvider with ChangeNotifier {
|
||||
ContactTelemetry? get selfTelemetry => _selfTelemetry;
|
||||
List<Contact> get favouriteContacts =>
|
||||
_contacts.values.where((c) => c.isFavourite).toList();
|
||||
List<Contact> get favouriteChatContacts =>
|
||||
chatContacts.where((c) => c.isFavourite).toList();
|
||||
List<Contact> get favouriteContactsWithLocation =>
|
||||
favouriteContacts.where((c) => effectiveLocationFor(c) != null).toList();
|
||||
List<SavedContactGroup> get savedContactGroups =>
|
||||
List<SavedContactGroup>.from(_savedContactGroups)
|
||||
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
|
||||
Reference in New Issue
Block a user