diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-07-15 18:36:00 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-07-16 02:54:17 +0000 |
| commit | 0aa5bd0de79df82958a7314a7db6e91e892d28b7 (patch) | |
| tree | e3829836614846d2faa436cdbfd6802e9676472b /android/app/src/main/java/org/terst | |
| parent | c1ba9994367dd077ba2422d31a658a0c62e2520b (diff) | |
feat(widget): add Android data layer for projects and label colors
Diffstat (limited to 'android/app/src/main/java/org/terst')
4 files changed, 131 insertions, 2 deletions
diff --git a/android/app/src/main/java/org/terst/doot/widget/data/Project.kt b/android/app/src/main/java/org/terst/doot/widget/data/Project.kt new file mode 100644 index 0000000..43ac63b --- /dev/null +++ b/android/app/src/main/java/org/terst/doot/widget/data/Project.kt @@ -0,0 +1,17 @@ +package org.terst.doot.widget.data + +import kotlinx.serialization.Serializable + +@Serializable +data class Project( + val id: String, + val name: String, + val color: String, + @kotlinx.serialization.SerialName("created_at") val createdAt: String +) + +@Serializable +data class LabelColor( + val name: String, + val color: String +) 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 index 3e1d9f8..515678e 100644 --- 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 @@ -11,7 +11,9 @@ data class TaskDetailResponse( @SerialName("due_date") val dueDate: String? = null, val completed: Boolean = false, val recurrence: TaskRecurrence? = null, - @SerialName("next_date") val nextDate: String? = null + @SerialName("next_date") val nextDate: String? = null, + val project: Project? = null, + val labels: List<String> = emptyList() ) @Serializable 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 fee15e1..92598ba 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 @@ -16,7 +16,8 @@ data class WidgetItem( @SerialName("due_date") val dueDate: String? = null, val url: String = "", val completable: Boolean = false, - @SerialName("recurring_event_id") val recurringEventId: String? = null + @SerialName("recurring_event_id") val recurringEventId: String? = null, + @SerialName("project_color") val projectColor: 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 8b27dac..16c76f2 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 @@ -4,6 +4,7 @@ import android.content.Context import androidx.datastore.preferences.core.edit import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -218,4 +219,112 @@ class WidgetRepository( check(response.isSuccessful) { "HTTP ${response.code}" } } } + + /** GETs the non-archived project list from /api/widget/projects. */ + suspend fun fetchProjects(): Result<List<Project>> = + withContext(Dispatchers.IO) { + val request = Request.Builder() + .url("$serverUrl/api/widget/projects") + .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<List<Project>>(body) + } + } + + @Serializable + private data class ProjectCreateRequest(val name: String, val color: String) + + /** POSTs a new project to /api/widget/projects. */ + suspend fun createProject(name: String, color: String): Result<Project> = + withContext(Dispatchers.IO) { + val body = json.encodeToString(ProjectCreateRequest(name, color)) + .toRequestBody("application/json".toMediaType()) + val request = Request.Builder() + .url("$serverUrl/api/widget/projects") + .header("Authorization", "Bearer $token") + .post(body) + .build() + runCatching { + val response = client.newCall(request).execute() + check(response.isSuccessful) { "HTTP ${response.code}" } + val responseBody = checkNotNull(response.body?.string()) { "Empty body" } + json.decodeFromString<Project>(responseBody) + } + } + + @Serializable + private data class TaskProjectRequest(val id: String, @SerialName("project_id") val projectId: String) + + /** POSTs a project assignment to /api/widget/task/project. projectId="" clears it. */ + suspend fun setTaskProject(id: String, projectId: String): Result<Unit> = + withContext(Dispatchers.IO) { + val body = json.encodeToString(TaskProjectRequest(id, projectId)) + .toRequestBody("application/json".toMediaType()) + val request = Request.Builder() + .url("$serverUrl/api/widget/task/project") + .header("Authorization", "Bearer $token") + .post(body) + .build() + runCatching { + val response = client.newCall(request).execute() + check(response.isSuccessful) { "HTTP ${response.code}" } + } + } + + @Serializable + private data class TaskLabelsRequest(val id: String, val labels: List<String>) + + /** POSTs a full label-set replacement to /api/widget/task/labels. */ + suspend fun setTaskLabels(id: String, labels: List<String>): Result<Unit> = + withContext(Dispatchers.IO) { + val body = json.encodeToString(TaskLabelsRequest(id, labels)) + .toRequestBody("application/json".toMediaType()) + val request = Request.Builder() + .url("$serverUrl/api/widget/task/labels") + .header("Authorization", "Bearer $token") + .post(body) + .build() + runCatching { + val response = client.newCall(request).execute() + check(response.isSuccessful) { "HTTP ${response.code}" } + } + } + + /** GETs every colored label from /api/widget/labels. */ + suspend fun fetchLabelColors(): Result<List<LabelColor>> = + withContext(Dispatchers.IO) { + val request = Request.Builder() + .url("$serverUrl/api/widget/labels") + .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<List<LabelColor>>(body) + } + } + + @Serializable + private data class LabelColorSetRequest(val name: String, val color: String) + + /** POSTs a label color assignment to /api/widget/labels/color. */ + suspend fun setLabelColor(name: String, color: String): Result<Unit> = + withContext(Dispatchers.IO) { + val body = json.encodeToString(LabelColorSetRequest(name, color)) + .toRequestBody("application/json".toMediaType()) + val request = Request.Builder() + .url("$serverUrl/api/widget/labels/color") + .header("Authorization", "Bearer $token") + .post(body) + .build() + runCatching { + val response = client.newCall(request).execute() + check(response.isSuccessful) { "HTTP ${response.code}" } + } + } } |
