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"