diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesScheduler.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesScheduler.kt index 05a1bac..a906f9b 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesScheduler.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesScheduler.kt @@ -83,9 +83,6 @@ class SpecialDatesSyncWorker( override suspend fun doWork(): Result { val deps = EntryPointAccessors.fromApplication(applicationContext, Deps::class.java) val prefs = deps.settingsPrefs() - // Respect the toggle even for already-queued work; never revive a - // disabled feature. - if (!prefs.specialDatesEnabled.first()) return Result.success() val now = System.currentTimeMillis() // A foreground resume can fire often — skip if we synced recently. @@ -95,13 +92,19 @@ class SpecialDatesSyncWorker( } return try { - val result = deps.syncEngine().sync() - val stalled = if (result == SpecialDatesSyncResult.PermissionMissing) { - SpecialDatesStalledReason.PermissionRevoked - } else { - null + when (val result = deps.syncEngine().sync()) { + // The engine re-checks the toggle, so already-queued work never + // revives a disabled feature; don't record a run either. + SpecialDatesSyncResult.Disabled -> return Result.success() + else -> { + val stalled = if (result == SpecialDatesSyncResult.PermissionMissing) { + SpecialDatesStalledReason.PermissionRevoked + } else { + null + } + prefs.recordSpecialDatesRun(now, stalled) + } } - prefs.recordSpecialDatesRun(now, stalled) if (foreground) prefs.setSpecialDatesLastForegroundSync(now) Result.success() } catch (e: Exception) { diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesSyncEngine.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesSyncEngine.kt index fa3369f..082d3bc 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesSyncEngine.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/contacts/SpecialDatesSyncEngine.kt @@ -6,7 +6,6 @@ import de.jeanlucmakiola.calendula.data.calendar.ManagedEventRow import de.jeanlucmakiola.calendula.data.calendar.toWriteTimes import de.jeanlucmakiola.calendula.data.prefs.CalendarReminderOverride import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs -import de.jeanlucmakiola.calendula.data.prefs.resolveDefaultReminder import de.jeanlucmakiola.calendula.domain.Availability import de.jeanlucmakiola.calendula.domain.EventForm import de.jeanlucmakiola.calendula.domain.RecurrenceFreq @@ -39,6 +38,16 @@ interface SpecialDatesCalendarSpec { fun defaultTitleTemplate(type: SpecialDateType): String } +/** + * The stored title template for [type], falling back to the localized default + * when blank/absent — the one resolution rule shared by the sync (what gets + * written) and the settings editor (what gets shown). + */ +fun SpecialDatesCalendarSpec.resolveTitleTemplate( + type: SpecialDateType, + stored: Map, +): String = stored[type]?.takeIf { it.isNotBlank() } ?: defaultTitleTemplate(type) + /** * Mirrors contact birthdays/anniversaries/custom dates into local calendars, * one per type. Every reconcile is an idempotent diff keyed on the deterministic @@ -72,8 +81,7 @@ class SpecialDatesSyncEngine @Inject constructor( val showYear = prefs.specialDatesShowYear.first() calendarByType.forEach { (type, calendarId) -> - val template = templates[type]?.takeIf { it.isNotBlank() } - ?: spec.defaultTitleTemplate(type) + val template = spec.resolveTitleTemplate(type, templates) syncType( calendarId = calendarId, type = type, @@ -143,8 +151,8 @@ class SpecialDatesSyncEngine @Inject constructor( } private suspend fun createCalendar(type: SpecialDateType): Long { + // reconcileCalendars persists the id — the single place ids are recorded. val id = calendars.createManagedCalendar(spec.displayName(type), spec.color(type), type) - prefs.setSpecialDatesCalendarId(type, id) // Seed a useful all-day reminder default (on the day + a week before), so // birthdays get lead time out of the box. Per-calendar, user-adjustable — // only set when the user hasn't already configured this calendar. @@ -212,29 +220,21 @@ class SpecialDatesSyncEngine @Inject constructor( ) } + // Managed events are always all-day, so only the all-day defaults apply. private suspend fun readReminderContext(): ReminderContext = ReminderContext( - timedGlobal = prefs.defaultReminderMinutes.first(), allDayGlobal = prefs.defaultAllDayReminderMinutes.first(), - timedOverrides = prefs.perCalendarReminderOverride.first(), allDayOverrides = prefs.perCalendarAllDayReminderOverride.first(), allDayTimeMinutes = prefs.allDayReminderTimeMinutes.first(), ) private data class ReminderContext( - val timedGlobal: List, val allDayGlobal: List, - val timedOverrides: Map>, val allDayOverrides: Map>, val allDayTimeMinutes: Int, ) { - fun resolveAllDay(calendarId: Long): List = resolveDefaultReminder( - timedGlobal = timedGlobal, - allDayGlobal = allDayGlobal, - timedOverrides = timedOverrides, - allDayOverrides = allDayOverrides, - calendarId = calendarId, - isAllDay = true, - ) + /** Same semantics as `resolveDefaultReminder`: present-empty = explicit none. */ + fun resolveAllDay(calendarId: Long): List = + allDayOverrides[calendarId] ?: allDayGlobal } private data class BuiltManagedEvent(val desired: ManagedEventDesired, val form: EventForm)