From a5f08af00bb479c8344bee31ca2d56f346be0bcd Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Fri, 14 Feb 2025 10:08:55 +0100 Subject: [PATCH 01/13] feat: Filter snoozed threads in inbox and snooze folders --- .../cache/mailboxContent/RefreshController.kt | 6 +++++- .../cache/mailboxContent/ThreadController.kt | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt index 757d0b36fd..3683cf3756 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt @@ -329,7 +329,11 @@ class RefreshController @Inject constructor( var inboxUnreadCount: Int? = null FolderController.updateFolder(folder.id, realm = this) { mutableRealm, it -> - val allCurrentFolderThreads = ThreadController.getThreadsByFolderId(it.id, realm = mutableRealm) + val allCurrentFolderThreads = when (folder.role) { + FolderRole.INBOX -> ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = false, realm = mutableRealm) + FolderRole.SNOOZED -> ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = true, realm = mutableRealm) + else -> ThreadController.getThreadsByFolderId(it.id, realm = mutableRealm) + } it.threads.replaceContent(list = allCurrentFolderThreads) inboxUnreadCount = updateFoldersUnreadCount( diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt index e26f8c46ad..d23595f243 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt @@ -24,6 +24,7 @@ import com.infomaniak.mail.data.api.ApiRepository import com.infomaniak.mail.data.cache.RealmDatabase import com.infomaniak.mail.data.models.Folder import com.infomaniak.mail.data.models.Folder.FolderRole +import com.infomaniak.mail.data.models.SnoozeState import com.infomaniak.mail.data.models.SwissTransferContainer import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.message.Message.MessageInitialState @@ -242,6 +243,21 @@ class ThreadController @Inject constructor( return realm.query("${Thread::folderId.name} == $0", folderId) } + private fun getThreadsWithSnoozeFilterQuery( + withSnooze: Boolean, + folderId: String, + realm: TypedRealm, + ): RealmQuery { + val snoozeState = SnoozeState.Snoozed.apiValue + val collectionOperator = if (withSnooze) "ANY" else "NONE" + + return realm.query( + "${Thread::folderId.name} == $0 AND $collectionOperator messages._snoozeState == $1", + folderId, + snoozeState, + ) + } + private fun getThreadQuery(uid: String, realm: TypedRealm): RealmSingleQuery { return realm.query("${Thread::uid.name} == $0", uid).first() } @@ -277,6 +293,11 @@ class ThreadController @Inject constructor( fun getThreadsByFolderId(folderId: String, realm: TypedRealm): RealmResults { return getThreadsByFolderIdQuery(folderId, realm).find() } + + fun getInboxThreadsWithSnoozeFilter(withSnooze: Boolean, realm: TypedRealm): List { + val inboxId = FolderController.getFolder(FolderRole.INBOX, realm)?.id ?: return emptyList() + return getThreadsWithSnoozeFilterQuery(withSnooze, folderId = inboxId, realm).find() + } //endregion //region Edit data From 7c8c4b3b8a18d1db27fc2ebe5c8553206c2b445f Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Fri, 14 Feb 2025 10:36:13 +0100 Subject: [PATCH 02/13] feat: Do not define thread query strategy in the middle of nowhere inside the code --- .../cache/mailboxContent/RefreshController.kt | 6 +----- .../com/infomaniak/mail/data/models/Folder.kt | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt index 3683cf3756..ad1c46eba1 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt @@ -329,11 +329,7 @@ class RefreshController @Inject constructor( var inboxUnreadCount: Int? = null FolderController.updateFolder(folder.id, realm = this) { mutableRealm, it -> - val allCurrentFolderThreads = when (folder.role) { - FolderRole.INBOX -> ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = false, realm = mutableRealm) - FolderRole.SNOOZED -> ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = true, realm = mutableRealm) - else -> ThreadController.getThreadsByFolderId(it.id, realm = mutableRealm) - } + val allCurrentFolderThreads = folder.threadQueryStrategy(folder.role, folder.id).applyStrategy(mutableRealm) it.threads.replaceContent(list = allCurrentFolderThreads) inboxUnreadCount = updateFoldersUnreadCount( diff --git a/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt b/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt index 2e2bf2f8bc..a0e090ec09 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt +++ b/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt @@ -26,6 +26,7 @@ import com.infomaniak.lib.core.utils.Utils.enumValueOfOrNull import com.infomaniak.lib.core.utils.removeAccents import com.infomaniak.mail.R import com.infomaniak.mail.data.cache.mailboxContent.MessageController +import com.infomaniak.mail.data.cache.mailboxContent.ThreadController import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread import com.infomaniak.mail.utils.SentryDebug @@ -146,6 +147,16 @@ class Folder : RealmObject, Cloneable { fun messages(realm: TypedRealm): List = MessageController.getMessagesByFolderId(id, realm) + fun threadQueryStrategy(folderRole: FolderRole?, folderId: String): ThreadQueryStrategy { + return ThreadQueryStrategy { realm -> + when (folderRole) { + FolderRole.INBOX -> ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = false, realm = realm) + FolderRole.SNOOZED -> ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = true, realm = realm) + else -> ThreadController.getThreadsByFolderId(folderId, realm = realm) + } + } + } + fun getLocalizedName(context: Context): String { return role?.folderNameRes?.let(context::getString) ?: name } @@ -177,6 +188,10 @@ class Folder : RealmObject, Cloneable { ARCHIVE(R.string.archiveFolder, R.drawable.ic_archive_folder, 1, "archiveFolder"), } + class ThreadQueryStrategy(private val strategy: (TypedRealm) -> List) { + fun applyStrategy(realm: TypedRealm): List = strategy(realm) + } + companion object { val rolePropertyName = Folder::_role.name val parentsPropertyName = Folder::_parents.name From 35315b4ee611bc47a376648b4f7521239d73ea63 Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Fri, 21 Feb 2025 10:29:16 +0100 Subject: [PATCH 03/13] feat: Refresh snooze folder when refreshing the folders to refresh together --- .../mail/data/cache/mailboxContent/RefreshController.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt index ad1c46eba1..8564b29032 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt @@ -764,11 +764,14 @@ class RefreshController @Inject constructor( } //endregion + // SCHEDULED_DRAFTS and SNOOZED need to be refreshed often because the folders only appear in the menu folder when there is at + // least one email in it. private val FOLDER_ROLES_TO_REFRESH_TOGETHER = setOf( FolderRole.INBOX, FolderRole.SENT, FolderRole.DRAFT, FolderRole.SCHEDULED_DRAFTS, + FolderRole.SNOOZED, ) enum class RefreshMode { From 7cf4e9d6ae8b8a7fb8baa2ffdf59302543c3db60 Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Mon, 24 Feb 2025 15:11:00 +0100 Subject: [PATCH 04/13] feat: Introduce refresh strategy to customize behavior between folders --- .../mailboxContent/CustomRefreshStrategies.kt | 33 +++++++++++++++++++ .../cache/mailboxContent/RefreshController.kt | 2 +- .../cache/mailboxContent/RefreshStrategies.kt | 32 ++++++++++++++++++ .../cache/mailboxContent/ThreadController.kt | 4 +-- .../com/infomaniak/mail/data/models/Folder.kt | 19 +++-------- 5 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt create mode 100644 app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt new file mode 100644 index 0000000000..e99c570e38 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt @@ -0,0 +1,33 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.mail.data.cache.mailboxContent + +import com.infomaniak.mail.data.models.thread.Thread +import io.realm.kotlin.TypedRealm + +val inboxRefreshStrategy = object : DefaultRefreshStrategy { + override fun queryFolderThreads(folderId: String, realm: TypedRealm): List { + return ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = false, realm = realm) + } +} + +val snoozeRefreshStrategy = object : DefaultRefreshStrategy { + override fun queryFolderThreads(folderId: String, realm: TypedRealm): List { + return ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = true, realm = realm) + } +} diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt index 8564b29032..8dd0ed4075 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt @@ -329,7 +329,7 @@ class RefreshController @Inject constructor( var inboxUnreadCount: Int? = null FolderController.updateFolder(folder.id, realm = this) { mutableRealm, it -> - val allCurrentFolderThreads = folder.threadQueryStrategy(folder.role, folder.id).applyStrategy(mutableRealm) + val allCurrentFolderThreads = folder.refreshStrategy().queryFolderThreads(folder.id, mutableRealm) it.threads.replaceContent(list = allCurrentFolderThreads) inboxUnreadCount = updateFoldersUnreadCount( diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt new file mode 100644 index 0000000000..c9f636c139 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt @@ -0,0 +1,32 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2025 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.mail.data.cache.mailboxContent + +import com.infomaniak.mail.data.models.thread.Thread +import io.realm.kotlin.TypedRealm + +interface RefreshStrategy { + fun queryFolderThreads(folderId: String, realm: TypedRealm): List + fun shouldForceUpdateMessagesWhenAdded(): Boolean +} + +interface DefaultRefreshStrategy : RefreshStrategy { + override fun queryFolderThreads(folderId: String, realm: TypedRealm): List { + return ThreadController.getThreadsByFolderId(folderId, realm) + } +} diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt index d23595f243..d4592425ee 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt @@ -244,8 +244,8 @@ class ThreadController @Inject constructor( } private fun getThreadsWithSnoozeFilterQuery( - withSnooze: Boolean, folderId: String, + withSnooze: Boolean, realm: TypedRealm, ): RealmQuery { val snoozeState = SnoozeState.Snoozed.apiValue @@ -296,7 +296,7 @@ class ThreadController @Inject constructor( fun getInboxThreadsWithSnoozeFilter(withSnooze: Boolean, realm: TypedRealm): List { val inboxId = FolderController.getFolder(FolderRole.INBOX, realm)?.id ?: return emptyList() - return getThreadsWithSnoozeFilterQuery(withSnooze, folderId = inboxId, realm).find() + return getThreadsWithSnoozeFilterQuery(inboxId, withSnooze, realm).find() } //endregion diff --git a/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt b/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt index a0e090ec09..2459342ffc 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt +++ b/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt @@ -25,8 +25,7 @@ import androidx.annotation.StringRes import com.infomaniak.lib.core.utils.Utils.enumValueOfOrNull import com.infomaniak.lib.core.utils.removeAccents import com.infomaniak.mail.R -import com.infomaniak.mail.data.cache.mailboxContent.MessageController -import com.infomaniak.mail.data.cache.mailboxContent.ThreadController +import com.infomaniak.mail.data.cache.mailboxContent.* import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread import com.infomaniak.mail.utils.SentryDebug @@ -147,14 +146,10 @@ class Folder : RealmObject, Cloneable { fun messages(realm: TypedRealm): List = MessageController.getMessagesByFolderId(id, realm) - fun threadQueryStrategy(folderRole: FolderRole?, folderId: String): ThreadQueryStrategy { - return ThreadQueryStrategy { realm -> - when (folderRole) { - FolderRole.INBOX -> ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = false, realm = realm) - FolderRole.SNOOZED -> ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = true, realm = realm) - else -> ThreadController.getThreadsByFolderId(folderId, realm = realm) - } - } + fun refreshStrategy(): RefreshStrategy = when (role) { + FolderRole.INBOX -> inboxRefreshStrategy + FolderRole.SNOOZED -> snoozeRefreshStrategy + else -> object : DefaultRefreshStrategy {} } fun getLocalizedName(context: Context): String { @@ -188,10 +183,6 @@ class Folder : RealmObject, Cloneable { ARCHIVE(R.string.archiveFolder, R.drawable.ic_archive_folder, 1, "archiveFolder"), } - class ThreadQueryStrategy(private val strategy: (TypedRealm) -> List) { - fun applyStrategy(realm: TypedRealm): List = strategy(realm) - } - companion object { val rolePropertyName = Folder::_role.name val parentsPropertyName = Folder::_parents.name From 4a6cfebe7a75ec91043af25ad95f898b44c32a12 Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Mon, 24 Feb 2025 15:17:52 +0100 Subject: [PATCH 05/13] feat: Update already existing messages when added in snooze refresh strategy --- .../data/cache/mailboxContent/CustomRefreshStrategies.kt | 2 ++ .../mail/data/cache/mailboxContent/RefreshController.kt | 8 ++++++++ .../mail/data/cache/mailboxContent/RefreshStrategies.kt | 2 ++ 3 files changed, 12 insertions(+) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt index e99c570e38..fcd333f7d8 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt @@ -30,4 +30,6 @@ val snoozeRefreshStrategy = object : DefaultRefreshStrategy { override fun queryFolderThreads(folderId: String, realm: TypedRealm): List { return ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = true, realm = realm) } + + override fun shouldForceUpdateMessagesWhenAdded(): Boolean = true } diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt index 8dd0ed4075..9879a4c758 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt @@ -512,6 +512,7 @@ class RefreshController @Inject constructor( val impactedThreadsManaged = mutableSetOf() val addedMessagesUids = mutableListOf() + val shouldForceUpdateMessages = folder.refreshStrategy().shouldForceUpdateMessagesWhenAdded() remoteMessages.forEach { remoteMessage -> scope.ensureActive() @@ -520,6 +521,8 @@ class RefreshController @Inject constructor( addedMessagesUids.add(remoteMessage.shortUid) + if (shouldForceUpdateMessages) updateExistingMessage(remoteMessage) + val newThread = if (isConversationMode) { handleAddedMessage(scope, remoteMessage, impactedThreadsManaged) } else { @@ -542,6 +545,11 @@ class RefreshController @Inject constructor( return impactedThreadsUnmanaged } + private fun MutableRealm.updateExistingMessage(remoteMessage: Message) { + val isMessageAlreadyInRealm = MessageController.getMessage(remoteMessage.uid, realm = this) != null + if (isMessageAlreadyInRealm) MessageController.upsertMessage(remoteMessage, realm = this) + } + private fun initMessageLocalValues(remoteMessage: Message, folder: Folder) { remoteMessage.initLocalValues( MessageInitialState( diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt index c9f636c139..2457be8c0b 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt @@ -29,4 +29,6 @@ interface DefaultRefreshStrategy : RefreshStrategy { override fun queryFolderThreads(folderId: String, realm: TypedRealm): List { return ThreadController.getThreadsByFolderId(folderId, realm) } + + override fun shouldForceUpdateMessagesWhenAdded(): Boolean = false } From 82167f78afafbbb1ecba7f90e9d01b7b3bcb2bc2 Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Fri, 28 Feb 2025 14:24:32 +0100 Subject: [PATCH 06/13] feat: Reuse the defaultRefreshStrategy instance for all folders that need it --- .../mail/data/cache/mailboxContent/CustomRefreshStrategies.kt | 2 ++ app/src/main/java/com/infomaniak/mail/data/models/Folder.kt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt index fcd333f7d8..cca950bfd2 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt @@ -20,6 +20,8 @@ package com.infomaniak.mail.data.cache.mailboxContent import com.infomaniak.mail.data.models.thread.Thread import io.realm.kotlin.TypedRealm +val defaultRefreshStrategy = object : DefaultRefreshStrategy {} + val inboxRefreshStrategy = object : DefaultRefreshStrategy { override fun queryFolderThreads(folderId: String, realm: TypedRealm): List { return ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = false, realm = realm) diff --git a/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt b/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt index 2459342ffc..4bd308788b 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt +++ b/app/src/main/java/com/infomaniak/mail/data/models/Folder.kt @@ -149,7 +149,7 @@ class Folder : RealmObject, Cloneable { fun refreshStrategy(): RefreshStrategy = when (role) { FolderRole.INBOX -> inboxRefreshStrategy FolderRole.SNOOZED -> snoozeRefreshStrategy - else -> object : DefaultRefreshStrategy {} + else -> defaultRefreshStrategy } fun getLocalizedName(context: Context): String { From fdfd494bde64ee8c634eeb9f44b53b921dd548f4 Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Tue, 4 Mar 2025 10:41:03 +0100 Subject: [PATCH 07/13] feat: Filter snoozed threads to display with a stronger condition This mimics the behavior on the web and help avoid displaying threads that are in an incoherent state on the API --- .../mail/data/cache/mailboxContent/ThreadController.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt index d4592425ee..ff9ac35dbc 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt @@ -249,10 +249,18 @@ class ThreadController @Inject constructor( realm: TypedRealm, ): RealmQuery { val snoozeState = SnoozeState.Snoozed.apiValue + val collectionOperator = if (withSnooze) "ANY" else "NONE" + val nullOperator = if (withSnooze) "!=" else "==" + + val isSnoozedState = "$collectionOperator messages._snoozeState == $1" + + // TODO: Use the thread snooze state values instead of ANY/NONE on the messages + // This mimics the behavior on the web and help avoid displaying threads that are in an incoherent state on the API + val hasCorrectMetadata = "messages.snoozeEndDate $nullOperator null AND messages.snoozeAction $nullOperator null" return realm.query( - "${Thread::folderId.name} == $0 AND $collectionOperator messages._snoozeState == $1", + "${Thread::folderId.name} == $0 AND $isSnoozedState AND $hasCorrectMetadata", folderId, snoozeState, ) From 61f536786a197370919d95f67e8e9b2970aacd3b Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Tue, 4 Mar 2025 14:17:48 +0100 Subject: [PATCH 08/13] refactor: Base folder's thread query on the thread snooze state instead of the one of messages --- .../data/cache/mailboxContent/ThreadController.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt index ff9ac35dbc..1ab67ab4e7 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt @@ -250,17 +250,13 @@ class ThreadController @Inject constructor( ): RealmQuery { val snoozeState = SnoozeState.Snoozed.apiValue - val collectionOperator = if (withSnooze) "ANY" else "NONE" - val nullOperator = if (withSnooze) "!=" else "==" - - val isSnoozedState = "$collectionOperator messages._snoozeState == $1" - - // TODO: Use the thread snooze state values instead of ANY/NONE on the messages - // This mimics the behavior on the web and help avoid displaying threads that are in an incoherent state on the API - val hasCorrectMetadata = "messages.snoozeEndDate $nullOperator null AND messages.snoozeAction $nullOperator null" + // Checking for snoozeEndDate and snoozeAction on top of _snoozeState mimics the behavior on the web and helps avoid + // displaying threads that are in an incoherent state on the API + val isSnoozedState = "_snoozeState == $1 AND snoozeEndDate != null AND snoozeAction != null" + val snoozeQuery = if (withSnooze) isSnoozedState else "NOT($isSnoozedState)" return realm.query( - "${Thread::folderId.name} == $0 AND $isSnoozedState AND $hasCorrectMetadata", + "${Thread::folderId.name} == $0 AND $snoozeQuery", folderId, snoozeState, ) From 46db740742e3ce448bb7498638bb0206279f6d2b Mon Sep 17 00:00:00 2001 From: Kevin Boulongne Date: Thu, 6 Mar 2025 09:01:55 +0100 Subject: [PATCH 09/13] refactor: Only update Snoozed-related fields when adding already existing Message --- .../cache/mailboxContent/CustomRefreshStrategies.kt | 10 +++++++++- .../data/cache/mailboxContent/RefreshController.kt | 4 ++-- .../data/cache/mailboxContent/RefreshStrategies.kt | 6 ++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt index cca950bfd2..af2a1fdc20 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt @@ -17,7 +17,9 @@ */ package com.infomaniak.mail.data.cache.mailboxContent +import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread +import io.realm.kotlin.MutableRealm import io.realm.kotlin.TypedRealm val defaultRefreshStrategy = object : DefaultRefreshStrategy {} @@ -33,5 +35,11 @@ val snoozeRefreshStrategy = object : DefaultRefreshStrategy { return ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = true, realm = realm) } - override fun shouldForceUpdateMessagesWhenAdded(): Boolean = true + override fun updateExistingMessageWhenAdded(remoteMessage: Message, realm: MutableRealm) { + MessageController.updateMessage(remoteMessage.uid, realm = realm) { localMessage -> + localMessage?.snoozeState = remoteMessage.snoozeState + localMessage?.snoozeEndDate = remoteMessage.snoozeEndDate + localMessage?.snoozeAction = remoteMessage.snoozeAction + } + } } diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt index 9879a4c758..8c48ca4bfb 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt @@ -512,7 +512,7 @@ class RefreshController @Inject constructor( val impactedThreadsManaged = mutableSetOf() val addedMessagesUids = mutableListOf() - val shouldForceUpdateMessages = folder.refreshStrategy().shouldForceUpdateMessagesWhenAdded() + val refreshStrategy = folder.refreshStrategy() remoteMessages.forEach { remoteMessage -> scope.ensureActive() @@ -521,7 +521,7 @@ class RefreshController @Inject constructor( addedMessagesUids.add(remoteMessage.shortUid) - if (shouldForceUpdateMessages) updateExistingMessage(remoteMessage) + refreshStrategy.updateExistingMessageWhenAdded(remoteMessage, realm = this) val newThread = if (isConversationMode) { handleAddedMessage(scope, remoteMessage, impactedThreadsManaged) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt index 2457be8c0b..775cec73ae 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt @@ -17,12 +17,14 @@ */ package com.infomaniak.mail.data.cache.mailboxContent +import com.infomaniak.mail.data.models.message.Message import com.infomaniak.mail.data.models.thread.Thread +import io.realm.kotlin.MutableRealm import io.realm.kotlin.TypedRealm interface RefreshStrategy { fun queryFolderThreads(folderId: String, realm: TypedRealm): List - fun shouldForceUpdateMessagesWhenAdded(): Boolean + fun updateExistingMessageWhenAdded(remoteMessage: Message, realm: MutableRealm) } interface DefaultRefreshStrategy : RefreshStrategy { @@ -30,5 +32,5 @@ interface DefaultRefreshStrategy : RefreshStrategy { return ThreadController.getThreadsByFolderId(folderId, realm) } - override fun shouldForceUpdateMessagesWhenAdded(): Boolean = false + override fun updateExistingMessageWhenAdded(remoteMessage: Message, realm: MutableRealm) = Unit } From e5fdbdcc33e5afdb4a2a07c5f4c75898d541e34e Mon Sep 17 00:00:00 2001 From: Kevin Boulongne Date: Thu, 6 Mar 2025 14:58:24 +0100 Subject: [PATCH 10/13] refactor: Clean code --- .../mail/data/cache/mailboxContent/RefreshController.kt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt index 8c48ca4bfb..8d19c5d9ab 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt @@ -545,11 +545,6 @@ class RefreshController @Inject constructor( return impactedThreadsUnmanaged } - private fun MutableRealm.updateExistingMessage(remoteMessage: Message) { - val isMessageAlreadyInRealm = MessageController.getMessage(remoteMessage.uid, realm = this) != null - if (isMessageAlreadyInRealm) MessageController.upsertMessage(remoteMessage, realm = this) - } - private fun initMessageLocalValues(remoteMessage: Message, folder: Folder) { remoteMessage.initLocalValues( MessageInitialState( @@ -772,8 +767,8 @@ class RefreshController @Inject constructor( } //endregion - // SCHEDULED_DRAFTS and SNOOZED need to be refreshed often because the folders only appear in the menu folder when there is at - // least one email in it. + // SCHEDULED_DRAFTS and SNOOZED need to be refreshed often because these folders + // only appear in the MenuDrawer when there is at least 1 email in it. private val FOLDER_ROLES_TO_REFRESH_TOGETHER = setOf( FolderRole.INBOX, FolderRole.SENT, From 5ffa5a2ccd4f3402a28115fd4d49b5b38dbfaf6f Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Fri, 7 Mar 2025 09:40:13 +0100 Subject: [PATCH 11/13] style: Write query on a single line --- .../mail/data/cache/mailboxContent/ThreadController.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt index 1ab67ab4e7..6944f7b229 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt @@ -255,11 +255,7 @@ class ThreadController @Inject constructor( val isSnoozedState = "_snoozeState == $1 AND snoozeEndDate != null AND snoozeAction != null" val snoozeQuery = if (withSnooze) isSnoozedState else "NOT($isSnoozedState)" - return realm.query( - "${Thread::folderId.name} == $0 AND $snoozeQuery", - folderId, - snoozeState, - ) + return realm.query("${Thread::folderId.name} == $0 AND $snoozeQuery", folderId, snoozeState) } private fun getThreadQuery(uid: String, realm: TypedRealm): RealmSingleQuery { From 2cedcf7b294e40849587d61e7993efe034f20a46 Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Fri, 7 Mar 2025 09:51:00 +0100 Subject: [PATCH 12/13] refactor: Remove intermediate variable --- .../mail/data/cache/mailboxContent/ThreadController.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt index 6944f7b229..f0fde985d6 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt @@ -248,14 +248,12 @@ class ThreadController @Inject constructor( withSnooze: Boolean, realm: TypedRealm, ): RealmQuery { - val snoozeState = SnoozeState.Snoozed.apiValue - // Checking for snoozeEndDate and snoozeAction on top of _snoozeState mimics the behavior on the web and helps avoid // displaying threads that are in an incoherent state on the API val isSnoozedState = "_snoozeState == $1 AND snoozeEndDate != null AND snoozeAction != null" val snoozeQuery = if (withSnooze) isSnoozedState else "NOT($isSnoozedState)" - return realm.query("${Thread::folderId.name} == $0 AND $snoozeQuery", folderId, snoozeState) + return realm.query("${Thread::folderId.name} == $0 AND $snoozeQuery", folderId, SnoozeState.Snoozed.apiValue) } private fun getThreadQuery(uid: String, realm: TypedRealm): RealmSingleQuery { From 8fc41c1cb8c356a92c5544413f8c1c19be0e118e Mon Sep 17 00:00:00 2001 From: Kevin Boulongne Date: Fri, 7 Mar 2025 09:52:44 +0100 Subject: [PATCH 13/13] docs: Update comment --- .../mail/data/cache/mailboxContent/ThreadController.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt index f0fde985d6..acfe48ab4f 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/ThreadController.kt @@ -248,8 +248,8 @@ class ThreadController @Inject constructor( withSnooze: Boolean, realm: TypedRealm, ): RealmQuery { - // Checking for snoozeEndDate and snoozeAction on top of _snoozeState mimics the behavior on the web and helps avoid - // displaying threads that are in an incoherent state on the API + // Checking for snoozeEndDate and snoozeAction on top of _snoozeState mimics the webmail's behavior + // and helps to avoid displaying threads that are in an incoherent state on the API val isSnoozedState = "_snoozeState == $1 AND snoozeEndDate != null AND snoozeAction != null" val snoozeQuery = if (withSnooze) isSnoozedState else "NOT($isSnoozedState)"