Fix media viewer rail items jumping around while paging.

main
Cody Henthorne 2022-12-21 16:18:42 -05:00 zatwierdzone przez Greyson Parrelli
rodzic 1e2f7f0775
commit 0fe6538ce4
2 zmienionych plików z 56 dodań i 80 usunięć

Wyświetl plik

@ -165,11 +165,14 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
{ media -> jumpViewPagerToMedia(media) }, { media -> jumpViewPagerToMedia(media) },
object : ImageLoadingListener() { object : ImageLoadingListener() {
override fun onAllRequestsFinished() { override fun onAllRequestsFinished() {
crossfadeViewIn(this@apply) val willAnimateIn = crossfadeViewIn(this@apply)
if (!willAnimateIn) {
visible = true
}
} }
} }
) )
this.adapter = albumRailAdapter adapter = albumRailAdapter
} }
} }
@ -303,32 +306,21 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
private fun bindAlbumRail(albumThumbnailMedia: List<Media>, currentItem: MediaTable.MediaRecord) { private fun bindAlbumRail(albumThumbnailMedia: List<Media>, currentItem: MediaTable.MediaRecord) {
val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView
if (albumThumbnailMedia.size > 1) { if (albumThumbnailMedia.size > 1) {
val albumPosition = albumThumbnailMedia.indexOfFirst { it.uri == currentItem.attachment?.uri }
if (albumRail.visibility == GONE) { if (albumRail.visibility == GONE) {
albumRail.visibility = View.INVISIBLE albumRail.visibility = View.INVISIBLE
} }
val railItems = albumThumbnailMedia.map { MediaRailAdapter.MediaRailItem(it, it.uri == currentItem.attachment?.uri) }
albumRailAdapter.currentItemPosition = albumPosition albumRailAdapter.submitList(railItems) { albumRail.post { scrollAlbumRailToCurrentAdapterPosition() } }
albumRailAdapter.submitList(albumThumbnailMedia)
scrollAlbumRailToCurrentAdapterPosition()
} else { } else {
albumRail.visibility = View.GONE albumRail.visibility = View.GONE
albumRailAdapter.submitList(emptyList()) albumRailAdapter.submitList(emptyList())
albumRailAdapter.imageLoadingListener.reset()
} }
} }
private fun scrollAlbumRailToCurrentAdapterPosition() { private fun scrollAlbumRailToCurrentAdapterPosition() {
val currentItemPosition = albumRailAdapter.currentItemPosition val currentItemPosition = albumRailAdapter.findSelectedItemPosition()
val currentList = albumRailAdapter.currentList
val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView
albumRail.scrollToPosition(currentItemPosition) albumRail.scrollToPosition(currentItemPosition)
for (i in currentList.indices) {
val isSelected = i == currentItemPosition
val stableId = albumRailAdapter.getItemId(i)
val viewHolder = albumRail.findViewHolderForItemId(stableId) as? MediaRailAdapter.MediaRailViewHolder
viewHolder?.setSelectedItem(isSelected)
}
val offsetFromStart = (albumRail.width - individualItemWidth) / 2 val offsetFromStart = (albumRail.width - individualItemWidth) / 2
val smoothScroller = OffsetSmoothScroller(requireContext(), offsetFromStart) val smoothScroller = OffsetSmoothScroller(requireContext(), offsetFromStart)
smoothScroller.targetPosition = currentItemPosition smoothScroller.targetPosition = currentItemPosition
@ -336,8 +328,8 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
layoutManager.startSmoothScroll(smoothScroller) layoutManager.startSmoothScroll(smoothScroller)
} }
private fun crossfadeViewIn(view: View, duration: Long = 200) { private fun crossfadeViewIn(view: View, duration: Long = 200): Boolean {
if (!view.isVisible && !fullscreenHelper.isSystemUiVisible) { return if (!view.isVisible && !fullscreenHelper.isSystemUiVisible) {
val viewPropertyAnimator = view.animate() val viewPropertyAnimator = view.animate()
.alpha(1f) .alpha(1f)
.setDuration(duration) .setDuration(duration)
@ -353,6 +345,9 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
viewPropertyAnimator.interpolator = PathInterpolator(0.17f, 0.17f, 0f, 1f) viewPropertyAnimator.interpolator = PathInterpolator(0.17f, 0.17f, 0f, 1f)
} }
viewPropertyAnimator.start() viewPropertyAnimator.start()
true
} else {
false
} }
} }

Wyświetl plik

