refactor(reminders): draw the reminder-override model from floret-kit

Replace Calendula's app-local reminder-override plumbing with floret-kit's
core-reminders, shared with Agendula:

- CalendarReminderOverride (Inherit/None/Minutes) -> kit ReminderOverride, with
  the kit's applyReminderOverride() map extension.
- The hand-rolled parse/serialize of the per-calendar override string -> kit
  ReminderOverrideCodec, configured with Calendula's on-disk dialect
  (id=minutes entries joined by ;, "none" sentinel). Format is unchanged, so
  stored prefs round-trip identically (SettingsPrefsTest still green).
- ReminderUnit enum + the custom-amount decomposition (decomposeReminder) ->
  kit ReminderUnit + decomposeReminderMinutes/ReminderAmount.

App-specific glue stays put: resolveDefaultReminder (timed vs all-day),
reminderLeadTimeLabel / reminderUnitLabel (app string resources), and the
global-default picker helpers.

Compile + unit tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 19:59:06 +02:00
parent 763bb915f4
commit 7a04eff471
8 changed files with 63 additions and 102 deletions

View File

@@ -162,6 +162,7 @@ dependencies {
implementation("de.jeanlucmakiola.floret:core-time") implementation("de.jeanlucmakiola.floret:core-time")
implementation("de.jeanlucmakiola.floret:core-locale") implementation("de.jeanlucmakiola.floret:core-locale")
implementation("de.jeanlucmakiola.floret:core-crash") implementation("de.jeanlucmakiola.floret:core-crash")
implementation("de.jeanlucmakiola.floret:core-reminders")
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.tooling)

View File

