feat(#610): render a received contact card as an Add Contact chip

The receive half of contact sharing. Until now an incoming
<key:type:name> rendered as raw text while the stock app showed a native
Add Contact button for the identical payload, so sharing worked outbound
only: a stock user could add an Offband user from a card, but not the
reverse. Confirmed by the owner on hardware.

Reuses the proven mention-chip mechanism in TranslatedMessageContent
rather than adding new machinery. Both patterns are now collected and
sorted by position, so a message carrying a mention AND a card renders
both in the right order; they cannot overlap, since a mention is @[...]
and a card is <...>.

Tapping opens the add dialog seeded with the card rather than adding
silently. A contact is an identity, so adding one stays a deliberate act
with the key, name and type visible first.

Two deliberate refusals:

- A card whose key is already a contact renders inert, mirroring stock,
  which warned the owner rather than silently re-adding.
- A card matching the shape but failing the parser, such as an
  out-of-range type, falls back to plain text. Showing an Add chip there
  would promise something the parser will refuse.

Parsing already existed from the #611 Gemini review, so this change is
rendering and dedupe only.

Epic #610 under Feature #609.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat/610-contact-card-receive
Strycher 3 weeks ago
parent 08416aa3cb
commit 2716afda50

@ -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",

@ -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:

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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';

@ -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 `<key:type:name>` 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, `<key:type:name>`.
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<MeshCoreConnector, bool>(
(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,
);
}
}

@ -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 = <Match>[
..._mention.allMatches(text),
..._contactCard.allMatches(text),
]..sort((a, b) => a.start.compareTo(b.start));
final spans = <InlineSpan>[];
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;

@ -0,0 +1,124 @@
// Receiving a contact card (#610).
//
// Before this, an incoming <key:type:name> 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<Contact> known;
@override
List<Contact> 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<void> _pump(WidgetTester tester, String text, _Conn conn) =>
tester.pumpWidget(
ChangeNotifierProvider<MeshCoreConnector>.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);
});
}

@ -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",

Loading…
Cancel
Save

Powered by TurnKey Linux.