From f37a589dce88cdff96b34e667143474f46c946d1 Mon Sep 17 00:00:00 2001 From: Ivan Kupalov Date: Mon, 27 Jul 2020 10:42:39 +0200 Subject: [PATCH] Roll back image viewer but retain swiping fix (#1863) This fixes crashes with images which were too wide (aspect ratio wise). --- app/build.gradle | 2 +- .../compose/dialog/CaptionDialog.kt | 6 +- .../tusky/fragment/ViewImageFragment.kt | 124 ++++++++++-------- .../main/res/layout/fragment_view_image.xml | 2 +- 4 files changed, 73 insertions(+), 61 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 38996d500..6f8f08a9d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -159,7 +159,7 @@ dependencies { implementation "com.github.connyduck:sparkbutton:4.0.0" - implementation 'com.github.MikeOrtiz:TouchImageView:3.0.1' + implementation "com.github.chrisbanes:PhotoView:2.3.0" implementation "com.mikepenz:materialdrawer:$materialdrawerVersion" implementation "com.mikepenz:materialdrawer-iconics:$materialdrawerVersion" diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt index a768df098..f1e4b3ea7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt @@ -33,9 +33,9 @@ import at.connyduck.sparkbutton.helpers.Utils import com.bumptech.glide.Glide import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition +import com.github.chrisbanes.photoview.PhotoView import com.keylesspalace.tusky.R import com.keylesspalace.tusky.util.withLifecycleContext -import com.ortiz.touchview.TouchImageView // https://github.com/tootsuite/mastodon/blob/1656663/app/models/media_attachment.rb#L94 private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 420 @@ -50,8 +50,8 @@ fun T.makeCaptionDialog(existingDescription: String?, dialogLayout.setPadding(padding, padding, padding, padding) dialogLayout.orientation = LinearLayout.VERTICAL - val imageView = TouchImageView(this).apply { - maxZoom = 6f + val imageView = PhotoView(this).apply { + maximumScale = 6f } val displayMetrics = DisplayMetrics() diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt index 49e313fae..a4b7ea212 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt @@ -29,6 +29,7 @@ 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 @@ -45,6 +46,7 @@ class ViewImageFragment : ViewMediaFragment() { fun onPhotoTap() } + private lateinit var attacher: PhotoViewAttacher private lateinit var photoActionsListener: PhotoActionsListener private lateinit var toolbar: View private var transition = BehaviorSubject.create() @@ -61,9 +63,68 @@ class ViewImageFragment : ViewMediaFragment() { photoActionsListener = context as PhotoActionsListener } + @SuppressLint("ClickableViewAccessibility") override fun setupMediaView(url: String, previewUrl: String?) { descriptionView = mediaDescription photoView.transitionName = url + attacher = PhotoViewAttacher(photoView).apply { + // This prevents conflicts with ViewPager + setAllowParentInterceptOnEdge(true) + + // Clicking outside the photo closes the viewer. + setOnOutsidePhotoTapListener { photoActionsListener.onDismiss() } + 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. */ + setOnSingleFlingListener { _, _, velocityX, velocityY -> + var result = false + if (abs(velocityY) > abs(velocityX)) { + photoActionsListener.onDismiss() + result = true + } + result + } + } + + val gestureDetector = GestureDetector(requireContext(), object : GestureDetector.SimpleOnGestureListener() { + override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { + onMediaTap() + return true + } + }) + + var lastY = 0f + + photoView.setOnTouchListener { v, event -> + // This part is for scaling/translating on vertical move. + // We use raw coordinates to get the correct ones during scaling + + gestureDetector.onTouchEvent(event) + + if (event.action == MotionEvent.ACTION_DOWN) { + lastY = event.rawY + } else if (event.pointerCount == 1 + && attacher.scale == 1f + && event.action == MotionEvent.ACTION_MOVE + ) { + 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 + return@setOnTouchListener true + } + } else if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) { + onGestureEnd() + } + attacher.onTouch(v, event) + } + startedTransition = false loadImageFromNetwork(url, previewUrl, photoView) } @@ -74,64 +135,9 @@ class ViewImageFragment : ViewMediaFragment() { return inflater.inflate(R.layout.fragment_view_image, container, false) } - @SuppressLint("ClickableViewAccessibility") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val gestureDetector = GestureDetector(requireContext(), object : GestureDetector.SimpleOnGestureListener() { - override fun onSingleTapConfirmed(e: MotionEvent?): Boolean { - onMediaTap() - return true - } - }) - - var lastY = 0f - photoView.setOnTouchListener { _, event -> - // This part is for scaling/translating on vertical move. - // We use raw coordinates to get the correct ones during scaling - var result = true - - gestureDetector.onTouchEvent(event) - - if (event.action == MotionEvent.ACTION_DOWN) { - lastY = event.rawY - } else if (!photoView.isZoomed && event.action == MotionEvent.ACTION_MOVE) { - 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 - } - return@setOnTouchListener true - } else if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) { - onGestureEnd() - } else if (event.pointerCount >= 2 || photoView.canScrollHorizontally(1) && photoView.canScrollHorizontally(-1)) { - // Starting from here is adapted code from TouchImageView to play nice with pager. - - // Can scroll horizontally checks if there's still a part of the image. - // That can be scrolled until you reach the edge multi-touch event. - val parent = view.parent - result = when (event.action) { - MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> { - // Disallow RecyclerView to intercept touch events. - parent.requestDisallowInterceptTouchEvent(true) - // Disable touch on view - false - } - MotionEvent.ACTION_UP -> { - // Allow RecyclerView to intercept touch events. - parent.requestDisallowInterceptTouchEvent(false) - true - } - else -> true - } - } - result - } val arguments = this.requireArguments() val attachment = arguments.getParcelable(ARG_ATTACHMENT) @@ -267,7 +273,13 @@ class ViewImageFragment : ViewMediaFragment() { // another branch. take() will unsubscribe after we have it to not leak menmory transition .take(1) - .subscribe { target.onResourceReady(resource, null) } + .subscribe { + target.onResourceReady(resource, null) + // It's needed. Don't ask why, I don't know, setImageDrawable() should + // do it by itself but somehow it doesn't work automatically. + // Just do it. If you don't, image will jump around when touched. + attacher.update() + } } return true } diff --git a/app/src/main/res/layout/fragment_view_image.xml b/app/src/main/res/layout/fragment_view_image.xml index 1e3f442ac..1c83b1999 100644 --- a/app/src/main/res/layout/fragment_view_image.xml +++ b/app/src/main/res/layout/fragment_view_image.xml @@ -7,7 +7,7 @@ android:clickable="true" android:focusable="true"> -