From 0faa712f230049d9d7d21cf7950a121225ff25a0 Mon Sep 17 00:00:00 2001
From: SpiritCroc <dev@spiritcroc.de>
Date: Tue, 2 May 2023 12:18:06 +0200
Subject: [PATCH] Fix possible emoji-autocompletion crash

Thread: main, Exception: java.lang.ClassCastException: im.vector.app.features.autocomplete.AutocompleteHeaderItem$Holder cannot be cast to im.vector.app.features.autocomplete.emoji.AutocompleteEmojiItem$Holder
at im.vector.app.features.autocomplete.emoji.AutocompleteEmojiItem_.handlePreBind(AutocompleteEmojiItem_.java:1)
at com.airbnb.epoxy.BaseEpoxyAdapter.onBindViewHolder(BaseEpoxyAdapter.java:22)
at com.airbnb.epoxy.BaseEpoxyAdapter.onBindViewHolder(BaseEpoxyAdapter.java:3)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:43)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:59)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:974)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:54)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:54)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:400)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:67)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:135)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:8)

Change-Id: I4d4919c35babea2606a06b3e99b5c3b3ce08e95d
---
 .../emoji/AutocompleteEmojiController.kt      |  6 +--
 .../emoji/AutocompleteEmojiHeaderItem.kt      | 44 +++++++++++++++++++
 .../emoji/AutocompleteEmojiItem.kt            |  3 ++
 .../emoji/AutocompleteExpandItem.kt           | 12 ++---
 .../res/layout/item_autocomplete_emoji.xml    | 15 +++++++
 5 files changed, 67 insertions(+), 13 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiHeaderItem.kt

diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiController.kt b/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiController.kt
index be4a2d4449..2042b4f989 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiController.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiController.kt
@@ -67,7 +67,7 @@ class AutocompleteEmojiController @Inject constructor(
     }
 
     private fun buildHeaderItem(header: AutocompleteEmojiDataItem.Header) {
-        autocompleteHeaderItem {
+        autocompleteEmojiHeaderItem {
             id("h/${header.id}")
             title(header.title)
         }
@@ -110,9 +110,7 @@ class AutocompleteEmojiController @Inject constructor(
     }
 
     companion object {
-        // Count of standard emoji matches - WARN: do not set to 8 or less, or epoxy/recycler will sometimes crash with some class casts,
-        // e.g. when repeatedly typing `:turt`, then deleting back to `:`?!?
-        // What makes 8 magic? Probably that no-search proposals also returns 8 results? But wtf?
+        // Count of standard emoji matches
         const val STANDARD_EMOJI_MAX = 9
         // Count of emojis for the current room's image pack
         const val CUSTOM_THIS_ROOM_MAX = 8
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiHeaderItem.kt b/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiHeaderItem.kt
new file mode 100644
index 0000000000..0dc4d5028c
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiHeaderItem.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ * Copyright 2023 SpiritCroc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.autocomplete.emoji
+
+import androidx.core.view.isVisible
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.VectorEpoxyModel
+
+// We were getting ClassCastExceptions for the holder of AutocompleteHeaderItem, which sometimes seemed to still be an AutocompleteEmojiItem...
+// https://github.com/SchildiChat/SchildiChat-android-rageshakes/issues/1040
+// So let's build some HeaderItem using the same holder
+@EpoxyModelClass
+abstract class AutocompleteEmojiHeaderItem : VectorEpoxyModel<AutocompleteEmojiItem.Holder>(R.layout.item_autocomplete_emoji) {
+
+    @EpoxyAttribute var title: String? = null
+
+    override fun bind(holder: AutocompleteEmojiItem.Holder) {
+        super.bind(holder)
+
+        holder.titleView.isVisible = true
+        holder.emojiText.isVisible = false
+        holder.emoteImage.isVisible = false
+        holder.emojiKeywordText.isVisible = false
+        holder.emojiNameText.isVisible = false
+        holder.titleView.text = title
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiItem.kt b/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiItem.kt
index f55f27a9a7..881dd2fe3a 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiItem.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteEmojiItem.kt
@@ -51,6 +51,8 @@ abstract class AutocompleteEmojiItem : VectorEpoxyModel<AutocompleteEmojiItem.Ho
 
     override fun bind(holder: Holder) {
         super.bind(holder)
+        holder.titleView.isVisible = false
+        holder.emojiNameText.isVisible = true
         if (emoteUrl?.isNotEmpty().orFalse()) {
             holder.emojiText.isVisible = false
             holder.emoteImage.isVisible = true
@@ -75,5 +77,6 @@ abstract class AutocompleteEmojiItem : VectorEpoxyModel<AutocompleteEmojiItem.Ho
         val emoteImage by bind<ImageView>(R.id.itemAutocompleteEmote)
         val emojiNameText by bind<TextView>(R.id.itemAutocompleteEmojiName)
         val emojiKeywordText by bind<TextView>(R.id.itemAutocompleteEmojiSubname)
+        val titleView by bind<TextView>(R.id.headerItemAutocompleteTitle)
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteExpandItem.kt b/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteExpandItem.kt
index 085dc7eb59..73fe3a63b8 100644
--- a/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteExpandItem.kt
+++ b/vector/src/main/java/im/vector/app/features/autocomplete/emoji/AutocompleteExpandItem.kt
@@ -27,7 +27,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel
 import im.vector.app.core.epoxy.onClick
 import im.vector.app.features.themes.ThemeUtils
 
-@EpoxyModelClass // Re-using item_autocomplete_emoji layout for now because I'm lazy - may want to change that if it causes troubles
+@EpoxyModelClass // Re-using item_autocomplete_emoji to avoid class-cast exceptions like https://github.com/SchildiChat/SchildiChat-android-rageshakes/issues/1040
 abstract class AutocompleteExpandItem : VectorEpoxyModel<AutocompleteEmojiItem.Holder>(R.layout.item_autocomplete_emoji) {
 
     @EpoxyAttribute
@@ -38,8 +38,10 @@ abstract class AutocompleteExpandItem : VectorEpoxyModel<AutocompleteEmojiItem.H
 
     override fun bind(holder: AutocompleteEmojiItem.Holder) {
         super.bind(holder)
+        holder.titleView.isVisible = false
         holder.emojiText.isVisible = false
         holder.emoteImage.isVisible = true
+        holder.emojiNameText.isVisible = true
         holder.emoteImage.setImageResource(R.drawable.ic_expand_more)
         holder.emoteImage.imageTintList = ColorStateList.valueOf(ThemeUtils.getColor(holder.emoteImage.context, R.attr.vctr_content_secondary))
         holder.emojiText.typeface = Typeface.DEFAULT
@@ -54,12 +56,4 @@ abstract class AutocompleteExpandItem : VectorEpoxyModel<AutocompleteEmojiItem.H
         holder.view.onClick(onClickListener)
     }
 
-    /*
-    class Holder : VectorEpoxyHolder() {
-        val emojiText by bind<TextView>(R.id.itemAutocompleteEmoji)
-        val emoteImage by bind<ImageView>(R.id.itemAutocompleteEmote)
-        val emojiNameText by bind<TextView>(R.id.itemAutocompleteEmojiName)
-        val emojiKeywordText by bind<TextView>(R.id.itemAutocompleteEmojiSubname)
-    }
-     */
 }
diff --git a/vector/src/main/res/layout/item_autocomplete_emoji.xml b/vector/src/main/res/layout/item_autocomplete_emoji.xml
index 212dc21780..e6b7199ba7 100644
--- a/vector/src/main/res/layout/item_autocomplete_emoji.xml
+++ b/vector/src/main/res/layout/item_autocomplete_emoji.xml
@@ -9,6 +9,21 @@
     android:padding="8dp"
     tools:viewBindingIgnore="true">
 
+    <!-- this one is only for AutocompleteEmojiHeaderItem...
+    see also https://github.com/SchildiChat/SchildiChat-android-rageshakes/issues/1040
+    (ClassCastException when having different holder types in autocompletions) -->
+    <TextView
+        android:id="@+id/headerItemAutocompleteTitle"
+        style="@style/Widget.Vector.TextView.Subtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:maxLines="1"
+        android:textColor="?vctr_content_secondary"
+        android:ellipsize="end"
+        android:visibility="gone"
+        tools:text="@string/custom_emotes_this_room" />
+
     <TextView
         android:id="@+id/itemAutocompleteEmoji"
         style="@style/Widget.Vector.TextView.Title"