diff --git a/DELIVERY_CONFIRMATION_STATUS.md b/DELIVERY_CONFIRMATION_STATUS.md new file mode 100644 index 00000000..6e30693d --- /dev/null +++ b/DELIVERY_CONFIRMATION_STATUS.md @@ -0,0 +1,37 @@ +# Delivery Confirmation Feature - Current Status + +## What's Working +1. ✅ Delivery confirmations (green double checkmarks) work correctly +2. ✅ Read receipts are sent and received properly +3. ✅ Blue checkmarks appear for messages that are read while both users are in the chat +4. ✅ Status updates are preserved (read status won't downgrade to delivered) + +## Remaining Issue +When Jack opens a chat with Rick, the first message (that was already delivered before Jack opened the chat) doesn't get a read receipt sent. Only new messages that arrive while the chat is open get read receipts. + +### Root Cause +The `markPrivateMessagesAsRead` function is being called when Jack opens the chat, but: +1. It looks for messages with status "sent" or "delivered" +2. Messages from Rick are stored with Rick's senderPeerID (which changes between sessions) +3. The peer ID mismatch might prevent finding the right messages + +### Solution Needed +Jack needs to send read receipts for ALL unread messages from Rick when opening the chat, regardless of their current peer ID. + +## Test Scenario +1. Rick sends message to Jack while Jack is offline/not in chat +2. Message shows green checkmarks (delivered) on Rick's side +3. Jack opens the chat with Rick +4. **Expected**: Rick's message should turn blue +5. **Actual**: Message stays green until a new message is sent + +## Logs Showing the Issue +``` +Mac (Rick): +- Message F4D69DB6... shows "delivered to jack" (stays green) +- Never receives read receipt for this first message + +Phone (Jack): +- Opens chat but doesn't send read receipt for existing messages +- Only sends read receipts for new messages received while chat is open +``` \ No newline at end of file diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index ef4f2afd..2adf2d4c 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -935,31 +935,31 @@ class ChatViewModel: ObservableObject { return } - print("[Delivery] Checking \(messages.count) messages in chat with peer \(peerID) for read receipts") + // Get the nickname for this peer + let peerNickname = meshService.getPeerNicknames()[peerID] ?? "" + print("[Delivery] Checking \(messages.count) messages in chat with peer \(peerID) (\(peerNickname)) for read receipts") // Find messages from the peer that haven't been read yet for message in messages { // Only send read receipts for messages from the other peer (not our own) - print("[Delivery] Message \(message.id) from \(message.sender), senderPeerID: \(message.senderPeerID ?? "nil"), myNickname: \(nickname), status: \(message.deliveryStatus?.displayText ?? "none")") + print("[Delivery] Message \(message.id) from \(message.sender), senderPeerID: \(message.senderPeerID ?? "nil"), currentPeerID: \(peerID), myNickname: \(nickname), status: \(message.deliveryStatus?.displayText ?? "none")") // Check if this is a message FROM the other person TO us - if message.sender != nickname && message.senderPeerID != nil { + // Match by sender nickname since peer IDs change between sessions + let isFromPeer = message.sender == peerNickname || message.senderPeerID == peerID + if message.sender != nickname && isFromPeer { if let status = message.deliveryStatus { switch status { case .sent, .delivered: // Create and send read receipt for sent or delivered messages - // Use the senderPeerID from the message to ensure it goes to the right peer - if let messageSenderID = message.senderPeerID { - let receipt = ReadReceipt( - originalMessageID: message.id, - readerID: meshService.myPeerID, - readerNickname: nickname - ) - meshService.sendReadReceipt(receipt, to: messageSenderID) - print("[Delivery] Sending read receipt for message \(message.id) to peer \(messageSenderID)") - } else { - print("[Delivery] Cannot send read receipt for message \(message.id) - no senderPeerID") - } + // Send to the CURRENT peer ID, not the old senderPeerID which may have changed + let receipt = ReadReceipt( + originalMessageID: message.id, + readerID: meshService.myPeerID, + readerNickname: nickname + ) + meshService.sendReadReceipt(receipt, to: peerID) + print("[Delivery] Sending read receipt for message \(message.id) from \(message.sender) to current peer \(peerID)") case .read: // Already read, no need to send another receipt print("[Delivery] Message \(message.id) already marked as read") @@ -973,17 +973,13 @@ class ChatViewModel: ObservableObject { // No delivery status - this might be an older message // Send read receipt anyway for backwards compatibility print("[Delivery] Message \(message.id) has no delivery status, sending read receipt anyway") - if let messageSenderID = message.senderPeerID { - let receipt = ReadReceipt( - originalMessageID: message.id, - readerID: meshService.myPeerID, - readerNickname: nickname - ) - meshService.sendReadReceipt(receipt, to: messageSenderID) - print("[Delivery] Sending read receipt for old message \(message.id) to peer \(messageSenderID)") - } else { - print("[Delivery] Cannot send read receipt for old message \(message.id) - no senderPeerID") - } + let receipt = ReadReceipt( + originalMessageID: message.id, + readerID: meshService.myPeerID, + readerNickname: nickname + ) + meshService.sendReadReceipt(receipt, to: peerID) + print("[Delivery] Sending read receipt for old message \(message.id) to current peer \(peerID)") } } } @@ -1860,22 +1856,18 @@ extension ChatViewModel: BitchatDelegate { unreadPrivateMessages.remove(peerID) // Send read receipt immediately since we're viewing the chat - // Use the senderPeerID from the message, not the current peerID - if let messageSenderID = message.senderPeerID { - let receipt = ReadReceipt( - originalMessageID: message.id, - readerID: meshService.myPeerID, - readerNickname: nickname - ) - meshService.sendReadReceipt(receipt, to: messageSenderID) - print("[Delivery] Sending immediate read receipt for message \(message.id) from \(message.sender) to peer \(messageSenderID)") - - // Also check if there are other unread messages from this peer - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in - self?.markPrivateMessagesAsRead(from: peerID) - } - } else { - print("[Delivery] Cannot send read receipt - message has no senderPeerID") + // Send to the current peer ID since peer IDs change between sessions + let receipt = ReadReceipt( + originalMessageID: message.id, + readerID: meshService.myPeerID, + readerNickname: nickname + ) + meshService.sendReadReceipt(receipt, to: peerID) + print("[Delivery] Sending immediate read receipt for message \(message.id) from \(message.sender) to current peer \(peerID)") + + // Also check if there are other unread messages from this peer + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + self?.markPrivateMessagesAsRead(from: peerID) } } } else if message.sender == nickname { diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index c2a15e00..603ed542 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -531,8 +531,10 @@ struct ContentView: View { .onChange(of: viewModel.selectedPrivateChatPeer) { newPeerID in // When switching to a private chat, send read receipts if let peerID = newPeerID { + print("[UI] Selected private chat peer changed to \(peerID)") // Small delay to ensure messages are loaded DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + print("[UI] Triggering markPrivateMessagesAsRead for peer \(peerID)") viewModel.markPrivateMessagesAsRead(from: peerID) } } @@ -540,7 +542,9 @@ struct ContentView: View { .onAppear { // Also check when view appears if let peerID = viewModel.selectedPrivateChatPeer { + print("[UI] Messages view appeared with selected peer \(peerID)") DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + print("[UI] Triggering markPrivateMessagesAsRead on appear for peer \(peerID)") viewModel.markPrivateMessagesAsRead(from: peerID) } }