diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt index 362bd923754..aa841dcb9c6 100644 --- a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt +++ b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt @@ -57,7 +57,7 @@ interface ConversationDAO { ) suspend fun updateConversationModifiedDate(qualifiedID: QualifiedIDEntity, date: Instant) - suspend fun updateConversationNotificationDate(qualifiedID: QualifiedIDEntity) + suspend fun updateConversationNotificationDate(qualifiedID: QualifiedIDEntity, date: Instant? = null) suspend fun updateConversationReadDate(conversationID: QualifiedIDEntity, date: Instant) suspend fun updateAllConversationsNotificationDate() suspend fun getAllConversations(): Flow> diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt index c28bb4ba726..b7e8afacff2 100644 --- a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt +++ b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt @@ -274,9 +274,13 @@ internal class ConversationDAOImpl internal constructor( } } - override suspend fun updateConversationNotificationDate(qualifiedID: QualifiedIDEntity) { + override suspend fun updateConversationNotificationDate(qualifiedID: QualifiedIDEntity, date: Instant?) { withContext(writeDispatcher.value) { - conversationQueries.updateConversationNotificationsDateWithTheLastMessage(qualifiedID) + if (date != null) { + conversationQueries.updateConversationNotificationsDate(date, qualifiedID) + } else { + conversationQueries.updateConversationNotificationsDateWithTheLastMessage(qualifiedID) + } } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt index dc22d4776ed..b9d409d861f 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt @@ -198,7 +198,7 @@ internal interface ConversationRepository { ): Either> suspend fun updateConversationGroupState(groupID: GroupID, groupState: GroupState): Either - suspend fun updateConversationNotificationDate(qualifiedID: QualifiedID): Either + suspend fun updateConversationNotificationDate(qualifiedID: QualifiedID, date: Instant? = null): Either suspend fun updateAllConversationsNotificationDate(): Either suspend fun updateConversationModifiedDate(qualifiedID: QualifiedID, date: Instant): Either suspend fun updateConversationReadDate(qualifiedID: QualifiedID, date: Instant): Either @@ -592,10 +592,11 @@ internal class ConversationDataSource internal constructor( } override suspend fun updateConversationNotificationDate( - qualifiedID: QualifiedID + qualifiedID: QualifiedID, + date: Instant?, ): Either = wrapStorageRequest { - conversationDAO.updateConversationNotificationDate(qualifiedID.toDao()) + conversationDAO.updateConversationNotificationDate(qualifiedID.toDao(), date) } override suspend fun updateAllConversationsNotificationDate(): Either = diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MarkMessagesAsNotifiedUseCase.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MarkMessagesAsNotifiedUseCase.kt index 29d6d46881e..792b0cdf77d 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MarkMessagesAsNotifiedUseCase.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/MarkMessagesAsNotifiedUseCase.kt @@ -22,6 +22,7 @@ import com.wire.kalium.common.error.StorageFailure import com.wire.kalium.logic.data.conversation.ConversationRepository import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.common.functional.fold +import kotlinx.datetime.Instant /** * Marks conversations in one or all conversations as notified, so the notifications for these messages won't show up again. @@ -39,8 +40,17 @@ public class MarkMessagesAsNotifiedUseCase internal constructor( when (conversationsToUpdate) { UpdateTarget.AllConversations -> conversationRepository.updateAllConversationsNotificationDate() - is UpdateTarget.SingleConversation -> - conversationRepository.updateConversationNotificationDate(conversationsToUpdate.conversationId) + is UpdateTarget.SingleConversation -> { + val notifiedDate = conversationsToUpdate.notifiedDate + if (notifiedDate != null) { + conversationRepository.updateConversationNotificationDate( + conversationsToUpdate.conversationId, + notifiedDate + ) + } else { + conversationRepository.updateConversationNotificationDate(conversationsToUpdate.conversationId) + } + } }.fold({ Result.Failure(it) }) { Result.Success } /** @@ -53,9 +63,17 @@ public class MarkMessagesAsNotifiedUseCase internal constructor( public data object AllConversations : UpdateTarget /** - * A specific conversation, represented by its [conversationId], should be marked as notified + * A specific conversation, represented by its [conversationId], should be marked as notified. + * @param conversationId The conversation to mark as notified + * @param notifiedDate The timestamp of the last notified message. When provided, this exact + * timestamp is used instead of looking up the latest message in the database. This prevents + * race conditions where new messages arrive between displaying notifications and marking them + * as notified. */ - public data class SingleConversation(val conversationId: ConversationId) : UpdateTarget + public data class SingleConversation( + val conversationId: ConversationId, + val notifiedDate: Instant? = null + ) : UpdateTarget } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/GetNotificationsUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/GetNotificationsUseCaseTest.kt index 10e844a3cad..5f62708db73 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/GetNotificationsUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/GetNotificationsUseCaseTest.kt @@ -347,7 +347,7 @@ class GetNotificationsUseCaseTest { ) }.also { coEvery { - conversationRepository.updateConversationNotificationDate(any()) + conversationRepository.updateConversationNotificationDate(any(), any()) }.returns(Either.Right(Unit)) coEvery { diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MarkMessagesAsNotifiedUseCaseTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MarkMessagesAsNotifiedUseCaseTest.kt index 6bce52ece12..dc87d5cfb82 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MarkMessagesAsNotifiedUseCaseTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/message/MarkMessagesAsNotifiedUseCaseTest.kt @@ -48,7 +48,7 @@ class MarkMessagesAsNotifiedUseCaseTest { }.wasInvoked(exactly = once) coVerify { - arrangement.conversationRepository.updateConversationNotificationDate(any()) + arrangement.conversationRepository.updateConversationNotificationDate(any(), any()) }.wasNotInvoked() assertEquals(result, Result.Success) @@ -63,7 +63,7 @@ class MarkMessagesAsNotifiedUseCaseTest { val result = markMessagesAsNotified(UpdateTarget.SingleConversation(CONVERSATION_ID)) coVerify { - arrangement.conversationRepository.updateConversationNotificationDate(eq(CONVERSATION_ID)) + arrangement.conversationRepository.updateConversationNotificationDate(eq(CONVERSATION_ID), any()) }.wasInvoked(exactly = once) coVerify { @@ -111,7 +111,7 @@ class MarkMessagesAsNotifiedUseCaseTest { suspend fun withUpdatingOneConversationReturning(result: Either) = apply { coEvery { - conversationRepository.updateConversationNotificationDate(any()) + conversationRepository.updateConversationNotificationDate(any(), any()) }.returns(result) }