From 8506d4ea600eda750c4603b7af081628f1329485 Mon Sep 17 00:00:00 2001 From: LJSigersmith Date: Mon, 20 Apr 2026 12:58:04 -0400 Subject: [PATCH 01/12] Toggle emoji colon mode on/off, process only emoji suggestions when in mode --- .../main/java/be/scri/helpers/KeyHandler.kt | 60 ++++++++++++++++--- .../main/res/xml/network_security_config.xml | 0 2 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 app/src/main/res/xml/network_security_config.xml diff --git a/app/src/main/java/be/scri/helpers/KeyHandler.kt b/app/src/main/java/be/scri/helpers/KeyHandler.kt index 928e29790..b869f5c6b 100644 --- a/app/src/main/java/be/scri/helpers/KeyHandler.kt +++ b/app/src/main/java/be/scri/helpers/KeyHandler.kt @@ -51,7 +51,7 @@ class KeyHandler( resetShiftIfNeeded(code) val previousWasLastKeySpace = wasLastKeySpace - if (code != KeyboardBase.KEYCODE_SPACE) { + if (code != KeyboardBase.KEYCODE_SPACE && !ime.emojiColonModeOn) { // None to clear in emoji colon mode, causes unnecessary flash when called suggestionHandler.clearLinguisticSuggestions() } @@ -134,6 +134,7 @@ class KeyHandler( /** * Handles the space key press and returns whether to reset wasLastKeySpace at the end. + * Switches emoji colon mode off, if on. * * @param previousWasLastKeySpace The previous state of wasLastKeySpace. * @@ -141,6 +142,11 @@ class KeyHandler( */ private fun handleSpaceKeyPress(previousWasLastKeySpace: Boolean): Boolean { wasLastKeySpace = spaceKeyProcessor.processKeycodeSpace(previousWasLastKeySpace) + // If we're suggesting emojis in colon mode, stop. Space should break emoji suggestions, and return to normal + if (ime.emojiColonModeOn) { + ime.emojiColonModeOn = false + ime.clearAutocomplete() + } return false } @@ -198,15 +204,27 @@ class KeyHandler( /** * Handles the delete/backspace key press. It delegates the deletion logic to the IME * and then triggers a re-evaluation of word suggestions based on the new text. + * Turns off emoji colon mode if colon is deleted that triggered it. */ private fun handleDeleteKey() { + val charToDelete = ime.currentInputConnection?.getTextBeforeCursor(1,0) ime.handleDelete(ime.isDeleteRepeating()) // pass the actual repeating status if (ime.currentState == ScribeState.IDLE) { + val deletedChar = charToDelete?.takeIf { it.isNotEmpty() }?.last() + if (deletedChar == ':' && ime.emojiColonModeOn) { + ime.emojiColonModeOn = false + ime.clearAutocomplete() + } + val currentWord = ime.getLastWordBeforeCursor() - autocompletionHandler.processAutocomplete(currentWord) - suggestionHandler.processEmojiSuggestions(currentWord) + if (ime.emojiColonModeOn) { + suggestionHandler.processEmojiSuggestions(currentWord) + } else { + autocompletionHandler.processAutocomplete(currentWord) + suggestionHandler.processEmojiSuggestions(currentWord) + } } } @@ -243,10 +261,15 @@ class KeyHandler( /** * Handles the mode change key press (e.g., switching to the symbol keyboard). * It delegates the logic to the IME and clears any active suggestions. + * In emoji colon mode, restore emoji suggestions from before mode change. */ private fun handleModeChangeKey() { ime.handleModeChange(ime.keyboardMode, ime.keyboardView, ime) - suggestionHandler.clearAllSuggestionsAndHideButtonUI() + if (ime.emojiColonModeOn) { + suggestionHandler.processEmojiSuggestions(ime.getLastWordBeforeCursor()) + } else { + suggestionHandler.clearAllSuggestionsAndHideButtonUI() + } } /** @@ -341,7 +364,7 @@ class KeyHandler( /** * Handles default key presses (regular characters, numbers, symbols). * Commits the character to the input connection and processes suggestions. - * + * Toggles colon emoji mode on if ':' typed. * @param code The key code representing the character to input. */ @@ -358,9 +381,32 @@ class KeyHandler( ime.handleElseCondition(code, ime.keyboardMode, isCommandBarActive) if (ime.currentState == ScribeState.IDLE) { + + if (code == ':'.code && ime.getLastWordBeforeCursor() == ":") { // " :" triggers emoji colon mode + ime.emojiColonModeOn = true + + val commonEmojis = EmojiUtils.COMMON_EMOJIS.toMutableList() + ime.autoSuggestEmojis = commonEmojis + ime.updateButtonVisibility(true) + ime.updateEmojiSuggestion(true, commonEmojis) // Show common emojis otherwise there's a small delay where words pop up + } + val currentWord = ime.getLastWordBeforeCursor() - autocompletionHandler.processAutocomplete(currentWord) - suggestionHandler.processEmojiSuggestions(currentWord) + if (ime.emojiColonModeOn) { + currentWord?.startsWith(":")?.let { + if (!it) { // Turn emoji colon mode off if there's no colon anymore (like if an emoji was just selected) + ime.emojiColonModeOn = false + ime.clearAutocomplete() + autocompletionHandler.processAutocomplete(currentWord) + suggestionHandler.processEmojiSuggestions(currentWord) + } else { + suggestionHandler.processEmojiSuggestions(currentWord) + } + } + } else { // Normal suggestions + autocompletionHandler.processAutocomplete(currentWord) + suggestionHandler.processEmojiSuggestions(currentWord) + } } else if (isCommandBarActive) { suggestionHandler.clearAllSuggestionsAndHideButtonUI() autocompletionHandler.clearAutocomplete() diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 000000000..e69de29bb From 5ac7d18b10a123ca665b90693770e79368776314 Mon Sep 17 00:00:00 2001 From: LJSigersmith Date: Mon, 20 Apr 2026 13:05:12 -0400 Subject: [PATCH 02/12] Display common emoji suggestions initially in emoji colon mode, otherwise find emojis by prefix. Show blank slots when no matches instead of words. --- .../java/be/scri/helpers/SuggestionHandler.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/be/scri/helpers/SuggestionHandler.kt b/app/src/main/java/be/scri/helpers/SuggestionHandler.kt index 57b44deed..60bb66426 100644 --- a/app/src/main/java/be/scri/helpers/SuggestionHandler.kt +++ b/app/src/main/java/be/scri/helpers/SuggestionHandler.kt @@ -4,6 +4,7 @@ package be.scri.helpers import android.os.Handler import android.os.Looper +import android.util.Log import be.scri.services.GeneralKeyboardIME import be.scri.services.GeneralKeyboardIME.ScribeState @@ -116,7 +117,6 @@ class SuggestionHandler( */ fun processEmojiSuggestions(currentWord: String?) { emojiSuggestionRunnable?.let { handler.removeCallbacks(it) } - emojiSuggestionRunnable = Runnable { if (ime.currentState != ScribeState.IDLE) { @@ -131,19 +131,27 @@ class SuggestionHandler( return@Runnable } - val emojis = - if (ime.emojiAutoSuggestionEnabled) { - ime.findEmojisForLastWord(ime.emojiKeywords, currentWord) + var emojis: MutableList? + if (ime.emojiColonModeOn) { + val currentWordCleaned = currentWord.removePrefix(":") // Drop colon that triggered emojiColonMode + emojis = if (currentWordCleaned.isEmpty()) { // For no word typed yet, show common emojis + EmojiUtils.COMMON_EMOJIS.toMutableList() } else { - null + ime.findEmojisForPrefix(ime.emojiKeywords, currentWordCleaned) } + } else { + emojis = null + } val hasEmojiSuggestion = !emojis.isNullOrEmpty() if (hasEmojiSuggestion) { ime.autoSuggestEmojis = emojis - ime.updateEmojiSuggestion(true, emojis) ime.updateButtonVisibility(true) + ime.updateEmojiSuggestion(true, emojis) + } else if (ime.emojiColonModeOn) { // Show blank buttons when there are no matches + ime.autoSuggestEmojis = mutableListOf() + ime.updateEmojiSuggestion(true, mutableListOf()) } else { ime.updateButtonVisibility(false) } @@ -172,6 +180,7 @@ class SuggestionHandler( fun clearAllSuggestionsAndHideButtonUI() { emojiSuggestionRunnable?.let { handler.removeCallbacks(it) } linguisticSuggestionRunnable?.let { handler.removeCallbacks(it) } + wordSuggestionRunnable?.let { handler.removeCallbacks(it) } ime.disableAutoSuggest() From d19e198041f0601aa2b2d43c7437174af5790078 Mon Sep 17 00:00:00 2001 From: LJSigersmith Date: Mon, 20 Apr 2026 13:10:35 -0400 Subject: [PATCH 03/12] Added emoji finding by keyword prefix. Handle emoji suggestions on state changes. --- .../be/scri/services/GeneralKeyboardIME.kt | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt b/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt index 50b791af4..04d6a478b 100644 --- a/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt +++ b/app/src/main/java/be/scri/services/GeneralKeyboardIME.kt @@ -141,6 +141,7 @@ abstract class GeneralKeyboardIME( var wordSuggestions: List? = null var checkIfPluralWord: Boolean = false private var currentEnterKeyType: Int? = null + var emojiColonModeOn: Boolean = false internal var currentState: ScribeState = ScribeState.IDLE internal var invalidCommandSource: ScribeState = ScribeState.IDLE @@ -320,6 +321,7 @@ abstract class GeneralKeyboardIME( emojiAutoSuggestionEnabled = getIsEmojiSuggestionsEnabled(applicationContext, language) autoSuggestEmojis = null suggestionHandler.clearAllSuggestionsAndHideButtonUI() + emojiColonModeOn = false moveToIdleState() @@ -626,6 +628,11 @@ abstract class GeneralKeyboardIME( currentVerbForConjugation = null } else { moveToIdleState() + // If the user just closed the command menu without selecting anything, + // and emoji colon mode was active, restore emoji suggestions. + if (emojiColonModeOn) { + suggestionHandler.processEmojiSuggestions(getLastWordBeforeCursor()) + } } refreshUI() } @@ -654,11 +661,16 @@ abstract class GeneralKeyboardIME( override fun onCloseClicked() { moveToIdleState() + // If the user just closed the command view without doing anything, + // and emoji colon mode was active, restore emoji suggestions. + if (emojiColonModeOn) { + suggestionHandler.processEmojiSuggestions(getLastWordBeforeCursor()) + } } override fun onEmojiSelected(emoji: String) { if (emoji.isNotEmpty()) { - insertEmoji(emoji, currentInputConnection, emojiKeywords, emojiMaxKeywordLength) + insertEmoji(emoji, currentInputConnection, emojiKeywords, emojiMaxKeywordLength, emojiColonModeOn) } } @@ -1178,6 +1190,27 @@ abstract class GeneralKeyboardIME( lastWord: String?, ) = lastWord?.let { emojiKeywords?.get(it.lowercase()) } + /** + * Finds associated emojis for the last typed word by matching prefixes of keywords. + * i.e. 'cheer' should match 'cheerful' + * + * @param emojiKeywords The map of keywords to emojis. + * @param prefix The word to look up. + * + * @return A mutable list of emoji suggestions, or null if none are found. + */ + fun findEmojisForPrefix( + emojiKeywords: HashMap>?, + prefix: String, + ): MutableList? = + emojiKeywords + ?.filterKeys { it.startsWith(prefix.lowercase(Locale.ROOT)) } + ?.values + ?.flatten() + ?.distinct() + ?.toMutableList() + ?.takeIf { it.isNotEmpty() } + /** * Finds the grammatical gender(s) for the last typed word. * @@ -1808,7 +1841,7 @@ abstract class GeneralKeyboardIME( fun updateEmojiSuggestion( enabled: Boolean, emojis: MutableList?, - ) = uiManager.updateEmojiSuggestion(currentState, enabled, emojis) + ) = uiManager.updateEmojiSuggestion(currentState, enabled, emojis, emojiColonModeOn) /** * Disables all auto-suggestions and resets the suggestion buttons to their default, inactive state. From 8dde6a34a10acecea2d476a409908c302e8a24bb Mon Sep 17 00:00:00 2001 From: LJSigersmith Date: Mon, 20 Apr 2026 13:12:18 -0400 Subject: [PATCH 04/12] Common emojis for no suggestions yet. Inserting emoji suggestion for emoji colon mode --- app/src/main/java/be/scri/helpers/EmojiUtils.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/java/be/scri/helpers/EmojiUtils.kt b/app/src/main/java/be/scri/helpers/EmojiUtils.kt index 8b333b018..8bb697e8a 100644 --- a/app/src/main/java/be/scri/helpers/EmojiUtils.kt +++ b/app/src/main/java/be/scri/helpers/EmojiUtils.kt @@ -10,6 +10,8 @@ import java.util.Locale object EmojiUtils { private const val DATA_SIZE_2 = 2 + val COMMON_EMOJIS = listOf("😀", "❤️", "👍", "😂", "🎉", "✨", "🔥", "👋", "😊") + /** * Checks if the end of a string is likely an emoji. * This is a heuristic check based on common emoji Unicode ranges. @@ -48,11 +50,22 @@ object EmojiUtils { ic: InputConnection, emojiKeywords: HashMap>?, emojiMaxKeywordLength: Int, + emojiColonModeOn: Boolean ) { val maxLookBack = emojiMaxKeywordLength.coerceAtLeast(1) ic.beginBatchEdit() try { val prevText = ic.getTextBeforeCursor(maxLookBack, 0)?.toString() ?: "" + // If emoji colon suggestion is on, look back to the : that triggered emoji suggestions + // Delete that text, and add the emoji + if (emojiColonModeOn) { + val colonIndex = prevText.lastIndexOf(':') + if (colonIndex != -1) { + ic.deleteSurroundingText(prevText.length - colonIndex, 0) + } + ic.commitText(emoji, 1) + return + } val lastSpace = prevText.lastIndexOf(' ') when { prevText.isEmpty() || From 024cf1513ad968a9f11a506457b2cdcedbb6424f Mon Sep 17 00:00:00 2001 From: LJSigersmith Date: Mon, 20 Apr 2026 13:16:19 -0400 Subject: [PATCH 05/12] update emoji suggestions in emoji colon mode for phone & tablet (6 phone, 9 tablet) --- .../be/scri/helpers/ui/KeyboardUIManager.kt | 107 +++++++++++++++--- 1 file changed, 91 insertions(+), 16 deletions(-) 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..bc4a35d8d 100644 --- a/app/src/main/java/be/scri/helpers/ui/KeyboardUIManager.kt +++ b/app/src/main/java/be/scri/helpers/ui/KeyboardUIManager.kt @@ -87,6 +87,33 @@ class KeyboardUIManager( var genderSuggestionLeft: Button? = binding.translateBtnLeft var genderSuggestionRight: Button? = binding.translateBtnRight + // 6-slot phone colon emoji row buttons + private val emojiColonPhoneButtons: List