Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b7526588 authored by cketti's avatar cketti
Browse files

Separate notification UI/UX logic from notification creation

parent c0c0e05a
Loading
Loading
Loading
Loading
+52 −0
Original line number Diff line number Diff line
package com.fsck.k9.notification

import com.fsck.k9.Account
import com.fsck.k9.K9
import com.fsck.k9.K9.LockScreenNotificationVisibility

private const val MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION = 5

internal class BaseNotificationDataCreator {

    fun createBaseNotificationData(notificationData: NotificationData): BaseNotificationData {
        val account = notificationData.account
        return BaseNotificationData(
            account = account,
            groupKey = NotificationGroupKeys.getGroupKey(account),
            accountName = getAccountName(account),
            color = account.chipColor,
            newMessagesCount = notificationData.newMessagesCount,
            lockScreenNotificationData = createLockScreenNotificationData(notificationData),
            appearance = createNotificationAppearance(account)
        )
    }

    private fun getAccountName(account: Account): String {
        val accountDescription = account.description?.takeIf { it.isNotEmpty() }
        return accountDescription ?: account.email
    }

    private fun createLockScreenNotificationData(data: NotificationData): LockScreenNotificationData {
        return when (K9.lockScreenNotificationVisibility) {
            LockScreenNotificationVisibility.NOTHING -> LockScreenNotificationData.None
            LockScreenNotificationVisibility.APP_NAME -> LockScreenNotificationData.AppName
            LockScreenNotificationVisibility.EVERYTHING -> LockScreenNotificationData.Public
            LockScreenNotificationVisibility.MESSAGE_COUNT -> LockScreenNotificationData.MessageCount
            LockScreenNotificationVisibility.SENDERS -> LockScreenNotificationData.SenderNames(getSenderNames(data))
        }
    }

    private fun getSenderNames(data: NotificationData): String {
        return data.getContentForSummaryNotification().asSequence()
            .map { it.sender }
            .distinct()
            .take(MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION)
            .joinToString()
    }

