Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9ed2743
feat: Add strings for new fragment
Elouan1411 Jul 22, 2026
c9aacdc
feat: Add checkmark in view item setting
Elouan1411 Jul 22, 2026
b0d2413
feat: Add description in view setting radio button
Elouan1411 Jul 22, 2026
4780d22
feat: Add removeSubtitle and icon color in item setting view
Elouan1411 Jul 22, 2026
7ab2d7b
feat: Add feature flag
Elouan1411 Jul 22, 2026
7614c3b
feat: Add reminder/schedule strings and dimens
Elouan1411 Jul 22, 2026
baae93a
feat: Add schedule/reminder config models and formatting helpers
Elouan1411 Jul 22, 2026
0ed4cae
feat: Add scheduleConfig/reminderConfig state to NewMessageViewModel
Elouan1411 Jul 22, 2026
728b1fa
feat: Replace compose schedule dialog with DraftSendOptionsFragment (…
Elouan1411 Jul 22, 2026
0657784
fix: Correct feature flag
Elouan1411 Jul 23, 2026
4f31429
feat: Add banner in newMessageFragment
Elouan1411 Jul 22, 2026
fbe4f13
fix: Correct click behavior on schedule option
Elouan1411 Jul 22, 2026
1be60c6
fix: Correct display last schedule option
Elouan1411 Jul 22, 2026
de452ef
refactor: Remove ScheduleOptionsHelper to simplify the setup
Elouan1411 Jul 27, 2026
4fd3a38
refactor: Add parameter names
Elouan1411 Jul 27, 2026
01dda73
refactor: Clean code
Elouan1411 Jul 28, 2026
6e9c620
refactor: Create handleCustomScheduleOptionClicked
Elouan1411 Jul 28, 2026
5018a43
feat: Add annotations
Elouan1411 Jul 28, 2026
81cba8b
fix: Remove divider to improve UI
Elouan1411 Jul 28, 2026
62e5d5a
fix: Remove save button
Elouan1411 Jul 28, 2026
41aba78
feat: Use SimpleSettingView to have the same feel as the other screen…
Elouan1411 Jul 28, 2026
be7980d
refactor: Remove useless LinearLayout
Elouan1411 Jul 28, 2026
64fe694
refactor: Initialize constants in a single location
Elouan1411 Jul 29, 2026
a7255f5
refactor: Convert liveData to stateFlow
Elouan1411 Jul 29, 2026
b742e81
refactor: Update UI with observer
Elouan1411 Jul 30, 2026
0500554
refactor: Rename functions and relocate logic to appropriate locations
Elouan1411 Jul 29, 2026
d6a570e
refactor: Clean code
Elouan1411 Jul 31, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enum class FeatureFlag(val apiName: String) {
AI("ai-mail-composer"),
BIMI("bimi"),
ENCRYPTION("mail-compose-encrypted"),
RESPONSE_REQUIRED("mail-response-required-flag"),
SCHEDULE_DRAFTS("schedule-send-draft"),
SNOOZE("mail-snooze"),
EMOJI_REACTION("mail-emoji-reaction"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,13 @@ import com.infomaniak.core.legacy.utils.setBackNavigationResult
import com.infomaniak.mail.MatomoMail.MatomoName
import com.infomaniak.mail.MatomoMail.trackScheduleSendEvent
import com.infomaniak.mail.R
import com.infomaniak.mail.utils.openKSuiteProBottomSheet
import com.infomaniak.mail.utils.openMailPremiumBottomSheet
import com.infomaniak.mail.utils.openMyKSuiteUpgradeBottomSheet
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class ScheduleSendBottomSheetDialog @Inject constructor() : SelectScheduleOptionBottomSheet() {
class RescheduleDraftBottomSheetDialog @Inject constructor() : SimpleSchedulePickerBottomSheet() {

private val navigationArgs: ScheduleSendBottomSheetDialogArgs by navArgs()
private val navigationArgs: RescheduleDraftBottomSheetDialogArgs by navArgs()

override val currentKSuite: KSuite? by lazy { navigationArgs.currentKSuite }

Expand All @@ -56,17 +53,12 @@ class ScheduleSendBottomSheetDialog @Inject constructor() : SelectScheduleOption
}

override fun onCustomScheduleOptionClicked() {
val kSuite = currentKSuite
val matomoName = MatomoName.ScheduledCustomDate.value
when (kSuite) {
KSuite.Perso.Free -> openMyKSuiteUpgradeBottomSheet(matomoName)
KSuite.Pro.Free -> openKSuiteProBottomSheet(kSuite, navigationArgs.isAdmin, matomoName)
KSuite.StarterPack -> openMailPremiumBottomSheet(matomoName)
else -> {
trackScheduleSendEvent(MatomoName.CustomSchedule)
setBackNavigationResult(OPEN_SCHEDULE_DRAFT_DATE_AND_TIME_PICKER, true)
}
}
handleCustomScheduleOptionClicked(
matomoName = MatomoName.ScheduledCustomDate.value,
backNavKey = OPEN_SCHEDULE_DRAFT_DATE_AND_TIME_PICKER,
isAdmin = navigationArgs.isAdmin,
onDefaultClicked = { trackScheduleSendEvent(MatomoName.CustomSchedule) }
)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*
* Infomaniak Mail - Android
* Copyright (C) 2025-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 <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.mail.ui.bottomSheetDialogs

import androidx.annotation.DrawableRes
import androidx.annotation.IntRange
import androidx.annotation.StringRes
import com.infomaniak.core.common.utils.getNextMonday
import com.infomaniak.core.common.utils.getTimeAtHour
import com.infomaniak.core.common.utils.isAtLeastXMinutesInTheFuture
import com.infomaniak.core.common.utils.isWeekend
import com.infomaniak.core.common.utils.tomorrow
import com.infomaniak.mail.MatomoMail.MatomoName
import com.infomaniak.mail.R
import com.infomaniak.mail.ui.bottomSheetDialogs.ScheduleOptionUtils.HIDE_INTERVAL
import com.infomaniak.mail.ui.newMessage.MIN_SELECTABLE_DATE_MINUTES
import java.util.Calendar
import java.util.Date
import kotlin.time.Duration.Companion.minutes

object ScheduleOptionUtils {
val HIDE_INTERVAL = 5.minutes // Beware: the API refuses schedules smaller than 5 minutes

fun getLastScheduleOptionDate(
lastSelectedEpoch: Long?,
currentlyScheduledEpochMillis: Long?,
): Date? {
val lastSelectedDate = lastSelectedEpoch?.let { Date(it) }

return if (
lastSelectedDate?.isAtLeastXMinutesInTheFuture(MIN_SELECTABLE_DATE_MINUTES) == true &&
lastSelectedDate.isNotAlreadySelected(currentlyScheduledEpochMillis)
) {
lastSelectedDate
} else {
null
}
}

fun getAvailableScheduleOptions(currentlyScheduledEpochMillis: Long?): List<ScheduleOption> {
val currentTime = Date()
return WeekPeriod.getCurrent().scheduleOptions.filter { scheduleOption ->
scheduleOption.canBeDisplayedAt(currentTime) &&
scheduleOption.date().isNotAlreadySelected(currentlyScheduledEpochMillis)
}
}

private fun Date.isNotAlreadySelected(currentlyScheduledEpochMillis: Long?): Boolean {
return time.truncateToMinute() != currentlyScheduledEpochMillis?.truncateToMinute()
}

private fun Long.truncateToMinute(): Long {
return Calendar.getInstance().apply {
timeInMillis = this@truncateToMinute
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.time.time
}
}

enum class ScheduleOption(
private val day: RelativeDay,
private val hour: HourOfTheDay,
@StringRes val titleRes: Int,
@DrawableRes val iconRes: Int,
val matomoName: MatomoName,
) {
LaterThisMorning(
day = RelativeDay.Today,
hour = HourOfTheDay.Morning,
titleRes = R.string.laterThisMorning,
iconRes = R.drawable.ic_morning_sunrise_schedule,
matomoName = MatomoName.LaterThisMorning,
),
ThisAfternoon(
day = RelativeDay.Today,
hour = HourOfTheDay.Afternoon,
titleRes = R.string.thisAfternoon,
iconRes = R.drawable.ic_afternoon_schedule,
matomoName = MatomoName.ThisAfternoon,
),
ThisEvening(
day = RelativeDay.Today,
hour = HourOfTheDay.Evening,
titleRes = R.string.thisEvening,
iconRes = R.drawable.ic_evening_schedule,
matomoName = MatomoName.ThisEvening,
),
TomorrowMorning(
day = RelativeDay.Tomorrow,
hour = HourOfTheDay.Morning,
titleRes = R.string.tomorrowMorning,
iconRes = R.drawable.ic_morning_schedule,
matomoName = MatomoName.TomorrowMorning,
),
NextMondayMorning(
day = RelativeDay.NextMonday,
hour = HourOfTheDay.Morning,
titleRes = R.string.nextMonday,
iconRes = R.drawable.ic_arrow_return,
matomoName = MatomoName.NextMonday,
),
MondayMorning(
day = RelativeDay.NextMonday,
hour = HourOfTheDay.Morning,
titleRes = R.string.mondayMorning,
iconRes = R.drawable.ic_morning_schedule,
matomoName = MatomoName.NextMondayMorning,
),
MondayAfternoon(
day = RelativeDay.NextMonday,
hour = HourOfTheDay.Afternoon,
titleRes = R.string.mondayAfternoon,
iconRes = R.drawable.ic_afternoon_schedule,
matomoName = MatomoName.NextMondayAfternoon,
);

fun date(): Date = day.getDate().getTimeAtHour(hour.hourOfTheDay)
fun canBeDisplayedAt(date: Date): Boolean = date.time < minimalDisplayTime()
private fun minimalDisplayTime() = date().time - HIDE_INTERVAL.inWholeMilliseconds
}

private enum class RelativeDay(val getDate: () -> Date) {
Today({ Date() }),
Tomorrow({ Date().tomorrow() }),
NextMonday({ Date().getNextMonday() }),
}

private enum class HourOfTheDay(@IntRange(0, 23) val hourOfTheDay: Int) {
Morning(8),
Afternoon(14),
Evening(18),
}

/**
* Represents a period inside the current week. In other words, a timeframe used to group relevant schedule options based on when
* they should be displayed.
*
* @param scheduleOptions The available schedule options that can be displayed to the user during each period
*/
private enum class WeekPeriod(vararg val scheduleOptions: ScheduleOption) {
Weekday(
ScheduleOption.LaterThisMorning,
ScheduleOption.ThisAfternoon,
ScheduleOption.ThisEvening,
ScheduleOption.TomorrowMorning,
ScheduleOption.NextMondayMorning,
),
Weekend(ScheduleOption.MondayMorning, ScheduleOption.MondayAfternoon);

companion object {
fun getCurrent(): WeekPeriod = if (Date().isWeekend()) Weekend else Weekday
}
}
Loading
Loading