From 35da63d0ca99ac08ff04ae1870c2ff343ec80987 Mon Sep 17 00:00:00 2001 From: prince-0408 <66916296+prince-0408@users.noreply.github.com> Date: Sun, 27 Sep 2026 03:26:33 +0530 Subject: [PATCH 1/2] refactor: extract KeyboardLayoutHandler from GeneralKeyboardIME (Part 15) - #426 --- CHANGELOG.md | 1 + .../be/scri/helpers/KeyboardLayoutHandler.kt | 136 ++++++++++++++++++ .../be/scri/services/GeneralKeyboardIME.kt | 78 +++------- .../scri/helpers/KeyboardLayoutHandlerTest.kt | 105 ++++++++++++++ .../GeneralKeyboardIMEInputTypeTest.kt | 25 ++-- 5 files changed, 277 insertions(+), 68 deletions(-) create mode 100644 app/src/keyboards/java/be/scri/helpers/KeyboardLayoutHandler.kt create mode 100644 app/src/testKeyboards/kotlin/be/scri/helpers/KeyboardLayoutHandlerTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aaa0324..3165e79b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,4 +104,5 @@ Emojis for the following are chosen based on [gitmoji](https://gitmoji.dev/). ### ♻️ Code Refactoring - Code quality improvements were continuously done to assure that the application is easy to maintain and meets Kotlin standards ([#426](https://github.com/scribe-org/Scribe-Android/issues/426)). +- `KeyboardLayoutHandler` was extracted from `GeneralKeyboardIME` to encapsulate layout XML resolution, symbol keyboard mapping, and width calculations ([#426](https://github.com/scribe-org/Scribe-Android/issues/426)). - `ShiftHandler` was extracted from `GeneralKeyboardIME` to encapsulate shift state machine toggling, double-tap caps lock lock timing, and keyboard layout mode switching ([#426](https://github.com/scribe-org/Scribe-Android/issues/426)). diff --git a/app/src/keyboards/java/be/scri/helpers/KeyboardLayoutHandler.kt b/app/src/keyboards/java/be/scri/helpers/KeyboardLayoutHandler.kt new file mode 100644 index 00000000..e69e49be --- /dev/null +++ b/app/src/keyboards/java/be/scri/helpers/KeyboardLayoutHandler.kt @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package be.scri.helpers + +import android.text.InputType.TYPE_CLASS_DATETIME +import android.text.InputType.TYPE_CLASS_NUMBER +import android.text.InputType.TYPE_CLASS_PHONE +import android.text.InputType.TYPE_MASK_CLASS +import be.scri.R +import be.scri.models.ScribeState +import be.scri.services.GeneralKeyboardIME + +private const val DATA_SIZE_2 = 2 +private const val DATA_SIZE_3 = 3 + +/** + * Encapsulates keyboard XML layout resolution, symbol layout mapping, + * keyboard width calculations, state-based layout XML selection, and view re-creation. + */ +class KeyboardLayoutHandler( + private val ime: GeneralKeyboardIME, +) { + /** + * Resolves the XML resource ID for the active keyboard layout. + * + * @return The XML layout resource ID. + */ + fun getCurrentKeyboardLayoutXML(): Int = + when (ime.keyboardMode) { + ime.keyboardSymbols -> getPrimarySymbolKeyboardLayoutXML() + ime.keyboardSymbolShift -> R.xml.keys_symbols_shift + else -> ime.getKeyboardLayoutXML() + } + + /** + * Resolves the primary symbol or numeric layout XML resource ID. + * + * @return The XML layout resource ID. + */ + fun getPrimarySymbolKeyboardLayoutXML(): Int = + if (ime.isNumericKeyboardActive) { + R.xml.keys_numeric + } else { + R.xml.keys_symbols + } + + /** + * Determines which keyboard layout XML to use based on the current [ScribeState]. + * + * @param state The current state of the Scribe keyboard. + * @param isSubsequentArea true if this is for a secondary conjugation view. + * @param dataSize The number of items to display, used to select an appropriate layout. + * @return The resource ID of the keyboard layout XML. + */ + fun getKeyboardLayoutForState( + state: ScribeState, + isSubsequentArea: Boolean = false, + dataSize: Int = 0, + ): Int = + when (state) { + ScribeState.SELECT_VERB_CONJUNCTION -> { + ime.saveConjugateModeType(ime.language) + if (!isSubsequentArea && dataSize == 0) { + ime.defaultConjugateLayoutXML + } else { + when (dataSize) { + DATA_SIZE_2 -> R.xml.conjugate_view_2x1 + DATA_SIZE_3 -> R.xml.conjugate_view_1x3 + else -> R.xml.conjugate_view_2x2 + } + } + } + + else -> { + ime.getKeyboardLayoutXML() + } + } + + /** + * Calculates the width of the keyboard container. + * + * @return The keyboard width in pixels. + */ + fun getKeyboardWidth(): Int = + if (ime.isFloatingMode) { + val density = ime.resources.displayMetrics.density + val screenWidth = ime.resources.displayMetrics.widthPixels + val floatWidth = (320f * density).toInt() + Math.min(floatWidth, (screenWidth * 0.85f).toInt()) + } else { + ime.resources.displayMetrics.widthPixels + } + + /** + * Re-instantiates the [KeyboardBase] and applies the updated shift state and layout. + */ + fun recreateKeyboard() { + if (!ime.isUiManagerInitialized) return + + val xmlId = getCurrentKeyboardLayoutXML() + val currentShiftState = ime.keyboard?.mShiftState ?: SHIFT_OFF + ime.keyboard = KeyboardBase(ime, xmlId, ime.enterKeyType, getKeyboardWidth()) + ime.keyboard?.setShifted(currentShiftState) + ime.keyboardView?.setKeyboard(ime.keyboard!!) + + if (xmlId == R.xml.keys_symbols) { + ime.uiManager.setupCurrencySymbol(ime.language) + } + ime.keyboardView?.invalidateAllKeys() + } + + companion object { + /** + * Returns true if the given [inputType] requires a numeric keyboard layout. + */ + fun shouldUseNumericKeyboard(inputType: Int): Boolean = + when (inputType and TYPE_MASK_CLASS) { + TYPE_CLASS_NUMBER, TYPE_CLASS_DATETIME, TYPE_CLASS_PHONE -> true + else -> false + } + + /** + * Returns the appropriate keyboard layout XML resource ID for the given [inputType]. + * Falls back to [letterKeyboardLayoutXML] when a numeric layout is not required. + */ + fun getKeyboardLayoutXMLForInputType( + inputType: Int, + letterKeyboardLayoutXML: Int, + ): Int = + if (shouldUseNumericKeyboard(inputType)) { + R.xml.keys_numeric + } else { + letterKeyboardLayoutXML + } + } +} diff --git a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt index ca86994d..a66e2463 100644 --- a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt +++ b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt @@ -10,9 +10,6 @@ import android.content.res.Resources import android.graphics.Rect import android.inputmethodservice.InputMethodService import android.text.InputType -import android.text.InputType.TYPE_CLASS_DATETIME -import android.text.InputType.TYPE_CLASS_NUMBER -import android.text.InputType.TYPE_CLASS_PHONE import android.text.InputType.TYPE_MASK_CLASS import android.view.View import android.view.inputmethod.EditorInfo @@ -37,6 +34,7 @@ import be.scri.helpers.KeyboardBase import be.scri.helpers.KeyboardDataHandler import be.scri.helpers.KeyboardIMEContext import be.scri.helpers.KeyboardLanguageMappingConstants +import be.scri.helpers.KeyboardLayoutHandler import be.scri.helpers.KeyboardStateManager import be.scri.helpers.LanguageMappingConstants.getLanguageAlias import be.scri.helpers.NativeSuggestionEngine @@ -159,6 +157,7 @@ abstract class GeneralKeyboardIME( override lateinit var autocompletionHandler: AutocompletionHandler internal lateinit var keyHandler: KeyHandler internal val floatingKeyboardHandler by lazy { FloatingKeyboardHandler(this) } + internal val layoutHandler by lazy { KeyboardLayoutHandler(this) } internal val commandHandler by lazy { CommandHandler(this) } internal val shiftHandler by lazy { ShiftHandler(this) } internal val suggestionUIHandler by lazy { SuggestionUIHandler(this) } @@ -273,22 +272,6 @@ abstract class GeneralKeyboardIME( internal const val MAX_TEXT_LENGTH = 1000 const val COMMIT_TEXT_CURSOR_POSITION = 1 internal const val CUSTOM_CURSOR = "│" // special tall cursor character - - internal fun shouldUseNumericKeyboard(inputType: Int): Boolean = - when (inputType and TYPE_MASK_CLASS) { - TYPE_CLASS_NUMBER, TYPE_CLASS_DATETIME, TYPE_CLASS_PHONE -> true - else -> false - } - - internal fun getKeyboardLayoutXMLForInputType( - inputType: Int, - letterKeyboardLayoutXML: Int, - ): Int = - if (shouldUseNumericKeyboard(inputType)) { - R.xml.keys_numeric - } else { - letterKeyboardLayoutXML - } } // MARK: Lifecycle Methods @@ -445,9 +428,9 @@ abstract class GeneralKeyboardIME( // This setter triggers the logic in the property override if not shadowed. hasTextBeforeCursor = currentInputConnection?.getTextBeforeCursor(1, 0)?.isNotEmpty() == true - isNumericKeyboardActive = shouldUseNumericKeyboard(editorInfo.inputType) + isNumericKeyboardActive = KeyboardLayoutHandler.shouldUseNumericKeyboard(editorInfo.inputType) keyboardMode = if (isNumericKeyboardActive) keyboardSymbols else keyboardLetters - val keyboardXml = getKeyboardLayoutXMLForInputType(editorInfo.inputType, getKeyboardLayoutXML()) + val keyboardXml = KeyboardLayoutHandler.getKeyboardLayoutXMLForInputType(editorInfo.inputType, getKeyboardLayoutXML()) loadLanguageData() @@ -765,20 +748,17 @@ abstract class GeneralKeyboardIME( override fun isNumericKeyboardActive(): Boolean = isNumericKeyboardActive - override fun getCurrentKeyboardLayoutXML(): Int = - when (keyboardMode) { - keyboardSymbols -> getPrimarySymbolKeyboardLayoutXML() - keyboardSymbolShift -> R.xml.keys_symbols_shift - else -> getKeyboardLayoutXML() - } - - internal fun getPrimarySymbolKeyboardLayoutXML(): Int = + /** + * Resolves the XML resource ID for the active keyboard layout. + * Delegated to [KeyboardLayoutHandler]. + */ + override fun getCurrentKeyboardLayoutXML(): Int = layoutHandler.getCurrentKeyboardLayoutXML() - if (isNumericKeyboardActive) { - R.xml.keys_numeric - } else { - R.xml.keys_symbols - } + /** + * Resolves the primary symbol or numeric layout XML resource ID. + * Delegated to [KeyboardLayoutHandler]. + */ + internal fun getPrimarySymbolKeyboardLayoutXML(): Int = layoutHandler.getPrimarySymbolKeyboardLayoutXML() override fun onKeyboardActionListener(): KeyboardView.OnKeyboardActionListener = this @@ -1215,6 +1195,12 @@ abstract class GeneralKeyboardIME( * Returns the subsequent dataset for conjugation sub-views. * Delegated to [ConjugationHandler]. */ + internal fun getKeyboardLayoutForState( + state: ScribeState, + isSubsequentArea: Boolean = false, + dataSize: Int = 0, + ): Int = layoutHandler.getKeyboardLayoutForState(state, isSubsequentArea, dataSize) + override fun returnSubsequentData(): List> = conjugationHandler.subsequentData /** @@ -1234,29 +1220,9 @@ abstract class GeneralKeyboardIME( // MARK: Floating Keyboard Integration - override fun getKeyboardWidth(): Int = - if (isFloatingMode) { - val density = resources.displayMetrics.density - val screenWidth = resources.displayMetrics.widthPixels - val floatWidth = (320f * density).toInt() - Math.min(floatWidth, (screenWidth * 0.85f).toInt()) - } else { - resources.displayMetrics.widthPixels - } + override fun getKeyboardWidth(): Int = layoutHandler.getKeyboardWidth() - override fun recreateKeyboard() { - if (!this::uiManager.isInitialized) return - val xmlId = getCurrentKeyboardLayoutXML() - val currentShiftState = keyboard?.mShiftState ?: SHIFT_OFF - keyboard = KeyboardBase(this, xmlId, enterKeyType, getKeyboardWidth()) - keyboard?.setShifted(currentShiftState) - keyboardView?.setKeyboard(keyboard!!) - - if (xmlId == R.xml.keys_symbols) { - uiManager.setupCurrencySymbol(language) - } - keyboardView?.invalidateAllKeys() - } + override fun recreateKeyboard() = layoutHandler.recreateKeyboard() val isFloatingMode: Boolean get() = floatingKeyboardHandler.isFloatingMode diff --git a/app/src/testKeyboards/kotlin/be/scri/helpers/KeyboardLayoutHandlerTest.kt b/app/src/testKeyboards/kotlin/be/scri/helpers/KeyboardLayoutHandlerTest.kt new file mode 100644 index 00000000..638bd97c --- /dev/null +++ b/app/src/testKeyboards/kotlin/be/scri/helpers/KeyboardLayoutHandlerTest.kt @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package be.scri.helpers + +import android.text.InputType.TYPE_CLASS_NUMBER +import android.text.InputType.TYPE_CLASS_TEXT +import be.scri.R +import be.scri.models.ScribeState +import be.scri.services.GeneralKeyboardIME +import io.mockk.every +import io.mockk.mockk +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test + +class KeyboardLayoutHandlerTest { + private lateinit var mockIme: GeneralKeyboardIME + private lateinit var layoutHandler: KeyboardLayoutHandler + + @Before + fun setUp() { + mockIme = mockk(relaxed = true) + layoutHandler = KeyboardLayoutHandler(mockIme) + } + + @Test + fun testGetPrimarySymbolKeyboardLayoutXML_numericActive() { + every { mockIme.isNumericKeyboardActive } returns true + val xmlResId = layoutHandler.getPrimarySymbolKeyboardLayoutXML() + assertEquals(R.xml.keys_numeric, xmlResId) + } + + @Test + fun testGetPrimarySymbolKeyboardLayoutXML_symbolsActive() { + every { mockIme.isNumericKeyboardActive } returns false + val xmlResId = layoutHandler.getPrimarySymbolKeyboardLayoutXML() + assertEquals(R.xml.keys_symbols, xmlResId) + } + + @Test + fun testGetCurrentKeyboardLayoutXML_letterMode() { + every { mockIme.keyboardMode } returns 0 + every { mockIme.keyboardSymbols } returns 1 + every { mockIme.keyboardSymbolShift } returns 2 + every { mockIme.getKeyboardLayoutXML() } returns R.xml.keys_symbols + + val xmlResId = layoutHandler.getCurrentKeyboardLayoutXML() + assertEquals(R.xml.keys_symbols, xmlResId) + } + + @Test + fun testGetCurrentKeyboardLayoutXML_symbolMode() { + every { mockIme.keyboardMode } returns 1 + every { mockIme.keyboardSymbols } returns 1 + every { mockIme.keyboardSymbolShift } returns 2 + every { mockIme.isNumericKeyboardActive } returns false + + val xmlResId = layoutHandler.getCurrentKeyboardLayoutXML() + assertEquals(R.xml.keys_symbols, xmlResId) + } + + @Test + fun testGetCurrentKeyboardLayoutXML_symbolShiftMode() { + every { mockIme.keyboardMode } returns 2 + every { mockIme.keyboardSymbols } returns 1 + every { mockIme.keyboardSymbolShift } returns 2 + + val xmlResId = layoutHandler.getCurrentKeyboardLayoutXML() + assertEquals(R.xml.keys_symbols_shift, xmlResId) + } + + @Test + fun testGetKeyboardLayoutForState_verbConjunction_dataSize2() { + every { mockIme.saveConjugateModeType(any()) } returns Unit + val xmlResId = layoutHandler.getKeyboardLayoutForState(ScribeState.SELECT_VERB_CONJUNCTION, isSubsequentArea = true, dataSize = 2) + assertEquals(R.xml.conjugate_view_2x1, xmlResId) + } + + @Test + fun testGetKeyboardLayoutForState_defaultState() { + every { mockIme.getKeyboardLayoutXML() } returns R.xml.keys_symbols + val xmlResId = layoutHandler.getKeyboardLayoutForState(ScribeState.IDLE) + assertEquals(R.xml.keys_symbols, xmlResId) + } + + @Test + fun testShouldUseNumericKeyboard() { + assertTrue(KeyboardLayoutHandler.shouldUseNumericKeyboard(TYPE_CLASS_NUMBER)) + assertFalse(KeyboardLayoutHandler.shouldUseNumericKeyboard(TYPE_CLASS_TEXT)) + } + + @Test + fun testGetKeyboardLayoutXMLForInputType() { + assertEquals( + R.xml.keys_numeric, + KeyboardLayoutHandler.getKeyboardLayoutXMLForInputType(TYPE_CLASS_NUMBER, R.xml.keys_symbols), + ) + assertEquals( + R.xml.keys_symbols, + KeyboardLayoutHandler.getKeyboardLayoutXMLForInputType(TYPE_CLASS_TEXT, R.xml.keys_symbols), + ) + } +} diff --git a/app/src/testKeyboards/kotlin/be/scri/services/GeneralKeyboardIMEInputTypeTest.kt b/app/src/testKeyboards/kotlin/be/scri/services/GeneralKeyboardIMEInputTypeTest.kt index 7ebbc920..418ed934 100644 --- a/app/src/testKeyboards/kotlin/be/scri/services/GeneralKeyboardIMEInputTypeTest.kt +++ b/app/src/testKeyboards/kotlin/be/scri/services/GeneralKeyboardIMEInputTypeTest.kt @@ -5,6 +5,7 @@ package be.scri.services import android.text.InputType import android.view.inputmethod.EditorInfo import be.scri.R +import be.scri.helpers.KeyboardLayoutHandler import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -15,45 +16,45 @@ class GeneralKeyboardIMEInputTypeTest { fun shouldUseNumericKeyboard_returnsTrueForNumberInputs() { val inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL - assertTrue(GeneralKeyboardIME.shouldUseNumericKeyboard(inputType)) + assertTrue(KeyboardLayoutHandler.shouldUseNumericKeyboard(inputType)) } @Test fun shouldUseNumericKeyboard_returnsTrueForDateTimeInputs() { val inputType = InputType.TYPE_CLASS_DATETIME or InputType.TYPE_DATETIME_VARIATION_DATE - assertTrue(GeneralKeyboardIME.shouldUseNumericKeyboard(inputType)) + assertTrue(KeyboardLayoutHandler.shouldUseNumericKeyboard(inputType)) } @Test fun shouldUseNumericKeyboard_returnsTrueForPhoneInputs() { - assertTrue(GeneralKeyboardIME.shouldUseNumericKeyboard(InputType.TYPE_CLASS_PHONE)) + assertTrue(KeyboardLayoutHandler.shouldUseNumericKeyboard(InputType.TYPE_CLASS_PHONE)) } @Test fun shouldUseNumericKeyboard_returnsFalseForTextInputs() { val inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS - assertFalse(GeneralKeyboardIME.shouldUseNumericKeyboard(inputType)) + assertFalse(KeyboardLayoutHandler.shouldUseNumericKeyboard(inputType)) } @Test - fun getKeyboardLayoutXMLForInputType_returnsNumericLayoutForNumberInputs() { - val inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + fun getKeyboardLayoutXMLForInputType_numeric_returnsNumericLayout() { + val inputType = InputType.TYPE_CLASS_NUMBER assertEquals( R.xml.keys_numeric, - GeneralKeyboardIME.getKeyboardLayoutXMLForInputType(inputType, R.xml.keys_letters_english), + KeyboardLayoutHandler.getKeyboardLayoutXMLForInputType(inputType, R.xml.keys_letters_english), ) } @Test - fun getKeyboardLayoutXMLForInputType_returnsLetterLayoutForTextInputs() { - val inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + fun getKeyboardLayoutXMLForInputType_text_returnsFallbackLayout() { + val inputType = InputType.TYPE_CLASS_TEXT assertEquals( R.xml.keys_letters_english, - GeneralKeyboardIME.getKeyboardLayoutXMLForInputType(inputType, R.xml.keys_letters_english), + KeyboardLayoutHandler.getKeyboardLayoutXMLForInputType(inputType, R.xml.keys_letters_english), ) } @@ -61,10 +62,10 @@ class GeneralKeyboardIMEInputTypeTest { fun defaultEditorInfo_fallsBackToLetterLayout() { val fallback = EditorInfo() - assertFalse(GeneralKeyboardIME.shouldUseNumericKeyboard(fallback.inputType)) + assertFalse(KeyboardLayoutHandler.shouldUseNumericKeyboard(fallback.inputType)) assertEquals( R.xml.keys_letters_english, - GeneralKeyboardIME.getKeyboardLayoutXMLForInputType(fallback.inputType, R.xml.keys_letters_english), + KeyboardLayoutHandler.getKeyboardLayoutXMLForInputType(fallback.inputType, R.xml.keys_letters_english), ) } } From e0d0c149be7d9fe7006f5652ac5df2d0ad5de111 Mon Sep 17 00:00:00 2001 From: prince-0408 <66916296+prince-0408@users.noreply.github.com> Date: Sun, 27 Sep 2026 03:27:06 +0530 Subject: [PATCH 2/2] refactor: address review comments on KeyboardLayoutHandler --- .../be/scri/helpers/KeyboardIMEContext.kt | 5 ++++ .../be/scri/helpers/KeyboardLayoutHandler.kt | 13 +++++---- .../be/scri/services/GeneralKeyboardIME.kt | 6 ++--- .../scri/helpers/KeyboardLayoutHandlerTest.kt | 27 ++++++++++--------- .../GeneralKeyboardIMEInputTypeTest.kt | 8 +++--- 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/app/src/keyboards/java/be/scri/helpers/KeyboardIMEContext.kt b/app/src/keyboards/java/be/scri/helpers/KeyboardIMEContext.kt index 31c97cc4..b9c2d7b2 100644 --- a/app/src/keyboards/java/be/scri/helpers/KeyboardIMEContext.kt +++ b/app/src/keyboards/java/be/scri/helpers/KeyboardIMEContext.kt @@ -51,6 +51,9 @@ interface KeyboardIMEContext { val keyboardSymbols: Int val keyboardSymbolShift: Int + val isFloatingMode: Boolean + val enterKeyType: Int + var lastShiftPressTS: Long val currentCommandBarHint: String @@ -79,6 +82,8 @@ interface KeyboardIMEContext { fun getKeyboardLayoutXML(): Int + fun isNumericKeyboardActive(): Boolean + fun handleDelete(isLongPress: Boolean = false) fun isDeleteRepeating(): Boolean diff --git a/app/src/keyboards/java/be/scri/helpers/KeyboardLayoutHandler.kt b/app/src/keyboards/java/be/scri/helpers/KeyboardLayoutHandler.kt index e69e49be..ec597b57 100644 --- a/app/src/keyboards/java/be/scri/helpers/KeyboardLayoutHandler.kt +++ b/app/src/keyboards/java/be/scri/helpers/KeyboardLayoutHandler.kt @@ -8,7 +8,6 @@ import android.text.InputType.TYPE_CLASS_PHONE import android.text.InputType.TYPE_MASK_CLASS import be.scri.R import be.scri.models.ScribeState -import be.scri.services.GeneralKeyboardIME private const val DATA_SIZE_2 = 2 private const val DATA_SIZE_3 = 3 @@ -18,7 +17,7 @@ private const val DATA_SIZE_3 = 3 * keyboard width calculations, state-based layout XML selection, and view re-creation. */ class KeyboardLayoutHandler( - private val ime: GeneralKeyboardIME, + private val ime: KeyboardIMEContext, ) { /** * Resolves the XML resource ID for the active keyboard layout. @@ -38,7 +37,7 @@ class KeyboardLayoutHandler( * @return The XML layout resource ID. */ fun getPrimarySymbolKeyboardLayoutXML(): Int = - if (ime.isNumericKeyboardActive) { + if (ime.isNumericKeyboardActive()) { R.xml.keys_numeric } else { R.xml.keys_symbols @@ -83,12 +82,12 @@ class KeyboardLayoutHandler( */ fun getKeyboardWidth(): Int = if (ime.isFloatingMode) { - val density = ime.resources.displayMetrics.density - val screenWidth = ime.resources.displayMetrics.widthPixels + val density = ime.getImeResources().displayMetrics.density + val screenWidth = ime.getImeResources().displayMetrics.widthPixels val floatWidth = (320f * density).toInt() Math.min(floatWidth, (screenWidth * 0.85f).toInt()) } else { - ime.resources.displayMetrics.widthPixels + ime.getImeResources().displayMetrics.widthPixels } /** @@ -99,7 +98,7 @@ class KeyboardLayoutHandler( val xmlId = getCurrentKeyboardLayoutXML() val currentShiftState = ime.keyboard?.mShiftState ?: SHIFT_OFF - ime.keyboard = KeyboardBase(ime, xmlId, ime.enterKeyType, getKeyboardWidth()) + ime.keyboard = KeyboardBase(ime.imeContext, xmlId, ime.enterKeyType, getKeyboardWidth()) ime.keyboard?.setShifted(currentShiftState) ime.keyboardView?.setKeyboard(ime.keyboard!!) diff --git a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt index a66e2463..15955d0a 100644 --- a/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt +++ b/app/src/keyboards/java/be/scri/services/GeneralKeyboardIME.kt @@ -98,7 +98,7 @@ abstract class GeneralKeyboardIME( abstract override var lastShiftPressTS: Long abstract override var keyboardMode: Int abstract var inputTypeClass: Int - abstract var enterKeyType: Int + abstract override var enterKeyType: Int abstract var switchToLetters: Boolean // Language-specific layout and behavior configurations (decoupled from base class). @@ -226,7 +226,7 @@ abstract class GeneralKeyboardIME( override var wordSuggestions: List? = null override var checkIfPluralWord: Boolean = false private var currentEnterKeyType: Int? = null - private var isNumericKeyboardActive: Boolean = false + internal var isNumericKeyboardActive: Boolean = false internal val stateManager = KeyboardStateManager() override val themeManager = KeyboardThemeManager() @@ -1224,7 +1224,7 @@ abstract class GeneralKeyboardIME( override fun recreateKeyboard() = layoutHandler.recreateKeyboard() - val isFloatingMode: Boolean + override val isFloatingMode: Boolean get() = floatingKeyboardHandler.isFloatingMode fun initFloatingMode() { diff --git a/app/src/testKeyboards/kotlin/be/scri/helpers/KeyboardLayoutHandlerTest.kt b/app/src/testKeyboards/kotlin/be/scri/helpers/KeyboardLayoutHandlerTest.kt index 638bd97c..df56c3c4 100644 --- a/app/src/testKeyboards/kotlin/be/scri/helpers/KeyboardLayoutHandlerTest.kt +++ b/app/src/testKeyboards/kotlin/be/scri/helpers/KeyboardLayoutHandlerTest.kt @@ -6,7 +6,6 @@ import android.text.InputType.TYPE_CLASS_NUMBER import android.text.InputType.TYPE_CLASS_TEXT import be.scri.R import be.scri.models.ScribeState -import be.scri.services.GeneralKeyboardIME import io.mockk.every import io.mockk.mockk import org.junit.Assert.assertEquals @@ -16,7 +15,7 @@ import org.junit.Before import org.junit.Test class KeyboardLayoutHandlerTest { - private lateinit var mockIme: GeneralKeyboardIME + private lateinit var mockIme: KeyboardIMEContext private lateinit var layoutHandler: KeyboardLayoutHandler @Before @@ -27,35 +26,39 @@ class KeyboardLayoutHandlerTest { @Test fun testGetPrimarySymbolKeyboardLayoutXML_numericActive() { - every { mockIme.isNumericKeyboardActive } returns true + every { mockIme.isNumericKeyboardActive() } returns true val xmlResId = layoutHandler.getPrimarySymbolKeyboardLayoutXML() assertEquals(R.xml.keys_numeric, xmlResId) } @Test fun testGetPrimarySymbolKeyboardLayoutXML_symbolsActive() { - every { mockIme.isNumericKeyboardActive } returns false + every { mockIme.isNumericKeyboardActive() } returns false val xmlResId = layoutHandler.getPrimarySymbolKeyboardLayoutXML() assertEquals(R.xml.keys_symbols, xmlResId) } @Test fun testGetCurrentKeyboardLayoutXML_letterMode() { + // keyboardMode does not match keyboardSymbols or keyboardSymbolShift, + // so the else branch delegates to getKeyboardLayoutXML(). + // Stub with a real letter layout to verify the correct branch is taken. every { mockIme.keyboardMode } returns 0 every { mockIme.keyboardSymbols } returns 1 every { mockIme.keyboardSymbolShift } returns 2 - every { mockIme.getKeyboardLayoutXML() } returns R.xml.keys_symbols + every { mockIme.getKeyboardLayoutXML() } returns R.xml.keys_letters_english val xmlResId = layoutHandler.getCurrentKeyboardLayoutXML() - assertEquals(R.xml.keys_symbols, xmlResId) + assertEquals(R.xml.keys_letters_english, xmlResId) } @Test fun testGetCurrentKeyboardLayoutXML_symbolMode() { + // keyboardMode matches keyboardSymbols, so symbols layout is returned. every { mockIme.keyboardMode } returns 1 every { mockIme.keyboardSymbols } returns 1 every { mockIme.keyboardSymbolShift } returns 2 - every { mockIme.isNumericKeyboardActive } returns false + every { mockIme.isNumericKeyboardActive() } returns false val xmlResId = layoutHandler.getCurrentKeyboardLayoutXML() assertEquals(R.xml.keys_symbols, xmlResId) @@ -80,9 +83,9 @@ class KeyboardLayoutHandlerTest { @Test fun testGetKeyboardLayoutForState_defaultState() { - every { mockIme.getKeyboardLayoutXML() } returns R.xml.keys_symbols + every { mockIme.getKeyboardLayoutXML() } returns R.xml.keys_letters_english val xmlResId = layoutHandler.getKeyboardLayoutForState(ScribeState.IDLE) - assertEquals(R.xml.keys_symbols, xmlResId) + assertEquals(R.xml.keys_letters_english, xmlResId) } @Test @@ -95,11 +98,11 @@ class KeyboardLayoutHandlerTest { fun testGetKeyboardLayoutXMLForInputType() { assertEquals( R.xml.keys_numeric, - KeyboardLayoutHandler.getKeyboardLayoutXMLForInputType(TYPE_CLASS_NUMBER, R.xml.keys_symbols), + KeyboardLayoutHandler.getKeyboardLayoutXMLForInputType(TYPE_CLASS_NUMBER, R.xml.keys_letters_english), ) assertEquals( - R.xml.keys_symbols, - KeyboardLayoutHandler.getKeyboardLayoutXMLForInputType(TYPE_CLASS_TEXT, R.xml.keys_symbols), + R.xml.keys_letters_english, + KeyboardLayoutHandler.getKeyboardLayoutXMLForInputType(TYPE_CLASS_TEXT, R.xml.keys_letters_english), ) } } diff --git a/app/src/testKeyboards/kotlin/be/scri/services/GeneralKeyboardIMEInputTypeTest.kt b/app/src/testKeyboards/kotlin/be/scri/services/GeneralKeyboardIMEInputTypeTest.kt index 418ed934..a7f96027 100644 --- a/app/src/testKeyboards/kotlin/be/scri/services/GeneralKeyboardIMEInputTypeTest.kt +++ b/app/src/testKeyboards/kotlin/be/scri/services/GeneralKeyboardIMEInputTypeTest.kt @@ -39,8 +39,8 @@ class GeneralKeyboardIMEInputTypeTest { } @Test - fun getKeyboardLayoutXMLForInputType_numeric_returnsNumericLayout() { - val inputType = InputType.TYPE_CLASS_NUMBER + fun getKeyboardLayoutXMLForInputType_returnsNumericLayoutForNumberInputs() { + val inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL assertEquals( R.xml.keys_numeric, @@ -49,8 +49,8 @@ class GeneralKeyboardIMEInputTypeTest { } @Test - fun getKeyboardLayoutXMLForInputType_text_returnsFallbackLayout() { - val inputType = InputType.TYPE_CLASS_TEXT + fun getKeyboardLayoutXMLForInputType_returnsLetterLayoutForTextInputs() { + val inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS assertEquals( R.xml.keys_letters_english,