Skip to Content
📱 Using the Appviewing-event-details

Last Updated: 6/9/2026


Viewing Event Details

Access comprehensive event information from the widget or app, including event time, location, attendees, RSVP status, and Zoom meeting links. The event details screen provides quick access to join meetings and respond to invitations.

Prerequisites

  • ZCal Buddy installed with widget configured
  • Zoom account connected
  • At least one calendar event synced

Opening Event Details

From the Widget

Tap any event card in the home screen widget. The widget creates an intent with event data:

Intent(context, MainActivity::class.java).apply { putExtra("screen", "event_details") putExtra("event_id", event.eventId) putExtra("event_calendar_id", event.calendarId) putExtra("event_title", event.title) putExtra("event_time", event.time) putExtra("event_location", event.location) putExtra("event_description", event.description) putExtra("event_zoom_url", event.zoomUrl) putExtra("event_start_millis", event.startMillis) putExtra("event_end_millis", event.endMillis) putExtra("event_all_day", event.allDay) }

The app opens to the EventDetailsScreen composable with the event data pre-filled.

From the App

  1. Open ZCal Buddy
  2. The home screen shows “Calendar synced.” if events are loaded
  3. Navigate to an event (implementation depends on app navigation)
  4. The event details screen displays

Event Information Display

The EventDetailsScreen composable shows event details in a card layout:

Event Title

The event title appears as a headline at the top of the details card:

Text( text = title, style = MaterialTheme.typography.headlineSmall )

If the event has no title, the widget displays “(No title)” but this placeholder isn’t shown in the details screen—the field would be blank.

Event Time

The time field displays the formatted time range from the event.time property. This is pre-formatted when the event is loaded from the calendar:

if (!time.isNullOrBlank()) { Text(text = time) }

For all-day events, the time field may show “All day” or be omitted entirely depending on how the event was formatted during sync.

Event Location

The location appears below the time if present:

if (!location.isNullOrBlank()) { Text(text = location) }

For Zoom Meeting events, the location field may contain the meeting room name or be empty. The Zoom join URL is displayed separately as a button.

If the event has a Zoom URL (!zoomUrl.isNullOrBlank()), a Join Zoom button appears:

if (!zoomUrl.isNullOrBlank()) { Button( onClick = { context.startActivity( Intent(Intent.ACTION_VIEW, Uri.parse(zoomUrl)) ) } ) { Text("Join Zoom") } }

Tapping this button opens the Zoom meeting using the system’s default handler for Zoom URLs.

Joining Zoom Meetings

From Event Details

Tap the Join Zoom button in the event details card. The app creates an ACTION_VIEW intent with the Zoom URL:

Intent(Intent.ACTION_VIEW, Uri.parse(zoomUrl))

Android routes this intent based on installed apps and URL handlers:

  1. Zoom app installed: The Zoom app opens directly to the meeting
  2. Zoom app not installed: The browser opens the Zoom web client
  3. Multiple handlers: Android shows an app chooser

From the Widget

Tap the blue Join button on any event card with a Zoom meeting. The widget creates a more specific intent that targets the Zoom app:

Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply { setPackage("us.zoom.videomeetings") }

This intent explicitly targets the Zoom app package (us.zoom.videomeetings). If the Zoom app isn’t installed, the intent fails and the system may prompt you to install it.

Opening Zoom App Directly

The widget header’s date label acts as a button to open the Zoom app. The openZoomApp function in MainActivitySupport.kt handles this:

fun openZoomApp(context: Context) { val packageName = "us.zoom.videomeetings" val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName) if (launchIntent != null) { launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) context.startActivity(launchIntent) return } // Fallback to Zoom URL scheme val zoomSchemeIntent = Intent( Intent.ACTION_VIEW, Uri.parse("zoomus://zoom.us") ).apply { setPackage(packageName) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } runCatching { context.startActivity(zoomSchemeIntent) }.onFailure { // Final fallback to Play Store context.startActivity( Intent( Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$packageName") ).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } ) } }

