Respect "show contact names" setting in message details
This commit is contained in:
parent
fba8e8aca3
commit
74f40dd0bd
6 changed files with 213 additions and 19 deletions
|
@ -10,10 +10,13 @@ val messageDetailsUiModule = module {
|
|||
messageRepository = get(),
|
||||
contactSettingsProvider = get(),
|
||||
contacts = get(),
|
||||
clipboardManager = get()
|
||||
clipboardManager = get(),
|
||||
accountManager = get(),
|
||||
participantFormatter = get()
|
||||
)
|
||||
}
|
||||
factory { ContactSettingsProvider() }
|
||||
factory { AddToContactsLauncher() }
|
||||
factory { ShowContactLauncher() }
|
||||
factory { createMessageDetailsParticipantFormatter(contactNameProvider = get()) }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
import android.text.SpannableString
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.K9
|
||||
import com.fsck.k9.helper.ContactNameProvider
|
||||
import com.fsck.k9.mail.Address
|
||||
|
||||
/**
|
||||
* Get the display name for a participant to be shown in the message details screen.
|
||||
*/
|
||||
internal interface MessageDetailsParticipantFormatter {
|
||||
fun getDisplayName(address: Address, account: Account): CharSequence?
|
||||
}
|
||||
|
||||
internal class RealMessageDetailsParticipantFormatter(
|
||||
private val contactNameProvider: ContactNameProvider,
|
||||
private val showContactNames: Boolean,
|
||||
private val contactNameColor: Int?
|
||||
) : MessageDetailsParticipantFormatter {
|
||||
override fun getDisplayName(address: Address, account: Account): CharSequence? {
|
||||
val identityDisplayName = account.findIdentity(address)?.name
|
||||
if (identityDisplayName != null) {
|
||||
return identityDisplayName
|
||||
}
|
||||
|
||||
return if (showContactNames) {
|
||||
getContactNameOrNull(address) ?: address.personal
|
||||
} else {
|
||||
address.personal
|
||||
}
|
||||
}
|
||||
|
||||
private fun getContactNameOrNull(address: Address): CharSequence? {
|
||||
val contactName = contactNameProvider.getNameForAddress(address.address) ?: return null
|
||||
|
||||
return if (contactNameColor != null) {
|
||||
SpannableString(contactName).apply {
|
||||
setSpan(ForegroundColorSpan(contactNameColor), 0, contactName.length, SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
} else {
|
||||
contactName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun createMessageDetailsParticipantFormatter(
|
||||
contactNameProvider: ContactNameProvider
|
||||
): MessageDetailsParticipantFormatter {
|
||||
return RealMessageDetailsParticipantFormatter(
|
||||
contactNameProvider = contactNameProvider,
|
||||
showContactNames = K9.isShowContactName,
|
||||
contactNameColor = if (K9.isChangeContactNameColor) K9.contactNameColor else null
|
||||
)
|
||||
}
|
|
@ -21,9 +21,13 @@ data class CryptoDetails(
|
|||
)
|
||||
|
||||
data class Participant(
|
||||
val address: Address,
|
||||
val displayName: CharSequence?,
|
||||
val emailAddress: String,
|
||||
val contactLookupUri: Uri?
|
||||
) {
|
||||
val isInContacts: Boolean
|
||||
get() = contactLookupUri != null
|
||||
|
||||
val address: Address
|
||||
get() = Address(emailAddress, displayName?.toString())
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.app.PendingIntent
|
|||
import android.content.res.Resources
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.controller.MessageReference
|
||||
import com.fsck.k9.helper.ClipboardManager
|
||||
import com.fsck.k9.helper.Contacts
|
||||
|
@ -11,6 +12,7 @@ import com.fsck.k9.mail.Address
|
|||
import com.fsck.k9.mailstore.CryptoResultAnnotation
|
||||
import com.fsck.k9.mailstore.MessageDate
|
||||
import com.fsck.k9.mailstore.MessageRepository
|
||||
import com.fsck.k9.preferences.AccountManager
|
||||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.view.MessageCryptoDisplayStatus
|
||||
import java.text.DateFormat
|
||||
|
@ -27,7 +29,9 @@ internal class MessageDetailsViewModel(
|
|||
private val messageRepository: MessageRepository,
|
||||
private val contactSettingsProvider: ContactSettingsProvider,
|
||||
private val contacts: Contacts,
|
||||
private val clipboardManager: ClipboardManager
|
||||
private val clipboardManager: ClipboardManager,
|
||||
private val accountManager: AccountManager,
|
||||
private val participantFormatter: MessageDetailsParticipantFormatter
|
||||
) : ViewModel() {
|
||||
private val dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, Locale.getDefault())
|
||||
private val uiState = MutableStateFlow<MessageDetailsState>(MessageDetailsState.Loading)
|
||||
|
@ -39,18 +43,19 @@ internal class MessageDetailsViewModel(
|
|||
fun loadData(messageReference: MessageReference): StateFlow<MessageDetailsState> {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
uiState.value = try {
|
||||
val account = accountManager.getAccount(messageReference.accountUuid) ?: error("Account not found")
|
||||
val messageDetails = messageRepository.getMessageDetails(messageReference)
|
||||
|
||||
val senderList = messageDetails.sender?.let { listOf(it) } ?: emptyList()
|
||||
val messageDetailsUi = MessageDetailsUi(
|
||||
date = buildDisplayDate(messageDetails.date),
|
||||
cryptoDetails = cryptoResult?.toCryptoDetails(),
|
||||
from = messageDetails.from.toParticipants(),
|
||||
sender = senderList.toParticipants(),
|
||||
replyTo = messageDetails.replyTo.toParticipants(),
|
||||
to = messageDetails.to.toParticipants(),
|
||||
cc = messageDetails.cc.toParticipants(),
|
||||
bcc = messageDetails.bcc.toParticipants()
|
||||
from = messageDetails.from.toParticipants(account),
|
||||
sender = senderList.toParticipants(account),
|
||||
replyTo = messageDetails.replyTo.toParticipants(account),
|
||||
to = messageDetails.to.toParticipants(account),
|
||||
cc = messageDetails.cc.toParticipants(account),
|
||||
bcc = messageDetails.bcc.toParticipants(account)
|
||||
)
|
||||
|
||||
MessageDetailsState.DataLoaded(
|
||||
|
@ -82,11 +87,15 @@ internal class MessageDetailsViewModel(
|
|||
)
|
||||
}
|
||||
|
||||
private fun List<Address>.toParticipants(): List<Participant> {
|
||||
private fun List<Address>.toParticipants(account: Account): List<Participant> {
|
||||
return this.map { address ->
|
||||
val displayName = participantFormatter.getDisplayName(address, account)
|
||||
val emailAddress = address.address
|
||||
|
||||
Participant(
|
||||
address = address,
|
||||
contactLookupUri = contacts.getContactUri(address.address)
|
||||
displayName = displayName,
|
||||
emailAddress = emailAddress,
|
||||
contactLookupUri = contacts.getContactUri(emailAddress)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,20 +38,18 @@ internal class ParticipantItem(
|
|||
|
||||
override fun bindView(item: ParticipantItem, payloads: List<Any>) {
|
||||
val participant = item.participant
|
||||
val address = participant.address
|
||||
val participantName = address.personal
|
||||
|
||||
if (participantName != null) {
|
||||
name.text = participantName
|
||||
email.text = address.address
|
||||
if (participant.displayName != null) {
|
||||
name.text = participant.displayName
|
||||
email.text = participant.emailAddress
|
||||
} else {
|
||||
name.text = address.address
|
||||
name.text = participant.emailAddress
|
||||
email.isVisible = false
|
||||
}
|
||||
menuAddContact.isVisible = !participant.isInContacts
|
||||
|
||||
if (item.showContactsPicture) {
|
||||
item.contactPictureLoader.setContactPicture(contactPicture, address)
|
||||
item.contactPictureLoader.setContactPicture(contactPicture, participant.address)
|
||||
} else {
|
||||
contactPicture.isVisible = false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
package com.fsck.k9.ui.messagedetails
|
||||
|
||||
import android.graphics.Color
|
||||
import android.text.Spannable
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import androidx.core.text.getSpans
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.Identity
|
||||
import com.fsck.k9.RobolectricTest
|
||||
import com.fsck.k9.helper.ContactNameProvider
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
private const val IDENTITY_NAME = "Alice"
|
||||
private const val IDENTITY_ADDRESS = "me@domain.example"
|
||||
|
||||
class MessageDetailsParticipantFormatterTest : RobolectricTest() {
|
||||
private val contactNameProvider = object : ContactNameProvider {
|
||||
override fun getNameForAddress(address: String): String? {
|
||||
return when (address) {
|
||||
"user1@domain.example" -> "Contact One"
|
||||
"spoof@domain.example" -> "contact@important.example"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val account = Account("uuid").apply {
|
||||
identities += Identity(name = IDENTITY_NAME, email = IDENTITY_ADDRESS)
|
||||
}
|
||||
|
||||
private val participantFormatter = createParticipantFormatter()
|
||||
|
||||
@Test
|
||||
fun `identity address`() {
|
||||
val displayName = participantFormatter.getDisplayName(Address(IDENTITY_ADDRESS, "irrelevant"), account)
|
||||
|
||||
assertThat(displayName).isEqualTo(IDENTITY_NAME)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `identity without a display name`() {
|
||||
val account = Account("uuid").apply {
|
||||
identities += Identity(name = null, email = IDENTITY_ADDRESS)
|
||||
}
|
||||
|
||||
val displayName = participantFormatter.getDisplayName(Address(IDENTITY_ADDRESS, "Bob"), account)
|
||||
|
||||
assertThat(displayName).isEqualTo("Bob")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `identity and address without a display name`() {
|
||||
val account = Account("uuid").apply {
|
||||
identities += Identity(name = null, email = IDENTITY_ADDRESS)
|
||||
}
|
||||
|
||||
val displayName = participantFormatter.getDisplayName(Address(IDENTITY_ADDRESS), account)
|
||||
|
||||
assertThat(displayName).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `email address without display name`() {
|
||||
val displayName = participantFormatter.getDisplayName(Address("alice@domain.example"), account)
|
||||
|
||||
assertThat(displayName).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `email address with display name`() {
|
||||
val displayName = participantFormatter.getDisplayName(Address("alice@domain.example", "Alice"), account)
|
||||
|
||||
assertThat(displayName).isEqualTo("Alice")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `don't look up contact when showContactNames = false`() {
|
||||
val participantFormatter = createParticipantFormatter(showContactNames = false)
|
||||
|
||||
val displayName = participantFormatter.getDisplayName(Address("user1@domain.example", "User 1"), account)
|
||||
|
||||
assertThat(displayName).isEqualTo("User 1")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `contact lookup`() {
|
||||
val displayName = participantFormatter.getDisplayName(Address("user1@domain.example"), account)
|
||||
|
||||
assertThat(displayName).isEqualTo("Contact One")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `contact lookup despite display name`() {
|
||||
val displayName = participantFormatter.getDisplayName(Address("user1@domain.example", "User 1"), account)
|
||||
|
||||
assertThat(displayName).isEqualTo("Contact One")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `colored contact name`() {
|
||||
val participantFormatter = createParticipantFormatter(contactNameColor = Color.RED)
|
||||
|
||||
val displayName = participantFormatter.getDisplayName(Address("user1@domain.example"), account)
|
||||
|
||||
assertThat(displayName.toString()).isEqualTo("Contact One")
|
||||
assertThat(displayName).isInstanceOf(Spannable::class.java)
|
||||
val spans = (displayName as Spannable).getSpans<ForegroundColorSpan>(0, displayName.length)
|
||||
assertThat(spans.map { it.foregroundColor }).containsExactly(Color.RED)
|
||||
}
|
||||
|
||||
private fun createParticipantFormatter(
|
||||
showContactNames: Boolean = true,
|
||||
contactNameColor: Int? = null
|
||||
): MessageDetailsParticipantFormatter {
|
||||
return RealMessageDetailsParticipantFormatter(
|
||||
contactNameProvider = contactNameProvider,
|
||||
showContactNames = showContactNames,
|
||||
contactNameColor = contactNameColor
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue