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:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user