Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package app.k9mail.core.android.common.contact

import android.net.Uri
import net.thunderbird.core.common.cache.Cache
import net.thunderbird.core.common.mail.EmailAddress
import net.thunderbird.core.common.mail.toEmailAddressOrNull

interface ContactRepository {

Expand All @@ -10,6 +12,8 @@ interface ContactRepository {
fun hasContactFor(emailAddress: EmailAddress): Boolean

fun hasAnyContactFor(emailAddresses: List<EmailAddress>): Boolean

fun getPhotoUri(emailAddress: String): Uri?
}

interface CachingRepository {
Expand Down Expand Up @@ -42,6 +46,12 @@ internal class CachingContactRepository(
override fun hasAnyContactFor(emailAddresses: List<EmailAddress>): Boolean =
emailAddresses.any { emailAddress -> hasContactFor(emailAddress) }

override fun getPhotoUri(emailAddress: String): Uri? {
return emailAddress.toEmailAddressOrNull()?.let { emailAddress ->
getContactFor(emailAddress)?.photoUri
}
}

override fun clearCache() {
cache.clear()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package app.k9mail.core.android.common.contact

import android.net.Uri
import assertk.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isFalse
Expand Down Expand Up @@ -140,4 +141,53 @@ internal class CachingContactRepositoryTest {

assertThat(cache[CONTACT_EMAIL_ADDRESS]).isNull()
}

@Test
fun `getPhotoUri() returns null when email is invalid`() {
val result = testSubject.getPhotoUri("invalid-email")

assertThat(result).isNull()
}

@Test
fun `getPhotoUri() returns null when no contact found for valid email`() {
dataSource.stub { on { getContactFor(CONTACT_EMAIL_ADDRESS) } doReturn null }

val result = testSubject.getPhotoUri(CONTACT_EMAIL_ADDRESS.address)

assertThat(result).isNull()
}

@Test
fun `getPhotoUri() returns contact photo uri when contact exists`() {
dataSource.stub { on { getContactFor(CONTACT_EMAIL_ADDRESS) } doReturn CONTACT }

val result = testSubject.getPhotoUri(CONTACT_EMAIL_ADDRESS.address)

assertThat(result).isEqualTo(CONTACT.photoUri)
}

@Test
fun `getPhotoUri() returns cached photo uri when contact already cached`() {
cache[CONTACT_EMAIL_ADDRESS] = CONTACT

val result = testSubject.getPhotoUri(CONTACT_EMAIL_ADDRESS.address)

assertThat(result).isEqualTo(CONTACT.photoUri)
}

@Test
fun `getPhotoUri() caches result after first fetch`() {
dataSource.stub {
on { getContactFor(CONTACT_EMAIL_ADDRESS) } doReturnConsecutively listOf(
CONTACT,
CONTACT.copy(photoUri = Uri.parse("content://other/photo")),
)
}

val result1 = testSubject.getPhotoUri(CONTACT_EMAIL_ADDRESS.address)
val result2 = testSubject.getPhotoUri(CONTACT_EMAIL_ADDRESS.address)

assertThat(result1).isEqualTo(result2)
}
}
2 changes: 2 additions & 0 deletions legacy/ui/legacy/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ dependencies {
implementation(projects.feature.notification.api)
// TODO: Remove AccountOauth dependency
implementation(projects.feature.account.oauth)
implementation(projects.feature.account.avatar.api)
implementation(projects.feature.account.avatar.impl)
implementation(projects.feature.funding.api)
implementation(projects.feature.search.implLegacy)
implementation(projects.feature.settings.import)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.fsck.k9.ui.messagelist.item

import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.PreviewLightDark
import app.k9mail.core.android.common.contact.Contact
import app.k9mail.core.android.common.contact.ContactRepository
import app.k9mail.core.ui.compose.designsystem.PreviewWithThemesLightDark
import com.fsck.k9.FontSizes
import com.fsck.k9.UiDensity
Expand All @@ -12,7 +14,9 @@ import com.fsck.k9.ui.messagelist.MessageListAppearance
import com.fsck.k9.ui.messagelist.MessageListItem
import net.thunderbird.core.android.account.Identity
import net.thunderbird.core.android.account.LegacyAccount
import net.thunderbird.core.common.mail.EmailAddress
import net.thunderbird.feature.account.AccountIdFactory
import net.thunderbird.feature.account.avatar.AvatarMonogramCreator
import net.thunderbird.feature.account.storage.profile.AvatarDto
import net.thunderbird.feature.account.storage.profile.AvatarTypeDto
import net.thunderbird.feature.account.storage.profile.ProfileDto
Expand All @@ -25,6 +29,8 @@ internal fun MessageItemContentPreview() {
item = fakeMessageListItem,
isActive = true,
isSelected = false,
contactRepository = fakeContactRepository,
avatarMonogramCreator = fakeAvatarMonogramCreator,
onClick = {},
onLongClick = {},
onAvatarClick = {},
Expand Down Expand Up @@ -97,3 +103,23 @@ private val fakeMessageListAppearance = MessageListAppearance(
showAccountIndicator = true,
density = UiDensity.Default,
)

private val fakeContactRepository = object : ContactRepository {
override fun getContactFor(emailAddress: EmailAddress): Contact? {
error("Not implemented")
}

override fun hasContactFor(emailAddress: EmailAddress): Boolean {
error("Not implemented")
}

override fun hasAnyContactFor(emailAddresses: List<EmailAddress>): Boolean {
error("Not implemented")
}

override fun getPhotoUri(emailAddress: String) = null
}

private val fakeAvatarMonogramCreator = object : AvatarMonogramCreator {
override fun create(name: String?, email: String?) = "SE"
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@ package com.fsck.k9.contacts
import android.content.ContentResolver
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import app.k9mail.core.android.common.contact.ContactRepository
import net.thunderbird.core.common.mail.toEmailAddressOrNull
import net.thunderbird.core.logging.legacy.Log

internal class ContactPhotoLoader(
private val contentResolver: ContentResolver,
private val contactRepository: ContactRepository,
) {
fun loadContactPhoto(emailAddress: String): Bitmap? {
val photoUri = getPhotoUri(emailAddress) ?: return null
val photoUri = contactRepository.getPhotoUri(emailAddress = emailAddress) ?: return null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return try {
contentResolver.openInputStream(photoUri).use { inputStream ->
BitmapFactory.decodeStream(inputStream)
Expand All @@ -23,10 +21,4 @@ internal class ContactPhotoLoader(
null
}
}

private fun getPhotoUri(email: String): Uri? {
return email.toEmailAddressOrNull()?.let { emailAddress ->
contactRepository.getContactFor(emailAddress)?.photoUri
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import app.k9mail.core.android.common.contact.ContactRepository
import app.k9mail.feature.launcher.FeatureLauncherActivity
import app.k9mail.feature.launcher.FeatureLauncherTarget
import app.k9mail.legacy.message.controller.MessageReference
Expand All @@ -27,6 +28,7 @@ import net.thunderbird.core.featureflag.FeatureFlagKey
import net.thunderbird.core.featureflag.FeatureFlagProvider
import net.thunderbird.core.featureflag.FeatureFlagResult
import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
import net.thunderbird.feature.account.avatar.AvatarMonogramCreator
import net.thunderbird.feature.notification.api.ui.action.NotificationAction

private const val FOOTER_ID = 1L
Expand All @@ -47,6 +49,8 @@ class MessageListAdapter internal constructor(
private val relativeDateTimeFormatter: RelativeDateTimeFormatter,
private val themeProvider: FeatureThemeProvider,
private val featureFlagProvider: FeatureFlagProvider,
private val contactRepository: ContactRepository,
private val avatarMonogramCreator: AvatarMonogramCreator,
) : RecyclerView.Adapter<MessageListViewHolder>() {

val colors: MessageViewHolderColors = MessageViewHolderColors.resolveColors(theme)
Expand Down Expand Up @@ -266,6 +270,8 @@ class MessageListAdapter internal constructor(
ComposableMessageViewHolder.create(
context = parent.context,
themeProvider = themeProvider,
contactRepository = contactRepository,
avatarMonogramCreator = avatarMonogramCreator,
onClick = { listItemListener.onMessageClicked(it) },
onLongClick = { listItemListener.onToggleMessageSelection(it) },
onFavouriteClick = { listItemListener.onToggleMessageFlag(it) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import app.k9mail.core.android.common.contact.ContactRepository
import app.k9mail.legacy.message.controller.MessageReference
import app.k9mail.legacy.message.controller.MessagingControllerRegistry
import app.k9mail.legacy.message.controller.SimpleMessagingListener
Expand Down Expand Up @@ -92,6 +93,7 @@ import net.thunderbird.core.logging.Logger
import net.thunderbird.core.logging.legacy.Log
import net.thunderbird.core.preference.GeneralSettingsManager
import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
import net.thunderbird.feature.account.avatar.AvatarMonogramCreator
import net.thunderbird.feature.mail.folder.api.OutboxFolderManager
import net.thunderbird.feature.mail.message.list.domain.DomainContract
import net.thunderbird.feature.mail.message.list.ui.dialog.SetupArchiveFolderDialogFragmentFactory
Expand Down Expand Up @@ -145,6 +147,9 @@ class MessageListFragment :
private val activityListener = MessageListActivityListener()
private val actionModeCallback = ActionModeCallback()

private val contactRepository: ContactRepository by inject()
private val avatarMonogramCreator: AvatarMonogramCreator by inject()

private val chooseFolderForMoveLauncher: ActivityResultLauncher<ChooseFolderResultContract.Input> =
registerForActivityResult(ChooseFolderResultContract(ChooseFolderActivity.Action.MOVE)) { result ->
handleChooseFolderResult(result) { folderId, messages ->
Expand Down Expand Up @@ -348,6 +353,8 @@ class MessageListFragment :
relativeDateTimeFormatter = RelativeDateTimeFormatter(requireContext(), clock),
themeProvider = featureThemeProvider,
featureFlagProvider = featureFlagProvider,
contactRepository = contactRepository,
avatarMonogramCreator = avatarMonogramCreator,
).apply {
activeMessage = [email protected]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package com.fsck.k9.ui.messagelist.item

import android.content.Context
import androidx.compose.ui.platform.ComposeView
import app.k9mail.core.android.common.contact.ContactRepository
import com.fsck.k9.ui.messagelist.MessageListAppearance
import com.fsck.k9.ui.messagelist.MessageListItem
import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
import net.thunderbird.feature.account.avatar.AvatarMonogramCreator

/**
* A composable view holder for message list items.
*/
@Suppress("LongParameterList")
class ComposableMessageViewHolder(
private val composeView: ComposeView,
private val themeProvider: FeatureThemeProvider,
Expand All @@ -17,6 +20,8 @@ class ComposableMessageViewHolder(
private val onAvatarClick: (MessageListItem) -> Unit,
private val onFavouriteClick: (MessageListItem) -> Unit,
private val appearance: MessageListAppearance,
private val contactRepository: ContactRepository,
private val avatarMonogramCreator: AvatarMonogramCreator,
) : MessageListViewHolder(composeView) {

var uniqueId: Long = -1L
Expand All @@ -30,6 +35,8 @@ class ComposableMessageViewHolder(
item = item,
isActive = isActive,
isSelected = isSelected,
contactRepository = contactRepository,
avatarMonogramCreator = avatarMonogramCreator,
onClick = { onClick(item) },
onLongClick = { onLongClick(item) },
onAvatarClick = { onAvatarClick(item) },
Expand All @@ -41,10 +48,12 @@ class ComposableMessageViewHolder(
}

companion object {

@Suppress("LongParameterList")
fun create(
context: Context,
themeProvider: FeatureThemeProvider,
contactRepository: ContactRepository,
avatarMonogramCreator: AvatarMonogramCreator,
onClick: (MessageListItem) -> Unit,
onLongClick: (MessageListItem) -> Unit,
onFavouriteClick: (MessageListItem) -> Unit,
Expand All @@ -56,6 +65,8 @@ class ComposableMessageViewHolder(
val holder = ComposableMessageViewHolder(
composeView = composeView,
themeProvider = themeProvider,
contactRepository = contactRepository,
avatarMonogramCreator = avatarMonogramCreator,
onClick = onClick,
onLongClick = onLongClick,
onAvatarClick = onAvatarClick,
Expand Down
Loading
Loading