summaryrefslogtreecommitdiff
path: root/android/app/src/main/java/org/terst
diff options
context:
space:
mode:
Diffstat (limited to 'android/app/src/main/java/org/terst')
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/ui/QuickAddActivity.kt56
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/work/AddTaskWorker.kt122
2 files changed, 142 insertions, 36 deletions
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
index 5d658e6..acd3447 100644
--- 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
@@ -17,7 +17,6 @@ import androidx.compose.ui.platform.LocalWindowInfo
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
@@ -27,6 +26,7 @@ import org.terst.doot.widget.data.Project
import org.terst.doot.widget.data.TaskRecurrence
import org.terst.doot.widget.data.WidgetRepository
import org.terst.doot.widget.data.dataStore
+import org.terst.doot.widget.work.AddTaskWorker
class QuickAddActivity : ComponentActivity() {
@@ -79,44 +79,28 @@ class QuickAddActivity : ComponentActivity() {
},
onAdd = { title, description ->
lifecycleScope.launch {
- val r = repo()
- if (r == null) {
+ // A local DataStore read, not a network call -- fast enough to
+ // check before dismissing, so a missing config doesn't silently
+ // eat what was typed.
+ val prefs = this@QuickAddActivity.dataStore.data.first()
+ val configured = !prefs[Keys.SERVER_URL].isNullOrBlank() && !prefs[Keys.TOKEN].isNullOrBlank()
+ if (!configured) {
toast("Not configured: set server URL/token in Settings")
return@launch
}
- r.addTask(title).onSuccess { id ->
- // addTask succeeding only means the task exists -- each
- // follow-up call below can independently fail (network
- // blip, expired token) without aborting the others, so
- // collect what didn't stick rather than assume success
- // silently the way the pre-fix code did for addTask itself.
- val failed = mutableListOf<String>()
- if (description.isNotBlank()) {
- r.updateTask(id, title, description).onFailure { failed += "description" }
- }
- dueDate?.let { if (r.reschedule(id, "doot", it).isFailure) failed += "due date" }
- project?.let { if (r.setTaskProject(id, it.id).isFailure) failed += "project" }
- if (labels.isNotEmpty()) {
- // Same "assign a default color the first time it's colored"
- // rule as the edit popup's label editor, so a quick-add task
- // with new labels doesn't leave them silently uncolored.
- val newlyAdded = labels.filter { it !in knownLabels }
- newlyAdded.forEach { label -> r.setLabelColor(label, colorForNewLabel(label)) }
- if (r.setTaskLabels(id, labels).isFailure) failed += "labels"
- }
- recurrence?.let {
- if (r.setTaskRecurrence(id, it.freq, it.interval, it.weekdays).isFailure) failed += "recurrence"
- }
-
- r.fetchAndPersist(this@QuickAddActivity)
- DootWidget().updateAll(this@QuickAddActivity)
- if (failed.isNotEmpty()) {
- toast("Task added, but couldn't save: ${failed.joinToString(", ")}")
- }
- finish()
- }.onFailure { e ->
- toast("Couldn't add task: ${e.message ?: "network error"}")
- }
+ // Dismiss and confirm immediately instead of blocking on addTask
+ // plus up to five sequential follow-up calls (description/due
+ // date/project/labels/recurrence) and a widget refresh -- that
+ // chain previously left the sheet sitting on screen doing
+ // nothing visible for a couple of seconds. AddTaskWorker runs
+ // the actual submission in the background, surviving this
+ // activity finishing (lifecycleScope wouldn't).
+ toast("Added \"$title\"")
+ AddTaskWorker.enqueue(
+ applicationContext, title, description, dueDate,
+ project?.id, labels, recurrence
+ )
+ finish()
}
},
onDismiss = ::finish
diff --git a/android/app/src/main/java/org/terst/doot/widget/work/AddTaskWorker.kt b/android/app/src/main/java/org/terst/doot/widget/work/AddTaskWorker.kt
new file mode 100644
index 0000000..c038629
--- /dev/null
+++ b/android/app/src/main/java/org/terst/doot/widget/work/AddTaskWorker.kt
@@ -0,0 +1,122 @@
+package org.terst.doot.widget.work
+
+import android.content.Context
+import android.widget.Toast
+import androidx.glance.appwidget.updateAll
+import androidx.work.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.withContext
+import org.terst.doot.widget.data.Keys
+import org.terst.doot.widget.data.TaskRecurrence
+import org.terst.doot.widget.data.WidgetRepository
+import org.terst.doot.widget.data.dataStore
+import org.terst.doot.widget.ui.DootWidget
+import org.terst.doot.widget.ui.colorForNewLabel
+
+/**
+ * Runs Quick Add's actual submission -- addTask plus up to five follow-up
+ * calls (description/due date/project/labels/recurrence), a data refresh,
+ * and a widget update -- as background work that survives QuickAddActivity
+ * finishing immediately on tap, instead of the activity's own lifecycleScope
+ * (which the previous in-place implementation used, and which gets cancelled
+ * the moment the activity is destroyed). See QuickAddActivity.onAdd: the
+ * sheet now dismisses and confirms right away rather than sitting on screen
+ * through this whole sequential chain.
+ */
+class AddTaskWorker(context: Context, params: WorkerParameters) :
+ CoroutineWorker(context, params) {
+
+ override suspend fun doWork(): Result {
+ val title = inputData.getString(KEY_TITLE) ?: return Result.failure()
+ val description = inputData.getString(KEY_DESCRIPTION).orEmpty()
+ val dueDate = inputData.getString(KEY_DUE_DATE)
+ val projectId = inputData.getString(KEY_PROJECT_ID)
+ val labels = inputData.getStringArray(KEY_LABELS)?.toList().orEmpty()
+ val recFreq = inputData.getString(KEY_REC_FREQ)
+ val recInterval = inputData.getInt(KEY_REC_INTERVAL, 1)
+ val recWeekdays = inputData.getIntArray(KEY_REC_WEEKDAYS)?.toList().orEmpty()
+
+ val prefs = applicationContext.dataStore.data.first()
+ val url = prefs[Keys.SERVER_URL]?.takeIf { it.isNotBlank() } ?: return Result.failure()
+ val token = prefs[Keys.TOKEN]?.takeIf { it.isNotBlank() } ?: return Result.failure()
+ val repo = WidgetRepository(okhttp3.OkHttpClient(), url, token)
+
+ val id = repo.addTask(title).getOrElse { e ->
+ toast("Couldn't add task: ${e.message ?: "network error"}")
+ return Result.failure()
+ }
+
+ // Each follow-up can independently fail (network blip, expired token)
+ // without aborting the others -- collect what didn't stick rather
+ // than assume success silently.
+ val failed = mutableListOf<String>()
+ if (description.isNotBlank()) {
+ repo.updateTask(id, title, description).onFailure { failed += "description" }
+ }
+ dueDate?.let { if (repo.reschedule(id, "doot", it).isFailure) failed += "due date" }
+ projectId?.let { if (repo.setTaskProject(id, it).isFailure) failed += "project" }
+ if (labels.isNotEmpty()) {
+ // Same "assign a default color the first time it's colored" rule
+ // as the edit popup's label editor, so a quick-add task with new
+ // labels doesn't leave them silently uncolored. Fetches current
+ // known labels fresh here rather than trusting a snapshot passed
+ // in from the activity, since this may run well after the sheet
+ // closed.
+ val known = repo.fetchLabelColors().getOrNull()?.map { it.name }.orEmpty()
+ labels.filter { it !in known }.forEach { repo.setLabelColor(it, colorForNewLabel(it)) }
+ if (repo.setTaskLabels(id, labels).isFailure) failed += "labels"
+ }
+ if (!recFreq.isNullOrEmpty()) {
+ if (repo.setTaskRecurrence(id, recFreq, recInterval, recWeekdays).isFailure) failed += "recurrence"
+ }
+
+ repo.fetchAndPersist(applicationContext)
+ DootWidget().updateAll(applicationContext)
+
+ if (failed.isNotEmpty()) {
+ toast("Task added, but couldn't save: ${failed.joinToString(", ")}")
+ }
+ return Result.success()
+ }
+
+ private suspend fun toast(message: String) = withContext(Dispatchers.Main) {
+ Toast.makeText(applicationContext, message, Toast.LENGTH_LONG).show()
+ }
+
+ companion object {
+ private const val KEY_TITLE = "title"
+ private const val KEY_DESCRIPTION = "description"
+ private const val KEY_DUE_DATE = "due_date"
+ private const val KEY_PROJECT_ID = "project_id"
+ private const val KEY_LABELS = "labels"
+ private const val KEY_REC_FREQ = "rec_freq"
+ private const val KEY_REC_INTERVAL = "rec_interval"
+ private const val KEY_REC_WEEKDAYS = "rec_weekdays"
+
+ fun enqueue(
+ context: Context,
+ title: String,
+ description: String,
+ dueDate: String?,
+ projectId: String?,
+ labels: List<String>,
+ recurrence: TaskRecurrence?
+ ) {
+ val data = workDataOf(
+ KEY_TITLE to title,
+ KEY_DESCRIPTION to description,
+ KEY_DUE_DATE to dueDate,
+ KEY_PROJECT_ID to projectId,
+ KEY_LABELS to labels.toTypedArray(),
+ KEY_REC_FREQ to recurrence?.freq,
+ KEY_REC_INTERVAL to (recurrence?.interval ?: 1),
+ KEY_REC_WEEKDAYS to (recurrence?.weekdays?.toIntArray() ?: IntArray(0))
+ )
+ val request = OneTimeWorkRequestBuilder<AddTaskWorker>()
+ .setInputData(data)
+ .build()
+ WorkManager.getInstance(context).enqueue(request)
+ }
+ }
+}