Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add scheduled Drafts #2143

Merged
merged 70 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from 68 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
f8d3979
feat: Add scheduled emails
TommyDL-Infomaniak Jan 23, 2025
f05cdf3
fix: Use `tomorrow` instead of `getTomorrow`
KevinBoulongne Jan 23, 2025
6d7f7c4
fix: Add missing DB bump
KevinBoulongne Jan 23, 2025
c90a628
refactor: Invert `visibleFoldersOnly` default value
KevinBoulongne Jan 24, 2025
a021ee7
fix: Fix DB bump issue with default value in a new column
KevinBoulongne Jan 24, 2025
0788926
feat: Display correct recipient avatar & name in Scheduled drafts folder
KevinBoulongne Jan 24, 2025
cf820b1
docs: Add some explanatory comment
KevinBoulongne Jan 24, 2025
c46a89d
fix: The application is now able to create Drafts again
KevinBoulongne Jan 24, 2025
e74ac79
feat: Improve DraftAction logic
TommyDL-Infomaniak Jan 24, 2025
1734f72
refactor: Rename stuff
KevinBoulongne Jan 24, 2025
fb3260f
refactor: Update comments
KevinBoulongne Jan 24, 2025
dbd57c8
refactor: Remove unused fields
KevinBoulongne Jan 24, 2025
95eb305
chore: Update translations
TommyDL-Infomaniak Jan 28, 2025
80b3364
docs: Add explanatory comment
KevinBoulongne Jan 29, 2025
3152175
chore: Remove redundant scheduleDraft API route
TommyDL-Infomaniak Jan 29, 2025
2e6c205
feat: Hide the section title in SCHEDULED_DRAFTS folder
TommyDL-Infomaniak Jan 29, 2025
776ae46
chore: Update strings
KevinBoulongne Jan 30, 2025
c7f664f
refactor: Update UI
KevinBoulongne Jan 30, 2025
3b7a49d
fix: Remove wrongly added `+` in `@id`
KevinBoulongne Jan 30, 2025
e42298c
fix: Add missing copyrights
KevinBoulongne Jan 30, 2025
48ff455
fix: Remove `quickActionBar` blink when opening a Thread
KevinBoulongne Jan 30, 2025
608a297
refactor: Factorize Draft related ApiRepository functions
KevinBoulongne Jan 30, 2025
043341f
refactor: Clean code
KevinBoulongne Jan 30, 2025
eaa0183
refactor: Rename `dateAndTimeScheduleDialog` related stuff
KevinBoulongne Jan 30, 2025
2706fe8
refactor: Rename scheduled Draft related stuff
KevinBoulongne Jan 30, 2025
6804dfb
refactor: Clean delete Thread function
KevinBoulongne Jan 30, 2025
244d8d8
refactor: Revamp Scheduled Drafts region in MainViewModel actions
KevinBoulongne Jan 30, 2025
a762dc4
refactor: Rename Drafts worker functions
KevinBoulongne Jan 30, 2025
76945ff
fix: Make factorized `uploadDraft()` works
KevinBoulongne Jan 30, 2025
ec31168
fix: Remove `getDraft()` before opening NewMessageActivity with a pre…
KevinBoulongne Jan 30, 2025
3abb8ea
feat: Allow to delete a Scheduled Draft
KevinBoulongne Jan 30, 2025
b39fe5b
chore: Rename lastSelectedScheduleDate to lastSelectedScheduleEpoch
TommyDL-Infomaniak Jan 31, 2025
be6c67a
chore: Remove icon keyword in content descriptions
TommyDL-Infomaniak Jan 31, 2025
9be55ce
refactor: Refactor `isPermanentDeleteFolder()`
KevinBoulongne Jan 31, 2025
39303c4
refactor: Factorize date UI update in SelectDateAndTimeForScheduledDr…
KevinBoulongne Jan 31, 2025
4e8faa1
refactor: Clean code
KevinBoulongne Jan 31, 2025
933ce92
fix: Don't schedule a draft when cancelling it after choosing a sched…
KevinBoulongne Jan 31, 2025
917c879
refactor: Simplify NIGHT condition in `getTimeToDisplayFromDate()`
KevinBoulongne Jan 31, 2025
6a97a80
fix: Correctly update UI visibility to handle views recycling
KevinBoulongne Jan 31, 2025
8910007
chore: Remove some coupling in ScheduleSendBottomSheetDialog
TommyDL-Infomaniak Jan 31, 2025
afd0cbb
fix: Only the day was selected
TommyDL-Infomaniak Jan 31, 2025
60da211
fix: Wrong icon tint in ActionItemView
TommyDL-Infomaniak Jan 31, 2025
fb6cf06
refactor: Clean code
KevinBoulongne Feb 3, 2025
d2cab0a
refactor: Clean `getBackNavigationResult()` related code
KevinBoulongne Feb 3, 2025
b794655
fix: Get correct last Message when replying
KevinBoulongne Feb 4, 2025
62d23c7
refactor: Update Matomo usage in Scheduled Drafts feature
KevinBoulongne Feb 5, 2025
b64b02d
refactor: Move `getLongOrNull()` to Extensions
KevinBoulongne Feb 5, 2025
d4b29b0
refactor: Reduce cognitive complexity
KevinBoulongne Feb 5, 2025
8818e1c
refactor: Move scheduled draft related constants to correct place
KevinBoulongne Feb 5, 2025
7719707
fix: Update strings
KevinBoulongne Feb 5, 2025
18f0f0e
fix: Listen to Scheduled Drafts feature flag updates instead of just …
KevinBoulongne Feb 5, 2025
83fcad9
fix: Use correct illustration for update logo
KevinBoulongne Feb 5, 2025
8be6602
fix: Delete duplicated `Sent` icon
KevinBoulongne Feb 5, 2025
f8f242f
feat: Disable MultiSelection in Scheduled Drafts folder
KevinBoulongne Feb 6, 2025
45e12aa
feat: Add actions buttons in ThreadAdapter when in Scheduled Drafts f…
KevinBoulongne Feb 6, 2025
0c59aa1
feat: Only keep Delete action in Thread `quickActionBar` for Schedule…
KevinBoulongne Feb 6, 2025
e146349
refactor: Various renaming to better differentiate between scheduled …
KevinBoulongne Feb 6, 2025
58e2c62
refactor: Simplify `resetScheduledDate()` code
KevinBoulongne Feb 6, 2025
49152fa
refactor: Use already existing `target draft` concept to move schedul…
KevinBoulongne Feb 6, 2025
ed379c2
fix: Also filter on limited for ScheduleSend paywall
KevinBoulongne Feb 7, 2025
2c094f1
fix: Correctly refresh Folders after scheduling a mail
KevinBoulongne Feb 7, 2025
b267d1b
refactor: Update german string
KevinBoulongne Feb 7, 2025
fbd1cc0
refactor: Normalize SelectDateAndTimeForScheduledDraftDialog callbacks
KevinBoulongne Feb 7, 2025
e50ac55
fix: Remove now useless ActionsBottomSheetDialog dependency in Schedu…
KevinBoulongne Feb 7, 2025
635fe0d
refactor: Normalize DescriptionAlertDialog callbacks
KevinBoulongne Feb 7, 2025
0794426
fix: Remove now unused `isAlreadyScheduled` & `draftResource` in Sche…
KevinBoulongne Feb 7, 2025
2eaff26
fix: Remove overlapping of action bar icons
KevinBoulongne Feb 7, 2025
abb7603
refactor: Use a LinearLayout instead of a ConstraintLayout in ActionI…
KevinBoulongne Feb 7, 2025
f642990
fix: Remove useless clickable & focusable
KevinBoulongne Feb 10, 2025
2fb4a2e
fix: Update strings
KevinBoulongne Feb 10, 2025
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
144 changes: 96 additions & 48 deletions .idea/navEditor.xml

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion app/src/main/java/com/infomaniak/mail/MatomoMail.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 2022-2024 Infomaniak Network SA
* Copyright (C) 2022-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
Expand Down Expand Up @@ -269,6 +269,10 @@ object MatomoMail : MatomoCore {
trackEvent("settingsAutoAdvance", name)
}

