Use cached preview as thumbnail in ViewImageFragment, fix #1267 (#1344)

* Use cached preview as thumbnail in ViewImageFragment, fix #1267

* Use cached preview as thumbnail in ViewImageFragment, fix #1267
This commit is contained in:
Ivan Kupalov 2019-08-04 20:22:57 +02:00 committed by Konrad Pozniak
commit 9805a985b2
7 changed files with 164 additions and 111 deletions

View file

@ -30,14 +30,15 @@ import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.github.chrisbanes.photoview.PhotoViewAttacher
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.util.hide
import com.keylesspalace.tusky.util.visible
import io.reactivex.subjects.BehaviorSubject
import kotlinx.android.synthetic.main.activity_view_media.*
import kotlinx.android.synthetic.main.fragment_view_image.*
import kotlin.math.abs
class ViewImageFragment : ViewMediaFragment() {
interface PhotoActionsListener {
@ -49,35 +50,35 @@ class ViewImageFragment : ViewMediaFragment() {
private lateinit var attacher: PhotoViewAttacher
private lateinit var photoActionsListener: PhotoActionsListener
private lateinit var toolbar: View
override lateinit var descriptionView: TextView
private var transition = BehaviorSubject.create<Unit>()
override lateinit var descriptionView: TextView
override fun onAttach(context: Context) {
super.onAttach(context)
photoActionsListener = context as PhotoActionsListener
}
override fun setupMediaView(url: String) {
override fun setupMediaView(url: String, previewUrl: String?) {
descriptionView = mediaDescription
photoView.transitionName = url
attacher = PhotoViewAttacher(photoView)
attacher = PhotoViewAttacher(photoView).apply {
// Clicking outside the photo closes the viewer.
setOnOutsidePhotoTapListener { photoActionsListener.onDismiss() }
setOnClickListener { onMediaTap() }
// Clicking outside the photo closes the viewer.
attacher.setOnOutsidePhotoTapListener { photoActionsListener.onDismiss() }
attacher.setOnClickListener { onMediaTap() }
/* A vertical swipe motion also closes the viewer. This is especially useful when the photo
* mostly fills the screen so clicking outside is difficult. */
attacher.setOnSingleFlingListener { _, _, velocityX, velocityY ->
var result = false
if (Math.abs(velocityY) > Math.abs(velocityX)) {
photoActionsListener.onDismiss()
result = true
/* A vertical swipe motion also closes the viewer. This is especially useful when the photo
* mostly fills the screen so clicking outside is difficult. */
setOnSingleFlingListener { _, _, velocityX, velocityY ->
var result = false
if (abs(velocityY) > abs(velocityX)) {
photoActionsListener.onDismiss()
result = true
}
result
}
result
}
loadImageFromNetwork(url, photoView)
loadImageFromNetwork(url, previewUrl, photoView)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@ -103,7 +104,7 @@ class ViewImageFragment : ViewMediaFragment() {
}
}
finalizeViewSetup(url, description)
finalizeViewSetup(url, attachment?.previewUrl, description)
}
private fun onMediaTap() {
@ -131,49 +132,71 @@ class ViewImageFragment : ViewMediaFragment() {
super.onDestroyView()
}
private fun loadImageFromNetwork(url: String, photoView: ImageView) =
//Request image from the any cache
Glide.with(this)
.load(url)
.dontAnimate()
.onlyRetrieveFromCache(true)
.error(
//Request image from the network on fail load image from cache
Glide.with(this)
.load(url)
.centerInside()
.addListener(ImageRequestListener(false))
)
.centerInside()
.addListener(ImageRequestListener(true))
.into(photoView)
private fun loadImageFromNetwork(url: String, previewUrl: String?, photoView: ImageView) {
val glide = Glide.with(this)
// Request image from the any cache
glide
.load(url)
.dontAnimate()
.onlyRetrieveFromCache(true)
.let {
if (previewUrl != null)
it.thumbnail(glide
.load(previewUrl)
.dontAnimate()
.onlyRetrieveFromCache(true)
.centerInside()
.addListener(ImageRequestListener(true, isThumnailRequest = true)))
else it
}
//Request image from the network on fail load image from cache
.error(glide.load(url)
.centerInside()
.addListener(ImageRequestListener(false, isThumnailRequest = false))
)
.centerInside()
.addListener(ImageRequestListener(true, isThumnailRequest = false))
.into(photoView)
}
/**
* @param isCacheRequest - is this listener for request image from cache or from the network
*/
private inner class ImageRequestListener(private val isCacheRequest: Boolean) : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
if (isCacheRequest) //Complete the transition on failed image from cache
completeTransition()
else
progressBar?.hide() //Hide progress bar only on fail request from internet
private inner class ImageRequestListener(
private val isCacheRequest: Boolean,
private val isThumnailRequest: Boolean) : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Drawable>,
isFirstResource: Boolean): Boolean {
// If cache for full image failed, complete transition
if (isCacheRequest && !isThumnailRequest) photoActionsListener.onBringUp()
// Hide progress bar only on fail request from internet
if (!isCacheRequest) progressBar?.hide()
return false
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
progressBar?.hide() //Always hide the progress bar on success
resource?.let {
target?.onResourceReady(resource, null)
if (isCacheRequest) completeTransition() //Complete transition on cache request only, because transition already completed on Network request
override fun onResourceReady(resource: Drawable, model: Any, target: Target<Drawable>,
dataSource: DataSource, isFirstResource: Boolean): Boolean {
progressBar?.hide() // Always hide the progress bar on success
if (isThumnailRequest) {
photoView.post {
target.onResourceReady(resource, null)
photoActionsListener.onBringUp()
}
} else {
transition
.take(1)
.subscribe {
target.onResourceReady(resource, null)
photoActionsListener.onBringUp()
}
}
return true
}
return false
}
}
private fun completeTransition() {
attacher.update()
photoActionsListener.onBringUp()
override fun onTransitionEnd() {
this.transition.onNext(Unit)
}
}

View file

@ -18,25 +18,29 @@ package com.keylesspalace.tusky.fragment
import android.os.Bundle
import android.text.TextUtils
import android.widget.TextView
import com.keylesspalace.tusky.SharedElementTransitionListener
import com.keylesspalace.tusky.ViewMediaActivity
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.util.visible
abstract class ViewMediaFragment : BaseFragment() {
abstract class ViewMediaFragment : BaseFragment(), SharedElementTransitionListener {
private var toolbarVisibiltyDisposable: Function0<Boolean>? = null
abstract fun setupMediaView(url: String)
abstract fun setupMediaView(url: String, previewUrl: String?)
abstract fun onToolbarVisibilityChange(visible: Boolean)
abstract val descriptionView : TextView
abstract val descriptionView: TextView
protected var showingDescription = false
protected var isDescriptionVisible = false
companion object {
@JvmStatic protected val ARG_START_POSTPONED_TRANSITION = "startPostponedTransition"
@JvmStatic protected val ARG_ATTACHMENT = "attach"
@JvmStatic protected val ARG_AVATAR_URL = "avatarUrl"
@JvmStatic
protected val ARG_START_POSTPONED_TRANSITION = "startPostponedTransition"
@JvmStatic
protected val ARG_ATTACHMENT = "attach"
@JvmStatic
protected val ARG_AVATAR_URL = "avatarUrl"
@JvmStatic
fun newInstance(attachment: Attachment, shouldStartPostponedTransition: Boolean): ViewMediaFragment {
@ -66,21 +70,20 @@ abstract class ViewMediaFragment : BaseFragment() {
}
}
protected fun finalizeViewSetup(url: String, description: String?) {
protected fun finalizeViewSetup(url: String, previewUrl: String?, description: String?) {
val mediaActivity = activity as ViewMediaActivity
setupMediaView(url)
setupMediaView(url, previewUrl)
descriptionView.text = description ?: ""
showingDescription = !TextUtils.isEmpty(description)
isDescriptionVisible = showingDescription
descriptionView.visible(showingDescription && mediaActivity.isToolbarVisible())
descriptionView.visible(showingDescription && mediaActivity.isToolbarVisible)
toolbarVisibiltyDisposable = (activity as ViewMediaActivity).addToolbarVisibilityListener(object: ViewMediaActivity.ToolbarVisibilityListener {
override fun onToolbarVisiblityChanged(isVisible: Boolean) {
onToolbarVisibilityChange(isVisible)
}
})
toolbarVisibiltyDisposable = (activity as ViewMediaActivity)
.addToolbarVisibilityListener { isVisible ->
onToolbarVisibilityChange(isVisible)
}
}
override fun onDestroyView() {

View file

@ -26,7 +26,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.MediaController
import android.widget.TextView
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.ViewMediaActivity
import com.keylesspalace.tusky.entity.Attachment
@ -56,7 +55,7 @@ class ViewVideoFragment : ViewMediaFragment() {
}
if (isVisibleToUser) {
if (mediaActivity.isToolbarVisible()) {
if (mediaActivity.isToolbarVisible) {
handler.postDelayed(hideToolbar, TOOLBAR_HIDE_DELAY_MS)
}
videoPlayer.start()
@ -68,7 +67,7 @@ class ViewVideoFragment : ViewMediaFragment() {
}
@SuppressLint("ClickableViewAccessibility")
override fun setupMediaView(url: String) {
override fun setupMediaView(url: String, previewUrl: String?) {
descriptionView = mediaDescription
val videoView = videoPlayer
videoView.transitionName = url
@ -114,7 +113,7 @@ class ViewVideoFragment : ViewMediaFragment() {
throw IllegalArgumentException("attachment has to be set")
}
url = attachment.url
finalizeViewSetup(url, attachment.description)
finalizeViewSetup(url, attachment.previewUrl, attachment.description)
}
override fun onToolbarVisibilityChange(visible: Boolean) {
@ -139,4 +138,7 @@ class ViewVideoFragment : ViewMediaFragment() {
handler.removeCallbacks(hideToolbar)
}
}
override fun onTransitionEnd() {
}
}