diff --git a/app/src/main/java/com/bitchat/android/service/ConversationNotificationReceiver.kt b/app/src/main/java/com/bitchat/android/service/ConversationNotificationReceiver.kt index e6dbef2a..6feca2a7 100644 --- a/app/src/main/java/com/bitchat/android/service/ConversationNotificationReceiver.kt +++ b/app/src/main/java/com/bitchat/android/service/ConversationNotificationReceiver.kt @@ -40,6 +40,10 @@ class ConversationNotificationReceiver : BroadcastReceiver() { ?.trim() ?.takeIf(String::isNotEmpty) ?: return@launch + // A notification can outlive the process/service that posted it. Promote + // the mesh runtime before dispatch so Android keeps the transport alive + // after this short-lived receiver finishes. + MeshForegroundService.start(context.applicationContext) val mesh = MeshServiceHolder.getUnifiedOrCreate( context.applicationContext ) diff --git a/app/src/main/java/com/bitchat/android/services/AppStateStore.kt b/app/src/main/java/com/bitchat/android/services/AppStateStore.kt index fc4c439c..66633fee 100644 --- a/app/src/main/java/com/bitchat/android/services/AppStateStore.kt +++ b/app/src/main/java/com/bitchat/android/services/AppStateStore.kt @@ -384,8 +384,11 @@ object AppStateStore { } if (changed) { _privateMessages.value = map - conversationRepository?.updateDeliveryStatus(messageID, status) } + // Full histories are unloaded after a chat closes, so the message may only exist in + // SQLite. Always offer the update to the repository; it safely ignores unknown IDs + // and enforces the same monotonic status rules as the in-memory path. + conversationRepository?.updateDeliveryStatus(messageID, status) } } diff --git a/app/src/test/kotlin/com/bitchat/android/services/IncomingMessageAdmissionTest.kt b/app/src/test/kotlin/com/bitchat/android/services/IncomingMessageAdmissionTest.kt index f94a498e..94cdcec4 100644 --- a/app/src/test/kotlin/com/bitchat/android/services/IncomingMessageAdmissionTest.kt +++ b/app/src/test/kotlin/com/bitchat/android/services/IncomingMessageAdmissionTest.kt @@ -3,6 +3,7 @@ package com.bitchat.android.services import android.content.Context import androidx.test.core.app.ApplicationProvider import com.bitchat.android.model.BitchatMessage +import com.bitchat.android.model.DeliveryStatus import com.bitchat.android.ui.ChatState import com.bitchat.android.ui.DataManager import com.bitchat.android.ui.MessageManager @@ -99,6 +100,40 @@ class IncomingMessageAdmissionTest { ) } + @Test + fun `delivery receipt persists after older message is unloaded from memory`() { + val older = privateMessage(id = "older-outgoing").copy( + sender = "me", + senderPeerID = "self", + recipientNickname = "alice", + deliveryStatus = DeliveryStatus.Sent + ) + val latest = privateMessage(id = "latest-summary").copy(timestamp = Date(2L)) + assertTrue(AppStateStore.addPrivateMessage("peer-a", older, forceRead = true)) + assertTrue(IncomingMessageAdmission.admitToAppState(latest)) + runBlocking { repository.awaitPendingWrites() } + + AppStateStore.releasePrivateConversationHistory("peer-a") + assertEquals( + listOf(latest.id), + AppStateStore.privateMessages.value.getValue("peer-a").map { it.id } + ) + + val delivered = DeliveryStatus.Delivered(to = "alice", at = Date(3L)) + AppStateStore.updatePrivateMessageStatus(older.id, delivered) + runBlocking { repository.awaitPendingWrites() } + + val snapshot = runBlocking { + repository.loadConversationAndWait("peer-a") + } + assertEquals( + delivered, + snapshot?.chats?.getValue("peer-a") + ?.single { it.id == older.id } + ?.deliveryStatus + ) + } + @Test fun `public and channel messages preserve best effort admission`() { val public = BitchatMessage( diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts index e2d4abc1..819fc5cd 100644 --- a/wear/build.gradle.kts +++ b/wear/build.gradle.kts @@ -78,6 +78,7 @@ val sharedSourceIncludes = listOf( "com/bitchat/android/services/ContactDirectory.kt", "com/bitchat/android/services/ContactIdentityResolver.kt", "com/bitchat/android/services/ConversationRepository.kt", + "com/bitchat/android/services/ConversationStorageCipher.kt", "com/bitchat/android/services/PrivateMessageArrivalOrder.kt", "com/bitchat/android/services/SeenMessageStore.kt", "com/bitchat/android/services/VerificationService.kt",