Skip to content

Commit 42abf7e

Browse files
committed
emojis on colon
1 parent 486754e commit 42abf7e

3 files changed

Lines changed: 352 additions & 7 deletions

File tree

app/src/main/java/be/scri/helpers/KeyHandler.kt

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class KeyHandler(
4646
* @param code the key code that was pressed.
4747
*/
4848
private fun updateKeyboardState(code: Int) {
49+
ime.handleColonEmojiMode(code)
4950
ime.lastWord = ime.getLastWordBeforeCursor()
5051
Log.d("Debug", "${ime.lastWord}")
5152
ime.autoSuggestEmojis = ime.findEmojisForLastWord(ime.emojiKeywords, ime.lastWord)
@@ -54,7 +55,9 @@ class KeyHandler(
5455
Log.i("MY-TAG", "${ime.checkIfPluralWord}")
5556
Log.d("Debug", "${ime.autoSuggestEmojis}")
5657
Log.d("MY-TAG", "${ime.nounTypeSuggestion}")
57-
ime.updateButtonText(ime.emojiAutoSuggestionEnabled, ime.autoSuggestEmojis)
58+
if (!ime.isColonEmojiModeEnabled()) {
59+
ime.updateButtonText(ime.emojiAutoSuggestionEnabled, ime.autoSuggestEmojis)
60+
}
5861
if (code != KeyboardBase.KEYCODE_SHIFT) {
5962
ime.updateShiftKeyState()
6063
}
@@ -88,7 +91,9 @@ class KeyHandler(
8891
} else {
8992
ime.handleElseCondition(code, ime.keyboardMode, ime.keyboardBinding, commandBarState = true)
9093
}
91-
ime.disableAutoSuggest()
94+
if (!ime.isColonEmojiModeEnabled()) {
95+
ime.disableAutoSuggest()
96+
}
9297
}
9398

9499
/**
@@ -103,7 +108,9 @@ class KeyHandler(
103108
}
104109
ime.handleDelete(shouldDelete, ime.keyboardBinding)
105110
ime.keyboardView!!.invalidateAllKeys()
106-
ime.disableAutoSuggest()
111+
if (!ime.isColonEmojiModeEnabled()) {
112+
ime.disableAutoSuggest()
113+
}
107114
}
108115

109116
/**
@@ -113,7 +120,9 @@ class KeyHandler(
113120
private fun handleShiftKey() {
114121
ime.handleKeyboardLetters(ime.keyboardMode, ime.keyboardView)
115122
ime.keyboardView!!.invalidateAllKeys()
116-
ime.disableAutoSuggest()
123+
if (!ime.isColonEmojiModeEnabled()) {
124+
ime.disableAutoSuggest()
125+
}
117126
}
118127

119128
/**
@@ -129,7 +138,9 @@ class KeyHandler(
129138
ime.switchToCommandToolBar()
130139
ime.updateUI()
131140
}
132-
ime.disableAutoSuggest()
141+
if (!ime.isColonEmojiModeEnabled()) {
142+
ime.disableAutoSuggest()
143+
}
133144
}
134145

135146
/**
@@ -138,7 +149,9 @@ class KeyHandler(
138149
*/
139150
private fun handleModeChangeKey() {
140151
ime.handleModeChange(ime.keyboardMode, ime.keyboardView, ime)
141-
ime.disableAutoSuggest()
152+
if (!ime.isColonEmojiModeEnabled()) {
153+
ime.disableAutoSuggest()
154+
}
142155
}
143156

144157
/**
@@ -168,7 +181,9 @@ class KeyHandler(
168181
val code = KeyboardBase.KEYCODE_SPACE
169182
if (ime.currentState == ScribeState.IDLE || ime.currentState == ScribeState.SELECT_COMMAND) {
170183
ime.handleElseCondition(code, ime.keyboardMode, binding = null)
171-
ime.updateAutoSuggestText(isPlural = ime.checkIfPluralWord)
184+
if (!ime.isColonEmojiModeEnabled()) {
185+
ime.updateAutoSuggestText(isPlural = ime.checkIfPluralWord)
186+
}
172187
} else {
173188
ime.handleElseCondition(code, ime.keyboardMode, ime.keyboardBinding, commandBarState = true)
174189
ime.disableAutoSuggest()

app/src/main/java/be/scri/services/GeneralKeyboardIME.kt

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ abstract class GeneralKeyboardIME(
8787
private var emojiSpaceTablet2: View? = null
8888
private var emojiBtnTablet3: Button? = null
8989

90+
private var colonEmojiBtn1: Button? = null
91+
private var colonEmojiBtn2: Button? = null
92+
private var colonEmojiBtn3: Button? = null
93+
private var colonEmojiBtn4: Button? = null
94+
private var colonEmojiBtn5: Button? = null
95+
private var colonEmojiBtn6: Button? = null
96+
private var colonEmojiBtn7: Button? = null
97+
private var colonEmojiBtn8: Button? = null
98+
private var colonEmojiBtn9: Button? = null
99+
90100
private var genderSuggestionLeft: Button? = null
91101
private var genderSuggestionRight: Button? = null
92102
private var isSingularAndPlural: Boolean = false
@@ -103,6 +113,8 @@ abstract class GeneralKeyboardIME(
103113
var emojiAutoSuggestionEnabled: Boolean = false
104114
var lastWord: String? = null
105115
var autoSuggestEmojis: MutableList<String>? = null
116+
private var isColonEmojiModeActive: Boolean = false
117+
private var colonEmojiQuery: String = ""
106118
var caseAnnotationSuggestion: MutableList<String>? = null
107119
var nounTypeSuggestion: List<String>? = null
108120
var checkIfPluralWord: Boolean = false
@@ -753,6 +765,15 @@ abstract class GeneralKeyboardIME(
753765
emojiBtnTablet2 = binding.emojiBtnTablet2
754766
emojiSpaceTablet2 = binding.emojiSpaceTablet2
755767
emojiBtnTablet3 = binding.emojiBtnTablet3
768+
colonEmojiBtn1 = binding.colonEmojiBtn1
769+
colonEmojiBtn2 = binding.colonEmojiBtn2
770+
colonEmojiBtn3 = binding.colonEmojiBtn3
771+
colonEmojiBtn4 = binding.colonEmojiBtn4
772+
colonEmojiBtn5 = binding.colonEmojiBtn5
773+
colonEmojiBtn6 = binding.colonEmojiBtn6
774+
colonEmojiBtn7 = binding.colonEmojiBtn7
775+
colonEmojiBtn8 = binding.colonEmojiBtn8
776+
colonEmojiBtn9 = binding.colonEmojiBtn9
756777
genderSuggestionLeft = binding.translateBtnLeft
757778
genderSuggestionRight = binding.translateBtnRight
758779
}
@@ -768,14 +789,51 @@ abstract class GeneralKeyboardIME(
768789
resources.configuration.screenLayout and
769790
Configuration.SCREENLAYOUT_SIZE_MASK
770791
) >= Configuration.SCREENLAYOUT_SIZE_LARGE
792+
if (isColonEmojiModeActive) {
793+
// Hide all normal toolbar elements.
794+
binding.scribeKey.visibility = View.GONE
795+
binding.separator1.visibility = View.GONE
796+
binding.translateBtn.visibility = View.GONE
797+
binding.translateBtnLeft.visibility = View.GONE
798+
binding.translateBtnRightText.visibility = View.GONE
799+
binding.translateBtnRight.visibility = View.GONE
800+
binding.separator2.visibility = View.GONE
801+
binding.conjugateBtn.visibility = View.GONE
802+
binding.separator3.visibility = View.GONE
803+
pluralBtn?.visibility = View.GONE
804+
binding.separator4.visibility = View.GONE
805+
emojiBtnPhone1?.visibility = View.GONE
806+
emojiSpacePhone?.visibility = View.GONE
807+
emojiBtnPhone2?.visibility = View.GONE
808+
binding.separator5.visibility = View.GONE
809+
emojiBtnTablet1?.visibility = View.GONE
810+
emojiSpaceTablet1?.visibility = View.GONE
811+
emojiBtnTablet2?.visibility = View.GONE
812+
emojiSpaceTablet2?.visibility = View.GONE
813+
binding.separator6.visibility = View.GONE
814+
emojiBtnTablet3?.visibility = View.GONE
815+
816+
// Show dedicated colon emoji slots.
817+
val slotCount = if (isTablet) 9 else 6
818+
listOf(
819+
colonEmojiBtn1, colonEmojiBtn2, colonEmojiBtn3,
820+
colonEmojiBtn4, colonEmojiBtn5, colonEmojiBtn6,
821+
).forEach { btn -> btn?.visibility = View.VISIBLE }
822+
listOf(colonEmojiBtn7, colonEmojiBtn8, colonEmojiBtn9).forEach { btn ->
823+
btn?.visibility = if (slotCount == 9) View.VISIBLE else View.GONE
824+
}
825+
return
826+
}
771827
if (isTablet) {
828+
binding.scribeKey.visibility = View.VISIBLE
772829
pluralBtn?.visibility = if (isAutoSuggestEnabled) View.INVISIBLE else View.VISIBLE
773830
emojiBtnTablet1?.visibility = if (isAutoSuggestEnabled) View.VISIBLE else View.INVISIBLE
774831
emojiSpaceTablet1?.visibility = if (isAutoSuggestEnabled) View.VISIBLE else View.INVISIBLE
775832
emojiBtnTablet2?.visibility = if (isAutoSuggestEnabled) View.VISIBLE else View.INVISIBLE
776833
emojiSpaceTablet2?.visibility = if (isAutoSuggestEnabled) View.VISIBLE else View.INVISIBLE
777834
emojiBtnTablet3?.visibility = if (isAutoSuggestEnabled) View.VISIBLE else View.INVISIBLE
778835
} else {
836+
binding.scribeKey.visibility = View.VISIBLE
779837
pluralBtn?.visibility = if (isAutoSuggestEnabled) View.INVISIBLE else View.VISIBLE
780838
emojiBtnPhone1?.visibility = if (isAutoSuggestEnabled) View.VISIBLE else View.INVISIBLE
781839
emojiSpacePhone?.visibility = if (isAutoSuggestEnabled) View.VISIBLE else View.INVISIBLE
@@ -805,6 +863,110 @@ abstract class GeneralKeyboardIME(
805863
return lastWord
806864
}
807865

866+
private fun getCurrentTokenBeforeCursor(): String {
867+
val textBeforeCursor = getText() ?: return ""
868+
if (textBeforeCursor.isEmpty()) return ""
869+
val lastWhitespaceIndex = textBeforeCursor.indexOfLast { it.isWhitespace() }
870+
return if (lastWhitespaceIndex == -1) {
871+
textBeforeCursor
872+
} else {
873+
textBeforeCursor.substring(lastWhitespaceIndex + 1)
874+
}
875+
}
876+
877+
fun isColonEmojiModeEnabled(): Boolean = isColonEmojiModeActive
878+
879+
fun handleColonEmojiMode(code: Int) {
880+
if (code == KeyboardBase.KEYCODE_SPACE) {
881+
exitColonEmojiMode()
882+
return
883+
}
884+
885+
val token = getCurrentTokenBeforeCursor()
886+
887+
when {
888+
// Token starts with ":" — enter or stay in colon mode.
889+
token.startsWith(":") -> {
890+
isColonEmojiModeActive = true
891+
colonEmojiQuery = token.drop(1).lowercase()
892+
}
893+
// In colon mode but token no longer starts with ":" — exit.
894+
isColonEmojiModeActive && token.isNotBlank() -> {
895+
exitColonEmojiMode()
896+
return
897+
}
898+
// Not in colon mode and token doesn't start with ":" — nothing to do.
899+
else -> return
900+
}
901+
902+
if (!isColonEmojiModeActive) return
903+
904+
val isTablet =
905+
(
906+
resources.configuration.screenLayout and
907+
Configuration.SCREENLAYOUT_SIZE_MASK
908+
) >= Configuration.SCREENLAYOUT_SIZE_LARGE
909+
val requiredCount = if (isTablet) 9 else 6
910+
val matched =
911+
if (colonEmojiQuery.isBlank()) {
912+
mutableListOf<String>()
913+
} else {
914+
findEmojisForQueryPrefix(colonEmojiQuery)
915+
}
916+
autoSuggestEmojis = fillWithFallbackEmojis(matched, requiredCount)
917+
918+
updateButtonVisibility(true)
919+
updateButtonText(true, autoSuggestEmojis)
920+
}
921+
922+
private fun findEmojisForQueryPrefix(query: String): MutableList<String> {
923+
val matched = LinkedHashSet<String>()
924+
emojiKeywords
925+
.filterKeys { it.startsWith(query, ignoreCase = true) }
926+
.values
927+
.forEach { emojiList ->
928+
emojiList.forEach { matched.add(it) }
929+
}
930+
return matched.take(MAX_COLON_EMOJI_RESULTS).toMutableList()
931+
}
932+
933+
private fun fillWithFallbackEmojis(
934+
matched: List<String>,
935+
requiredCount: Int,
936+
): MutableList<String> {
937+
val orderedUnique = LinkedHashSet<String>()
938+
matched.forEach { emoji ->
939+
if (emoji.isNotBlank()) {
940+
orderedUnique.add(emoji)
941+
}
942+
}
943+
DEFAULT_COLON_EMOJIS.forEach { emoji ->
944+
if (orderedUnique.size < requiredCount) {
945+
orderedUnique.add(emoji)
946+
}
947+
}
948+
return orderedUnique.take(requiredCount).toMutableList()
949+
}
950+
951+
private fun exitColonEmojiMode() {
952+
isColonEmojiModeActive = false
953+
colonEmojiQuery = ""
954+
// Hide dedicated colon emoji buttons.
955+
listOf(
956+
colonEmojiBtn1, colonEmojiBtn2, colonEmojiBtn3,
957+
colonEmojiBtn4, colonEmojiBtn5, colonEmojiBtn6,
958+
colonEmojiBtn7, colonEmojiBtn8, colonEmojiBtn9,
959+
).forEach { btn -> btn?.visibility = View.GONE }
960+
// Restore normal toolbar.
961+
binding.scribeKey.visibility = View.VISIBLE
962+
binding.separator1.visibility = View.VISIBLE
963+
if (currentState == ScribeState.IDLE || currentState == ScribeState.SELECT_COMMAND) {
964+
setupIdleView()
965+
}
966+
updateButtonVisibility(emojiAutoSuggestionEnabled)
967+
disableAutoSuggest()
968+
}
969+
808970
/**
809971
* Finds and returns a list of emojis that are relevant to the last word typed.
810972
*
@@ -909,6 +1071,33 @@ abstract class GeneralKeyboardIME(
9091071
autoSuggestEmojis: MutableList<String>?,
9101072
) {
9111073
if (isAutoSuggestEnabled) {
1074+
if (isColonEmojiModeActive) {
1075+
val emojis = (autoSuggestEmojis ?: mutableListOf()).toList()
1076+
val isTablet =
1077+
(
1078+
resources.configuration.screenLayout and
1079+
Configuration.SCREENLAYOUT_SIZE_MASK
1080+
) >= Configuration.SCREENLAYOUT_SIZE_LARGE
1081+
val slotCount = if (isTablet) 9 else 6
1082+
val slotText = fillWithFallbackEmojis(emojis, slotCount)
1083+
1084+
val colonBtns =
1085+
listOf(
1086+
colonEmojiBtn1, colonEmojiBtn2, colonEmojiBtn3,
1087+
colonEmojiBtn4, colonEmojiBtn5, colonEmojiBtn6,
1088+
colonEmojiBtn7, colonEmojiBtn8, colonEmojiBtn9,
1089+
).take(slotCount)
1090+
1091+
colonBtns.forEachIndexed { index, btn ->
1092+
btn?.text = slotText.getOrElse(index) { " " }
1093+
btn?.setOnClickListener { insertEmoji(btn.text.toString()) }
1094+
}
1095+
return
1096+
}
1097+
1098+
binding.translateBtn.visibility = View.VISIBLE
1099+
binding.translateBtnLeft.visibility = View.INVISIBLE
1100+
binding.translateBtnRight.visibility = View.INVISIBLE
9121101
emojiBtnTablet1?.text = autoSuggestEmojis?.get(0)
9131102
emojiBtnTablet2?.text = autoSuggestEmojis?.get(1)
9141103
emojiBtnTablet3?.text = autoSuggestEmojis?.get(2)
@@ -1292,11 +1481,21 @@ abstract class GeneralKeyboardIME(
12921481
* @param emoji The emoji character to be inserted.
12931482
*/
12941483
private fun insertEmoji(emoji: String) {
1484+
if (emoji.isBlank()) return
12951485
val inputConnection = currentInputConnection ?: return
12961486
val maxLookBack = emojiMaxKeywordLength.coerceAtLeast(1)
12971487

12981488
inputConnection.beginBatchEdit()
12991489
try {
1490+
if (isColonEmojiModeActive) {
1491+
val token = getCurrentTokenBeforeCursor()
1492+
if (token.startsWith(":")) {
1493+
inputConnection.deleteSurroundingText(token.length, 0)
1494+
}
1495+
inputConnection.commitText(emoji, 1)
1496+
return
1497+
}
1498+
13001499
val previousText = inputConnection.getTextBeforeCursor(maxLookBack, 0)?.toString() ?: ""
13011500

13021501
// Find last word boundary efficiently
@@ -1707,5 +1906,7 @@ abstract class GeneralKeyboardIME(
17071906
const val LIGHT_THEME = "#4b4b4b"
17081907
const val MAX_TEXT_LENGTH = 1000
17091908
const val COMMIT_TEXT_CURSOR_POSITION = 1
1909+
private val DEFAULT_COLON_EMOJIS = listOf("😂", "❤️", "😍", "🔥", "👍", "🙏", "😊", "🎉", "😭")
1910+
private const val MAX_COLON_EMOJI_RESULTS = 9
17101911
}
17111912
}

0 commit comments

Comments
 (0)