From 5115d8bbe350f3ecb6ca93460380eeb03c31c6b2 Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Sat, 31 Jan 2026 15:00:33 -0800 Subject: [PATCH] Added zero-hop contact sharing functionality and related UI updates --- lib/connector/meshcore_protocol.dart | 9 +++ lib/l10n/app_en.arb | 8 +- lib/screens/contacts_screen.dart | 105 ++++++++++++++++++++++----- 3 files changed, 101 insertions(+), 21 deletions(-) diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index df077583..64a9963f 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -755,4 +755,13 @@ Uint8List buildImportContactFrame(String contactFrame) { writer.writeByte(cmdImportContact); writer.writeHex(contactFrame); 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(); } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1203b20a..ba6afc4b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1338,5 +1338,11 @@ "contacts_zeroHopAdvert":"Zero Hop Advert", "contacts_floodAdvert":"Flood Advert", "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." } diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index 59a8d05e..99bad878 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -55,6 +55,9 @@ class _ContactsScreenState extends State Timer? _searchDebounce; bool _imported = false; + bool _zeroHopContact = false; + bool _copyedContact = false; + StreamSubscription? _frameSubscription; @override @@ -98,20 +101,53 @@ class _ContactsScreenState extends State Clipboard.setData(ClipboardData(text: "meshcore://$hexString")); } - if(code == respCodeOk && _imported) { + if(code == respCodeOk) { // 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; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.contacts_contactImported)), - ); } - if(code == respCodeErr && _imported) { - // Show a snackbar indicating success + if(code == respCodeErr) { + // 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; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.contacts_contactImportFailed)), - ); + _zeroHopContact = false; } }); @@ -120,35 +156,49 @@ class _ContactsScreenState extends State Future _contactExport(Uint8List pubKey) async { final connector = Provider.of(context, listen: false); final exportContactFrame = buildExportContactFrame(pubKey); + _copyedContact = true; await connector.sendFrame(exportContactFrame); return; } + Future _contactZeroHop(Uint8List pubKey) async { + final connector = Provider.of(context, listen: false); + final exportContactZeroHopFrame = buildZeroHopContact(pubKey); + _zeroHopContact = true; + await connector.sendFrame(exportContactZeroHopFrame); + } + Future _contactImport() async { + final connector = Provider.of(context, listen: false); final clipboardData = await Clipboard.getData('text/plain'); if (clipboardData == null || clipboardData.text == null) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.contacts_clipboardEmpty)), - ); + if(mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.contacts_clipboardEmpty)), + ); + } return; } final text = clipboardData.text!.trim(); if (!text.startsWith('meshcore://')) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)), - ); + if(mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)), + ); + } return; } final hexString = text.substring('meshcore://'.length); try { - final connector = Provider.of(context, listen: false); final importContactFrame = buildImportContactFrame(hexString); await connector.sendFrame(importContactFrame); _imported = true; } catch (e) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)), - ); + if(mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)), + ); + } } } @@ -977,6 +1027,22 @@ class _ContactsScreenState extends State }, ), ], + 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( leading: const Icon(Icons.delete, color: Colors.red), title: Text( @@ -988,7 +1054,6 @@ class _ContactsScreenState extends State _confirmDelete(context, connector, contact); }, ), - ], ], ), ),