summaryrefslogtreecommitdiff
path: root/android/app/src/main/java/org/terst/doot/widget
diff options
context:
space:
mode:
Diffstat (limited to 'android/app/src/main/java/org/terst/doot/widget')
-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))
- }
- }
-}