From 615fde1a7bc5a124d8001a0e31e3988f0b3d89be Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 10 Dec 2023 10:49:05 +0100 Subject: [PATCH] add new parser for emoji arrays --- .../openboard/inputmethod/keyboard/Key.java | 41 +++++++ .../keyboard/emoji/DynamicGridKeyboard.java | 4 +- .../keyboard/internal/KeyboardBuilder.kt | 24 ++-- .../internal/keyboard_parser/EmojiParser.kt | 116 ++++++++++++++++++ 4 files changed, 168 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/EmojiParser.kt diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/Key.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/Key.java index ca74c52c5..32654dc22 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/Key.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/Key.java @@ -1326,6 +1326,47 @@ public class Key implements Comparable { mEnabled = true; } + /** constructor for emoji parser */ // essentially the same as the GridRows constructor, but without coordinates and outputText + public KeyParams(@Nullable final String label, final int code, @Nullable final String hintLabel, + @Nullable final String moreKeySpecs, final int labelFlags, final KeyboardParams params) { + mKeyboardParams = params; + mHintLabel = hintLabel; + mLabelFlags = labelFlags; + mBackgroundType = BACKGROUND_TYPE_EMPTY; + + if (moreKeySpecs != null) { + String[] moreKeys = MoreKeySpec.splitKeySpecs(moreKeySpecs); + mMoreKeysColumnAndFlags = getMoreKeysColumnAndFlagsAndSetNullInArray(params, moreKeys); + + moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, null); + int actionFlags = 0; + if (moreKeys != null) { + actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS; + mMoreKeys = new MoreKeySpec[moreKeys.length]; + for (int i = 0; i < moreKeys.length; i++) { + mMoreKeys[i] = new MoreKeySpec(moreKeys[i], false, Locale.getDefault()); + } + } else { + mMoreKeys = null; + } + mActionFlags = actionFlags; + } else { + // TODO: Pass keyActionFlags as an argument. + mActionFlags = ACTION_FLAGS_NO_KEY_PREVIEW; + mMoreKeys = null; + mMoreKeysColumnAndFlags = 0; + } + + mLabel = label; + mOptionalAttributes = code == Constants.CODE_OUTPUT_TEXT + ? OptionalAttributes.newInstance(label, CODE_UNSPECIFIED, ICON_UNDEFINED, 0, 0) + : null; + mCode = code; + mEnabled = (code != CODE_UNSPECIFIED); + mIconId = KeyboardIconsSet.ICON_UNDEFINED; + mKeyVisualAttributes = null; + } + /** constructor for */ public KeyParams(@Nullable final String label, final int code, @Nullable final String outputText, @Nullable final String hintLabel, @Nullable final String moreKeySpecs, diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/emoji/DynamicGridKeyboard.java b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/emoji/DynamicGridKeyboard.java index 0a11a7604..bd34e288d 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/emoji/DynamicGridKeyboard.java +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/emoji/DynamicGridKeyboard.java @@ -6,6 +6,8 @@ package org.dslul.openboard.inputmethod.keyboard.emoji; +import static org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.EmojiParserKt.EMOJI_HINT_LABEL; + import android.content.SharedPreferences; import android.text.TextUtils; import android.util.Log; @@ -115,7 +117,7 @@ final class DynamicGridKeyboard extends Keyboard { // if key comes from another keyboard (ie. a {@link MoreKeysKeyboard}). final boolean dropMoreKeys = mIsRecents; // Check if hint was a more emoji indicator and prevent its copy if more keys aren't copied - final boolean dropHintLabel = dropMoreKeys && "\u25E5".equals(usedKey.getHintLabel()); + final boolean dropHintLabel = dropMoreKeys && EMOJI_HINT_LABEL.equals(usedKey.getHintLabel()); final GridKey key = new GridKey(usedKey, dropMoreKeys ? null : usedKey.getMoreKeys(), dropHintLabel ? null : usedKey.getHintLabel(), diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardBuilder.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardBuilder.kt index 19c7076d3..91b7727ee 100644 --- a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardBuilder.kt +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/KeyboardBuilder.kt @@ -16,7 +16,7 @@ import org.dslul.openboard.inputmethod.keyboard.Key import org.dslul.openboard.inputmethod.keyboard.Key.KeyParams import org.dslul.openboard.inputmethod.keyboard.Keyboard import org.dslul.openboard.inputmethod.keyboard.KeyboardId -import org.dslul.openboard.inputmethod.keyboard.MoreKeysKeyboard +import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.EmojiParser import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.KeyboardParser import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.XmlKeyboardParser import org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser.addLocaleKeyTextsToParams @@ -25,7 +25,6 @@ import org.dslul.openboard.inputmethod.latin.R import org.dslul.openboard.inputmethod.latin.common.Constants import org.dslul.openboard.inputmethod.latin.define.DebugFlags import org.dslul.openboard.inputmethod.latin.settings.Settings -import org.dslul.openboard.inputmethod.latin.suggestions.MoreSuggestions import org.dslul.openboard.inputmethod.latin.utils.sumOf import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException @@ -68,18 +67,6 @@ open class KeyboardBuilder(protected val mContext: Context, return this // todo: further plan - // make the remove duplicate moreKey thing an option? - // why is it on for serbian (latin), but not for german (german)? - // only nordic and serbian_qwertz layouts have it disabled, default is enabled - // -> add the option, but disable it by default for all layouts - // migrate emoji layouts to this style - // emojis are defined in that string array, should be simple to handle - // parsing could be done into a single row, which is then split as needed - // this might help with split layout (no change in key size, but in number of rows) - // write another parser, it should already consider split - // add a setting to display all emojis (and use emojiv2 or emojicompat or whatever is necessary) - // mention in subtitle that they may not be displayed properly, depending on the app you're writing in - // uncomment the toast below // number layouts missing details // landscape: numpad layout has some extra keys // tablet: number and phone layout have some extra keys (unify with numpad, so that extra keys show both in land and sw600? or only land?) @@ -165,6 +152,12 @@ open class KeyboardBuilder(protected val mContext: Context, readAttributes(xmlId) return this } + if (id.mElementId >= KeyboardId.ELEMENT_EMOJI_RECENTS && id.mElementId <= KeyboardId.ELEMENT_EMOJI_CATEGORY16) { + mParams.mId = id + readAttributes(R.xml.kbd_emoji_category1) // all the same anyway, gridRows are ignored + keysInRows = EmojiParser(mParams, mContext).parse(Settings.getInstance().current.mIsSplitKeyboardEnabled) + return this + } if (loadFromAssets(id) != null) { if (!DebugFlags.DEBUG_ENABLED) return this @@ -242,8 +235,7 @@ open class KeyboardBuilder(protected val mContext: Context, if (DebugFlags.DEBUG_ENABLED) { // looks like only emoji keyboards are still using the old parser, which is expected Log.w(TAG, "falling back to old parser for $id") - if (mParams.mId.mElementId < KeyboardId.ELEMENT_EMOJI_RECENTS || mParams.mId.mElementId > KeyboardId.ELEMENT_EMOJI_CATEGORY16) - Toast.makeText(mContext, "using old parser for $id", Toast.LENGTH_LONG).show() + Toast.makeText(mContext, "using old parser for $id", Toast.LENGTH_LONG).show() } } mParams.mId = id diff --git a/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/EmojiParser.kt b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/EmojiParser.kt new file mode 100644 index 000000000..18befa5e6 --- /dev/null +++ b/app/src/main/java/org/dslul/openboard/inputmethod/keyboard/internal/keyboard_parser/EmojiParser.kt @@ -0,0 +1,116 @@ +package org.dslul.openboard.inputmethod.keyboard.internal.keyboard_parser + +import android.content.Context +import android.os.Build +import org.dslul.openboard.inputmethod.keyboard.Key +import org.dslul.openboard.inputmethod.keyboard.Key.KeyParams +import org.dslul.openboard.inputmethod.keyboard.KeyboardId +import org.dslul.openboard.inputmethod.keyboard.internal.KeyboardParams +import org.dslul.openboard.inputmethod.latin.R +import org.dslul.openboard.inputmethod.latin.common.Constants +import org.dslul.openboard.inputmethod.latin.common.StringUtils + +class EmojiParser(private val params: KeyboardParams, private val context: Context) { + + fun parse(splitKeyboard: Boolean): ArrayList> { // todo: split should be read from params, but currently this is disabled, right? + val emojiArrayId = when (params.mId.mElementId) { + KeyboardId.ELEMENT_EMOJI_RECENTS -> R.array.emoji_recents + KeyboardId.ELEMENT_EMOJI_CATEGORY1 -> R.array.emoji_smileys_emotion + KeyboardId.ELEMENT_EMOJI_CATEGORY2 -> R.array.emoji_people_body + KeyboardId.ELEMENT_EMOJI_CATEGORY3 -> R.array.emoji_animals_nature + KeyboardId.ELEMENT_EMOJI_CATEGORY4 -> R.array.emoji_food_drink + KeyboardId.ELEMENT_EMOJI_CATEGORY5 -> R.array.emoji_travel_places + KeyboardId.ELEMENT_EMOJI_CATEGORY6 -> R.array.emoji_activities + KeyboardId.ELEMENT_EMOJI_CATEGORY7 -> R.array.emoji_objects + KeyboardId.ELEMENT_EMOJI_CATEGORY8 -> R.array.emoji_symbols + KeyboardId.ELEMENT_EMOJI_CATEGORY9 -> R.array.emoji_flags + KeyboardId.ELEMENT_EMOJI_CATEGORY10 -> R.array.emoji_emoticons + else -> throw(IllegalStateException("can only parse emoji categories where an array exists")) + } + val emojiArray = context.resources.getStringArray(emojiArrayId) + val moreEmojisArray = if (params.mId.mElementId == KeyboardId.ELEMENT_EMOJI_CATEGORY2) + context.resources.getStringArray(R.array.emoji_people_body_more) + else null + if (moreEmojisArray != null && emojiArray.size != moreEmojisArray.size) + throw(IllegalStateException("Inconsistent array size between codesArray and moreKeysArray")) + + // now we have the params in one long list -> split into lines and maybe add spacer + // todo: disabled, because it doesn't work properly... spacer keys get added to the end every 3 rows + // the sorting and sizing seems to be done in DynamicGridKeyboard + // only the template keys there are relevant for dimensions, resizing keys here doesn't have any effect + // -> this is really weird and unexpected, and should be changed (might also help with the text emojis...) +/* val numColumns = (1 / params.mDefaultRelativeKeyWidth).toInt() + val spacerNumKeys: Int + val spacerWidth: Float + if (splitKeyboard) { + val spacerRelativeWidth = Settings.getInstance().current.mSpacerRelativeWidth + // adjust gaps for the whole keyboard, so it's the same for all rows + params.mRelativeHorizontalGap *= 1f / (1f + spacerRelativeWidth) + params.mHorizontalGap = (params.mRelativeHorizontalGap * params.mId.mWidth).toInt() + // round the spacer width, so it's a number of keys, and number should be even if emoji count is even, odd otherwise + spacerNumKeys = (spacerRelativeWidth / params.mDefaultRelativeKeyWidth).roundTo(numColumns % 2 == 0) + spacerWidth = spacerNumKeys * params.mDefaultRelativeKeyWidth + } else { + spacerNumKeys = 0 + spacerWidth = 0f + } + val spacerIndex = if (spacerNumKeys > 0) (numColumns - spacerNumKeys) / 2 else -1 +*/ + val row = ArrayList(emojiArray.size) + var currentX = params.mLeftPadding.toFloat() + val currentY = params.mTopPadding.toFloat() + emojiArray.forEachIndexed { i, codeArraySpec -> + val keyParams = parseEmojiKey(codeArraySpec, moreEmojisArray?.get(i)?.takeIf { it.isNotEmpty() }) ?: return@forEachIndexed + keyParams.setDimensionsFromRelativeSize(currentX, currentY) + currentX += keyParams.mFullWidth // exact value seems to be not really relevant, but keeping 0 doesn't work + row.add(keyParams) +// if (row.size % numColumns == spacerIndex) { // also removed for now (would be missing setting the size and updating x +// repeat(spacerNumKeys) { row.add(KeyParams.newSpacer(params, params.mDefaultRelativeKeyWidth)) } +// } + } + return arrayListOf(row) + } + +// private fun Float.roundTo(even: Boolean) = if (toInt() % 2 == if (even) 0 else 1) toInt() else toInt() + 1 + + private fun getLabelAndCode(spec: String): Pair? { + val specAndSdk = spec.split("||") + if (specAndSdk.getOrNull(1)?.toIntOrNull()?.let { it > Build.VERSION.SDK_INT } == true) return null + if ("," !in specAndSdk.first()) { + val code = specAndSdk.first().toIntOrNull(16) ?: return specAndSdk.first() to Constants.CODE_OUTPUT_TEXT // text emojis + val label = StringUtils.newSingleCodePointString(code) + return label to code + } + val labelBuilder = StringBuilder() + for (codePointString in specAndSdk.first().split(",")) { + val cp = codePointString.toInt(16) + labelBuilder.appendCodePoint(cp) + } + return labelBuilder.toString() to Constants.CODE_OUTPUT_TEXT + } + + private fun parseEmojiKey(spec: String, moreKeysString: String? = null): Key.KeyParams? { + val (label, code) = getLabelAndCode(spec) ?: return null + val sb = StringBuilder() + moreKeysString?.split(";")?.let { moreKeys -> + moreKeys.forEach { + val (mkLabel, _) = getLabelAndCode(it) ?: return@forEach + sb.append(mkLabel).append(",") + } + } + val moreKeysSpec = if (sb.isNotEmpty()) { + sb.deleteCharAt(sb.length - 1) + sb.toString() + } else null + return KeyParams( + label, + code, + if (moreKeysSpec != null) EMOJI_HINT_LABEL else null, + moreKeysSpec, + Key.LABEL_FLAGS_FONT_NORMAL, + params + ) + } +} + +const val EMOJI_HINT_LABEL = "◥" \ No newline at end of file