Added zero-hop contact sharing functionality and related UI updates

This commit is contained in:
Winston Lowe
2026-01-31 15:00:33 -08:00
parent 8470171e88
commit 5115d8bbe3
3 changed files with 101 additions and 21 deletions
+9
View File
@@ -755,4 +755,13 @@ Uint8List buildImportContactFrame(String contactFrame) {
writer.writeByte(cmdImportContact); writer.writeByte(cmdImportContact);
writer.writeHex(contactFrame); writer.writeHex(contactFrame);
return writer.toBytes(); return writer.toBytes();
}
// Build a export contact frame
// [cmd][pub_key x32]
Uint8List buildZeroHopContact(Uint8List pubKey) {
final writer = BufferWriter();
writer.writeByte(cmdShareContact);
writer.writeBytes(pubKey);
return writer.toBytes();
} }
+7 -1
View File
@@ -1338,5 +1338,11 @@
"contacts_zeroHopAdvert":"Zero Hop Advert", "contacts_zeroHopAdvert":"Zero Hop Advert",
"contacts_floodAdvert":"Flood Advert", "contacts_floodAdvert":"Flood Advert",
"contacts_copyAdvertToClipboard":"Copy Advert to Clipboard", "contacts_copyAdvertToClipboard":"Copy Advert to Clipboard",
"contacts_addContactFromClipboard":"Add Contact from Clipboard" "contacts_addContactFromClipboard":"Add Contact from Clipboard",
"contacts_ShareContact": "Copy contact to Clipboard",
"contacts_ShareContactZeroHop": "Share contact by advert",
"contacts_zeroHopContactAdvertSent": "Sent contact by advert.",
"contacts_zeroHopContactAdvertFailed": "Failed to send contact.",
"contacts_contactAdvertCopied": "Advert copied to Clipboard.",
"contacts_contactAdvertCopyFailed": "Copying advert to Clipboard failed."
} }
+85 -20
View File
@@ -55,6 +55,9 @@ class _ContactsScreenState extends State<ContactsScreen>
Timer? _searchDebounce; Timer? _searchDebounce;
bool _imported = false; bool _imported = false;
bool _zeroHopContact = false;
bool _copyedContact = false;
StreamSubscription<Uint8List>? _frameSubscription; StreamSubscription<Uint8List>? _frameSubscription;
@override @override
@@ -98,20 +101,53 @@ class _ContactsScreenState extends State<ContactsScreen>
Clipboard.setData(ClipboardData(text: "meshcore://$hexString")); Clipboard.setData(ClipboardData(text: "meshcore://$hexString"));
} }
if(code == respCodeOk && _imported) { if(code == respCodeOk) {
// Show a snackbar indicating success // Show a snackbar indicating success
if(_imported && mounted){
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactImported)),
);
}
if(_zeroHopContact && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_zeroHopContactAdvertSent)),
);
}
if(_copyedContact && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactAdvertCopied)),
);
}
_copyedContact = false;
_zeroHopContact = false;
_imported = false; _imported = false;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactImported)),
);
} }
if(code == respCodeErr && _imported) { if(code == respCodeErr) {
// Show a snackbar indicating success // Show a snackbar indicating failure
if(_imported && mounted){
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactImportFailed)),
);
}
if(_zeroHopContact && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_zeroHopContactAdvertFailed)),
);
}
if(_copyedContact && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactAdvertCopyFailed)),
);
}
_copyedContact = false;
_imported = false; _imported = false;
ScaffoldMessenger.of(context).showSnackBar( _zeroHopContact = false;
SnackBar(content: Text(context.l10n.contacts_contactImportFailed)),
);
} }
}); });
@@ -120,35 +156,49 @@ class _ContactsScreenState extends State<ContactsScreen>
Future<void> _contactExport(Uint8List pubKey) async { Future<void> _contactExport(Uint8List pubKey) async {
final connector = Provider.of<MeshCoreConnector>(context, listen: false); final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final exportContactFrame = buildExportContactFrame(pubKey); final exportContactFrame = buildExportContactFrame(pubKey);
_copyedContact = true;
await connector.sendFrame(exportContactFrame); await connector.sendFrame(exportContactFrame);
return; return;
} }
Future<void> _contactZeroHop(Uint8List pubKey) async {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final exportContactZeroHopFrame = buildZeroHopContact(pubKey);
_zeroHopContact = true;
await connector.sendFrame(exportContactZeroHopFrame);
}
Future<void> _contactImport() async { Future<void> _contactImport() async {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final clipboardData = await Clipboard.getData('text/plain'); final clipboardData = await Clipboard.getData('text/plain');
if (clipboardData == null || clipboardData.text == null) { if (clipboardData == null || clipboardData.text == null) {
ScaffoldMessenger.of(context).showSnackBar( if(mounted) {
SnackBar(content: Text(context.l10n.contacts_clipboardEmpty)), ScaffoldMessenger.of(context).showSnackBar(
); SnackBar(content: Text(context.l10n.contacts_clipboardEmpty)),
);
}
return; return;
} }
final text = clipboardData.text!.trim(); final text = clipboardData.text!.trim();
if (!text.startsWith('meshcore://')) { if (!text.startsWith('meshcore://')) {
ScaffoldMessenger.of(context).showSnackBar( if(mounted) {
SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)), ScaffoldMessenger.of(context).showSnackBar(
); SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
);
}
return; return;
} }
final hexString = text.substring('meshcore://'.length); final hexString = text.substring('meshcore://'.length);
try { try {
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final importContactFrame = buildImportContactFrame(hexString); final importContactFrame = buildImportContactFrame(hexString);
await connector.sendFrame(importContactFrame); await connector.sendFrame(importContactFrame);
_imported = true; _imported = true;
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( if(mounted) {
SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)), ScaffoldMessenger.of(context).showSnackBar(
); SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)),
);
}
} }
} }
@@ -977,6 +1027,22 @@ class _ContactsScreenState extends State<ContactsScreen>
}, },
), ),
], ],
ListTile(
leading: const Icon(Icons.copy),
title: Text(context.l10n.contacts_ShareContact),
onTap: () {
Navigator.pop(sheetContext);
_contactExport(contact.publicKey);
},
),
ListTile(
leading: const Icon(Icons.connect_without_contact),
title: Text(context.l10n.contacts_ShareContactZeroHop),
onTap: () {
Navigator.pop(sheetContext);
_contactZeroHop(contact.publicKey);
},
),
ListTile( ListTile(
leading: const Icon(Icons.delete, color: Colors.red), leading: const Icon(Icons.delete, color: Colors.red),
title: Text( title: Text(
@@ -988,7 +1054,6 @@ class _ContactsScreenState extends State<ContactsScreen>
_confirmDelete(context, connector, contact); _confirmDelete(context, connector, contact);
}, },
), ),
],
], ],
), ),
), ),