Refactor contact handling and enhance UI with new advert options and localized strings

This commit is contained in:
Winston Lowe
2026-01-28 11:04:34 -08:00
parent d0c8fab6fb
commit 42115bf200
5 changed files with 52 additions and 37 deletions
-7
View File
@@ -1680,12 +1680,6 @@ class MeshCoreConnector extends ChangeNotifier {
_isLoadingContacts = true; _isLoadingContacts = true;
notifyListeners(); notifyListeners();
break; break;
case pushCodeNewAdvert:
debugPrint('Got NEW_ADVERT');
_handleContact(frame);
notifyListeners();
unawaited(_persistContacts());
break;
case respCodeContact: case respCodeContact:
debugPrint('Got CONTACT'); debugPrint('Got CONTACT');
_handleContact(frame); _handleContact(frame);
@@ -1743,7 +1737,6 @@ class MeshCoreConnector extends ChangeNotifier {
break; break;
case respCodeCustomVars: case respCodeCustomVars:
_handleCustomVars(frame); _handleCustomVars(frame);
break;
default: default:
debugPrint('Unknown frame code: $code'); debugPrint('Unknown frame code: $code');
} }
+21
View File
@@ -18,6 +18,10 @@ class BufferReader {
return data; return data;
} }
void skipBytes(int count) {
_pointer += count;
}
Uint8List readRemainingBytes() => readBytes(remaining); Uint8List readRemainingBytes() => readBytes(remaining);
String readString() => String readString() =>
@@ -135,6 +139,7 @@ const int cmdSendStatusReq = 27;
const int cmdGetContactByKey = 30; const int cmdGetContactByKey = 30;
const int cmdGetChannel = 31; const int cmdGetChannel = 31;
const int cmdSetChannel = 32; const int cmdSetChannel = 32;
const int cmdSendTracePath = 36;
const int cmdGetRadioSettings = 57; const int cmdGetRadioSettings = 57;
const int cmdGetTelemetryReq = 39; const int cmdGetTelemetryReq = 39;
const int cmdGetCustomVar = 40; const int cmdGetCustomVar = 40;
@@ -185,6 +190,7 @@ const int pushCodeLoginSuccess = 0x85;
const int pushCodeLoginFail = 0x86; const int pushCodeLoginFail = 0x86;
const int pushCodeStatusResponse = 0x87; const int pushCodeStatusResponse = 0x87;
const int pushCodeLogRxData = 0x88; const int pushCodeLogRxData = 0x88;
const int pushCodeTraceData = 0x89;
const int pushCodeNewAdvert = 0x8A; const int pushCodeNewAdvert = 0x8A;
const int pushCodeTelemetryResponse = 0x8B; const int pushCodeTelemetryResponse = 0x8B;
const int pushCodeBinaryResponse = 0x8C; const int pushCodeBinaryResponse = 0x8C;
@@ -718,6 +724,21 @@ Uint8List buildSendBinaryReq(Uint8List repeaterPubKey, {Uint8List? payload}) {
return writer.toBytes(); return writer.toBytes();
} }
//Build a trace request frame
//[cmd][tag x4][auth x4][flag][payload]
Uint8List buildTraceReq(int tag, int auth, int flag, {Uint8List? payload})
{
final writer = BufferWriter();
writer.writeByte(cmdSendTracePath);
writer.writeUInt32LE(tag);
writer.writeUInt32LE(auth);
writer.writeByte(flag);
if (payload != null && payload.isNotEmpty) {
writer.writeBytes(payload);
}
return writer.toBytes();
}
// Build a export contact frame // Build a export contact frame
// [cmd][pub_key x32 / if empty exports your contact info] // [cmd][pub_key x32 / if empty exports your contact info]
Uint8List buildExportContactFrame(Uint8List pubKey) { Uint8List buildExportContactFrame(Uint8List pubKey) {
+5 -1
View File
@@ -1327,5 +1327,9 @@
"contacts_clipboardEmpty": "Clipboard Is Empty.", "contacts_clipboardEmpty": "Clipboard Is Empty.",
"contacts_invalidAdvertFormat": "Invalid Contact Data", "contacts_invalidAdvertFormat": "Invalid Contact Data",
"contacts_contactImported": "Contact has been Imported.", "contacts_contactImported": "Contact has been Imported.",
"contacts_contactImportFailed": "Contact Failed to Imported." "contacts_contactImportFailed": "Contact Failed to Imported.",
"contacts_zeroHopAdvert":"Zero Hop Advert",
"contacts_floodAdvert":"Flood Advert",
"contacts_copyAdvertToClipboard":"Copy Advert to Clipboard",
"contacts_addContactFromClipboard":"Add Contact from Clipboard"
} }
+4
View File
@@ -102,6 +102,10 @@ class Contact {
return parts.join(','); return parts.join(',');
} }
String get shortPubKeyHex {
return "<${publicKeyHex.substring(0, 8)}...${publicKeyHex.substring(publicKeyHex.length - 8)}>";
}
Uint8List get _pathBytesForDisplay { Uint8List get _pathBytesForDisplay {
if (pathOverride != null) { if (pathOverride != null) {
if (pathOverride! < 0) return Uint8List(0); if (pathOverride! < 0) return Uint8List(0);
+22 -29
View File
@@ -177,7 +177,7 @@ class _ContactsScreenState extends State<ContactsScreen>
children: [ children: [
const Icon(Icons.connect_without_contact), const Icon(Icons.connect_without_contact),
const SizedBox(width: 8), const SizedBox(width: 8),
Text("Zero Hop Advert"), Text(context.l10n.contacts_zeroHopAdvert),
], ],
), ),
onTap: () => { onTap: () => {
@@ -192,7 +192,7 @@ class _ContactsScreenState extends State<ContactsScreen>
children: [ children: [
const Icon(Icons.cell_tower), const Icon(Icons.cell_tower),
const SizedBox(width: 8), const SizedBox(width: 8),
Text("Flood Advert"), Text(context.l10n.contacts_floodAdvert),
], ],
), ),
onTap: () => { onTap: () => {
@@ -207,7 +207,7 @@ class _ContactsScreenState extends State<ContactsScreen>
children: [ children: [
const Icon(Icons.copy), const Icon(Icons.copy),
const SizedBox(width: 8), const SizedBox(width: 8),
Text("Copy Advert to Clipboard"), Text(context.l10n.contacts_copyAdvertToClipboard),
], ],
), ),
onTap: () => _contactExport(Uint8List.fromList([])), onTap: () => _contactExport(Uint8List.fromList([])),
@@ -217,7 +217,7 @@ class _ContactsScreenState extends State<ContactsScreen>
children: [ children: [
const Icon(Icons.paste), const Icon(Icons.paste),
const SizedBox(width: 8), const SizedBox(width: 8),
Text("Add Contact from Clipboard"), Text(context.l10n.contacts_addContactFromClipboard),
], ],
), ),
onTap: () => _contactImport(), onTap: () => _contactImport(),
@@ -227,12 +227,22 @@ class _ContactsScreenState extends State<ContactsScreen>
), ),
PopupMenuButton( PopupMenuButton(
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.logout, color: Colors.red),
const SizedBox(width: 8),
Text(context.l10n.common_disconnect),
],
),
onTap: () => _disconnect(context, connector),
),
PopupMenuItem( PopupMenuItem(
child: Row( child: Row(
children: [ children: [
const Icon(Icons.settings), const Icon(Icons.settings),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text('Settings'), Text(context.l10n.settings_title),
], ],
), ),
onTap: () => Navigator.push( onTap: () => Navigator.push(
@@ -240,16 +250,6 @@ class _ContactsScreenState extends State<ContactsScreen>
MaterialPageRoute(builder: (context) => const SettingsScreen()), 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), icon: const Icon(Icons.more_vert),
), ),
@@ -930,7 +930,7 @@ class _ContactsScreenState extends State<ContactsScreen>
_showRoomLogin(context, contact, RoomLoginDestination.management); _showRoomLogin(context, contact, RoomLoginDestination.management);
}, },
), ),
] else ] else ...[
ListTile( ListTile(
leading: const Icon(Icons.chat), leading: const Icon(Icons.chat),
title: Text(context.l10n.contacts_openChat), title: Text(context.l10n.contacts_openChat),
@@ -1005,17 +1005,16 @@ class _ContactTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final shotPublicKey =
"<${contact.publicKeyHex.substring(0, 8)}...${contact.publicKeyHex.substring(contact.publicKeyHex.length - 8)}>";
return ListTile( return ListTile(
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: _getTypeColor(contact.type), backgroundColor: _getTypeColor(contact.type),
child: _buildContactAvatar(contact), child: _buildContactAvatar(contact),
), ),
title: Text(contact.name), title: Text(contact.name),
subtitle: Text( subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
'${contact.typeLabel}\n${contact.shortPubKeyHex}', Text(contact.pathLabel),
), Text(contact.shortPubKeyHex, style: TextStyle(fontSize: 12))
],),
// Clamp text scaling in trailing section to prevent overflow while // Clamp text scaling in trailing section to prevent overflow while
// maintaining accessibility. Primary content (title/subtitle) scales normally. // maintaining accessibility. Primary content (title/subtitle) scales normally.
trailing: MediaQuery( trailing: MediaQuery(
@@ -1039,21 +1038,15 @@ class _ContactTile extends StatelessWidget {
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(contact.pathLabel,
style: TextStyle(fontSize: 12, color: Colors.grey[600])),
if (contact.hasLocation) if (contact.hasLocation)
Icon(Icons.location_on, size: 14, color: Colors.grey[400]), Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
], ],
), ),
Text(
_formatLastSeen(context, lastSeen),
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
if (contact.hasLocation)
Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
], ],
), ),
), ),
onTap: onTap,
onLongPress: onLongPress,
); );
} }