Added a wrapper to the message body results after decryption to be able to distinguish if the body was decrypted or not. Because even if decryption fails we still need to show a body.

MAILAND-2283
This commit is contained in:
Zorica Stojchevska 2021-08-17 15:26:34 +02:00 committed by Marino Meneghel
parent 267ed05ad6
commit 5760b43c65
6 changed files with 65 additions and 21 deletions

View File

@ -53,6 +53,7 @@ import ch.protonmail.android.details.data.toConversationUiModel
import ch.protonmail.android.details.presentation.MessageDetailsActivity
import ch.protonmail.android.details.presentation.model.ConversationUiModel
import ch.protonmail.android.domain.entity.LabelId
import ch.protonmail.android.details.presentation.model.MessageBodyState
import ch.protonmail.android.domain.entity.Name
import ch.protonmail.android.events.DownloadEmbeddedImagesEvent
import ch.protonmail.android.events.Status
@ -300,19 +301,24 @@ internal class MessageDetailsViewModel @Inject constructor(
Timber.v("loadMessageBody ${message.messageId} isNotDecrypted: ${message.decryptedHTML.isNullOrEmpty()}")
if (!message.decryptedHTML.isNullOrEmpty()) {
emit(message)
emit(MessageBodyState.Success(message))
} else {
val userId = userManager.requireCurrentUserId()
val messageId = requireNotNull(message.messageId)
val fetchedMessage = messageRepository.getMessage(userId, messageId, true)
val isDecrypted = fetchedMessage?.tryDecrypt(publicKeys)
if (isDecrypted == true) {
Timber.v("message $messageId isDecrypted, isRead: ${fetchedMessage.isRead}")
if (!fetchedMessage.isRead) {
messageRepository.markRead(listOf(messageId))
}
emit(fetchedMessage)
val fetchedMessage = messageRepository.getMessage(userId, messageId, true) ?: return@flow
val isDecrypted = fetchedMessage.tryDecrypt(publicKeys)
Timber.v("message $messageId isDecrypted, isRead: ${fetchedMessage.isRead}")
if (!fetchedMessage.isRead) {
messageRepository.markRead(listOf(messageId))
}
if (isDecrypted == true) {
emit(MessageBodyState.Success(fetchedMessage))
} else {
emit(MessageBodyState.Error.DecryptionError(fetchedMessage))
}
}
}.flowOn(dispatchers.Io)

View File

@ -52,6 +52,7 @@ import ch.protonmail.android.data.local.model.Attachment
import ch.protonmail.android.data.local.model.Message
import ch.protonmail.android.details.domain.MessageBodyParser
import ch.protonmail.android.details.presentation.model.ConversationUiModel
import ch.protonmail.android.details.presentation.model.MessageBodyState
import ch.protonmail.android.events.DownloadEmbeddedImagesEvent
import ch.protonmail.android.events.DownloadedAttachmentEvent
import ch.protonmail.android.events.PostPhishingReportEvent
@ -178,6 +179,7 @@ internal class MessageDetailsActivity : BaseStoragePermissionActivity() {
message.decryptedHTML,
messageId,
false,
false,
message.attachments
)
}
@ -214,8 +216,10 @@ internal class MessageDetailsActivity : BaseStoragePermissionActivity() {
private fun onLoadMessageBody(message: Message) {
if (message.messageId != null) {
viewModel.loadMessageBody(message).mapLatest { loadedMessage ->
viewModel.loadMessageBody(message).mapLatest { messageBodyState ->
val showDecryptionError = messageBodyState is MessageBodyState.Error.DecryptionError
val loadedMessage = messageBodyState.message
val parsedBody = viewModel.formatMessageHtmlBody(
loadedMessage,
UiUtil.getRenderWidth(this.windowManager),

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.details.presentation.model
import ch.protonmail.android.data.local.model.Message
sealed class MessageBodyState(val message: Message) {
data class Success(val _message: Message) : MessageBodyState(_message)
sealed class Error(message: Message) : MessageBodyState(message) {
data class DecryptionError(val _message: Message) : Error(_message)
}
}

View File

@ -29,7 +29,7 @@ import ch.protonmail.android.databinding.LayoutMessageDetailsDecryptionErrorInfo
/**
* A view for info banner in message details
*/
class MessageDetailsDecryptionErrorBannerView @JvmOverloads constructor(
class DecryptionErrorBanner @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0

View File

@ -133,7 +133,7 @@
android:visibility="visible"
tools:text="@string/spam_score_101" />
<ch.protonmail.android.details.presentation.view.MessageDetailsDecryptionErrorBannerView
<ch.protonmail.android.details.presentation.view.DecryptionErrorBanner
android:id="@+id/decryptionErrorView"
android:layout_width="0dp"
android:layout_height="wrap_content"

View File

@ -42,6 +42,7 @@ import ch.protonmail.android.details.presentation.MessageDetailsActivity.Compani
import ch.protonmail.android.details.presentation.MessageDetailsActivity.Companion.EXTRA_MESSAGE_OR_CONVERSATION_ID
import ch.protonmail.android.details.presentation.model.ConversationUiModel
import ch.protonmail.android.domain.entity.LabelId
import ch.protonmail.android.details.presentation.model.MessageBodyState
import ch.protonmail.android.domain.entity.Name
import ch.protonmail.android.labels.domain.usecase.MoveMessagesToFolder
import ch.protonmail.android.mailbox.domain.ChangeConversationsReadStatus
@ -69,7 +70,6 @@ import io.mockk.runs
import io.mockk.verify
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
@ -538,14 +538,15 @@ class MessageDetailsViewModelTest : ArchTest, CoroutinesTest {
// When
val actual = viewModel.loadMessageBody(message).first()
val expected = MessageBodyState.Success(message)
// Then
verify { messageRepository.markRead(listOf("messageId1")) }
assertEquals(message, actual)
assertEquals(expected, actual)
}
@Test
fun loadMessageBodyDoesNotMarkMessageAsReadWhenTheMessageDecryptionFails() = runBlockingTest {
fun loadMessageBodyDoesMarksMessageAsReadWhenTheMessageDecryptionFails() = runBlockingTest {
// Given
val message = mockk<Message>(relaxed = true)
every { message.messageId } returns "messageId2"
@ -555,12 +556,15 @@ class MessageDetailsViewModelTest : ArchTest, CoroutinesTest {
every { message.senderEmail } returns "senderEmail"
every { message.decrypt(any(), any(), any()) } throws Exception("Test - Decryption failed")
coEvery { messageRepository.getMessage(any(), any(), any()) } returns message
coEvery { messageRepository.markRead(any()) } just Runs
// When
val actual: Flow<Message> = viewModel.loadMessageBody(message)
val actual = viewModel.loadMessageBody(message).first()
val expected = MessageBodyState.Error.DecryptionError(message)
// Then
verify(exactly = 0) { messageRepository.markRead(any()) }
verify { messageRepository.markRead(listOf("messageId2")) }
assertEquals(expected, actual)
}
@Test
@ -582,7 +586,7 @@ class MessageDetailsViewModelTest : ArchTest, CoroutinesTest {
// Then
verify(exactly = 0) { messageRepository.markRead(any()) }
assertEquals(message, expectItem())
assertEquals(MessageBodyState.Success(message), expectItem())
expectComplete()
}
}
@ -604,7 +608,7 @@ class MessageDetailsViewModelTest : ArchTest, CoroutinesTest {
// Then
verify(exactly = 1) { messageRepository.markRead(any()) }
assertEquals(message, expectItem())
assertEquals(MessageBodyState.Success(message), expectItem())
expectComplete()
}
}
@ -687,7 +691,7 @@ class MessageDetailsViewModelTest : ArchTest, CoroutinesTest {
val decryptedMessage = viewModel.loadMessageBody(message).first()
assertEquals(decryptedMessageHtml, decryptedMessage.decryptedHTML)
assertEquals(decryptedMessageHtml, (decryptedMessage as MessageBodyState.Success).message.decryptedHTML)
}
@Test
@ -712,9 +716,10 @@ class MessageDetailsViewModelTest : ArchTest, CoroutinesTest {
// When
val decryptedMessage = viewModel.loadMessageBody(message).first()
val expectedMessage = MessageBodyState.Success(fetchedMessage)
// Then
assertEquals(fetchedMessage, decryptedMessage)
assertEquals(expectedMessage, decryptedMessage)
verify(exactly = 0) { messageRepository.markRead(any()) }
}