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]).