Skip to content

Commit 5dce3da

Browse files
authored
feat: add tools popup to comma (or whatever) key (#360)
* feat: add tools popup to comma (or whatever) key This change introduces a popup menu on the second left key in the bottom row (often the comma), providing quick access to emoji and settings. - A new `keyRole` attribute to identify special keys like the new "tools" key. The comma key on most letter layouts is now designated as the `tools` key. - A long press on the comma key reveals a popup with icons for emoji and settings. - When the dedicated emoji key is enabled in settings, the emoji icon is removed from the tools popup and its secondary icon hint. - Introduced new key codes (`KEYCODE_POPUP_EMOJI`, `KEYCODE_POPUP_SETTINGS`) to handle actions from the tools popup. - Refactored the secondary icon drawing logi for better reusability. Refs: #62 * refactor: extract space bar and emoji/language long press handlers * fix: use proper colors for popup icons * fix: avoid clearing popup when numbers row is enabled * style: simplify condition
1 parent b272c82 commit 5dce3da

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+401
-210
lines changed

app/src/main/kotlin/org/fossify/keyboard/helpers/MyKeyboard.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ class MyKeyboard {
6767
const val KEYCODE_DELETE = -5
6868
const val KEYCODE_SPACE = 32
6969
const val KEYCODE_EMOJI_OR_LANGUAGE = -6
70+
const val KEYCODE_POPUP_EMOJI = -7
71+
const val KEYCODE_POPUP_SETTINGS = -8
72+
73+
// Key roles for special keys
74+
const val KEY_ROLE_TOOLS = "tools"
7075

7176
fun getDimensionOrFraction(a: TypedArray, index: Int, base: Int, defValue: Int): Int {
7277
val value = a.peekValue(index) ?: return defValue
@@ -169,6 +174,9 @@ class MyKeyboard {
169174
/** Popup characters showing after long pressing the key */
170175
var popupCharacters: CharSequence? = null
171176

177+
/** Role identifier for special keys (e.g., "tools" for the tools popup host key) */
178+
var role: String? = null
179+
172180
/**
173181
* Flags that specify the anchoring to edges of the keyboard for detecting touch events that are just out of the boundary of the key.
174182
* This is a bit mask of [MyKeyboard.EDGE_LEFT], [MyKeyboard.EDGE_RIGHT].
@@ -221,7 +229,7 @@ class MyKeyboard {
221229
secondaryIcon?.setBounds(0, 0, secondaryIcon!!.intrinsicWidth, secondaryIcon!!.intrinsicHeight)
222230

223231
topSmallNumber = a.getString(R.styleable.MyKeyboard_Key_topSmallNumber) ?: ""
224-
232+
role = a.getString(R.styleable.MyKeyboard_Key_keyRole)
225233

226234
a.recycle()
227235
}
@@ -271,7 +279,6 @@ class MyKeyboard {
271279
* @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
272280
* @param enterKeyType determines what icon should we show on Enter key
273281
*/
274-
@JvmOverloads
275282
constructor(context: Context, @XmlRes xmlLayoutResId: Int, enterKeyType: Int) {
276283
mDisplayWidth = context.resources.displayMetrics.widthPixels
277284
mDefaultHorizontalGap = 0
@@ -378,8 +385,9 @@ class MyKeyboard {
378385
if (context.config.showNumbersRow) {
379386
// Removes numbers (i.e 0-9) from the popupCharacters if numbers row is enabled
380387
key.apply {
388+
val hadPopupCharacters = popupCharacters != null
381389
popupCharacters = popupCharacters?.replace(Regex("\\d+"), "")
382-
if (popupCharacters.isNullOrEmpty()) {
390+
if (hadPopupCharacters && popupCharacters.isNullOrEmpty()) {
383391
popupResId = 0
384392
}
385393
}

app/src/main/kotlin/org/fossify/keyboard/services/SimpleKeyboardIME.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.fossify.keyboard.services
22

33
import android.annotation.SuppressLint
4+
import android.content.Intent
45
import android.content.SharedPreferences
56
import android.graphics.Bitmap
67
import android.graphics.drawable.Icon
@@ -37,6 +38,7 @@ import androidx.core.view.updatePadding
3738
import org.fossify.commons.extensions.*
3839
import org.fossify.commons.helpers.*
3940
import org.fossify.keyboard.R
41+
import org.fossify.keyboard.activities.SettingsActivity
4042
import org.fossify.keyboard.databinding.KeyboardViewKeyboardBinding
4143
import org.fossify.keyboard.extensions.config
4244
import org.fossify.keyboard.extensions.getKeyboardBackgroundColor
@@ -268,6 +270,13 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
268270
}
269271
}
270272

273+
MyKeyboard.KEYCODE_POPUP_EMOJI -> keyboardView?.openEmojiPalette()
274+
MyKeyboard.KEYCODE_POPUP_SETTINGS -> Intent(this, SettingsActivity::class.java)
275+
.apply {
276+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
277+
startActivity(this)
278+
}
279+
271280
else -> {
272281
var codeChar = code.toChar()
273282
val originalText = inputConnection.getExtractedText(ExtractedTextRequest(), 0)?.text
@@ -634,6 +643,16 @@ class SimpleKeyboardIME : InputMethodService(), OnKeyboardActionListener, Shared
634643
}
635644
}
636645
}
646+
647+
// When emoji key is enabled, show settings-only popup with no hint on tools key
648+
if (config.showEmojiKey) {
649+
val currentKeys = keyboard.mKeys ?: return keyboard
650+
val toolsKey = currentKeys.firstOrNull { it.role == MyKeyboard.KEY_ROLE_TOOLS }
651+
if (toolsKey != null) {
652+
toolsKey.popupResId = R.xml.popup_tools
653+
toolsKey.secondaryIcon = null
654+
}
655+
}
637656
}
638657
return keyboard
639658
}

app/src/main/kotlin/org/fossify/keyboard/views/MyKeyboardView.kt

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ import org.fossify.keyboard.helpers.MyKeyboard.Companion.KEYCODE_DELETE
9999
import org.fossify.keyboard.helpers.MyKeyboard.Companion.KEYCODE_EMOJI_OR_LANGUAGE
100100
import org.fossify.keyboard.helpers.MyKeyboard.Companion.KEYCODE_ENTER
101101
import org.fossify.keyboard.helpers.MyKeyboard.Companion.KEYCODE_MODE_CHANGE
102+
import org.fossify.keyboard.helpers.MyKeyboard.Companion.KEYCODE_POPUP_EMOJI
103+
import org.fossify.keyboard.helpers.MyKeyboard.Companion.KEYCODE_POPUP_SETTINGS
102104
import org.fossify.keyboard.helpers.MyKeyboard.Companion.KEYCODE_SHIFT
103105
import org.fossify.keyboard.helpers.MyKeyboard.Companion.KEYCODE_SPACE
104106
import org.fossify.keyboard.helpers.MyKeyboard.Companion.KEYCODE_SYMBOLS_MODE_CHANGE
@@ -722,9 +724,9 @@ class MyKeyboardView @JvmOverloads constructor(
722724
canvas.drawText(row, key.width / 2f, startY + textSize * index, paint)
723725
}
724726

725-
if (key.topSmallNumber.isNotEmpty() && !(context.config.showNumbersRow && Regex("\\d").matches(
726-
key.topSmallNumber
727-
))
727+
if (
728+
key.topSmallNumber.isNotEmpty()
729+
&& !(context.config.showNumbersRow && Regex("\\d").matches(key.topSmallNumber))
728730
) {
729731
val bounds = Rect().also {
730732
smallLetterPaint.getTextBounds(
@@ -749,6 +751,9 @@ class MyKeyboardView @JvmOverloads constructor(
749751
)
750752
}
751753

754+
// Draw secondary icons for label-based keys
755+
drawSecondaryIcon(key, canvas, textColor)
756+
752757
// Turn off drop shadow
753758
paint.setShadowLayer(0f, 0f, 0f, 0)
754759
} else if (key.icon != null && mKeyboard != null) {
@@ -765,7 +770,15 @@ class MyKeyboardView @JvmOverloads constructor(
765770
val contrastColor = mPrimaryColor.getContrastColor()
766771
key.icon!!.applyColorFilter(contrastColor)
767772
key.secondaryIcon?.applyColorFilter(contrastColor.adjustAlpha(0.6f))
768-
} else if (code == KEYCODE_DELETE || code == KEYCODE_SHIFT || code == KEYCODE_EMOJI_OR_LANGUAGE) {
773+
} else if (
774+
code in arrayOf(
775+
KEYCODE_DELETE,
776+
KEYCODE_SHIFT,
777+
KEYCODE_EMOJI_OR_LANGUAGE,
778+
KEYCODE_POPUP_EMOJI,
779+
KEYCODE_POPUP_SETTINGS
780+
)
781+
) {
769782
key.icon!!.applyColorFilter(textColor)
770783
key.secondaryIcon?.applyColorFilter(
771784
if (key.pressed) {
@@ -779,10 +792,9 @@ class MyKeyboardView @JvmOverloads constructor(
779792
val keyIcon = key.icon!!
780793
val secondaryIcon = key.secondaryIcon
781794
if (secondaryIcon != null) {
795+
// When secondary icon exists, shrink main icon to 90%
782796
val keyIconWidth = (keyIcon.intrinsicWidth * 0.9f).toInt()
783797
val keyIconHeight = (keyIcon.intrinsicHeight * 0.9f).toInt()
784-
val secondaryIconWidth = (secondaryIcon.intrinsicWidth * 0.5f).toInt()
785-
val secondaryIconHeight = (secondaryIcon.intrinsicHeight * 0.5f).toInt()
786798

787799
val centerX = key.width / 2
788800
val centerY = key.height / 2
@@ -798,20 +810,7 @@ class MyKeyboardView @JvmOverloads constructor(
798810
)
799811
keyIcon.draw(canvas)
800812

801-
val secondaryIconPaddingRight = 10
802-
val secondaryIconLeft =
803-
key.width - secondaryIconPaddingRight - secondaryIconWidth
804-
val secondaryIconRight = secondaryIconLeft + secondaryIconWidth
805-
806-
val secondaryIconTop = 14 // This will act as a topPadding
807-
val secondaryIconBottom = secondaryIconTop + secondaryIconHeight
808-
809-
secondaryIcon.setBounds(
810-
secondaryIconLeft, secondaryIconTop, secondaryIconRight, secondaryIconBottom
811-
)
812-
secondaryIcon.draw(canvas)
813-
814-
secondaryIcon.draw(canvas)
813+
drawSecondaryIcon(key, canvas, textColor)
815814
} else {
816815
val drawableX = (key.width - keyIcon.intrinsicWidth) / 2
817816
val drawableY = (key.height - keyIcon.intrinsicHeight) / 2
@@ -897,6 +896,25 @@ class MyKeyboardView @JvmOverloads constructor(
897896
return resources.getDrawable(drawableId, context.theme)
898897
}
899898

899+
private fun drawSecondaryIcon(key: MyKeyboard.Key, canvas: Canvas, textColor: Int) {
900+
val secondaryIcon = key.secondaryIcon ?: return
901+
secondaryIcon.applyColorFilter(
902+
if (key.pressed) textColor else mTextColor.adjustAlpha(0.6f)
903+
)
904+
val secondaryIconWidth = (secondaryIcon.intrinsicWidth * 0.5f).toInt()
905+
val secondaryIconHeight = (secondaryIcon.intrinsicHeight * 0.5f).toInt()
906+
val secondaryIconPaddingRight = 10
907+
val secondaryIconLeft = key.width - secondaryIconPaddingRight - secondaryIconWidth
908+
val secondaryIconRight = secondaryIconLeft + secondaryIconWidth
909+
val secondaryIconTop = 14
910+
val secondaryIconBottom = secondaryIconTop + secondaryIconHeight
911+
912+
secondaryIcon.setBounds(
913+
secondaryIconLeft, secondaryIconTop, secondaryIconRight, secondaryIconBottom
914+
)
915+
secondaryIcon.draw(canvas)
916+
}
917+
900918
private fun handleClipboard() {
901919
if (mToolbarHolder != null && mPopupParent.id != R.id.mini_keyboard_view && context.config.showClipboardContent) {
902920
val clipboardContent = context.getCurrentClip()
@@ -1178,36 +1196,21 @@ class MyKeyboardView @JvmOverloads constructor(
11781196
*/
11791197
private fun onLongPress(popupKey: MyKeyboard.Key, me: MotionEvent): Boolean {
11801198
if (popupKey.code == KEYCODE_SPACE) {
1181-
return if (!mCursorControlActive) {
1182-
setCurrentKeyPressed(false)
1183-
mRepeatKeyIndex = NOT_A_KEY
1184-
mHandler?.removeMessages(MSG_REPEAT)
1185-
vibrateIfNeeded()
1186-
SwitchLanguageDialog(this) {
1187-
mOnKeyboardActionListener?.reloadKeyboard()
1188-
}
1189-
true
1190-
} else false
1199+
return onSpaceBarLongPressed()
11911200
} else if (popupKey.code == KEYCODE_EMOJI_OR_LANGUAGE) {
1192-
setCurrentKeyPressed(false)
1193-
if (context.config.showEmojiKey) {
1194-
openEmojiPalette()
1195-
} else {
1196-
SwitchLanguageDialog(this) {
1197-
mOnKeyboardActionListener?.reloadKeyboard()
1198-
}
1199-
}
1200-
return true
1201+
return onEmojiOrLanguageLongPressed()
12011202
} else {
12021203
val popupKeyboardId = popupKey.popupResId
12031204
if (popupKeyboardId != 0) {
12041205
mMiniKeyboardContainer = mMiniKeyboardCache[popupKey]
12051206

12061207
// For 'number' and 'phone' keyboards the count of popup keys might be bigger than count of keys in the main keyboard.
12071208
// And therefore the width of the key might be smaller than width declared in MyKeyboard.Key.width for the main keyboard.
1208-
val popupKeyWidth = popupKey.calcKeyWidth(
1209-
containerWidth = mMiniKeyboardContainer?.measuredWidth ?: width
1210-
)
1209+
val popupKeyWidth = if (popupKey.popupCharacters != null) {
1210+
popupKey.calcKeyWidth(containerWidth = mMiniKeyboardContainer?.measuredWidth ?: width)
1211+
} else {
1212+
popupKey.width
1213+
}
12111214

12121215
if (mMiniKeyboardContainer == null) {
12131216
val inflater =
@@ -1243,8 +1246,9 @@ class MyKeyboardView @JvmOverloads constructor(
12431246
mPopupX = popupKey.x
12441247
mPopupY = popupKey.y
12451248

1246-
val widthToUse =
1247-
mMiniKeyboardContainer!!.measuredWidth - (popupKey.popupCharacters!!.length / 2) * popupKeyWidth
1249+
// Use popupCharacters length if available, otherwise use actual key count from the loaded keyboard
1250+
val popupKeyCount = popupKey.popupCharacters?.length ?: mMiniKeyboard!!.mKeys.size
1251+
val widthToUse = mMiniKeyboardContainer!!.measuredWidth - (popupKeyCount / 2) * popupKeyWidth
12481252
mPopupX = mPopupX + popupKeyWidth - widthToUse
12491253
mPopupY -= mMiniKeyboardContainer!!.measuredHeight
12501254
val x = mPopupX + mCoordinates[0]
@@ -1606,6 +1610,32 @@ class MyKeyboardView @JvmOverloads constructor(
16061610
setupStoredClips()
16071611
}
16081612

1613+
private fun onSpaceBarLongPressed(): Boolean {
1614+
return if (!mCursorControlActive) {
1615+
setCurrentKeyPressed(false)
1616+
mRepeatKeyIndex = NOT_A_KEY
1617+
mHandler?.removeMessages(MSG_REPEAT)
1618+
vibrateIfNeeded()
1619+
SwitchLanguageDialog(this) {
1620+
mOnKeyboardActionListener?.reloadKeyboard()
1621+
}
1622+
true
1623+
} else false
1624+
}
1625+
1626+
private fun onEmojiOrLanguageLongPressed(): Boolean {
1627+
setCurrentKeyPressed(false)
1628+
if (context.config.showEmojiKey) {
1629+
openEmojiPalette()
1630+
} else {
1631+
SwitchLanguageDialog(this) {
1632+
mOnKeyboardActionListener?.reloadKeyboard()
1633+
}
1634+
}
1635+
1636+
return true
1637+
}
1638+
16091639
private fun setupStoredClips() {
16101640
ensureBackgroundThread {
16111641
val clips = ArrayList<ListItem>()

app/src/main/res/values/attrs.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,7 @@
3838
<attr name="secondaryKeyIcon" format="reference" />
3939
<!-- Top small number shown above letters of the first row. -->
4040
<attr name="topSmallNumber" format="string" />
41+
<!-- Role identifier for special keys (e.g., "tools" for the tools popup host key). -->
42+
<attr name="keyRole" format="string" />
4143
</declare-styleable>
4244
</resources>

app/src/main/res/xml/keys_letters_belarusian_cyrl.xml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,17 +132,17 @@
132132
app:keyWidth="8%p" />
133133
<Key
134134
app:keyLabel="і"
135+
app:keyWidth="8%p"
135136
app:popupCharacters="иї"
136-
app:popupKeyboard="@xml/keyboard_popup_template"
137-
app:keyWidth="8%p" />
137+
app:popupKeyboard="@xml/keyboard_popup_template" />
138138
<Key
139139
app:keyLabel="т"
140140
app:keyWidth="8%p" />
141141
<Key
142142
app:keyLabel="ь"
143+
app:keyWidth="8%p"
143144
app:popupCharacters="ъ"
144-
app:popupKeyboard="@xml/keyboard_popup_template"
145-
app:keyWidth="8%p" />
145+
app:popupKeyboard="@xml/keyboard_popup_template" />
146146
<Key
147147
app:keyLabel="б"
148148
app:keyWidth="8%p" />
@@ -164,7 +164,10 @@
164164
app:keyWidth="15%p" />
165165
<Key
166166
app:keyLabel=","
167-
app:keyWidth="10%p" />
167+
app:keyWidth="10%p"
168+
app:keyRole="tools"
169+
app:popupKeyboard="@xml/popup_tools_with_emoji_key"
170+
app:secondaryKeyIcon="@drawable/ic_emoji_emotions_outline_vector" />
168171
<Key
169172
app:code="-6"
170173
app:keyEdgeFlags="left"

app/src/main/res/xml/keys_letters_belarusian_latn.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,10 @@
148148
app:keyWidth="15%p" />
149149
<Key
150150
app:keyLabel=","
151-
app:keyWidth="10%p" />
151+
app:keyWidth="10%p"
152+
app:keyRole="tools"
153+
app:popupKeyboard="@xml/popup_tools_with_emoji_key"
154+
app:secondaryKeyIcon="@drawable/ic_emoji_emotions_outline_vector" />
152155
<Key
153156
app:code="-6"
154157
app:keyEdgeFlags="left"

app/src/main/res/xml/keys_letters_bengali.xml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,22 +191,25 @@
191191
app:keyWidth="15%p" />
192192
<Key
193193
app:keyLabel=","
194-
app:keyWidth="10%p" />
194+
app:keyWidth="10%p"
195+
app:keyRole="tools"
196+
app:popupKeyboard="@xml/popup_tools_with_emoji_key"
197+
app:secondaryKeyIcon="@drawable/ic_emoji_emotions_outline_vector" />
195198
<Key
196199
app:code="-6"
197200
app:keyEdgeFlags="left"
198201
app:keyIcon="@drawable/ic_emoji_emotions_outline_vector"
199-
app:secondaryKeyIcon="@drawable/ic_language_outlined"
200-
app:keyWidth="10%p" />
202+
app:keyWidth="10%p"
203+
app:secondaryKeyIcon="@drawable/ic_language_outlined" />
201204
<Key
202205
app:code="32"
203206
app:isRepeatable="true"
204207
app:keyWidth="40%p" />
205208
<Key
206209
app:keyLabel="."
210+
app:keyWidth="10%p"
207211
app:popupCharacters=",?!;:'…"
208-
app:popupKeyboard="@xml/keyboard_popup_template"
209-
app:keyWidth="10%p" />
212+
app:popupKeyboard="@xml/keyboard_popup_template" />
210213
<Key
211214
app:code="-4"
212215
app:keyEdgeFlags="right"

0 commit comments

Comments
 (0)