summaryrefslogtreecommitdiff
path: root/android/app/src/main/java/org
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-13 17:52:10 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-16 02:42:31 +0000
commitc4066f78d83b1440155248f9bc724b905aa88697 (patch)
tree70b557a1e340f12aa125fd0c53effabcbda4027b /android/app/src/main/java/org
parentfb41a5eb47a4213c3f9a7095da4b8741f1598725 (diff)
feat(widget): tap event to open its source directly, drop detail popup
Tapping a calendar event (or any event-type item) now opens the event's URL (Google Calendar, Plan to Eat, etc.) directly instead of showing an intermediate popup with an "Open in Calendar" button. Removes EventDetailActivity and the recurrence-schedule lookup it was the only consumer of: WidgetRepository.getRecurrence, the Go /api/widget/recurrence endpoint, HandleWidgetRecurrence, GoogleCalendarAPI.GetRecurrenceRule, and formatRecurrence, plus their tests. RecurringEventID itself stays -- it's general calendar-sync metadata used elsewhere in the timeline pipeline, not exclusive to the removed popup.
Diffstat (limited to 'android/app/src/main/java/org')
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/data/WidgetRepository.kt20
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/ui/DootWidget.kt37
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/ui/EventDetailActivity.kt145
3 files changed, 10 insertions, 192 deletions
diff --git a/android/app/src/main/java/org/terst/doot/widget/data/WidgetRepository.kt b/android/app/src/main/java/org/terst/doot/widget/data/WidgetRepository.kt
index 3574eb5..134e3bc 100644
--- a/android/app/src/main/java/org/terst/doot/widget/data/WidgetRepository.kt
+++ b/android/app/src/main/java/org/terst/doot/widget/data/WidgetRepository.kt
@@ -19,9 +19,6 @@ private data class UpdateDescriptionRequest(val id: String, val source: String,
@Serializable
private data class WidgetAddRequest(val title: String)
-@Serializable
-private data class RecurrenceResponse(val recurrence: String)
-
/**
* Optimistically drops an item from the cached list so the widget can update instantly on
* completion, ahead of the authoritative refresh a worker performs once the API call succeeds.
@@ -148,21 +145,4 @@ class WidgetRepository(
check(response.isSuccessful) { "HTTP ${response.code}" }
}
}
-
- /** GETs the formatted recurrence schedule for a recurring event. */
- suspend fun getRecurrence(recurringEventId: String): Result<String> =
- withContext(Dispatchers.IO) {
- val encodedId = java.net.URLEncoder.encode(recurringEventId, "UTF-8")
- val request = Request.Builder()
- .url("$serverUrl/api/widget/recurrence?recurring_event_id=$encodedId")
- .header("Authorization", "Bearer $token")
- .build()
- runCatching {
- val response = client.newCall(request).execute()
- check(response.isSuccessful) { "HTTP ${response.code}" }
- val body = checkNotNull(response.body?.string()) { "Empty body" }
- val parsed = json.decodeFromString<RecurrenceResponse>(body)
- parsed.recurrence
- }
- }
}
diff --git a/android/app/src/main/java/org/terst/doot/widget/ui/DootWidget.kt b/android/app/src/main/java/org/terst/doot/widget/ui/DootWidget.kt
index 85a93d6..1983383 100644
--- a/android/app/src/main/java/org/terst/doot/widget/ui/DootWidget.kt
+++ b/android/app/src/main/java/org/terst/doot/widget/ui/DootWidget.kt
@@ -2,6 +2,7 @@ package org.terst.doot.widget.ui
import android.content.Context
import android.content.Intent
+import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@@ -37,6 +38,12 @@ fun sourceColor(source: String): Color = when (source) {
else -> Color(0xFF888888)
}
+/** Opens the event's source URL (Google Calendar, Plan to Eat, etc.) directly. */
+fun calendarViewIntent(url: String): Intent =
+ Intent(Intent.ACTION_VIEW, Uri.parse(url.ifEmpty { "https://calendar.google.com" })).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
private val json = Json { ignoreUnknownKeys = true }
class DootWidget : GlanceAppWidget() {
@@ -154,20 +161,12 @@ fun WidgetRoot(items: List<WidgetItem>, now: Instant, isRefreshing: Boolean, tex
@Composable
fun AllDayRow(event: WidgetItem, textSize: WidgetTextSize) {
- val context = LocalContext.current
- val detailIntent = Intent(context, EventDetailActivity::class.java).apply {
- putExtra(EventDetailActivity.EXTRA_TITLE, event.title)
- putExtra(EventDetailActivity.EXTRA_SOURCE, event.source)
- putExtra(EventDetailActivity.EXTRA_URL, event.url)
- putExtra(EventDetailActivity.EXTRA_RECURRING_EVENT_ID, event.recurringEventId)
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
val color = sourceColor(event.source)
Row(
modifier = GlanceModifier
.fillMaxWidth()
.padding(vertical = 3.dp)
- .clickable(actionStartActivity(detailIntent)),
+ .clickable(actionStartActivity(calendarViewIntent(event.url))),
verticalAlignment = Alignment.CenterVertically
) {
Box(modifier = GlanceModifier.width(3.dp).height(16.dp).background(color)) {}
@@ -291,21 +290,13 @@ fun HourRow(
@Composable
fun EventBlock(event: WidgetItem, isPast: Boolean, textSize: WidgetTextSize) {
- val context = LocalContext.current
- val detailIntent = Intent(context, EventDetailActivity::class.java).apply {
- putExtra(EventDetailActivity.EXTRA_TITLE, event.title)
- putExtra(EventDetailActivity.EXTRA_SOURCE, event.source)
- putExtra(EventDetailActivity.EXTRA_URL, event.url)
- putExtra(EventDetailActivity.EXTRA_RECURRING_EVENT_ID, event.recurringEventId)
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
val color = sourceColor(event.source)
val alpha = if (isPast) 0.5f else 1f
Row(
modifier = GlanceModifier
.fillMaxWidth()
.padding(vertical = 4.dp)
- .clickable(actionStartActivity(detailIntent)),
+ .clickable(actionStartActivity(calendarViewIntent(event.url))),
verticalAlignment = Alignment.CenterVertically
) {
Box(
@@ -453,14 +444,6 @@ fun TomorrowSection(items: List<WidgetItem>, fragments: List<TaskFragment>, allD
@Composable
fun TomorrowEventRow(event: WidgetItem, zone: ZoneId, textSize: WidgetTextSize) {
- val context = LocalContext.current
- val detailIntent = Intent(context, EventDetailActivity::class.java).apply {
- putExtra(EventDetailActivity.EXTRA_TITLE, event.title)
- putExtra(EventDetailActivity.EXTRA_SOURCE, event.source)
- putExtra(EventDetailActivity.EXTRA_URL, event.url)
- putExtra(EventDetailActivity.EXTRA_RECURRING_EVENT_ID, event.recurringEventId)
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- }
val color = sourceColor(event.source)
val timeLabel = event.start?.let {
val t = Instant.parse(it).atZone(zone)
@@ -470,7 +453,7 @@ fun TomorrowEventRow(event: WidgetItem, zone: ZoneId, textSize: WidgetTextSize)
modifier = GlanceModifier
.fillMaxWidth()
.padding(vertical = 3.dp)
- .clickable(actionStartActivity(detailIntent)),
+ .clickable(actionStartActivity(calendarViewIntent(event.url))),
verticalAlignment = Alignment.CenterVertically
) {
Text(
diff --git a/android/app/src/main/java/org/terst/doot/widget/ui/EventDetailActivity.kt b/android/app/src/main/java/org/terst/doot/widget/ui/EventDetailActivity.kt
deleted file mode 100644
index b8423ce..0000000
--- a/android/app/src/main/java/org/terst/doot/widget/ui/EventDetailActivity.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-package org.terst.doot.widget.ui
-
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.lifecycle.lifecycleScope
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.launch
-import okhttp3.OkHttpClient
-import org.terst.doot.widget.data.Keys
-import org.terst.doot.widget.data.WidgetRepository
-import org.terst.doot.widget.data.dataStore
-
-class EventDetailActivity : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- val title = intent.getStringExtra(EXTRA_TITLE) ?: ""
- val source = intent.getStringExtra(EXTRA_SOURCE) ?: "calendar"
- val url = intent.getStringExtra(EXTRA_URL) ?: ""
- val recurringEventId = intent.getStringExtra(EXTRA_RECURRING_EVENT_ID)
-
- setContent {
- MaterialTheme(colorScheme = darkColorScheme()) {
- EventDetailSheet(
- title = title,
- source = source,
- recurringEventId = recurringEventId,
- onOpenCalendar = {
- startActivity(
- Intent(Intent.ACTION_VIEW, Uri.parse(url.ifEmpty { "https://calendar.google.com" }))
- )
- finish()
- },
- onDismiss = ::finish
- )
- }
- }
- }
-
- companion object {
- const val EXTRA_TITLE = "event_title"
- const val EXTRA_SOURCE = "event_source"
- const val EXTRA_URL = "event_url"
- const val EXTRA_RECURRING_EVENT_ID = "event_recurring_id"
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun EventDetailSheet(
- title: String,
- source: String,
- recurringEventId: String?,
- onOpenCalendar: () -> Unit,
- onDismiss: () -> Unit
-) {
- val context = LocalContext.current
- var recurrenceText by remember { mutableStateOf<String?>(null) }
-
- LaunchedEffect(recurringEventId) {
- if (recurringEventId == null) return@LaunchedEffect
- recurrenceText = "Loading…"
- val prefs = context.dataStore.data.first()
- val url = prefs[Keys.SERVER_URL]?.trimEnd('/') ?: return@LaunchedEffect
- val token = prefs[Keys.TOKEN] ?: return@LaunchedEffect
- val repo = WidgetRepository(OkHttpClient(), url, token)
- repo.getRecurrence(recurringEventId).onSuccess { text ->
- recurrenceText = text
- }.onFailure {
- recurrenceText = null
- }
- }
-
- ModalBottomSheet(
- onDismissRequest = onDismiss,
- sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
- containerColor = Color(0xFF1E293B),
- tonalElevation = 0.dp,
- dragHandle = {
- Box(
- modifier = Modifier
- .padding(vertical = 12.dp)
- .width(36.dp)
- .height(4.dp)
- .background(Color.White.copy(alpha = 0.25f), RoundedCornerShape(2.dp))
- )
- }
- ) {
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(horizontal = 20.dp)
- .navigationBarsPadding()
- ) {
- Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
- Box(
- modifier = Modifier
- .size(10.dp)
- .background(sourceColor(source), RoundedCornerShape(5.dp))
- )
- Spacer(Modifier.width(10.dp))
- Text(
- text = title,
- fontSize = 17.sp,
- fontWeight = FontWeight.SemiBold,
- color = Color.White,
- modifier = Modifier.weight(1f)
- )
- }
- recurrenceText?.let { text ->
- Spacer(Modifier.height(8.dp))
- Text(
- text = text,
- fontSize = 13.sp,
- color = Color.White.copy(alpha = 0.6f)
- )
- }
- Spacer(Modifier.height(20.dp))
- OutlinedButton(
- onClick = onOpenCalendar,
- modifier = Modifier.fillMaxWidth(),
- colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.White),
- border = androidx.compose.foundation.BorderStroke(1.dp, Color.White.copy(alpha = 0.3f))
- ) {
- Text("Open in Calendar", fontSize = 15.sp)
- }
- Spacer(Modifier.height(20.dp))
- }
- }
-}