From 4ccd0b81f2d71569b32900f106e04f8df08d71cb Mon Sep 17 00:00:00 2001 From: Owm Date: Wed, 22 Jul 2026 01:49:16 +0530 Subject: [PATCH] feat-emoji --- .../main/java/be/scri/helpers/KeyboardBase.kt | 45 ----- .../main/java/be/scri/views/KeyboardView.kt | 158 ++++++++++++------ .../res/drawable/ic_settings_cog_vector.xml | 19 ++- .../res/layout/emoji_key_long_hold_popup.xml | 60 +++++++ app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/keys_emoji_popup.xml | 17 ++ app/src/main/res/xml/keys_symbols.xml | 6 +- 7 files changed, 208 insertions(+), 100 deletions(-) create mode 100644 app/src/main/res/layout/emoji_key_long_hold_popup.xml create mode 100644 app/src/main/res/xml/keys_emoji_popup.xml diff --git a/app/src/main/java/be/scri/helpers/KeyboardBase.kt b/app/src/main/java/be/scri/helpers/KeyboardBase.kt index f5c89fdf..254c684a 100644 --- a/app/src/main/java/be/scri/helpers/KeyboardBase.kt +++ b/app/src/main/java/be/scri/helpers/KeyboardBase.kt @@ -660,51 +660,6 @@ class KeyboardBase { } } - if (currentKeyboardMode == keyboardLettersMode && mKeys != null) { - val spaceKey = mKeys!!.find { it?.code == 32 } ?: return - val emojiKey = mKeys!!.find { it?.code == KEYCODE_EMOJI } ?: return - val commaKey = mKeys!!.find { it?.code == ','.code } ?: return - val periodKey = mKeys!!.find { it?.code == '.'.code } - val enterKey = mKeys!!.find { it?.code == KEYCODE_ENTER } - val modeChangeKey = mKeys!!.find { it?.code == KEYCODE_MODE_CHANGE } - - val row = mRows.lastOrNull() ?: return - - val clipWidth = (mDisplayWidth * 0.10).toInt() - - val clipKey = Key(row) - clipKey.code = KEYCODE_CLIPBOARD - clipKey.width = clipWidth - clipKey.height = spaceKey.height - clipKey.gap = spaceKey.gap - clipKey.icon = context.resources.getDrawable(R.drawable.ic_clipboard_vector, context.theme) - clipKey.icon?.setBounds(0, 0, clipKey.icon!!.intrinsicWidth, clipKey.icon!!.intrinsicHeight) - - spaceKey.width -= (clipWidth + clipKey.gap) - - val emojiIdxInList = mKeys!!.indexOf(emojiKey) - val emojiIdxInRow = row.mKeys.indexOf(emojiKey) - - if (emojiIdxInList != -1 && emojiIdxInRow != -1) { - modeChangeKey?.let { emojiKey.x = it.x + it.width + it.gap } - - clipKey.x = emojiKey.x + emojiKey.width + emojiKey.gap - clipKey.y = emojiKey.y - - commaKey.x = clipKey.x + clipKey.width + clipKey.gap - commaKey.y = emojiKey.y - - spaceKey.x = commaKey.x + commaKey.width + commaKey.gap - periodKey?.x = spaceKey.x + spaceKey.width + spaceKey.gap - - val lastPositionedKey = periodKey ?: spaceKey - enterKey?.x = lastPositionedKey.x + lastPositionedKey.width + lastPositionedKey.gap - - mKeys!!.add(emojiIdxInList + 1, clipKey) - row.mKeys.add(emojiIdxInRow + 1, clipKey) - } - } - mHeight = y } diff --git a/app/src/main/java/be/scri/views/KeyboardView.kt b/app/src/main/java/be/scri/views/KeyboardView.kt index 8efcf3f7..4e48a64a 100644 --- a/app/src/main/java/be/scri/views/KeyboardView.kt +++ b/app/src/main/java/be/scri/views/KeyboardView.kt @@ -64,6 +64,7 @@ import be.scri.helpers.KeyboardBase.Companion.KEYCODE_TAB import be.scri.helpers.KeyboardBase.Companion.SHIFT_LOCKED import be.scri.helpers.KeyboardBase.MyCustomActions import be.scri.helpers.MAX_KEYS_PER_MINI_ROW +import be.scri.helpers.PreferencesHelper import be.scri.helpers.SHIFT_OFF import be.scri.helpers.SHIFT_ON_ONE_CHAR import be.scri.helpers.SHIFT_ON_PERMANENT @@ -1151,12 +1152,41 @@ class KeyboardView } // Controls where icons are located on their keys. - val drawableX = (key.width - key.icon!!.intrinsicWidth) / 2 - val drawableY = (key.height - key.icon!!.intrinsicHeight) / 2 + var iconWidth = key.icon!!.intrinsicWidth + var iconHeight = key.icon!!.intrinsicHeight + val isEmojiOrClipboard = code == KeyboardBase.KEYCODE_EMOJI || code == KeyboardBase.KEYCODE_CLIPBOARD + val scaleFactor = if (isEmojiOrClipboard) 0.45f else 0.6f + val maxIconWidth = (key.width * scaleFactor).toInt() + val maxIconHeight = (key.height * scaleFactor).toInt() + if (iconWidth > maxIconWidth || iconHeight > maxIconHeight) { + val ratio = iconWidth.toFloat() / iconHeight.toFloat() + if (ratio > 1) { + iconWidth = maxIconWidth + iconHeight = (maxIconWidth / ratio).toInt() + } else { + iconHeight = maxIconHeight + iconWidth = (maxIconHeight * ratio).toInt() + } + } + val drawableX = (key.width - iconWidth) / 2 + val drawableY = (key.height - iconHeight) / 2 canvas.translate(drawableX.toFloat(), drawableY.toFloat()) - key.icon!!.setBounds(0, 0, key.icon!!.intrinsicWidth, key.icon!!.intrinsicHeight) + key.icon!!.setBounds(0, 0, iconWidth, iconHeight) key.icon!!.draw(canvas) canvas.translate(-drawableX.toFloat(), -drawableY.toFloat()) + + if (code == KeyboardBase.KEYCODE_EMOJI) { + val settingsIcon = resources.getDrawable(R.drawable.ic_settings_cog_vector, context.theme) + settingsIcon.applyColorFilter(mTextColor) + val density = context.resources.displayMetrics.density + val cogSize = (12 * density).toInt() + val rightPadding = keyMargin - shadowOffset + padding + (2 * density).toInt() + val topPadding = keyMargin - shadowOffset + padding + (2 * density).toInt() + val cogX = key.width - cogSize - rightPadding + val cogY = topPadding + settingsIcon.setBounds(cogX, cogY, cogX + cogSize, cogY + cogSize) + settingsIcon.draw(canvas) + } } canvas.translate(-key.x.toFloat(), -key.y.toFloat()) } @@ -1396,6 +1426,16 @@ class KeyboardView } private fun openPopupIfRequired(me: MotionEvent): Boolean { + val currentKey = if (mCurrentKey in mKeys.indices) mKeys[mCurrentKey] else null + if (currentKey?.code == KeyboardBase.KEYCODE_EMOJI) { + val result = onLongPress(currentKey, me) + if (result) { + mAbortKey = true + showPreview(NOT_A_KEY) + } + return result + } + if (mPopupLayout == 0 || mCurrentKey !in mKeys.indices) { return false } @@ -1410,21 +1450,14 @@ class KeyboardView return result } - /** - * Called when a key is long pressed. - * By default this will open any popup keyboard associated with this key through the attributes - * popupLayout and popupCharacters. - * - * @param popupKey The key that was long pressed. - * - * @return true if the long press is handled, false otherwise. - * Subclasses should call the method on the base class if the subclass doesn't wish to - * handle the call. - */ private fun onLongPress( popupKey: KeyboardBase.Key, me: MotionEvent, ): Boolean { + if (popupKey.code == KeyboardBase.KEYCODE_EMOJI) { + popupKey.popupResId = R.xml.keys_emoji_popup + } + val popupKeyboardId = popupKey.popupResId if (popupKeyboardId != 0) { mMiniKeyboardContainer = mMiniKeyboardCache[popupKey] @@ -1517,11 +1550,9 @@ class KeyboardView mPopupX = popupKey.x mPopupY = popupKey.y - val widthToUse = - mMiniKeyboardContainer!!.measuredWidth - - (popupKey.popupCharacters!!.length / 2) * - popupKey.width - mPopupX = mPopupX + popupKey.width - widthToUse + var leftX = popupKey.x + (popupKey.width - mMiniKeyboardContainer!!.measuredWidth) / 2 + leftX = leftX.coerceIn(0, (width - mMiniKeyboardContainer!!.measuredWidth).coerceAtLeast(0)) + mPopupX = leftX mPopupY -= mMiniKeyboardContainer!!.measuredHeight val x = mPopupX + mCoordinates[0] val y = mPopupY + mCoordinates[1] @@ -1544,7 +1575,14 @@ class KeyboardView } selectedKeyIndex = Math.max(0, Math.min(selectedKeyIndex, keysCnt - 1)) - if (setHoldForAltCharacters) { + val isEmojiPopup = mMiniKeyboard!!.mKeys.any { it.code == KeyboardBase.KEYCODE_EMOJI || it.code == KeyboardBase.KEYCODE_CLIPBOARD } + if (isEmojiPopup) { + // Emoji popup: start with no pre-selection; user slides to choose and lifts to confirm. + for (i in 0 until keysCnt) { + mMiniKeyboard!!.mKeys[i].focused = false + } + mMiniKeyboardSelectedKeyIndex = -1 + } else if (setHoldForAltCharacters) { for (i in 0 until keysCnt) { mMiniKeyboard!!.mKeys[i].focused = i == selectedKeyIndex } @@ -1591,6 +1629,7 @@ class KeyboardView } if (mPopupKeyboard.isShowing) { + val isEmojiPopup = mMiniKeyboard?.mKeys?.any { it.code == KeyboardBase.KEYCODE_EMOJI || it.code == KeyboardBase.KEYCODE_CLIPBOARD } == true when (action) { MotionEvent.ACTION_MOVE -> { val miniKeyboard = mMiniKeyboard @@ -1601,11 +1640,11 @@ class KeyboardView miniKeyboard.getGlobalVisibleRect(popupRect) val widthPerKey = miniKeyboard.width / keysCnt.toFloat() - var selectedKeyIndex = ((me.x - popupRect.left) / widthPerKey).toInt() + var selectedKeyIndex = ((me.rawX - popupRect.left) / widthPerKey).toInt() selectedKeyIndex = selectedKeyIndex.coerceIn(0, keysCnt - 1) if (selectedKeyIndex != mMiniKeyboardSelectedKeyIndex) { - if (setHoldForAltCharacters) { + if (setHoldForAltCharacters || isEmojiPopup) { for (i in 0 until keysCnt) { miniKeyboard.mKeys[i].focused = i == selectedKeyIndex } @@ -1624,35 +1663,54 @@ class KeyboardView mMiniKeyboardSelectedKeyIndex = selectedKeyIndex - if (setHoldForAltCharacters) { - // Hold mode, so use long delay. - hoverRunnable = - Runnable { - val key = miniKeyboard.mKeys[mMiniKeyboardSelectedKeyIndex] - key.focused = false - miniKeyboard.invalidateAllKeys() - - mOnKeyboardActionListener?.onKey(key.code) - mMiniKeyboardSelectedKeyIndex = -1 - hoverRunnable = null - dismissPopupKeyboard() - } - hoverHandler?.postDelayed(hoverRunnable!!, hoverDelay) - } else { - hoverRunnable = - Runnable { - val key = miniKeyboard.mKeys[mMiniKeyboardSelectedKeyIndex] - key.focused = false - miniKeyboard.invalidateAllKeys() - - mOnKeyboardActionListener?.onKey(key.code) - mMiniKeyboardSelectedKeyIndex = -1 - hoverRunnable = null - dismissPopupKeyboard() - } - hoverHandler?.postDelayed(hoverRunnable!!, 220L) + if (!isEmojiPopup) { + // Non-emoji popup: auto-fire after hover delay. + if (setHoldForAltCharacters) { + hoverRunnable = + Runnable { + val key = miniKeyboard.mKeys[mMiniKeyboardSelectedKeyIndex] + key.focused = false + miniKeyboard.invalidateAllKeys() + + mOnKeyboardActionListener?.onKey(key.code) + mMiniKeyboardSelectedKeyIndex = -1 + hoverRunnable = null + dismissPopupKeyboard() + } + hoverHandler?.postDelayed(hoverRunnable!!, hoverDelay) + } else { + hoverRunnable = + Runnable { + val key = miniKeyboard.mKeys[mMiniKeyboardSelectedKeyIndex] + key.focused = false + miniKeyboard.invalidateAllKeys() + + mOnKeyboardActionListener?.onKey(key.code) + mMiniKeyboardSelectedKeyIndex = -1 + hoverRunnable = null + dismissPopupKeyboard() + } + hoverHandler?.postDelayed(hoverRunnable!!, 220L) + } } + // Emoji popup: no auto-fire on hover; wait for finger lift (ACTION_UP). + } + } + } + + MotionEvent.ACTION_UP -> { + if (isEmojiPopup) { + // Fire whichever key is currently highlighted when the finger lifts. + val idx = mMiniKeyboardSelectedKeyIndex + if (idx >= 0 && idx < (mMiniKeyboard?.mKeys?.size ?: 0)) { + val key = mMiniKeyboard!!.mKeys[idx] + key.focused = false + mMiniKeyboard!!.invalidateAllKeys() + mOnKeyboardActionListener?.onKey(key.code) } + mMiniKeyboardSelectedKeyIndex = -1 + dismissPopupKeyboard() + return true } } @@ -1664,7 +1722,7 @@ class KeyboardView return onModifiedTouchEvent(me) } - if (setHoldForAltCharacters) { + if (!isEmojiPopup && setHoldForAltCharacters) { if (mMiniKeyboardSelectedKeyIndex >= 0) { val key = mMiniKeyboard!!.mKeys[mMiniKeyboardSelectedKeyIndex] mOnKeyboardActionListener?.onKey(key.code) diff --git a/app/src/main/res/drawable/ic_settings_cog_vector.xml b/app/src/main/res/drawable/ic_settings_cog_vector.xml index 3dcc7482..93e74c0d 100644 --- a/app/src/main/res/drawable/ic_settings_cog_vector.xml +++ b/app/src/main/res/drawable/ic_settings_cog_vector.xml @@ -1,3 +1,18 @@ - - + + + + + + + diff --git a/app/src/main/res/layout/emoji_key_long_hold_popup.xml b/app/src/main/res/layout/emoji_key_long_hold_popup.xml new file mode 100644 index 00000000..fe3fb25e --- /dev/null +++ b/app/src/main/res/layout/emoji_key_long_hold_popup.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f2d1d411..6961a484 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,4 +3,7 @@ Scribe + Clipboard + Floating keyboard + diff --git a/app/src/main/res/xml/keys_emoji_popup.xml b/app/src/main/res/xml/keys_emoji_popup.xml new file mode 100644 index 00000000..e86aaaf2 --- /dev/null +++ b/app/src/main/res/xml/keys_emoji_popup.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/app/src/main/res/xml/keys_symbols.xml b/app/src/main/res/xml/keys_symbols.xml index cb0b62d5..abe85d72 100755 --- a/app/src/main/res/xml/keys_symbols.xml +++ b/app/src/main/res/xml/keys_symbols.xml @@ -108,11 +108,11 @@ app:keyLabel="ABC" app:keyWidth="15%p" />