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:
Janez T
2026-06-12 10:01:10 +02:00
parent 3e1113035d
commit ba27843166
62 changed files with 12546 additions and 2521 deletions

View File

@@ -1002,6 +1002,70 @@ void main() {
);
});
group('ContactsProvider device-authoritative sync', () {
late ContactsProvider provider;
late Contact keptContact;
late Contact staleContact;
setUp(() async {
SharedPreferences.setMockInitialValues({});
provider = ContactsProvider();
keptContact = createContact(
key: createPublicKey(10),
type: ContactType.chat,
name: 'Still On Device',
);
staleContact = createContact(
key: createPublicKey(40),
type: ContactType.chat,
name: 'Deleted On Device',
);
provider.addOrUpdateContact(keptContact);
provider.addOrUpdateContact(staleContact);
await provider.prepareForDeviceContactSync();
});
test('removes contacts the device no longer reports after a full sync', () {
// Device only re-sends one of the two contacts during sync.
provider.addOrUpdateContact(keptContact);
provider.finalizeDeviceContactSync(deviceListComplete: true);
expect(provider.findContactByKey(keptContact.publicKey), isNotNull);
expect(provider.findContactByKey(staleContact.publicKey), isNull);
});
test('keeps all contacts when the sync was partial', () {
provider.addOrUpdateContact(keptContact);
provider.finalizeDeviceContactSync(deviceListComplete: false);
expect(provider.findContactByKey(keptContact.publicKey), isNotNull);
expect(provider.findContactByKey(staleContact.publicKey), isNotNull);
});
test('never removes channel pseudo-contacts during contact sync', () {
final channelKey = Uint8List(32)
..[0] = 0xFF
..[1] = 3;
provider.addOrUpdateContact(
createContact(
key: channelKey,
type: ContactType.channel,
name: 'Team Channel',
),
);
// Re-prepare so the channel pseudo-contact is part of the snapshot.
provider.prepareForDeviceContactSync();
provider.addOrUpdateContact(keptContact);
provider.finalizeDeviceContactSync(deviceListComplete: true);
expect(provider.findContactByKey(channelKey), isNotNull);
expect(provider.findContactByKey(staleContact.publicKey), isNull);
});
});
test('stores raw telemetry hex in metadata for verification', () async {
SharedPreferences.setMockInitialValues({});
final provider = ContactsProvider();