diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml index 80df9d7..81a002b 100644 --- a/ios/fastlane/report.xml +++ b/ios/fastlane/report.xml @@ -5,22 +5,22 @@ - + - + - + - + diff --git a/lib/providers/app_provider.dart b/lib/providers/app_provider.dart index c29e267..81f5267 100644 --- a/lib/providers/app_provider.dart +++ b/lib/providers/app_provider.dart @@ -202,6 +202,21 @@ class AppProvider with ChangeNotifier { } } + String? _resolveContactNameForNotification(Uint8List? publicKey) { + if (publicKey == null || publicKey.isEmpty) return null; + + Contact? contact; + if (publicKey.length >= 32) { + contact = contactsProvider.findContactByKey(publicKey); + } + contact ??= publicKey.length >= 6 + ? contactsProvider.findContactByPrefix( + Uint8List.fromList(publicKey.sublist(0, 6)), + ) + : null; + return contact?.advName; + } + /// Load simple mode setting from shared preferences Future _loadSimpleMode() async { try { @@ -437,6 +452,10 @@ class AppProvider with ChangeNotifier { void _setupCallbacks() { // Monitor connection state changes to start/stop location tracking connectionProvider.addListener(_handleConnectionStateChange); + messagesProvider.resolveContactNameCallback = + _resolveContactNameForNotification; + messagesProvider.resolveChannelNameCallback = + channelsProvider.getChannelDisplayName; voiceProvider.sendRawPacketCallback = ({ diff --git a/lib/providers/messages_provider.dart b/lib/providers/messages_provider.dart index 28b8324..65e4e94 100644 --- a/lib/providers/messages_provider.dart +++ b/lib/providers/messages_provider.dart @@ -11,6 +11,7 @@ import '../services/notification_service.dart'; import '../utils/sar_message_parser.dart'; import '../utils/drawing_message_parser.dart'; import '../utils/voice_message_parser.dart'; +import '../utils/image_message_parser.dart'; import '../l10n/app_localizations.dart'; import 'helpers/message_retry_manager.dart'; @@ -79,6 +80,9 @@ class MessagesProvider with ChangeNotifier { Future Function({required Contact contact, required int failureStreak})? onDirectPathFailedCallback; + String? Function(Uint8List? publicKey)? resolveContactNameCallback; + String Function(int channelIdx)? resolveChannelNameCallback; + List get messages => List.unmodifiable(_messages); List get contactMessages => @@ -576,33 +580,31 @@ class MessagesProvider with ChangeNotifier { /// Trigger notification for regular message Future _triggerMessageNotification(Message message) async { try { - // Get sender name from message - final senderName = - message.senderName ?? message.senderKeyShort ?? 'Unknown'; - - // Determine if it's a channel message + final senderName = _resolveParticipantName( + publicKey: message.senderPublicKeyPrefix, + fallback: message.senderName ?? message.senderKeyShort, + ); final isChannelMessage = message.isChannelMessage; - - // Get channel name if available - String? channelName; - if (isChannelMessage) { - // You could map channelIdx to channel name here if needed - // For now, use "Public" for channel 0 - channelName = message.channelIdx == 0 - ? 'Public' - : 'Channel ${message.channelIdx}'; - } + final channelName = isChannelMessage + ? _resolveChannelName(message.channelIdx) + : null; + final messageText = _buildNotificationMessageText( + message, + senderName: senderName, + isChannelMessage: isChannelMessage, + channelName: channelName, + ); debugPrint('🔔 [MessagesProvider] Triggering message notification'); debugPrint(' Sender: $senderName'); debugPrint(' Type: ${isChannelMessage ? "Channel" : "Direct"}'); debugPrint( - ' Message: ${message.text.substring(0, message.text.length > 50 ? 50 : message.text.length)}...', + ' Message: ${messageText.substring(0, messageText.length > 50 ? 50 : messageText.length)}...', ); await _notificationService.showMessageNotification( senderName: senderName, - messageText: message.text, + messageText: messageText, isChannelMessage: isChannelMessage, channelName: channelName, localizations: _localizations, @@ -614,6 +616,78 @@ class MessagesProvider with ChangeNotifier { } } + String _resolveParticipantName({ + required Uint8List? publicKey, + String? fallback, + }) { + final resolved = resolveContactNameCallback?.call(publicKey)?.trim(); + if (resolved != null && resolved.isNotEmpty) { + return resolved; + } + final normalizedFallback = fallback?.trim(); + if (normalizedFallback != null && normalizedFallback.isNotEmpty) { + return normalizedFallback; + } + return 'Unknown'; + } + + String _resolveChannelName(int? channelIdx) { + final idx = channelIdx ?? 0; + final resolved = resolveChannelNameCallback?.call(idx).trim(); + if (resolved != null && resolved.isNotEmpty) { + return resolved; + } + return idx == 0 ? 'Public' : 'Channel $idx'; + } + + String _buildNotificationMessageText( + Message message, { + required String senderName, + required bool isChannelMessage, + String? channelName, + }) { + final voiceEnvelope = VoiceEnvelope.tryParseText(message.text); + if (voiceEnvelope != null) { + final seconds = (voiceEnvelope.durationMs / 1000).ceil(); + final route = isChannelMessage + ? 'Channel: ${channelName ?? _resolveChannelName(message.channelIdx)}' + : 'From: $senderName'; + return '$route\nVoice message - ${voiceEnvelope.mode.label} - ${seconds}s - ${voiceEnvelope.total} packets'; + } + + final imageEnvelope = ImageEnvelope.tryParse(message.text); + if (imageEnvelope != null) { + final route = isChannelMessage + ? 'Channel: ${channelName ?? _resolveChannelName(message.channelIdx)}' + : 'From: $senderName'; + return '$route\nImage - ${imageEnvelope.format.label} - ${imageEnvelope.width}x${imageEnvelope.height} - ${_formatBytes(imageEnvelope.sizeBytes)}'; + } + + if (!isChannelMessage && message.recipientPublicKey != null) { + final recipientName = _resolveParticipantName( + publicKey: message.recipientPublicKey, + fallback: null, + ); + if (recipientName != 'Unknown') { + return 'From: $senderName\nTo: $recipientName\n${message.text}'; + } + } + + if (isChannelMessage && channelName != null && channelName.isNotEmpty) { + return 'Channel: $channelName\n${message.text}'; + } + + return message.text; + } + + String _formatBytes(int bytes) { + if (bytes < 1024) return '$bytes B'; + final kib = bytes / 1024; + if (kib < 1024) return '${kib.toStringAsFixed(kib >= 10 ? 0 : 1)} KB'; + final mib = kib / 1024; + return '${mib.toStringAsFixed(mib >= 10 ? 0 : 1)} MB'; + } + /// Persist messages to storage (async, non-blocking) Future _persistMessages() async { try { diff --git a/pubspec.lock b/pubspec.lock index 65aff5b..3becaca 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: "direct main" description: name: audioplayers - sha256: "5441fa0ceb8807a5ad701199806510e56afde2b4913d9d17c2f19f2902cf0ae4" + sha256: a72dd459d1a48f61a6fb9c0134dba26597c9236af40639ff0eb70eb4e0baab70 url: "https://pub.dev" source: hosted - version: "6.5.1" + version: "6.6.0" audioplayers_android: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: audioplayers_darwin - sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333" + sha256: c994b3bb3a921e4904ac40e013fbc94488e824fd7c1de6326f549943b0b44a91 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.4.0" audioplayers_linux: dependency: transitive description: @@ -69,18 +69,18 @@ packages: dependency: transitive description: name: audioplayers_web - sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7" + sha256: faa8fa6587f996a6f604433b53af44c57a1407d4fe8dff5766cf63d6875e8de9 url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "5.2.0" audioplayers_windows: dependency: transitive description: name: audioplayers_windows - sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7" + sha256: bafff2b38b6f6d331887558ba6e0a01c9c208d9dbb3ad0005234db065122a734 url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "4.3.0" bluez: dependency: transitive description: @@ -851,7 +851,7 @@ packages: description: path: "." ref: main - resolved-ref: fb0a92a53b8e1ab23ffca50c2bffe819829843e6 + resolved-ref: cea66b5251135c7f9b84f15c0878d4c8af6e88e9 url: "https://github.com/dz0ny/meshcore_client.git" source: git version: "0.1.0"