@@ -12,6 +12,9 @@ import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange
import de.jeanlucmakiola.calendula.ui.agenda.parseAgendaRange import de.jeanlucmakiola.calendula.ui.agenda.parseAgendaRange
import de.jeanlucmakiola.calendula.ui.agenda.storageValue import de.jeanlucmakiola.calendula.ui.agenda.storageValue
import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.CalendarView
import de.jeanlucmakiola.floret.reminders.ReminderOverride
import de.jeanlucmakiola.floret.reminders.ReminderOverrideCodec
import de.jeanlucmakiola.floret.reminders.applyReminderOverride
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.datetime.DayOfWeek import kotlinx.datetime.DayOfWeek
@@ -296,14 +299,14 @@ class SettingsPrefs @Inject constructor(
* [defaultAllDayReminderMinutes].) * [defaultAllDayReminderMinutes].)
*/ */
val perCalendarReminderOverride: Flow<Map<Long, Int?>> = store.data.map { prefs -> val perCalendarReminderOverride: Flow<Map<Long, Int?>> = store.data.map { prefs ->
parseReminderOverrides(prefs[CALENDAR_REMINDER_OVERRIDE_KEY]) reminderOverrideCodec.parse(prefs[CALENDAR_REMINDER_OVERRIDE_KEY])
} }
suspend fun setCalendarReminderOverride(calendarId: Long, override: CalendarReminderOverride) { suspend fun setCalendarReminderOverride(calendarId: Long, override: ReminderOverride) {
store.edit { prefs -> store.edit { prefs ->
val current = parseReminderOverrides(prefs[CALENDAR_REMINDER_OVERRIDE_KEY]).toMutableMap() val current = reminderOverrideCodec.parse(prefs[CALENDAR_REMINDER_OVERRIDE_KEY]).toMutableMap()
current.applyOverride(calendarId, override) current.applyReminderOverride(calendarId, override)
prefs[CALENDAR_REMINDER_OVERRIDE_KEY] = serializeReminderOverrides(current) prefs[CALENDAR_REMINDER_OVERRIDE_KEY] = reminderOverrideCodec.serialize(current)
} }
} }
@@ -313,18 +316,18 @@ class SettingsPrefs @Inject constructor(
* inherit the global all-day default; present null = no reminder). * inherit the global all-day default; present null = no reminder).
*/ */
val perCalendarAllDayReminderOverride: Flow<Map<Long, Int?>> = store.data.map { prefs -> val perCalendarAllDayReminderOverride: Flow<Map<Long, Int?>> = store.data.map { prefs ->
parseReminderOverrides(prefs[CALENDAR_ALLDAY_REMINDER_OVERRIDE_KEY]) reminderOverrideCodec.parse(prefs[CALENDAR_ALLDAY_REMINDER_OVERRIDE_KEY])
} }
suspend fun setCalendarAllDayReminderOverride( suspend fun setCalendarAllDayReminderOverride(
calendarId: Long, calendarId: Long,
override: CalendarReminderOverride, override: ReminderOverride,
) { ) {
store.edit { prefs -> store.edit { prefs ->
val current = val current =
parseReminderOverrides(prefs[CALENDAR_ALLDAY_REMINDER_OVERRIDE_KEY]).toMutableMap() reminderOverrideCodec.parse(prefs[CALENDAR_ALLDAY_REMINDER_OVERRIDE_KEY]).toMutableMap()
current.applyOverride(calendarId, override) current.applyReminderOverride(calendarId, override)
prefs[CALENDAR_ALLDAY_REMINDER_OVERRIDE_KEY] = serializeReminderOverrides(current) prefs[CALENDAR_ALLDAY_REMINDER_OVERRIDE_KEY] = reminderOverrideCodec.serialize(current)
} }
} }
@@ -434,16 +437,6 @@ data class BackupStatus(
val consecutiveFailures: Int, val consecutiveFailures: Int,
) )
/** A calendar's reminder-default override (see [SettingsPrefs.perCalendarReminderOverride]). */
sealed interface CalendarReminderOverride {
/** No override — the calendar uses the global default. */
data object Inherit : CalendarReminderOverride
/** Explicit "no reminder" for this calendar, regardless of the global default. */
data object None : CalendarReminderOverride
/** A specific lead time in minutes before the event start. */
data class Minutes(val minutes: Int) : CalendarReminderOverride
}
/** /**
* The lead time to prefill on a new event: the matching per-calendar override * The lead time to prefill on a new event: the matching per-calendar override
* if [calendarId] has one for this event kind, otherwise the global default for * if [calendarId] has one for this event kind, otherwise the global default for
@@ -468,18 +461,6 @@ fun resolveDefaultReminder(
} }
} }
/** Apply a [CalendarReminderOverride] to an override map ([Inherit] removes the key). */
private fun MutableMap<Long, Int?>.applyOverride(
calendarId: Long,
override: CalendarReminderOverride,
) {
when (override) {
CalendarReminderOverride.Inherit -> remove(calendarId)
CalendarReminderOverride.None -> put(calendarId, null)
is CalendarReminderOverride.Minutes -> put(calendarId, override.minutes)
}
}
/** Sentinel stored for [WeekStartPref.Auto]; days store their [DayOfWeek.name]. */ /** Sentinel stored for [WeekStartPref.Auto]; days store their [DayOfWeek.name]. */
private const val WEEK_START_AUTO = "AUTO" private const val WEEK_START_AUTO = "AUTO"
@@ -500,26 +481,18 @@ private fun parseWeekStart(stored: String?): WeekStartPref = when (stored) {
} }
private const val NONE = "none" private const val NONE = "none"
private const val ENTRY_SEP = ";"
private const val KEY_VALUE_SEP = "="
private fun String?.toReminderMinutes(): Int? = when (this) { private fun String?.toReminderMinutes(): Int? = when (this) {
null, "", NONE -> null null, "", NONE -> null
else -> toIntOrNull() else -> toIntOrNull()
} }
private fun parseReminderOverrides(stored: String?): Map<Long, Int?> { /**
if (stored.isNullOrBlank()) return emptyMap() * Calendula's dialect of the shared per-target reminder-override codec: entries
return stored.split(ENTRY_SEP).mapNotNull { entry -> * `id=minutes` joined by `;`, with `none` for an explicit no-reminder. These
val parts = entry.split(KEY_VALUE_SEP).takeIf { it.size == 2 } ?: return@mapNotNull null * separators are the on-disk format and must not change without a migration.
val id = parts[0].toLongOrNull() ?: return@mapNotNull null */
val value = if (parts[1] == NONE) null else parts[1].toIntOrNull() ?: return@mapNotNull null private val reminderOverrideCodec = ReminderOverrideCodec(entrySep = ";", keyValueSep = "=", noneToken = NONE)
id to value
}.toMap()
}
private fun serializeReminderOverrides(map: Map<Long, Int?>): String =
map.entries.joinToString(ENTRY_SEP) { (id, minutes) -> "$id$KEY_VALUE_SEP${minutes ?: NONE}" }
private inline fun <reified E : Enum<E>> String?.toEnum(default: E): E = private inline fun <reified E : Enum<E>> String?.toEnum(default: E): E =
this?.let { stored -> enumValues<E>().firstOrNull { it.name == stored } } ?: default this?.let { stored -> enumValues<E>().firstOrNull { it.name == stored } } ?: default

