summaryrefslogtreecommitdiff
path: root/android/app
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-07-12 10:38:59 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-07-12 10:38:59 +0000
commit029902606396cf5ec583e187e7adc576c4aafb71 (patch)
treec6e9666291863d0ed7d83671167de73909e485d2 /android/app
parentdfb06e896b92003d219979cbb1476ed16b7734a3 (diff)
feat(widget): add quick-add button and entry sheet
Diffstat (limited to 'android/app')
-rw-r--r--android/app/src/main/AndroidManifest.xml6
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/data/WidgetRepository.kt26
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/ui/DootWidget.kt23
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/ui/QuickAddActivity.kt114
-rw-r--r--android/app/src/main/res/drawable/ic_add.xml13
5 files changed, 182 insertions, 0 deletions
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 3fc5603..cae6ffb 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -16,6 +16,12 @@
android:theme="@style/Theme.TaskDetail"
android:exported="false" />
+ <!-- Quick-add bottom sheet (opened by widget "+" button) -->
+ <activity
+ android:name=".ui.QuickAddActivity"
+ android:theme="@style/Theme.TaskDetail"
+ android:exported="false" />
+
<!-- Settings activity (also the widget config screen) -->
<activity
android:name=".ui.SettingsActivity"
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 a54910d..39f2a55 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.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
@@ -13,6 +14,9 @@ import okhttp3.RequestBody.Companion.toRequestBody
private val json = Json { ignoreUnknownKeys = true }
+@Serializable
+private data class WidgetAddRequest(val title: String)
+
class WidgetRepository(
private val client: OkHttpClient,
private val serverUrl: String,
@@ -74,4 +78,26 @@ class WidgetRepository(
check(response.isSuccessful) { "HTTP ${response.code}" }
}
}
+
+ /**
+ * POSTs a new task title to /api/widget/add. Uses proper JSON encoding
+ * (not manual string interpolation like reschedule/complete above) because
+ * the title is arbitrary user text that could contain characters that
+ * break hand-built JSON -- id/source above are safe because they're
+ * internal identifiers, never user-typed free text.
+ */
+ suspend fun addTask(title: String): Result<Unit> =
+ withContext(Dispatchers.IO) {
+ val body = json.encodeToString(WidgetAddRequest(title))
+ .toRequestBody("application/json".toMediaType())
+ val request = Request.Builder()
+ .url("$serverUrl/api/widget/add")
+ .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/main/java/org/terst/doot/widget/ui/DootWidget.kt b/android/app/src/main/java/org/terst/doot/widget/ui/DootWidget.kt
index a0adf85..e326537 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
@@ -121,6 +121,8 @@ fun WidgetRoot(items: List<WidgetItem>, now: Instant, isRefreshing: Boolean) {
),
modifier = GlanceModifier.defaultWeight()
)
+ QuickAddButton()
+ Spacer(modifier = GlanceModifier.width(4.dp))
RefreshButton(isRefreshing)
}
@@ -177,6 +179,27 @@ fun RefreshButton(isRefreshing: Boolean) {
}
@Composable
+fun QuickAddButton() {
+ val context = LocalContext.current
+ val intent = Intent(context, QuickAddActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ Box(
+ modifier = GlanceModifier
+ .size(24.dp)
+ .clickable(actionStartActivity(intent)),
+ contentAlignment = Alignment.Center
+ ) {
+ Image(
+ provider = ImageProvider(org.terst.doot.widget.R.drawable.ic_add),
+ contentDescription = "Add task",
+ colorFilter = ColorFilter.tint(ColorProvider(Color(0x99FFFFFF))),
+ modifier = GlanceModifier.size(14.dp)
+ )
+ }
+}
+
+@Composable
fun HourRow(
hour: Int,
nowZoned: ZonedDateTime,
diff --git a/android/app/src/main/java/org/terst/doot/widget/ui/QuickAddActivity.kt b/android/app/src/main/java/org/terst/doot/widget/ui/QuickAddActivity.kt
new file mode 100644
index 0000000..52933ea
--- /dev/null
+++ b/android/app/src/main/java/org/terst/doot/widget/ui/QuickAddActivity.kt
@@ -0,0 +1,114 @@
+package org.terst.doot.widget.ui
+
+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.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.glance.appwidget.updateAll
+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 QuickAddActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ MaterialTheme(colorScheme = darkColorScheme()) {
+ QuickAddSheet(
+ onAdd = { title ->
+ lifecycleScope.launch {
+ val prefs = this@QuickAddActivity.dataStore.data.first()
+ val url = prefs[Keys.SERVER_URL]?.trimEnd('/') ?: return@launch
+ val token = prefs[Keys.TOKEN] ?: return@launch
+ val repo = WidgetRepository(OkHttpClient(), url, token)
+ repo.addTask(title).onSuccess {
+ repo.fetchAndPersist(this@QuickAddActivity)
+ DootWidget().updateAll(this@QuickAddActivity)
+ finish()
+ }
+ }
+ },
+ onDismiss = ::finish
+ )
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun QuickAddSheet(
+ onAdd: (String) -> Unit,
+ onDismiss: () -> Unit
+) {
+ var title by remember { mutableStateOf("") }
+
+ 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()
+ ) {
+ Text(
+ text = "Quick Add",
+ fontSize = 17.sp,
+ fontWeight = FontWeight.SemiBold,
+ color = Color.White
+ )
+ Spacer(Modifier.height(16.dp))
+ OutlinedTextField(
+ value = title,
+ onValueChange = { title = it },
+ placeholder = { Text("Task title") },
+ modifier = Modifier.fillMaxWidth(),
+ singleLine = true,
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedTextColor = Color.White,
+ unfocusedTextColor = Color.White,
+ focusedBorderColor = Color(0xFF3B82F6),
+ unfocusedBorderColor = Color.White.copy(alpha = 0.3f)
+ )
+ )
+ Spacer(Modifier.height(16.dp))
+ Button(
+ onClick = { onAdd(title.trim()) },
+ enabled = title.isNotBlank(),
+ modifier = Modifier.fillMaxWidth(),
+ colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF3B82F6))
+ ) {
+ Text("Add", fontSize = 15.sp)
+ }
+ Spacer(Modifier.height(20.dp))
+ }
+ }
+}
diff --git a/android/app/src/main/res/drawable/ic_add.xml b/android/app/src/main/res/drawable/ic_add.xml
new file mode 100644
index 0000000..a96533f
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_add.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,5 L12,19 M5,12 L19,12"
+ android:strokeWidth="2"
+ android:strokeColor="#FFFFFF"
+ android:fillColor="@android:color/transparent"
+ android:strokeLineCap="round" />
+</vector>