summaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/data/TaskDetail.kt22
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/data/WidgetRepository.kt73
-rw-r--r--android/app/src/test/java/org/terst/doot/widget/WidgetRepositoryTest.kt43
3 files changed, 138 insertions, 0 deletions
diff --git a/android/app/src/main/java/org/terst/doot/widget/data/TaskDetail.kt b/android/app/src/main/java/org/terst/doot/widget/data/TaskDetail.kt
new file mode 100644
index 0000000..3e1d9f8
--- /dev/null
+++ b/android/app/src/main/java/org/terst/doot/widget/data/TaskDetail.kt
@@ -0,0 +1,22 @@
+package org.terst.doot.widget.data
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class TaskDetailResponse(
+ val id: String,
+ val title: String,
+ val description: String,
+ @SerialName("due_date") val dueDate: String? = null,
+ val completed: Boolean = false,
+ val recurrence: TaskRecurrence? = null,
+ @SerialName("next_date") val nextDate: String? = null
+)
+
+@Serializable
+data class TaskRecurrence(
+ val freq: String,
+ val interval: Int,
+ val weekdays: List<Int> = emptyList()
+)
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 134e3bc..8b27dac 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
@@ -145,4 +145,77 @@ class WidgetRepository(
check(response.isSuccessful) { "HTTP ${response.code}" }
}
}
+
+ /** GETs full detail for a single doot-native task from /api/widget/task. */
+ suspend fun fetchTaskDetail(id: String): Result<TaskDetailResponse> =
+ withContext(Dispatchers.IO) {
+ val encodedId = java.net.URLEncoder.encode(id, "UTF-8")
+ val request = Request.Builder()
+ .url("$serverUrl/api/widget/task?id=$encodedId&source=doot")
+ .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" }
+ json.decodeFromString<TaskDetailResponse>(body)
+ }
+ }
+
+ @Serializable
+ private data class TaskUpdateRequest(val id: String, val title: String, val description: String)
+
+ /** POSTs a title/description update to /api/widget/task/update. */
+ suspend fun updateTask(id: String, title: String, description: String): Result<Unit> =
+ withContext(Dispatchers.IO) {
+ val body = json.encodeToString(TaskUpdateRequest(id, title, description))
+ .toRequestBody("application/json".toMediaType())
+ val request = Request.Builder()
+ .url("$serverUrl/api/widget/task/update")
+ .header("Authorization", "Bearer $token")
+ .post(body)
+ .build()
+ runCatching {
+ val response = client.newCall(request).execute()
+ check(response.isSuccessful) { "HTTP ${response.code}" }
+ }
+ }
+
+ @Serializable
+ private data class TaskRecurrenceRequest(val id: String, val freq: String, val interval: Int, val weekdays: List<Int>)
+
+ /** POSTs a recurrence pattern change to /api/widget/task/recurrence. freq="" clears it. */
+ suspend fun setTaskRecurrence(id: String, freq: String, interval: Int, weekdays: List<Int>): Result<Unit> =
+ withContext(Dispatchers.IO) {
+ val body = json.encodeToString(TaskRecurrenceRequest(id, freq, interval, weekdays))
+ .toRequestBody("application/json".toMediaType())
+ val request = Request.Builder()
+ .url("$serverUrl/api/widget/task/recurrence")
+ .header("Authorization", "Bearer $token")
+ .post(body)
+ .build()
+ runCatching {
+ val response = client.newCall(request).execute()
+ check(response.isSuccessful) { "HTTP ${response.code}" }
+ }
+ }
+
+ @Serializable
+ private data class TaskNextDateRequest(val id: String, val date: String)
+
+ /** POSTs a one-shot next-occurrence override to /api/widget/task/next-date. */
+ suspend fun setTaskNextDate(id: String, dateISO: String): Result<Unit> =
+ withContext(Dispatchers.IO) {
+ val body = json.encodeToString(TaskNextDateRequest(id, dateISO))
+ .toRequestBody("application/json".toMediaType())
+ val request = Request.Builder()
+ .url("$serverUrl/api/widget/task/next-date")
+ .header("Authorization", "Bearer $token")
+ .post(body)
+ .build()
+ runCatching {
+ val response = client.newCall(request).execute()
+ check(response.isSuccessful) { "HTTP ${response.code}" }
+ }
+ }
}
diff --git a/android/app/src/test/java/org/terst/doot/widget/WidgetRepositoryTest.kt b/android/app/src/test/java/org/terst/doot/widget/WidgetRepositoryTest.kt
index 09fd467..31e6642 100644
--- a/android/app/src/test/java/org/terst/doot/widget/WidgetRepositoryTest.kt
+++ b/android/app/src/test/java/org/terst/doot/widget/WidgetRepositoryTest.kt
@@ -62,4 +62,47 @@ class WidgetRepositoryTest {
assertEquals("Bearer mysecret", capturedRequest.captured.header("Authorization"))
}
+
+ @Test
+ fun `fetchTaskDetail returns success with valid JSON`() = runTest {
+ val json = """{"id":"rec-1","title":"Water plants","description":"Use the blue can","due_date":"2026-07-13T00:00:00-10:00","completed":false,"recurrence":{"freq":"weekly","interval":1,"weekdays":[1,3,5]},"next_date":"2026-07-15T00:00:00-10:00"}"""
+ every { mockClient.newCall(any()) } returns mockCall
+ every { mockCall.execute() } returns responseOf(200, json)
+
+ val repo = WidgetRepository(mockClient, "https://doot.example.com", "token")
+ val result = repo.fetchTaskDetail("rec-1")
+
+ assertTrue(result.isSuccess)
+ val detail = result.getOrThrow()
+ assertEquals("Water plants", detail.title)
+ assertEquals("weekly", detail.recurrence?.freq)
+ assertEquals(listOf(1, 3, 5), detail.recurrence?.weekdays)
+ assertEquals("2026-07-15T00:00:00-10:00", detail.nextDate)
+ }
+
+ @Test
+ fun `fetchTaskDetail returns failure on non-200`() = runTest {
+ every { mockClient.newCall(any()) } returns mockCall
+ every { mockCall.execute() } returns responseOf(404, "not found")
+
+ val repo = WidgetRepository(mockClient, "https://doot.example.com", "token")
+ val result = repo.fetchTaskDetail("does-not-exist")
+
+ assertTrue(result.isFailure)
+ }
+
+ @Test
+ fun `fetchTaskDetail requests the given id with source=doot`() = runTest {
+ val json = """{"id":"rec-1","title":"Water plants","description":"","completed":false}"""
+ val capturedRequest = slot<Request>()
+ every { mockClient.newCall(capture(capturedRequest)) } returns mockCall
+ every { mockCall.execute() } returns responseOf(200, json)
+
+ val repo = WidgetRepository(mockClient, "https://doot.example.com", "token")
+ repo.fetchTaskDetail("rec-1")
+
+ val url = capturedRequest.captured.url.toString()
+ assertTrue("expected id=rec-1 in URL, got $url", url.contains("id=rec-1"))
+ assertTrue("expected source=doot in URL, got $url", url.contains("source=doot"))
+ }
}