mirror of
https://github.com/ProtonMail/android-mail.git
synced 2026-05-15 09:50:40 +00:00
Inject address signature in RE/FWD case
MAILANDR-498
This commit is contained in:
Generated
+1
@@ -13,6 +13,7 @@
|
||||
</list>
|
||||
</option>
|
||||
<option name="enableDetekt" value="true" />
|
||||
<option name="enableForProjectResult" value="Accepted" />
|
||||
<option name="enableFormatting" value="true" />
|
||||
<option name="pluginJarPaths">
|
||||
<set>
|
||||
|
||||
+10
-1
@@ -21,4 +21,13 @@ package ch.protonmail.android.mailcomposer.domain.model
|
||||
data class AddressSignature(
|
||||
val html: String,
|
||||
val plaintext: String
|
||||
)
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
const val SeparatorPlaintext = "\n\n\n"
|
||||
|
||||
val BlankSignature = AddressSignature("", "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-4
@@ -20,11 +20,13 @@ package ch.protonmail.android.mailcomposer.presentation.usecase
|
||||
|
||||
import android.content.Context
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import ch.protonmail.android.mailcommon.domain.model.DataError
|
||||
import ch.protonmail.android.mailcommon.presentation.model.TextUiModel
|
||||
import ch.protonmail.android.mailcommon.presentation.usecase.FormatExtendedTime
|
||||
import ch.protonmail.android.mailcomposer.domain.model.AddressSignature
|
||||
import ch.protonmail.android.mailcomposer.domain.model.DraftAction
|
||||
import ch.protonmail.android.mailcomposer.domain.model.DraftBody
|
||||
import ch.protonmail.android.mailcomposer.domain.model.DraftFields
|
||||
@@ -35,6 +37,7 @@ import ch.protonmail.android.mailcomposer.domain.model.RecipientsCc
|
||||
import ch.protonmail.android.mailcomposer.domain.model.RecipientsTo
|
||||
import ch.protonmail.android.mailcomposer.domain.model.SenderEmail
|
||||
import ch.protonmail.android.mailcomposer.domain.model.Subject
|
||||
import ch.protonmail.android.mailcomposer.domain.usecase.GetAddressSignature
|
||||
import ch.protonmail.android.mailcomposer.domain.usecase.ObserveUserAddresses
|
||||
import ch.protonmail.android.mailcomposer.presentation.R
|
||||
import ch.protonmail.android.mailmessage.domain.model.DecryptedMessageBody
|
||||
@@ -53,7 +56,8 @@ import kotlin.time.Duration.Companion.seconds
|
||||
class ParentMessageToDraftFields @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val observeUserAddresses: ObserveUserAddresses,
|
||||
private val formatExtendedTime: FormatExtendedTime
|
||||
private val formatExtendedTime: FormatExtendedTime,
|
||||
private val getAddressSignature: GetAddressSignature
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
@@ -65,11 +69,12 @@ class ParentMessageToDraftFields @Inject constructor(
|
||||
val decryptedBody = messageWithDecryptedBody.decryptedMessageBody
|
||||
val userAddresses = observeUserAddresses(userId).firstOrNull() ?: return DataError.Local.NoDataCached.left()
|
||||
val sender = getSenderEmail(userAddresses, message)
|
||||
val senderAddressSignature = getAddressSignature(userId, sender).getOrElse { AddressSignature.BlankSignature }
|
||||
|
||||
return DraftFields(
|
||||
sender,
|
||||
Subject("${subjectPrefixForAction(action)} ${message.subject}"),
|
||||
buildQuotedPlainTextBody(message, decryptedBody),
|
||||
buildQuotedPlainTextBody(message, decryptedBody, senderAddressSignature),
|
||||
RecipientsTo(recipientsForAction(action, messageWithDecryptedBody.messageWithBody, sender)),
|
||||
RecipientsCc(ccRecipientsForAction(action, message)),
|
||||
RecipientsBcc(emptyList()),
|
||||
@@ -77,8 +82,15 @@ class ParentMessageToDraftFields @Inject constructor(
|
||||
).right()
|
||||
}
|
||||
|
||||
private fun buildQuotedPlainTextBody(message: Message, decryptedBody: DecryptedMessageBody): DraftBody {
|
||||
if (decryptedBody.mimeType != MimeType.PlainText) {
|
||||
private fun buildQuotedPlainTextBody(
|
||||
message: Message,
|
||||
decryptedBody: DecryptedMessageBody,
|
||||
senderAddressSignature: AddressSignature
|
||||
): DraftBody {
|
||||
if (decryptedBody.mimeType != MimeType.PlainText && senderAddressSignature.plaintext.isNotBlank()) {
|
||||
// HTML quote is fully created elsewhere, but we still need to inject signature into editable body
|
||||
return DraftBody("${AddressSignature.SeparatorPlaintext}${senderAddressSignature.plaintext}")
|
||||
} else if (decryptedBody.mimeType != MimeType.PlainText) {
|
||||
return DraftBody("")
|
||||
}
|
||||
|
||||
@@ -88,6 +100,8 @@ class ParentMessageToDraftFields @Inject constructor(
|
||||
.joinToString(separator = PlainTextNewLine) { "$PlainTextQuotePrefix $it" }
|
||||
|
||||
val raw = StringBuilder()
|
||||
.append(if (senderAddressSignature.plaintext.isNotBlank()) AddressSignature.SeparatorPlaintext else "")
|
||||
.append(if (senderAddressSignature.plaintext.isNotBlank()) senderAddressSignature.plaintext else "")
|
||||
.append(PlainTextNewLine)
|
||||
.append(PlainTextNewLine)
|
||||
.append(PlainTextNewLine)
|
||||
|
||||
+177
-1
@@ -20,13 +20,17 @@ package ch.protonmail.android.mailcomposer.presentation.usecase
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import arrow.core.right
|
||||
import ch.protonmail.android.mailcommon.domain.sample.UserAddressSample
|
||||
import ch.protonmail.android.mailcommon.domain.sample.UserIdSample
|
||||
import ch.protonmail.android.mailcommon.presentation.model.TextUiModel
|
||||
import ch.protonmail.android.mailcommon.presentation.usecase.FormatExtendedTime
|
||||
import ch.protonmail.android.mailcomposer.domain.model.AddressSignature
|
||||
import ch.protonmail.android.mailcomposer.domain.model.DraftAction
|
||||
import ch.protonmail.android.mailcomposer.domain.model.MessageWithDecryptedBody
|
||||
import ch.protonmail.android.mailcomposer.domain.model.SenderEmail
|
||||
import ch.protonmail.android.mailcomposer.domain.model.Subject
|
||||
import ch.protonmail.android.mailcomposer.domain.usecase.GetAddressSignature
|
||||
import ch.protonmail.android.mailcomposer.domain.usecase.ObserveUserAddresses
|
||||
import ch.protonmail.android.mailcomposer.presentation.R
|
||||
import ch.protonmail.android.mailcomposer.presentation.usecase.ParentMessageToDraftFields.Companion.CloseProtonMailBlockquote
|
||||
@@ -39,6 +43,7 @@ import ch.protonmail.android.mailmessage.domain.sample.MessageSample
|
||||
import ch.protonmail.android.mailmessage.domain.sample.MessageWithBodySample
|
||||
import ch.protonmail.android.mailmessage.domain.sample.RecipientSample
|
||||
import ch.protonmail.android.testdata.message.DecryptedMessageBodyTestData
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
@@ -56,6 +61,7 @@ class ParentMessageToDraftFieldsTest {
|
||||
private val observeUserAddresses = mockk<ObserveUserAddresses>()
|
||||
private val context = mockk<Context>()
|
||||
private val formatTime = mockk<FormatExtendedTime>()
|
||||
private val getAddressSignatureMock = mockk<GetAddressSignature>()
|
||||
|
||||
private val expectedOriginalMessageRes = expectStringRes(R.string.composer_original_message_quote) {
|
||||
"Original Message"
|
||||
@@ -67,7 +73,8 @@ class ParentMessageToDraftFieldsTest {
|
||||
private val parentMessageToDraftFields = ParentMessageToDraftFields(
|
||||
context,
|
||||
observeUserAddresses,
|
||||
formatTime
|
||||
formatTime,
|
||||
getAddressSignatureMock
|
||||
)
|
||||
|
||||
@Test
|
||||
@@ -90,6 +97,7 @@ class ParentMessageToDraftFieldsTest {
|
||||
)
|
||||
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
|
||||
val expectedBody = expectedDecryptedMessage.decryptedMessageBody.value
|
||||
expectBlankSignatureForSenderAddress(userId, SenderEmail(UserAddressSample.PrimaryAddress.email))
|
||||
|
||||
// When
|
||||
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
|
||||
@@ -111,6 +119,152 @@ class ParentMessageToDraftFieldsTest {
|
||||
assertEquals(expectedQuotedHtmlBody, actual.originalHtmlQuote?.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns draft body with injected sender signature for plaintext message`() = runTest {
|
||||
// Given
|
||||
val userId = UserIdSample.Primary
|
||||
val expectedAction = DraftAction.Reply(MessageIdSample.Invoice)
|
||||
val expectedDecryptedMessage = MessageWithDecryptedBody(
|
||||
MessageWithBodySample.Invoice,
|
||||
DecryptedMessageBodyTestData.PlainTextDecryptedBody
|
||||
)
|
||||
val expectedTime = expectFormattedTime(MessageSample.Invoice.time.seconds) {
|
||||
TextUiModel.Text("Sep 13, 2023 3:36 PM")
|
||||
}
|
||||
val expectedOriginalMessageQuote = "-------- $expectedOriginalMessageRes --------"
|
||||
val expectedSenderQuote = expectedSenderQuoteRes.format(
|
||||
expectedTime.value,
|
||||
expectedDecryptedMessage.messageWithBody.message.sender.name,
|
||||
expectedDecryptedMessage.messageWithBody.message.sender.address
|
||||
)
|
||||
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
|
||||
val expectedBody = "${ParentMessageToDraftFields.PlainTextQuotePrefix} " +
|
||||
expectedDecryptedMessage.decryptedMessageBody.value
|
||||
val expectedSignature = expectSignatureForSenderAddress(
|
||||
userId,
|
||||
SenderEmail(UserAddressSample.PrimaryAddress.email)
|
||||
)
|
||||
|
||||
// When
|
||||
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
|
||||
|
||||
// Then
|
||||
val expectedQuotedPlaintextBody = StringBuilder()
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(expectedSignature.plaintext)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(expectedOriginalMessageQuote)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(expectedSenderQuote)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(expectedBody)
|
||||
.toString()
|
||||
|
||||
assertEquals(expectedQuotedPlaintextBody, actual.body.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns draft body with injected blank sender signature for plaintext message`() = runTest {
|
||||
// Given
|
||||
val userId = UserIdSample.Primary
|
||||
val expectedAction = DraftAction.Reply(MessageIdSample.Invoice)
|
||||
val expectedDecryptedMessage = MessageWithDecryptedBody(
|
||||
MessageWithBodySample.Invoice,
|
||||
DecryptedMessageBodyTestData.PlainTextDecryptedBody
|
||||
)
|
||||
val expectedTime = expectFormattedTime(MessageSample.Invoice.time.seconds) {
|
||||
TextUiModel.Text("Sep 13, 2023 3:36 PM")
|
||||
}
|
||||
val expectedOriginalMessageQuote = "-------- $expectedOriginalMessageRes --------"
|
||||
val expectedSenderQuote = expectedSenderQuoteRes.format(
|
||||
expectedTime.value,
|
||||
expectedDecryptedMessage.messageWithBody.message.sender.name,
|
||||
expectedDecryptedMessage.messageWithBody.message.sender.address
|
||||
)
|
||||
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
|
||||
val expectedBody = "${ParentMessageToDraftFields.PlainTextQuotePrefix} " +
|
||||
expectedDecryptedMessage.decryptedMessageBody.value
|
||||
expectBlankSignatureForSenderAddress(
|
||||
userId,
|
||||
SenderEmail(UserAddressSample.PrimaryAddress.email)
|
||||
)
|
||||
|
||||
// When
|
||||
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
|
||||
|
||||
// Then
|
||||
val expectedQuotedPlaintextBody = StringBuilder()
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(expectedOriginalMessageQuote)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(expectedSenderQuote)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(ParentMessageToDraftFields.PlainTextNewLine)
|
||||
.append(expectedBody)
|
||||
.toString()
|
||||
|
||||
assertEquals(expectedQuotedPlaintextBody, actual.body.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns draft body with injected sender signature for HTML message`() = runTest {
|
||||
// Given
|
||||
val userId = UserIdSample.Primary
|
||||
val expectedAction = DraftAction.Reply(MessageIdSample.HtmlInvoice)
|
||||
val expectedDecryptedMessage = MessageWithDecryptedBody(
|
||||
MessageWithBodySample.HtmlInvoice,
|
||||
DecryptedMessageBodyTestData.htmlInvoice
|
||||
)
|
||||
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) {
|
||||
TextUiModel.Text("Sep 13, 2023 3:36 PM")
|
||||
}
|
||||
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
|
||||
val expectedSignature = expectSignatureForSenderAddress(
|
||||
userId,
|
||||
SenderEmail(UserAddressSample.PrimaryAddress.email)
|
||||
)
|
||||
val expectedBody = "${AddressSignature.SeparatorPlaintext}${expectedSignature.plaintext}"
|
||||
|
||||
// When
|
||||
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
|
||||
|
||||
// Then
|
||||
assertEquals(expectedBody, actual.body.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns draft body with injected blank sender signature for HTML message`() = runTest {
|
||||
// Given
|
||||
val userId = UserIdSample.Primary
|
||||
val expectedAction = DraftAction.Reply(MessageIdSample.HtmlInvoice)
|
||||
val expectedDecryptedMessage = MessageWithDecryptedBody(
|
||||
MessageWithBodySample.HtmlInvoice,
|
||||
DecryptedMessageBodyTestData.htmlInvoice
|
||||
)
|
||||
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) {
|
||||
TextUiModel.Text("Sep 13, 2023 3:36 PM")
|
||||
}
|
||||
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
|
||||
expectBlankSignatureForSenderAddress(
|
||||
userId,
|
||||
SenderEmail(UserAddressSample.PrimaryAddress.email)
|
||||
)
|
||||
val expectedBody = ""
|
||||
|
||||
// When
|
||||
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
|
||||
|
||||
// Then
|
||||
assertEquals(expectedBody, actual.body.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns draft data with prefixed subject based on draft action`() = runTest {
|
||||
// Given
|
||||
@@ -122,6 +276,7 @@ class ParentMessageToDraftFieldsTest {
|
||||
)
|
||||
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
|
||||
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) { TextUiModel.Text("Sep 13, 2023 3:36 PM") }
|
||||
expectBlankSignatureForSenderAddress(userId, SenderEmail(UserAddressSample.PrimaryAddress.email))
|
||||
|
||||
// When
|
||||
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
|
||||
@@ -142,6 +297,7 @@ class ParentMessageToDraftFieldsTest {
|
||||
)
|
||||
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
|
||||
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) { TextUiModel.Text("Sep 13, 2023 3:36 PM") }
|
||||
expectBlankSignatureForSenderAddress(userId, SenderEmail(UserAddressSample.PrimaryAddress.email))
|
||||
|
||||
// When
|
||||
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
|
||||
@@ -168,6 +324,7 @@ class ParentMessageToDraftFieldsTest {
|
||||
)
|
||||
expectedUserAddresses(userId) { listOf(UserAddressSample.PrimaryAddress) }
|
||||
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) { TextUiModel.Text("Sep 13, 2023 3:36 PM") }
|
||||
expectBlankSignatureForSenderAddress(userId, SenderEmail(UserAddressSample.PrimaryAddress.email))
|
||||
|
||||
// When
|
||||
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
|
||||
@@ -196,6 +353,7 @@ class ParentMessageToDraftFieldsTest {
|
||||
)
|
||||
expectedUserAddresses(userId) { listOf(johnUserAddress) }
|
||||
expectFormattedTime(MessageSample.HtmlInvoice.time.seconds) { TextUiModel.Text("Sep 13, 2023 3:36 PM") }
|
||||
expectBlankSignatureForSenderAddress(userId, SenderEmail(johnUserAddress.email))
|
||||
|
||||
// When
|
||||
val actual = parentMessageToDraftFields(userId, expectedDecryptedMessage, expectedAction).getOrNull()!!
|
||||
@@ -217,4 +375,22 @@ class ParentMessageToDraftFieldsTest {
|
||||
private fun expectedUserAddresses(userId: UserId, addresses: () -> List<UserAddress>) = addresses().also {
|
||||
every { observeUserAddresses.invoke(userId) } returns flowOf(it)
|
||||
}
|
||||
|
||||
private fun expectSignatureForSenderAddress(
|
||||
expectedUserId: UserId,
|
||||
expectedSenderEmail: SenderEmail
|
||||
): AddressSignature = AddressSignature(
|
||||
"<div>HTML signature</div>",
|
||||
"Plaintext signature"
|
||||
).also {
|
||||
coEvery { getAddressSignatureMock(expectedUserId, expectedSenderEmail) } returns it.right()
|
||||
}
|
||||
|
||||
private fun expectBlankSignatureForSenderAddress(
|
||||
expectedUserId: UserId,
|
||||
expectedSenderEmail: SenderEmail
|
||||
): AddressSignature = AddressSignature(
|
||||
"",
|
||||
""
|
||||
).also { coEvery { getAddressSignatureMock(expectedUserId, expectedSenderEmail) } returns it.right() }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user