diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/RecyclerViewParentTransitionController.kt b/app/src/main/java/org/thoughtcrime/securesms/components/RecyclerViewParentTransitionController.kt new file mode 100644 index 0000000000..3af2ee0d64 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/RecyclerViewParentTransitionController.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.components + +import android.animation.LayoutTransition +import android.view.View +import android.view.View.OnAttachStateChangeListener +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView + +/** + * Helps manage layout transition state inside a RecyclerView. + * + * Because of how RecyclerViews scroll, we need to be very careful about LayoutTransition + * usage inside of them. This class helps wrap up the pattern of finding and listening to + * the scroll events of a parent recycler view, so that we don't need to manually wire + * the scroll state in everywhere. + */ +class RecyclerViewParentTransitionController( + private val child: ViewGroup, + private val transition: LayoutTransition = LayoutTransition() +) : RecyclerView.OnScrollListener(), OnAttachStateChangeListener { + + private var recyclerViewParent: RecyclerView? = null + + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + if (newState == RecyclerView.SCROLL_STATE_IDLE) { + child.layoutTransition = transition + } else { + child.layoutTransition = null + } + } + + override fun onViewAttachedToWindow(v: View) { + val parent = findRecyclerParent() + + if (parent != null) { + onScrollStateChanged(parent, parent.scrollState) + } + + parent?.addOnScrollListener(this) + recyclerViewParent = parent + } + + override fun onViewDetachedFromWindow(v: View) { + recyclerViewParent?.removeOnScrollListener(this) + child.layoutTransition = null + } + + private fun findRecyclerParent(): RecyclerView? { + var target: ViewGroup? = child.parent as? ViewGroup + while (target != null) { + if (target is RecyclerView) { + return target + } + + target = target.parent as? ViewGroup + } + + return null + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt index 83d44d081c..31142d752c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/transfercontrols/TransferControlView.kt @@ -4,7 +4,6 @@ */ package org.thoughtcrime.securesms.components.transfercontrols -import android.animation.LayoutTransition import android.annotation.SuppressLint import android.content.Context import android.os.Build @@ -23,6 +22,7 @@ import org.greenrobot.eventbus.ThreadMode import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.attachments.Attachment +import org.thoughtcrime.securesms.components.RecyclerViewParentTransitionController import org.thoughtcrime.securesms.database.AttachmentTable import org.thoughtcrime.securesms.databinding.TransferControlsViewBinding import org.thoughtcrime.securesms.events.PartProgressEvent @@ -45,10 +45,11 @@ class TransferControlView @JvmOverloads constructor(context: Context, attrs: Att binding = TransferControlsViewBinding.inflate(LayoutInflater.from(context), this) visibility = GONE isLongClickable = false - layoutTransition = LayoutTransition() disposables += store.stateFlowable.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()).subscribe { applyState(it) } + + addOnAttachStateChangeListener(RecyclerViewParentTransitionController(child = this)) } override fun onAttachedToWindow() {