diff --git a/app/src/main/java/be/scri/helpers/KeyHandler.kt b/app/src/main/java/be/scri/helpers/KeyHandler.kt index 928e29790..33bc0fae4 100644 --- a/app/src/main/java/be/scri/helpers/KeyHandler.kt +++ b/app/src/main/java/be/scri/helpers/KeyHandler.kt @@ -20,9 +20,9 @@ import be.scri.services.GeneralKeyboardIME.ScribeState class KeyHandler( private val ime: GeneralKeyboardIME, ) { - private val suggestionHandler = SuggestionHandler(ime) + private val suggestionHandler = ime.suggestionHandler private val spaceKeyProcessor = SpaceKeyProcessor(ime, suggestionHandler) - private val autocompletionHandler = AutocompletionHandler(ime) + private val autocompletionHandler = ime.autocompletionHandler /** Tracks if the last key pressed was a space, used for "period on double space" logic. */ private var wasLastKeySpace: Boolean = false diff --git a/app/src/main/java/be/scri/helpers/ui/KeyboardUIManager.kt b/app/src/main/java/be/scri/helpers/ui/KeyboardUIManager.kt index 928a94523..225e69d37 100644 --- a/app/src/main/java/be/scri/helpers/ui/KeyboardUIManager.kt +++ b/app/src/main/java/be/scri/helpers/ui/KeyboardUIManager.kt @@ -62,6 +62,8 @@ class KeyboardUIManager( fun getKeyboardLayoutXML(): Int + fun getCurrentKeyboardLayoutXML(): Int + fun getCurrentEnterKeyType(): Int fun commitText(text: String) @@ -69,6 +71,8 @@ class KeyboardUIManager( fun onKeyboardActionListener(): KeyboardView.OnKeyboardActionListener fun processLinguisticSuggestions(word: String) + + fun isNumericKeyboardActive(): Boolean } var keyboardView: KeyboardView = binding.keyboardView @@ -159,7 +163,12 @@ class KeyboardUIManager( val isUserDarkMode = getIsDarkModeOrNot(context) when (currentState) { - ScribeState.IDLE -> setupIdleView(language, emojiAutoSuggestionEnabled, autoSuggestEmojis) + ScribeState.IDLE -> + setupIdleView( + language, + emojiAutoSuggestionEnabled, + autoSuggestEmojis, + ) ScribeState.SELECT_COMMAND -> setupSelectCommandView(language) ScribeState.INVALID -> setupInvalidView(language, invalidCommandSource) ScribeState.TRANSLATE -> { @@ -184,7 +193,7 @@ class KeyboardUIManager( emojiAutoSuggestionEnabled: Boolean, autoSuggestEmojis: MutableList?, ) { - binding.commandOptionsBar.visibility = View.VISIBLE + binding.commandOptionsBar.visibility = if (listener.isNumericKeyboardActive()) View.GONE else View.VISIBLE binding.toolbarBar.visibility = View.GONE val isUserDarkMode = getIsDarkModeOrNot(context) @@ -226,7 +235,11 @@ class KeyboardUIManager( binding.scribeKeyOptions.foreground = AppCompatResources.getDrawable(context, R.drawable.ic_scribe_icon_vector) - initializeKeyboard(listener.getKeyboardLayoutXML()) + val keyboardXml = listener.getCurrentKeyboardLayoutXML() + initializeKeyboard(keyboardXml) + if (keyboardXml == R.xml.keys_symbols) { + setupCurrencySymbol(language) + } updateButtonVisibility(ScribeState.IDLE, emojiAutoSuggestionEnabled, autoSuggestEmojis) updateEmojiSuggestion(ScribeState.IDLE, emojiAutoSuggestionEnabled, autoSuggestEmojis) @@ -239,7 +252,7 @@ class KeyboardUIManager( * (Translate, Conjugate, Plural). */ private fun setupSelectCommandView(language: String) { - binding.commandOptionsBar.visibility = View.VISIBLE + binding.commandOptionsBar.visibility = if (listener.isNumericKeyboardActive()) View.GONE else View.VISIBLE binding.toolbarBar.visibility = View.GONE val isUserDarkMode = getIsDarkModeOrNot(context) @@ -786,6 +799,8 @@ class KeyboardUIManager( * Disables all auto-suggestions and resets the suggestion buttons to their default, inactive state. */ fun disableAutoSuggest(language: String) { + val isNumericKeyboard = listener.getCurrentKeyboardLayoutXML() == R.xml.keys_numeric + binding.translateBtnRight.visibility = View.INVISIBLE binding.translateBtnLeft.visibility = View.INVISIBLE binding.translateBtn.visibility = View.VISIBLE @@ -801,9 +816,20 @@ class KeyboardUIManager( binding.translateBtn.background = null binding.translateBtn.setOnClickListener(createSuggestionClickListener(suggestion1)) - val suggestion2 = suggestions.getOrNull(1) ?: "" - binding.conjugateBtn.text = suggestion2 - binding.conjugateBtn.setOnClickListener(createSuggestionClickListener(suggestion2)) + if (isNumericKeyboard) { + binding.conjugateBtn.text = "" + binding.conjugateBtn.setOnClickListener(null) + binding.conjugateBtn.visibility = View.GONE + binding.separator2.visibility = View.GONE + binding.separator3.visibility = View.GONE + } else { + val suggestion2 = suggestions.getOrNull(1) ?: "" + binding.conjugateBtn.visibility = View.VISIBLE + binding.conjugateBtn.text = suggestion2 + binding.conjugateBtn.setOnClickListener(createSuggestionClickListener(suggestion2)) + binding.separator2.visibility = View.VISIBLE + binding.separator3.visibility = View.VISIBLE + } val suggestion3 = suggestions.getOrNull(2) ?: "" binding.pluralBtn.text = suggestion3 diff --git a/app/src/main/java/be/scri/services/EnglishKeyboardIME.kt b/app/src/main/java/be/scri/services/EnglishKeyboardIME.kt index 572d84bd9..8978d7232 100644 --- a/app/src/main/java/be/scri/services/EnglishKeyboardIME.kt +++ b/app/src/main/java/be/scri/services/EnglishKeyboardIME.kt @@ -32,7 +32,6 @@ class EnglishKeyboardIME : GeneralKeyboardIME("English") { override var inputTypeClass: Int = InputType.TYPE_CLASS_TEXT override var enterKeyType: Int = IME_ACTION_NONE override var switchToLetters: Boolean = false - override var hasTextBeforeCursor: Boolean = false private val keyHandler by lazy { KeyHandler(this) } diff --git a/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt b/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt index e59b56c3a..e8f0dbd34 100644 --- a/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt +++ b/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt @@ -121,8 +121,8 @@ abstract class GeneralKeyboardIME( private val shiftPermToggleSpeed: Int = DEFAULT_SHIFT_PERM_TOGGLE_SPEED private lateinit var dbManagers: DatabaseManagers - private lateinit var suggestionHandler: SuggestionHandler - private lateinit var autocompletionHandler: AutocompletionHandler + internal lateinit var suggestionHandler: SuggestionHandler + internal lateinit var autocompletionHandler: AutocompletionHandler private lateinit var autocompletionManager: AutocompletionDataManager private var dataContract: DataContract? = null @@ -144,6 +144,7 @@ abstract class GeneralKeyboardIME( var wordSuggestions: List? = null var checkIfPluralWord: Boolean = false private var currentEnterKeyType: Int? = null + private var isNumericKeyboardActive: Boolean = false internal var currentState: ScribeState = ScribeState.IDLE internal var invalidCommandSource: ScribeState = ScribeState.IDLE @@ -176,6 +177,22 @@ 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 + } } enum class ScribeState { IDLE, SELECT_COMMAND, TRANSLATE, CONJUGATE, PLURAL, SELECT_VERB_CONJUNCTION, INVALID, ALREADY_PLURAL } @@ -286,25 +303,16 @@ abstract class GeneralKeyboardIME( // This setter triggers the logic in the property override if not shadowed. hasTextBeforeCursor = currentInputConnection?.getTextBeforeCursor(1, 0)?.isNotEmpty() == true - val keyboardXml = - when (inputTypeClass) { - TYPE_CLASS_NUMBER, TYPE_CLASS_DATETIME, TYPE_CLASS_PHONE -> { - keyboardMode = keyboardSymbols - R.xml.keys_symbols - } - - else -> { - keyboardMode = keyboardLetters - getKeyboardLayoutXML() - } - } + isNumericKeyboardActive = shouldUseNumericKeyboard(attribute.inputType) + keyboardMode = if (isNumericKeyboardActive) keyboardSymbols else keyboardLetters + val keyboardXml = getKeyboardLayoutXMLForInputType(attribute.inputType, getKeyboardLayoutXML()) loadLanguageData() keyboard = KeyboardBase(this, keyboardXml, enterKeyType) keyboardView?.setKeyboard(keyboard!!) - if (keyboardXml == R.xml.keys_symbols) { + if (this::uiManager.isInitialized && keyboardXml == R.xml.keys_symbols) { uiManager.setupCurrencySymbol(language) } } @@ -334,7 +342,7 @@ abstract class GeneralKeyboardIME( banner.visibility = if (hasData) View.GONE else View.VISIBLE binding.commandOptionsBar.visibility = - if (hasData) View.VISIBLE else View.GONE + if (hasData && !isNumericKeyboardActive) View.VISIBLE else View.GONE val isDarkMode = getIsDarkModeOrNot(applicationContext) val bannerColor = if (isDarkMode) R.color.dark_tutorial_button_color else R.color.light_tutorial_button_color val bannerTextColor = if (isDarkMode) R.color.dark_button_outline_color else R.color.light_text_color @@ -727,6 +735,22 @@ abstract class GeneralKeyboardIME( override fun getCurrentEnterKeyType(): Int = enterKeyType + override fun isNumericKeyboardActive(): Boolean = isNumericKeyboardActive + + override fun getCurrentKeyboardLayoutXML(): Int = + when (keyboardMode) { + keyboardSymbols -> getPrimarySymbolKeyboardLayoutXML() + keyboardSymbolShift -> R.xml.keys_symbols_shift + else -> getKeyboardLayoutXML() + } + + private fun getPrimarySymbolKeyboardLayoutXML(): Int = + if (isNumericKeyboardActive) { + R.xml.keys_numeric + } else { + R.xml.keys_symbols + } + override fun onKeyboardActionListener(): KeyboardView.OnKeyboardActionListener = this override fun processLinguisticSuggestions(word: String) { @@ -1171,7 +1195,7 @@ abstract class GeneralKeyboardIME( R.xml.keys_symbols_shift } else { this.keyboardMode = keyboardSymbols - R.xml.keys_symbols + getPrimarySymbolKeyboardLayoutXML() } keyboard = KeyboardBase(this, keyboardXml, enterKeyType) keyboardView!!.setKeyboard(keyboard!!) @@ -1196,7 +1220,7 @@ abstract class GeneralKeyboardIME( val keyboardXml = if (keyboardMode == keyboardLetters) { this.keyboardMode = keyboardSymbols - R.xml.keys_symbols + getPrimarySymbolKeyboardLayoutXML() } else { this.keyboardMode = keyboardLetters getKeyboardLayoutXML() diff --git a/app/src/main/java/be/scri/services/ItalianKeyboardIME.kt b/app/src/main/java/be/scri/services/ItalianKeyboardIME.kt index d4da08ce8..26826de08 100644 --- a/app/src/main/java/be/scri/services/ItalianKeyboardIME.kt +++ b/app/src/main/java/be/scri/services/ItalianKeyboardIME.kt @@ -32,7 +32,6 @@ class ItalianKeyboardIME : GeneralKeyboardIME("Italian") { override var inputTypeClass: Int = InputType.TYPE_CLASS_TEXT override var enterKeyType: Int = IME_ACTION_NONE override var switchToLetters: Boolean = false - override var hasTextBeforeCursor: Boolean = false private val keyHandler by lazy { KeyHandler(this) } diff --git a/app/src/main/java/be/scri/services/RussianKeyboardIME.kt b/app/src/main/java/be/scri/services/RussianKeyboardIME.kt index 753982b88..afb59dbfb 100644 --- a/app/src/main/java/be/scri/services/RussianKeyboardIME.kt +++ b/app/src/main/java/be/scri/services/RussianKeyboardIME.kt @@ -32,7 +32,6 @@ class RussianKeyboardIME : GeneralKeyboardIME("Russian") { override var inputTypeClass: Int = InputType.TYPE_CLASS_TEXT override var enterKeyType: Int = IME_ACTION_NONE override var switchToLetters: Boolean = false - override var hasTextBeforeCursor: Boolean = false private val keyHandler by lazy { KeyHandler(this) } diff --git a/app/src/main/java/be/scri/services/SwedishKeyboardIME.kt b/app/src/main/java/be/scri/services/SwedishKeyboardIME.kt index 3404336ad..f423aae6e 100644 --- a/app/src/main/java/be/scri/services/SwedishKeyboardIME.kt +++ b/app/src/main/java/be/scri/services/SwedishKeyboardIME.kt @@ -42,7 +42,6 @@ class SwedishKeyboardIME : GeneralKeyboardIME("Swedish") { override var inputTypeClass: Int = InputType.TYPE_CLASS_TEXT override var enterKeyType: Int = IME_ACTION_NONE override var switchToLetters: Boolean = false - override var hasTextBeforeCursor: Boolean = false private val keyHandler by lazy { KeyHandler(this) } diff --git a/app/src/main/res/xml/keys_numeric.xml b/app/src/main/res/xml/keys_numeric.xml new file mode 100644 index 000000000..2d3ee570e --- /dev/null +++ b/app/src/main/res/xml/keys_numeric.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/test/kotlin/be/scri/helpers/ui/KeyboardUIManagerTest.kt b/app/src/test/kotlin/be/scri/helpers/ui/KeyboardUIManagerTest.kt index c5818c0a7..2e0c5088f 100644 --- a/app/src/test/kotlin/be/scri/helpers/ui/KeyboardUIManagerTest.kt +++ b/app/src/test/kotlin/be/scri/helpers/ui/KeyboardUIManagerTest.kt @@ -57,6 +57,7 @@ class KeyboardUIManagerTest { // Mock Listener listener = mockk(relaxed = true) every { listener.getKeyboardLayoutXML() } returns be.scri.R.xml.keys_letters_english + every { listener.getCurrentKeyboardLayoutXML() } returns be.scri.R.xml.keys_letters_english every { listener.onKeyboardActionListener() } returns mockk() // Init Manager diff --git a/app/src/test/kotlin/be/scri/services/GeneralKeyboardIMEInputTypeTest.kt b/app/src/test/kotlin/be/scri/services/GeneralKeyboardIMEInputTypeTest.kt new file mode 100644 index 000000000..797167697 --- /dev/null +++ b/app/src/test/kotlin/be/scri/services/GeneralKeyboardIMEInputTypeTest.kt @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package be.scri.services + +import android.text.InputType +import be.scri.R +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class GeneralKeyboardIMEInputTypeTest { + @Test + fun shouldUseNumericKeyboard_returnsTrueForNumberInputs() { + val inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + + assertTrue(GeneralKeyboardIME.shouldUseNumericKeyboard(inputType)) + } + + @Test + fun shouldUseNumericKeyboard_returnsTrueForDateTimeInputs() { + val inputType = InputType.TYPE_CLASS_DATETIME or InputType.TYPE_DATETIME_VARIATION_DATE + + assertTrue(GeneralKeyboardIME.shouldUseNumericKeyboard(inputType)) + } + + @Test + fun shouldUseNumericKeyboard_returnsTrueForPhoneInputs() { + assertTrue(GeneralKeyboardIME.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)) + } + + @Test + fun getKeyboardLayoutXMLForInputType_returnsNumericLayoutForNumberInputs() { + val inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + + assertEquals( + R.xml.keys_numeric, + GeneralKeyboardIME.getKeyboardLayoutXMLForInputType(inputType, R.xml.keys_letters_english), + ) + } + + @Test + fun getKeyboardLayoutXMLForInputType_returnsLetterLayoutForTextInputs() { + val inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + + assertEquals( + R.xml.keys_letters_english, + GeneralKeyboardIME.getKeyboardLayoutXMLForInputType(inputType, R.xml.keys_letters_english), + ) + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b94a71a3b..33fb9d5b9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Tue Jan 04 09:48:27 CET 2022 +#Sat May 09 08:20:13 PST 2026 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists