From ca54afb5a4806c75114505f5f389c9d6115722d6 Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Wed, 22 Jul 2026 16:16:31 +0200 Subject: [PATCH 1/3] feat: Add reminder field to Realm draft --- .../mail/data/models/draft/Draft.kt | 3 ++ .../data/models/draft/ReminderDraftInfo.kt | 37 +++++++++++++++++++ .../mail/data/cache/RealmDatabase.kt | 4 +- 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/draft/ReminderDraftInfo.kt diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/draft/Draft.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/draft/Draft.kt index 0feee25317a..1a0e61df0bf 100644 --- a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/draft/Draft.kt +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/draft/Draft.kt @@ -118,6 +118,9 @@ class Draft : RealmObject { delay = null field = value } + + @SerialName("reminder") + var reminder: ReminderDraftInfo? = null //endregion //region Local data (Transient) diff --git a/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/draft/ReminderDraftInfo.kt b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/draft/ReminderDraftInfo.kt new file mode 100644 index 00000000000..7f36893f00e --- /dev/null +++ b/OldKotlin/realm-models/src/main/kotlin/com/infomaniak/mail/data/models/draft/ReminderDraftInfo.kt @@ -0,0 +1,37 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2026 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 . + */ +@file:UseSerializers(RealmInstantSerializer::class) + +package com.infomaniak.mail.data.models.draft + +import com.infomaniak.mail.data.api.RealmInstantSerializer +import io.realm.kotlin.types.EmbeddedRealmObject +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers + +@Serializable +class ReminderDraftInfo : EmbeddedRealmObject { + @SerialName("delta") + var reminderDelta: Int? = null + + @SerialName("display") + var shouldRemindRecipient: Boolean? = null + + companion object +} diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/RealmDatabase.kt b/app/src/main/java/com/infomaniak/mail/data/cache/RealmDatabase.kt index 0ca3fee5777..40d2687c01e 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/RealmDatabase.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/RealmDatabase.kt @@ -37,6 +37,7 @@ import com.infomaniak.mail.data.models.calendar.CalendarEventResponse import com.infomaniak.mail.data.models.correspondent.MergedContact import com.infomaniak.mail.data.models.correspondent.Recipient import com.infomaniak.mail.data.models.draft.Draft +import com.infomaniak.mail.data.models.draft.ReminderDraftInfo import com.infomaniak.mail.data.models.mailbox.Mailbox import com.infomaniak.mail.data.models.mailbox.MailboxPermissions import com.infomaniak.mail.data.models.mailbox.SenderDetails @@ -204,7 +205,7 @@ object RealmDatabase { //region Configurations versions const val USER_INFO_SCHEMA_VERSION = 5L const val MAILBOX_INFO_SCHEMA_VERSION = 19L - const val MAILBOX_CONTENT_SCHEMA_VERSION = 39L + const val MAILBOX_CONTENT_SCHEMA_VERSION = 40L //endregion //region Configurations names @@ -236,6 +237,7 @@ object RealmDatabase { Folder::class, Thread::class, Message::class, + ReminderDraftInfo::class, Headers::class, Draft::class, Recipient::class, From d975b8851b8cbfd1e8de8b1b5c5d00222d80f46f Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Wed, 22 Jul 2026 16:17:05 +0200 Subject: [PATCH 2/3] feat: Save send options in realm draft --- .../mail/ui/newMessage/NewMessageViewModel.kt | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt index c1edcdb3036..c6b66b9a57f 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt @@ -72,6 +72,7 @@ import com.infomaniak.mail.data.models.correspondent.Recipient import com.infomaniak.mail.data.models.draft.Draft import com.infomaniak.mail.data.models.draft.Draft.DraftMode import com.infomaniak.mail.data.models.draft.DraftAction +import com.infomaniak.mail.data.models.draft.ReminderDraftInfo import com.infomaniak.mail.data.models.extensions.action import com.infomaniak.mail.data.models.extensions.createValidRecipientOrNull import com.infomaniak.mail.data.models.extensions.getDefaultSignatureWithFallback @@ -149,7 +150,9 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jsoup.nodes.Document import splitties.experimental.ExperimentalSplittiesApi +import java.text.SimpleDateFormat import java.util.Date +import java.util.Locale import javax.inject.Inject @OptIn(ExperimentalCoroutinesApi::class) @@ -586,6 +589,9 @@ class NewMessageViewModel @Inject constructor( isEncrypted = isEncrypted, encryptionPassword = encryptionKey ?: "", attachmentsLocalUuids = attachments.mapTo(mutableSetOf()) { it.localUuid }, + scheduleDate = scheduleDate, + reminderDelta = reminder?.reminderDelta, + shouldRemindRecipient = reminder?.shouldRemindRecipient ?: true, ) } @@ -621,6 +627,21 @@ class NewMessageViewModel @Inject constructor( } isEncryptionActivated.postValue(isEncrypted) + + scheduleDate?.let { dateString -> + SimpleDateFormat(FORMAT_ISO_8601_WITH_TIMEZONE_SEPARATOR, Locale.getDefault()) + .parse(dateString) + ?.time + ?.let { epoch -> + setScheduleConfig(ScheduleConfig.Scheduled(epoch)) + } + } + + reminder?.reminderDelta?.let { minutes -> + setReminderConfig(ReminderConfig.Delayed(minutes, isCustom = false)) + } + + reminder?.shouldRemindRecipient?.let { setShouldRemindRecipient(it) } } fun setQuotesButtonVisibility(isVisible: Boolean) { @@ -932,17 +953,6 @@ class NewMessageViewModel @Inject constructor( }.cancellable().onFailure(Sentry::captureException) } - fun setScheduleDate(date: Date?) = viewModelScope.launch(ioDispatcher) { - val localUuid = draftLocalUuid ?: return@launch - mailboxContentRealm().write { - DraftController.getDraftBlocking(localUuid, realm = this)?.also { draft -> - draft.scheduleDate = date?.format(FORMAT_ISO_8601_WITH_TIMEZONE_SEPARATOR) - } - } - } - - fun resetScheduledDate() = setScheduleDate(date = null) - fun storeBodyAndSubject(subject: String, html: String) { globalCoroutineScope.launch(ioDispatcher) { _subjectAndBodyChannel.send(SubjectAndBodyData(subject, html, channelExpirationIdTarget)) @@ -1033,10 +1043,22 @@ class NewMessageViewModel @Inject constructor( cc = ccLiveData.valueOrEmpty().toRealmList() bcc = bccLiveData.valueOrEmpty().toRealmList() - if (draftAction == DraftAction.SEND) delay = localSettings.cancelDelay - ackRequest = localSettings.askEmailAcknowledgement + scheduleDate = getCurrentScheduleDate() + + if (draftAction == DraftAction.SEND && scheduleDate == null) delay = localSettings.cancelDelay + + val currentReminderDelta = getCurrentReminderDelta() + reminder = if (currentReminderDelta != null) { + ReminderDraftInfo().apply { + reminderDelta = currentReminderDelta + shouldRemindRecipient = this@NewMessageViewModel.shouldRemindRecipient.value ?: true + } + } else { + null + } + updateDraftAttachmentsWithLiveData( uiAttachments = attachmentsLiveData.valueOrEmpty(), step = "executeDraftActionWhenStopping (action = ${draftAction.name}) -> updateDraftFromLiveData", @@ -1145,6 +1167,16 @@ class NewMessageViewModel @Inject constructor( SentryDebug.addDraftBreadcrumbs(draft = this, step) } + private fun getCurrentScheduleDate(): String? { + return (scheduleConfig.value as? ScheduleConfig.Scheduled)?.let { schedule -> + Date(schedule.epochMillis).format(FORMAT_ISO_8601_WITH_TIMEZONE_SEPARATOR) + } + } + + private fun getCurrentReminderDelta(): Int? { + return (reminderConfig.value as? ReminderConfig.Delayed)?.delayMinutes + } + private fun isSnapshotTheSame(subjectValue: String?, uiBodyValue: String): Boolean { return snapshot?.let { draftSnapshot -> draftSnapshot.identityId == fromLiveData.value?.signature?.id?.toString() && @@ -1156,7 +1188,10 @@ class NewMessageViewModel @Inject constructor( draftSnapshot.isEncrypted == isEncryptionActivated.value && draftSnapshot.encryptionPassword == encryptionPassword.value && draftSnapshot.attachmentsLocalUuids == attachmentsLiveData.valueOrEmpty() - .mapTo(mutableSetOf()) { it.localUuid } + .mapTo(mutableSetOf()) { it.localUuid } && + draftSnapshot.scheduleDate == getCurrentScheduleDate() && + draftSnapshot.reminderDelta == getCurrentReminderDelta() && + draftSnapshot.shouldRemindRecipient == shouldRemindRecipient.value } ?: false } @@ -1285,6 +1320,9 @@ class NewMessageViewModel @Inject constructor( var isEncrypted: Boolean, var encryptionPassword: String, val attachmentsLocalUuids: Set, + val scheduleDate: String?, + val reminderDelta: Int?, + val shouldRemindRecipient: Boolean?, ) private data class SubjectAndBodyData(val subject: String, val body: String, val expirationId: Int) From 738e75602379149b311240fdf3775142170a70ac Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Thu, 23 Jul 2026 13:48:34 +0200 Subject: [PATCH 3/3] fix: Reopen with custom reminder if user exited draft with a custom reminder --- .../com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt index c6b66b9a57f..0a1b1c879bf 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/newMessage/NewMessageViewModel.kt @@ -638,7 +638,8 @@ class NewMessageViewModel @Inject constructor( } reminder?.reminderDelta?.let { minutes -> - setReminderConfig(ReminderConfig.Delayed(minutes, isCustom = false)) + val isCustom = !ReminderPreset.entries.any { preset -> preset.delayMinutes == minutes } + setReminderConfig(ReminderConfig.Delayed(minutes, isCustom = isCustom)) } reminder?.shouldRemindRecipient?.let { setShouldRemindRecipient(it) }