@ -1,12 +1,8 @@
package org.thoughtcrime.securesms.mediapreview.mediarail package org.thoughtcrime.securesms.mediapreview.mediarail
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.GlideException
@ -16,45 +12,59 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ThumbnailView import org.thoughtcrime.securesms.components.ThumbnailView
import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.util.adapter.StableIdGenerator import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
import org.thoughtcrime.securesms.util.visible import org.thoughtcrime.securesms.util.visible
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
/** /**
* This is the RecyclerView.Adapter for the row of thumbnails present in the media viewer screen. * This is the RecyclerView.Adapter for the row of thumbnails present in the media viewer screen.
*/ */
class MediaRailAdapter(private val glideRequests: GlideRequests, listener: RailItemListener, imageLoadingListener: ImageLoadingListener) : ListAdapter<Media, MediaRailAdapter.MediaRailViewHolder>(MediaDiffer()) { class MediaRailAdapter(
val imageLoadingListener: ImageLoadingListener private val glideRequests: GlideRequests,
private val onRailItemSelected: (Media) -> Unit,
var currentItemPosition: Int = -1 private val imageLoadingListener: ImageLoadingListener
) : MappingAdapter() {
private val listener: RailItemListener
private val stableIdGenerator: StableIdGenerator<Media>
init { init {
this.listener = listener registerFactory(MediaRailItem::class.java, ::MediaRailViewHolder, R.layout.mediarail_media_item)
stableIdGenerator = StableIdGenerator()
this.imageLoadingListener = imageLoadingListener
setHasStableIds(true)
} }
override fun onCreateViewHolder(viewGroup: ViewGroup, type: Int): MediaRailViewHolder { override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
return MediaRailViewHolder(LayoutInflater.from(viewGroup.context).inflate(R.layout.mediarail_media_item, viewGroup, false)) super.onAttachedToRecyclerView(recyclerView)
recyclerView.itemAnimator = null
} }
override fun onBindViewHolder(viewHolder: MediaRailViewHolder, i: Int) { override fun submitList(list: List<MappingModel<*>>?) {
viewHolder.bind(getItem(i), i == currentItemPosition, glideRequests, listener, imageLoadingListener) super.submitList(list)
if (list?.isEmpty() == true) {
imageLoadingListener.reset()
}
} }
override fun onViewRecycled(holder: MediaRailViewHolder) { override fun submitList(list: List<MappingModel<*>>?, commitCallback: Runnable?) {
holder.recycle() super.submitList(list, commitCallback)
if (list?.isEmpty() == true) {
imageLoadingListener.reset()
}
} }
override fun getItemId(position: Int): Long { fun findSelectedItemPosition(): Int {
return stableIdGenerator.getId(getItem(position)) return indexOfFirst(MediaRailItem::class.java) { it.isSelected }.coerceAtLeast(0)
} }
class MediaRailViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { data class MediaRailItem(val media: Media, val isSelected: Boolean) : MappingModel<MediaRailItem> {
override fun areItemsTheSame(newItem: MediaRailItem): Boolean {
return media.uri == newItem.media.uri
}
override fun areContentsTheSame(newItem: MediaRailItem): Boolean {
return this == newItem
}
}
private inner class MediaRailViewHolder(itemView: View) : MappingViewHolder<MediaRailItem>(itemView) {
private val image: ThumbnailView private val image: ThumbnailView
private val outline: View private val outline: View
private val captionIndicator: View private val captionIndicator: View
@ -67,34 +77,15 @@ class MediaRailAdapter(private val glideRequests: GlideRequests, listener: RailI
overlay = itemView.findViewById(R.id.rail_item_overlay) overlay = itemView.findViewById(R.id.rail_item_overlay)
} }
fun bind( override fun bind(model: MediaRailItem) {
media: Media, imageLoadingListener.onRequest()
isCurrentlySelected: Boolean, image.setImageResource(glideRequests, model.media.uri, 0, 0, false, imageLoadingListener)
glideRequests: GlideRequests, image.setOnClickListener { onRailItemSelected(model.media) }
railItemListener: RailItemListener, captionIndicator.visibility = if (model.media.caption.isPresent) View.VISIBLE else View.GONE
listener: ImageLoadingListener
) { outline.visible = model.isSelected
listener.onRequest() overlay.setImageResource(if (model.isSelected) R.drawable.mediapreview_rail_item_overlay_selected else R.drawable.mediapreview_rail_item_overlay_unselected)
image.setImageResource(glideRequests, media.uri, 0, 0, false, listener)
image.setOnClickListener { railItemListener.onRailItemClicked(media) }
captionIndicator.visibility = if (media.caption.isPresent) View.VISIBLE else View.GONE
setSelectedItem(isCurrentlySelected)
} }
fun recycle() {
image.setOnClickListener(null)
}
fun setSelectedItem(isActive: Boolean) {
outline.visible = isActive
val resId = if (isActive) R.drawable.mediapreview_rail_item_overlay_selected else R.drawable.mediapreview_rail_item_overlay_unselected
overlay.setImageResource(resId)
}
}
fun interface RailItemListener {
fun onRailItemClicked(media: Media)
} }
abstract class ImageLoadingListener : RequestListener<Drawable?> { abstract class ImageLoadingListener : RequestListener<Drawable?> {
@ -125,14 +116,4 @@ class MediaRailAdapter(private val glideRequests: GlideRequests, listener: RailI
abstract fun onAllRequestsFinished() abstract fun onAllRequestsFinished()
} }
class MediaDiffer : DiffUtil.ItemCallback<Media>() {
override fun areItemsTheSame(oldItem: Media, newItem: Media): Boolean {
return oldItem.uri == newItem.uri
}
override fun areContentsTheSame(oldItem: Media, newItem: Media): Boolean {
return oldItem == newItem
}
}
} }