Skip to content

Commit

Permalink
Fix media viewer rail items jumping around while paging.
Browse files Browse the repository at this point in the history
  • Loading branch information
cody-signal authored and greyson-signal committed Dec 30, 2022
1 parent 1e2f7f0 commit 0fe6538
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,14 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
{ media -> jumpViewPagerToMedia(media) },
object : ImageLoadingListener() {
override fun onAllRequestsFinished() {
crossfadeViewIn(this@apply)
val willAnimateIn = crossfadeViewIn(this@apply)
if (!willAnimateIn) {
visible = true
}
}
}
)
this.adapter = albumRailAdapter
adapter = albumRailAdapter
}
}

Expand Down Expand Up @@ -303,41 +306,30 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
private fun bindAlbumRail(albumThumbnailMedia: List<Media>, currentItem: MediaTable.MediaRecord) {
val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView
if (albumThumbnailMedia.size > 1) {
val albumPosition = albumThumbnailMedia.indexOfFirst { it.uri == currentItem.attachment?.uri }
if (albumRail.visibility == GONE) {
albumRail.visibility = View.INVISIBLE
}

albumRailAdapter.currentItemPosition = albumPosition
albumRailAdapter.submitList(albumThumbnailMedia)
scrollAlbumRailToCurrentAdapterPosition()
val railItems = albumThumbnailMedia.map { MediaRailAdapter.MediaRailItem(it, it.uri == currentItem.attachment?.uri) }
albumRailAdapter.submitList(railItems) { albumRail.post { scrollAlbumRailToCurrentAdapterPosition() } }
} else {
albumRail.visibility = View.GONE
albumRailAdapter.submitList(emptyList())
albumRailAdapter.imageLoadingListener.reset()
}
}

private fun scrollAlbumRailToCurrentAdapterPosition() {
val currentItemPosition = albumRailAdapter.currentItemPosition
val currentList = albumRailAdapter.currentList
val currentItemPosition = albumRailAdapter.findSelectedItemPosition()
val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.recyclerView
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 smoothScroller = OffsetSmoothScroller(requireContext(), offsetFromStart)
smoothScroller.targetPosition = currentItemPosition
val layoutManager = albumRail.layoutManager as LinearLayoutManager
layoutManager.startSmoothScroll(smoothScroller)
}

private fun crossfadeViewIn(view: View, duration: Long = 200) {
if (!view.isVisible && !fullscreenHelper.isSystemUiVisible) {
private fun crossfadeViewIn(view: View, duration: Long = 200): Boolean {
return if (!view.isVisible && !fullscreenHelper.isSystemUiVisible) {
val viewPropertyAnimator = view.animate()
.alpha(1f)
.setDuration(duration)
Expand All @@ -353,6 +345,9 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
viewPropertyAnimator.interpolator = PathInterpolator(0.17f, 0.17f, 0f, 1f)
}
viewPropertyAnimator.start()
true
} else {
false
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
package org.thoughtcrime.securesms.mediapreview.mediarail

import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
Expand All @@ -16,45 +12,59 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ThumbnailView
import org.thoughtcrime.securesms.mediasend.Media
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 java.util.concurrent.atomic.AtomicInteger

/**
* 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()) {
val imageLoadingListener: ImageLoadingListener

var currentItemPosition: Int = -1

private val listener: RailItemListener
private val stableIdGenerator: StableIdGenerator<Media>
class MediaRailAdapter(
private val glideRequests: GlideRequests,
private val onRailItemSelected: (Media) -> Unit,
private val imageLoadingListener: ImageLoadingListener
) : MappingAdapter() {

init {
this.listener = listener
stableIdGenerator = StableIdGenerator()
this.imageLoadingListener = imageLoadingListener
setHasStableIds(true)
registerFactory(MediaRailItem::class.java, ::MediaRailViewHolder, R.layout.mediarail_media_item)
}

override fun onCreateViewHolder(viewGroup: ViewGroup, type: Int): MediaRailViewHolder {
return MediaRailViewHolder(LayoutInflater.from(viewGroup.context).inflate(R.layout.mediarail_media_item, viewGroup, false))
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
recyclerView.itemAnimator = null
}

override fun submitList(list: List<MappingModel<*>>?) {
super.submitList(list)
if (list?.isEmpty() == true) {
imageLoadingListener.reset()
}
}

override fun onBindViewHolder(viewHolder: MediaRailViewHolder, i: Int) {
viewHolder.bind(getItem(i), i == currentItemPosition, glideRequests, listener, imageLoadingListener)
override fun submitList(list: List<MappingModel<*>>?, commitCallback: Runnable?) {
super.submitList(list, commitCallback)
if (list?.isEmpty() == true) {
imageLoadingListener.reset()
}
}

override fun onViewRecycled(holder: MediaRailViewHolder) {
holder.recycle()
fun findSelectedItemPosition(): Int {
return indexOfFirst(MediaRailItem::class.java) { it.isSelected }.coerceAtLeast(0)
}

override fun getItemId(position: Int): Long {
return stableIdGenerator.getId(getItem(position))
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
}
}

class MediaRailViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private inner class MediaRailViewHolder(itemView: View) : MappingViewHolder<MediaRailItem>(itemView) {
private val image: ThumbnailView
private val outline: View
private val captionIndicator: View
Expand All @@ -67,36 +77,17 @@ class MediaRailAdapter(private val glideRequests: GlideRequests, listener: RailI
overlay = itemView.findViewById(R.id.rail_item_overlay)
}

fun bind(
media: Media,
isCurrentlySelected: Boolean,
glideRequests: GlideRequests,
railItemListener: RailItemListener,
listener: ImageLoadingListener
) {
listener.onRequest()
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
override fun bind(model: MediaRailItem) {
imageLoadingListener.onRequest()
image.setImageResource(glideRequests, model.media.uri, 0, 0, false, imageLoadingListener)
image.setOnClickListener { onRailItemSelected(model.media) }
captionIndicator.visibility = if (model.media.caption.isPresent) View.VISIBLE else View.GONE

val resId = if (isActive) R.drawable.mediapreview_rail_item_overlay_selected else R.drawable.mediapreview_rail_item_overlay_unselected
overlay.setImageResource(resId)
outline.visible = model.isSelected
overlay.setImageResource(if (model.isSelected) R.drawable.mediapreview_rail_item_overlay_selected else R.drawable.mediapreview_rail_item_overlay_unselected)
}
}

fun interface RailItemListener {
fun onRailItemClicked(media: Media)
}

abstract class ImageLoadingListener : RequestListener<Drawable?> {
private val activeJobs = AtomicInteger()
fun onRequest() {
Expand Down Expand Up @@ -125,14 +116,4 @@ class MediaRailAdapter(private val glideRequests: GlideRequests, listener: RailI

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
}
}
}

0 comments on commit 0fe6538

Please sign in to comment.