From 1c037e5fab8dfca6d5b5aeb32d7fe41d442a8bd6 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 15 Jan 2026 09:23:28 +0800 Subject: [PATCH] Show avatars on message notifications --- .../K9NotificationResourceProvider.kt | 10 ++++++++ .../CoreNotificationKoinModule.kt | 2 ++ .../k9/notification/NotificationContent.kt | 3 ++- .../NotificationContentCreator.kt | 23 ++++++++++--------- .../NotificationResourceProvider.kt | 5 ++++ .../SingleMessageNotificationCreator.kt | 20 ++++++++++++++-- .../BaseNotificationDataCreatorTest.kt | 6 +++-- .../NewMailNotificationManagerTest.kt | 15 ++++++------ .../NotificationContentCreatorTest.kt | 12 +++++----- .../notification/NotificationDataStoreTest.kt | 3 ++- ...ingleMessageNotificationDataCreatorTest.kt | 3 ++- .../SummaryNotificationDataCreatorTest.kt | 3 ++- .../TestNotificationResourceProvider.kt | 5 ++++ 13 files changed, 78 insertions(+), 32 deletions(-) diff --git a/legacy/common/src/main/java/com/fsck/k9/notification/K9NotificationResourceProvider.kt b/legacy/common/src/main/java/com/fsck/k9/notification/K9NotificationResourceProvider.kt index c6a32bb3fa3..415a8fcfc15 100644 --- a/legacy/common/src/main/java/com/fsck/k9/notification/K9NotificationResourceProvider.kt +++ b/legacy/common/src/main/java/com/fsck/k9/notification/K9NotificationResourceProvider.kt @@ -1,8 +1,14 @@ package com.fsck.k9.notification import android.content.Context +import android.graphics.Bitmap import app.k9mail.core.ui.legacy.designsystem.atom.icon.Icons +import com.fsck.k9.activity.misc.ContactPicture +import com.fsck.k9.mail.Address import com.fsck.k9.ui.R +import com.fsck.k9.view.RecipientSelectView +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext class K9NotificationResourceProvider(private val context: Context) : NotificationResourceProvider { override val iconWarning: Int = Icons.Outlined.Warning @@ -38,6 +44,10 @@ class K9NotificationResourceProvider(private val context: Context) : Notificatio override fun authenticationErrorBody(accountName: String): String = context.getString(R.string.notification_authentication_error_text, accountName) + override suspend fun avatar(address: Address): Bitmap? = withContext(Dispatchers.IO) { + ContactPicture.getContactPictureLoader().getContactPicture(RecipientSelectView.Recipient(address)) + } + override fun notifyErrorTitle(): String = context.getString(R.string.notification_notify_error_title) override fun notifyErrorText(): String = context.getString(R.string.notification_notify_error_text) diff --git a/legacy/core/src/main/java/com/fsck/k9/notification/CoreNotificationKoinModule.kt b/legacy/core/src/main/java/com/fsck/k9/notification/CoreNotificationKoinModule.kt index 3eea49a2260..85b30259b8a 100644 --- a/legacy/core/src/main/java/com/fsck/k9/notification/CoreNotificationKoinModule.kt +++ b/legacy/core/src/main/java/com/fsck/k9/notification/CoreNotificationKoinModule.kt @@ -5,6 +5,7 @@ import android.content.Context import androidx.core.app.NotificationManagerCompat import java.util.concurrent.Executors import kotlin.time.ExperimentalTime +import org.koin.android.ext.koin.androidApplication import org.koin.dsl.module val coreNotificationModule = module { @@ -111,6 +112,7 @@ val coreNotificationModule = module { actionCreator = get(), resourceProvider = get(), lockScreenNotificationCreator = get(), + application = androidApplication(), ) } factory { diff --git a/legacy/core/src/main/java/com/fsck/k9/notification/NotificationContent.kt b/legacy/core/src/main/java/com/fsck/k9/notification/NotificationContent.kt index 3034648b475..b7b70ddf8b6 100644 --- a/legacy/core/src/main/java/com/fsck/k9/notification/NotificationContent.kt +++ b/legacy/core/src/main/java/com/fsck/k9/notification/NotificationContent.kt @@ -1,10 +1,11 @@ package com.fsck.k9.notification import app.k9mail.legacy.message.controller.MessageReference +import com.fsck.k9.mail.Address internal data class NotificationContent( val messageReference: MessageReference, - val sender: String, + val sender: Address, val subject: String, val preview: CharSequence, val summary: CharSequence, diff --git a/legacy/core/src/main/java/com/fsck/k9/notification/NotificationContentCreator.kt b/legacy/core/src/main/java/com/fsck/k9/notification/NotificationContentCreator.kt index 270bc69b827..e781f5c7a86 100644 --- a/legacy/core/src/main/java/com/fsck/k9/notification/NotificationContentCreator.kt +++ b/legacy/core/src/main/java/com/fsck/k9/notification/NotificationContentCreator.kt @@ -4,6 +4,7 @@ import android.text.SpannableStringBuilder import app.k9mail.core.android.common.contact.ContactRepository import app.k9mail.legacy.message.extractors.PreviewResult.PreviewType import com.fsck.k9.helper.MessageHelper +import com.fsck.k9.mail.Address import com.fsck.k9.mail.Message import com.fsck.k9.mailstore.LocalMessage import net.thunderbird.core.android.account.LegacyAccountDto @@ -19,10 +20,10 @@ internal class NotificationContentCreator( return NotificationContent( messageReference = message.makeMessageReference(), - sender = getMessageSenderForDisplay(sender), + sender = sender, subject = getMessageSubject(message), preview = getMessagePreview(message), - summary = buildMessageSummary(sender, getMessageSubject(message)), + summary = buildMessageSummary(sender.personal, getMessageSubject(message)), ) } @@ -70,7 +71,7 @@ internal class NotificationContentCreator( } @Suppress("ReturnCount") - private fun getMessageSender(account: LegacyAccountDto, message: Message): String? { + private fun getMessageSender(account: LegacyAccountDto, message: Message): Address { val messageListPreferences = messageListPreferencesManager.getConfig() val localContactRepository = if (messageListPreferences.isShowContactName) contactRepository else null var isSelf = false @@ -79,13 +80,15 @@ internal class NotificationContentCreator( if (!fromAddresses.isNullOrEmpty()) { isSelf = account.isAnIdentity(fromAddresses) if (!isSelf) { - return MessageHelper.toFriendly( - fromAddresses.first(), + val firstFrom = fromAddresses.first() + val personal = MessageHelper.toFriendly( + firstFrom, messageListPreferences.isShowCorrespondentNames, messageListPreferences.isChangeContactNameColor, messageListPreferences.contactNameColor, localContactRepository, ).toString() + return Address(firstFrom.address, personal) } } @@ -93,6 +96,7 @@ internal class NotificationContentCreator( // show To: if the message was sent from me val recipients = message.getRecipients(Message.RecipientType.TO) if (!recipients.isNullOrEmpty()) { + val firstRecipient = recipients.first() val recipientDisplayName = MessageHelper.toFriendly( address = recipients.first(), isShowCorrespondentNames = messageListPreferences @@ -102,14 +106,11 @@ internal class NotificationContentCreator( contactNameColor = messageListPreferences.contactNameColor, contactRepository = localContactRepository, ).toString() - return resourceProvider.recipientDisplayName(recipientDisplayName) + val personal = resourceProvider.recipientDisplayName(recipientDisplayName) + return Address(firstRecipient.address, personal) } } - return null - } - - private fun getMessageSenderForDisplay(sender: String?): String { - return sender ?: resourceProvider.noSender() + return Address("no.sender@example.com", resourceProvider.noSender()) } } diff --git a/legacy/core/src/main/java/com/fsck/k9/notification/NotificationResourceProvider.kt b/legacy/core/src/main/java/com/fsck/k9/notification/NotificationResourceProvider.kt index 91b97999f2f..83c4d3dcccf 100644 --- a/legacy/core/src/main/java/com/fsck/k9/notification/NotificationResourceProvider.kt +++ b/legacy/core/src/main/java/com/fsck/k9/notification/NotificationResourceProvider.kt @@ -1,5 +1,8 @@ package com.fsck.k9.notification +import android.graphics.Bitmap +import com.fsck.k9.mail.Address + interface NotificationResourceProvider { val iconWarning: Int val iconMarkAsRead: Int @@ -25,6 +28,8 @@ interface NotificationResourceProvider { fun authenticationErrorTitle(): String fun authenticationErrorBody(accountName: String): String + suspend fun avatar(address: Address): Bitmap? + fun notifyErrorTitle(): String fun notifyErrorText(): String diff --git a/legacy/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt b/legacy/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt index 4c4837b30fc..bd26ab90473 100644 --- a/legacy/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt +++ b/legacy/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationCreator.kt @@ -1,8 +1,14 @@ package com.fsck.k9.notification +import android.app.Application import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.WearableExtender +import androidx.core.graphics.drawable.IconCompat import com.fsck.k9.notification.NotificationChannelManager.ChannelType +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import net.thunderbird.core.logging.legacy.Log import androidx.core.app.NotificationCompat.Builder as NotificationBuilder @@ -11,12 +17,15 @@ internal class SingleMessageNotificationCreator( private val actionCreator: NotificationActionCreator, private val resourceProvider: NotificationResourceProvider, private val lockScreenNotificationCreator: LockScreenNotificationCreator, + private val application: Application, ) { + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + fun createSingleNotification( baseNotificationData: BaseNotificationData, singleNotificationData: SingleNotificationData, isGroupSummary: Boolean = false, - ) { + ) = scope.launch { val account = baseNotificationData.account val notificationId = singleNotificationData.notificationId val content = singleNotificationData.content @@ -27,10 +36,11 @@ internal class SingleMessageNotificationCreator( .setGroupSummary(isGroupSummary) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) .setSmallIcon(resourceProvider.iconNewMail) + .setAvatar(singleNotificationData.content) .setColor(baseNotificationData.color) .setWhen(singleNotificationData.timestamp) .setTicker(content.summary) - .setContentTitle(content.sender) + .setContentTitle(content.sender.personal) .setContentText(content.subject) .setSubText(baseNotificationData.accountName) .setBigText(content.preview) @@ -52,6 +62,12 @@ internal class SingleMessageNotificationCreator( notificationHelper.notify(account, notificationId, notification) } + private suspend fun NotificationBuilder.setAvatar(content: NotificationContent) = apply { + resourceProvider.avatar(content.sender)?.let { + setLargeIcon(IconCompat.createWithAdaptiveBitmap(it).toIcon(application)) + } + } + private fun NotificationBuilder.setBigText(text: CharSequence) = apply { setStyle(NotificationCompat.BigTextStyle().bigText(text)) } diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/BaseNotificationDataCreatorTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/BaseNotificationDataCreatorTest.kt index 890e9a6d7a1..5750832a905 100644 --- a/legacy/core/src/test/java/com/fsck/k9/notification/BaseNotificationDataCreatorTest.kt +++ b/legacy/core/src/test/java/com/fsck/k9/notification/BaseNotificationDataCreatorTest.kt @@ -7,6 +7,7 @@ import assertk.assertions.isNotNull import assertk.assertions.isSameInstanceAs import com.fsck.k9.K9 import com.fsck.k9.K9.LockScreenNotificationVisibility +import com.fsck.k9.mail.Address import net.thunderbird.core.android.account.Identity import net.thunderbird.core.android.account.LegacyAccountDto import net.thunderbird.feature.notification.NotificationLight @@ -129,7 +130,8 @@ class BaseNotificationDataCreatorTest { assertThat(result.lockScreenNotificationData).isInstanceOf() val senderNamesData = result.lockScreenNotificationData as LockScreenNotificationData.SenderNames - assertThat(senderNamesData.senderNames).isEqualTo("Sender One, Sender Two, Sender Three") + assertThat(senderNamesData.senderNames) + .isEqualTo("Sender One , Sender Two , Sender Three ") } @Test @@ -201,7 +203,7 @@ class BaseNotificationDataCreatorTest { timestamp = 0L, content = NotificationContent( messageReference = mock(), - sender = sender, + sender = Address("irrelevant", sender), preview = "irrelevant", summary = "irrelevant", subject = "irrelevant", diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt index 1f903ef1c84..7fb51970470 100644 --- a/legacy/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt +++ b/legacy/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt @@ -13,6 +13,7 @@ import assertk.assertions.isInstanceOf import assertk.assertions.isNotNull import assertk.assertions.isNull import assertk.assertions.isTrue +import com.fsck.k9.mail.Address import com.fsck.k9.mailstore.LocalMessage import com.fsck.k9.mailstore.LocalStore import com.fsck.k9.mailstore.LocalStoreProvider @@ -116,7 +117,7 @@ class NewMailNotificationManagerTest { assertThat(result.singleNotificationData.first().content).isEqualTo( NotificationContent( messageReference = createMessageReference("msg-1"), - sender = "sender", + sender = Address("irrelevant", "sender"), subject = "subject", preview = "preview", summary = "summary", @@ -153,7 +154,7 @@ class NewMailNotificationManagerTest { assertThat(result.singleNotificationData.first().content).isEqualTo( NotificationContent( messageReference = createMessageReference("msg-2"), - sender = "Zoe", + sender = Address("irrelevant", "Zoe"), subject = "Meeting", preview = "We need to talk", summary = "Zoe Meeting", @@ -320,7 +321,7 @@ class NewMailNotificationManagerTest { assertThat(singleNotificationData.content).isEqualTo( NotificationContent( messageReference = createMessageReference("msg-restore"), - sender = "Alice", + sender = Address("irrelevant", "Alice"), subject = "Another one", preview = "Are you tired of me yet?", summary = "Alice Another one", @@ -362,7 +363,7 @@ class NewMailNotificationManagerTest { assertThat(singleNotificationData.content).isEqualTo( NotificationContent( messageReference = createMessageReference("uid-1"), - sender = "Sender", + sender = Address("irrelevant", "Sender"), subject = "Subject", preview = "Preview", summary = "Summary", @@ -375,7 +376,7 @@ class NewMailNotificationManagerTest { assertThat(summaryNotificationData.singleNotificationData.content).isEqualTo( NotificationContent( messageReference = createMessageReference("uid-1"), - sender = "Sender", + sender = Address("irrelevant", "Sender"), subject = "Subject", preview = "Preview", summary = "Summary", @@ -445,7 +446,7 @@ class NewMailNotificationManagerTest { on { createFromMessage(account, message) } doReturn NotificationContent( messageReference = createMessageReference(messageUid), - sender, + sender = Address("irrelevant", sender), subject, preview, summary, @@ -473,7 +474,7 @@ class NewMailNotificationManagerTest { on { createFromMessage(account, message) } doReturn NotificationContent( messageReference = createMessageReference(messageUid), - sender, + Address("irrelevant", sender), subject, preview, summary, diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt index db2b8191ab0..2a6fe677f15 100644 --- a/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt +++ b/legacy/core/src/test/java/com/fsck/k9/notification/NotificationContentCreatorTest.kt @@ -40,7 +40,7 @@ class NotificationContentCreatorTest : RobolectricTest() { val content = contentCreator.createFromMessage(account, message) assertThat(content.messageReference).isEqualTo(messageReference) - assertThat(content.sender).isEqualTo(SENDER_NAME) + assertThat(content.sender.personal).isEqualTo(SENDER_NAME) assertThat(content.subject).isEqualTo(SUBJECT) assertThat(content.preview.toString()).isEqualTo("$SUBJECT\n$PREVIEW") assertThat(content.summary.toString()).isEqualTo("$SENDER_NAME $SUBJECT") @@ -106,8 +106,8 @@ class NotificationContentCreatorTest : RobolectricTest() { val content = contentCreator.createFromMessage(account, message) - assertThat(content.sender).isEqualTo("No sender") - assertThat(content.summary.toString()).isEqualTo(SUBJECT) + assertThat(content.sender.personal).isEqualTo("No sender") + assertThat(content.summary.toString()).isEqualTo("No sender $SUBJECT") } @Test @@ -118,7 +118,7 @@ class NotificationContentCreatorTest : RobolectricTest() { val content = contentCreator.createFromMessage(account, message) - assertThat(content.sender).isEqualTo("To:Bob") + assertThat(content.sender.personal).isEqualTo("To:Bob") assertThat(content.summary.toString()).isEqualTo("To:Bob $SUBJECT") } @@ -133,10 +133,10 @@ class NotificationContentCreatorTest : RobolectricTest() { val content = contentCreator.createFromMessage(account, message) - assertThat(content.sender).isEqualTo("No sender") + assertThat(content.sender.personal).isEqualTo("No sender") assertThat(content.subject).isEqualTo("(No subject)") assertThat(content.preview.toString()).isEqualTo("(No subject)") - assertThat(content.summary.toString()).isEqualTo("(No subject)") + assertThat(content.summary.toString()).isEqualTo("No sender (No subject)") } private fun createNotificationContentCreator(): NotificationContentCreator { diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt index f0b466dffa6..0aa84b522d5 100644 --- a/legacy/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt +++ b/legacy/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt @@ -12,6 +12,7 @@ import assertk.assertions.isNotNull import assertk.assertions.isNull import assertk.assertions.isSameInstanceAs import assertk.assertions.isTrue +import com.fsck.k9.mail.Address import kotlin.test.assertNotNull import net.thunderbird.core.android.account.LegacyAccountDto import net.thunderbird.core.android.testing.RobolectricTest @@ -247,7 +248,7 @@ class NotificationDataStoreTest : RobolectricTest() { private fun createNotificationContent(messageReference: MessageReference): NotificationContent { return NotificationContent( messageReference = messageReference, - sender = "irrelevant", + sender = Address("irrelevant", "irrelevant"), subject = "irrelevant", preview = "irrelevant", summary = "irrelevant", diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/SingleMessageNotificationDataCreatorTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/SingleMessageNotificationDataCreatorTest.kt index 7c8053b6218..706a70a3033 100644 --- a/legacy/core/src/test/java/com/fsck/k9/notification/SingleMessageNotificationDataCreatorTest.kt +++ b/legacy/core/src/test/java/com/fsck/k9/notification/SingleMessageNotificationDataCreatorTest.kt @@ -9,6 +9,7 @@ import assertk.assertions.isFalse import assertk.assertions.isTrue import com.fsck.k9.K9 import com.fsck.k9.K9.NotificationQuickDelete +import com.fsck.k9.mail.Address import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -265,7 +266,7 @@ class SingleMessageNotificationDataCreatorTest { private fun createNotificationContent() = NotificationContent( messageReference = MessageReference("irrelevant", 1, "irrelevant"), - sender = "irrelevant", + sender = Address("irrelevant", "irrelevant"), subject = "irrelevant", preview = "irrelevant", summary = "irrelevant", diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt b/legacy/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt index 7b37e05ef7e..22bb128ca0f 100644 --- a/legacy/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt +++ b/legacy/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt @@ -9,6 +9,7 @@ import assertk.assertions.isFalse import assertk.assertions.isInstanceOf import assertk.assertions.isTrue import com.fsck.k9.K9 +import com.fsck.k9.mail.Address import java.util.Calendar import kotlin.time.Clock import kotlin.time.ExperimentalTime @@ -315,7 +316,7 @@ class SummaryNotificationDataCreatorTest { private fun createNotificationContent() = NotificationContent( messageReference = MessageReference("irrelevant", 1, "irrelevant"), - sender = "irrelevant", + sender = Address("irrelevant", "irrelevant"), subject = "irrelevant", preview = "irrelevant", summary = "irrelevant", diff --git a/legacy/core/src/test/java/com/fsck/k9/notification/TestNotificationResourceProvider.kt b/legacy/core/src/test/java/com/fsck/k9/notification/TestNotificationResourceProvider.kt index e37e6161dd2..044384a4ed3 100644 --- a/legacy/core/src/test/java/com/fsck/k9/notification/TestNotificationResourceProvider.kt +++ b/legacy/core/src/test/java/com/fsck/k9/notification/TestNotificationResourceProvider.kt @@ -1,5 +1,8 @@ package com.fsck.k9.notification +import android.graphics.Bitmap +import com.fsck.k9.mail.Address + class TestNotificationResourceProvider : NotificationResourceProvider { override val iconWarning: Int = 1 override val iconMarkAsRead: Int = 2 @@ -36,6 +39,8 @@ class TestNotificationResourceProvider : NotificationResourceProvider { "Tap to open notification settings." } + override suspend fun avatar(address: Address): Bitmap? = null + override fun certificateErrorTitle(): String = "Certificate error" override fun certificateErrorTitle(accountName: String): String = "Certificate error for $accountName"