    private fun createNotificationAppearance(account: Account): NotificationAppearance {
        return with(account.notificationSetting) {
            NotificationAppearance(ringtone = ringtone, vibrationPattern = vibration, ledColor = ledColor)
        }
    }
}
+21 −7
Original line number Diff line number Diff line
@@ -56,31 +56,45 @@ val coreNotificationModule = module {
    }
    single {
        NewMailNotificationController(
            notificationHelper = get(),
            contentCreator = get(),
            notificationManager = get(),
            newMailNotificationManager = get(),
            summaryNotificationCreator = get(),
            singleMessageNotificationCreator = get()
        )
    }
    single { NotificationContentCreator(context = get(), resourceProvider = get()) }
    single {
        NewMailNotificationManager(
            contentCreator = get(),
            baseNotificationDataCreator = get(),
            singleMessageNotificationDataCreator = get(),
            summaryNotificationDataCreator = get(),
            clock = get()
        )
    }
    factory { NotificationContentCreator(context = get(), resourceProvider = get()) }
    factory { BaseNotificationDataCreator() }
    factory { SingleMessageNotificationDataCreator() }
    factory { SummaryNotificationDataCreator(singleMessageNotificationDataCreator = get()) }
    factory {
        SingleMessageNotificationCreator(
            notificationHelper = get(),
            actionCreator = get(),
            resourceProvider = get(),
            lockScreenNotificationCreator = get()
            lockScreenNotificationCreator = get(),
            notificationManager = get()
        )
    }
    single {
    factory {
        SummaryNotificationCreator(
            notificationHelper = get(),
            actionCreator = get(),
            lockScreenNotificationCreator = get(),
            singleMessageNotificationCreator = get(),
            resourceProvider = get()
            resourceProvider = get(),
            notificationManager = get()
        )
    }
    single { LockScreenNotificationCreator(notificationHelper = get(), resourceProvider = get()) }
    factory { LockScreenNotificationCreator(notificationHelper = get(), resourceProvider = get()) }
    single {
        PushNotificationManager(
            context = get(),
+25 −48
Original line number Diff line number Diff line
@@ -2,82 +2,59 @@ package com.fsck.k9.notification

import android.app.Notification
import androidx.core.app.NotificationCompat
import com.fsck.k9.K9
import com.fsck.k9.K9.LockScreenNotificationVisibility

internal class LockScreenNotificationCreator(
    private val notificationHelper: NotificationHelper,
    private val resourceProvider: NotificationResourceProvider
) {
    fun configureLockScreenNotification(builder: NotificationCompat.Builder, notificationData: NotificationData) {
        when (K9.lockScreenNotificationVisibility) {
            LockScreenNotificationVisibility.NOTHING -> {
    fun configureLockScreenNotification(
        builder: NotificationCompat.Builder,
        baseNotificationData: BaseNotificationData
    ) {
        when (baseNotificationData.lockScreenNotificationData) {
            LockScreenNotificationData.None -> {
                builder.setVisibility(NotificationCompat.VISIBILITY_SECRET)
            }
            LockScreenNotificationVisibility.APP_NAME -> {
            LockScreenNotificationData.AppName -> {
                builder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
            }
            LockScreenNotificationVisibility.EVERYTHING -> {
            LockScreenNotificationData.Public -> {
                builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            }
            LockScreenNotificationVisibility.SENDERS -> {
                val publicNotification = createPublicNotificationWithSenderList(notificationData)
            is LockScreenNotificationData.SenderNames -> {
                val publicNotification = createPublicNotificationWithSenderList(baseNotificationData)
                builder.setPublicVersion(publicNotification)
            }
            LockScreenNotificationVisibility.MESSAGE_COUNT -> {
                val publicNotification = createPublicNotificationWithNewMessagesCount(notificationData)
            LockScreenNotificationData.MessageCount -> {
                val publicNotification = createPublicNotificationWithNewMessagesCount(baseNotificationData)
                builder.setPublicVersion(publicNotification)
            }
        }
    }

    private fun createPublicNotificationWithSenderList(notificationData: NotificationData): Notification {
        val builder = createPublicNotification(notificationData)

        val newMessages = notificationData.newMessagesCount
        if (newMessages == 1) {
            val holder = notificationData.holderForLatestNotification
            builder.setContentText(holder.content.sender)
        } else {
            val contents = notificationData.getContentForSummaryNotification()
            val senderList = createCommaSeparatedListOfSenders(contents)
            builder.setContentText(senderList)
    private fun createPublicNotificationWithSenderList(baseNotificationData: BaseNotificationData): Notification {
        val notificationData = baseNotificationData.lockScreenNotificationData as LockScreenNotificationData.SenderNames
        return createPublicNotification(baseNotificationData)
            .setContentText(notificationData.senderNames)
            .build()
    }

        return builder.build()
    }

    private fun createPublicNotificationWithNewMessagesCount(notificationData: NotificationData): Notification {
        val builder = createPublicNotification(notificationData)
        val account = notificationData.account
        val accountName = notificationHelper.getAccountName(account)

        builder.setContentText(accountName)
        return builder.build()
    private fun createPublicNotificationWithNewMessagesCount(baseNotificationData: BaseNotificationData): Notification {
        return createPublicNotification(baseNotificationData)
            .setContentText(baseNotificationData.accountName)
            .build()
    }

    private fun createPublicNotification(notificationData: NotificationData): NotificationCompat.Builder {
        val account = notificationData.account
        val newMessagesCount = notificationData.newMessagesCount
    private fun createPublicNotification(baseNotificationData: BaseNotificationData): NotificationCompat.Builder {
        val account = baseNotificationData.account
        val newMessagesCount = baseNotificationData.newMessagesCount
        val title = resourceProvider.newMessagesTitle(newMessagesCount)

        return notificationHelper.createNotificationBuilder(account, NotificationChannelManager.ChannelType.MESSAGES)
            .setSmallIcon(resourceProvider.iconNewMail)
            .setColor(account.chipColor)
            .setColor(baseNotificationData.color)
            .setNumber(newMessagesCount)
            .setContentTitle(title)
            .setCategory(NotificationCompat.CATEGORY_EMAIL)
    }

    fun createCommaSeparatedListOfSenders(contents: List<NotificationContent>): String {
        return contents.asSequence()
            .map { it.sender }
            .distinct()
            .take(MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION)
            .joinToString()
    }

    companion object {
        const val MAX_NUMBER_OF_SENDERS_IN_LOCK_SCREEN_NOTIFICATION = 5
    }
}
+30 −105
Original line number Diff line number Diff line
package com.fsck.k9.notification

import android.util.SparseArray
import androidx.core.app.NotificationManagerCompat
import com.fsck.k9.Account
import com.fsck.k9.controller.MessageReference
@@ -9,135 +8,61 @@ import com.fsck.k9.mailstore.LocalMessage
/**
 * Handle notifications for new messages.
 */
internal open class NewMailNotificationController(
    private val notificationHelper: NotificationHelper,
    private val contentCreator: NotificationContentCreator,
internal class NewMailNotificationController(
    private val notificationManager: NotificationManagerCompat,
    private val newMailNotificationManager: NewMailNotificationManager,
    private val summaryNotificationCreator: SummaryNotificationCreator,
    private val singleMessageNotificationCreator: SingleMessageNotificationCreator
) {
    private val notifications = SparseArray<NotificationData>()
    private val lock = Any()

    fun addNewMailNotification(account: Account, message: LocalMessage, silent: Boolean) {
        val content = contentCreator.createFromMessage(account, message)

        synchronized(lock) {
            val notificationData = getOrCreateNotificationData(account)

            val result = notificationData.addNotificationContent(content)
            if (result.shouldCancelNotification) {
                val notificationId = result.notificationId
                cancelNotification(notificationId)
            }

            if (notificationData.isSingleMessageNotification) {
                createSingleMessageNotificationWithLockScreenNotification(account, notificationData)
            } else {
                createSingleMessageNotification(account, result.notificationHolder)
            }
        val notificationData = newMailNotificationManager.addNewMailNotification(account, message, silent)

            createSummaryNotification(account, notificationData, silent)
        }
        processNewMailNotificationData(notificationData)
    }

    fun removeNewMailNotification(account: Account, messageReference: MessageReference) {
        synchronized(lock) {
            val notificationData = getNotificationData(account) ?: return

            val result = notificationData.removeNotificationForMessage(messageReference)
            if (result.isUnknownNotification) return

            cancelNotification(result.notificationId)
        val notificationData = newMailNotificationManager.removeNewMailNotification(account, messageReference)

            if (notificationData.isSingleMessageNotification) {
                createSingleMessageNotificationWithLockScreenNotification(account, notificationData)
            } else if (result.shouldCreateNotification) {
                createSingleMessageNotification(account, result.notificationHolder)
            }

            updateSummaryNotification(account, notificationData)
        if (notificationData != null) {
            processNewMailNotificationData(notificationData)
        }
    }

    fun clearNewMailNotifications(account: Account) {
        val notificationData = synchronized(lock) { removeNotificationData(account) } ?: return

        for (notificationId in notificationData.getActiveNotificationIds()) {
            cancelNotification(notificationId)
        }
        val cancelNotificationIds = newMailNotificationManager.clearNewMailNotifications(account)

        val notificationId = NotificationIds.getNewMailSummaryNotificationId(account)
        cancelNotification(notificationId)
        cancelNotifications(cancelNotificationIds)
    }

    private fun getOrCreateNotificationData(account: Account): NotificationData {
        val notificationData = getNotificationData(account)
        if (notificationData != null) return notificationData

        val accountNumber = account.accountNumber
        val newNotificationHolder = createNotificationData(account)
        notifications.put(accountNumber, newNotificationHolder)

        return newNotificationHolder
    }
    private fun processNewMailNotificationData(notificationData: NewMailNotificationData) {
        cancelNotifications(notificationData.cancelNotificationIds)

    private fun getNotificationData(account: Account): NotificationData? {
        val accountNumber = account.accountNumber
        return notifications[accountNumber]
        for (singleNotificationData in notificationData.singleNotificationData) {
            createSingleNotification(notificationData.baseNotificationData, singleNotificationData)
        }

    private fun removeNotificationData(account: Account): NotificationData? {
        val accountNumber = account.accountNumber
        val notificationData = notifications[accountNumber]
        notifications.remove(accountNumber)
        return notificationData
        notificationData.summaryNotificationData?.let { summaryNotificationData ->
            createSummaryNotification(notificationData.baseNotificationData, summaryNotificationData)
        }

    protected open fun createNotificationData(account: Account): NotificationData {
        return NotificationData(account)
    }

    private fun cancelNotification(notificationId: Int) {
    private fun cancelNotifications(notificationIds: List<Int>) {
        for (notificationId in notificationIds) {
            notificationManager.cancel(notificationId)
        }

    private fun updateSummaryNotification(account: Account, notificationData: NotificationData) {
        if (notificationData.newMessagesCount == 0) {
            clearNewMailNotifications(account)
        } else {
            createSummaryNotification(account, notificationData, silent = true)
        }
    }

    private fun createSummaryNotification(account: Account, notificationData: NotificationData, silent: Boolean) {
        val notification = summaryNotificationCreator.buildSummaryNotification(account, notificationData, silent)
        val notificationId = NotificationIds.getNewMailSummaryNotificationId(account)
        notificationManager.notify(notificationId, notification)
    }

    private fun createSingleMessageNotification(account: Account, holder: NotificationHolder) {
        val notification = singleMessageNotificationCreator.buildSingleMessageNotification(account, holder)
        val notificationId = holder.notificationId
        notificationManager.notify(notificationId, notification)
    private fun createSingleNotification(
        baseNotificationData: BaseNotificationData,
        singleNotificationData: SingleNotificationData
    ) {
        singleMessageNotificationCreator.createSingleNotification(baseNotificationData, singleNotificationData)
    }

    // When there's only one notification the "public version" of the notification that might be displayed on a secure
    // lockscreen isn't taken from the summary notification, but from the single "grouped" notification.
    private fun createSingleMessageNotificationWithLockScreenNotification(
        account: Account,
        notificationData: NotificationData
    private fun createSummaryNotification(
        baseNotificationData: BaseNotificationData,
        summaryNotificationData: SummaryNotificationData
    ) {
        val holder = notificationData.holderForLatestNotification
        val notification = singleMessageNotificationCreator.buildSingleMessageNotificationWithLockScreenNotification(
            account,
            holder,
            notificationData
        )

        val notificationId = holder.notificationId
        notificationManager.notify(notificationId, notification)
        summaryNotificationCreator.createSummaryNotification(baseNotificationData, summaryNotificationData)
    }

    private val notificationManager: NotificationManagerCompat
        get() = notificationHelper.getNotificationManager()
}
+87 −0
Original line number Diff line number Diff line
package com.fsck.k9.notification

import com.fsck.k9.Account
import com.fsck.k9.controller.MessageReference

internal data class NewMailNotificationData(
    val cancelNotificationIds: List<Int>,
    val baseNotificationData: BaseNotificationData,
    val singleNotificationData: List<SingleNotificationData>,
    val summaryNotificationData: SummaryNotificationData?
)

internal data class BaseNotificationData(
    val account: Account,
    val accountName: String,
    val groupKey: String,
    val color: Int,
    val newMessagesCount: Int,
    val lockScreenNotificationData: LockScreenNotificationData,
    val appearance: NotificationAppearance
)

internal sealed interface LockScreenNotificationData {
    object None : LockScreenNotificationData
    object AppName : LockScreenNotificationData
    object Public : LockScreenNotificationData
    object MessageCount : LockScreenNotificationData
    data class SenderNames(val senderNames: String) : LockScreenNotificationData
}

internal data class NotificationAppearance(
    val ringtone: String?,
    val vibrationPattern: LongArray?,
    val ledColor: Int?
)

internal data class SingleNotificationData(
    val notificationId: Int,
    val isSilent: Boolean,
    val timestamp: Long,
    val content: NotificationContent,
    val actions: List<NotificationAction>,
    val wearActions: List<WearNotificationAction>,
    val addLockScreenNotification: Boolean
)

internal sealed interface SummaryNotificationData

internal data class SummarySingleNotificationData(
    val singleNotificationData: SingleNotificationData
) : SummaryNotificationData

internal data class SummaryInboxNotificationData(
    val notificationId: Int,
    val isSilent: Boolean,
    val timestamp: Long,
    val content: List<CharSequence>,
    val additionalMessagesCount: Int,
    val messageReferences: List<MessageReference>,
    val actions: List<SummaryNotificationAction>,
    val wearActions: List<SummaryWearNotificationAction>
) : SummaryNotificationData

internal enum class NotificationAction {
    Reply,
    MarkAsRead,
    Delete
}

internal enum class WearNotificationAction {
    Reply,
    MarkAsRead,
    Delete,
    Archive,
    Spam
}

internal enum class SummaryNotificationAction {
    MarkAsRead,
    Delete
}

internal enum class SummaryWearNotificationAction {
    MarkAsRead,
    Delete,
    Archive
}
Loading