View File

@@ -38,8 +38,10 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider import androidx.compose.ui.window.DialogWindowProvider
import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.R
import de.jeanlucmakiola.calendula.data.prefs.CalendarReminderOverride
import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange
import de.jeanlucmakiola.floret.reminders.ReminderOverride
import de.jeanlucmakiola.floret.reminders.ReminderUnit
import de.jeanlucmakiola.floret.reminders.decomposeReminderMinutes
/** /**
* Shared full-screen scaffold for selection pickers: a full-bleed [Dialog] that * Shared full-screen scaffold for selection pickers: a full-bleed [Dialog] that
@@ -114,29 +116,29 @@ fun <T> OptionPicker(
* Reminder-default picker, full-screen: the grouped list (with an optional "Use * Reminder-default picker, full-screen: the grouped list (with an optional "Use
* default reminder" row and a "None" row), the [presets] as lead-time rows, and * default reminder" row and a "None" row), the [presets] as lead-time rows, and
* a "Custom" row that expands an inline number field plus a segmented unit * a "Custom" row that expands an inline number field plus a segmented unit
* selector. Returns the choice as a [CalendarReminderOverride]. * selector. Returns the choice as a [ReminderOverride].
*/ */
@Composable @Composable
fun ReminderDefaultPicker( fun ReminderDefaultPicker(
title: String, title: String,
presets: List<Int>, presets: List<Int>,
selected: CalendarReminderOverride, selected: ReminderOverride,
allowInherit: Boolean, allowInherit: Boolean,
onSelect: (CalendarReminderOverride) -> Unit, onSelect: (ReminderOverride) -> Unit,
onDismiss: () -> Unit, onDismiss: () -> Unit,
) { ) {
val selectedMinutes = (selected as? CalendarReminderOverride.Minutes)?.minutes val selectedMinutes = (selected as? ReminderOverride.Minutes)?.minutes
val customSelected = selectedMinutes != null && selectedMinutes !in presets val customSelected = selectedMinutes != null && selectedMinutes !in presets
val seed = decomposeReminder(selectedMinutes?.takeIf { customSelected }) val seed = decomposeReminderMinutes(selectedMinutes?.takeIf { customSelected })
var customExpanded by rememberSaveable { mutableStateOf(false) } var customExpanded by rememberSaveable { mutableStateOf(false) }
var amountText by rememberSaveable { mutableStateOf(seed.first) } var amountText by rememberSaveable { mutableStateOf(seed.amount?.toString() ?: "") }
var unit by rememberSaveable { mutableStateOf(seed.second) } var unit by rememberSaveable { mutableStateOf(seed.unit) }
val options = buildList { val options = buildList {
if (allowInherit) add(CalendarReminderOverride.Inherit) if (allowInherit) add(ReminderOverride.Inherit)
add(CalendarReminderOverride.None) add(ReminderOverride.None)
presets.forEach { add(CalendarReminderOverride.Minutes(it)) } presets.forEach { add(ReminderOverride.Minutes(it)) }
} }
val rowCount = options.size + 1 // + the custom row val rowCount = options.size + 1 // + the custom row
@@ -189,7 +191,7 @@ fun ReminderDefaultPicker(
unit = unit, unit = unit,
onUnitChange = { unit = it }, onUnitChange = { unit = it },
onConfirm = { minutes -> onConfirm = { minutes ->
onSelect(CalendarReminderOverride.Minutes(minutes)) onSelect(ReminderOverride.Minutes(minutes))
onDismiss() onDismiss()
}, },
) )
@@ -429,17 +431,8 @@ fun agendaRangeLabel(range: AgendaRange): String = when (range) {
} }
@Composable @Composable
private fun reminderOverrideLabel(override: CalendarReminderOverride): String = when (override) { private fun reminderOverrideLabel(override: ReminderOverride): String = when (override) {
CalendarReminderOverride.Inherit -> stringResource(R.string.reminder_use_default) ReminderOverride.Inherit -> stringResource(R.string.reminder_use_default)
CalendarReminderOverride.None -> stringResource(R.string.reminder_none) ReminderOverride.None -> stringResource(R.string.reminder_none)
is CalendarReminderOverride.Minutes -> reminderLeadTimeLabel(override.minutes) is ReminderOverride.Minutes -> reminderLeadTimeLabel(override.minutes)
}
/** Seed the custom editor: the largest exact unit for [minutes] (null → empty). */
private fun decomposeReminder(minutes: Int?): Pair<String, ReminderUnit> = when {
minutes == null -> "" to ReminderUnit.Minutes
minutes % 10_080 == 0 -> (minutes / 10_080).toString() to ReminderUnit.Weeks
minutes % 1_440 == 0 -> (minutes / 1_440).toString() to ReminderUnit.Days
minutes % 60 == 0 -> (minutes / 60).toString() to ReminderUnit.Hours
else -> minutes.toString() to ReminderUnit.Minutes
} }

