diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9c0b9df402..3d4c579057 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -68,7 +68,7 @@ material = "1.13.0" mockk = "1.14.6" paparazzi = "1.3.5" proton-core = "34.1.0" -proton-rust-core = "0.160.4" +proton-rust-core = "0.160.5" robolectric = "4.15.1" # 4.16.0 requires Java 21 kotlinpoet-ksp = "2.2.0" leakcanary = "2.14" diff --git a/mail-conversation/data/src/main/kotlin/ch/protonmail/android/mailconversation/data/local/RustConversationDetailQueryImpl.kt b/mail-conversation/data/src/main/kotlin/ch/protonmail/android/mailconversation/data/local/RustConversationDetailQueryImpl.kt index c8c5b4a9fc..36cf8a9e46 100644 --- a/mail-conversation/data/src/main/kotlin/ch/protonmail/android/mailconversation/data/local/RustConversationDetailQueryImpl.kt +++ b/mail-conversation/data/src/main/kotlin/ch/protonmail/android/mailconversation/data/local/RustConversationDetailQueryImpl.kt @@ -135,7 +135,7 @@ class RustConversationDetailQueryImpl @Inject constructor( conversationWithMessagesMutableFlow.value = conversationEither.map { LocalConversationWithMessages( conversation = it.conversation, - messages = LocalConversationMessages(it.messageIdToOpen, it.messages) + messages = LocalConversationMessages(it.focusedMessageId, it.messages) ) } currentConversationId = conversationId @@ -175,7 +175,7 @@ class RustConversationDetailQueryImpl @Inject constructor( conversationWithMessagesMutableFlow.value = conversationEither.map { LocalConversationWithMessages( conversation = it.conversation, - messages = LocalConversationMessages(it.messageIdToOpen, it.messages) + messages = LocalConversationMessages(it.focusedMessageId, it.messages) ) } diff --git a/mail-conversation/data/src/main/kotlin/ch/protonmail/android/mailconversation/data/mapper/ConversationMapper.kt b/mail-conversation/data/src/main/kotlin/ch/protonmail/android/mailconversation/data/mapper/ConversationMapper.kt index 3339c7db4f..62221fe68c 100644 --- a/mail-conversation/data/src/main/kotlin/ch/protonmail/android/mailconversation/data/mapper/ConversationMapper.kt +++ b/mail-conversation/data/src/main/kotlin/ch/protonmail/android/mailconversation/data/mapper/ConversationMapper.kt @@ -19,9 +19,7 @@ package ch.protonmail.android.mailconversation.data.mapper import arrow.core.Either -import arrow.core.left import arrow.core.right -import arrow.core.toNonEmptyListOrNull import ch.protonmail.android.mailattachments.data.mapper.getCalendarAttachmentCount import ch.protonmail.android.mailattachments.data.mapper.toAttachmentMetadata import ch.protonmail.android.mailattachments.domain.model.AttachmentCount @@ -75,12 +73,11 @@ private fun LocalConversationId.toConversationId(): ConversationId = Conversatio fun LocalConversationMessages.toConversationMessagesWithMessageToOpen(): Either { - val messages = messages.toNonEmptyListOrNull()?.map { it.toMessage() } - ?: return ConversationError.ConvoWithNoMessages.left() + val messages = messages.toList().map { it.toMessage() } return ConversationMessages( messages = messages, - messageIdToOpen = messageIdToOpen.toMessageId() + messageIdToOpen = messageIdToOpen?.toMessageId() ).right() } diff --git a/mail-conversation/data/src/test/kotlin/ch/protonmail/android/mailconversation/data/local/RustConversationDetailQueryImplTest.kt b/mail-conversation/data/src/test/kotlin/ch/protonmail/android/mailconversation/data/local/RustConversationDetailQueryImplTest.kt index 3a3ff4b076..a2cc90815e 100644 --- a/mail-conversation/data/src/test/kotlin/ch/protonmail/android/mailconversation/data/local/RustConversationDetailQueryImplTest.kt +++ b/mail-conversation/data/src/test/kotlin/ch/protonmail/android/mailconversation/data/local/RustConversationDetailQueryImplTest.kt @@ -87,7 +87,7 @@ internal class RustConversationDetailQueryImplTest { val watcherMock = mockk { every { this@mockk.conversation } returns expectedConversation every { this@mockk.messages } returns expectedMessages.messages - every { this@mockk.messageIdToOpen } returns messageToOpen + every { this@mockk.focusedMessageId } returns messageToOpen every { this@mockk.handle } returns mockk { every { disconnect() } returns Unit } @@ -102,7 +102,7 @@ internal class RustConversationDetailQueryImplTest { } returns watcherMock.right() coEvery { getRustConversation(mailbox, conversationId, showAll) - } returns ConversationAndMessages(expectedConversation, messageToOpen, expectedMessages.messages).right() + } returns ConversationAndMessages(expectedConversation, expectedMessages.messages, messageToOpen).right() // When rustConversationQuery.observeConversationWithMessages(userId, conversationId, localLabelId, entryPoint, showAll) @@ -130,7 +130,7 @@ internal class RustConversationDetailQueryImplTest { val watcherMock = mockk { every { this@mockk.conversation } returns expectedConversation every { this@mockk.messages } returns expectedMessages.messages - every { this@mockk.messageIdToOpen } returns messageToOpen + every { this@mockk.focusedMessageId } returns messageToOpen every { this@mockk.handle } returns mockk { every { disconnect() } returns Unit } @@ -145,7 +145,7 @@ internal class RustConversationDetailQueryImplTest { } returns watcherMock.right() coEvery { getRustConversation(mailbox, conversationId, showAll) - } returns ConversationAndMessages(expectedConversation, messageToOpen, messages).right() + } returns ConversationAndMessages(expectedConversation, messages, messageToOpen).right() rustConversationQuery.observeConversationWithMessages(userId, conversationId, localLabelId, entryPoint, showAll) .test { skipItems(1) @@ -155,13 +155,13 @@ internal class RustConversationDetailQueryImplTest { val updatedMessages = expectedMessages.copy(messageIdToOpen = updatedMessageToOpen) every { watcherMock.conversation } returns updatedConversation every { watcherMock.messages } returns updatedMessages.messages - every { watcherMock.messageIdToOpen } returns messageToOpen + every { watcherMock.focusedMessageId } returns messageToOpen coEvery { getRustConversation(mailbox, conversationId, showAll) } returns ConversationAndMessages( updatedConversation, - updatedMessageToOpen, - updatedMessages.messages + updatedMessages.messages, + updatedMessageToOpen ).right() // When @@ -191,7 +191,7 @@ internal class RustConversationDetailQueryImplTest { val watcherMock = mockk { every { conversation } returns expectedConversation every { messages } returns expectedMessages.messages - every { messageIdToOpen } returns messageToOpen + every { focusedMessageId } returns messageToOpen every { handle } returns mockk { every { disconnect() } returns Unit } @@ -205,7 +205,7 @@ internal class RustConversationDetailQueryImplTest { } returns watcherMock.right() coEvery { getRustConversation(mailbox, conversationId, showAll) - } returns ConversationAndMessages(expectedConversation, messageToOpen, expectedMessages.messages).right() + } returns ConversationAndMessages(expectedConversation, expectedMessages.messages, messageToOpen).right() rustConversationQuery.observeConversationWithMessages(userId, conversationId, localLabelId, entryPoint, showAll) .test { @@ -249,7 +249,7 @@ internal class RustConversationDetailQueryImplTest { val watcherMock1 = mockk { every { conversation } returns expectedConversation1 every { messages } returns expectedMessages.messages - every { messageIdToOpen } returns messageToOpen + every { focusedMessageId } returns messageToOpen every { handle } returns mockk { every { disconnect() } returns Unit } @@ -257,7 +257,7 @@ internal class RustConversationDetailQueryImplTest { val watcherMock2 = mockk { every { conversation } returns expectedConversation2 every { messages } returns expectedMessages.messages - every { messageIdToOpen } returns messageToOpen + every { focusedMessageId } returns messageToOpen every { handle } returns mockk { every { disconnect() } returns Unit } @@ -276,10 +276,10 @@ internal class RustConversationDetailQueryImplTest { } returns watcherMock2.right() coEvery { getRustConversation(mailbox, conversationId1, showAll) - } returns ConversationAndMessages(expectedConversation1, messageToOpen, expectedMessages.messages).right() + } returns ConversationAndMessages(expectedConversation1, expectedMessages.messages, messageToOpen).right() coEvery { getRustConversation(newMailbox, conversationId2, showAll) - } returns ConversationAndMessages(expectedConversation2, messageToOpen, expectedMessages.messages).right() + } returns ConversationAndMessages(expectedConversation2, expectedMessages.messages, messageToOpen).right() // When - First conversation val job1 = launch { @@ -339,7 +339,7 @@ internal class RustConversationDetailQueryImplTest { val watcherMock1 = mockk { every { conversation } returns expectedConversation1 every { messages } returns expectedMessages.messages - every { messageIdToOpen } returns messageToOpen + every { focusedMessageId } returns messageToOpen every { handle } returns mockk { every { disconnect() } returns Unit } @@ -347,7 +347,7 @@ internal class RustConversationDetailQueryImplTest { val watcherMock2 = mockk { every { conversation } returns expectedConversation2 every { messages } returns expectedMessages.messages - every { messageIdToOpen } returns messageToOpen + every { focusedMessageId } returns messageToOpen every { handle } returns mockk { every { disconnect() } returns Unit } @@ -365,10 +365,10 @@ internal class RustConversationDetailQueryImplTest { } returns watcherMock2.right() coEvery { getRustConversation(mailbox, conversationId1, showAll) - } returns ConversationAndMessages(expectedConversation1, messageToOpen, expectedMessages.messages).right() + } returns ConversationAndMessages(expectedConversation1, expectedMessages.messages, messageToOpen).right() coEvery { getRustConversation(mailbox, conversationId2, showAll) - } returns ConversationAndMessages(expectedConversation2, messageToOpen, expectedMessages.messages).right() + } returns ConversationAndMessages(expectedConversation2, expectedMessages.messages, messageToOpen).right() // When - First conversation val job1 = launch { @@ -424,7 +424,7 @@ internal class RustConversationDetailQueryImplTest { val watcherMock = mockk { every { conversation } returns expectedConversation every { messages } returns expectedMessages.messages - every { messageIdToOpen } returns messageToOpen + every { focusedMessageId } returns messageToOpen every { handle } returns mockk { every { disconnect() } returns Unit } @@ -443,10 +443,10 @@ internal class RustConversationDetailQueryImplTest { } returns watcherMock.right() coEvery { getRustConversation(mailbox, conversationId, showAll1) - } returns ConversationAndMessages(expectedConversation, messageToOpen, expectedMessages.messages).right() + } returns ConversationAndMessages(expectedConversation, expectedMessages.messages, messageToOpen).right() coEvery { getRustConversation(mailbox, conversationId, showAll2) - } returns ConversationAndMessages(expectedConversation, messageToOpen, expectedMessages.messages).right() + } returns ConversationAndMessages(expectedConversation, expectedMessages.messages, messageToOpen).right() // When - First conversation val job1 = launch { @@ -500,7 +500,7 @@ internal class RustConversationDetailQueryImplTest { val watcherMock = mockk { every { conversation } returns expectedConversation every { messages } returns expectedMessages.messages - every { messageIdToOpen } returns messageToOpen + every { focusedMessageId } returns messageToOpen every { handle } returns mockk { every { disconnect() } returns Unit } @@ -518,7 +518,7 @@ internal class RustConversationDetailQueryImplTest { } coEvery { getRustConversation(mailbox, conversationId, showAll) - } returns ConversationAndMessages(expectedConversation, messageToOpen, expectedMessages.messages).right() + } returns ConversationAndMessages(expectedConversation, expectedMessages.messages, messageToOpen).right() // When val numberOfConcurrentCalls = 10 diff --git a/mail-conversation/data/src/test/kotlin/ch/protonmail/android/mailconversation/data/repository/RustConversationRepositoryImplTest.kt b/mail-conversation/data/src/test/kotlin/ch/protonmail/android/mailconversation/data/repository/RustConversationRepositoryImplTest.kt index 5712de8079..76253d9830 100644 --- a/mail-conversation/data/src/test/kotlin/ch/protonmail/android/mailconversation/data/repository/RustConversationRepositoryImplTest.kt +++ b/mail-conversation/data/src/test/kotlin/ch/protonmail/android/mailconversation/data/repository/RustConversationRepositoryImplTest.kt @@ -230,54 +230,6 @@ internal class RustConversationRepositoryImplTest { } } - @Test - fun `observeConversationMessages should return DataError when no messages found`() = runTest { - // Given - val userId = UserIdTestData.userId - val conversationId = LocalConversationIdSample.AugConversation.toConversationId() - val expectedError = ConversationError.ConvoWithNoMessages.left() - val labelId = LabelId("2") - val entryPoint = ConversationDetailEntryPoint.Mailbox - val showAll = false - - coEvery { - rustConversationDataSource.observeConversationWithMessages( - userId, - conversationId.toLocalConversationId(), - labelId.toLocalLabelId(), - entryPoint, - showAll - ) - } returns flowOf( - LocalConversationWithMessages( - conversation = mockk(), - messages = LocalConversationMessages( - messageIdToOpen = LocalMessageIdSample.AugWeatherForecast, - messages = emptyList() - ) - ).right() - ) - - // When - rustConversationRepository.observeConversationMessages(userId, conversationId, labelId, entryPoint, showAll) - .test { - val result = awaitItem() - - // Then - assertEquals(expectedError, result) - coVerify { - rustConversationDataSource.observeConversationWithMessages( - userId, - conversationId.toLocalConversationId(), - labelId.toLocalLabelId(), - entryPoint, - showAll - ) - } - awaitComplete() - } - } - @Test fun `when getConversationCursor returns a cursor with the first conversationId`() = runTest { // Given diff --git a/mail-conversation/domain/src/main/kotlin/ch/protonmail/android/mailconversation/domain/entity/ConversationError.kt b/mail-conversation/domain/src/main/kotlin/ch/protonmail/android/mailconversation/domain/entity/ConversationError.kt index 8904a868d3..496f629525 100644 --- a/mail-conversation/domain/src/main/kotlin/ch/protonmail/android/mailconversation/domain/entity/ConversationError.kt +++ b/mail-conversation/domain/src/main/kotlin/ch/protonmail/android/mailconversation/domain/entity/ConversationError.kt @@ -26,7 +26,6 @@ sealed interface ConversationError { data object UnknownMessage : ConversationError data object UnknownContentId : ConversationError data object NullValueReturned : ConversationError - data object ConvoWithNoMessages : ConversationError data class Other(val error: DataError) : ConversationError } diff --git a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModel.kt b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModel.kt index 3a760d560e..9a12be50dd 100644 --- a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModel.kt +++ b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModel.kt @@ -22,7 +22,6 @@ import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import arrow.core.Either -import arrow.core.NonEmptyList import arrow.core.getOrElse import arrow.core.left import arrow.core.right @@ -595,8 +594,7 @@ class ConversationDetailViewModel @AssistedInject constructor( ) } - error is ConversationError.ConvoWithNoMessages || - error is ConversationError.NullValueReturned -> { + error is ConversationError.NullValueReturned -> { ConversationDataLoadingResult.Exit } @@ -645,7 +643,7 @@ class ConversationDetailViewModel @AssistedInject constructor( val initialScrollTo = initialScrollToMessageId ?: conversationWithMessages.messages.messageIdToOpen - .let { messageIdUiModelMapper.toUiModel(it) } + ?.let { messageIdUiModelMapper.toUiModel(it) } val messagesEvent = if (stateIsLoadingOrOffline() && allCollapsed(conversationViewState.messagesState)) { @@ -655,7 +653,8 @@ class ConversationDetailViewModel @AssistedInject constructor( openedFromLocation ) } else { - val requestScrollTo = requestScrollToMessageId(conversationViewState.messagesState) + val requestScrollTo = + requestScrollToMessageId(conversationViewState.messagesState) ?: initialScrollTo ConversationDetailEvent.MessagesData( messagesUiModels, requestScrollTo, @@ -736,11 +735,11 @@ class ConversationDetailViewModel @AssistedInject constructor( } private suspend fun buildMessagesUiModels( - messages: NonEmptyList, + messages: List, primaryUserAddress: String?, currentViewState: InMemoryConversationStateRepository.MessagesState, avatarImageStates: AvatarImageStates - ): NonEmptyList { + ): List { val messagesList = messages.map { message -> val avatarImageState = avatarImageStates.getStateForAddress(message.sender.address) val attachmentListExpandCollapseMode = currentViewState.attachmentsListExpandCollapseMode[message.messageId] diff --git a/mail-message/data/src/main/kotlin/ch/protonmail/android/mailmessage/data/model/LocalConversationMessages.kt b/mail-message/data/src/main/kotlin/ch/protonmail/android/mailmessage/data/model/LocalConversationMessages.kt index a22a71580a..a8a75c22c2 100644 --- a/mail-message/data/src/main/kotlin/ch/protonmail/android/mailmessage/data/model/LocalConversationMessages.kt +++ b/mail-message/data/src/main/kotlin/ch/protonmail/android/mailmessage/data/model/LocalConversationMessages.kt @@ -22,6 +22,6 @@ import ch.protonmail.android.mailcommon.data.mapper.LocalMessageId import ch.protonmail.android.mailcommon.data.mapper.LocalMessageMetadata data class LocalConversationMessages( - val messageIdToOpen: LocalMessageId, + val messageIdToOpen: LocalMessageId?, val messages: List ) diff --git a/mail-message/domain/src/main/kotlin/ch/protonmail/android/mailmessage/domain/model/ConversationMessages.kt b/mail-message/domain/src/main/kotlin/ch/protonmail/android/mailmessage/domain/model/ConversationMessages.kt index 1061707554..5f4b26714e 100644 --- a/mail-message/domain/src/main/kotlin/ch/protonmail/android/mailmessage/domain/model/ConversationMessages.kt +++ b/mail-message/domain/src/main/kotlin/ch/protonmail/android/mailmessage/domain/model/ConversationMessages.kt @@ -18,9 +18,7 @@ package ch.protonmail.android.mailmessage.domain.model -import arrow.core.NonEmptyList - data class ConversationMessages( - val messages: NonEmptyList, - val messageIdToOpen: MessageId + val messages: List, + val messageIdToOpen: MessageId? )