From 823a3c50f7c103f7f0ca857cb5ab6053bbd2260c Mon Sep 17 00:00:00 2001 From: Janez T Date: Wed, 15 Oct 2025 10:56:52 +0200 Subject: [PATCH] feat: Enhance message handling with recipient public key and delivery status for improved retry support --- lib/screens/messages_tab.dart | 46 ++++++++++++++++++----- lib/services/message_storage_service.dart | 25 ++++++++++++ 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/lib/screens/messages_tab.dart b/lib/screens/messages_tab.dart index ff46bdb..21e28dd 100644 --- a/lib/screens/messages_tab.dart +++ b/lib/screens/messages_tab.dart @@ -170,7 +170,7 @@ class _MessagesTabState extends State { final devicePublicKey = connectionProvider.deviceInfo.publicKey; final senderPublicKeyPrefix = devicePublicKey?.sublist(0, 6); - // Create sent message object + // Create sent message object with recipient public key for retry support final sentMessage = Message( id: messageId, messageType: MessageType.contact, @@ -181,6 +181,7 @@ class _MessagesTabState extends State { text: fullMessage, receivedAt: DateTime.now(), deliveryStatus: MessageDeliveryStatus.sending, + recipientPublicKey: roomPublicKey, // Store recipient for retry // SAR marker data is automatically added by SarMessageParser.enhanceMessage in MessagesProvider ); @@ -408,15 +409,42 @@ class _MessageBubble extends StatelessWidget { // Resend the message if (failedMessage.messageType == MessageType.contact) { - // Direct message retry - NOT YET IMPLEMENTED - // Would need to look up contact's full public key by senderKeyShort - messagesProvider.markMessageFailed(retryMessageId); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Direct message retry not yet implemented'), - backgroundColor: Colors.orange, - ), + // Direct message retry (for SAR markers sent to rooms) + if (failedMessage.recipientPublicKey == null) { + messagesProvider.markMessageFailed(retryMessageId); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Cannot retry: recipient information missing'), + backgroundColor: Colors.red, + ), + ); + return; + } + + // Resend to the same room + final sentSuccessfully = await connectionProvider.sendTextMessage( + contactPublicKey: failedMessage.recipientPublicKey!, + text: failedMessage.text, + messageId: retryMessageId, ); + + if (!sentSuccessfully) { + messagesProvider.markMessageFailed(retryMessageId); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Failed to resend message'), + backgroundColor: Colors.red, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Retrying message...'), + backgroundColor: Colors.orange, + duration: Duration(seconds: 2), + ), + ); + } } else if (failedMessage.messageType == MessageType.channel) { // Channel message retry await connectionProvider.sendChannelMessage( diff --git a/lib/services/message_storage_service.dart b/lib/services/message_storage_service.dart index cbd5db8..e8d34d8 100644 --- a/lib/services/message_storage_service.dart +++ b/lib/services/message_storage_service.dart @@ -120,6 +120,14 @@ class MessageStorageService { 'sarGpsLon': message.sarGpsCoordinates?.longitude, 'receivedAtMillis': message.receivedAt.millisecondsSinceEpoch, 'senderName': message.senderName, + 'deliveryStatus': message.deliveryStatus.name, + 'expectedAckTag': message.expectedAckTag, + 'suggestedTimeoutMs': message.suggestedTimeoutMs, + 'roundTripTimeMs': message.roundTripTimeMs, + 'deliveredAtMillis': message.deliveredAt?.millisecondsSinceEpoch, + 'recipientPublicKey': message.recipientPublicKey != null + ? base64Encode(message.recipientPublicKey!) + : null, }; } @@ -155,6 +163,23 @@ class MessageStorageService { receivedAt: DateTime.fromMillisecondsSinceEpoch( json['receivedAtMillis'] as int), senderName: json['senderName'] as String?, + deliveryStatus: json['deliveryStatus'] != null + ? MessageDeliveryStatus.values.firstWhere( + (e) => e.name == json['deliveryStatus'], + orElse: () => MessageDeliveryStatus.received, + ) + : MessageDeliveryStatus.received, + expectedAckTag: json['expectedAckTag'] as int?, + suggestedTimeoutMs: json['suggestedTimeoutMs'] as int?, + roundTripTimeMs: json['roundTripTimeMs'] as int?, + deliveredAt: json['deliveredAtMillis'] != null + ? DateTime.fromMillisecondsSinceEpoch( + json['deliveredAtMillis'] as int) + : null, + recipientPublicKey: json['recipientPublicKey'] != null + ? Uint8List.fromList( + base64Decode(json['recipientPublicKey'] as String)) + : null, ); } catch (e) { print('❌ [MessageStorage] Error parsing message from JSON: $e');