diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 248b112..d7a9dfd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -2521,6 +2521,23 @@ "contacts_verifiedByMessage": "Key confirmed. A message with this contact went through, which only works with the matching key.", "contacts_verifiedKeyOnly": "Added from a key. Nothing has confirmed it on air yet.", "contacts_lastSeenNever": "Not heard yet", + "contacts_cardAddContact": "Add {name}", + "@contacts_cardAddContact": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "contacts_cardAlreadyAdded": "{name}", + "@contacts_cardAlreadyAdded": { + "placeholders": { + "name": { + "type": "String" + } + } + }, + "contacts_cardAlreadyAddedTooltip": "Already in your contacts", "chat_attachTooltip": "Add to message", "chat_attachGif": "GIF", "chat_attachMyContact": "My contact card", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index c886449..0518899 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -7576,6 +7576,24 @@ abstract class AppLocalizations { /// **'Not heard yet'** String get contacts_lastSeenNever; + /// No description provided for @contacts_cardAddContact. + /// + /// In en, this message translates to: + /// **'Add {name}'** + String contacts_cardAddContact(String name); + + /// No description provided for @contacts_cardAlreadyAdded. + /// + /// In en, this message translates to: + /// **'{name}'** + String contacts_cardAlreadyAdded(String name); + + /// No description provided for @contacts_cardAlreadyAddedTooltip. + /// + /// In en, this message translates to: + /// **'Already in your contacts'** + String get contacts_cardAlreadyAddedTooltip; + /// No description provided for @chat_attachTooltip. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index b0cf93b..cba48a2 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -4425,6 +4425,19 @@ class AppLocalizationsBg extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index a55a6ac..39ddff6 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -4437,6 +4437,19 @@ class AppLocalizationsDe extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 0415d17..a308c43 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -4359,6 +4359,19 @@ class AppLocalizationsEn extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 7bd6e11..f6523dc 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -4426,6 +4426,19 @@ class AppLocalizationsEs extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 4d514b2..a1097d3 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -4448,6 +4448,19 @@ class AppLocalizationsFr extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 676dafb..c236146 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -4443,6 +4443,19 @@ class AppLocalizationsHu extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 5e9bb80..a00a17b 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -4431,6 +4431,19 @@ class AppLocalizationsIt extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 566fa32..b1e0e9b 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -4228,6 +4228,19 @@ class AppLocalizationsJa extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 0321b79..a7a03f1 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -4230,6 +4230,19 @@ class AppLocalizationsKo extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index f6dd647..7aa8ff0 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -4411,6 +4411,19 @@ class AppLocalizationsNl extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 08c0a80..77fd257 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -4441,6 +4441,19 @@ class AppLocalizationsPl extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index e091edf..a20e565 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -4423,6 +4423,19 @@ class AppLocalizationsPt extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 3a543c3..500f4f9 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -4433,6 +4433,19 @@ class AppLocalizationsRu extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 0cba92f..5e2bfac 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -4407,6 +4407,19 @@ class AppLocalizationsSk extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index b7bbd5a..68874f3 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -4401,6 +4401,19 @@ class AppLocalizationsSl extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index b8e4588..8d1031a 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -4380,6 +4380,19 @@ class AppLocalizationsSv extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 4c6569b..ac8e0fc 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -4432,6 +4432,19 @@ class AppLocalizationsUk extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 39fff5d..d0a2d12 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -4131,6 +4131,19 @@ class AppLocalizationsZh extends AppLocalizations { @override String get contacts_lastSeenNever => 'Not heard yet'; + @override + String contacts_cardAddContact(String name) { + return 'Add $name'; + } + + @override + String contacts_cardAlreadyAdded(String name) { + return '$name'; + } + + @override + String get contacts_cardAlreadyAddedTooltip => 'Already in your contacts'; + @override String get chat_attachTooltip => 'Add to message'; diff --git a/lib/widgets/contact_card_chip.dart b/lib/widgets/contact_card_chip.dart new file mode 100644 index 0000000..051f882 --- /dev/null +++ b/lib/widgets/contact_card_chip.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../connector/meshcore_connector.dart'; +import '../l10n/l10n.dart'; +import '../models/contact.dart'; +import 'add_contact_by_key_dialog.dart'; + +/// Renders a received contact share card as a tappable Add Contact affordance +/// instead of the raw `` text. (#610) +/// +/// This is the receive half of the exchange. The send half (#611) already +/// emits this format, and the stock app already renders it as a native Add +/// Contact button, so until now sharing worked outbound only: a stock user +/// could add an Offband user from a card, but not the reverse. +/// +/// Tapping opens the add dialog seeded with the card, so the user sees the key, +/// name and type before anything is written to the radio. It does NOT add +/// silently: a contact is an identity, and adding one should be a deliberate +/// act with the details visible. +class ContactCardChip extends StatelessWidget { + const ContactCardChip({super.key, required this.card, required this.style}); + + /// The raw matched card text, ``. + final String card; + + final TextStyle style; + + @override + Widget build(BuildContext context) { + final parsed = Contact.fromChannelShare(card); + final scheme = Theme.of(context).colorScheme; + final l10n = context.l10n; + + // The regex matched the shape but the parser rejected the contents, e.g. a + // type outside the documented range. Show the original text rather than a + // chip that would lie about being addable. + if (parsed == null) return Text(card, style: style); + + // Mirrors what stock does: it warned the owner when the contact was + // already held rather than silently re-adding. + final known = context.select( + (c) => c.contacts.any((x) => x.publicKeyHex == parsed.publicKeyHex), + ); + + final label = known + ? l10n.contacts_cardAlreadyAdded(parsed.name) + : l10n.contacts_cardAddContact(parsed.name); + + final chip = Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + color: known + ? scheme.onSurface.withValues(alpha: 0.08) + : scheme.primaryContainer, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + known ? Icons.how_to_reg : Icons.person_add_alt_1, + size: 15, + color: known ? scheme.onSurfaceVariant : scheme.onPrimaryContainer, + ), + const SizedBox(width: 5), + Text( + label, + style: style.copyWith( + fontWeight: FontWeight.w500, + color: known + ? scheme.onSurfaceVariant + : scheme.onPrimaryContainer, + ), + ), + ], + ), + ); + + if (known) { + return Tooltip( + message: l10n.contacts_cardAlreadyAddedTooltip, + child: chip, + ); + } + + return InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => showAddContactByKeyDialog(context, initialKeyText: card), + child: chip, + ); + } +} diff --git a/lib/widgets/translated_message_content.dart b/lib/widgets/translated_message_content.dart index 96b8917..97b8623 100644 --- a/lib/widgets/translated_message_content.dart +++ b/lib/widgets/translated_message_content.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../helpers/link_handler.dart'; +import 'contact_card_chip.dart'; class TranslatedMessageContent extends StatelessWidget { final String displayText; @@ -25,6 +26,15 @@ class TranslatedMessageContent extends StatelessWidget { // A leading `@[Name] ` reply prefix. static final RegExp _replyPrefix = RegExp(r'^@\[([^\]]+)\]\s+'); + /// A contact share card, `<64-hex key:type:name>`, rendered as a tappable + /// Add Contact chip rather than the raw text it used to show. (#610) + /// + /// This is the format real clients put on the air, confirmed because the + /// stock app renders it as a native Add Contact button. The name is the final + /// field and may contain colons and spaces, so it is matched greedily to the + /// closing bracket; brackets themselves are stripped by the emitter. + static final RegExp _contactCard = RegExp(r'<[0-9a-fA-F]{64}:\d+:[^>]*>'); + /// The name of a leading `@[Name]` reply mention, or null. Lets callers show /// a reply chip above content that isn't rendered as text (e.g. a reply-gif, /// #232). @@ -50,27 +60,42 @@ class TranslatedMessageContent extends StatelessWidget { } Widget _buildText(BuildContext context, String text, TextStyle textStyle) { - if (!_mention.hasMatch(text)) { + final hasMention = _mention.hasMatch(text); + final hasCard = _contactCard.hasMatch(text); + if (!hasMention && !hasCard) { return LinkHandler.buildLinkifyText( context: context, text: text, style: textStyle, ); } - // Mentions can't be interleaved with the Linkify widget, so a message that - // contains a mention renders as rich text with chip spans (links inside a - // mention message are not tappable, same as the prior leading-mention - // path). Messages without a mention keep full link support above. + // Neither chip can be interleaved with the Linkify widget, so a message + // containing one renders as rich text with chip spans (links inside such a + // message are not tappable, same as the prior leading-mention path). + // Messages with neither keep full link support above. + // + // Both patterns are collected and sorted by position, so a message + // carrying a mention AND a contact card renders both in the right order. + // They cannot overlap: a mention is `@[...]`, a card is `<...>`. + final matches = [ + ..._mention.allMatches(text), + ..._contactCard.allMatches(text), + ]..sort((a, b) => a.start.compareTo(b.start)); + final spans = []; var last = 0; - for (final m in _mention.allMatches(text)) { + for (final m in matches) { + if (m.start < last) continue; if (m.start > last) { spans.add(TextSpan(text: text.substring(last, m.start))); } + final isCard = text[m.start] == '<'; spans.add( WidgetSpan( alignment: PlaceholderAlignment.middle, - child: mentionChip(context, m.group(1)!, textStyle), + child: isCard + ? ContactCardChip(card: m.group(0)!, style: textStyle) + : mentionChip(context, m.group(1)!, textStyle), ), ); last = m.end; diff --git a/test/widgets/contact_card_chip_test.dart b/test/widgets/contact_card_chip_test.dart new file mode 100644 index 0000000..01bfe63 --- /dev/null +++ b/test/widgets/contact_card_chip_test.dart @@ -0,0 +1,124 @@ +// Receiving a contact card (#610). +// +// Before this, an incoming rendered as raw text while the stock +// app showed a native Add Contact button for the very same payload. Sharing +// worked outbound only. + +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; + +import 'package:meshcore_open/connector/meshcore_connector.dart'; +import 'package:meshcore_open/connector/meshcore_protocol.dart'; +import 'package:meshcore_open/l10n/app_localizations.dart'; +import 'package:meshcore_open/models/contact.dart'; +import 'package:meshcore_open/widgets/translated_message_content.dart'; + +const _key = '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff'; +const _card = '<$_key:1:Ka8sbi>'; + +class _Conn extends MeshCoreConnector { + _Conn({this.known = const []}); + final List known; + + @override + List get contacts => known; +} + +Contact _contact(String keyHex) => Contact( + publicKey: hex2Uint8List(keyHex), + name: 'Ka8sbi', + type: advTypeChat, + pathLength: -1, + path: Uint8List(0), + lastSeen: DateTime(2026, 9, 9), +); + +Future _pump(WidgetTester tester, String text, _Conn conn) => + tester.pumpWidget( + ChangeNotifierProvider.value( + value: conn, + child: MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold( + body: TranslatedMessageContent( + displayText: text, + style: const TextStyle(fontSize: 14), + ), + ), + ), + ), + ); + +void main() { + testWidgets('a received card renders as an actionable chip, not raw text', ( + tester, + ) async { + await _pump(tester, 'here is mine $_card', _Conn()); + + // The raw payload must not be shown. + expect(find.textContaining(_key), findsNothing); + // An actionable affordance is offered instead. + expect(find.byIcon(Icons.person_add_alt_1), findsOneWidget); + expect(find.byType(InkWell), findsOneWidget); + // The caption survives alongside it. + expect(find.textContaining('here is mine'), findsOneWidget); + }); + + testWidgets('a card for a known contact does not offer to re-add', ( + tester, + ) async { + // Mirrors stock, which warned rather than silently re-adding. + await _pump(tester, _card, _Conn(known: [_contact(_key)])); + + expect(find.byIcon(Icons.how_to_reg), findsOneWidget); + expect(find.byIcon(Icons.person_add_alt_1), findsNothing); + // Not tappable, so it cannot be double-added. + expect(find.byType(InkWell), findsNothing); + }); + + testWidgets('a malformed card falls back to plain text rather than lying', ( + tester, + ) async { + // Right shape, impossible type. Rendering an Add chip here would promise + // something the parser will refuse. + const bad = '<$_key:9:Bob>'; + await _pump(tester, bad, _Conn()); + + expect(find.byIcon(Icons.person_add_alt_1), findsNothing); + expect(find.textContaining(bad), findsOneWidget); + }); + + testWidgets('a mention and a card in one message both render', ( + tester, + ) async { + // The two patterns are collected and sorted by position, so this is the + // case that would break a naive single-regex implementation. + await _pump(tester, '@[Bob] add this $_card', _Conn()); + + expect(find.textContaining('@Bob'), findsOneWidget); + expect(find.byIcon(Icons.person_add_alt_1), findsOneWidget); + expect(find.textContaining(_key), findsNothing); + }); + + testWidgets('a plain message is untouched and keeps link support', ( + tester, + ) async { + await _pump(tester, 'just a normal message', _Conn()); + + expect(find.byIcon(Icons.person_add_alt_1), findsNothing); + expect(find.textContaining('just a normal message'), findsOneWidget); + }); + + testWidgets('names with spaces and emoji survive into the chip label', ( + tester, + ) async { + await _pump(tester, '<$_key:2:Roger KY4RS 🧙>', _Conn()); + + expect(find.textContaining('Roger KY4RS 🧙'), findsOneWidget); + expect(find.byIcon(Icons.person_add_alt_1), findsOneWidget); + }); +} diff --git a/untranslated.json b/untranslated.json index 2f8061a..8a88df9 100644 --- a/untranslated.json +++ b/untranslated.json @@ -115,6 +115,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -261,6 +264,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -407,6 +413,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -553,6 +562,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -699,6 +711,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -845,6 +860,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -991,6 +1009,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -1137,6 +1158,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -1283,6 +1307,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -1429,6 +1456,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -1575,6 +1605,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -1721,6 +1754,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -1867,6 +1903,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -2013,6 +2052,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -2159,6 +2201,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -2305,6 +2350,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact", @@ -2451,6 +2499,9 @@ "contacts_verifiedByMessage", "contacts_verifiedKeyOnly", "contacts_lastSeenNever", + "contacts_cardAddContact", + "contacts_cardAlreadyAdded", + "contacts_cardAlreadyAddedTooltip", "chat_attachTooltip", "chat_attachGif", "chat_attachMyContact",