From 7a0bd15e4a3b7c2ad4e9a222ef5dd22e35779cde Mon Sep 17 00:00:00 2001 From: Adolfo Santiago Date: Fri, 5 Sep 2025 07:31:03 +0200 Subject: [PATCH 1/2] Improve performance while loading emojis --- .../emojireactions/CustomEmojiPickerPage.kt | 27 ++++--- .../CustomEmojiPickerViewModel.kt | 4 +- .../view/emojireactions/ListEmojiAdapter.kt | 71 +++++++++++++++++++ .../main/res/layout/layout_emoji_custom.xml | 61 +++++++++++----- 4 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/ListEmojiAdapter.kt diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerPage.kt b/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerPage.kt index a2d5746b..ba9412fb 100644 --- a/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerPage.kt +++ b/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerPage.kt @@ -10,9 +10,9 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.GridLayoutManager -import com.keylesspalace.tusky.adapter.EmojiAdapter import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener import com.keylesspalace.tusky.core.extensions.afterTextChanged +import com.keylesspalace.tusky.core.extensions.gone import com.keylesspalace.tusky.databinding.LayoutEmojiCustomBinding import com.keylesspalace.tusky.entity.Emoji import kotlinx.coroutines.launch @@ -25,6 +25,16 @@ class CustomEmojiPickerPage( private lateinit var binding: LayoutEmojiCustomBinding private val customEmojiViewModel by viewModel() + private val adapter by lazy { + ListEmojiAdapter( + object : OnEmojiSelectedListener { + override fun onEmojiSelected(shortcode: String) { + onReactionCallback(shortcode) + } + }, + animateEmojis = false + ) + } override fun onCreateView( inflater: LayoutInflater, @@ -51,15 +61,12 @@ class CustomEmojiPickerPage( viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { customEmojiViewModel.emojis.collect { emojis -> - binding.emojiGrid.adapter = EmojiAdapter( - emojis, - object : OnEmojiSelectedListener { - override fun onEmojiSelected(shortcode: String) { - onReactionCallback(shortcode) - } - }, - animateEmojis = false // TODO - ) + adapter.setAnimateEmojis(false) + + binding.emojiGrid.adapter = adapter + adapter.submitList(emojis) + + binding.loadingOverlay.gone() } } } diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerViewModel.kt b/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerViewModel.kt index b0c57be6..408abf8c 100644 --- a/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerViewModel.kt +++ b/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerViewModel.kt @@ -36,7 +36,9 @@ class CustomEmojiPickerViewModel : ViewModel() { ) fun setEmojis(list: List) { - allEmojis.value = list + allEmojis.value = list.filter { emoji -> + emoji.visibleInPicker == null || emoji.visibleInPicker + }.sortedBy { it.shortcode.lowercase() } } fun updateQuery(newQuery: String) { diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/ListEmojiAdapter.kt b/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/ListEmojiAdapter.kt new file mode 100644 index 00000000..670cbc6a --- /dev/null +++ b/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/ListEmojiAdapter.kt @@ -0,0 +1,71 @@ +package com.keylesspalace.tusky.view.emojireactions + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.github.penfeizhou.animation.glide.AnimationDecoderOption +import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener +import com.keylesspalace.tusky.databinding.ItemEmojiButtonBinding +import com.keylesspalace.tusky.entity.Emoji + +class ListEmojiAdapter( + private val onEmojiSelectedListener: OnEmojiSelectedListener, + private var animateEmojis: Boolean +) : ListAdapter(EmojiDiffer) { + + private object EmojiDiffer : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Emoji, newItem: Emoji): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame(oldItem: Emoji, newItem: Emoji): Boolean { + return ((oldItem.category == newItem.category) && + (oldItem.shortcode == newItem.shortcode) && + (oldItem.url == newItem.url) && + (oldItem.staticUrl == newItem.staticUrl) && + (oldItem.visibleInPicker == newItem.visibleInPicker)) + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): ListEmojiHolder { + return ListEmojiHolder( + ItemEmojiButtonBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + override fun onBindViewHolder( + holder: ListEmojiHolder, + position: Int + ) { + val emoji = getItem(position) + + Glide.with(holder.layout.composeEmojiButton) + .load(emoji.url) + .set(AnimationDecoderOption.DISABLE_ANIMATION_GIF_DECODER, !animateEmojis) + .set(AnimationDecoderOption.DISABLE_ANIMATION_WEBP_DECODER, !animateEmojis) + .set(AnimationDecoderOption.DISABLE_ANIMATION_APNG_DECODER, !animateEmojis) + .into(holder.layout.composeEmojiButton) + + holder.layout.composeEmojiButton.setOnClickListener { + onEmojiSelectedListener.onEmojiSelected(emoji.shortcode) + } + + holder.layout.composeEmojiButton.contentDescription = emoji.shortcode + } + + fun setAnimateEmojis(animate: Boolean) { + animateEmojis = animate + } + + inner class ListEmojiHolder(val layout: ItemEmojiButtonBinding) : RecyclerView.ViewHolder(layout.composeEmojiButton) +} diff --git a/husky/app/src/main/res/layout/layout_emoji_custom.xml b/husky/app/src/main/res/layout/layout_emoji_custom.xml index 32d013fb..1ac88ff6 100644 --- a/husky/app/src/main/res/layout/layout_emoji_custom.xml +++ b/husky/app/src/main/res/layout/layout_emoji_custom.xml @@ -1,24 +1,51 @@ - + android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:orientation="vertical"> - + + + + + + + android:layout_height="300dp" + android:background="?android:attr/colorBackground" + android:gravity="center" + android:orientation="vertical"> + + + + + + - + From c4d7750c7e43f4550f3e37b04145a65615e794f2 Mon Sep 17 00:00:00 2001 From: Adolfo Santiago Date: Fri, 5 Sep 2025 07:48:17 +0200 Subject: [PATCH 2/2] Animate custom emojis in the selector --- .../tusky/view/emojireactions/CustomEmojiPickerPage.kt | 8 +++++--- .../tusky/view/emojireactions/ListEmojiAdapter.kt | 4 ---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerPage.kt b/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerPage.kt index ba9412fb..1dc5acfb 100644 --- a/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerPage.kt +++ b/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/CustomEmojiPickerPage.kt @@ -1,6 +1,7 @@ package com.keylesspalace.tusky.view.emojireactions import android.content.Context +import android.content.SharedPreferences import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -15,7 +16,9 @@ import com.keylesspalace.tusky.core.extensions.afterTextChanged import com.keylesspalace.tusky.core.extensions.gone import com.keylesspalace.tusky.databinding.LayoutEmojiCustomBinding import com.keylesspalace.tusky.entity.Emoji +import com.keylesspalace.tusky.settings.PrefKeys import kotlinx.coroutines.launch +import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel class CustomEmojiPickerPage( @@ -24,6 +27,7 @@ class CustomEmojiPickerPage( ) : Fragment() { private lateinit var binding: LayoutEmojiCustomBinding + private val preferences: SharedPreferences by inject() private val customEmojiViewModel by viewModel() private val adapter by lazy { ListEmojiAdapter( @@ -32,7 +36,7 @@ class CustomEmojiPickerPage( onReactionCallback(shortcode) } }, - animateEmojis = false + animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ) } @@ -61,8 +65,6 @@ class CustomEmojiPickerPage( viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { customEmojiViewModel.emojis.collect { emojis -> - adapter.setAnimateEmojis(false) - binding.emojiGrid.adapter = adapter adapter.submitList(emojis) diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/ListEmojiAdapter.kt b/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/ListEmojiAdapter.kt index 670cbc6a..92fe5838 100644 --- a/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/ListEmojiAdapter.kt +++ b/husky/app/src/main/java/com/keylesspalace/tusky/view/emojireactions/ListEmojiAdapter.kt @@ -63,9 +63,5 @@ class ListEmojiAdapter( holder.layout.composeEmojiButton.contentDescription = emoji.shortcode } - fun setAnimateEmojis(animate: Boolean) { - animateEmojis = animate - } - inner class ListEmojiHolder(val layout: ItemEmojiButtonBinding) : RecyclerView.ViewHolder(layout.composeEmojiButton) }