From eeb8ff34e8959bd060b0ae4aa706fb341d7f6c0a Mon Sep 17 00:00:00 2001 From: Winston Lowe Date: Mon, 26 Jan 2026 16:11:21 -0800 Subject: [PATCH] Implement contact import functionality from clipboard and add relevant UI options --- lib/connector/meshcore_protocol.dart | 14 +++++-- lib/l10n/app_en.arb | 6 ++- lib/screens/contacts_screen.dart | 60 +++++++++++++++++++++++----- 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index dda07bd..0609adb 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -98,6 +98,14 @@ class BufferWriter { } writeBytes(bytes); } + + void writeHex(String hex) { + List result = []; + for (int i = 0; i < hex.length ~/ 2; i++) { + result.add(int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16)); + } + writeBytes(Uint8List.fromList(result)); + } } // Command codes (to device) @@ -720,10 +728,10 @@ Uint8List buildExportContactFrame(Uint8List pubKey) { } // Build a import contact frame -// [cmd][contact_frame x148] -Uint8List buildImportContactFrame(Uint8List contactFrame) { +// [cmd][contact_frame x98+] +Uint8List buildImportContactFrame(String contactFrame) { final writer = BufferWriter(); writer.writeByte(cmdImportContact); - writer.writeBytes(contactFrame); + writer.writeHex(contactFrame); return writer.toBytes(); } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 56cb1cc..7c8a0a7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1308,5 +1308,9 @@ "listFilter_repeaters": "Repeaters", "listFilter_roomServers": "Room servers", "listFilter_unreadOnly": "Unread only", - "listFilter_newGroup": "New group" + "listFilter_newGroup": "New group", + + "contacts_clipboardEmpty": "Clipboard Is Empty.", + "contacts_invalidAdvertFormat": "Invalid Contact Data", + "contacts_contactAdded": "Contact has been added." } diff --git a/lib/screens/contacts_screen.dart b/lib/screens/contacts_screen.dart index d5d3377..89a07e0 100644 --- a/lib/screens/contacts_screen.dart +++ b/lib/screens/contacts_screen.dart @@ -106,6 +106,36 @@ class _ContactsScreenState extends State return; } + Future _contactImport() async { + final clipboardData = await Clipboard.getData('text/plain'); + if (clipboardData == null || clipboardData.text == null) { + 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)), + ); + return; + } + final hexString = text.substring('meshcore://'.length); + try { + final connector = Provider.of(context, listen: false); + final importContactFrame = buildImportContactFrame(hexString); + await connector.sendFrame(importContactFrame); + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar(content: Text(context.l10n.contacts_contactAdded)), + // ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.contacts_invalidAdvertFormat)), + ); + } + } + @override Widget build(BuildContext context) { final connector = context.watch(); @@ -166,21 +196,21 @@ class _ContactsScreenState extends State ), onTap: () => _contactExport(Uint8List.fromList([])), ), + PopupMenuItem( + child: Row( + children: [ + const Icon(Icons.paste), + const SizedBox(width: 8), + Text("Add Contact from Clipboard"), + ], + ), + onTap: () => _contactImport(), + ), ], icon: const Icon(Icons.connect_without_contact), ), PopupMenuButton( itemBuilder: (context) => [ - PopupMenuItem( - child: Row( - children: [ - const Icon(Icons.logout, color: Colors.red), - const SizedBox(width: 8), - const Text('Disconnect'), - ], - ), - onTap: () => _disconnect(context, connector), - ), PopupMenuItem( child: Row( children: [ @@ -194,6 +224,16 @@ class _ContactsScreenState extends State MaterialPageRoute(builder: (context) => const SettingsScreen()), ), ), + PopupMenuItem( + child: Row( + children: [ + const Icon(Icons.logout, color: Colors.red), + const SizedBox(width: 8), + const Text('Disconnect'), + ], + ), + onTap: () => _disconnect(context, connector), + ) ], icon: const Icon(Icons.more_vert), ),