summaryrefslogtreecommitdiff
path: root/android/app/src
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-08-06 16:35:50 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-08-06 16:35:50 +0000
commitb56da63fe7776b14bc2cff4fe3c63ae2dd65ac73 (patch)
treeaa00f4c78126c1cc290702b9e95b54cfcf985ca5 /android/app/src
parent702e0ea04984f203048d9a440bc6e1c8ba8b6d4e (diff)
Widget: fix QuickAdd keyboard-focus race, force widget re-render on save
QuickAddActivity's autofocus used delay(150) then requestFocus() + keyboard.show() -- a fixed delay racing against the window actually gaining focus. This is a translucent, separate-taskAffinity popup activity, so window focus transfer is variable/device-dependent; a show() call made before the window is focused is silently dropped by the IME service, no error, no retry -- the classic cause of "keyboard doesn't appear until I tap out and back in" (reported 2026-08-06, "more consistent here" than the same general Android quirk elsewhere). Fixed to react to LocalWindowInfo.isWindowFocused instead of guessing a timing window, plus windowSoftInputMode="adjustResize|stateVisible" as an OS-level second line of defense. Tried to verify live and hit a real limitation worth recording: this box's AVDs run -no-window (headless), and in that mode mInputShown never reports true via dumpsys input_method even for a manual, deliberate tap on the field -- confirmed by testing a plain tap directly, independent of any app code. The harness can't observe IME visibility here, so this fix is verified by code-level reasoning (LocalWindowInfo-driven focus is the standard, documented fix for this exact bug class) and confirmed window-focus DOES transfers correctly (mServedView moves to the bottom sheet's window), not by watching the keyboard actually appear. Real confirmation has to happen on-device. Also: SettingsActivity.saveAndFinish() only ever called DootWidget().updateAll() indirectly, as a side effect of RefreshWorker succeeding its network fetch -- so a slow or failing request could delay or block the widget from reflecting a setting the user just saved, even though every setting saved there (theme, text size, background, checkboxes) is already fully local and needs no network round trip to take effect. Now calls updateAll() directly and immediately after writing prefs; RefreshWorker still runs afterward to separately pull fresh server data. Verified installable and crash-free on a real API 36 emulator. Deployed as doot-widget.apk.
Diffstat (limited to 'android/app/src')
-rw-r--r--android/app/src/main/AndroidManifest.xml2
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/ui/QuickAddActivity.kt22
-rw-r--r--android/app/src/main/java/org/terst/doot/widget/ui/SettingsActivity.kt14
3 files changed, 32 insertions, 6 deletions
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index c2200ba..148d841 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -31,7 +31,7 @@
android:theme="@style/Theme.TaskDetail"
android:taskAffinity="${applicationId}.quickadd"
android:exported="false"
- android:windowSoftInputMode="adjustResize" />
+ android:windowSoftInputMode="adjustResize|stateVisible" />
<!-- Full dashboard, wrapped in a WebView (see DashboardActivity's doc comment) -->
<activity
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 980f286..5d658e6 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
@@ -13,12 +13,12 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+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.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
@@ -153,10 +153,22 @@ fun QuickAddSheet(
val focusRequester = remember { FocusRequester() }
val keyboard = LocalSoftwareKeyboardController.current
- LaunchedEffect(Unit) {
- delay(150)
- focusRequester.requestFocus()
- keyboard?.show()
+ // A fixed delay() here (the original approach) races against the window
+ // actually gaining focus -- this is a translucent, separately-task-affinity'd
+ // popup activity, so its window can take a variable, device-dependent amount
+ // of time to become focused after launch. Requesting focus / calling show()
+ // before that happens is the classic cause of "keyboard doesn't appear until
+ // I tap out and back in": the IME service silently drops a show() request
+ // made against a window that isn't focused yet, no error, no retry. Reacting
+ // to the real isWindowFocused signal instead of guessing a timing window
+ // fixes the race at its source (reported 2026-08-06 as "more consistent
+ // here" than the same general Android quirk in other apps).
+ val windowInfo = LocalWindowInfo.current
+ LaunchedEffect(windowInfo.isWindowFocused) {
+ if (windowInfo.isWindowFocused) {
+ focusRequester.requestFocus()
+ keyboard?.show()
+ }
}
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = System.currentTimeMillis())
diff --git a/android/app/src/main/java/org/terst/doot/widget/ui/SettingsActivity.kt b/android/app/src/main/java/org/terst/doot/widget/ui/SettingsActivity.kt
index 44a7a20..9b77621 100644
--- a/android/app/src/main/java/org/terst/doot/widget/ui/SettingsActivity.kt
+++ b/android/app/src/main/java/org/terst/doot/widget/ui/SettingsActivity.kt
@@ -25,6 +25,7 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.datastore.preferences.core.edit
+import androidx.glance.appwidget.updateAll
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
@@ -109,6 +110,19 @@ class SettingsActivity : ComponentActivity() {
prefs[Keys.BACKGROUND_ALPHA] = backgroundAlpha
prefs[Keys.HIDE_CHECKBOXES] = hideCheckboxes
}
+
+ // Re-render immediately and unconditionally, not just as a side effect of
+ // RefreshWorker succeeding. Every rendering-affecting setting saved above
+ // (theme, text size, background, checkboxes) is already fully local --
+ // none of it needs a network round trip to take effect. Before this,
+ // updateAll() only ever got called from inside RefreshWorker.doWork(),
+ // AFTER fetchAndPersist() succeeded -- so a slow or failing network
+ // request could delay or entirely block the widget from reflecting a
+ // setting the user just saved (reported 2026-08-06). RefreshWorker below
+ // still runs too, to also pull fresh server data, but that's a separate
+ // concern from making local preference changes visible.
+ DootWidget().updateAll(this)
+
RefreshWorker.schedule(this)
RefreshWorker.runOnce(this)