The function tries three approaches:

  1. Launch intent from package manager
  2. Custom Zoom URL scheme (zoomus://zoom.us)
  3. Play Store link if Zoom isn’t installed

RSVP Functionality

The event details screen includes an RSVP section when canRespond == true. This allows you to respond to meeting invitations.

RSVP Options

Three response buttons appear in the “Going?” section:

Yes: Sets responseStatus = "accepted"

Maybe: Sets responseStatus = "tentative"

No: Sets responseStatus = "declined"

The currently selected response is shown as a filled button, while unselected options appear as outlined buttons:

@Composable fun RsvpButton( label: String, selected: Boolean, onClick: () -> Unit ) { if (selected) { Button(onClick = onClick) { Text(label) } } else { OutlinedButton(onClick = onClick) { Text(label) } } }

Submitting RSVP

When you tap an RSVP button, the app calls submitRsvpResponse:

val result = submitRsvpResponse( context = context, eventId = eventId, calendarId = calendarId, selfAttendeeEmail = selfAttendeeEmail, responseStatus = "accepted" // or "tentative" or "declined" )

The function performs two operations:

1. Update via API (if selfAttendeeEmail is not blank):

ZoomAuthRepository().updateMyResponseStatus( accessToken = accessToken, calendarId = calendarId, eventId = eventId, selfEmail = attendeeEmail, responseStatus = responseStatus )

This sends a PATCH request to https://api.zoom.us/v2/calendars/{calendarId}/events/{eventId}?sendUpdates=all with the updated attendee response.

2. Update local storage:

The function updates the cached event in local storage to reflect the new response status:

val updatedEvents = currentEvents.map { event -> if (event.eventId == eventId && event.calendarId == calendarId) { event.copy( selfAttendeeEmail = effectiveAttendeeEmail, selfResponseStatus = responseStatus, attendees = event.attendees.map { attendee -> if (attendee.self || attendee.email.equals(effectiveAttendeeEmail, true)) { attendee.copy(responseStatus = responseStatus) } else { attendee } } ) } else { event } }

This ensures the widget immediately reflects your response without waiting for the next API sync.

RSVP Status Display

The widget displays events differently based on your response status:

Accepted or Tentative (selfResponseStatus == "accepted" or "tentative"):

  • Solid colored background
  • Normal title text
  • Event appears in your schedule

Declined (selfResponseStatus == "declined"):

  • Hollow outline (colored border with dark background)
  • Strikethrough title: ”━━━━ Event Title ━━━━”
  • Muted appearance to indicate you’re not attending

The widget determines the display style in the EventCard composable:

val isDeclined = event.selfResponseStatus.equals("declined", true) val isAccepted = event.selfResponseStatus.equals("accepted", true) || event.selfResponseStatus.equals("tentative", true) val isHollow = !isAccepted || isDeclined

Event Details Screen Actions

Back Button

Tap Back in the top-left corner to return to the previous screen. The onBack callback is provided by the parent composable:

Button(onClick = onBack) { Text("Back") }

This typically returns to the home screen.

Edit Button

Tap Edit in the top-right corner to open the event editor. The onEdit callback navigates to the edit screen:

Button(onClick = onEdit) { Text("Edit") }

This opens EditEventScreen with the current event data pre-filled.

Refresh After RSVP

After submitting an RSVP, the app calls onRefresh() to reload the event data:

if (result.success) { selectedStatus.value = "accepted" onRefresh() }

The refresh callback fetches updated events from local storage and refreshes the widget:

onRefresh = { scope.launch { val refreshedEvents = withContext(Dispatchers.IO) { CalendarRepository.getEvents(context) } val refreshedCalendars = withContext(Dispatchers.IO) { CalendarListRepository.getCalendars(context) } events.value = refreshedEvents calendars.value = refreshedCalendars forceWidgetRefresh(context) } }

Event Properties Reference

The CalendarEvent data class contains these properties displayed in the details screen:

PropertyTypeDescription
eventIdStringUnique event identifier
calendarIdStringParent calendar identifier
titleStringEvent name/summary
timeStringFormatted time range
locationStringEvent location
descriptionStringEvent notes/details
zoomUrlString?Zoom meeting join URL (null if no meeting)
startMillisLongStart time as Unix timestamp
endMillisLongEnd time as Unix timestamp
allDayBooleanWhether event spans entire day
selfResponseStatusStringYour RSVP status: “accepted”, “tentative”, “declined”, or “needsAction”
attendeesList<EventAttendee>List of invitees
selfAttendeeEmailString?Your email address as an attendee

Troubleshooting Details Screen

“Join Zoom” button doesn’t appear

The event doesn’t have a Zoom URL. This happens when:

  • The event was created as “Offline” type
  • The Zoom meeting creation failed during event creation
  • The meeting was deleted but the event wasn’t updated

Edit the event and convert it to “Zoom Meeting” type to add a meeting.

Zoom app doesn’t open when tapping “Join Zoom”

The Zoom app may not be installed or the URL handler isn’t configured. Try:

  1. Install the Zoom app from the Play Store
  2. Open the Zoom app once to initialize URL handlers
  3. Try joining from the widget’s Join button instead

RSVP buttons don’t appear

The canRespond parameter is false. This happens when:

  • The event data doesn’t include your attendee email
  • You’re the organizer (organizers don’t RSVP to their own events)
  • The event wasn’t loaded with full attendee data

RSVP update fails with “No saved Zoom access token”

Your Zoom connection expired. The app needs a valid access token to update your response via the API. Tap Connect Zoom on the home screen to re-authenticate.

Response updated but widget still shows old status

The local storage was updated but the widget hasn’t refreshed. Try:

  1. Wait a few seconds for the automatic refresh
  2. Tap the refresh button (↻) in the widget header
  3. Close and reopen the app

Event time shows in wrong time zone

The app displays events in your device’s local time zone. The event is stored with its original time zone, but the display converts it to your system time zone. Check your device’s date & time settings if the conversion appears incorrect.

What’s Next

You’ve learned how to view event details, join Zoom meetings, and respond to invitations. To customize how events appear in your widget, explore the widget settings to configure display options, calendar filters, and refresh behavior.