From 2e36b2bf1b12785bbe4c56db674536580e8ef33d Mon Sep 17 00:00:00 2001 From: Rocka Date: Sat, 13 Jun 2026 14:53:19 +0800 Subject: [PATCH] Introduce PickerPolicy and Popup.Keyboard.Explicit for decoupling emoji picker --- .../fcitx5/android/input/keyboard/KeyDef.kt | 9 +- .../android/input/keyboard/KeyDefPreset.kt | 8 +- .../android/input/keyboard/TextKeyboard.kt | 17 ++- .../android/input/picker/PickerPageUi.kt | 102 ++++++++++-------- .../input/picker/PickerPagesAdapter.kt | 57 +++++----- .../android/input/picker/PickerPolicy.kt | 76 +++++++++++++ .../android/input/picker/PickerWindow.kt | 36 +------ .../input/picker/PickerWindowPreset.kt | 5 +- .../android/input/popup/EmojiModifier.kt | 13 +-- .../android/input/popup/PopupComponent.kt | 25 +++-- 10 files changed, 213 insertions(+), 135 deletions(-) create mode 100644 app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPolicy.kt diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDef.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDef.kt index 211a2b37..57f46197 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDef.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDef.kt @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors + * SPDX-FileCopyrightText: Copyright 2021-2026 Fcitx5 for Android Contributors */ package org.fcitx.fcitx5.android.input.keyboard @@ -117,7 +117,12 @@ open class KeyDef( class AltPreview(content: String, val alternative: String) : Preview(content) - class Keyboard(val label: String) : Popup() + sealed class Keyboard : Popup() { + data class Preset(val label: String, val transformPunctuation: Boolean = true) : + Keyboard() + + class Explicit(val items: Array) : Keyboard() + } class Menu(val items: Array) : Popup() { class Item(val label: String, @DrawableRes val icon: Int, val action: KeyAction) diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDefPreset.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDefPreset.kt index 96b1ba94..c3313418 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDefPreset.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/KeyDefPreset.kt @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors + * SPDX-FileCopyrightText: Copyright 2021-2026 Fcitx5 for Android Contributors */ package org.fcitx.fcitx5.android.input.keyboard @@ -35,7 +35,7 @@ class SymbolKey( ), popup ?: arrayOf( Popup.Preview(symbol), - Popup.Keyboard(symbol) + Popup.Keyboard.Preset(symbol) ) ) @@ -57,7 +57,7 @@ class AlphabetKey( ), popup ?: arrayOf( Popup.AltPreview(character, punctuation), - Popup.Keyboard(character) + Popup.Keyboard.Preset(character) ) ) @@ -78,7 +78,7 @@ class AlphabetDigitKey( ), popup ?: arrayOf( Popup.AltPreview(character, altText), - Popup.Keyboard(character) + Popup.Keyboard.Preset(character) ) ) { constructor( diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt index f3476aaf..368cf6cd 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/keyboard/TextKeyboard.kt @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors + * SPDX-FileCopyrightText: Copyright 2021-2026 Fcitx5 for Android Contributors */ package org.fcitx.fcitx5.android.input.keyboard @@ -185,10 +185,17 @@ class TextKeyboard( is PopupAction.PreviewAction -> action.copy(content = transformPopupPreview(action.content)) is PopupAction.PreviewUpdateAction -> action.copy(content = transformPopupPreview(action.content)) is PopupAction.ShowKeyboardAction -> { - val label = action.keyboard.label - if (label.length == 1 && label[0].isLetter()) - action.copy(keyboard = KeyDef.Popup.Keyboard(transformAlphabet(label))) - else action + when (action.keyboard) { + is KeyDef.Popup.Keyboard.Preset -> { + val label = action.keyboard.label + if (label.length == 1 && label[0].isLetter()) + action.copy( + keyboard = action.keyboard.copy(label = transformAlphabet(label)) + ) + else action + } + is KeyDef.Popup.Keyboard.Explicit -> action + } } else -> action } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPageUi.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPageUi.kt index d36086d9..1dfd15d5 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPageUi.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPageUi.kt @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2025 Fcitx5 for Android Contributors + * SPDX-FileCopyrightText: Copyright 2021-2026 Fcitx5 for Android Contributors */ package org.fcitx.fcitx5.android.input.picker @@ -29,7 +29,6 @@ import org.fcitx.fcitx5.android.input.keyboard.KeyDef.Appearance.Border import org.fcitx.fcitx5.android.input.keyboard.KeyDef.Appearance.Variant import org.fcitx.fcitx5.android.input.keyboard.KeyView import org.fcitx.fcitx5.android.input.keyboard.TextKeyView -import org.fcitx.fcitx5.android.input.popup.EmojiModifier import org.fcitx.fcitx5.android.input.popup.PopupAction import org.fcitx.fcitx5.android.input.popup.PopupActionListener import splitties.views.dsl.constraintlayout.below @@ -72,10 +71,7 @@ class PickerPageUi( Low(12, 4, 3, 19f, true, false) } - companion object { - val BackspaceAction = SymAction(KeySym(FcitxKeyMapping.FcitxKey_BackSpace)) - private var popupOnKeyPress by AppPrefs.getInstance().keyboard.popupOnKeyPress - } + private val popupOnKeyPress by AppPrefs.getInstance().keyboard.popupOnKeyPress var keyActionListener: KeyActionListener? = null var popupActionListener: PopupActionListener? = null @@ -106,8 +102,9 @@ class PickerPageUi( ) private val backspaceKey by lazy { + val backspaceAction = SymAction(KeySym(FcitxKeyMapping.FcitxKey_BackSpace)) val action: (View) -> Unit = { - keyActionListener?.onKeyAction(BackspaceAction, Source.Keyboard) + keyActionListener?.onKeyAction(backspaceAction, Source.Keyboard) } val listener = View.OnClickListener { action.invoke(it) } ImageKeyView(ctx, theme, backspaceAppearance).apply { @@ -175,11 +172,26 @@ class PickerPageUi( layoutParams = ViewGroup.LayoutParams(matchParent, matchParent) } - private fun onSymbolClick(str: String) { - keyActionListener?.onKeyAction(CommitAction(str), Source.Keyboard) + fun setItems(items: List) { + keyViews.forEachIndexed { i, keyView -> + keyView.apply { + if (i >= items.size) { + isEnabled = false + mainText.text = "" + setOnClickListener(null) + } else { + isEnabled = true + mainText.text = items[i] + setOnClickListener { onItemClick(items[i]) } + } + swipeEnabled = false + onGestureListener = null + setOnLongClickListener(null) + } + } } - fun setItems(items: List, withSkinTone: Boolean = false) { + fun setItems(items: List, policy: PickerPolicy) { keyViews.forEachIndexed { i, keyView -> keyView.apply { if (i >= items.size) { @@ -191,28 +203,15 @@ class PickerPageUi( onGestureListener = null } else { isEnabled = true - val label = items[i] - val commitString = - if (withSkinTone) EmojiModifier.getPreferredTone(label) else label - mainText.text = commitString + val item = items[i] + mainText.text = item setOnClickListener { - onSymbolClick(commitString) + onItemClick(item) } - setOnLongClickListener { view -> - view as KeyView - if (!popupOnKeyPress) { - // in case "popup on keypress" is disabled, popup keyboard need to know - // the actual bounds on press. see [^1] as well - view.updateBounds() - } - // TODO: maybe popup keyboard should just accept String as label? - onPopupAction( - PopupAction.ShowKeyboardAction( - view.id, - KeyDef.Popup.Keyboard(label), - bounds - ) - ) + setOnLongClickListener longClick@{ view -> + if (view !is KeyView) return@longClick false + val popup = policy.popup(item) ?: return@longClick false + onItemLongClick(view, popup) false } swipeEnabled = true @@ -220,23 +219,13 @@ class PickerPageUi( view as KeyView when (event.type) { CustomGestureView.GestureType.Down -> { - if (popupOnKeyPress) { - // [^1]: bounds is first calculated in KeyView's onLayout(), it - // not in screen viewport at the time of layout. - // eg. it's inside the next page of ViewPager - // so update bounds when it's pressed - view.updateBounds() - onPopupAction( - PopupAction.PreviewAction(view.id, label, view.bounds) - ) - } + onPopupShow(view, item) + // never "consume" the gesture on touch down false } - CustomGestureView.GestureType.Move -> { onPopupChangeFocus(view.id, event.x, event.y) } - CustomGestureView.GestureType.Up -> { onPopupTrigger(view.id).also { onPopupAction(PopupAction.DismissAction(view.id)) @@ -249,22 +238,45 @@ class PickerPageUi( } } + private fun onItemClick(item: String) { + keyActionListener?.onKeyAction(CommitAction(item), Source.Keyboard) + } + private fun onPopupAction(action: PopupAction) { popupActionListener?.onPopupAction(action) } + private fun onItemLongClick(view: KeyView, popup: KeyDef.Popup.Keyboard) { + if (!popupOnKeyPress) { + // in case "popup on keypress" is disabled, popup keyboard need to know + // the actual bounds on press. see [^1] as well + view.updateBounds() + } + onPopupAction(PopupAction.ShowKeyboardAction(view.id, popup, view.bounds)) + } + + private fun onPopupShow(view: KeyView, item: String) { + if (!popupOnKeyPress) return + // [^1]: bounds is first calculated in KeyView's onLayout(), it + // not in screen viewport at the time of layout. + // e.g. it's inside the next page of ViewPager + // so update bounds when it's pressed + view.updateBounds() + onPopupAction(PopupAction.PreviewAction(view.id, item, view.bounds)) + return + } + private fun onPopupChangeFocus(viewId: Int, x: Float, y: Float): Boolean { val changeFocusAction = PopupAction.ChangeFocusAction(viewId, x, y) - popupActionListener?.onPopupAction(changeFocusAction) + onPopupAction(changeFocusAction) return changeFocusAction.outResult } private fun onPopupTrigger(viewId: Int): Boolean { val triggerAction = PopupAction.TriggerAction(viewId) - // TODO: maybe popup keyboard should just yield String value? onPopupAction(triggerAction) val action = triggerAction.outAction as? FcitxKeyAction ?: return false - onSymbolClick(action.act) + onItemClick(action.act) onPopupAction(PopupAction.DismissAction(viewId)) return true } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPagesAdapter.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPagesAdapter.kt index be5e28cf..a1de4bc5 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPagesAdapter.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPagesAdapter.kt @@ -1,14 +1,13 @@ /* * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2025 Fcitx5 for Android Contributors + * SPDX-FileCopyrightText: Copyright 2021-2026 Fcitx5 for Android Contributors */ package org.fcitx.fcitx5.android.input.picker -import android.text.TextPaint +import android.annotation.SuppressLint import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import org.fcitx.fcitx5.android.data.RecentlyUsed -import org.fcitx.fcitx5.android.data.prefs.AppPrefs import org.fcitx.fcitx5.android.data.theme.Theme import org.fcitx.fcitx5.android.input.keyboard.KeyActionListener import org.fcitx.fcitx5.android.input.popup.PopupActionListener @@ -20,8 +19,8 @@ class PickerPagesAdapter( private val rawData: List>>, private val density: PickerPageUi.Density, recentlyUsedFileName: String, - private val bordered: Boolean = false, - private val isEmoji: Boolean = false + private val bordered: Boolean, + private val policy: PickerPolicy ) : RecyclerView.Adapter() { class ViewHolder(val ui: PickerPageUi) : RecyclerView.ViewHolder(ui.root) @@ -38,17 +37,9 @@ class PickerPagesAdapter( */ private val pages: MutableList> = mutableListOf(listOf()) - private fun buildCategories( - data: List>>, - knowGraphOnly: Boolean = false - ) { - val textPaint = if (knowGraphOnly) TextPaint() else null + private fun buildCategories(data: List>>) { data.forEach { (cat, arr) -> - val list = if (textPaint != null) { - arr.filter { textPaint.hasGlyph(it) } - } else { - arr.asList() - } + val list = arr.filter(policy::filter).map(policy::transform) val chunks = list.chunked(density.pageSize) categories.add(cat to IntRange(pages.size, pages.size + chunks.size - 1)) pages.addAll(chunks) @@ -56,20 +47,29 @@ class PickerPagesAdapter( } init { - buildCategories( - rawData, - isEmoji && AppPrefs.getInstance().symbols.hideUnsupportedEmojis.getValue() - ) + buildCategories(rawData) } - fun rebuildCategories(knowGraphOnly: Boolean = false) { + private fun rebuildCategories() { categories.clear() // empty "RecentlyUsed" category categories.add(PickerData.RecentlyUsedCategory to IntRange(0, 0)) pages.clear() // empty "RecentlyUsed" page pages.add(emptyList()) - buildCategories(rawData, knowGraphOnly) + buildCategories(rawData) + } + + private var lastInvalidateKey = policy.invalidateKey() + + @SuppressLint("NotifyDataSetChanged") + fun refreshIfNeeded() { + val newKey = policy.invalidateKey() + if (lastInvalidateKey != newKey) { + lastInvalidateKey = newKey + rebuildCategories() + notifyDataSetChanged() + } } private val recentlyUsed = RecentlyUsed(recentlyUsedFileName, density.pageSize) @@ -102,18 +102,21 @@ class PickerPagesAdapter( } override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.ui.setItems(pages[position], isEmoji) + if (position == 0) { + // RecentlyUsed content should be displayed as-is, without popups + holder.ui.setItems(recentlyUsed.items) + } else { + holder.ui.setItems(pages[position], policy) + } } override fun onViewAttachedToWindow(holder: ViewHolder) { holder.ui.keyActionListener = keyActionListener - if (holder.bindingAdapterPosition == 0) { + holder.ui.popupActionListener = if (holder.bindingAdapterPosition == 0) { // prevent popup on RecentlyUsed page - holder.ui.popupActionListener = null - // RecentlyUsed content are already modified with skin tones - holder.ui.setItems(recentlyUsed.items, withSkinTone = false) + null } else { - holder.ui.popupActionListener = popupActionListener + popupActionListener } } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPolicy.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPolicy.kt new file mode 100644 index 00000000..e78cf4b5 --- /dev/null +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerPolicy.kt @@ -0,0 +1,76 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2026 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.input.picker + +import android.text.TextPaint +import org.fcitx.fcitx5.android.data.prefs.AppPrefs +import org.fcitx.fcitx5.android.input.keyboard.KeyDef +import org.fcitx.fcitx5.android.input.popup.EmojiModifier + +interface PickerPolicy { + /** + * Whether Picker should include this symbol + */ + fun filter(raw: String): Boolean + + /** + * Actual string to commit when this symbol was selected + */ + fun transform(raw: String): String + + /** + * Long press options for this symbol + */ + fun popup(raw: String): KeyDef.Popup.Keyboard? + + /** + * Return different value to make the Picker rebuild all pages on attach + */ + fun invalidateKey(): Any +} + +/** + * Default policy: show all symbols as provided and never need to rebuild + */ +class DefaultPickerPolicy : PickerPolicy { + override fun filter(raw: String) = true + override fun transform(raw: String) = raw + override fun popup(raw: String) = + KeyDef.Popup.Keyboard.Preset(label = raw, transformPunctuation = false) + + override fun invalidateKey() = Unit +} + +/** + * Emoji policy: filter unsupported glyph, transform skintone, rebuild on preference changes + */ +class EmojiPickerPolicy : PickerPolicy { + data class Prefs( + val hideUnsupportedEmojis: Boolean, + val defaultSkinTone: EmojiModifier.SkinTone + ) + + private val symbolPrefs = AppPrefs.getInstance().symbols + private val hideUnsupportedEmojis by symbolPrefs.hideUnsupportedEmojis + private val defaultSkinTone by symbolPrefs.defaultEmojiSkinTone + + private val prefs get() = Prefs(hideUnsupportedEmojis, defaultSkinTone) + + override fun filter(raw: String): Boolean { + return if (hideUnsupportedEmojis) TextPaint().hasGlyph(raw) else true + } + + override fun transform(raw: String): String { + return EmojiModifier.getPreferredTone(raw, defaultSkinTone) + } + + override fun popup(raw: String): KeyDef.Popup.Keyboard? { + val items = EmojiModifier.produceSkinTones(raw, defaultSkinTone) ?: return null + return KeyDef.Popup.Keyboard.Explicit(items) + } + + override fun invalidateKey(): Prefs = prefs +} diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindow.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindow.kt index 6bfe0a52..20ccfba2 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindow.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindow.kt @@ -1,15 +1,12 @@ /* * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2025 Fcitx5 for Android Contributors + * SPDX-FileCopyrightText: Copyright 2021-2026 Fcitx5 for Android Contributors */ package org.fcitx.fcitx5.android.input.picker -import android.annotation.SuppressLint import androidx.core.content.ContextCompat import androidx.transition.Transition import androidx.viewpager2.widget.ViewPager2 -import org.fcitx.fcitx5.android.data.prefs.AppPrefs -import org.fcitx.fcitx5.android.data.prefs.ManagedPreference import org.fcitx.fcitx5.android.data.theme.ThemeManager import org.fcitx.fcitx5.android.input.broadcast.ReturnKeyDrawableComponent import org.fcitx.fcitx5.android.input.dependency.theme @@ -32,7 +29,8 @@ class PickerWindow( private val density: PickerPageUi.Density, private val switchKey: KeyDef, private val popupPreview: Boolean = true, - private val followKeyBorder: Boolean = true + private val followKeyBorder: Boolean = true, + private val policy: PickerPolicy = DefaultPickerPolicy() ) : InputWindow.ExtendedInputWindow(), EssentialWindow { enum class Key : EssentialWindow.Key { @@ -104,14 +102,12 @@ class PickerWindow( } } - private val isEmoji = key === Key.Emoji - override fun onCreateView() = PickerLayout(context, theme, switchKey).apply { pickerLayout = this val bordered = followKeyBorder && keyBorder pickerPagesAdapter = PickerPagesAdapter( theme, keyActionListener, popupActionListener, data, - density, key.name, bordered, isEmoji + density, key.name, bordered, policy ) tabsUi.apply { setTabs(pickerPagesAdapter.getCategoryList()) @@ -149,39 +145,17 @@ class PickerWindow( override fun onCreateBarExtension() = pickerLayout.tabsUi.root - val symbolPrefs = AppPrefs.getInstance().symbols - private val hideUnsupportedEmojisPrefs = symbolPrefs.hideUnsupportedEmojis - private val defaultEmojiSkinTonePrefs = symbolPrefs.defaultEmojiSkinTone - - @SuppressLint("NotifyDataSetChanged") - private val initDataListener = ManagedPreference.OnChangeListener { _, _ -> - pickerPagesAdapter.rebuildCategories(hideUnsupportedEmojisPrefs.getValue()) - pickerPagesAdapter.notifyDataSetChanged() - }.takeIf { isEmoji } - - @SuppressLint("NotifyDataSetChanged") - private val refreshPagesListener = ManagedPreference.OnChangeListener { _, _ -> - pickerPagesAdapter.notifyDataSetChanged() - } - override fun onAttached() { pickerLayout.embeddedKeyboard.also { + pickerPagesAdapter.refreshIfNeeded() it.onReturnDrawableUpdate(returnKeyDrawable.resourceId) it.keyActionListener = keyActionListener } - if (isEmoji) { - hideUnsupportedEmojisPrefs.registerOnChangeListener(initDataListener!!) - defaultEmojiSkinTonePrefs.registerOnChangeListener(refreshPagesListener) - } } override fun onDetached() { popup.dismissAll() pickerLayout.embeddedKeyboard.keyActionListener = null - if (isEmoji) { - hideUnsupportedEmojisPrefs.unregisterOnChangeListener(initDataListener!!) - defaultEmojiSkinTonePrefs.unregisterOnChangeListener(refreshPagesListener) - } } override val showTitle = false diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindowPreset.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindowPreset.kt index 5fae0d88..61067dd9 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindowPreset.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/picker/PickerWindowPreset.kt @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2021-2023 Fcitx5 for Android Contributors + * SPDX-FileCopyrightText: Copyright 2021-2026 Fcitx5 for Android Contributors */ package org.fcitx.fcitx5.android.input.picker @@ -23,7 +23,8 @@ fun emojiPicker(): PickerWindow = PickerWindow( density = PickerPageUi.Density.Medium, switchKey = TextPickerSwitchKey(":-)", PickerWindow.Key.Emoticon), popupPreview = false, - followKeyBorder = false + followKeyBorder = false, + policy = EmojiPickerPolicy() ) fun emoticonPicker(): PickerWindow = PickerWindow( diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/popup/EmojiModifier.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/popup/EmojiModifier.kt index dedd206b..9104f2b8 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/popup/EmojiModifier.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/popup/EmojiModifier.kt @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: LGPL-2.1-or-later - * SPDX-FileCopyrightText: Copyright 2025 Fcitx5 for Android Contributors + * SPDX-FileCopyrightText: Copyright 2025-2026 Fcitx5 for Android Contributors */ package org.fcitx.fcitx5.android.input.popup @@ -13,7 +13,6 @@ import android.os.Build import android.text.TextPaint import androidx.annotation.RequiresApi import org.fcitx.fcitx5.android.R -import org.fcitx.fcitx5.android.data.prefs.AppPrefs import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceEnum object EmojiModifier { @@ -45,8 +44,6 @@ object EmojiModifier { 0x1F9D1, 0x200D, 0x1F91D, 0x200D, 0x1F9D1, ) - private val defaultSkinTone by AppPrefs.getInstance().symbols.defaultEmojiSkinTone - fun isSupported(): Boolean { // UProperty.EMOJI_MODIFIER_BASE requires API 28 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P @@ -107,22 +104,20 @@ object EmojiModifier { return DefaultTextPaint.hasGlyph(emoji) } - fun getPreferredTone(emoji: String): String { + fun getPreferredTone(emoji: String, tone: SkinTone): String { if (!isSupported()) return emoji val (codePoints, modifiable) = getCodePoints(emoji) - val tone = defaultSkinTone if (tone == SkinTone.Default || !isModifiable(modifiable)) return emoji val candidate = buildEmoji(codePoints, modifiable, tone) return if (isValidEmoji(candidate)) candidate else emoji } - fun produceSkinTones(emoji: String): Array? { + fun produceSkinTones(emoji: String, excludeTone: SkinTone): Array? { if (!isSupported()) return null val (codePoints, modifiable) = getCodePoints(emoji) - val tone = defaultSkinTone if (!isModifiable(modifiable)) return null val candidates = SkinTone.entries - .filter { it != tone } + .filter { it != excludeTone } .map { buildEmoji(codePoints, modifiable, it) } .filter { isValidEmoji(it) } return if (candidates.isEmpty()) null else candidates.toTypedArray() diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupComponent.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupComponent.kt index 3c8b09e8..daa13c42 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupComponent.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/popup/PopupComponent.kt @@ -116,18 +116,23 @@ class PopupComponent : } private fun showKeyboard(viewId: Int, keyboard: KeyDef.Popup.Keyboard, bounds: Rect) { - val keys = PopupPreset[keyboard.label] - ?: EmojiModifier.produceSkinTones(keyboard.label) - ?: return + var keys: Array + var labels: Array + when (keyboard) { + is KeyDef.Popup.Keyboard.Preset -> { + val preset = PopupPreset[keyboard.label] ?: return + keys = preset + labels = if (keyboard.transformPunctuation && punctuation.enabled) { + Array(keys.size) { punctuation.transform(keys[it]) } + } else keys + } + is KeyDef.Popup.Keyboard.Explicit -> { + keys = keyboard.items + labels = keyboard.items + } + } // clear popup preview text OR create empty popup preview showingEntryUi[viewId]?.setText("") ?: showPopup(viewId, "", bounds) - reallyShowKeyboard(viewId, keys, bounds) - } - - private fun reallyShowKeyboard(viewId: Int, keys: Array, bounds: Rect) { - val labels = if (punctuation.enabled) { - Array(keys.size) { punctuation.transform(keys[it]) } - } else keys val keyboardUi = PopupKeyboardUi( context, theme,