fun Fragment.trackScheduleSendEvent(name: String) {
trackEvent("scheduleSend", name)
}

// We need to invert this logical value to keep a coherent value for analytics because actions
// conditions are inverted (ex: if the condition is `message.isSpam`, then we want to unspam)
private fun Boolean.toMailActionValue() = (!this).toFloat()
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/infomaniak/mail/data/LocalSettings.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 2022-2024 Infomaniak Network SA
* Copyright (C) 2022-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
Expand Down Expand Up @@ -69,6 +69,7 @@ class LocalSettings private constructor(context: Context) : SharedValues {
var autoAdvanceNaturalThread by sharedValue("autoAdvanceNaturalThreadKey", AutoAdvanceMode.FOLLOWING_THREAD)
var showWebViewOutdated by sharedValue("showWebViewOutdatedKey", true)
var accessTokenApiCallRecord by sharedValue<ApiCallRecord>("accessTokenApiCallRecordKey", null)
var lastSelectedScheduleEpoch by sharedValue<Long>("lastSelectedScheduleEpochKey", null)

fun removeSettings() = sharedPreferences.transaction { clear() }

Expand Down
50 changes: 32 additions & 18 deletions app/src/main/java/com/infomaniak/mail/data/api/ApiRepository.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 2022-2024 Infomaniak Network SA
* Copyright (C) 2022-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
Expand Down Expand Up @@ -30,7 +30,6 @@ import com.infomaniak.lib.core.utils.FORMAT_FULL_DATE_WITH_HOUR
import com.infomaniak.lib.core.utils.format
import com.infomaniak.mail.data.LocalSettings.AiEngine
import com.infomaniak.mail.data.models.*
import com.infomaniak.mail.data.models.AttachmentDisposition
import com.infomaniak.mail.data.models.addressBook.AddressBooksResult
import com.infomaniak.mail.data.models.ai.AiMessage
import com.infomaniak.mail.data.models.ai.AiResult
Expand All @@ -43,6 +42,7 @@ import com.infomaniak.mail.data.models.correspondent.Contact
import com.infomaniak.mail.data.models.correspondent.Recipient
import com.infomaniak.mail.data.models.draft.Draft
import com.infomaniak.mail.data.models.draft.SaveDraftResult
import com.infomaniak.mail.data.models.draft.ScheduleDraftResult
import com.infomaniak.mail.data.models.draft.SendDraftResult
import com.infomaniak.mail.data.models.getMessages.ActivitiesResult
import com.infomaniak.mail.data.models.getMessages.GetMessagesByUidsResult
Expand All @@ -58,7 +58,6 @@ import com.infomaniak.mail.data.models.thread.ThreadResult
import com.infomaniak.mail.ui.newMessage.AiViewModel.Shortcut
import com.infomaniak.mail.utils.Utils
import io.realm.kotlin.ext.copyFromRealm
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
Expand Down Expand Up @@ -170,28 +169,35 @@ object ApiRepository : ApiRepositoryCore() {
}

fun saveDraft(mailboxUuid: String, draft: Draft, okHttpClient: OkHttpClient): ApiResponse<SaveDraftResult> {

val body = getDraftBody(draft)

fun postDraft(): ApiResponse<SaveDraftResult> = callApi(ApiRoutes.draft(mailboxUuid), POST, body, okHttpClient)

fun putDraft(uuid: String): ApiResponse<SaveDraftResult> =
callApi(ApiRoutes.draft(mailboxUuid, uuid), PUT, body, okHttpClient)

return draft.remoteUuid?.let(::putDraft) ?: run(::postDraft)
return uploadDraft(mailboxUuid, draft, okHttpClient)
}

fun sendDraft(mailboxUuid: String, draft: Draft, okHttpClient: OkHttpClient): ApiResponse<SendDraftResult> {
return uploadDraft(mailboxUuid, draft, okHttpClient)
}

val body = getDraftBody(draft)
fun scheduleDraft(mailboxUuid: String, draft: Draft, okHttpClient: OkHttpClient): ApiResponse<ScheduleDraftResult> {
return uploadDraft(mailboxUuid, draft, okHttpClient)
}

fun postDraft(): ApiResponse<SendDraftResult> = callApi(ApiRoutes.draft(mailboxUuid), POST, body, okHttpClient)
private inline fun <reified T> uploadDraft(mailboxUuid: String, draft: Draft, okHttpClient: OkHttpClient): ApiResponse<T> {
val body = getDraftBody(draft)
return draft.remoteUuid?.let { putDraft(mailboxUuid, body, okHttpClient, it) }
?: run { postDraft(mailboxUuid, body, okHttpClient) }
}

fun putDraft(uuid: String): ApiResponse<SendDraftResult> =
callApi(ApiRoutes.draft(mailboxUuid, uuid), PUT, body, okHttpClient)
private inline fun <reified T> putDraft(
mailboxUuid: String,
body: String,
okHttpClient: OkHttpClient,
uuid: String,
): ApiResponse<T> = callApi(ApiRoutes.draft(mailboxUuid, uuid), PUT, body, okHttpClient)

return draft.remoteUuid?.let(::putDraft) ?: run(::postDraft)
}
private inline fun <reified T> postDraft(
mailboxUuid: String,
body: String,
okHttpClient: OkHttpClient,
): ApiResponse<T> = callApi(ApiRoutes.draft(mailboxUuid), POST, body, okHttpClient)

private fun getDraftBody(draft: Draft): String {
val updatedDraft = if (draft.identityId == Draft.NO_IDENTITY.toString()) {
Expand Down Expand Up @@ -240,6 +246,14 @@ object ApiRepository : ApiRepositoryCore() {
return callApi(ApiRoutes.draft(mailboxUuid, remoteDraftUuid), DELETE)
}

fun unscheduleDraft(unscheduleDraftUrl: String): ApiResponse<Unit> {
return callApi(ApiRoutes.resource(unscheduleDraftUrl), DELETE)
}

fun rescheduleDraft(draftResource: String, scheduleDate: Date): ApiResponse<Unit> {
return callApi(ApiRoutes.rescheduleDraft(draftResource, scheduleDate), PUT)
}

fun getDraft(messageDraftResource: String): ApiResponse<Draft> = callApi(ApiRoutes.resource(messageDraftResource), GET)

fun addToFavorites(mailboxUuid: String, messagesUids: List<String>): List<ApiResponse<Unit>> {
Expand Down
15 changes: 12 additions & 3 deletions app/src/main/java/com/infomaniak/mail/data/api/ApiRoutes.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 2022-2024 Infomaniak Network SA
* Copyright (C) 2022-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
Expand All @@ -18,8 +18,12 @@
package com.infomaniak.mail.data.api

import com.infomaniak.lib.core.BuildConfig.INFOMANIAK_API_V1
import com.infomaniak.lib.core.utils.FORMAT_SCHEDULE_MAIL
import com.infomaniak.lib.core.utils.format
import com.infomaniak.mail.BuildConfig.MAIL_API
import com.infomaniak.mail.utils.Utils
import java.net.URLEncoder
import java.util.Date

object ApiRoutes {

Expand Down Expand Up @@ -127,11 +131,11 @@ object ApiRoutes {
}

fun folders(mailboxUuid: String): String {
return "${mailMailbox(mailboxUuid)}/folder"
return "${mailMailbox(mailboxUuid)}/folder?with=ik-static"
}

private fun folder(mailboxUuid: String, folderId: String): String {
return "${folders(mailboxUuid)}/$folderId"
return "${mailMailbox(mailboxUuid)}/folder/$folderId"
KevinBoulongne marked this conversation as resolved.
Show resolved Hide resolved
}

fun flushFolder(mailboxUuid: String, folderId: String): String {
Expand Down Expand Up @@ -223,6 +227,11 @@ object ApiRoutes {
return "${draft(mailboxUuid)}/$remoteDraftUuid"
}

fun rescheduleDraft(draftResource: String, scheduleDate: Date): String {
val formatedDate = scheduleDate.format(FORMAT_SCHEDULE_MAIL)
return "${MAIL_API}${draftResource}/schedule?schedule_date=${URLEncoder.encode(formatedDate, "UTF-8")}"
}

fun createAttachment(mailboxUuid: String): String {
return "${draft(mailboxUuid)}/attachment"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ object RealmDatabase {

//region Configurations versions
const val USER_INFO_SCHEMA_VERSION = 2L
const val MAILBOX_INFO_SCHEMA_VERSION = 7L
const val MAILBOX_CONTENT_SCHEMA_VERSION = 19L
const val MAILBOX_INFO_SCHEMA_VERSION = 8L
const val MAILBOX_CONTENT_SCHEMA_VERSION = 20L
//endregion

//region Configurations names
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ val MAILBOX_INFO_MIGRATION = AutomaticSchemaMigration { migrationContext ->
val MAILBOX_CONTENT_MIGRATION = AutomaticSchemaMigration { migrationContext ->
SentryDebug.addMigrationBreadcrumb(migrationContext)
migrationContext.deleteRealmFromFirstMigration()
migrationContext.keepDefaultValuesAfterNineteenthMigration()
}

// Migrate to version #1
private fun MigrationContext.deleteRealmFromFirstMigration() {
if (oldRealm.schemaVersion() < 1L) newRealm.deleteAll()
}

//region Use default property values when adding a new column in a migration
/**
* Migrate from version #6
*
Expand Down Expand Up @@ -73,3 +75,26 @@ private fun MigrationContext.keepDefaultValuesAfterSixthMigration() {
}
}
}

// Migrate from version #19
private fun MigrationContext.keepDefaultValuesAfterNineteenthMigration() {

if (oldRealm.schemaVersion() <= 19L) {

enumerate(className = "Folder") { _: DynamicRealmObject, newObject: DynamicMutableRealmObject? ->
newObject?.apply {
// Add property with default value
set(propertyName = "isDisplayed", value = true)
}
}

enumerate(className = "Message") { oldObject: DynamicRealmObject, newObject: DynamicMutableRealmObject? ->
newObject?.apply {
// Rename property without losing its previous value
set(propertyName = "isScheduledMessage", value = oldObject.getValue<Boolean>(fieldName = "isScheduled"))
}
}

}
}
//endregion
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,31 @@ class FolderController @Inject constructor(

//region Get data
fun getMenuDrawerDefaultFoldersAsync(): Flow<ResultsChange<Folder>> {
return getFoldersQuery(mailboxContentRealm(), withoutType = FoldersType.CUSTOM, withoutChildren = true).asFlow()
return getFoldersQuery(
realm = mailboxContentRealm(),
withoutTypes = listOf(FoldersType.CUSTOM),
withoutChildren = true,
).asFlow()
}

fun getMenuDrawerCustomFoldersAsync(): Flow<ResultsChange<Folder>> {
return getFoldersQuery(mailboxContentRealm(), withoutType = FoldersType.DEFAULT, withoutChildren = true).asFlow()
return getFoldersQuery(
realm = mailboxContentRealm(),
withoutTypes = listOf(FoldersType.DEFAULT),
withoutChildren = true,
).asFlow()
}

fun getSearchFoldersAsync(): Flow<ResultsChange<Folder>> {
return getFoldersQuery(mailboxContentRealm(), withoutChildren = true).asFlow()
}

fun getMoveFolders(): RealmResults<Folder> {
return getFoldersQuery(mailboxContentRealm(), withoutType = FoldersType.DRAFT, withoutChildren = true).find()
return getFoldersQuery(
realm = mailboxContentRealm(),
withoutTypes = listOf(FoldersType.SCHEDULED_DRAFTS, FoldersType.DRAFT),
withoutChildren = true,
).find()
}

fun getFolder(id: String): Folder? {
Expand Down Expand Up @@ -108,20 +120,25 @@ class FolderController @Inject constructor(

remoteFolders.forEach { remoteFolder ->

getFolder(remoteFolder.id, realm = this)?.let { localFolder ->
val localFolder = getFolder(remoteFolder.id, realm = this)

if (remoteFolder.role == FolderRole.SCHEDULED_DRAFTS && localFolder == null) remoteFolder.isDisplayed = false

localFolder?.let {

val collapseStateNeedsReset = remoteFolder.isRoot && remoteFolder.children.isEmpty()
val isCollapsed = if (collapseStateNeedsReset) false else localFolder.isCollapsed
val isCollapsed = if (collapseStateNeedsReset) false else it.isCollapsed

remoteFolder.initLocalValues(
localFolder.lastUpdatedAt,
localFolder.cursor,
localFolder.unreadCountLocal,
localFolder.threads,
localFolder.oldMessagesUidsToFetch,
localFolder.newMessagesUidsToFetch,
localFolder.remainingOldMessagesToFetch,
localFolder.isHidden,
it.lastUpdatedAt,
it.cursor,
it.unreadCountLocal,
it.threads,
it.oldMessagesUidsToFetch,
it.newMessagesUidsToFetch,
it.remainingOldMessagesToFetch,
it.isDisplayed,
it.isHidden,
isCollapsed,
)
}
Expand All @@ -134,6 +151,7 @@ class FolderController @Inject constructor(
enum class FoldersType {
DEFAULT,
CUSTOM,
SCHEDULED_DRAFTS,
DRAFT,
}

Expand All @@ -145,17 +163,21 @@ class FolderController @Inject constructor(
//region Queries
private fun getFoldersQuery(
realm: TypedRealm,
withoutType: FoldersType? = null,
withoutTypes: List<FoldersType> = emptyList(),
withoutChildren: Boolean = false,
visibleFoldersOnly: Boolean = true,
): RealmQuery<Folder> {
val rootsQuery = if (withoutChildren) " AND $isRootFolder" else ""
val typeQuery = when (withoutType) {
FoldersType.DEFAULT -> " AND ${Folder.rolePropertyName} == nil"
FoldersType.CUSTOM -> " AND ${Folder.rolePropertyName} != nil"
FoldersType.DRAFT -> " AND ${Folder.rolePropertyName} != '${FolderRole.DRAFT.name}'"
null -> ""
val typeQuery = withoutTypes.joinToString(separator = "") {
when (it) {
FoldersType.DEFAULT -> " AND ${Folder.rolePropertyName} == nil"
FoldersType.CUSTOM -> " AND ${Folder.rolePropertyName} != nil"
FoldersType.SCHEDULED_DRAFTS -> " AND ${Folder.rolePropertyName} != '${FolderRole.SCHEDULED_DRAFTS.name}'"
FoldersType.DRAFT -> " AND ${Folder.rolePropertyName} != '${FolderRole.DRAFT.name}'"
}
}
return realm.query<Folder>("$isNotSearch${rootsQuery}${typeQuery}").sortFolders()
val visibilityQuery = if (visibleFoldersOnly) " AND ${Folder::isDisplayed.name} == true" else ""
return realm.query<Folder>("${isNotSearch}${rootsQuery}${typeQuery}${visibilityQuery}").sortFolders()
}

private fun getFoldersQuery(exceptionsFoldersIds: List<String>, realm: TypedRealm): RealmQuery<Folder> {
Expand All @@ -170,7 +192,7 @@ class FolderController @Inject constructor(
//region Get data
private fun getFolders(exceptionsFoldersIds: List<String> = emptyList(), realm: TypedRealm): RealmResults<Folder> {
val realmQuery = if (exceptionsFoldersIds.isEmpty()) {
getFoldersQuery(realm)
getFoldersQuery(realm, visibleFoldersOnly = false)
} else {
getFoldersQuery(exceptionsFoldersIds, realm)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,13 @@ class MessageController @Inject constructor(private val mailboxContentRealm: Rea

fun getLastMessageToExecuteAction(thread: Thread): Message = with(thread) {

val isNotScheduledDraft = "${Message::isScheduledDraft.name} == false"
val isNotFromMe = "SUBQUERY(${Message::from.name}, \$recipient, " +
"\$recipient.${Recipient::email.name} != '${AccountUtils.currentMailboxEmail}').@count > 0"

return messages.query("$isNotDraft AND $isNotFromMe").find().lastOrNull()
?: messages.query(isNotDraft).find().lastOrNull()
return messages.query("$isNotDraft AND $isNotScheduledDraft AND $isNotFromMe").find().lastOrNull()
?: messages.query("$isNotDraft AND $isNotScheduledDraft").find().lastOrNull()
?: messages.query(isNotScheduledDraft).find().lastOrNull()
?: messages.last()
}

Expand All @@ -83,11 +85,11 @@ class MessageController @Inject constructor(private val mailboxContentRealm: Rea

fun getMovableMessages(thread: Thread): List<Message> {
val byFolderId = "${Message::folderId.name} == '${thread.folderId}'"
return getMessagesAndDuplicates(thread, "$byFolderId AND $isNotScheduled")
return getMessagesAndDuplicates(thread, "$byFolderId AND $isNotScheduledMessage")
}

fun getUnscheduledMessages(thread: Thread): List<Message> {
return getMessagesAndDuplicates(thread, isNotScheduled)
return getMessagesAndDuplicates(thread, isNotScheduledMessage)
}

private fun getMessagesAndDuplicates(thread: Thread, query: String): List<Message> {
Expand Down Expand Up @@ -150,7 +152,7 @@ class MessageController @Inject constructor(private val mailboxContentRealm: Rea

companion object {
private val isNotDraft = "${Message::isDraft.name} == false"
private val isNotScheduled = "${Message::isScheduled.name} == false"
private val isNotScheduledMessage = "${Message::isScheduledMessage.name} == false"

//region Queries
private fun getMessagesQuery(messageUid: String, realm: TypedRealm): RealmQuery<Message> {
Expand Down
Loading
Loading