From 9d805aaadd09d7b2377a5f12f95dbd5f9aafdf41 Mon Sep 17 00:00:00 2001 From: Asaf Mahlev Date: Thu, 11 Jun 2026 02:24:51 +0300 Subject: [PATCH 1/2] fix(two-thumb): grace-commit auto-caps refresh + opt-in 'auto-finish only after swipes' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two pre-existing bugs in the combining-grace auto-commit path, found on-device (neither caused by #23 — that only defers the space behind a default-off flag). 1. Erratic capitalization after a grace auto-commit. onCombiningGraceExpired runs on the async grace timer, OFF the normal onCodeInput path that refreshes the shift state after a commit — so the next word's auto-caps was stale (dropped, or applied mid-word -> 'WORds'). Fix: call requestUpdatingShiftState(getCurrentAutoCapsState, getCurrentRecapitalizeState) at the end of onCombiningGraceExpired, mirroring the gesture-commit path. Unconditional — it was a bug for every grace commit. 2. No way to stop the grace timer auto-finishing tap-only words. enterCombiningMode armed for any composing word regardless of tap vs swipe (the fromTap param was unused); mCombiningAutospaceOnlyAfterGesture only gates the space, not the commit. Add opt-in PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE (default off): when on, enterCombiningMode skips arming for a tap-only word (no gesture fragment). Tap-then-swipe still arms (the gesture trigger re-enters with the fragment). Verify: InputLogicTest + SettingsContainerTest -> 116 completed, 3 failed (pre-existing baseline), 1 skipped; 0 new failures; new pref registered. Feel (correct caps + tap-skip) validated on-device. --- .../keyboard/latin/inputlogic/InputLogic.java | 14 ++++++++++++++ .../helium314/keyboard/latin/settings/Defaults.kt | 1 + .../keyboard/latin/settings/Settings.java | 3 +++ .../keyboard/latin/settings/SettingsValues.java | 4 ++++ .../settings/screens/TwoThumbTypingScreen.kt | 6 ++++++ app/src/main/res/values/strings.xml | 4 ++++ .../keyboard/settings/SettingsContainerTest.kt | 6 ++++++ 7 files changed, 38 insertions(+) diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 6c221323e..14851f166 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -985,6 +985,14 @@ private void enterCombiningMode(final SettingsValues settingsValues, final boole // separators / cursor-front recompositions there's nothing to auto-commit, and arming // the timer would draw a spurious progress bar. if (!mWordComposer.isComposingWord()) return; + // #14: "auto-finish only after swipes" — don't arm the auto-commit timer for a tap-only + // word (no gesture fragment yet). Pure tap-typing shouldn't get auto-finished. A + // tap-then-swipe word still arms: the gesture trigger re-enters here with fromTap=false + // once the fragment is present. + if (fromTap && settingsValues.mCombiningGraceOnlyAfterGesture + && !mCombiningWordHasGestureFragment && !mWordComposer.isBatchMode()) { + return; + } final int graceMs = baseGraceMs + Math.max(0, settingsValues.mCombiningTapExtraMs); cancelCombiningTimerOnly(); mInCombiningMode = true; @@ -1309,6 +1317,12 @@ private void onCombiningGraceExpired() { mBackspaceUnits.setCommitted(writtenChars, committedFragments); } // "keep_alternatives" — fall through, do nothing. + // #14 bug fix: this commit ran on the async grace timer, OFF the normal onCodeInput path + // that refreshes the shift state after a commit. Without this, the next word's auto-caps + // is stale — auto-caps gets dropped after a grace auto-commit and capitalization comes out + // erratic. Mirror the gesture-commit path's requestUpdatingShiftState. + KeyboardSwitcher.getInstance().requestUpdatingShiftState( + getCurrentAutoCapsState(sv), getCurrentRecapitalizeState()); } /** diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt index 0dd8647b9..e2cf0191a 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt @@ -150,6 +150,7 @@ object Defaults { const val PREF_COMBINING_TAP_EXTRA_MS = 250 const val PREF_COMBINING_AUTOSPACE_ONLY_AFTER_GESTURE = false const val PREF_SPACING_DEFER_GRACE_SPACE = false + const val PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE = false const val PREF_COMBINING_AUTOSPACE_SUGGESTIONS = "alternatives_then_next_word" const val PREF_COMBINING_BACKSPACE_DELETES_GESTURE_WORD = true const val PREF_COMBINING_BACKSPACE_DELETES_COMPOSING_TEXT = true diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index 75edd6b6c..cf89a5379 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -160,6 +160,9 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang // #23: route the two-thumb grace-mode auto-commit space through the deferred PHANTOM // mechanism (like the default gesture path) instead of writing it eagerly. Experimental. public static final String PREF_SPACING_DEFER_GRACE_SPACE = "spacing_defer_grace_space"; + // #14: when on, the combining grace timer only auto-commits words that include a swipe — + // pure tap-typed words are never auto-finished by the timer. Experimental, default off. + public static final String PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE = "combining_grace_only_after_gesture"; // What the suggestion strip shows after the combining grace timer auto-commits a word. // Values: "keep_alternatives" (1) | "next_word" (2, default) | "alternatives_then_next_word" (3). public static final String PREF_COMBINING_AUTOSPACE_SUGGESTIONS = "combining_autospace_suggestions"; diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java index ae1e4e17f..95efd7756 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -138,6 +138,7 @@ public class SettingsValues { public final int mCombiningTapExtraMs; public final boolean mCombiningAutospaceOnlyAfterGesture; public final boolean mSpacingDeferGraceSpace; + public final boolean mCombiningGraceOnlyAfterGesture; // Raw string value: "keep_alternatives" | "next_word" | "alternatives_then_next_word" public final String mCombiningAutospaceSuggestions; public final boolean mCombiningBackspaceDeletesGestureWord; @@ -386,6 +387,9 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina mSpacingDeferGraceSpace = prefs.getBoolean( Settings.PREF_SPACING_DEFER_GRACE_SPACE, Defaults.PREF_SPACING_DEFER_GRACE_SPACE); + mCombiningGraceOnlyAfterGesture = prefs.getBoolean( + Settings.PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE, + Defaults.PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE); mCombiningAutospaceSuggestions = prefs.getString(Settings.PREF_COMBINING_AUTOSPACE_SUGGESTIONS, Defaults.PREF_COMBINING_AUTOSPACE_SUGGESTIONS); final boolean nonNormalTwoThumbSpacing = mGestureManualSpacing || mCombiningGraceMs > 0; diff --git a/app/src/main/java/helium314/keyboard/settings/screens/TwoThumbTypingScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/TwoThumbTypingScreen.kt index 9469bddc4..e73389788 100644 --- a/app/src/main/java/helium314/keyboard/settings/screens/TwoThumbTypingScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/screens/TwoThumbTypingScreen.kt @@ -70,6 +70,7 @@ fun TwoThumbTypingScreen( add(Settings.PREF_COMBINING_AUTOCORRECT_ON_AUTOSPACE) add(Settings.PREF_COMBINING_AUTOSPACE_SUGGESTIONS) add(Settings.PREF_SPACING_DEFER_GRACE_SPACE) + add(Settings.PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE) } if (nonNormalSpacing) { add(Settings.PREF_MULTIPART_FULL_WORD_SUGGESTIONS) @@ -152,6 +153,11 @@ fun createTwoThumbTypingSettings(context: Context) = listOf( R.string.spacing_defer_grace_space_summary) { SwitchPreference(it, Defaults.PREF_SPACING_DEFER_GRACE_SPACE) }, + Setting(context, Settings.PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE, + R.string.combining_grace_only_after_gesture, + R.string.combining_grace_only_after_gesture_summary) { + SwitchPreference(it, Defaults.PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE) + }, Setting(context, Settings.PREF_COMBINING_AUTOSPACE_SUGGESTIONS, R.string.combining_autospace_suggestions, R.string.combining_autospace_suggestions_summary) { def -> val items = listOf( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9948cff76..705ad3ed8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -299,6 +299,10 @@ Defer grace space (experimental) Route the two-thumb grace auto-commit space through the deferred mechanism (like the default swipe path) instead of writing it immediately. The space appears on your next input and stays backspace-reversible. + + Auto-finish only after swipes + + When enabled, the grace timer only auto-commits words that include a swipe. Words you tap out letter-by-letter are never auto-finished by the timer \u2014 tap space yourself when you\'re done. Backspace deletes last swipe diff --git a/app/src/test/java/helium314/keyboard/settings/SettingsContainerTest.kt b/app/src/test/java/helium314/keyboard/settings/SettingsContainerTest.kt index 24fb0054b..30451afb7 100644 --- a/app/src/test/java/helium314/keyboard/settings/SettingsContainerTest.kt +++ b/app/src/test/java/helium314/keyboard/settings/SettingsContainerTest.kt @@ -66,6 +66,12 @@ class SettingsContainerTest { container[Settings.PREF_SPACING_DEFER_GRACE_SPACE]?.key) } + @Test + fun combiningGraceOnlyAfterGestureSettingIsRegistered() { + assertEquals(Settings.PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE, + container[Settings.PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE]?.key) + } + @Test fun twoThumbLowLevelBackspaceSettingIsHiddenFromSearchRegistry() { assertNull(container[Settings.PREF_COMBINING_BACKSPACE_DELETES_GESTURE_WORD]) From f340ad767551417528d86a596365c2d76790430b Mon Sep 17 00:00:00 2001 From: Asaf Mahlev Date: Thu, 11 Jun 2026 08:13:10 +0300 Subject: [PATCH 2/2] feat(two-thumb): default 'only auto-finish swiped words' ON + clearer labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tapped words auto-finishing was the root of a whole cluster of confusing behavior (premature commit, Text Expander procing early, suggestion-pick appending, garbled words) — so make the swipe-only gate the default. - Default PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE to true (was false). Only affects users who enabled the grace timer (advanced opt-in); for them, tap words no longer auto-finish. - Reworded the two near-identical toggles so they can't be confused: 'Only auto-space after swipes' (the SPACE) vs 'Only auto-finish swiped words' (whether the word COMMITS at all), with disambiguating summaries. - Restructure enterCombiningMode: the gate now suppresses only the TIMER ARMING, not combining-mode entry — so a tap-then-swipe word still extends and auto-finishes (fixed a regression the default flip exposed in tapThenGestureCombiningWordStillAutospaces). Verify: InputLogicTest + SettingsContainerTest -> 116 completed, 3 failed (pre-existing baseline), 1 skipped; 0 new failures. --- .../keyboard/latin/inputlogic/InputLogic.java | 23 ++++++++++--------- .../keyboard/latin/settings/Defaults.kt | 2 +- app/src/main/res/values/strings.xml | 6 ++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 14851f166..4ac354a4d 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -985,23 +985,24 @@ private void enterCombiningMode(final SettingsValues settingsValues, final boole // separators / cursor-front recompositions there's nothing to auto-commit, and arming // the timer would draw a spurious progress bar. if (!mWordComposer.isComposingWord()) return; - // #14: "auto-finish only after swipes" — don't arm the auto-commit timer for a tap-only - // word (no gesture fragment yet). Pure tap-typing shouldn't get auto-finished. A - // tap-then-swipe word still arms: the gesture trigger re-enters here with fromTap=false - // once the fragment is present. - if (fromTap && settingsValues.mCombiningGraceOnlyAfterGesture - && !mCombiningWordHasGestureFragment && !mWordComposer.isBatchMode()) { - return; - } final int graceMs = baseGraceMs + Math.max(0, settingsValues.mCombiningTapExtraMs); cancelCombiningTimerOnly(); mInCombiningMode = true; + // #14 "only auto-finish swiped words": still ENTER combining mode (so a following swipe + // can extend this word), but DON'T arm the auto-commit timer for a pure tap word — it + // stays open until the user commits. A tap-then-swipe still arms: the gesture re-enters + // here with fromTap=false and the fragment present, so it arms then. + final boolean armTimer = !(fromTap && settingsValues.mCombiningGraceOnlyAfterGesture + && !mCombiningWordHasGestureFragment && !mWordComposer.isBatchMode()); final long startTime = SystemClock.uptimeMillis(); - mPendingCombiningCommit = () -> onCombiningGraceExpired(); - mCombiningHandler.postDelayed(mPendingCombiningCommit, graceMs); + if (armTimer) { + mPendingCombiningCommit = () -> onCombiningGraceExpired(); + mCombiningHandler.postDelayed(mPendingCombiningCommit, graceMs); + } final MainKeyboardView kv = KeyboardSwitcher.getInstance().getMainKeyboardView(); if (kv != null) { - final boolean showAutospaceIndicator = settingsValues.shouldInsertSpacesAutomatically() + final boolean showAutospaceIndicator = armTimer + && settingsValues.shouldInsertSpacesAutomatically() && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces && (!settingsValues.mCombiningAutospaceOnlyAfterGesture || mCombiningWordHasGestureFragment) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt index e2cf0191a..c0e875464 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt @@ -150,7 +150,7 @@ object Defaults { const val PREF_COMBINING_TAP_EXTRA_MS = 250 const val PREF_COMBINING_AUTOSPACE_ONLY_AFTER_GESTURE = false const val PREF_SPACING_DEFER_GRACE_SPACE = false - const val PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE = false + const val PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE = true // default on: tapped words shouldn't auto-finish const val PREF_COMBINING_AUTOSPACE_SUGGESTIONS = "alternatives_then_next_word" const val PREF_COMBINING_BACKSPACE_DELETES_GESTURE_WORD = true const val PREF_COMBINING_BACKSPACE_DELETES_COMPOSING_TEXT = true diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 705ad3ed8..82e563c1c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -294,15 +294,15 @@ Extra autospace delay after taps Add this much extra time when the last input was a tapped letter, so tap-then-swipe combinations are easier to continue. Only auto-space after swipes - When enabled, tap-only words are committed without an automatic space. Words that include a swipe still auto-space. + Controls the automatic space only \u2014 not whether a word commits. When on, a tap-only word still commits but without an auto-space; swiped words still get one. Defer grace space (experimental) Route the two-thumb grace auto-commit space through the deferred mechanism (like the default swipe path) instead of writing it immediately. The space appears on your next input and stays backspace-reversible. - Auto-finish only after swipes + Only auto-finish swiped words - When enabled, the grace timer only auto-commits words that include a swipe. Words you tap out letter-by-letter are never auto-finished by the timer \u2014 tap space yourself when you\'re done. + The pause timer auto-commits a word only when it includes a swipe. Words you tap out stay open until you press space or pick a suggestion, so tapped shortcuts and corrections won\'t fire early. On by default \u2014 this controls whether the word commits (the auto-space option above only controls the trailing space). Backspace deletes last swipe