All checks were successful
Release — F-Droid repo + Gitea/Codeberg release + Play / detect (push) Successful in 6s
Release — F-Droid repo + Gitea/Codeberg release + Play / play (push) Has been skipped
Release — F-Droid repo + Gitea/Codeberg release + Play / release (push) Has been skipped
Renovate / renovate (push) Successful in 2m21s
Release v2.19.4. **Fixed** - A deleted occurrence stays deleted through a series re-timing, and through backup/restore ([#225]). - "Only this event" saves the edit on calendars that never synced, and on a row that doesn't recur it edits or deletes the event itself instead of filing an exception against nothing. - A reminder left on the account's default is no longer read as a lead time of Calendula's own. - The widgets turn the page at midnight; the re-arm backstop for the inexact rollover alarm is back, throttled to once per 15 minutes on calendar changes. - An event running past midnight can be dragged by either half ([#253]). - One event the provider refuses no longer costs the rest of the import, and an import cut short reports honestly how far it got. - EXDATE zone prefixes are read in the bare `<zone>;` form the provider and DAVx5 actually write, not only the iCalendar `TZID=` spelling; a list holding a stamp we can't parse is kept whole rather than re-emitted stripped of its zone; RDATE travels with RRULE when the recurrence set is rewritten. **Added** - Belarusian, Hungarian and Slovak, from community translators on Weblate. **Changed** - Arabic, Polish and Simplified Chinese are complete; Czech grew, and Russian is now at about half. Bumps `versionName` to 2.19.4, so merging this triggers the release pipeline. Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de> Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/259
125 lines
5.1 KiB
Kotlin
125 lines
5.1 KiB
Kotlin
package de.jeanlucmakiola.calendula
|
|
|
|
import android.app.Application
|
|
import dagger.hilt.android.EntryPointAccessors
|
|
import dagger.hilt.android.HiltAndroidApp
|
|
import de.jeanlucmakiola.calendula.data.backup.BackupScheduler
|
|
import de.jeanlucmakiola.calendula.data.calendar.CalendarVisibilityReconciler
|
|
import de.jeanlucmakiola.calendula.data.backup.BackupWorker
|
|
import de.jeanlucmakiola.calendula.data.contacts.SpecialDatesScheduler
|
|
import de.jeanlucmakiola.calendula.data.contacts.SpecialDatesSyncWorker
|
|
import de.jeanlucmakiola.calendula.data.reminders.ReminderMaintenanceScheduler
|
|
import de.jeanlucmakiola.calendula.data.reminders.ReminderMaintenanceWorker
|
|
import de.jeanlucmakiola.calendula.widget.WidgetRolloverScheduler
|
|
import de.jeanlucmakiola.floret.crash.CrashConfig
|
|
import de.jeanlucmakiola.floret.crash.CrashReporter
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.SupervisorJob
|
|
import kotlinx.coroutines.flow.first
|
|
import kotlinx.coroutines.launch
|
|
|
|
/**
|
|
* Application entry point. Registered as android:name=".CalendulaApp"
|
|
* in AndroidManifest.xml. Hilt initializes its component graph here.
|
|
*/
|
|
@HiltAndroidApp
|
|
class CalendulaApp : Application() {
|
|
|
|
override fun onCreate() {
|
|
super.onCreate()
|
|
// Install first thing so startup crashes are captured too (privacy-
|
|
// respecting, on-device; the user submits the report by hand). The
|
|
// capture/loop-detection/report machinery lives in floret-kit's
|
|
// core-crash; only the app label + issue-tracker URLs are app-specific.
|
|
CrashReporter.install(
|
|
this,
|
|
CrashConfig(
|
|
appLabel = getString(R.string.app_name),
|
|
newIssueUrl = getString(R.string.report_issue_url),
|
|
chooseIssueUrl = getString(R.string.report_issue_choose_url),
|
|
issueTitle = getString(R.string.crash_report_issue_title),
|
|
),
|
|
)
|
|
reconcileAutoBackup()
|
|
reconcileSpecialDates()
|
|
reconcileCalendarVisibility()
|
|
startReminderDelivery()
|
|
reconcileWidgetRollover()
|
|
}
|
|
|
|
/**
|
|
* Re-arm the widgets' midnight rollover from whatever is actually placed
|
|
* (#228). Idempotent, and it covers what no broadcast reaches — an alarm
|
|
* dropped by a force-stop is armed again the next time the app is opened.
|
|
* Off the main thread: a handful of binder calls on every process start.
|
|
*/
|
|
private fun reconcileWidgetRollover() {
|
|
CoroutineScope(SupervisorJob() + Dispatchers.Default).launch {
|
|
WidgetRolloverScheduler.sync(this@CalendulaApp)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bring reminder delivery up with the process (#75): a scan re-arms whatever
|
|
* the system dropped and posts what a missed alarm still owes, then the
|
|
* provider watch keeps edits re-planned. The daily worker is the backstop.
|
|
*/
|
|
private fun startReminderDelivery() {
|
|
val deps = EntryPointAccessors.fromApplication(
|
|
this, ReminderMaintenanceWorker.Deps::class.java,
|
|
)
|
|
val scanner = deps.reminderScanner()
|
|
scanner.startWatchingProvider()
|
|
scanner.scanInBackground()
|
|
ReminderMaintenanceScheduler.apply(this)
|
|
}
|
|
|
|
/**
|
|
* Flush any calendar switch-off not yet written to `Calendars.VISIBLE`,
|
|
* including the set inherited from the retired app-local model (#75). A
|
|
* no-op in the steady state; `RootScreen` re-runs it after a later grant.
|
|
*/
|
|
private fun reconcileCalendarVisibility() {
|
|
val deps = EntryPointAccessors.fromApplication(
|
|
this, CalendarVisibilityReconciler.Deps::class.java,
|
|
)
|
|
CoroutineScope(SupervisorJob() + Dispatchers.Default).launch {
|
|
deps.calendarVisibilityReconciler().run()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bring the scheduled auto-backup work back in line with the saved settings
|
|
* on every launch — re-arms it after a reinstall and, crucially, cancels any
|
|
* orphaned work once backup has been turned off.
|
|
*/
|
|
private fun reconcileAutoBackup() {
|
|
val deps = EntryPointAccessors.fromApplication(this, BackupWorker.Deps::class.java)
|
|
CoroutineScope(SupervisorJob() + Dispatchers.Default).launch {
|
|
val prefs = deps.settingsPrefs()
|
|
BackupScheduler.apply(
|
|
context = this@CalendulaApp,
|
|
enabled = prefs.autoBackupEnabled.first(),
|
|
intervalMinutes = prefs.autoBackupIntervalMinutes.first(),
|
|
hasFolder = prefs.autoBackupFolderUri.first() != null,
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Re-arm (or cancel) the daily special-dates reconcile from the saved
|
|
* settings, like [reconcileAutoBackup]. The on-open refresh is RootScreen's
|
|
* ON_RESUME trigger, so no immediate run is needed here.
|
|
*/
|
|
private fun reconcileSpecialDates() {
|
|
val deps = EntryPointAccessors.fromApplication(this, SpecialDatesSyncWorker.Deps::class.java)
|
|
CoroutineScope(SupervisorJob() + Dispatchers.Default).launch {
|
|
SpecialDatesScheduler.apply(
|
|
context = this@CalendulaApp,
|
|
enabled = deps.settingsPrefs().specialDatesEnabled.first(),
|
|
)
|
|
}
|
|
}
|
|
}
|