From b6a45b7264cab2b3926a00dd77d578654615af4b Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 28 Jun 2026 12:15:56 +0200 Subject: [PATCH] fix(intents): register calendar intent filters so Calendula can be the default calendar (#9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calendula didn't declare the intent filters launchers and the system use for calendar actions, so it never appeared in the "default calendar app" chooser — on every platform, not just GrapheneOS (issue #9). Android exposes no API for an app to set itself default, so registering these filters is the only way users can pick it from the system picker. Adds to MainActivity: - MAIN + APP_CALENDAR — the "open the calendar app" action the OS/launchers use. - VIEW on content://com.android.calendar/time/ and the time/epoch mime type — a launcher/clock date tap. The provider's time Uri is parsed into a LocalDate and opened on the day view, rooted over the default home view (a new sourceless WidgetNavRequest.OpenDate). The .ics import path now ignores the calendar provider host so a date tap isn't mistaken for a file to import. Bumps to 2.11.1. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 10 +++++++ app/build.gradle.kts | 4 +-- app/src/main/AndroidManifest.xml | 29 ++++++++++++++++++ .../jeanlucmakiola/calendula/MainActivity.kt | 30 +++++++++++++++++++ .../calendula/ui/CalendarHost.kt | 4 ++- .../calendula/ui/WidgetNavRequest.kt | 9 ++++-- .../android/en-US/changelogs/21101.txt | 6 ++++ 7 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/21101.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index db69497..398434f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.11.1] — 2026-06-28 + +### Fixed +- Calendula can now be set as your default calendar app. It registers the + calendar-app intent filters the system uses, so it appears in the chooser when + you tap a date in a launcher or clock — and opening one takes you straight to + that day. Android has no way for an app to make itself the default, so you pick + it once from the system picker. Thanks to @abrossimow for the report ([#9]). + ## [2.11.0] — 2026-06-27 ### Added @@ -685,3 +694,4 @@ automatically, with zero telemetry and no internet permission. [#6]: https://codeberg.org/jlmakiola/calendula/issues/6 [#7]: https://codeberg.org/jlmakiola/calendula/issues/7 [#8]: https://codeberg.org/jlmakiola/calendula/issues/8 +[#9]: https://codeberg.org/jlmakiola/calendula/issues/9 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 46440cf..51bb325 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -28,8 +28,8 @@ android { // which builds this version and then creates the matching vX.Y.Z tag + // release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 + // PATCH from versionName, e.g. 2.7.2 -> 20702). See docs/RELEASING.md. - versionCode = 21100 - versionName = "2.11.0" + versionCode = 21101 + versionName = "2.11.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 04a21a8..29f9c5e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -59,6 +59,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/MainActivity.kt b/app/src/main/java/de/jeanlucmakiola/calendula/MainActivity.kt index 840f09b..a2de42c 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/MainActivity.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/MainActivity.kt @@ -35,6 +35,9 @@ import de.jeanlucmakiola.calendula.ui.crash.submitCrashReport import de.jeanlucmakiola.calendula.ui.settings.SettingsViewModel import de.jeanlucmakiola.calendula.ui.theme.CalendulaTheme import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import kotlin.time.Instant @AndroidEntryPoint class MainActivity : AppCompatActivity() { @@ -156,10 +159,33 @@ class MainActivity : AppCompatActivity() { Intent.ACTION_SEND -> IntentCompat.getParcelableExtra(this, Intent.EXTRA_STREAM, Uri::class.java) else -> null } ?: return null + // The calendar "view time" Uri (a date tap) is also ACTION_VIEW/content; + // it's a navigation, not a file to import, so [navRequestOrNull] owns it. + if (uri.host == CALENDAR_PROVIDER_HOST) return null return uri.takeIf { it.scheme == "content" || it.scheme == "file" } } + /** + * The date a launcher/clock date tap points at, parsed from the AOSP calendar + * "view time" intent: ACTION_VIEW on `content://com.android.calendar/time/ + * `. Null for any other intent. The matching manifest filter is + * what lets users pick Calendula from the system calendar chooser (issue #9). + */ + private fun Intent.calendarTimeDateOrNull(): LocalDate? { + if (action != Intent.ACTION_VIEW) return null + val uri = data ?: return null + if (uri.host != CALENDAR_PROVIDER_HOST) return null + val segments = uri.pathSegments + if (segments.firstOrNull() != "time") return null + val millis = segments.getOrNull(1)?.toLongOrNull() ?: return null + return Instant.fromEpochMilliseconds(millis) + .toLocalDateTime(TimeZone.currentSystemDefault()).date + } + private fun Intent.navRequestOrNull(): WidgetNavRequest? { + // An external date tap (launcher/clock) has no widget source, so it opens + // the day view rooted over the default home view (OpenDate source = null). + calendarTimeDateOrNull()?.let { return WidgetNavRequest.OpenDate(it.toString(), source = null) } val source = sourceViewOrNull() val eventId = getLongExtra(EXTRA_EVENT_ID, -1L) return when { @@ -201,6 +227,10 @@ class MainActivity : AppCompatActivity() { } companion object { + // The calendar provider's authority/host. A date tap arrives as + // ACTION_VIEW on content://com.android.calendar/time/. + private const val CALENDAR_PROVIDER_HOST = "com.android.calendar" + private const val EXTRA_EVENT_ID = "de.jeanlucmakiola.calendula.extra.EVENT_ID" private const val EXTRA_BEGIN_MILLIS = "de.jeanlucmakiola.calendula.extra.BEGIN" private const val EXTRA_END_MILLIS = "de.jeanlucmakiola.calendula.extra.END" diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt index d300334..50ee186 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt @@ -196,7 +196,9 @@ fun CalendarHost( dismissCoveringOverlays() createDateIso = null pendingDayIso = req.dateIso - viewStack = viewBaseStack(defaultView, req.source).drillToDay() + // No widget source (an external date tap) roots over the default + // home view, so backing out of the day returns home then exits. + viewStack = viewBaseStack(defaultView, req.source ?: defaultView).drillToDay() onWidgetNavConsumed() } is WidgetNavRequest.OpenEvent -> { diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/WidgetNavRequest.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/WidgetNavRequest.kt index 91889a5..6588d10 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/WidgetNavRequest.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/WidgetNavRequest.kt @@ -13,8 +13,13 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarView * detail-key channel and leave the base view untouched.) */ sealed interface WidgetNavRequest { - /** Open the day view anchored on [dateIso] (an ISO `yyyy-MM-dd` date), over [source]. */ - data class OpenDate(val dateIso: String, val source: CalendarView) : WidgetNavRequest + /** + * Open the day view anchored on [dateIso] (an ISO `yyyy-MM-dd` date), over + * [source]. A null [source] means the request came from outside the app (a + * launcher/clock date tap, issue #9) rather than a widget, so it roots over + * the default home view instead of a widget's view. + */ + data class OpenDate(val dateIso: String, val source: CalendarView?) : WidgetNavRequest /** Open one occurrence's detail (an agenda-widget event tap), over [source]. */ data class OpenEvent( diff --git a/fastlane/metadata/android/en-US/changelogs/21101.txt b/fastlane/metadata/android/en-US/changelogs/21101.txt new file mode 100644 index 0000000..8bf0960 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/21101.txt @@ -0,0 +1,6 @@ +### Fixed +- Calendula can now be set as your default calendar app. It registers the + calendar-app intent filters the system uses, so it appears in the chooser when + you tap a date in a launcher or clock — and opening one takes you straight to + that day. Android has no way for an app to make itself the default, so you pick + it once from the system picker. Thanks to @abrossimow for the report ([#9]).