diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-08-06 16:35:50 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-08-06 16:35:50 +0000 |
| commit | b56da63fe7776b14bc2cff4fe3c63ae2dd65ac73 (patch) | |
| tree | aa00f4c78126c1cc290702b9e95b54cfcf985ca5 /android/app/src/main/java | |
| parent | 702e0ea04984f203048d9a440bc6e1c8ba8b6d4e (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/main/java')
| -rw-r--r-- | android/app/src/main/java/org/terst/doot/widget/ui/QuickAddActivity.kt | 22 | ||||
| -rw-r--r-- | android/app/src/main/java/org/terst/doot/widget/ui/SettingsActivity.kt | 14 |
2 files changed, 31 insertions, 5 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 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) |
