ViewImageFragment: finalize transition to BigImageViewer. Now it should correctly handle EXIF rotation, do not stuck at transition and pick preview image if it's in cache but full image isn't
This commit is contained in:
parent
28a78a2566
commit
483b847120
@ -35,9 +35,11 @@ import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.github.piasy.biv.loader.glide.GlideCustomImageLoader
|
||||
import com.github.piasy.biv.view.BigImageView
|
||||
import com.github.piasy.biv.loader.ImageLoader
|
||||
import com.github.piasy.biv.view.GlideImageViewFactory
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.util.withLifecycleContext
|
||||
import java.io.File
|
||||
|
||||
// https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94
|
||||
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 420
|
||||
@ -53,8 +55,19 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
||||
|
||||
dialogLayout.orientation = LinearLayout.VERTICAL
|
||||
val imageView = BigImageView(this)
|
||||
// imageView.ssiv.maxScale = 6f
|
||||
imageView.setImageViewFactory(GlideImageViewFactory())
|
||||
imageView.setImageLoaderCallback(object : ImageLoader.Callback {
|
||||
override fun onSuccess(image: File?) {
|
||||
imageView.ssiv?.let { it.maxScale = 6f }
|
||||
}
|
||||
override fun onFail(error: Exception?) {}
|
||||
override fun onStart() {}
|
||||
override fun onCacheHit(imageType: Int, image: File?) {}
|
||||
override fun onCacheMiss(imageType: Int, image: File?) {}
|
||||
override fun onFinish() {}
|
||||
override fun onProgress(progress: Int) {}
|
||||
})
|
||||
imageView.showImage(previewUri)
|
||||
|
||||
val displayMetrics = DisplayMetrics()
|
||||
windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
@ -97,12 +110,8 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
|
||||
dialog.show()
|
||||
|
||||
// Load the image and manually set it into the ImageView because it doesn't have a fixed
|
||||
// size. Maybe we should limit the size of CustomTarget
|
||||
imageView.showImage(previewUri)
|
||||
}
|
||||
|
||||
private fun Activity.showFailedCaptionMessage() {
|
||||
Toast.makeText(this, R.string.error_failed_set_caption, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
@ -25,11 +25,15 @@ import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.TextView
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import com.bumptech.glide.Glide
|
||||
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.CustomTarget
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.github.piasy.biv.BigImageViewer
|
||||
import com.github.piasy.biv.loader.ImageLoader
|
||||
import com.github.piasy.biv.view.GlideImageViewFactory
|
||||
import com.keylesspalace.tusky.R
|
||||
@ -42,9 +46,10 @@ import kotlinx.android.synthetic.main.fragment_view_image.*
|
||||
import java.io.File
|
||||
import java.lang.Exception
|
||||
import kotlin.math.abs
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
|
||||
|
||||
class ViewImageFragment : ViewMediaFragment(), ImageLoader.Callback, View.OnTouchListener {
|
||||
class ViewImageFragment : ViewMediaFragment() {
|
||||
interface PhotoActionsListener {
|
||||
fun onBringUp()
|
||||
fun onDismiss()
|
||||
@ -60,6 +65,10 @@ class ViewImageFragment : ViewMediaFragment(), ImageLoader.Callback, View.OnTouc
|
||||
@Volatile
|
||||
private var startedTransition = false
|
||||
|
||||
private var uri = Uri.EMPTY
|
||||
private var previewUri = Uri.EMPTY
|
||||
private var showingPreview = false
|
||||
|
||||
override lateinit var descriptionView: TextView
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
@ -70,7 +79,11 @@ class ViewImageFragment : ViewMediaFragment(), ImageLoader.Callback, View.OnTouc
|
||||
descriptionView = mediaDescription
|
||||
photoView.transitionName = url
|
||||
startedTransition = false
|
||||
loadImageFromNetwork(url, previewUrl)
|
||||
uri = Uri.parse(url)
|
||||
if(previewUrl != null && !previewUrl.equals(url)) {
|
||||
previewUri = Uri.parse(previewUrl)
|
||||
}
|
||||
loadImageFromNetwork()
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
@ -78,51 +91,53 @@ class ViewImageFragment : ViewMediaFragment(), ImageLoader.Callback, View.OnTouc
|
||||
return inflater.inflate(R.layout.fragment_view_image, container, false)
|
||||
}
|
||||
|
||||
private var lastY = 0.0f
|
||||
private var swipeStartedWithOneFinger = false
|
||||
private lateinit var gestureDetector : GestureDetector
|
||||
|
||||
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||
// This part is for scaling/translating on vertical move.
|
||||
// We use raw coordinates to get the correct ones during scaling
|
||||
gestureDetector.onTouchEvent(event)
|
||||
private val imageOnTouchListener = object : View.OnTouchListener {
|
||||
private var lastY = 0.0f
|
||||
private var swipeStartedWithOneFinger = false
|
||||
|
||||
if(event.pointerCount != 1) {
|
||||
swipeStartedWithOneFinger = false
|
||||
return false
|
||||
}
|
||||
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||
// This part is for scaling/translating on vertical move.
|
||||
// We use raw coordinates to get the correct ones during scaling
|
||||
gestureDetector.onTouchEvent(event)
|
||||
|
||||
var result = false
|
||||
|
||||
when(event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
swipeStartedWithOneFinger = true
|
||||
lastY = event.rawY
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
if(event.pointerCount != 1) {
|
||||
onGestureEnd()
|
||||
swipeStartedWithOneFinger = false
|
||||
return false
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if(swipeStartedWithOneFinger && photoView.ssiv.scale <= photoView.ssiv.minScale) {
|
||||
val diff = event.rawY - lastY
|
||||
// This code is to prevent transformations during page scrolling
|
||||
// If we are already translating or we reached the threshold, then transform.
|
||||
if (photoView.translationY != 0f || abs(diff) > 40) {
|
||||
photoView.translationY += (diff)
|
||||
val scale = (-abs(photoView.translationY) / 720 + 1).coerceAtLeast(0.5f)
|
||||
photoView.scaleY = scale
|
||||
photoView.scaleX = scale
|
||||
lastY = event.rawY
|
||||
|
||||
when(event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
swipeStartedWithOneFinger = true
|
||||
lastY = event.rawY
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
onGestureEnd()
|
||||
swipeStartedWithOneFinger = false
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if(swipeStartedWithOneFinger && photoView.ssiv.scale <= photoView.ssiv.minScale) {
|
||||
val diff = event.rawY - lastY
|
||||
// This code is to prevent transformations during page scrolling
|
||||
// If we are already translating or we reached the threshold, then transform.
|
||||
if (photoView.translationY != 0f || abs(diff) > 40) {
|
||||
photoView.translationY += (diff)
|
||||
val scale = (-abs(photoView.translationY) / 720 + 1).coerceAtLeast(0.5f)
|
||||
photoView.scaleY = scale
|
||||
photoView.scaleX = scale
|
||||
lastY = event.rawY
|
||||
}
|
||||
}
|
||||
result = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
@ -135,7 +150,7 @@ class ViewImageFragment : ViewMediaFragment(), ImageLoader.Callback, View.OnTouc
|
||||
})
|
||||
|
||||
// photoView.setOnTouchListener(this)
|
||||
photoView.setImageLoaderCallback(this)
|
||||
photoView.setImageLoaderCallback(imageLoaderCallback)
|
||||
photoView.setImageViewFactory(GlideImageViewFactory())
|
||||
|
||||
val arguments = this.requireArguments()
|
||||
@ -190,33 +205,97 @@ class ViewImageFragment : ViewMediaFragment(), ImageLoader.Callback, View.OnTouc
|
||||
photoView.ssiv?.recycle()
|
||||
}
|
||||
|
||||
private fun loadImageFromNetwork(url: String, previewUrl: String?) {
|
||||
photoView.showImage(Uri.parse(previewUrl), Uri.parse(url))
|
||||
private inner class DummyCacheTarget(val ctx: Context, val requestPreview : Boolean) : CustomTarget<File>() {
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
if(requestPreview) {
|
||||
// no preview, no full image in cache, load full image
|
||||
// forget about fancy transition
|
||||
showingPreview = false
|
||||
photoView.showImage(uri)
|
||||
} else {
|
||||
// let's start downloading full image that we supposedly don't have
|
||||
BigImageViewer.prefetch(uri)
|
||||
|
||||
// meanwhile poke cache about preview image
|
||||
Glide.with(ctx).asFile()
|
||||
.load(previewUri)
|
||||
.dontAnimate()
|
||||
.onlyRetrieveFromCache(true)
|
||||
.into(DummyCacheTarget(ctx, true))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: File, transition: Transition<in File>?) {
|
||||
showingPreview = requestPreview
|
||||
if(requestPreview) {
|
||||
// have preview cached but not full image
|
||||
photoView.showImage(previewUri, uri, true)
|
||||
} else {
|
||||
photoView.showImage(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSuccess(image: File?) {
|
||||
progressBar?.hide() // Always hide the progress bar on success
|
||||
photoActionsListener.onBringUp()
|
||||
photoView.ssiv?.setOnTouchListener(this)
|
||||
}
|
||||
|
||||
override fun onFail(error: Exception?) {
|
||||
progressBar?.hide()
|
||||
photoActionsListener.onBringUp()
|
||||
}
|
||||
|
||||
override fun onCacheHit(imageType: Int, image: File?) {
|
||||
}
|
||||
|
||||
override fun onCacheMiss(imageType: Int, image: File?) {
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
}
|
||||
|
||||
override fun onProgress(progress: Int) {
|
||||
private fun loadImageFromNetwork() {
|
||||
if(previewUri != Uri.EMPTY) {
|
||||
// check if we have full image in the cache, if yes, use it
|
||||
// if not, look for preview in cache and use it if available
|
||||
// if not, load full image anyway
|
||||
Glide.with(this).asFile()
|
||||
.load(uri)
|
||||
.onlyRetrieveFromCache(true)
|
||||
.dontAnimate()
|
||||
.into(DummyCacheTarget(context!!, false))
|
||||
} else {
|
||||
// no need in cache lookup, just load full image
|
||||
showingPreview = false
|
||||
photoView.showImage(uri)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTransitionEnd() {
|
||||
// if we had preview, load full image, as transition has ended
|
||||
if (showingPreview) {
|
||||
showingPreview = false
|
||||
photoView.loadMainImageNow()
|
||||
}
|
||||
}
|
||||
|
||||
private val imageLoaderCallback = object : ImageLoader.Callback {
|
||||
override fun onSuccess(image: File?) {
|
||||
if(!showingPreview) {
|
||||
progressBar?.hide()
|
||||
photoView.ssiv?.let {
|
||||
it.orientation = SubsamplingScaleImageView.ORIENTATION_USE_EXIF
|
||||
it.setOnTouchListener(imageOnTouchListener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFail(error: Exception?) {
|
||||
progressBar?.hide()
|
||||
}
|
||||
|
||||
override fun onCacheHit(imageType: Int, image: File?) {
|
||||
// image is here, bring up the activity!
|
||||
photoActionsListener.onBringUp()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
// cache miss but image is downloading, bring up the activity
|
||||
photoActionsListener.onBringUp()
|
||||
}
|
||||
|
||||
override fun onCacheMiss(imageType: Int, image: File?) {
|
||||
// this callback is useless because it's called after
|
||||
// image is downloaded or pulled from cache
|
||||
// so in case of cache miss, onStart is used
|
||||
}
|
||||
|
||||
override fun onFinish() {}
|
||||
override fun onProgress(progress: Int) {
|
||||
// TODO: make use of it :)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user