diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt index b2e4f5d84..424222d9e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt @@ -14,6 +14,7 @@ import android.os.Build import android.os.Bundle import android.view.GestureDetector import android.view.MotionEvent +import android.view.ScaleGestureDetector import android.view.View import android.view.animation.Interpolator import android.widget.FrameLayout @@ -34,6 +35,7 @@ import io.reactivex.rxjava3.core.Observable import org.signal.core.util.DimensionUnit import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.animation.AnimationCompleteListener import org.thoughtcrime.securesms.components.AvatarImageView import org.thoughtcrime.securesms.components.segmentedprogressbar.SegmentedProgressBar import org.thoughtcrime.securesms.components.segmentedprogressbar.SegmentedProgressBarListener @@ -226,9 +228,24 @@ class StoryViewerPageFragment : ) ) + val scaleListener = StoryScaleListener( + viewModel, sharedViewModel, card + ) + + val scaleDetector = ScaleGestureDetector( + requireContext(), + scaleListener + ) + cardWrapper.setOnInterceptTouchEventListener { !storySlate.state.hasClickableContent && childFragmentManager.findFragmentById(R.id.story_content_container) !is StoryTextPostPreviewFragment } cardWrapper.setOnTouchListener { _, event -> - val result = gestureDetector.onTouchEvent(event) + scaleDetector.onTouchEvent(event) + val result = if (scaleDetector.isInProgress || scaleListener.isPerformingEndAnimation) { + true + } else { + gestureDetector.onTouchEvent(event) + } + if (event.actionMasked == MotionEvent.ACTION_DOWN) { viewModel.setIsUserTouching(true) } else if (event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL) { @@ -1026,6 +1043,53 @@ class StoryViewerPageFragment : } } + private class StoryScaleListener( + val viewModel: StoryViewerPageViewModel, + val sharedViewModel: StoryViewerViewModel, + val card: View + ) : ScaleGestureDetector.SimpleOnScaleGestureListener() { + + private var scaleFactor = 1f + + var isPerformingEndAnimation: Boolean = false + private set + + override fun onScaleBegin(detector: ScaleGestureDetector): Boolean { + viewModel.setIsUserScaling(true) + sharedViewModel.setIsChildScrolling(true) + card.animate().cancel() + card.apply { + pivotX = detector.focusX + pivotY = detector.focusY + } + + return true + } + + override fun onScale(detector: ScaleGestureDetector): Boolean { + scaleFactor *= detector.scaleFactor + + card.apply { + scaleX = max(scaleFactor, 1f) + scaleY = max(scaleFactor, 1f) + } + + return true + } + + override fun onScaleEnd(detector: ScaleGestureDetector) { + scaleFactor = 1f + isPerformingEndAnimation = true + card.animate().scaleX(1f).scaleY(1f).setListener(object : AnimationCompleteListener() { + override fun onAnimationEnd(animation: Animator?) { + isPerformingEndAnimation = false + viewModel.setIsUserScaling(false) + sharedViewModel.setIsChildScrolling(false) + } + }) + } + } + private class StoryGestureListener( private val container: View, private val onGoToNext: () -> Unit, @@ -1044,7 +1108,7 @@ class StoryViewerPageFragment : private val maxSlide = DimensionUnit.DP.toPixels(56f * 2) - override fun onDown(e: MotionEvent?): Boolean { + override fun onDown(e: MotionEvent): Boolean { return true } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt index 95c2a5c28..c1914bec5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageViewModel.kt @@ -259,6 +259,10 @@ class StoryViewerPageViewModel( storyViewerPlaybackStore.update { it.copy(isDisplayingInfoDialog = isDisplayingInfoDialog) } } + fun setIsUserScaling(isUserScaling: Boolean) { + storyViewerPlaybackStore.update { it.copy(isUserScaling = isUserScaling) } + } + private fun resolveSwipeToReplyState(state: StoryViewerPageState, index: Int): StoryViewerPageState.ReplyState { if (index !in state.posts.indices) { return StoryViewerPageState.ReplyState.NONE diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPlaybackState.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPlaybackState.kt index a52e29d3d..585cc5596 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPlaybackState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPlaybackState.kt @@ -19,11 +19,12 @@ data class StoryViewerPlaybackState( val isDisplayingFirstTimeNavigation: Boolean = false, val isDisplayingInfoDialog: Boolean = false, val isUserLongTouching: Boolean = false, - val isUserScrollingChild: Boolean = false + val isUserScrollingChild: Boolean = false, + val isUserScaling: Boolean = false ) { val hideChromeImmediate: Boolean = isRunningSharedElementAnimation - val hideChrome: Boolean = isRunningSharedElementAnimation || isUserLongTouching || isUserScrollingChild + val hideChrome: Boolean = isRunningSharedElementAnimation || isUserLongTouching || isUserScrollingChild || isUserScaling val isPaused: Boolean = !areSegmentsInitialized || isUserTouching || @@ -42,5 +43,6 @@ data class StoryViewerPlaybackState( isDisplayingReactionAnimation || isRunningSharedElementAnimation || isDisplayingFirstTimeNavigation || - isDisplayingInfoDialog + isDisplayingInfoDialog || + isUserScaling } diff --git a/app/src/main/res/layout/stories_viewer_fragment_page.xml b/app/src/main/res/layout/stories_viewer_fragment_page.xml index 8ebd38066..80b20e21d 100644 --- a/app/src/main/res/layout/stories_viewer_fragment_page.xml +++ b/app/src/main/res/layout/stories_viewer_fragment_page.xml @@ -1,16 +1,18 @@ + tools:context=".stories.viewer.StoryViewerActivity" + tools:viewBindingIgnore="true"> + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + android:alpha="0" />