-
Notifications
You must be signed in to change notification settings - Fork 121
refactor: extract KeyboardLayoutHandler from GeneralKeyboardIME (Part 15) - #426 #692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| // 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 { | ||
| 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 | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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.KeyEvent | ||
| import android.view.View | ||
|
|
@@ -40,6 +37,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 | ||
|
|
@@ -167,6 +165,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 var dataContract: DataContract? | ||
| get() = dataHandler.dataContract | ||
|
|
@@ -232,7 +231,7 @@ abstract class GeneralKeyboardIME( | |
| override var wordSuggestions: List<String>? = 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() | ||
| internal val themeManager = KeyboardThemeManager() | ||
|
|
@@ -280,22 +279,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 | ||
|
|
@@ -451,9 +434,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(attribute.inputType) | ||
| isNumericKeyboardActive = KeyboardLayoutHandler.shouldUseNumericKeyboard(attribute.inputType) | ||
| keyboardMode = if (isNumericKeyboardActive) keyboardSymbols else keyboardLetters | ||
| val keyboardXml = getKeyboardLayoutXMLForInputType(attribute.inputType, getKeyboardLayoutXML()) | ||
| val keyboardXml = KeyboardLayoutHandler.getKeyboardLayoutXMLForInputType(attribute.inputType, getKeyboardLayoutXML()) | ||
|
|
||
| loadLanguageData() | ||
|
|
||
|
|
@@ -784,19 +767,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() | ||
| } | ||
| /** | ||
| * Resolves the XML resource ID for the active keyboard layout. | ||
| * Delegated to [KeyboardLayoutHandler]. | ||
| */ | ||
| override fun getCurrentKeyboardLayoutXML(): Int = layoutHandler.getCurrentKeyboardLayoutXML() | ||
|
|
||
| private fun getPrimarySymbolKeyboardLayoutXML(): Int = | ||
| 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 | ||
|
|
||
|
|
@@ -1969,29 +1950,11 @@ abstract class GeneralKeyboardIME( | |
| * | ||
| * @return The resource ID of the keyboard layout XML. | ||
| */ | ||
| private fun getKeyboardLayoutForState( | ||
| internal fun getKeyboardLayoutForState( | ||
| state: ScribeState, | ||
| isSubsequentArea: Boolean = false, | ||
| dataSize: Int = 0, | ||
| ): Int = | ||
| when (state) { | ||
| ScribeState.SELECT_VERB_CONJUNCTION -> { | ||
| saveConjugateModeType(language) | ||
| if (!isSubsequentArea && dataSize == 0) { | ||
| defaultConjugateLayoutXML | ||
| } else { | ||
| when (dataSize) { | ||
| DATA_SIZE_2 -> R.xml.conjugate_view_2x1 | ||
| DATA_CONSTANT_3 -> R.xml.conjugate_view_1x3 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Naming nit , DATA_CONSTANT_3 should be DATA_SIZE_3 to match DATA_SIZE_2 wherever data_constant is mentioned
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated |
||
| else -> R.xml.conjugate_view_2x2 | ||
| } | ||
| } | ||
| } | ||
|
|
||
| else -> { | ||
| getKeyboardLayoutXML() | ||
| } | ||
| } | ||
| ): Int = layoutHandler.getKeyboardLayoutForState(state, isSubsequentArea, dataSize) | ||
|
|
||
| /** | ||
| * Updates the visibility of the suggestion buttons based on device type (phone/tablet) | ||
|
|
@@ -2010,29 +1973,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 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!!) | ||
| override fun getKeyboardWidth(): Int = layoutHandler.getKeyboardWidth() | ||
|
|
||
| if (xmlId == R.xml.keys_symbols) { | ||
| uiManager.setupCurrencySymbol(language) | ||
| } | ||
| keyboardView?.invalidateAllKeys() | ||
| } | ||
| override fun recreateKeyboard() = layoutHandler.recreateKeyboard() | ||
|
|
||
| val isFloatingMode: Boolean | ||
| get() = floatingKeyboardHandler.isFloatingMode | ||
|
|
@@ -2077,12 +2020,3 @@ abstract class GeneralKeyboardIME( | |
| clipboardHandler.closeClipboardPanel() | ||
| } | ||
| } | ||
|
|
||
| private fun Float.coerceInSafe( | ||
| bound1: Float, | ||
| bound2: Float, | ||
| ): Float { | ||
| val minVal = if (bound1 < bound2) bound1 else bound2 | ||
| val maxVal = if (bound1 > bound2) bound1 else bound2 | ||
| return this.coerceIn(minVal, maxVal) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Circular import: KeyboardLayoutHandler (helpers) and GeneralKeyboardIME (services) import each other, the handler isn't truly decoupled, it just holds a full ime reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. In this PR (Part 15),
KeyboardLayoutHandlerfollowed the same structure as previous helper extractions. In the dedicated follow-up #693 (Part 16), we introduce theKeyboardIMEContextinterface contract across all handlers, removing theGeneralKeyboardIMEreference fromKeyboardLayoutHandlerand breaking the circular import across the codebase.