diff --git a/app/src/keyboards/java/be/scri/helpers/KeyboardStateManager.kt b/app/src/keyboards/java/be/scri/helpers/KeyboardStateManager.kt new file mode 100644 index 00000000..518f75a5 --- /dev/null +++ b/app/src/keyboards/java/be/scri/helpers/KeyboardStateManager.kt @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package be.scri.helpers + +import be.scri.models.ScribeState + +/** + * Manages the current state transitions and state flags for the Scribe keyboard command system. + */ +class KeyboardStateManager { + /** + * The active state of the keyboard command system. + */ + var currentState: ScribeState = ScribeState.IDLE + + /** + * Records the previous command source when an invalid state occurs. + */ + var invalidCommandSource: ScribeState = ScribeState.IDLE + + /** + * Checks if the keyboard is currently in the `IDLE` state. + */ + val isIdle: Boolean + get() = currentState == ScribeState.IDLE + + /** + * Checks if the keyboard is currently in the `SELECT_COMMAND` state. + */ + val isSelectCommand: Boolean + get() = currentState == ScribeState.SELECT_COMMAND + + /** + * Checks if a command bar mode (translate, conjugate, plural, etc.) is active. + */ + val isCommandBarActive: Boolean + get() = currentState != ScribeState.IDLE && currentState != ScribeState.SELECT_COMMAND + + /** + * Checks if the keyboard is currently in the `INVALID` state. + */ + val isInvalid: Boolean + get() = currentState == ScribeState.INVALID + + /** + * Checks if the keyboard is currently in the `ALREADY_PLURAL` state. + */ + val isAlreadyPlural: Boolean + get() = currentState == ScribeState.ALREADY_PLURAL + + /** + * Transitions the keyboard state to [newState]. + */ + fun moveToState(newState: ScribeState) { + currentState = newState + } + + /** + * Resets the keyboard state to `IDLE`. + */ + fun moveToIdle() { + currentState = ScribeState.IDLE + } + + /** + * Transitions the keyboard to the `INVALID` state and records the [source] state. + */ + fun setInvalidState(source: ScribeState) { + invalidCommandSource = source + currentState = ScribeState.INVALID + } + + /** + * Resets both [currentState] and [invalidCommandSource] to `IDLE`. + */ + fun reset() { + currentState = ScribeState.IDLE + invalidCommandSource = ScribeState.IDLE + } +} diff --git a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt index c474e552..7b34094e 100644 --- a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt +++ b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt @@ -51,6 +51,7 @@ import be.scri.helpers.EmojiUtils.insertEmoji import be.scri.helpers.FloatingKeyboardHandler import be.scri.helpers.KeyboardBase import be.scri.helpers.KeyboardLanguageMappingConstants +import be.scri.helpers.KeyboardStateManager import be.scri.helpers.LanguageMappingConstants.getLanguageAlias import be.scri.helpers.NativeSuggestionEngine import be.scri.helpers.PreferencesHelper @@ -187,8 +188,19 @@ abstract class GeneralKeyboardIME( private var currentEnterKeyType: Int? = null private var isNumericKeyboardActive: Boolean = false - internal var currentState: ScribeState = ScribeState.IDLE - internal var invalidCommandSource: ScribeState = ScribeState.IDLE + internal val stateManager = KeyboardStateManager() + + internal var currentState: ScribeState + get() = stateManager.currentState + set(value) { + stateManager.currentState = value + } + + internal var invalidCommandSource: ScribeState + get() = stateManager.invalidCommandSource + set(value) { + stateManager.invalidCommandSource = value + } // Properties used by BackspaceHandler, delegated to UI Manager. internal var currentCommandBarHint: String @@ -793,7 +805,7 @@ abstract class GeneralKeyboardIME( */ internal fun moveToIdleState() { clearSuggestionData() - currentState = ScribeState.IDLE + stateManager.moveToIdle() saveConjugateModeType("none") currentVerbForConjugation = null selectedConjugationSubCategory = null @@ -813,9 +825,9 @@ abstract class GeneralKeyboardIME( // MARK: KeyboardUIListener override fun onScribeKeyOptionsClicked() { - if (currentState == ScribeState.IDLE) { + if (stateManager.isIdle) { clearSuggestionData() - currentState = ScribeState.SELECT_COMMAND + stateManager.moveToState(ScribeState.SELECT_COMMAND) saveConjugateModeType("none") currentVerbForConjugation = null } else { @@ -829,20 +841,20 @@ abstract class GeneralKeyboardIME( } override fun onTranslateClicked() { - currentState = ScribeState.TRANSLATE + stateManager.moveToState(ScribeState.TRANSLATE) saveConjugateModeType("none") refreshUI() } override fun onConjugateClicked() { - if (currentState != ScribeState.SELECT_VERB_CONJUNCTION) { - currentState = ScribeState.CONJUGATE + if (stateManager.currentState != ScribeState.SELECT_VERB_CONJUNCTION) { + stateManager.moveToState(ScribeState.CONJUGATE) } refreshUI() } override fun onPluralClicked() { - currentState = ScribeState.PLURAL + stateManager.moveToState(ScribeState.PLURAL) saveConjugateModeType("none") if (isPluralCapitalized) keyboard?.mShiftState = SHIFT_ON_ONE_CHAR refreshUI() @@ -992,8 +1004,7 @@ abstract class GeneralKeyboardIME( } if (commandModeOutput.isEmpty()) { - invalidCommandSource = currentState - currentState = ScribeState.INVALID + stateManager.setInvalidState(currentState) refreshUI() } else { applyCommandOutput(commandModeOutput, inputConnection) @@ -1027,14 +1038,12 @@ abstract class GeneralKeyboardIME( conjugateLabels = dbManagers.conjugateDataManager.extractConjugateHeadings(dataContract, searchInput) - currentState = - if (conjugateOutput == null) { - invalidCommandSource = ScribeState.CONJUGATE - ScribeState.INVALID - } else { - saveConjugateModeType(language) - ScribeState.SELECT_VERB_CONJUNCTION - } + if (conjugateOutput == null) { + stateManager.setInvalidState(ScribeState.CONJUGATE) + } else { + saveConjugateModeType(language) + stateManager.moveToState(ScribeState.SELECT_VERB_CONJUNCTION) + } refreshUI() } diff --git a/app/src/testKeyboards/kotlin/be/scri/helpers/KeyboardStateManagerTest.kt b/app/src/testKeyboards/kotlin/be/scri/helpers/KeyboardStateManagerTest.kt new file mode 100644 index 00000000..b1b1a515 --- /dev/null +++ b/app/src/testKeyboards/kotlin/be/scri/helpers/KeyboardStateManagerTest.kt @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package be.scri.helpers + +import be.scri.models.ScribeState +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class KeyboardStateManagerTest { + private lateinit var stateManager: KeyboardStateManager + + @Before + fun setUp() { + stateManager = KeyboardStateManager() + } + + @Test + fun initialState_isIdle() { + assertEquals(ScribeState.IDLE, stateManager.currentState) + assertEquals(ScribeState.IDLE, stateManager.invalidCommandSource) + assertTrue(stateManager.isIdle) + assertFalse(stateManager.isSelectCommand) + assertFalse(stateManager.isCommandBarActive) + assertFalse(stateManager.isInvalid) + assertFalse(stateManager.isAlreadyPlural) + } + + @Test + fun moveToState_updatesCurrentState() { + stateManager.moveToState(ScribeState.TRANSLATE) + assertEquals(ScribeState.TRANSLATE, stateManager.currentState) + assertFalse(stateManager.isIdle) + assertTrue(stateManager.isCommandBarActive) + + stateManager.moveToState(ScribeState.SELECT_COMMAND) + assertEquals(ScribeState.SELECT_COMMAND, stateManager.currentState) + assertTrue(stateManager.isSelectCommand) + assertFalse(stateManager.isCommandBarActive) + } + + @Test + fun moveToIdle_resetsToIdle() { + stateManager.moveToState(ScribeState.CONJUGATE) + stateManager.moveToIdle() + assertEquals(ScribeState.IDLE, stateManager.currentState) + assertTrue(stateManager.isIdle) + } + + @Test + fun setInvalidState_setsInvalidStateAndRecordsSource() { + stateManager.moveToState(ScribeState.PLURAL) + stateManager.setInvalidState(ScribeState.PLURAL) + + assertEquals(ScribeState.INVALID, stateManager.currentState) + assertEquals(ScribeState.PLURAL, stateManager.invalidCommandSource) + assertTrue(stateManager.isInvalid) + } + + @Test + fun isAlreadyPlural_returnsTrueWhenStateIsAlreadyPlural() { + stateManager.moveToState(ScribeState.ALREADY_PLURAL) + assertTrue(stateManager.isAlreadyPlural) + } + + @Test + fun reset_resetsStateAndInvalidSourceToIdle() { + stateManager.setInvalidState(ScribeState.TRANSLATE) + stateManager.reset() + + assertEquals(ScribeState.IDLE, stateManager.currentState) + assertEquals(ScribeState.IDLE, stateManager.invalidCommandSource) + assertTrue(stateManager.isIdle) + } +}