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). */