diff --git a/lib/models/message.dart b/lib/models/message.dart index fa78dbb..d2cda26 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -116,7 +116,7 @@ class Message { return '${diff.inDays}d ago'; } - /// Get display name for sender + /// Get display name for sender (basic fallback without contact info) String get displaySender { if (senderName != null && senderName!.isNotEmpty) { return senderName!; @@ -130,6 +130,21 @@ class Message { return 'Unknown'; } + /// Get rich display name for sender using contact information + /// Returns emoji + display name if available, otherwise falls back to displaySender + String getRichDisplayName(dynamic contact) { + if (contact == null) return displaySender; + + // If contact has roleEmoji, use it with displayName + final roleEmoji = contact.roleEmoji; + if (roleEmoji != null && roleEmoji.isNotEmpty) { + return '$roleEmoji ${contact.displayName}'; + } + + // Otherwise just use advName or displayName + return contact.displayName ?? contact.advName ?? displaySender; + } + /// Convert to SAR marker if applicable SarMarker? toSarMarker() { if (!isSarMarker || sarMarkerType == null || sarGpsCoordinates == null) { diff --git a/lib/screens/messages_tab.dart b/lib/screens/messages_tab.dart index f1dc810..24a4c61 100644 --- a/lib/screens/messages_tab.dart +++ b/lib/screens/messages_tab.dart @@ -537,6 +537,26 @@ class _MessageBubble extends StatelessWidget { final selfPublicKey = connectionProvider.deviceInfo.publicKey; final isOwnMessage = message.isFromSelf(selfPublicKey); + // Look up contact information for rich display name + final contactsProvider = context.read(); + dynamic senderContact; + if (message.senderPublicKeyPrefix != null && !isOwnMessage) { + // Find contact by public key prefix (first 6 bytes) + final senderKeyHex = message.senderPublicKeyPrefix! + .sublist(0, message.senderPublicKeyPrefix!.length < 6 ? message.senderPublicKeyPrefix!.length : 6) + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join(''); + + senderContact = contactsProvider.contacts.where((c) { + return c.publicKeyHex.startsWith(senderKeyHex); + }).firstOrNull; + } + + // Get rich display name (with emoji if available) + final displayName = isOwnMessage + ? 'You' + : message.getRichDisplayName(senderContact); + // Debug: Log message details if (message.text.startsWith('S:')) { debugPrint('🎨 [MessageBubble] Rendering SAR message:'); @@ -622,7 +642,7 @@ class _MessageBubble extends StatelessWidget { const Icon(Icons.person, size: 16), const SizedBox(width: 4), Text( - isOwnMessage ? 'You' : message.displaySender, + displayName, style: Theme.of(context).textTheme.labelMedium?.copyWith( fontWeight: FontWeight.bold, color: isOwnMessage ? Theme.of(context).colorScheme.primary : null, diff --git a/lib/widgets/contacts/contact_tile.dart b/lib/widgets/contacts/contact_tile.dart index aedadeb..fcd71f5 100644 --- a/lib/widgets/contacts/contact_tile.dart +++ b/lib/widgets/contacts/contact_tile.dart @@ -9,6 +9,7 @@ import '../../providers/connection_provider.dart'; import '../../providers/map_provider.dart'; import 'direct_message_sheet.dart'; import 'room_login_sheet.dart'; +import '../../utils/toast_logger.dart'; class ContactTile extends StatelessWidget { final Contact contact; @@ -301,15 +302,11 @@ class ContactTile extends StatelessWidget { final hasPath = contact.hasPath; // Show initial notification reflecting the method being used - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - hasPath - ? 'Pinging ${contact.displayName} (direct via path)...' - : 'Pinging ${contact.displayName} (flooding - no path)...', - ), - duration: const Duration(seconds: 6), - ), + ToastLogger.info( + context, + hasPath + ? 'Pinging ${contact.displayName} (direct via path)...' + : 'Pinging ${contact.displayName} (flooding - no path)...', ); // Use smart ping with automatic fallback @@ -319,14 +316,9 @@ class ContactTile extends StatelessWidget { onRetryWithFlooding: () { // Called when retrying with flooding after direct timeout if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Direct ping timeout - retrying ${contact.displayName} with flooding...', - ), - duration: const Duration(seconds: 3), - backgroundColor: Colors.orange, - ), + ToastLogger.warning( + context, + 'Direct ping timeout - retrying ${contact.displayName} with flooding...', ); } }, @@ -334,17 +326,17 @@ class ContactTile extends StatelessWidget { // Show final result if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - result.success - ? 'Ping successful to ${contact.displayName}${result.retriedWithFlooding ? ' (via flooding fallback)' : ''}' - : 'Ping failed to ${contact.displayName} - no response received', - ), - duration: const Duration(seconds: 2), - backgroundColor: result.success ? Colors.green : Colors.red, - ), - ); + if (result.success) { + ToastLogger.success( + context, + 'Ping successful to ${contact.displayName}${result.retriedWithFlooding ? ' (via flooding fallback)' : ''}', + ); + } else { + ToastLogger.error( + context, + 'Ping failed to ${contact.displayName} - no response received', + ); + } } }, ), @@ -600,12 +592,7 @@ class ContactTile extends StatelessWidget { onPressed: () { final connectionProvider = context.read(); connectionProvider.requestTelemetry(contact.publicKey, zeroHop: true); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Requesting telemetry from ${contact.displayName}...'), - duration: const Duration(seconds: 2), - ), - ); + ToastLogger.info(context, 'Requesting telemetry from ${contact.displayName}...'); }, icon: const Icon(Icons.refresh, size: 18), label: const Text('Refresh'), @@ -665,12 +652,7 @@ class ContactTile extends StatelessWidget { child: OutlinedButton.icon( onPressed: () { connectionProvider.resetPath(contact.publicKey); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Path reset for ${contact.displayName}. Next message will find a new route.'), - duration: const Duration(seconds: 3), - ), - ); + ToastLogger.info(context, 'Path reset for ${contact.displayName}. Next message will find a new route.'); }, icon: const Icon(Icons.route), label: const Text('Reset Path (Re-route)'), diff --git a/lib/widgets/contacts/direct_message_sheet.dart b/lib/widgets/contacts/direct_message_sheet.dart index 2523288..4eaead4 100644 --- a/lib/widgets/contacts/direct_message_sheet.dart +++ b/lib/widgets/contacts/direct_message_sheet.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import '../../models/contact.dart'; import '../../providers/connection_provider.dart'; +import '../../utils/toast_logger.dart'; class DirectMessageSheet extends StatefulWidget { final Contact contact; @@ -46,12 +47,7 @@ class _DirectMessageSheetState extends State { if (!connectionProvider.deviceInfo.isConnected) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Not connected to device'), - backgroundColor: Colors.red, - ), - ); + ToastLogger.error(context, 'Not connected to device'); return; } @@ -69,21 +65,10 @@ class _DirectMessageSheetState extends State { if (!mounted) return; Navigator.pop(context); // Close the dialog - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Direct message sent to ${widget.contact.displayName}'), - backgroundColor: Colors.green, - duration: const Duration(seconds: 2), - ), - ); + ToastLogger.success(context, 'Direct message sent to ${widget.contact.displayName}'); } catch (e) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Failed to send: $e'), - backgroundColor: Colors.red, - ), - ); + ToastLogger.error(context, 'Failed to send: $e'); } }