kopia lustrzana https://github.com/ryukoposting/Signal-Android
Implement slide to close in Story viewer.
rodzic
403958fed3
commit
db309b7930
|
@ -36,6 +36,10 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
|
||||||
val adapter = StoryViewerPagerAdapter(this, storyId)
|
val adapter = StoryViewerPagerAdapter(this, storyId)
|
||||||
storyPager.adapter = adapter
|
storyPager.adapter = adapter
|
||||||
|
|
||||||
|
viewModel.isChildScrolling.observe(viewLifecycleOwner) {
|
||||||
|
storyPager.isUserInputEnabled = !it
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||||
adapter.setPages(state.pages)
|
adapter.setPages(state.pages)
|
||||||
if (state.pages.isNotEmpty() && storyPager.currentItem != state.page) {
|
if (state.pages.isNotEmpty() && storyPager.currentItem != state.page) {
|
||||||
|
|
|
@ -22,6 +22,9 @@ class StoryViewerViewModel(
|
||||||
private val scrollStatePublisher: MutableLiveData<Boolean> = MutableLiveData(false)
|
private val scrollStatePublisher: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||||
val isScrolling: LiveData<Boolean> = scrollStatePublisher
|
val isScrolling: LiveData<Boolean> = scrollStatePublisher
|
||||||
|
|
||||||
|
private val childScrollStatePublisher: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||||
|
val isChildScrolling: LiveData<Boolean> = childScrollStatePublisher
|
||||||
|
|
||||||
init {
|
init {
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
@ -103,6 +106,10 @@ class StoryViewerViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setIsChildScrolling(isChildScrolling: Boolean) {
|
||||||
|
childScrollStatePublisher.value = isChildScrolling
|
||||||
|
}
|
||||||
|
|
||||||
class Factory(
|
class Factory(
|
||||||
private val startRecipientId: RecipientId,
|
private val startRecipientId: RecipientId,
|
||||||
private val repository: StoryViewerRepository
|
private val repository: StoryViewerRepository
|
||||||
|
|
|
@ -10,15 +10,16 @@ import android.os.Bundle
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.animation.Interpolator
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.constraintlayout.widget.ConstraintSet
|
import androidx.constraintlayout.widget.ConstraintSet
|
||||||
import androidx.core.view.GestureDetectorCompat
|
import androidx.core.view.GestureDetectorCompat
|
||||||
|
import androidx.core.view.animation.PathInterpolatorCompat
|
||||||
import androidx.core.view.doOnNextLayout
|
import androidx.core.view.doOnNextLayout
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.setFragmentResultListener
|
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.core.Observable
|
import io.reactivex.rxjava3.core.Observable
|
||||||
|
@ -61,6 +62,7 @@ import org.thoughtcrime.securesms.util.visible
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class StoryViewerPageFragment :
|
class StoryViewerPageFragment :
|
||||||
Fragment(R.layout.stories_viewer_fragment_page),
|
Fragment(R.layout.stories_viewer_fragment_page),
|
||||||
|
@ -148,7 +150,9 @@ class StoryViewerPageFragment :
|
||||||
cardWrapper,
|
cardWrapper,
|
||||||
viewModel::goToNextPost,
|
viewModel::goToNextPost,
|
||||||
viewModel::goToPreviousPost,
|
viewModel::goToPreviousPost,
|
||||||
this::startReply
|
this::startReply,
|
||||||
|
sharedViewModel = sharedViewModel
|
||||||
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -161,6 +165,18 @@ class StoryViewerPageFragment :
|
||||||
} else if (event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL) {
|
} else if (event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL) {
|
||||||
viewModel.setIsUserTouching(false)
|
viewModel.setIsUserTouching(false)
|
||||||
showChrome()
|
showChrome()
|
||||||
|
|
||||||
|
val canCloseFromHorizontalSlide = requireView().translationX > DimensionUnit.DP.toPixels(56f)
|
||||||
|
val canCloseFromVerticalSlide = requireView().translationY > DimensionUnit.DP.toPixels(56f)
|
||||||
|
if ((canCloseFromHorizontalSlide || canCloseFromVerticalSlide) && event.actionMasked == MotionEvent.ACTION_UP) {
|
||||||
|
requireActivity().finish()
|
||||||
|
} else {
|
||||||
|
requireView().animate()
|
||||||
|
.setInterpolator(StoryGestureListener.INTERPOLATOR)
|
||||||
|
.setDuration(100)
|
||||||
|
.translationX(0f)
|
||||||
|
.translationY(0f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
|
@ -664,18 +680,49 @@ class StoryViewerPageFragment :
|
||||||
private val container: View,
|
private val container: View,
|
||||||
private val onGoToNext: () -> Unit,
|
private val onGoToNext: () -> Unit,
|
||||||
private val onGoToPrevious: () -> Unit,
|
private val onGoToPrevious: () -> Unit,
|
||||||
private val onReplyToPost: () -> Unit
|
private val onReplyToPost: () -> Unit,
|
||||||
|
private val viewToTranslate: View = container.parent as View,
|
||||||
|
private val sharedViewModel: StoryViewerViewModel
|
||||||
) : GestureDetector.SimpleOnGestureListener() {
|
) : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val BOUNDARY_NEXT = 0.80f
|
private const val BOUNDARY_NEXT = 0.80f
|
||||||
private const val BOUNDARY_PREV = 1f - BOUNDARY_NEXT
|
private const val BOUNDARY_PREV = 1f - BOUNDARY_NEXT
|
||||||
|
|
||||||
|
val INTERPOLATOR: Interpolator = PathInterpolatorCompat.create(0.4f, 0f, 0.2f, 1f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val maxSlide = DimensionUnit.DP.toPixels(56f * 2)
|
||||||
|
|
||||||
override fun onDown(e: MotionEvent?): Boolean {
|
override fun onDown(e: MotionEvent?): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
|
||||||
|
val isFirstStory = sharedViewModel.state.value?.page == 0
|
||||||
|
val isXMagnitudeGreaterThanYMagnitude = abs(distanceX) > abs(distanceY) || viewToTranslate.translationX > 0f
|
||||||
|
val isFirstAndHasYTranslationOrNegativeY = isFirstStory && (viewToTranslate.translationY > 0f || distanceY < 0f)
|
||||||
|
|
||||||
|
sharedViewModel.setIsChildScrolling(isXMagnitudeGreaterThanYMagnitude || isFirstAndHasYTranslationOrNegativeY)
|
||||||
|
if (isFirstStory) {
|
||||||
|
val delta = max(0f, (e2.rawY - e1.rawY)) / 3f
|
||||||
|
val percent = INTERPOLATOR.getInterpolation(delta/maxSlide)
|
||||||
|
val distance = maxSlide * percent
|
||||||
|
|
||||||
|
viewToTranslate.animate().cancel()
|
||||||
|
viewToTranslate.translationY = distance
|
||||||
|
}
|
||||||
|
|
||||||
|
val delta = max(0f, (e2.rawX - e1.rawX)) / 3f
|
||||||
|
val percent = INTERPOLATOR.getInterpolation(delta / maxSlide)
|
||||||
|
val distance = maxSlide * percent
|
||||||
|
|
||||||
|
viewToTranslate.animate().cancel()
|
||||||
|
viewToTranslate.translationX = distance
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
|
override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
|
||||||
val isSideSwipe = abs(velocityX) > abs(velocityY)
|
val isSideSwipe = abs(velocityX) > abs(velocityY)
|
||||||
if (!isSideSwipe) {
|
if (!isSideSwipe) {
|
||||||
|
|
Ładowanie…
Reference in New Issue