diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1139b8b..47866af 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -123,6 +123,22 @@
+
+
+
+
+
+
+
+
`, the way AOSP fires it (e.g.
+ * tapping an existing event in the Todo Agenda widget, issue #48). Reuses the
+ * same occurrence-key channel as reminder taps. The launcher passes the
+ * occurrence's times as `EXTRA_EVENT_BEGIN_TIME` / `EXTRA_EVENT_END_TIME` when
+ * it has them; a bare URI omits them, so we carry [NO_OCCURRENCE_TIME] and
+ * [EventDetailViewModel] falls back to the event row's own DTSTART/DTEND
+ * rather than rendering at the epoch.
+ */
+ private fun Intent.viewEventKeyOrNull(): LongArray? {
+ 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() != "events") return null
+ val eventId = segments.getOrNull(1)?.toLongOrNull() ?: return null
+ return longArrayOf(
+ eventId,
+ longExtraOrNull(CalendarContract.EXTRA_EVENT_BEGIN_TIME) ?: NO_OCCURRENCE_TIME,
+ longExtraOrNull(CalendarContract.EXTRA_EVENT_END_TIME) ?: NO_OCCURRENCE_TIME,
+ )
+ }
+
companion object {
// The calendar provider's authority/host. A date tap arrives as
// ACTION_VIEW on content://com.android.calendar/time/.
diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailViewModel.kt
index 8374c21..4a4aebd 100644
--- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailViewModel.kt
+++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailViewModel.kt
@@ -141,13 +141,20 @@ class EventDetailViewModel @Inject constructor(
private suspend fun loadDetail(target: Target): EventDetailUiState = try {
val detail = repository.eventDetail(target.eventId)
// The Events row holds the series start; replace it with this
- // occurrence's time so recurring events render correctly.
- val corrected = detail.copy(
- instance = detail.instance.copy(
- start = Instant.fromEpochMilliseconds(target.beginMillis),
- end = Instant.fromEpochMilliseconds(target.endMillis),
- ),
- )
+ // occurrence's time so recurring events render correctly. An external
+ // "open event" that names no occurrence ([NO_OCCURRENCE_TIME] — e.g. a
+ // bare content://.../events/ VIEW intent, issue #48) keeps the row's
+ // own DTSTART/DTEND instead of overriding it to the epoch.
+ val corrected = if (target.beginMillis == NO_OCCURRENCE_TIME) {
+ detail
+ } else {
+ detail.copy(
+ instance = detail.instance.copy(
+ start = Instant.fromEpochMilliseconds(target.beginMillis),
+ end = Instant.fromEpochMilliseconds(target.endMillis),
+ ),
+ )
+ }
val calendar = repository.calendars().first()
.firstOrNull { it.id == corrected.instance.calendarId }
EventDetailUiState.Success(
@@ -168,6 +175,16 @@ class EventDetailViewModel @Inject constructor(
/** A tapped occurrence: the series [eventId] plus this occurrence's own times. */
private data class Target(val eventId: Long, val beginMillis: Long, val endMillis: Long)
+
+ companion object {
+ /**
+ * Sentinel begin/end for an "open this event" that names no occurrence —
+ * a bare `content://com.android.calendar/events/` VIEW intent with no
+ * `EXTRA_EVENT_BEGIN_TIME` (issue #48). [loadDetail] then keeps the event
+ * row's own DTSTART/DTEND instead of overriding it to the epoch.
+ */
+ const val NO_OCCURRENCE_TIME: Long = Long.MIN_VALUE
+ }
}
/** A filesystem-safe `.ics` file name from an event title (or a fallback). */