View File

@@ -5,18 +5,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.R
import de.jeanlucmakiola.floret.reminders.ReminderUnit
/** Common reminder lead times offered as quick picks in the form and settings. */ /** Common reminder lead times offered as quick picks in the form and settings. */
val REMINDER_PRESETS = listOf(0, 10, 30, 60, 1_440) val REMINDER_PRESETS = listOf(0, 10, 30, 60, 1_440)
/** The unit of a custom reminder lead time; [minutesFactor] converts to minutes. */
enum class ReminderUnit(val minutesFactor: Int) {
Minutes(1),
Hours(60),
Days(1_440),
Weeks(10_080),
}
@StringRes @StringRes
fun reminderUnitLabel(unit: ReminderUnit): Int = when (unit) { fun reminderUnitLabel(unit: ReminderUnit): Int = when (unit) {
ReminderUnit.Minutes -> R.string.reminder_unit_minutes ReminderUnit.Minutes -> R.string.reminder_unit_minutes

View File

@@ -130,8 +130,8 @@ import de.jeanlucmakiola.calendula.ui.common.OptionCard
import de.jeanlucmakiola.calendula.ui.common.Position import de.jeanlucmakiola.calendula.ui.common.Position
import de.jeanlucmakiola.calendula.ui.common.positionOf import de.jeanlucmakiola.calendula.ui.common.positionOf
import de.jeanlucmakiola.calendula.ui.common.REMINDER_PRESETS import de.jeanlucmakiola.calendula.ui.common.REMINDER_PRESETS
import de.jeanlucmakiola.calendula.ui.common.ReminderUnit
import de.jeanlucmakiola.calendula.ui.common.TimePickerAlert import de.jeanlucmakiola.calendula.ui.common.TimePickerAlert
import de.jeanlucmakiola.floret.reminders.ReminderUnit
import de.jeanlucmakiola.calendula.ui.common.currentLocale import de.jeanlucmakiola.calendula.ui.common.currentLocale
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
import de.jeanlucmakiola.calendula.ui.common.timeOfDayFormatter import de.jeanlucmakiola.calendula.ui.common.timeOfDayFormatter

View File

@@ -84,7 +84,7 @@ import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.R
import de.jeanlucmakiola.calendula.data.prefs.CalendarReminderOverride import de.jeanlucmakiola.floret.reminders.ReminderOverride
import de.jeanlucmakiola.calendula.data.prefs.ThemeMode import de.jeanlucmakiola.calendula.data.prefs.ThemeMode
import de.jeanlucmakiola.calendula.data.prefs.TimeFormatPref import de.jeanlucmakiola.calendula.data.prefs.TimeFormatPref
import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref
@@ -973,12 +973,12 @@ private fun NotificationsScreen(
private data class OverrideTarget(val calendarId: Long, val isAllDay: Boolean) private data class OverrideTarget(val calendarId: Long, val isAllDay: Boolean)
/** A global default (null = none) as a picker choice for selection highlighting. */ /** A global default (null = none) as a picker choice for selection highlighting. */
private fun Int?.toReminderChoice(): CalendarReminderOverride = private fun Int?.toReminderChoice(): ReminderOverride =
if (this == null) CalendarReminderOverride.None else CalendarReminderOverride.Minutes(this) if (this == null) ReminderOverride.None else ReminderOverride.Minutes(this)
/** A picked choice as global-default minutes (Inherit isn't offered for globals). */ /** A picked choice as global-default minutes (Inherit isn't offered for globals). */
private fun CalendarReminderOverride.toMinutesOrNull(): Int? = private fun ReminderOverride.toMinutesOrNull(): Int? =
(this as? CalendarReminderOverride.Minutes)?.minutes (this as? ReminderOverride.Minutes)?.minutes
/** /**
* Whether Calendula is exempt from battery optimisation, re-read on every * Whether Calendula is exempt from battery optimisation, re-read on every
@@ -1053,10 +1053,10 @@ private fun formatTimeOfDay(context: Context, minutesOfDay: Int): String {
} }
/** The stored override for [calendarId], as a picker choice (absent → inherit). */ /** The stored override for [calendarId], as a picker choice (absent → inherit). */
private fun Map<Long, Int?>.choiceFor(calendarId: Long): CalendarReminderOverride = when { private fun Map<Long, Int?>.choiceFor(calendarId: Long): ReminderOverride = when {
!containsKey(calendarId) -> CalendarReminderOverride.Inherit !containsKey(calendarId) -> ReminderOverride.Inherit
this[calendarId] == null -> CalendarReminderOverride.None this[calendarId] == null -> ReminderOverride.None
else -> CalendarReminderOverride.Minutes(this.getValue(calendarId)!!) else -> ReminderOverride.Minutes(this.getValue(calendarId)!!)
} }
/** Label for a global-default choice: null → "None", else the lead time. */ /** Label for a global-default choice: null → "None", else the lead time. */
@@ -1067,13 +1067,13 @@ private fun reminderChoiceLabel(minutes: Int?): String =
/** Row summary for a calendar: its override, or the inherited global default. */ /** Row summary for a calendar: its override, or the inherited global default. */
@Composable @Composable
private fun calendarOverrideSummary( private fun calendarOverrideSummary(
choice: CalendarReminderOverride, choice: ReminderOverride,
globalDefault: Int?, globalDefault: Int?,
): String = when (choice) { ): String = when (choice) {
CalendarReminderOverride.Inherit -> ReminderOverride.Inherit ->
stringResource(R.string.settings_calendar_reminder_inherits, reminderChoiceLabel(globalDefault)) stringResource(R.string.settings_calendar_reminder_inherits, reminderChoiceLabel(globalDefault))
CalendarReminderOverride.None -> stringResource(R.string.reminder_none) ReminderOverride.None -> stringResource(R.string.reminder_none)
is CalendarReminderOverride.Minutes -> reminderLeadTimeLabel(choice.minutes) is ReminderOverride.Minutes -> reminderLeadTimeLabel(choice.minutes)
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@@ -10,9 +10,9 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import de.jeanlucmakiola.calendula.data.calendar.CalendarRepository import de.jeanlucmakiola.calendula.data.calendar.CalendarRepository
import de.jeanlucmakiola.calendula.data.prefs.CalendarReminderOverride
import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.calendula.data.prefs.ThemeMode import de.jeanlucmakiola.calendula.data.prefs.ThemeMode
import de.jeanlucmakiola.floret.reminders.ReminderOverride
import de.jeanlucmakiola.calendula.data.prefs.TimeFormatPref import de.jeanlucmakiola.calendula.data.prefs.TimeFormatPref
import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref
import de.jeanlucmakiola.calendula.domain.CalendarSource import de.jeanlucmakiola.calendula.domain.CalendarSource
@@ -229,11 +229,11 @@ class SettingsViewModel @Inject constructor(
viewModelScope.launch { prefs.setSnoozeMinutes(minutes) } viewModelScope.launch { prefs.setSnoozeMinutes(minutes) }
} }
fun setCalendarReminderOverride(calendarId: Long, override: CalendarReminderOverride) { fun setCalendarReminderOverride(calendarId: Long, override: ReminderOverride) {
viewModelScope.launch { prefs.setCalendarReminderOverride(calendarId, override) } viewModelScope.launch { prefs.setCalendarReminderOverride(calendarId, override) }
} }
fun setCalendarAllDayReminderOverride(calendarId: Long, override: CalendarReminderOverride) { fun setCalendarAllDayReminderOverride(calendarId: Long, override: ReminderOverride) {
viewModelScope.launch { prefs.setCalendarAllDayReminderOverride(calendarId, override) } viewModelScope.launch { prefs.setCalendarAllDayReminderOverride(calendarId, override) }
} }

View File

@@ -6,6 +6,7 @@ import androidx.datastore.preferences.core.Preferences
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import de.jeanlucmakiola.calendula.domain.EventFormField import de.jeanlucmakiola.calendula.domain.EventFormField
import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange
import de.jeanlucmakiola.floret.reminders.ReminderOverride
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.datetime.DayOfWeek import kotlinx.datetime.DayOfWeek
@@ -206,14 +207,14 @@ class SettingsPrefsTest {
val prefs = SettingsPrefs(newDataStore(tempDir)) val prefs = SettingsPrefs(newDataStore(tempDir))
assertThat(prefs.perCalendarReminderOverride.first()).isEmpty() assertThat(prefs.perCalendarReminderOverride.first()).isEmpty()
prefs.setCalendarReminderOverride(7L, CalendarReminderOverride.Minutes(15)) prefs.setCalendarReminderOverride(7L, ReminderOverride.Minutes(15))
prefs.setCalendarReminderOverride(9L, CalendarReminderOverride.None) prefs.setCalendarReminderOverride(9L, ReminderOverride.None)
prefs.perCalendarReminderOverride.first().let { map -> prefs.perCalendarReminderOverride.first().let { map ->
assertThat(map).containsExactly(7L, 15, 9L, null) assertThat(map).containsExactly(7L, 15, 9L, null)
} }
// Inherit drops the override entirely (absent != null value). // Inherit drops the override entirely (absent != null value).
prefs.setCalendarReminderOverride(9L, CalendarReminderOverride.Inherit) prefs.setCalendarReminderOverride(9L, ReminderOverride.Inherit)
prefs.perCalendarReminderOverride.first().let { map -> prefs.perCalendarReminderOverride.first().let { map ->
assertThat(map).containsExactly(7L, 15) assertThat(map).containsExactly(7L, 15)
assertThat(map.containsKey(9L)).isFalse() assertThat(map.containsKey(9L)).isFalse()
@@ -233,12 +234,12 @@ class SettingsPrefsTest {
@Test @Test
fun `per-calendar all-day override round-trips independently of the timed one`(@TempDir tempDir: Path) = runTest { fun `per-calendar all-day override round-trips independently of the timed one`(@TempDir tempDir: Path) = runTest {
val prefs = SettingsPrefs(newDataStore(tempDir)) val prefs = SettingsPrefs(newDataStore(tempDir))
prefs.setCalendarReminderOverride(7L, CalendarReminderOverride.Minutes(15)) prefs.setCalendarReminderOverride(7L, ReminderOverride.Minutes(15))
prefs.setCalendarAllDayReminderOverride(7L, CalendarReminderOverride.Minutes(1_440)) prefs.setCalendarAllDayReminderOverride(7L, ReminderOverride.Minutes(1_440))
assertThat(prefs.perCalendarReminderOverride.first()).containsExactly(7L, 15) assertThat(prefs.perCalendarReminderOverride.first()).containsExactly(7L, 15)
assertThat(prefs.perCalendarAllDayReminderOverride.first()).containsExactly(7L, 1_440) assertThat(prefs.perCalendarAllDayReminderOverride.first()).containsExactly(7L, 1_440)
// Clearing the all-day override leaves the timed one untouched. // Clearing the all-day override leaves the timed one untouched.
prefs.setCalendarAllDayReminderOverride(7L, CalendarReminderOverride.Inherit) prefs.setCalendarAllDayReminderOverride(7L, ReminderOverride.Inherit)
assertThat(prefs.perCalendarAllDayReminderOverride.first()).isEmpty() assertThat(prefs.perCalendarAllDayReminderOverride.first()).isEmpty()
assertThat(prefs.perCalendarReminderOverride.first()).containsExactly(7L, 15) assertThat(prefs.perCalendarReminderOverride.first()).containsExactly(7L, 15)
} }