diff --git a/FLEKSY_AUTOCORRECT_HISTORY_NOTES.md b/FLEKSY_AUTOCORRECT_HISTORY_NOTES.md
new file mode 100644
index 0000000000..f25b678a5e
--- /dev/null
+++ b/FLEKSY_AUTOCORRECT_HISTORY_NOTES.md
@@ -0,0 +1,25 @@
+# Fleksy Autocorrect Revert History Notes
+
+Current swipe-up autocorrect revert is intentionally span/text based, not word-boundary based. This is required for corrections where one typed token becomes multiple committed words, e.g. `alot` -> `a lot`.
+
+Potential future improvement: keep a small history of recent revertable autocorrect commits instead of only relying on the latest `LastComposedWord`.
+
+Each history entry should store:
+
+- originally typed text, e.g. `alot`
+- committed text, e.g. `a lot`
+- separator string, usually a space
+- active/consumed state
+- optional cursor/end offset or timestamp if useful
+
+Important constraint: saving more words only helps if revert matching is still validated against actual editor text. Do not revert an old entry unless the text around the cursor still matches the stored committed span. Word-boundary matching is not enough for split-word autocorrections.
+
+Likely implementation shape:
+
+- Keep existing `mLastComposedWord` behavior for backspace undo to minimize risk.
+- Add a separate capped deque/list of recent autocorrect commits, probably 3-5 entries.
+- Push entries when `commitChosenWord(...)` creates a revertable autocorrect.
+- On swipe-up, scan newest to oldest and revert the first entry whose committed text plus optional separator matches before the cursor.
+- Mark entries consumed/inactive after reverting so stale history cannot repeatedly mutate text.
+
+Avoid broadening normal backspace undo until swipe-up history is proven safe; backspace currently assumes a single most recent commit.
diff --git a/java/res-large b/java/res-large
index d87d9dbdf3..17f3030148 160000
--- a/java/res-large
+++ b/java/res-large
@@ -1 +1 @@
-Subproject commit d87d9dbdf3966bbe18413be375dab2f6c7bbdfdd
+Subproject commit 17f303014869d7a8ea1bef72489bae2141cdbdf8
diff --git a/java/res/values/strings-uix.xml b/java/res/values/strings-uix.xml
index eaff240785..ca746b2eca 100644
--- a/java/res/values/strings-uix.xml
+++ b/java/res/values/strings-uix.xml
@@ -454,6 +454,16 @@
Misc. letters from common languages
e.g. [ß] on [s] in all Latin script languages
+
+
+ Swipe input modes
+ Swipe Typing (alpha)
+ Disabled
+ Allow swiping from key to key to write words.
+ Swipe Actions (alpha)
+ Fleksy-style directional action swipes.
+ Turn off swipe typing and swipe actions.
+
Typing preferences
Emoji Suggestions
@@ -699,4 +709,4 @@ Default is %1$s
Delete extra dictionary file?
%1$s will be deleted
-
\ No newline at end of file
+
diff --git a/java/src/org/futo/inputmethod/engine/IMEInterface.kt b/java/src/org/futo/inputmethod/engine/IMEInterface.kt
index b52598e041..174f076c7c 100644
--- a/java/src/org/futo/inputmethod/engine/IMEInterface.kt
+++ b/java/src/org/futo/inputmethod/engine/IMEInterface.kt
@@ -86,6 +86,7 @@ interface IMEInterface {
fun onUpWithDeletePointerActive()
fun onUpWithPointerActive()
fun onSwipeLanguage(direction: Int)
+ fun onSwipeAction(direction: Int)
fun onMovingCursorLockEvent(canMoveCursor: Boolean)
fun clearUserHistoryDictionaries()
diff --git a/java/src/org/futo/inputmethod/engine/general/ActionInputTransactionIME.kt b/java/src/org/futo/inputmethod/engine/general/ActionInputTransactionIME.kt
index 5906d48f95..53cff439fc 100644
--- a/java/src/org/futo/inputmethod/engine/general/ActionInputTransactionIME.kt
+++ b/java/src/org/futo/inputmethod/engine/general/ActionInputTransactionIME.kt
@@ -66,6 +66,7 @@ class ActionInputTransactionIME(val helper: IMEHelper) : IMEInterface, ActionInp
override fun onUpWithDeletePointerActive() {}
override fun onUpWithPointerActive() {}
override fun onSwipeLanguage(direction: Int) {}
+ override fun onSwipeAction(direction: Int) {}
override fun onMovingCursorLockEvent(canMoveCursor: Boolean) {}
override fun clearUserHistoryDictionaries() {}
override fun requestSuggestionRefresh() {}
@@ -111,4 +112,4 @@ class ActionInputTransactionIME(val helper: IMEHelper) : IMEInterface, ActionInp
fun ensureFinished() {
isFinished = true
}
-}
\ No newline at end of file
+}
diff --git a/java/src/org/futo/inputmethod/engine/general/ChineseIME.kt b/java/src/org/futo/inputmethod/engine/general/ChineseIME.kt
index fcfe2a17e5..008c9b78a5 100644
--- a/java/src/org/futo/inputmethod/engine/general/ChineseIME.kt
+++ b/java/src/org/futo/inputmethod/engine/general/ChineseIME.kt
@@ -902,6 +902,8 @@ class ChineseIME(val helper: IMEHelper) : IMEInterface, SuggestionStripViewAcces
switchToNextLanguage(helper.context, direction)
}
+ override fun onSwipeAction(direction: Int) {}
+
private var prevSuggest: SuggestedWords? = null
private val blacklist = SuggestionBlacklist(Settings.getInstance(), helper.context, helper.lifecycleScope)
override fun setNeutralSuggestionStrip() {
@@ -948,4 +950,4 @@ class ChineseIME(val helper: IMEHelper) : IMEInterface, SuggestionStripViewAcces
val debugInfo: String
get() = "configuration=${prevConfiguration}\nlayoutHint=${layoutHint}\nlocale=${Settings.getInstance().current.mLocale}\nisSimplified=${isSimplifiedChinese(Settings.getInstance().current.mLocale)}\nrawInput=${rawInput.text}"
-}
\ No newline at end of file
+}
diff --git a/java/src/org/futo/inputmethod/engine/general/GeneralIME.kt b/java/src/org/futo/inputmethod/engine/general/GeneralIME.kt
index 06370ccc0b..ebf049cd2b 100644
--- a/java/src/org/futo/inputmethod/engine/general/GeneralIME.kt
+++ b/java/src/org/futo/inputmethod/engine/general/GeneralIME.kt
@@ -26,11 +26,14 @@ import org.futo.inputmethod.engine.IMEMessage
import org.futo.inputmethod.engine.NonExpandableSuggestionBar
import org.futo.inputmethod.event.Event
import org.futo.inputmethod.event.InputTransaction
+import org.futo.inputmethod.keyboard.KeyboardActionListener
import org.futo.inputmethod.keyboard.KeyboardSwitcher
import org.futo.inputmethod.latin.BuildConfig
+import org.futo.inputmethod.latin.Dictionary
import org.futo.inputmethod.latin.DictionaryFacilitator
import org.futo.inputmethod.latin.DictionaryFacilitatorImpl
import org.futo.inputmethod.latin.DictionaryFacilitatorProvider
+import org.futo.inputmethod.latin.LastComposedWord
import org.futo.inputmethod.latin.NgramContext
import org.futo.inputmethod.latin.RichInputMethodManager
import org.futo.inputmethod.latin.Subtypes.switchToNextLanguage
@@ -51,6 +54,7 @@ import org.futo.inputmethod.latin.uix.isDirectBootUnlocked
import org.futo.inputmethod.latin.utils.AsyncResultHolder
import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator
import org.futo.inputmethod.v2keyboard.KeyboardLayoutSetV2
+import java.util.LinkedHashSet
import java.util.concurrent.atomic.AtomicInteger
interface WordLearner {
@@ -244,6 +248,7 @@ class GeneralIME(val helper: IMEHelper) : IMEInterface, WordLearner, SuggestionS
private val expandableExpandableCfg = ExpandableSuggestionBarConfiguration(true, false)
private var expandableCfg: ExpandableSuggestionBarConfiguration = NonExpandableSuggestionBar
override fun onStartInput() {
+ resetSwipeSuggestionSession()
expandableCfg = if(helper.context.getSetting(UseExpandableSuggestionsForGeneralIME)) {
expandableExpandableCfg
} else {
@@ -287,6 +292,7 @@ class GeneralIME(val helper: IMEHelper) : IMEInterface, WordLearner, SuggestionS
}
override fun onFinishInput() {
+ resetSwipeSuggestionSession()
inputLogic.finishInput()
dictionaryFacilitator.onFinishInput(context)
updateSuggestionJob?.cancel()
@@ -301,12 +307,20 @@ class GeneralIME(val helper: IMEHelper) : IMEInterface, WordLearner, SuggestionS
composingSpanStart: Int,
composingSpanEnd: Int
) {
- inputLogic.onUpdateSelection(
+ val selectionChanged = oldSelStart != newSelStart || oldSelEnd != newSelEnd
+
+ val cursorMovedByUser = inputLogic.onUpdateSelection(
oldSelStart, oldSelEnd,
newSelStart, newSelEnd,
composingSpanStart, composingSpanEnd,
Settings.getInstance().current
)
+
+ if (swipeSuggestionSelectionUpdatesToIgnore > 0) {
+ swipeSuggestionSelectionUpdatesToIgnore -= 1
+ } else if (selectionChanged && cursorMovedByUser) {
+ resetSwipeSuggestionSession()
+ }
}
override fun isGestureHandlingAvailable(): Boolean =
@@ -315,9 +329,15 @@ class GeneralIME(val helper: IMEHelper) : IMEInterface, WordLearner, SuggestionS
private fun onEventInternal(event: Event, ignoreSuggestionUpdate: Boolean = false) {
helper.requestCursorUpdate()
+ if (isSwipeActionsModeEnabled() && event.eventType != Event.EVENT_TYPE_SUGGESTION_PICKED) {
+ resetSwipeSuggestionSession()
+ }
+
+ val swipeActionPunctuationTransaction = handleSwipeActionTrailingSpacePunctuation(event)
+
val cursorBefore = inputLogic.mConnection.mExpectedSelStart
- val inputTransaction = when (event.eventType) {
+ val inputTransaction = swipeActionPunctuationTransaction ?: when (event.eventType) {
Event.EVENT_TYPE_INPUT_KEYPRESS,
Event.EVENT_TYPE_INPUT_KEYPRESS_RESUMED -> {
inputLogic.onCodeInput(
@@ -520,6 +540,425 @@ class GeneralIME(val helper: IMEHelper) : IMEInterface, WordLearner, SuggestionS
private val sequenceIdCompleted = AtomicInteger(0)
private val computationMutex = Mutex()
private var timeTakenToUpdate = 40L
+ private var swipeSuggestionIndex = -1
+ private var swipeSuggestionWord: String? = null
+ private var swipeSuggestionCandidates: List? = null
+ private var swipeSuggestionRestingWord: String? = null
+ private var swipeSuggestionRevertWord: String? = null
+ private var swipeReplacementSession: SwipeReplacementSession? = null
+ private var swipeSuggestionSelectionUpdatesToIgnore = 0
+
+ private data class SwipeReplacementSession(
+ val entries: List,
+ var index: Int
+ )
+
+ private fun getLastAutocorrectRevertPair(lastComposedWord: LastComposedWord): Pair? {
+ val typedWord = lastComposedWord.mTypedWord
+ val committedWord = lastComposedWord.mCommittedWord?.toString()
+
+ if (!lastComposedWord.canRevertCommit()
+ || typedWord.isNullOrEmpty()
+ || committedWord.isNullOrEmpty()
+ || typedWord == committedWord) {
+ return null
+ }
+
+ return committedWord to typedWord
+ }
+
+ private fun resetSwipeSuggestionSession() {
+ swipeSuggestionIndex = -1
+ swipeSuggestionWord = null
+ swipeSuggestionCandidates = null
+ swipeSuggestionRestingWord = null
+ swipeSuggestionRevertWord = null
+ swipeReplacementSession = null
+ }
+
+ private fun moveCursorForSwipeSuggestions(steps: Int) {
+ swipeSuggestionSelectionUpdatesToIgnore += 1
+
+ if (steps < 0) {
+ inputLogic.cursorLeft(-steps, false, false)
+ } else {
+ inputLogic.cursorRight(steps, false, false)
+ }
+ }
+
+ private fun replaceCommittedSwipeSuggestionIfNeeded(
+ currentSwipeWord: String?,
+ replacement: String
+ ): Boolean {
+ val resolvedSwipeWord = currentSwipeWord ?: inputLogic.mLastComposedWord.mCommittedWord?.toString()
+ if (resolvedSwipeWord.isNullOrEmpty() || inputLogic.mConnection.hasSelection()) {
+ return false
+ }
+
+ if (!inputLogic.mConnection.sameAsTextBeforeCursor(resolvedSwipeWord)) {
+ return false
+ }
+
+ helper.requestCursorUpdate()
+ inputLogic.mConnection.beginBatchEdit()
+ inputLogic.mConnection.finishComposingText()
+ inputLogic.mConnection.deleteTextBeforeCursor(resolvedSwipeWord.length)
+ inputLogic.mConnection.commitText(replacement, 1)
+ inputLogic.mConnection.endBatchEdit()
+ inputLogic.mConnection.send()
+ helper.keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState())
+ return true
+ }
+
+ private fun getReplacementForSwipeSuggestion(replacement: String): String {
+ val restingWord = swipeSuggestionRestingWord
+ if (restingWord.isNullOrEmpty() || !restingWord.contains(' ') || replacement.contains(' ')) {
+ return replacement
+ }
+
+ val prefix = restingWord.substringBeforeLast(' ', "")
+ return if (prefix.isNotEmpty()) "$prefix $replacement" else replacement
+ }
+
+ private fun buildSwipeReplacementSession(
+ candidates: List,
+ committedWord: String,
+ typedWord: String
+ ): SwipeReplacementSession {
+ val entries = ArrayList()
+ val seen = HashSet()
+
+ if (seen.add(typedWord)) {
+ entries.add(typedWord)
+ }
+ if (seen.add(committedWord)) {
+ entries.add(committedWord)
+ }
+
+ for (candidate in candidates) {
+ val word = getReplacementForSwipeSuggestion(candidate.mWord)
+ if (seen.add(word)) {
+ entries.add(word)
+ }
+ }
+
+ return SwipeReplacementSession(entries, entries.indexOf(committedWord))
+ }
+
+ private fun applySwipeReplacementSessionStep(direction: Int): Boolean {
+ val session = swipeReplacementSession ?: return false
+ if (session.index !in session.entries.indices) {
+ resetSwipeSuggestionSession()
+ return false
+ }
+
+ val step = if (direction == KeyboardActionListener.SWIPE_ACTION_UP) -1 else 1
+ val nextIndex = session.index + step
+ if (nextIndex !in session.entries.indices) {
+ return true
+ }
+
+ val currentWord = session.entries[session.index]
+ val replacement = session.entries[nextIndex]
+ if (!replaceCommittedSwipeSuggestionIfNeeded(currentWord, replacement)) {
+ resetSwipeSuggestionSession()
+ return false
+ }
+
+ session.index = nextIndex
+ swipeSuggestionWord = if (replacement == swipeSuggestionRestingWord) null else replacement
+ return true
+ }
+
+ private fun revertLastAutocorrectForSwipeIfNeeded(lastComposedWord: LastComposedWord): Boolean {
+ val (committedWord, typedWord) = getLastAutocorrectRevertPair(lastComposedWord)
+ ?: return false
+
+ if (inputLogic.mConnection.hasSelection()) {
+ return false
+ }
+
+ val separator = lastComposedWord.mSeparatorString ?: LastComposedWord.NOT_A_SEPARATOR
+ val committedText = committedWord + separator
+ val usePhantomSpace = separator == Constants.STRING_SPACE
+ val textToCommit = typedWord + if (usePhantomSpace) "" else separator
+ val textToDelete = if (inputLogic.mConnection.sameAsTextBeforeCursor(committedText)) {
+ committedText
+ } else if (inputLogic.mConnection.sameAsTextBeforeCursor(committedWord)) {
+ committedWord
+ } else {
+ return false
+ }
+
+ helper.requestCursorUpdate()
+ inputLogic.mConnection.beginBatchEdit()
+ inputLogic.mConnection.finishComposingText()
+ inputLogic.mConnection.deleteTextBeforeCursor(textToDelete.length)
+ inputLogic.mConnection.commitText(textToCommit, 1)
+ inputLogic.mConnection.endBatchEdit()
+ inputLogic.mConnection.send()
+ lastComposedWord.deactivate()
+ swipeSuggestionRestingWord = typedWord
+ swipeSuggestionRevertWord = null
+ swipeSuggestionCandidates = null
+ swipeReplacementSession = null
+ helper.keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState())
+ return true
+ }
+
+ private fun shouldResetSwipeSuggestionSessionForTouchedWord(touchedWord: String?): Boolean {
+ if (swipeSuggestionCandidates == null && swipeSuggestionRestingWord == null && swipeSuggestionWord == null) {
+ return false
+ }
+
+ if (touchedWord.isNullOrEmpty()) {
+ return false
+ }
+
+ return touchedWord != swipeSuggestionWord && touchedWord != swipeSuggestionRestingWord
+ }
+
+ private fun getSwipeSuggestionInfo(
+ candidates: List,
+ word: String
+ ): SuggestedWordInfo {
+ val existing = candidates.firstOrNull { it.mWord == word }
+ if (existing != null) {
+ return existing
+ }
+
+ return SuggestedWordInfo(
+ word,
+ "",
+ SuggestedWordInfo.MAX_SCORE,
+ SuggestedWordInfo.KIND_TYPED,
+ Dictionary.DICTIONARY_USER_TYPED,
+ SuggestedWordInfo.NOT_AN_INDEX,
+ SuggestedWordInfo.NOT_A_CONFIDENCE
+ )
+ }
+
+ private fun isSwipeActionsModeEnabled(): Boolean {
+ return settings.current.mGestureActionsEnabled
+ }
+
+ private fun sendDeleteKeypress() {
+ onEvent(
+ Event.createSoftwareKeypressEvent(
+ Event.NOT_A_CODE_POINT,
+ Constants.CODE_DELETE,
+ Constants.NOT_A_COORDINATE,
+ Constants.NOT_A_COORDINATE,
+ false
+ )
+ )
+ }
+
+ private fun performSwipeWordDelete() {
+ helper.requestCursorUpdate()
+
+ val result = inputLogic.onWordBackspace(
+ settings.current,
+ helper.keyboardShiftMode,
+ helper.currentKeyboardScriptId
+ )
+ val inputTransaction = result.mInputTransaction
+
+ inputLogic.mConnection.send()
+
+ when (inputTransaction.requiredShiftUpdate) {
+ InputTransaction.SHIFT_UPDATE_LATER,
+ InputTransaction.SHIFT_UPDATE_NOW ->
+ helper.keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState())
+ }
+
+ if (inputTransaction.requiresUpdateSuggestions()) {
+ updateSuggestions(SuggestedWords.INPUT_STYLE_TYPING)
+ }
+
+ showDeletedTextUndoSuggestion(result.mDeletedText)
+ }
+
+ private fun moveCursorToLastWordIfTrailingSpace(): Boolean {
+ val beforeCursor = inputLogic.mConnection.getTextBeforeCursor(1, 0)?.toString()
+ if (!beforeCursor.isNullOrEmpty()
+ && beforeCursor.last() == ' '
+ && inputLogic.mConnection.hasCursorPosition()
+ && !inputLogic.mConnection.hasSelection()) {
+ moveCursorForSwipeSuggestions(-1)
+ return true
+ }
+
+ return false
+ }
+
+ private fun restoreCursorIfMoved(movedCursorToLastWord: Boolean) {
+ if (movedCursorToLastWord) {
+ moveCursorForSwipeSuggestions(1)
+ }
+ }
+
+ private fun getSwipeActionTrailingSpacePunctuationCodePoint(event: Event): Int? {
+ if (!isSwipeActionsModeEnabled()) {
+ return null
+ }
+
+ val codePoint = when (event.eventType) {
+ Event.EVENT_TYPE_INPUT_KEYPRESS,
+ Event.EVENT_TYPE_INPUT_KEYPRESS_RESUMED -> event.mCodePoint
+ Event.EVENT_TYPE_SOFTWARE_GENERATED_STRING -> {
+ val text = event.getTextToCommit().toString()
+ if (text.codePointCount(0, text.length) != 1) {
+ return null
+ }
+ text.codePointAt(0)
+ }
+ else -> return null
+ }
+
+ if (codePoint == Event.NOT_A_CODE_POINT
+ || Character.isWhitespace(codePoint)
+ || !settings.current.isWordSeparator(codePoint)
+ || !settings.current.isUsuallyFollowedBySpace(codePoint)
+ || settings.current.isOptionallyPrecededBySpace(codePoint)) {
+ return null
+ }
+
+ val beforeCursor = inputLogic.mConnection.getTextBeforeCursor(1, 0)?.toString()
+ return if (!beforeCursor.isNullOrEmpty()
+ && beforeCursor.last() == ' '
+ && inputLogic.mConnection.hasCursorPosition()
+ && !inputLogic.mConnection.hasSelection()) {
+ codePoint
+ } else {
+ null
+ }
+ }
+
+ private fun handleSwipeActionTrailingSpacePunctuation(event: Event): InputTransaction? {
+ val punctuationCodePoint = getSwipeActionTrailingSpacePunctuationCodePoint(event)
+ ?: return null
+ inputLogic.mConnection.removeTrailingSpace()
+
+ val normalizedEvent = Event.createSoftwareKeypressEvent(
+ punctuationCodePoint,
+ punctuationCodePoint,
+ Constants.NOT_A_COORDINATE,
+ Constants.NOT_A_COORDINATE,
+ false
+ )
+
+ val punctuationTransaction = inputLogic.onCodeInput(
+ settings.current,
+ normalizedEvent,
+ helper.keyboardShiftMode,
+ helper.currentKeyboardScriptId
+ )
+
+ val codePointAfterCursor = inputLogic.mConnection.getCodePointAfterCursor()
+ if (inputLogic.mConnection.spaceFollowsCursor()
+ || (codePointAfterCursor != Constants.NOT_A_CODE
+ && Character.isWhitespace(codePointAfterCursor))) {
+ return punctuationTransaction
+ }
+
+ return inputLogic.onCodeInput(
+ settings.current,
+ Event.createSoftwareKeypressEvent(
+ Constants.CODE_SPACE,
+ Constants.CODE_SPACE,
+ Constants.NOT_A_COORDINATE,
+ Constants.NOT_A_COORDINATE,
+ false
+ ),
+ helper.keyboardShiftMode,
+ helper.currentKeyboardScriptId
+ )
+ }
+
+ private fun getSwipePunctuationCycle(): List {
+ val cycle = LinkedHashSet()
+
+ val suggestedPunctuation = settings.current.mSpacingAndPunctuations.suggestPuncList
+ for (index in 0 until suggestedPunctuation.size()) {
+ val punctuation = suggestedPunctuation.getWord(index)
+ if (!punctuation.isNullOrEmpty() && punctuation.codePointCount(0, punctuation.length) == 1) {
+ cycle.add(punctuation)
+ }
+ }
+
+ if (cycle.isEmpty()) {
+ return emptyList()
+ }
+
+ val ordered = cycle.toMutableList()
+ val commaIndex = ordered.indexOf(",")
+ if (commaIndex > 0) {
+ ordered.removeAt(commaIndex)
+ ordered.add(0, ",")
+ }
+
+ return ordered
+ }
+
+ private fun replacePunctuationWith(replacement: String): Boolean {
+ if (replacement.codePointCount(0, replacement.length) != 1) {
+ return false
+ }
+
+ resetSwipeSuggestionSession()
+ sendDeleteKeypress()
+
+ val replacementCodePoint = replacement.codePointAt(0)
+ onEvent(
+ Event.createSoftwareKeypressEvent(
+ replacementCodePoint,
+ replacementCodePoint,
+ Constants.NOT_A_COORDINATE,
+ Constants.NOT_A_COORDINATE,
+ false
+ )
+ )
+
+ return true
+ }
+
+ private fun trySwipeCyclePunctuation(direction: Int): Boolean {
+ val beforeCursor = inputLogic.mConnection.getTextBeforeCursor(1, 0)?.toString()
+ if (beforeCursor.isNullOrEmpty()) {
+ return false
+ }
+
+ val currentPunctuation = beforeCursor.last().toString()
+ val cycle = getSwipePunctuationCycle()
+ if (cycle.size < 2) {
+ return false
+ }
+
+ val currentIndex = cycle.indexOf(currentPunctuation)
+ if (currentIndex < 0) {
+ if (currentPunctuation == ".") {
+ val replacement = if (direction == KeyboardActionListener.SWIPE_ACTION_UP) {
+ cycle.last()
+ } else {
+ cycle.first()
+ }
+ return replacePunctuationWith(replacement)
+ }
+
+ return false
+ }
+
+ val step = if (direction == KeyboardActionListener.SWIPE_ACTION_UP) -1 else 1
+ val nextIndex = (currentIndex + step + cycle.size) % cycle.size
+ val replacement = cycle[nextIndex]
+ if (replacement == currentPunctuation) {
+ return false
+ }
+
+ return replacePunctuationWith(replacement)
+ }
+
fun updateSuggestions(inputStyle: Int) {
updateSuggestionJob?.cancel()
@@ -687,37 +1126,45 @@ class GeneralIME(val helper: IMEHelper) : IMEInterface, WordLearner, SuggestionS
ignoreSuggestionUpdate = true
)
- if (selection != null && settings.current.mInputAttributes.mShouldShowSuggestions) {
- val info = ArrayList()
- info.add(
- SuggestedWordInfo(
- selection.toString(),
- "",
- 0,
- SuggestedWordInfo.KIND_UNDO,
- null,
- 0,
- 0
- )
- )
- showSuggestionStrip(
- SuggestedWords(
- info,
- null,
- null,
- false,
- false,
- false,
- 0,
- 0
- )
- )
+ if (settings.current.mInputAttributes.mShouldShowSuggestions) {
+ showDeletedTextUndoSuggestion(selection?.toString())
}
} else {
onUpWithPointerActive()
}
}
+ private fun showDeletedTextUndoSuggestion(deletedText: String?) {
+ if (deletedText == null) {
+ return
+ }
+
+ val info = ArrayList()
+ info.add(
+ SuggestedWordInfo(
+ deletedText,
+ "",
+ 0,
+ SuggestedWordInfo.KIND_UNDO,
+ null,
+ 0,
+ 0
+ )
+ )
+ showSuggestionStrip(
+ SuggestedWords(
+ info,
+ null,
+ null,
+ false,
+ false,
+ false,
+ 0,
+ 0
+ )
+ )
+ }
+
override fun onUpWithPointerActive() {
inputLogic.restartSuggestionsOnWordTouchedByCursor(
settings.current, null,
@@ -730,6 +1177,219 @@ class GeneralIME(val helper: IMEHelper) : IMEInterface, WordLearner, SuggestionS
switchToNextLanguage(context, direction)
}
+ override fun onSwipeAction(direction: Int) {
+ if (!isSwipeActionsModeEnabled()) return
+
+ when (direction) {
+ KeyboardActionListener.SWIPE_ACTION_RIGHT -> {
+ resetSwipeSuggestionSession()
+ onEvent(
+ Event.createSoftwareKeypressEvent(
+ Constants.CODE_SPACE,
+ Constants.CODE_SPACE,
+ Constants.NOT_A_COORDINATE,
+ Constants.NOT_A_COORDINATE,
+ false
+ )
+ )
+ }
+
+ KeyboardActionListener.SWIPE_ACTION_LEFT -> {
+ resetSwipeSuggestionSession()
+ setNeutralSuggestionStrip()
+ performSwipeWordDelete()
+ }
+
+ KeyboardActionListener.SWIPE_ACTION_UP,
+ KeyboardActionListener.SWIPE_ACTION_DOWN -> {
+ val lastComposedWordAtSwipeStart = inputLogic.mLastComposedWord
+ val movedCursorToLastWord = moveCursorToLastWordIfTrailingSpace()
+
+ if (trySwipeCyclePunctuation(direction)) {
+ restoreCursorIfMoved(movedCursorToLastWord)
+ return
+ }
+
+ if (applySwipeReplacementSessionStep(direction)) {
+ restoreCursorIfMoved(movedCursorToLastWord)
+ return
+ }
+
+ if (direction == KeyboardActionListener.SWIPE_ACTION_UP
+ && revertLastAutocorrectForSwipeIfNeeded(lastComposedWordAtSwipeStart)) {
+ restoreCursorIfMoved(movedCursorToLastWord)
+ return
+ }
+
+ inputLogic.restartSuggestionsOnWordTouchedByCursor(
+ settings.current,
+ null,
+ false,
+ helper.currentKeyboardScriptId
+ )
+
+ if (!ensureSuggestionsCompleted()) {
+ restoreCursorIfMoved(movedCursorToLastWord)
+ return
+ }
+
+ val touchedWord = inputLogic.mWordComposer.typedWord
+
+ if (shouldResetSwipeSuggestionSessionForTouchedWord(touchedWord)) {
+ resetSwipeSuggestionSession()
+ }
+
+ val autocorrectRevertPair = getLastAutocorrectRevertPair(lastComposedWordAtSwipeStart)
+ if (swipeSuggestionRestingWord == null) {
+ swipeSuggestionRestingWord = autocorrectRevertPair?.first ?: touchedWord
+ }
+
+ if (swipeSuggestionRevertWord == null && autocorrectRevertPair != null) {
+ swipeSuggestionRevertWord = autocorrectRevertPair.second
+ }
+
+ val candidates = swipeSuggestionCandidates ?: run {
+ val suggestions = inputLogic.mSuggestedWords
+ val rebuiltCandidates = ArrayList()
+ val seen = HashSet()
+ for (index in 0 until suggestions.size()) {
+ val info = suggestions.getInfo(index)
+ if (!info.isKindOf(SuggestedWordInfo.KIND_UNDO) && seen.add(info.mWord)) {
+ rebuiltCandidates.add(info)
+ }
+ }
+ swipeSuggestionCandidates = rebuiltCandidates
+ rebuiltCandidates
+ }
+
+ if (autocorrectRevertPair != null) {
+ swipeReplacementSession = buildSwipeReplacementSession(
+ candidates,
+ autocorrectRevertPair.first,
+ autocorrectRevertPair.second
+ )
+ if (applySwipeReplacementSessionStep(direction)) {
+ restoreCursorIfMoved(movedCursorToLastWord)
+ return
+ }
+ resetSwipeSuggestionSession()
+ restoreCursorIfMoved(movedCursorToLastWord)
+ return
+ }
+
+ if (direction == KeyboardActionListener.SWIPE_ACTION_UP) {
+ val restingWord = swipeSuggestionRestingWord
+ val currentWord = swipeSuggestionWord ?: restingWord ?: touchedWord
+
+ if (!swipeSuggestionWord.isNullOrEmpty()
+ && swipeSuggestionIndex in candidates.indices
+ && candidates[swipeSuggestionIndex].mWord == swipeSuggestionWord) {
+ val previousIndex = (swipeSuggestionIndex - 1 + candidates.size) % candidates.size
+ val selected = if (!restingWord.isNullOrEmpty()
+ && swipeSuggestionIndex == 0) {
+ getSwipeSuggestionInfo(candidates, restingWord)
+ } else {
+ candidates[previousIndex]
+ }
+ val replacement = getReplacementForSwipeSuggestion(selected.mWord)
+ if (!replaceCommittedSwipeSuggestionIfNeeded(
+ swipeSuggestionWord ?: currentWord,
+ replacement
+ )) {
+ onEvent(Event.createSuggestionPickedEvent(getSwipeSuggestionInfo(candidates, replacement)))
+ }
+ if (!restingWord.isNullOrEmpty() && replacement == restingWord) {
+ swipeSuggestionIndex = -1
+ swipeSuggestionWord = null
+ } else {
+ swipeSuggestionIndex = previousIndex
+ swipeSuggestionWord = replacement
+ }
+ restoreCursorIfMoved(movedCursorToLastWord)
+ return
+ }
+
+ val revertWord = swipeSuggestionRevertWord
+ if (!revertWord.isNullOrEmpty()
+ && !currentWord.isNullOrEmpty()
+ && currentWord == restingWord
+ && revertWord != currentWord) {
+ val selected = getSwipeSuggestionInfo(candidates, revertWord)
+ if (!replaceCommittedSwipeSuggestionIfNeeded(currentWord, selected.mWord)) {
+ onEvent(Event.createSuggestionPickedEvent(selected))
+ }
+ swipeSuggestionIndex = -1
+ swipeSuggestionWord = null
+ swipeSuggestionRestingWord = selected.mWord
+ swipeSuggestionRevertWord = null
+ swipeSuggestionCandidates = null
+ restoreCursorIfMoved(movedCursorToLastWord)
+ return
+ }
+
+ restoreCursorIfMoved(movedCursorToLastWord)
+ return
+ }
+
+ if (candidates.size < 2) {
+ resetSwipeSuggestionSession()
+ restoreCursorIfMoved(movedCursorToLastWord)
+ return
+ }
+
+ val typedWord = inputLogic.mWordComposer.typedWord
+ val currentWord = swipeSuggestionWord ?: swipeSuggestionRestingWord ?: typedWord
+ val typedWordIndex = if (typedWord != null) {
+ candidates.indexOfFirst { it.mWord == typedWord }
+ } else {
+ -1
+ }
+ val currentWordIndex = if (currentWord != null) {
+ candidates.indexOfFirst { it.mWord == currentWord }
+ } else {
+ -1
+ }
+
+ val baseIndex = if (swipeSuggestionWord != null
+ && swipeSuggestionIndex in candidates.indices
+ && candidates[swipeSuggestionIndex].mWord == swipeSuggestionWord) {
+ swipeSuggestionIndex
+ } else if (currentWordIndex >= 0) {
+ currentWordIndex
+ } else if (typedWordIndex >= 0) {
+ typedWordIndex
+ } else {
+ 0
+ }
+
+ val step = 1
+ var nextIndex = (baseIndex + step + candidates.size) % candidates.size
+ if (currentWord != null && candidates[nextIndex].mWord == currentWord) {
+ nextIndex = (nextIndex + step + candidates.size) % candidates.size
+ }
+
+ val selected = candidates[nextIndex]
+ if (currentWord != null && selected.mWord == currentWord) {
+ restoreCursorIfMoved(movedCursorToLastWord)
+ return
+ }
+
+ if (!replaceCommittedSwipeSuggestionIfNeeded(
+ swipeSuggestionWord ?: currentWord,
+ getReplacementForSwipeSuggestion(selected.mWord)
+ )) {
+ onEvent(Event.createSuggestionPickedEvent(
+ getSwipeSuggestionInfo(candidates, getReplacementForSwipeSuggestion(selected.mWord))
+ ))
+ }
+ swipeSuggestionIndex = nextIndex
+ swipeSuggestionWord = getReplacementForSwipeSuggestion(selected.mWord)
+
+ restoreCursorIfMoved(movedCursorToLastWord)
+ }
+ }
+ }
+
override fun onMovingCursorLockEvent(canMoveCursor: Boolean) {
// GeneralIME does nothing
}
@@ -814,4 +1474,4 @@ class GeneralIME(val helper: IMEHelper) : IMEInterface, WordLearner, SuggestionS
@OptIn(ExperimentalCoroutinesApi::class)
val dictionaryScope = Dispatchers.Default.limitedParallelism(1)
}
-}
\ No newline at end of file
+}
diff --git a/java/src/org/futo/inputmethod/engine/general/JapaneseIME.kt b/java/src/org/futo/inputmethod/engine/general/JapaneseIME.kt
index 726d8daba5..6e8d4320f2 100644
--- a/java/src/org/futo/inputmethod/engine/general/JapaneseIME.kt
+++ b/java/src/org/futo/inputmethod/engine/general/JapaneseIME.kt
@@ -1207,6 +1207,10 @@ class JapaneseIME(val helper: IMEHelper) : IMEInterface {
}
+ override fun onSwipeAction(direction: Int) {
+
+ }
+
override fun onMovingCursorLockEvent(canMoveCursor: Boolean) {
}
@@ -1244,4 +1248,4 @@ class JapaneseIME(val helper: IMEHelper) : IMEInterface {
prevSuggestions = words
helper.showSuggestionStrip(words, expandableUiCfg)
}
-}
\ No newline at end of file
+}
diff --git a/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java b/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java
index 813426429e..e7f108f880 100644
--- a/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java
@@ -20,6 +20,11 @@
import org.futo.inputmethod.latin.common.InputPointers;
public interface KeyboardActionListener {
+ public static final int SWIPE_ACTION_LEFT = -1;
+ public static final int SWIPE_ACTION_RIGHT = 1;
+ public static final int SWIPE_ACTION_UP = -2;
+ public static final int SWIPE_ACTION_DOWN = 2;
+
/**
* Called when the user presses a key. This is sent before the {@link #onCodeInput} is called.
* For keys that repeat, this is only called once.
@@ -105,6 +110,7 @@ public interface KeyboardActionListener {
public void onMoveDeletePointer(int steps);
public void onUpWithDeletePointerActive();
public void onUpWithPointerActive();
+ public void onSwipeAction(int direction);
public void onMovingCursorLockEvent(boolean canMoveCursor);
public static final KeyboardActionListener EMPTY_LISTENER = new Adapter();
@@ -148,6 +154,8 @@ public void onSwipeLanguageReleased() {}
@Override
public void onSwipeLanguageProgress(float progress) {}
@Override
+ public void onSwipeAction(int direction) {}
+ @Override
public void onMovingCursorLockEvent(boolean canMoveCursor) {}
}
}
diff --git a/java/src/org/futo/inputmethod/keyboard/PointerTracker.java b/java/src/org/futo/inputmethod/keyboard/PointerTracker.java
index 6469fe1ad2..a2cfd57494 100644
--- a/java/src/org/futo/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/org/futo/inputmethod/keyboard/PointerTracker.java
@@ -97,6 +97,9 @@ public static void setStateHint(StateHint stateHint) {
private static PointerTrackerParams sParams;
private static final int sPointerStep = (int)(16.0 * Resources.getSystem().getDisplayMetrics().density);
private static final int sPointerBigStep = (int)(32.0 * Resources.getSystem().getDisplayMetrics().density);
+ private static final int sPointerSwipeActionStep = (int)(18.0 * Resources.getSystem().getDisplayMetrics().density);
+ private static final float SWIPE_ACTION_HORIZONTAL_DOMINANCE_RATIO = 1.0f;
+ private static final float SWIPE_ACTION_VERTICAL_DOMINANCE_RATIO = 0.70f;
private static final int sPointerHugeStep = Integer.min(
(int)(64.0 * Resources.getSystem().getDisplayMetrics().density),
Resources.getSystem().getDisplayMetrics().widthPixels * 3 / 2
@@ -158,6 +161,7 @@ public static void setStateHint(StateHint stateHint) {
private boolean mCursorMoved = false;
private boolean mProgressReported = false;
private boolean mSpacebarLongPressed = false;
+ private boolean mSwipeActionTriggered = false;
// true if keyboard layout has been changed.
private boolean mKeyboardLayoutHasBeenChanged;
@@ -761,8 +765,14 @@ private void onDownEventInternal(final int x, final int y, final long eventTime)
mStartTime = System.currentTimeMillis();
mStartedOnFastLongPress = key.isFastLongPress();
mSpacebarLongPressed = false;
+ mSwipeActionTriggered = false;
- mIsSlidingCursor = key.getCode() == Constants.CODE_DELETE || key.getCode() == Constants.CODE_SPACE;
+ final boolean swipeActionsMode =
+ Settings.getInstance().getCurrent().mGestureActionsEnabled;
+
+ mIsSlidingCursor = key.getCode() == Constants.CODE_DELETE
+ || key.getCode() == Constants.CODE_SPACE
+ || swipeActionsMode;
mIsFlickingKey = !mIsSlidingCursor && key.getHasFlick();
mFlickDirection = key.flickDirection(0, 0);
mCurrentKey = key;
@@ -970,6 +980,52 @@ private void onMoveEventInternal(final int x, final int y, final long eventTime)
final SettingsValues settingsValues = Settings.getInstance().getCurrent();
+ if (mIsSlidingCursor && oldKey != null
+ && oldKey.getCode() != Constants.CODE_SPACE
+ && oldKey.getCode() != Constants.CODE_DELETE
+ && settingsValues.mGestureActionsEnabled) {
+ final int pointerStep = sPointerSwipeActionStep;
+ final int swipeIgnoreTime = settingsValues.mKeyLongpressTimeout
+ / MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
+ final int dx = x - mStartX;
+ final int dy = y - mStartY;
+ final long swipeDistanceSquared = (long)dx * dx + (long)dy * dy;
+ final long swipeStepSquared = (long)pointerStep * pointerStep;
+
+ if (!mSwipeActionTriggered
+ && mStartTime + swipeIgnoreTime < System.currentTimeMillis()
+ && swipeDistanceSquared >= swipeStepSquared) {
+ sTimerProxy.cancelKeyTimersOf(this);
+ mCursorMoved = true;
+ mSwipeActionTriggered = true;
+
+ final int absDx = Math.abs(dx);
+ final int absDy = Math.abs(dy);
+ final float horizontalScore = absDx
+ - absDy * SWIPE_ACTION_HORIZONTAL_DOMINANCE_RATIO;
+ final float verticalScore = absDy
+ - absDx * SWIPE_ACTION_VERTICAL_DOMINANCE_RATIO;
+ final boolean isHorizontalSwipe = horizontalScore >= 0.0f;
+ final boolean isVerticalSwipe = verticalScore >= 0.0f;
+
+ if ((isHorizontalSwipe && !isVerticalSwipe)
+ || (isHorizontalSwipe == isVerticalSwipe
+ && horizontalScore >= verticalScore)) {
+ sListener.onSwipeAction(dx > 0
+ ? KeyboardActionListener.SWIPE_ACTION_RIGHT
+ : KeyboardActionListener.SWIPE_ACTION_LEFT);
+ } else {
+ sListener.onSwipeAction(dy < 0
+ ? KeyboardActionListener.SWIPE_ACTION_UP
+ : KeyboardActionListener.SWIPE_ACTION_DOWN);
+ }
+ }
+
+ mLastX = x;
+ mLastY = y;
+ return;
+ }
+
if (!sInGesture && mIsSlidingCursor && oldKey != null && oldKey.getCode() == Constants.CODE_SPACE) {
boolean allowedBySettings = (mSpacebarLongPressed && settingsValues.mSpacebarHoldMode == Settings.SPACEBAR_MODE_CURSOR)
|| (!mSpacebarLongPressed && settingsValues.mSpacebarSwipeMode != Settings.SPACEBAR_MODE_OFF);
diff --git a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
index 7807c3dc84..54a63fcf66 100644
--- a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
+++ b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
@@ -685,6 +685,13 @@ public void onSwipeLanguageProgress(float progress) {
mSwipeLanguageProgress = progress;
}
+ @Override
+ public void onSwipeAction(int direction) {
+ mImeManager.getActiveIME(
+ mSettings.getCurrent()
+ ).onSwipeAction(direction);
+ }
+
@Override
public void onMovingCursorLockEvent(boolean canMoveCursor) {
if(canMoveCursor) {
diff --git a/java/src/org/futo/inputmethod/latin/RichInputConnection.java b/java/src/org/futo/inputmethod/latin/RichInputConnection.java
index e4f1428405..06511b5926 100644
--- a/java/src/org/futo/inputmethod/latin/RichInputConnection.java
+++ b/java/src/org/futo/inputmethod/latin/RichInputConnection.java
@@ -559,6 +559,20 @@ public void deleteTextBeforeCursor(final int beforeLength) {
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
+ public void deleteTextAroundCursor(final int beforeLength, final int afterLength) {
+ if (DEBUG_BATCH_NESTING) checkBatchEdit();
+ final int lengthToDeleteBefore = Math.min(beforeLength, mExpectedSelStart);
+ mExpectedSelStart -= lengthToDeleteBefore;
+ mExpectedSelEnd = mExpectedSelStart;
+ mComposingText.setLength(0);
+
+ if (isConnected()) {
+ mIC.deleteSurroundingText(beforeLength, afterLength);
+ }
+ reloadTextCache();
+ if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
+ }
+
public void performEditorAction(final int actionId) {
updateConnection();
if (isConnected()) {
diff --git a/java/src/org/futo/inputmethod/latin/inputlogic/InputLogic.java b/java/src/org/futo/inputmethod/latin/inputlogic/InputLogic.java
index ec9e330ec7..fa0a881c85 100644
--- a/java/src/org/futo/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/org/futo/inputmethod/latin/inputlogic/InputLogic.java
@@ -124,6 +124,7 @@ private class RememberedSuggestedWords {
// Keeps track of most recently inserted text (multi-character key) for reverting
private String mEnteredText;
+ private String mLastBackspaceDeletedText;
// TODO: This boolean is persistent state and causes large side effects at unexpected times.
// Find a way to remove it for readability.
@@ -629,6 +630,58 @@ public boolean onUpdateSelection(final int oldSelStart, final int oldSelEnd,
public InputTransaction onCodeInput(final SettingsValues settingsValues,
@Nonnull final Event event, final int keyboardShiftMode,
final int currentKeyboardScriptId) {
+ return onCodeInputInternal(settingsValues, event, keyboardShiftMode,
+ currentKeyboardScriptId, false /* forceDeleteWholeWords */,
+ false /* deleteWordAroundCursor */);
+ }
+
+ public static final class WordBackspaceResult {
+ public final InputTransaction mInputTransaction;
+ public final String mDeletedText;
+
+ public WordBackspaceResult(final InputTransaction inputTransaction,
+ final String deletedText) {
+ mInputTransaction = inputTransaction;
+ mDeletedText = deletedText;
+ }
+ }
+
+ private static final class BackspaceDeletionTarget {
+ public final String mDeletedText;
+ public final int mCharsBeforeCursor;
+ public final int mCharsAfterCursor;
+
+ private BackspaceDeletionTarget(final String deletedText, final int charsBeforeCursor,
+ final int charsAfterCursor) {
+ mDeletedText = deletedText;
+ mCharsBeforeCursor = charsBeforeCursor;
+ mCharsAfterCursor = charsAfterCursor;
+ }
+
+ public int getTotalCharsToDelete() {
+ return mCharsBeforeCursor + mCharsAfterCursor;
+ }
+ }
+
+ public WordBackspaceResult onWordBackspace(final SettingsValues settingsValues,
+ final int keyboardShiftMode, final int currentKeyboardScriptId) {
+ final Event event = Event.createSoftwareKeypressEvent(
+ Event.NOT_A_CODE_POINT,
+ Constants.CODE_DELETE,
+ Constants.NOT_A_COORDINATE,
+ Constants.NOT_A_COORDINATE,
+ true /* isKeyRepeat */);
+ final InputTransaction inputTransaction = onCodeInputInternal(settingsValues, event,
+ keyboardShiftMode, currentKeyboardScriptId,
+ true /* forceDeleteWholeWords */, true /* deleteWordAroundCursor */);
+ return new WordBackspaceResult(inputTransaction, mLastBackspaceDeletedText);
+ }
+
+ private InputTransaction onCodeInputInternal(final SettingsValues settingsValues,
+ @Nonnull final Event event, final int keyboardShiftMode,
+ final int currentKeyboardScriptId, final boolean forceDeleteWholeWords,
+ final boolean deleteWordAroundCursor) {
+ mLastBackspaceDeletedText = null;
mWordBeingCorrectedByCursor = null;
if(settingsValues.needsToLookupSuggestions()) {
@@ -673,7 +726,8 @@ public InputTransaction onCodeInput(final SettingsValues settingsValues,
if (currentEvent.isConsumed()) {
handleConsumedEvent(currentEvent, inputTransaction);
} else if (currentEvent.isFunctionalKeyEvent()) {
- handleFunctionalEvent(currentEvent, inputTransaction, currentKeyboardScriptId);
+ handleFunctionalEvent(currentEvent, inputTransaction, currentKeyboardScriptId,
+ forceDeleteWholeWords, deleteWordAroundCursor);
} else {
handleNonFunctionalEvent(currentEvent, inputTransaction);
}
@@ -708,6 +762,64 @@ public InputTransaction onCodeInput(final SettingsValues settingsValues,
return inputTransaction;
}
+ private BackspaceDeletionTarget getDeletionTargetBeforeCursor(final boolean deleteWholeWords) {
+ final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
+ if (codePointBeforeCursor == Constants.NOT_A_CODE) {
+ return null;
+ }
+
+ String textDeleted = new String(Character.toChars(codePointBeforeCursor));
+ int lengthToDelete = Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
+
+ final CharSequence textBeforeCursor = mConnection.getTextBeforeCursor(
+ deleteWholeWords ? 48 : 8, 0);
+ if (textBeforeCursor != null && textBeforeCursor.length() > 0) {
+ final BreakIterator breakIterator = deleteWholeWords
+ ? BreakIterator.getWordInstance() : BreakIterator.getCharacterInstance();
+ breakIterator.setText(textBeforeCursor.toString());
+ final int end = breakIterator.last();
+ int start = breakIterator.previous();
+
+ if (deleteWholeWords && start != BreakIterator.DONE
+ && textBeforeCursor.subSequence(start, end).toString().equals(" ")) {
+ start = breakIterator.previous();
+ }
+
+ if (start != BreakIterator.DONE) {
+ lengthToDelete = end - start;
+ textDeleted = textBeforeCursor.subSequence(start, end).toString();
+ }
+ }
+
+ return new BackspaceDeletionTarget(textDeleted, lengthToDelete, 0);
+ }
+
+ private BackspaceDeletionTarget getDeletionTargetForWordAtCursor(
+ final SettingsValues settingsValues, final int currentKeyboardScriptId) {
+ final TextRange range = mConnection.getWordRangeAtCursor(
+ settingsValues, currentKeyboardScriptId, true);
+ if (range == null || range.length() <= 0) {
+ return null;
+ }
+
+ final int charsBeforeCursor = range.getNumberOfCharsInWordBeforeCursor();
+ final int charsAfterCursor = range.getNumberOfCharsInWordAfterCursor();
+ if (charsBeforeCursor <= 0 && charsAfterCursor <= 0) {
+ return null;
+ }
+
+ return new BackspaceDeletionTarget(range.mWord.toString(), charsBeforeCursor,
+ charsAfterCursor);
+ }
+
+ private void deleteUsingTarget(final BackspaceDeletionTarget target) {
+ if (target.mCharsAfterCursor > 0) {
+ mConnection.deleteTextAroundCursor(target.mCharsBeforeCursor, target.mCharsAfterCursor);
+ } else {
+ mConnection.deleteTextBeforeCursor(target.mCharsBeforeCursor);
+ }
+ }
+
/**
* Updates keys whose hitboxes are boosted. This works by looking at the word being composed,
* and checking for next letters that would still produce a valid word within the dictionary
@@ -885,7 +997,8 @@ private void handleConsumedEvent(final Event event, final InputTransaction input
* @param inputTransaction The transaction in progress.
*/
private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction,
- final int currentKeyboardScriptId) {
+ final int currentKeyboardScriptId, final boolean forceDeleteWholeWords,
+ final boolean deleteWordAroundCursor) {
if(event.getEventType() == Event.EVENT_TYPE_STOP_COMPOSING) {
commitTyped(inputTransaction.mSettingsValues, "");
@@ -907,7 +1020,8 @@ private void handleFunctionalEvent(final Event event, final InputTransaction inp
switch (event.mKeyCode) {
case Constants.CODE_DELETE:
- handleBackspaceEvent(event, inputTransaction, currentKeyboardScriptId);
+ handleBackspaceEvent(event, inputTransaction, currentKeyboardScriptId,
+ forceDeleteWholeWords, deleteWordAroundCursor);
// Backspace is a functional key, but it affects the contents of the editor.
inputTransaction.setDidAffectContents();
break;
@@ -1398,7 +1512,8 @@ private void handleSeparatorEvent(final Event event, final InputTransaction inpu
* @param inputTransaction The transaction in progress.
*/
private void handleBackspaceEvent(final Event event, final InputTransaction inputTransaction,
- final int currentKeyboardScriptId) {
+ final int currentKeyboardScriptId, final boolean forceDeleteWholeWords,
+ final boolean deleteWordAroundCursor) {
mSpaceState = SpaceState.NONE;
mDeleteCount++;
@@ -1423,8 +1538,14 @@ private void handleBackspaceEvent(final Event event, final InputTransaction inpu
resetComposingWord(inputTransaction.mSettingsValues, false);
}
- final boolean deleteWholeWords = event.isKeyRepeat()
- && inputTransaction.mSettingsValues.mBackspaceModeHold == Settings.BACKSPACE_MODE_WORDS;
+ final boolean deleteWholeWords = forceDeleteWholeWords || (event.isKeyRepeat()
+ && inputTransaction.mSettingsValues.mBackspaceModeHold == Settings.BACKSPACE_MODE_WORDS);
+
+ if (deleteWholeWords && deleteWordAroundCursor && mWordComposer.isComposingWord()
+ && !mConnection.hasSelection()) {
+ mConnection.finishComposingText();
+ mWordComposer.reset(true);
+ }
if (mWordComposer.isComposingWord() && !mConnection.hasSelection()) {
if (mWordComposer.isBatchMode()) {
@@ -1436,9 +1557,29 @@ private void handleBackspaceEvent(final Event event, final InputTransaction inpu
Constants.EVENT_REJECTION);
}
StatsUtils.onBackspaceWordDelete(rejectedSuggestion.length());
+ } else if (deleteWholeWords && deleteWordAroundCursor) {
+ final BackspaceDeletionTarget target = getDeletionTargetForWordAtCursor(
+ inputTransaction.mSettingsValues, currentKeyboardScriptId);
+ if (target != null) {
+ mLastBackspaceDeletedText = target.mDeletedText;
+ unlearnWord(target.mDeletedText, inputTransaction.mSettingsValues,
+ Constants.EVENT_BACKSPACE);
+ deleteUsingTarget(target);
+ mWordComposer.reset(true);
+ StatsUtils.onBackspacePressed(target.getTotalCharsToDelete());
+ } else {
+ final String removedWord = mWordComposer.getTypedWord();
+ mWordComposer.reset(true);
+ mLastBackspaceDeletedText = removedWord;
+ if (!TextUtils.isEmpty(removedWord)) {
+ unlearnWord(removedWord, inputTransaction.mSettingsValues,
+ Constants.EVENT_BACKSPACE);
+ }
+ }
} else if(deleteWholeWords) {
final String removedWord = mWordComposer.getTypedWord();
mWordComposer.reset(true);
+ mLastBackspaceDeletedText = removedWord;
if (!TextUtils.isEmpty(removedWord)) {
unlearnWord(removedWord, inputTransaction.mSettingsValues,
Constants.EVENT_BACKSPACE);
@@ -1454,7 +1595,7 @@ private void handleBackspaceEvent(final Event event, final InputTransaction inpu
}
inputTransaction.setRequiresUpdateSuggestions();
} else {
- if (mLastComposedWord.canRevertCommit()
+ if (!deleteWordAroundCursor && mLastComposedWord.canRevertCommit()
&& inputTransaction.mSettingsValues.mBackspaceUndoesAutocorrect) {
final String lastComposedWord = mLastComposedWord.mTypedWord;
revertCommit(inputTransaction, inputTransaction.mSettingsValues);
@@ -1527,6 +1668,7 @@ private void handleBackspaceEvent(final Event event, final InputTransaction inpu
// We also need to unlearn the selected text.
final CharSequence selection = mConnection.getSelectedText(0 /* 0 for no styles */);
if (!TextUtils.isEmpty(selection)) {
+ mLastBackspaceDeletedText = selection.toString();
unlearnWord(selection.toString(), inputTransaction.mSettingsValues,
Constants.EVENT_BACKSPACE);
hasUnlearnedWordBeingDeleted = true;
@@ -1538,6 +1680,32 @@ private void handleBackspaceEvent(final Event event, final InputTransaction inpu
mConnection.deleteTextBeforeCursor(numCharsDeleted);
StatsUtils.onBackspaceSelectedText(numCharsDeleted);
} else {
+ if (deleteWordAroundCursor) {
+ final BackspaceDeletionTarget target = getDeletionTargetForWordAtCursor(
+ inputTransaction.mSettingsValues, currentKeyboardScriptId);
+ if (target != null) {
+ mLastBackspaceDeletedText = target.mDeletedText;
+ unlearnWord(target.mDeletedText, inputTransaction.mSettingsValues,
+ Constants.EVENT_BACKSPACE);
+ hasUnlearnedWordBeingDeleted = true;
+ deleteUsingTarget(target);
+ StatsUtils.onBackspacePressed(target.getTotalCharsToDelete());
+
+ if (mConnection.hasSlowInputConnection()) {
+ mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
+ } else if (inputTransaction.mSettingsValues.isSuggestionsEnabledPerUserSettings()
+ && inputTransaction.mSettingsValues.mSpacingAndPunctuations
+ .currentLanguageHasSpaces
+ && !mConnection.isCursorFollowedByWordCharacter(
+ inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
+ restartSuggestionsOnWordTouchedByCursor(
+ inputTransaction.mSettingsValues, inputTransaction,
+ false /* forStartInput */, currentKeyboardScriptId);
+ }
+ return;
+ }
+ }
+
// There is no selection, just delete one character.
if (inputTransaction.mSettingsValues.isBeforeJellyBean()
|| inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()
@@ -1585,36 +1753,12 @@ private void handleBackspaceEvent(final Event event, final InputTransaction inpu
nowHasWordCharacter = true;
}
- String textDeleted = new String(Character.toChars(codePointBeforeCursor));
- int lengthToDelete =
- Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
-
- // Handle emoji sequences (flags, etc)
- CharSequence textBeforeCursor = mConnection.getTextBeforeCursor(deleteWholeWords ? 48 : 8, 0);
- if (textBeforeCursor != null && textBeforeCursor.length() > 0) {
- BreakIterator breakIterator;
-
- if(deleteWholeWords) {
- breakIterator = BreakIterator.getWordInstance();
- } else {
- breakIterator = BreakIterator.getCharacterInstance();
- }
- breakIterator.setText(textBeforeCursor.toString());
- int end = breakIterator.last();
- int start = breakIterator.previous();
-
- if(deleteWholeWords && textBeforeCursor.subSequence(start, end).toString().equals(" ")) {
- start = breakIterator.previous();
- }
-
- if (start != BreakIterator.DONE) {
- lengthToDelete = end - start;
- textDeleted = textBeforeCursor.subSequence(start, end).toString();
- }
- }
+ final BackspaceDeletionTarget target = getDeletionTargetBeforeCursor(
+ deleteWholeWords);
+ mLastBackspaceDeletedText = target.mDeletedText;
- mConnection.deleteTextBeforeCursor(lengthToDelete);
- int totalDeletedLength = lengthToDelete;
+ deleteUsingTarget(target);
+ int totalDeletedLength = target.getTotalCharsToDelete();
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
// If this is an accelerated (i.e., double) deletion, then we need to
// consider unlearning here because we may have already reached
diff --git a/java/src/org/futo/inputmethod/latin/settings/Settings.java b/java/src/org/futo/inputmethod/latin/settings/Settings.java
index 055c166a6c..e53ee0d0a5 100644
--- a/java/src/org/futo/inputmethod/latin/settings/Settings.java
+++ b/java/src/org/futo/inputmethod/latin/settings/Settings.java
@@ -88,7 +88,11 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
"pref_key_preview_popup_dismiss_delay";
public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction";
- public static final String PREF_GESTURE_INPUT = "gesture_input";
+ public static final String PREF_GESTURE_INPUT_MODE = "pref_gesture_input_mode";
+ private static final String PREF_GESTURE_INPUT_LEGACY = "gesture_input";
+ public static final int GESTURE_INPUT_MODE_TYPING = 0;
+ public static final int GESTURE_INPUT_MODE_ACTIONS = 1;
+ public static final int GESTURE_INPUT_MODE_NONE = 2;
public static final String PREF_GESTURE_INPUT_SENSITIVITY = "gesture_input_sensitivity";
public static final String PREF_VIBRATION_DURATION_SETTINGS =
"pref_vibration_duration_settings";
@@ -304,10 +308,24 @@ public static boolean readFromBuildConfigIfGestureInputEnabled(final Resources r
return res.getBoolean(R.bool.config_gesture_input_enabled_by_build_config);
}
- public static boolean readGestureInputEnabled(final SharedPreferences prefs,
+ public static int readGestureInputMode(final SharedPreferences prefs,
final Resources res) {
- return readFromBuildConfigIfGestureInputEnabled(res)
- && prefs.getBoolean(PREF_GESTURE_INPUT, true);
+ if (prefs.contains(PREF_GESTURE_INPUT_MODE)) {
+ final int mode = prefs.getInt(PREF_GESTURE_INPUT_MODE, GESTURE_INPUT_MODE_TYPING);
+ if (mode == GESTURE_INPUT_MODE_TYPING
+ || mode == GESTURE_INPUT_MODE_ACTIONS
+ || mode == GESTURE_INPUT_MODE_NONE) {
+ return mode;
+ }
+ }
+
+ if (prefs.contains(PREF_GESTURE_INPUT_LEGACY)) {
+ return prefs.getBoolean(PREF_GESTURE_INPUT_LEGACY, true)
+ ? GESTURE_INPUT_MODE_TYPING
+ : GESTURE_INPUT_MODE_NONE;
+ }
+
+ return GESTURE_INPUT_MODE_TYPING;
}
public static boolean readFromBuildConfigIfToShowKeyPreviewPopupOption(final Resources res) {
diff --git a/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java b/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
index 30803dc90a..85b95aecda 100644
--- a/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
@@ -84,7 +84,9 @@ public class SettingsValues {
// Use bigrams to predict the next word when there is no input for it yet
public final boolean mBigramPredictionEnabled;
public final boolean mTransformerPredictionEnabled;
+ public final int mGestureInputMode;
public final boolean mGestureInputEnabled;
+ public final boolean mGestureActionsEnabled;
public final boolean mGestureInputSensitive;
public final boolean mGestureTrailEnabled;
public final boolean mGestureFloatingPreviewTextEnabled;
@@ -231,7 +233,13 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina
mAutoCorrectionThreshold = readAutoCorrectionThreshold(res,
autoCorrectionThresholdRawValue);
mPlausibilityThreshold = Settings.readPlausibilityThreshold(res);
- mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
+ mGestureInputMode = Settings.readGestureInputMode(prefs, res);
+ final boolean gestureInputAllowedByBuild =
+ Settings.readFromBuildConfigIfGestureInputEnabled(res);
+ mGestureInputEnabled = gestureInputAllowedByBuild
+ && mGestureInputMode == Settings.GESTURE_INPUT_MODE_TYPING;
+ mGestureActionsEnabled = gestureInputAllowedByBuild
+ && mGestureInputMode == Settings.GESTURE_INPUT_MODE_ACTIONS;
mGestureInputSensitive = prefs.getBoolean(Settings.PREF_GESTURE_INPUT_SENSITIVITY, false);
mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true);
mCloudSyncEnabled = prefs.getBoolean(LocalSettingsConstants.PREF_ENABLE_CLOUD_SYNC, false);
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/Components.kt b/java/src/org/futo/inputmethod/latin/uix/settings/Components.kt
index a87b44242b..036ec55724 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/Components.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/Components.kt
@@ -43,6 +43,8 @@ import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material.icons.filled.Send
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.Icon
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
@@ -127,7 +129,7 @@ fun ScreenTitle(title: String, showBack: Boolean = false, navController: NavHost
}
Text(title, style = Typography.Heading.Medium, modifier = Modifier
.align(CenterVertically)
- .padding(0.dp, 16.dp))
+ .padding(top = 16.dp, bottom = 10.dp))
}
}
@@ -140,7 +142,7 @@ fun ScreenTitleWithIcon(title: String, painter: Painter) {
Spacer(modifier = Modifier.width(18.dp))
Text(title, style = Typography.Heading.Medium, modifier = Modifier
.align(CenterVertically)
- .padding(0.dp, 16.dp))
+ .padding(top = 16.dp, bottom = 10.dp))
}
}
@@ -442,24 +444,31 @@ fun SettingToggleSharedPrefs(
@Composable
fun SettingRadio(
- title: String,
+ title: String? = null,
options: List,
optionNames: List,
setting: DataStoreItem,
+ optionSubtitles: List? = null,
+ compact: Boolean = false,
modifier: Modifier = Modifier,
hints: List<@Composable () -> Unit>? = null,
subcontent: List<@Composable () -> Unit>? = null,
) {
- ScreenTitle(title, showBack = false)
+ if (!title.isNullOrBlank()) {
+ ScreenTitle(title, showBack = false)
+ }
Column {
options.zip(optionNames).forEachIndexed { i, it ->
- SettingItem(title = it.second, onClick = { setting.setValue(it.first) }, icon = {
+ val subtitle = optionSubtitles?.getOrNull(i)
+ SettingItem(title = it.second, subtitle = subtitle, onClick = { setting.setValue(it.first) }, icon = {
RadioButton(selected = setting.value == it.first, onClick = null)
}, modifier = modifier.clearAndSetSemantics {
- this.text = AnnotatedString(it.second)
+ this.text = AnnotatedString(
+ if (subtitle.isNullOrBlank()) it.second else "${it.second}. $subtitle"
+ )
this.role = Role.RadioButton
this.selected = setting.value == it.first
- }, subcontent = subcontent?.getOrNull(i)) {
+ }, compact = compact, subcontent = subcontent?.getOrNull(i)) {
hints?.getOrNull(i)?.invoke()
}
}
@@ -885,7 +894,7 @@ fun DropDownPicker(
role = Role.DropdownList
}
) {
- if(selection != null) {
+ if (selection != null) {
Text(
text = getDisplayName(selection),
style = Typography.Body.Regular,
@@ -941,8 +950,7 @@ fun DropDownPicker(
MaterialTheme.colorScheme.onSurface
} else {
MaterialTheme.colorScheme.onSurfaceVariant
- },
- modifier = Modifier.align(Alignment.CenterStart)
+ }
)
}
}
@@ -1029,4 +1037,4 @@ fun PreviewPrimarySetting() {
"Enable",
dataStoreItem = DataStoreItem(false, { error("") })
)
-}
\ No newline at end of file
+}
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Swipe.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Swipe.kt
index 201fb0439b..98b06cca76 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Swipe.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Swipe.kt
@@ -43,17 +43,20 @@ import org.futo.inputmethod.latin.uix.LocalNavController
import org.futo.inputmethod.latin.uix.SettingsTextEdit
import org.futo.inputmethod.latin.uix.SuggestionSeparator
import org.futo.inputmethod.latin.uix.getSetting
+import org.futo.inputmethod.latin.uix.settings.DataStoreItem
import org.futo.inputmethod.latin.uix.settings.NavigationItem
import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle
import org.futo.inputmethod.latin.uix.settings.ScreenTitle
import org.futo.inputmethod.latin.uix.settings.ScrollableList
import org.futo.inputmethod.latin.uix.settings.SettingRadio
import org.futo.inputmethod.latin.uix.settings.SettingToggleDataStore
+import org.futo.inputmethod.latin.uix.settings.SettingToggleDataStoreItem
import org.futo.inputmethod.latin.uix.settings.SettingToggleRaw
import org.futo.inputmethod.latin.uix.settings.UserSetting
import org.futo.inputmethod.latin.uix.settings.UserSettingsMenu
import org.futo.inputmethod.latin.uix.settings.useDataStore
import org.futo.inputmethod.latin.uix.settings.useDataStoreValue
+import org.futo.inputmethod.latin.uix.settings.useSharedPrefsInt
import org.futo.inputmethod.latin.uix.settings.userSettingDecorationOnly
import org.futo.inputmethod.latin.uix.settings.userSettingNavigationItem
import org.futo.inputmethod.latin.uix.settings.userSettingToggleSharedPrefs
@@ -207,12 +210,31 @@ val SwipeMenu = UserSettingsMenu(
title = R.string.swipe_settings_title,
navPath = "swipe", registerNavPath = true,
settings = listOf(
- userSettingToggleSharedPrefs(
- title = R.string.swipe_settings_enable_swipe_typing,
+ UserSetting(
+ name = R.string.swipe_settings_enable_swipe_typing,
subtitle = R.string.swipe_settings_enable_swipe_typing_subtitle,
- key = Settings.PREF_GESTURE_INPUT,
- default = {true},
- ),
+ ) {
+ val gestureInputMode = useSharedPrefsInt(
+ Settings.PREF_GESTURE_INPUT_MODE,
+ Settings.GESTURE_INPUT_MODE_TYPING
+ )
+
+ SettingToggleDataStoreItem(
+ title = stringResource(R.string.swipe_settings_enable_swipe_typing),
+ subtitle = stringResource(R.string.swipe_settings_enable_swipe_typing_subtitle),
+ dataStoreItem = DataStoreItem(
+ gestureInputMode.value == Settings.GESTURE_INPUT_MODE_TYPING
+ ) { enabled ->
+ gestureInputMode.setValue(
+ if (enabled) {
+ Settings.GESTURE_INPUT_MODE_TYPING
+ } else {
+ Settings.GESTURE_INPUT_MODE_NONE
+ }
+ )
+ }
+ )
+ },
userSettingToggleSharedPrefs(
title = R.string.swipe_settings_sensitive_swipe,
@@ -291,4 +313,4 @@ val SwipeMenu = UserSettingsMenu(
}
},
)
-)
\ No newline at end of file
+)
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt
index 0218653c24..5783b27c2d 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt
@@ -852,6 +852,15 @@ val KeyboardSettingsMenu = UserSettingsMenu(
)
)
+val SwipeInputSettingsMenu = UserSettingsMenu(
+ title = R.string.swipe_input_settings_title,
+ navPath = "swipe", registerNavPath = true,
+ settings = listOf(
+ UserSetting(name = R.string.swipe_input_settings_title) {
+ SwipeAlphaModesSetting()
+ }
+ )
+)
val TypingSettingsMenu = UserSettingsMenu(
title = R.string.typing_settings_title,
navPath = "typing", registerNavPath = true,
@@ -1018,6 +1027,33 @@ val TypingSettingsMenu = UserSettingsMenu(
)
)
+@Composable
+private fun SwipeAlphaModesSetting() {
+ SettingRadio(
+ title = stringResource(R.string.swipe_input_settings_title),
+ options = listOf(
+ Settings.GESTURE_INPUT_MODE_TYPING,
+ Settings.GESTURE_INPUT_MODE_ACTIONS,
+ Settings.GESTURE_INPUT_MODE_NONE
+ ),
+ optionNames = listOf(
+ stringResource(R.string.swipe_input_settings_swipe),
+ stringResource(R.string.swipe_input_settings_swipe_actions_mode),
+ stringResource(R.string.swipe_input_settings_swipe_disabled)
+ ),
+ optionSubtitles = listOf(
+ stringResource(R.string.swipe_input_settings_swipe_subtitle),
+ stringResource(R.string.swipe_input_settings_swipe_actions_mode_subtitle),
+ stringResource(R.string.swipe_input_settings_swipe_disabled_subtitle)
+ ),
+ compact = true,
+ setting = useSharedPrefsInt(
+ key = Settings.PREF_GESTURE_INPUT_MODE,
+ default = Settings.GESTURE_INPUT_MODE_TYPING
+ )
+ )
+}
+
@Preview(showBackground = true)
@Composable
fun KeyboardAndTypingScreen(navController: NavHostController = rememberNavController()) {
@@ -1043,8 +1079,9 @@ fun KeyboardAndTypingScreen(navController: NavHostController = rememberNavContro
}
KeyboardSettingsMenu.render(showBack = false, showTitle = false)
+ SwipeInputSettingsMenu.render(showBack = false, showTitle = false)
TypingSettingsMenu.render(showBack = false)
BottomSpacer()
}
-}
\ No newline at end of file
+}