summaryrefslogtreecommitdiff
path: root/android/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'android/app/src/main/java')
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/data/WidgetItem.kt3
-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.kt31
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/ui/EventDetailActivity.kt145
4 files changed, 194 insertions, 5 deletions
diff --git a/android/app/src/main/java/org/terst/doot/widget/data/WidgetItem.kt b/android/app/src/main/java/org/terst/doot/widget/data/WidgetItem.kt
index ff2ff28..50cfddc 100644
--- a/android/app/src/main/java/org/terst/doot/widget/data/WidgetItem.kt
+++ b/android/app/src/main/java/org/terst/doot/widget/data/WidgetItem.kt
@@ -15,7 +15,8 @@ data class WidgetItem(
@SerialName("is_overdue") val isOverdue: Boolean = false,
@SerialName("due_date") val dueDate: String? = null,
val url: String = "",
- val completable: Boolean = false
+ val completable: Boolean = false,
+ @SerialName("recurring_event_id") val recurringEventId: String? = null
)
@Serializable
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 39f2a55..b604d39 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
@@ -17,6 +17,9 @@ private val json = Json { ignoreUnknownKeys = true }
@Serializable
private data class WidgetAddRequest(val title: String)
+@Serializable
+private data class RecurrenceResponse(val recurrence: String)
+
class WidgetRepository(
private val client: OkHttpClient,
private val serverUrl: String,
@@ -100,4 +103,21 @@ 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 e326537..2dbc96b 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,7 +2,6 @@ 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
@@ -140,12 +139,20 @@ fun WidgetRoot(items: List<WidgetItem>, now: Instant, isRefreshing: Boolean) {
@Composable
fun AllDayRow(event: WidgetItem) {
+ 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(Intent(Intent.ACTION_VIEW, Uri.parse(event.url.ifEmpty { "https://calendar.google.com" })))),
+ .clickable(actionStartActivity(detailIntent)),
verticalAlignment = Alignment.CenterVertically
) {
Box(modifier = GlanceModifier.width(3.dp).height(16.dp).background(color)) {}
@@ -260,13 +267,21 @@ fun HourRow(
@Composable
fun EventBlock(event: WidgetItem, isPast: Boolean) {
+ 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(Intent(Intent.ACTION_VIEW, Uri.parse(event.url.ifEmpty { "https://calendar.google.com" })))),
+ .clickable(actionStartActivity(detailIntent)),
verticalAlignment = Alignment.CenterVertically
) {
Box(
@@ -412,6 +427,14 @@ fun TomorrowSection(items: List<WidgetItem>, fragments: List<TaskFragment>, allD
@Composable
fun TomorrowEventRow(event: WidgetItem, zone: ZoneId) {
+ 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)
@@ -421,7 +444,7 @@ fun TomorrowEventRow(event: WidgetItem, zone: ZoneId) {
modifier = GlanceModifier
.fillMaxWidth()
.padding(vertical = 3.dp)
- .clickable(actionStartActivity(Intent(Intent.ACTION_VIEW, android.net.Uri.parse(event.url.ifEmpty { "https://calendar.google.com" })))),
+ .clickable(actionStartActivity(detailIntent)),
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
new file mode 100644
index 0000000..b8423ce
--- /dev/null
+++ b/android/app/src/main/java/org/terst/doot/widget/ui/EventDetailActivity.kt
@@ -0,0 +1,145 @@
+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))
+ }
+ }
+}