From 30759e301e6419235a1c18ed2b1f7d10269c4996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Mon, 28 Aug 2023 10:54:30 +0200 Subject: [PATCH] Add emoji categories to emoji list This closes #229 --- .../keyboard/adapters/EmojisAdapter.kt | 51 ++++++++++--- .../keyboard/helpers/EmojiHelper.kt | 32 +++++++- .../keyboard/views/MyKeyboardView.kt | 75 +++++++++++++++++-- .../drawable/ic_emoji_category_activities.xml | 5 ++ .../drawable/ic_emoji_category_animals.xml | 6 ++ .../res/drawable/ic_emoji_category_flags.xml | 5 ++ .../res/drawable/ic_emoji_category_food.xml | 6 ++ .../drawable/ic_emoji_category_objects.xml | 5 ++ .../res/drawable/ic_emoji_category_people.xml | 6 ++ .../drawable/ic_emoji_category_smileys.xml | 5 ++ .../drawable/ic_emoji_category_symbols.xml | 11 +++ .../res/drawable/ic_emoji_category_travel.xml | 5 ++ app/src/main/res/layout/emoji_list.xml | 8 ++ .../main/res/layout/item_emoji_category.xml | 10 +++ .../res/layout/keyboard_view_keyboard.xml | 17 +++++ app/src/main/res/values/dimens.xml | 2 + 16 files changed, 230 insertions(+), 19 deletions(-) create mode 100644 app/src/main/res/drawable/ic_emoji_category_activities.xml create mode 100644 app/src/main/res/drawable/ic_emoji_category_animals.xml create mode 100644 app/src/main/res/drawable/ic_emoji_category_flags.xml create mode 100644 app/src/main/res/drawable/ic_emoji_category_food.xml create mode 100644 app/src/main/res/drawable/ic_emoji_category_objects.xml create mode 100644 app/src/main/res/drawable/ic_emoji_category_people.xml create mode 100644 app/src/main/res/drawable/ic_emoji_category_smileys.xml create mode 100644 app/src/main/res/drawable/ic_emoji_category_symbols.xml create mode 100644 app/src/main/res/drawable/ic_emoji_category_travel.xml create mode 100644 app/src/main/res/layout/emoji_list.xml create mode 100644 app/src/main/res/layout/item_emoji_category.xml diff --git a/app/src/main/kotlin/com/simplemobiletools/keyboard/adapters/EmojisAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/keyboard/adapters/EmojisAdapter.kt index 48b7b16..b27f20a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/keyboard/adapters/EmojisAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/keyboard/adapters/EmojisAdapter.kt @@ -6,39 +6,72 @@ import android.view.View import android.view.ViewGroup import androidx.emoji2.text.EmojiCompat import androidx.recyclerview.widget.RecyclerView +import com.simplemobiletools.commons.databinding.DividerBinding +import com.simplemobiletools.commons.extensions.beInvisible import com.simplemobiletools.keyboard.databinding.ItemEmojiBinding +import com.simplemobiletools.keyboard.helpers.EmojiData -class EmojisAdapter(val context: Context, var items: List, val itemClick: (emoji: String) -> Unit) : RecyclerView.Adapter() { +class EmojisAdapter(val context: Context, private val items: List, val itemClick: (emoji: EmojiData) -> Unit) : + RecyclerView.Adapter() { private val layoutInflater = LayoutInflater.from(context) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmojisAdapter.ViewHolder { - val view = ItemEmojiBinding.inflate(layoutInflater, parent, false).root - return ViewHolder(view) + return when (viewType) { + ITEM_TYPE_EMOJI -> { + val view = ItemEmojiBinding.inflate(layoutInflater, parent, false).root + ViewHolder(view) + } + + else -> { + val view = DividerBinding.inflate(layoutInflater, parent, false).root.apply { beInvisible() } + ViewHolder(view) + } + } } override fun onBindViewHolder(holder: EmojisAdapter.ViewHolder, position: Int) { val item = items[position] - holder.bindView(item) { itemView -> - setupEmoji(itemView, item) + if (item is Item.Emoji) { + holder.bindView(item) { itemView -> + setupEmoji(itemView, item) + } + } + } + + override fun getItemViewType(position: Int): Int { + return if (items[position] is Item.Emoji) { + ITEM_TYPE_EMOJI + } else { + ITEM_TYPE_CATEGORY } } override fun getItemCount() = items.size - private fun setupEmoji(view: View, emoji: String) { - val processed = EmojiCompat.get().process(emoji) + private fun setupEmoji(view: View, emoji: Item.Emoji) { + val processed = EmojiCompat.get().process(emoji.value.emoji) ItemEmojiBinding.bind(view).emojiValue.text = processed } inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - fun bindView(emoji: String, callback: (itemView: View) -> Unit): View { + fun bindView(emoji: Item.Emoji, callback: (itemView: View) -> Unit): View { return itemView.apply { callback(this) setOnClickListener { - itemClick.invoke(emoji) + itemClick.invoke(emoji.value) } } } } + + sealed interface Item { + data class Emoji(val value: EmojiData) : Item + data class Category(val value: String) : Item + } + + companion object { + private const val ITEM_TYPE_EMOJI = 0 + private const val ITEM_TYPE_CATEGORY = 1 + } } diff --git a/app/src/main/kotlin/com/simplemobiletools/keyboard/helpers/EmojiHelper.kt b/app/src/main/kotlin/com/simplemobiletools/keyboard/helpers/EmojiHelper.kt index 89d622a..6e34c65 100644 --- a/app/src/main/kotlin/com/simplemobiletools/keyboard/helpers/EmojiHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/keyboard/helpers/EmojiHelper.kt @@ -1,10 +1,11 @@ package com.simplemobiletools.keyboard.helpers import android.content.Context +import com.simplemobiletools.keyboard.R import org.json.JSONObject import java.io.InputStream -private var cachedEmojiData: MutableList? = null +private var cachedEmojiData: MutableList? = null val cachedVNTelexData: HashMap = HashMap() /** @@ -14,18 +15,21 @@ val cachedVNTelexData: HashMap = HashMap() * @param context The initiating view's context. * @param path The path to the asset file. */ -fun parseRawEmojiSpecsFile(context: Context, path: String): MutableList { +fun parseRawEmojiSpecsFile(context: Context, path: String): MutableList { if (cachedEmojiData != null) { return cachedEmojiData!! } - val emojis = mutableListOf() + val emojis = mutableListOf() var emojiEditorList: MutableList? = null + var category: String? = null fun commitEmojiEditorList() { emojiEditorList?.let { // add only the base emoji for now, ignore the variations - emojis.add(it.first()) + val base = it.first() + val variants = it.drop(1) + emojis.add(EmojiData(category ?: "none", base, variants)) } emojiEditorList = null } @@ -36,6 +40,7 @@ fun parseRawEmojiSpecsFile(context: Context, path: String): MutableList // Comment line } else if (line.startsWith("[")) { commitEmojiEditorList() + category = line.replace("[", "").replace("]", "") } else if (line.trim().isEmpty()) { // Empty line continue @@ -84,3 +89,22 @@ fun parseRawJsonSpecsFile(context: Context, path: String): HashMap +) { + fun getCategoryIcon(): Int = + when (category) { + "people_body" -> R.drawable.ic_emoji_category_people + "animals_nature" -> R.drawable.ic_emoji_category_animals + "food_drink" -> R.drawable.ic_emoji_category_food + "travel_places" -> R.drawable.ic_emoji_category_travel + "activities" -> R.drawable.ic_emoji_category_activities + "objects" -> R.drawable.ic_emoji_category_objects + "symbols" -> R.drawable.ic_emoji_category_symbols + "flags" -> R.drawable.ic_emoji_category_flags + else -> R.drawable.ic_emoji_category_smileys + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboardView.kt b/app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboardView.kt index db67102..5cf1bd6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboardView.kt +++ b/app/src/main/kotlin/com/simplemobiletools/keyboard/views/MyKeyboardView.kt @@ -7,6 +7,7 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.content.res.ColorStateList import android.graphics.* import android.graphics.Paint.Align import android.graphics.drawable.* @@ -21,6 +22,7 @@ import android.view.animation.AccelerateInterpolator import android.view.inputmethod.EditorInfo import android.widget.LinearLayout import android.widget.PopupWindow +import android.widget.RadioGroup import android.widget.TextView import android.widget.inline.InlineContentView import androidx.annotation.RequiresApi @@ -29,6 +31,9 @@ import androidx.core.animation.doOnStart import androidx.core.view.* import androidx.emoji2.text.EmojiCompat import androidx.emoji2.text.EmojiCompat.EMOJI_SUPPORTED +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup +import androidx.recyclerview.widget.LinearLayoutManager import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.helpers.isPiePlus @@ -37,6 +42,7 @@ import com.simplemobiletools.keyboard.activities.ManageClipboardItemsActivity import com.simplemobiletools.keyboard.activities.SettingsActivity import com.simplemobiletools.keyboard.adapters.ClipsKeyboardAdapter import com.simplemobiletools.keyboard.adapters.EmojisAdapter +import com.simplemobiletools.keyboard.databinding.ItemEmojiCategoryBinding import com.simplemobiletools.keyboard.databinding.KeyboardKeyPreviewBinding import com.simplemobiletools.keyboard.databinding.KeyboardPopupKeyboardBinding import com.simplemobiletools.keyboard.databinding.KeyboardViewKeyboardBinding @@ -1529,8 +1535,8 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut } val emojis = fullEmojiList.filter { emoji -> - systemFontPaint.hasGlyph(emoji) || (EmojiCompat.get().loadState == EmojiCompat.LOAD_STATE_SUCCEEDED && EmojiCompat.get() - .getEmojiMatch(emoji, emojiCompatMetadataVersion) == EMOJI_SUPPORTED) + systemFontPaint.hasGlyph(emoji.emoji) || (EmojiCompat.get().loadState == EmojiCompat.LOAD_STATE_SUCCEEDED && EmojiCompat.get() + .getEmojiMatch(emoji.emoji, emojiCompatMetadataVersion) == EMOJI_SUPPORTED) } Handler(Looper.getMainLooper()).post { @@ -1546,19 +1552,76 @@ class MyKeyboardView @JvmOverloads constructor(context: Context, attrs: Attribut } } - private fun setupEmojiAdapter(emojis: List) { + private fun setupEmojiAdapter(emojis: List) { + val categories = emojis.groupBy { it.category } + val allItems = mutableListOf() + categories.entries.forEach { (category, emojis) -> + allItems.add(EmojisAdapter.Item.Category(category)) + allItems.addAll(emojis.map(EmojisAdapter.Item::Emoji)) + } + val checkIds = mutableMapOf() + val checkedChangedListener: (RadioGroup, Int) -> Unit = { _, checkedId -> + (keyboardViewBinding?.emojisList?.layoutManager as? GridLayoutManager)?.scrollToPositionWithOffset( + allItems.indexOfFirst { it is EmojisAdapter.Item.Category && it.value == checkIds[checkedId] }, + 0 + ) + } + keyboardViewBinding?.emojiCategoriesStrip?.apply { + removeAllViews() + this.setOnCheckedChangeListener(checkedChangedListener) + categories.entries.forEach { (category, emojis) -> + ItemEmojiCategoryBinding.inflate(LayoutInflater.from(context), this, true).apply { + root.id = generateViewId() + checkIds[root.id] = category + root.setButtonDrawable(emojis.first().getCategoryIcon()) + root.buttonTintList = ColorStateList( + arrayOf( + intArrayOf(android.R.attr.state_checked), + intArrayOf(-android.R.attr.state_checked), + ), + intArrayOf( + context.getProperPrimaryColor(), + Color.WHITE + ) + ) + } + } + } keyboardViewBinding?.emojisList?.apply { val emojiItemWidth = context.resources.getDimensionPixelSize(R.dimen.emoji_item_size) val emojiTopBarElevation = context.resources.getDimensionPixelSize(R.dimen.emoji_top_bar_elevation).toFloat() - layoutManager = AutoGridLayoutManager(context, emojiItemWidth) - adapter = EmojisAdapter(context = context, items = emojis) { emoji -> - mOnKeyboardActionListener!!.onText(emoji) + layoutManager = AutoGridLayoutManager(context, emojiItemWidth).apply { + spanSizeLookup = object : SpanSizeLookup() { + override fun getSpanSize(position: Int): Int = + if (allItems[position] is EmojisAdapter.Item.Category) { + spanCount + } else { + 1 + } + } + } + adapter = EmojisAdapter(context = context, items = allItems) { emoji -> + mOnKeyboardActionListener!!.onText(emoji.emoji) vibrateIfNeeded() } + clearOnScrollListeners() onScroll { keyboardViewBinding!!.emojiPaletteTopBar.elevation = if (it > 4) emojiTopBarElevation else 0f + (keyboardViewBinding?.emojisList?.layoutManager as? LinearLayoutManager)?.findFirstCompletelyVisibleItemPosition()?.also { firstVisibleIndex -> + allItems + .withIndex() + .lastOrNull { it.value is EmojisAdapter.Item.Category && it.index <= firstVisibleIndex } + ?.also { activeCategory -> + val id = checkIds.entries.first { it.value == (activeCategory.value as EmojisAdapter.Item.Category).value }.key + keyboardViewBinding?.emojiCategoriesStrip?.apply { + setOnCheckedChangeListener(null) + check(id) + setOnCheckedChangeListener(checkedChangedListener) + } + } + } } } } diff --git a/app/src/main/res/drawable/ic_emoji_category_activities.xml b/app/src/main/res/drawable/ic_emoji_category_activities.xml new file mode 100644 index 0000000..490be07 --- /dev/null +++ b/app/src/main/res/drawable/ic_emoji_category_activities.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_emoji_category_animals.xml b/app/src/main/res/drawable/ic_emoji_category_animals.xml new file mode 100644 index 0000000..1b92b4d --- /dev/null +++ b/app/src/main/res/drawable/ic_emoji_category_animals.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/drawable/ic_emoji_category_flags.xml b/app/src/main/res/drawable/ic_emoji_category_flags.xml new file mode 100644 index 0000000..138d59f --- /dev/null +++ b/app/src/main/res/drawable/ic_emoji_category_flags.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_emoji_category_food.xml b/app/src/main/res/drawable/ic_emoji_category_food.xml new file mode 100644 index 0000000..5d34b3d --- /dev/null +++ b/app/src/main/res/drawable/ic_emoji_category_food.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/drawable/ic_emoji_category_objects.xml b/app/src/main/res/drawable/ic_emoji_category_objects.xml new file mode 100644 index 0000000..e583c54 --- /dev/null +++ b/app/src/main/res/drawable/ic_emoji_category_objects.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_emoji_category_people.xml b/app/src/main/res/drawable/ic_emoji_category_people.xml new file mode 100644 index 0000000..b48d19e --- /dev/null +++ b/app/src/main/res/drawable/ic_emoji_category_people.xml @@ -0,0 +1,6 @@ + + + + diff --git a/app/src/main/res/drawable/ic_emoji_category_smileys.xml b/app/src/main/res/drawable/ic_emoji_category_smileys.xml new file mode 100644 index 0000000..338e483 --- /dev/null +++ b/app/src/main/res/drawable/ic_emoji_category_smileys.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_emoji_category_symbols.xml b/app/src/main/res/drawable/ic_emoji_category_symbols.xml new file mode 100644 index 0000000..59c06cd --- /dev/null +++ b/app/src/main/res/drawable/ic_emoji_category_symbols.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_emoji_category_travel.xml b/app/src/main/res/drawable/ic_emoji_category_travel.xml new file mode 100644 index 0000000..e1aa314 --- /dev/null +++ b/app/src/main/res/drawable/ic_emoji_category_travel.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/emoji_list.xml b/app/src/main/res/layout/emoji_list.xml new file mode 100644 index 0000000..5baf167 --- /dev/null +++ b/app/src/main/res/layout/emoji_list.xml @@ -0,0 +1,8 @@ + diff --git a/app/src/main/res/layout/item_emoji_category.xml b/app/src/main/res/layout/item_emoji_category.xml new file mode 100644 index 0000000..3fdd618 --- /dev/null +++ b/app/src/main/res/layout/item_emoji_category.xml @@ -0,0 +1,10 @@ + + diff --git a/app/src/main/res/layout/keyboard_view_keyboard.xml b/app/src/main/res/layout/keyboard_view_keyboard.xml index 83e2f8d..3ddcecc 100644 --- a/app/src/main/res/layout/keyboard_view_keyboard.xml +++ b/app/src/main/res/layout/keyboard_view_keyboard.xml @@ -164,6 +164,23 @@ android:text="@string/emojis" android:textSize="@dimen/big_text_size" /> + + + + + -10dp 28dp 46dp + 24dp 4dp 42dp 200dp @@ -17,4 +18,5 @@ 26sp 16sp 28sp + 14sp