From 7e0e6c2786b9f7b80551728e505ac5daef8765fb Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 7 Jun 2023 12:51:08 -0400 Subject: [PATCH] Fix pool limits and y-translation issues with CFv2 recycler view. --- .../conversation/v2/ConversationAdapterV2.kt | 24 ++++++++++- .../conversation/v2/ConversationFragment.kt | 42 ++++++++++++++++++- .../giph/mp4/GiphyMp4ItemDecoration.kt | 17 ++++++-- .../util/adapter/mapping/MappingAdapter.java | 4 ++ .../adapter/mapping/PagingMappingAdapter.java | 2 +- .../conversation_item_thread_header.xml | 18 ++++++++ 6 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 app/src/main/res/layout/conversation_item_thread_header.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt index 7119187284..69bde9aeef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt @@ -10,6 +10,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.text.HtmlCompat import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.RecyclerView import com.google.android.exoplayer2.MediaItem import org.signal.core.util.logging.Log import org.signal.core.util.toOptional @@ -74,7 +75,7 @@ class ConversationAdapterV2( private val condensedMode: ConversationItemDisplayMode? = null init { - registerFactory(ThreadHeader::class.java, ::ThreadHeaderViewHolder, R.layout.conversation_item_banner) + registerFactory(ThreadHeader::class.java, ::ThreadHeaderViewHolder, R.layout.conversation_item_thread_header) registerFactory(ConversationUpdate::class.java) { parent -> val view = CachedInflater.from(parent.context).inflate(R.layout.conversation_item_update, parent, false) @@ -102,6 +103,27 @@ class ConversationAdapterV2( } } + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + + for ((model, type) in itemTypes) { + val count: Int = when (model) { + ThreadHeader::class.java -> 1 + ConversationUpdate::class.java -> 5 + OutgoingTextOnly::class.java -> 25 + OutgoingMedia::class.java -> 15 + IncomingTextOnly::class.java -> 25 + IncomingMedia::class.java -> 15 + Placeholder::class.java -> 5 + else -> 0 + } + + if (count > 0) { + recyclerView.recycledViewPool.setMaxRecycledViews(type, count) + } + } + } + /** [messagePosition] is one-based index and adapter is zero-based. */ fun getAdapterPositionForMessagePosition(messagePosition: Int): Int { return messagePosition - 1 diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 25b99dfb97..43b3f4a34c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -13,9 +13,11 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter +import android.graphics.Rect import android.net.Uri import android.os.Bundle import android.provider.Settings @@ -38,6 +40,7 @@ import androidx.activity.result.ActivityResultLauncher import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode +import androidx.appcompat.widget.Toolbar import androidx.core.app.ActivityCompat import androidx.core.app.ActivityOptionsCompat import androidx.core.content.ContextCompat @@ -114,6 +117,7 @@ import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity import org.thoughtcrime.securesms.conversation.AttachmentKeyboardButton import org.thoughtcrime.securesms.conversation.BadDecryptLearnMoreDialog import org.thoughtcrime.securesms.conversation.ConversationAdapter +import org.thoughtcrime.securesms.conversation.ConversationHeaderView import org.thoughtcrime.securesms.conversation.ConversationIntents import org.thoughtcrime.securesms.conversation.ConversationIntents.ConversationScreenType import org.thoughtcrime.securesms.conversation.ConversationItem @@ -321,6 +325,7 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) private lateinit var attachmentManager: AttachmentManager private lateinit var multiselectItemDecoration: MultiselectItemDecoration private lateinit var openableGiftItemDecoration: OpenableGiftItemDecoration + private lateinit var threadHeaderMarginDecoration: ThreadHeaderMarginDecoration private var animationsAllowed = false private var actionMode: ActionMode? = null @@ -394,6 +399,8 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) .addTo(disposables) container.fragmentManager = childFragmentManager + + ToolbarDependentMarginListener(binding.toolbar) } override fun onResume() { @@ -422,6 +429,11 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) EventBus.getDefault().unregister(this) } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + ToolbarDependentMarginListener(binding.toolbar) + } + override fun onDestroyView() { super.onDestroyView() if (pinnedShortcutReceiver != null) { @@ -861,7 +873,6 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) adapter::getAdapterPositionForMessagePosition ) - ConversationAdapter.initializePool(binding.conversationItemRecycler.recycledViewPool) adapter.setPagingController(viewModel.pagingController) recyclerViewColorizer = RecyclerViewColorizer(binding.conversationItemRecycler) @@ -895,6 +906,9 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) true } ) + + threadHeaderMarginDecoration = ThreadHeaderMarginDecoration() + binding.conversationItemRecycler.addItemDecoration(threadHeaderMarginDecoration) } private fun initializeGiphyMp4(): GiphyMp4ProjectionRecycler { @@ -2654,4 +2668,30 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } //endregion + + private inner class ToolbarDependentMarginListener(private val toolbar: Toolbar) : ViewTreeObserver.OnGlobalLayoutListener { + + init { + toolbar.viewTreeObserver.addOnGlobalLayoutListener(this) + } + + override fun onGlobalLayout() { + val rect = Rect() + toolbar.getGlobalVisibleRect(rect) + threadHeaderMarginDecoration.toolbarMargin = rect.bottom + 16.dp + binding.conversationItemRecycler.invalidateItemDecorations() + toolbar.viewTreeObserver.removeOnGlobalLayoutListener(this) + } + } + + private inner class ThreadHeaderMarginDecoration : RecyclerView.ItemDecoration() { + var toolbarMargin: Int = 0 + + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + super.getItemOffsets(outRect, view, parent, state) + if (view is ConversationHeaderView) { + outRect.top = toolbarMargin + } + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ItemDecoration.kt b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ItemDecoration.kt index 2442f2e0e7..5f78d1e2a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ItemDecoration.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/giph/mp4/GiphyMp4ItemDecoration.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.giph.mp4 import android.graphics.Canvas +import android.graphics.Rect import androidx.core.view.children import androidx.recyclerview.widget.RecyclerView import org.thoughtcrime.securesms.conversation.ConversationAdapter @@ -27,18 +28,28 @@ class GiphyMp4ItemDecoration( parent.translationY = 0f onRecyclerVerticalTranslationSet(parent.translationY) } else { - val footerViewHolder = parent.children + val threadHeaderViewHolder = parent.children .map { parent.getChildViewHolder(it) } .filter { it is ConversationAdapter.FooterViewHolder || it is ConversationAdapterV2.ThreadHeaderViewHolder } .firstOrNull() - if (footerViewHolder == null) { + if (threadHeaderViewHolder == null) { parent.translationY = 0f onRecyclerVerticalTranslationSet(parent.translationY) return } - val childTop: Int = footerViewHolder.itemView.top + val toolbarMargin = if (threadHeaderViewHolder is ConversationAdapterV2.ThreadHeaderViewHolder) { + // A decorator adds the margin for the toolbar, margin is difference of the bounds "height" and the view height + val bounds = Rect() + parent.getDecoratedBoundsWithMargins(threadHeaderViewHolder.itemView, bounds) + bounds.bottom - bounds.top - threadHeaderViewHolder.itemView.height + } else { + // Deprecated not needed for CFv2 + 0 + } + + val childTop: Int = threadHeaderViewHolder.itemView.top - toolbarMargin parent.translationY = min(0, -childTop).toFloat() onRecyclerVerticalTranslationSet(parent.translationY) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/MappingAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/MappingAdapter.java index d46ad3f5d1..9741e0b8f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/MappingAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/MappingAdapter.java @@ -89,6 +89,10 @@ public class MappingAdapter extends ListAdapter, MappingViewHold registerFactory(clazz, new LayoutFactory<>(creator, layout)); } + public Map, Integer> getItemTypes() { + return new HashMap<>(itemTypes); + } + @Override public int getItemViewType(int position) { Integer type = itemTypes.get(getItem(position).getClass()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java index 38f6c9984d..cd8469a1ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java @@ -93,7 +93,7 @@ public class PagingMappingAdapter extends MappingAdapter { return getItem(position) != null; } - private static class Placeholder implements MappingModel { + protected static class Placeholder implements MappingModel { @Override public boolean areItemsTheSame(@NonNull Placeholder newItem) { return false; diff --git a/app/src/main/res/layout/conversation_item_thread_header.xml b/app/src/main/res/layout/conversation_item_thread_header.xml new file mode 100644 index 0000000000..b60b16cb05 --- /dev/null +++ b/app/src/main/res/layout/conversation_item_thread_header.xml @@ -0,0 +1,18 @@ + + +