Add scale gesture to stories.
This commit is contained in:
parent
9a21f5abca
commit
ffa249885e
4 changed files with 82 additions and 10 deletions
|
@ -14,6 +14,7 @@ import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
import android.view.ScaleGestureDetector
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.animation.Interpolator
|
import android.view.animation.Interpolator
|
||||||
import android.widget.FrameLayout
|
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.DimensionUnit
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
|
import org.thoughtcrime.securesms.animation.AnimationCompleteListener
|
||||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
import org.thoughtcrime.securesms.components.segmentedprogressbar.SegmentedProgressBar
|
import org.thoughtcrime.securesms.components.segmentedprogressbar.SegmentedProgressBar
|
||||||
import org.thoughtcrime.securesms.components.segmentedprogressbar.SegmentedProgressBarListener
|
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.setOnInterceptTouchEventListener { !storySlate.state.hasClickableContent && childFragmentManager.findFragmentById(R.id.story_content_container) !is StoryTextPostPreviewFragment }
|
||||||
cardWrapper.setOnTouchListener { _, event ->
|
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) {
|
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||||
viewModel.setIsUserTouching(true)
|
viewModel.setIsUserTouching(true)
|
||||||
} else if (event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL) {
|
} 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 class StoryGestureListener(
|
||||||
private val container: View,
|
private val container: View,
|
||||||
private val onGoToNext: () -> Unit,
|
private val onGoToNext: () -> Unit,
|
||||||
|
@ -1044,7 +1108,7 @@ class StoryViewerPageFragment :
|
||||||
|
|
||||||
private val maxSlide = DimensionUnit.DP.toPixels(56f * 2)
|
private val maxSlide = DimensionUnit.DP.toPixels(56f * 2)
|
||||||
|
|
||||||
override fun onDown(e: MotionEvent?): Boolean {
|
override fun onDown(e: MotionEvent): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -259,6 +259,10 @@ class StoryViewerPageViewModel(
|
||||||
storyViewerPlaybackStore.update { it.copy(isDisplayingInfoDialog = isDisplayingInfoDialog) }
|
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 {
|
private fun resolveSwipeToReplyState(state: StoryViewerPageState, index: Int): StoryViewerPageState.ReplyState {
|
||||||
if (index !in state.posts.indices) {
|
if (index !in state.posts.indices) {
|
||||||
return StoryViewerPageState.ReplyState.NONE
|
return StoryViewerPageState.ReplyState.NONE
|
||||||
|
|
|
@ -19,11 +19,12 @@ data class StoryViewerPlaybackState(
|
||||||
val isDisplayingFirstTimeNavigation: Boolean = false,
|
val isDisplayingFirstTimeNavigation: Boolean = false,
|
||||||
val isDisplayingInfoDialog: Boolean = false,
|
val isDisplayingInfoDialog: Boolean = false,
|
||||||
val isUserLongTouching: Boolean = false,
|
val isUserLongTouching: Boolean = false,
|
||||||
val isUserScrollingChild: Boolean = false
|
val isUserScrollingChild: Boolean = false,
|
||||||
|
val isUserScaling: Boolean = false
|
||||||
) {
|
) {
|
||||||
val hideChromeImmediate: Boolean = isRunningSharedElementAnimation
|
val hideChromeImmediate: Boolean = isRunningSharedElementAnimation
|
||||||
|
|
||||||
val hideChrome: Boolean = isRunningSharedElementAnimation || isUserLongTouching || isUserScrollingChild
|
val hideChrome: Boolean = isRunningSharedElementAnimation || isUserLongTouching || isUserScrollingChild || isUserScaling
|
||||||
|
|
||||||
val isPaused: Boolean = !areSegmentsInitialized ||
|
val isPaused: Boolean = !areSegmentsInitialized ||
|
||||||
isUserTouching ||
|
isUserTouching ||
|
||||||
|
@ -42,5 +43,6 @@ data class StoryViewerPlaybackState(
|
||||||
isDisplayingReactionAnimation ||
|
isDisplayingReactionAnimation ||
|
||||||
isRunningSharedElementAnimation ||
|
isRunningSharedElementAnimation ||
|
||||||
isDisplayingFirstTimeNavigation ||
|
isDisplayingFirstTimeNavigation ||
|
||||||
isDisplayingInfoDialog
|
isDisplayingInfoDialog ||
|
||||||
|
isUserScaling
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:viewBindingIgnore="true"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".stories.viewer.StoryViewerActivity">
|
tools:context=".stories.viewer.StoryViewerActivity"
|
||||||
|
tools:viewBindingIgnore="true">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout
|
<org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout
|
||||||
android:id="@+id/story_content_card_touch_interceptor"
|
android:id="@+id/story_content_card_touch_interceptor"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintDimensionRatio="9:16"
|
app:layout_constraintDimensionRatio="9:16"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
@ -54,10 +56,10 @@
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.stories.StoryVolumeOverlayView
|
<org.thoughtcrime.securesms.stories.StoryVolumeOverlayView
|
||||||
android:id="@+id/story_volume_overlay"
|
android:id="@+id/story_volume_overlay"
|
||||||
android:alpha="0"
|
|
||||||
android:layout_gravity="center_vertical|end"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:alpha="0" />
|
||||||
|
|
||||||
</org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout>
|
</org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue