From 454b1f69ed72773c1cf0646a2ea03e05566ae82b Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Thu, 7 Sep 2023 11:26:45 -0300 Subject: [PATCH] Suppress LayoutTransition during scroll events. --- .../v2/items/V2ConversationItemShapeTest.kt | 1 + .../securesms/BindableConversationItem.java | 4 ++++ .../conversation/ConversationAdapterBridge.kt | 1 + .../conversation/ConversationItem.java | 5 ++++ .../ConversationItemBodyBubble.java | 21 ++++++++++++++--- .../conversation/v2/ConversationAdapterV2.kt | 23 +++++++++++++++++++ .../v2/items/V2ConversationContext.kt | 1 + .../V2ConversationItemTextOnlyViewHolder.kt | 20 ++++++++++++++-- 8 files changed, 71 insertions(+), 5 deletions(-) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt index 4aa9b41edb..cec6475645 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt @@ -217,6 +217,7 @@ class V2ConversationItemShapeTest { override val isMessageRequestAccepted: Boolean = true override val searchQuery: String? = null override val glideRequests: GlideRequests = mockk() + override val isParentInScroll: Boolean = false override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java index b9ca20d545..b209726fe9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java @@ -57,6 +57,10 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, void setEventListener(@Nullable EventListener listener); + default void setParentScrolling(boolean isParentScrolling) { + // Intentionally Blank. + } + default void updateTimestamps() { // Intentionally Blank. } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapterBridge.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapterBridge.kt index 2b1768cdf0..a9aeb78f8f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapterBridge.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapterBridge.kt @@ -16,6 +16,7 @@ interface ConversationAdapterBridge { const val PAYLOAD_TIMESTAMP = 0 const val PAYLOAD_NAME_COLORS = 1 const val PAYLOAD_SELECTED = 2 + const val PAYLOAD_PARENT_SCROLLING = 3 } fun hasNoConversationMessages(): Boolean diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 8f8838331e..4fa6c0c8c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -418,6 +418,11 @@ public final class ConversationItem extends RelativeLayout implements BindableCo this.conversationRecipient.observeForever(this); } + @Override + public void setParentScrolling(boolean isParentScrolling) { + bodyBubble.setParentScrolling(isParentScrolling); + } + @Override public void updateSelectedState() { setHasBeenQuoted(conversationMessage); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemBodyBubble.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemBodyBubble.java index 7a4fe1fa71..416a3f1b4c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemBodyBubble.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItemBodyBubble.java @@ -12,6 +12,7 @@ import androidx.annotation.Nullable; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; +import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.components.Outliner; import org.thoughtcrime.securesms.util.Projection; import org.thoughtcrime.securesms.util.Util; @@ -30,19 +31,33 @@ public class ConversationItemBodyBubble extends LinearLayout { private Projection quoteViewProjection; private Projection videoPlayerProjection; + private final BodyBubbleLayoutTransition bodyBubbleLayoutTransition = new BodyBubbleLayoutTransition(); + public ConversationItemBodyBubble(Context context) { super(context); - setLayoutTransition(new BodyBubbleLayoutTransition()); + init(); } public ConversationItemBodyBubble(Context context, @Nullable AttributeSet attrs) { super(context, attrs); - setLayoutTransition(new BodyBubbleLayoutTransition()); + init(); } public ConversationItemBodyBubble(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - setLayoutTransition(new BodyBubbleLayoutTransition()); + init(); + } + + private void init() { + setLayoutTransition(bodyBubbleLayoutTransition); + } + + public void setParentScrolling(boolean isParentScrolling) { + if (isParentScrolling) { + setLayoutTransition(null); + } else { + setLayoutTransition(bodyBubbleLayoutTransition); + } } public void setOutliners(@NonNull List outliners) { 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 eb3eb2b023..1afdd4d2fd 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 @@ -90,6 +90,10 @@ class ConversationAdapterV2( override var isMessageRequestAccepted: Boolean = false + override var isParentInScroll: Boolean = false + + private val onScrollStateChangedListener = OnScrollStateChangedListener() + init { registerFactory(ThreadHeader::class.java, ::ThreadHeaderViewHolder, R.layout.conversation_item_thread_header) @@ -162,6 +166,8 @@ class ConversationAdapterV2( recyclerView.recycledViewPool.setMaxRecycledViews(type, count) } } + + recyclerView.addOnScrollListener(onScrollStateChangedListener) } override fun onViewRecycled(holder: MappingViewHolder<*>) { @@ -176,6 +182,8 @@ class ConversationAdapterV2( .children .filterIsInstance() .forEach { it.unbind() } + + recyclerView.removeOnScrollListener(onScrollStateChangedListener) } override val displayMode: ConversationItemDisplayMode @@ -498,6 +506,11 @@ class ConversationAdapterV2( fun bindPayloadsIfAvailable(): Boolean { var payloadApplied = false + bindable.setParentScrolling(isParentInScroll) + if (payload.contains(ConversationAdapterBridge.PAYLOAD_PARENT_SCROLLING)) { + payloadApplied = true + } + if (payload.contains(ConversationAdapterBridge.PAYLOAD_TIMESTAMP)) { bindable.updateTimestamps() payloadApplied = true @@ -627,4 +640,14 @@ class ConversationAdapterV2( } } } + + private inner class OnScrollStateChangedListener : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + val oldState = isParentInScroll + isParentInScroll = newState != RecyclerView.SCROLL_STATE_IDLE + if (isParentInScroll != oldState) { + notifyItemRangeChanged(0, itemCount, ConversationAdapterBridge.PAYLOAD_PARENT_SCROLLING) + } + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationContext.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationContext.kt index 2439824623..9de9b631cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationContext.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationContext.kt @@ -23,6 +23,7 @@ interface V2ConversationContext { val selectedItems: Set val isMessageRequestAccepted: Boolean val searchQuery: String? + val isParentInScroll: Boolean fun onStartExpirationTimeout(messageRecord: MessageRecord) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt index 1e7aa753e9..fb98fa4462 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt @@ -100,6 +100,7 @@ open class V2ConversationItemTextOnlyViewHolder>( private val bodyBubbleDrawable = ChatColorsDrawable() private val footerDrawable = ChatColorsDrawable() + private val bodyBubbleLayoutTransition = BodyBubbleLayoutTransition() protected lateinit var shape: V2ConversationItemShape.MessageShape @@ -155,7 +156,7 @@ open class V2ConversationItemTextOnlyViewHolder>( } binding.conversationItemBodyWrapper.background = bodyBubbleDrawable - binding.conversationItemBodyWrapper.layoutTransition = BodyBubbleLayoutTransition() + binding.conversationItemBodyWrapper.layoutTransition = bodyBubbleLayoutTransition binding.conversationItemFooterBackground.background = footerDrawable } @@ -166,6 +167,22 @@ open class V2ConversationItemTextOnlyViewHolder>( } override fun bind(model: Model) { + var hasProcessedSupportedPayload = false + + binding.conversationItemBodyWrapper.layoutTransition = if (conversationContext.isParentInScroll) { + null + } else { + bodyBubbleLayoutTransition + } + + if (ConversationAdapterBridge.PAYLOAD_PARENT_SCROLLING in payload) { + if (payload.size == 1) { + return + } else { + hasProcessedSupportedPayload = true + } + } + check(model is ConversationMessageElement) conversationMessage = model.conversationMessage @@ -176,7 +193,6 @@ open class V2ConversationItemTextOnlyViewHolder>( adapterPosition = bindingAdapterPosition ) - var hasProcessedSupportedPayload = false if (ConversationAdapterBridge.PAYLOAD_TIMESTAMP in payload) { presentDate() hasProcessedSupportedPayload = true