kopia lustrzana https://github.com/ryukoposting/Signal-Android
218 wiersze
7.3 KiB
Kotlin
218 wiersze
7.3 KiB
Kotlin
package org.thoughtcrime.securesms.stories.viewer
|
|
|
|
import android.graphics.RenderEffect
|
|
import android.graphics.Shader
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import android.view.View
|
|
import androidx.core.app.ActivityCompat
|
|
import androidx.core.view.ViewCompat
|
|
import androidx.fragment.app.Fragment
|
|
import androidx.fragment.app.viewModels
|
|
import androidx.viewpager2.widget.ViewPager2
|
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
import org.signal.core.util.getParcelableArrayListCompat
|
|
import org.signal.core.util.getParcelableCompat
|
|
import org.thoughtcrime.securesms.R
|
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
|
import org.thoughtcrime.securesms.recipients.Recipient
|
|
import org.thoughtcrime.securesms.recipients.RecipientId
|
|
import org.thoughtcrime.securesms.stories.StoryViewerArgs
|
|
import org.thoughtcrime.securesms.stories.viewer.first.StoryFirstTimeNavigationFragment
|
|
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageArgs
|
|
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageFragment
|
|
import org.thoughtcrime.securesms.stories.viewer.reply.StoriesSharedElementCrossFaderView
|
|
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
|
|
|
/**
|
|
* Fragment which manages a vertical pager fragment of stories.
|
|
*/
|
|
class StoryViewerFragment :
|
|
Fragment(R.layout.stories_viewer_fragment),
|
|
StoryViewerPageFragment.Callback,
|
|
StoriesSharedElementCrossFaderView.Callback {
|
|
|
|
private val onPageChanged = OnPageChanged()
|
|
|
|
private lateinit var storyPager: ViewPager2
|
|
|
|
private val viewModel: StoryViewerViewModel by viewModels(
|
|
factoryProducer = {
|
|
StoryViewerViewModel.Factory(storyViewerArgs, StoryViewerRepository())
|
|
}
|
|
)
|
|
|
|
private val lifecycleDisposable = LifecycleDisposable()
|
|
|
|
private val storyViewerArgs: StoryViewerArgs by lazy { requireArguments().getParcelableCompat(ARGS, StoryViewerArgs::class.java)!! }
|
|
|
|
private lateinit var storyCrossfader: StoriesSharedElementCrossFaderView
|
|
|
|
private var pagerOnPageSelectedLock: Boolean = false
|
|
|
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
storyCrossfader = view.findViewById(R.id.story_content_crossfader)
|
|
storyPager = view.findViewById(R.id.story_item_pager)
|
|
|
|
ViewCompat.setTransitionName(storyCrossfader, "story")
|
|
storyCrossfader.callback = this
|
|
|
|
val adapter = StoryViewerPagerAdapter(
|
|
this,
|
|
StoryViewerPageArgs(
|
|
recipientId = Recipient.UNKNOWN.id,
|
|
initialStoryId = storyViewerArgs.storyId,
|
|
isJumpForwardToUnviewed = storyViewerArgs.isJumpToUnviewed,
|
|
isOutgoingOnly = storyViewerArgs.isFromMyStories,
|
|
source = when {
|
|
storyViewerArgs.isFromInfoContextMenuAction -> StoryViewerPageArgs.Source.INFO_CONTEXT
|
|
storyViewerArgs.isFromNotification -> StoryViewerPageArgs.Source.NOTIFICATION
|
|
else -> StoryViewerPageArgs.Source.UNKNOWN
|
|
},
|
|
groupReplyStartPosition = storyViewerArgs.groupReplyStartPosition
|
|
)
|
|
)
|
|
|
|
storyPager.adapter = adapter
|
|
storyPager.overScrollMode = ViewPager2.OVER_SCROLL_NEVER
|
|
|
|
lifecycleDisposable += viewModel.allowParentScrolling.observeOn(AndroidSchedulers.mainThread()).subscribe {
|
|
storyPager.isUserInputEnabled = it
|
|
}
|
|
|
|
storyPager.offscreenPageLimit = 1
|
|
|
|
lifecycleDisposable.bindTo(viewLifecycleOwner)
|
|
lifecycleDisposable += viewModel.state.observeOn(AndroidSchedulers.mainThread()).subscribe { state ->
|
|
if (state.noPosts) {
|
|
ActivityCompat.finishAfterTransition(requireActivity())
|
|
}
|
|
|
|
adapter.setPages(state.pages)
|
|
if (state.pages.isNotEmpty() && storyPager.currentItem != state.page) {
|
|
pagerOnPageSelectedLock = true
|
|
storyPager.isUserInputEnabled = false
|
|
storyPager.setCurrentItem(state.page, state.previousPage > -1)
|
|
pagerOnPageSelectedLock = false
|
|
|
|
if (state.page >= state.pages.size) {
|
|
ActivityCompat.finishAfterTransition(requireActivity())
|
|
lifecycleDisposable.clear()
|
|
}
|
|
}
|
|
|
|
when (state.crossfadeSource) {
|
|
is StoryViewerState.CrossfadeSource.TextModel -> storyCrossfader.setSourceView(state.crossfadeSource.storyTextPostModel)
|
|
is StoryViewerState.CrossfadeSource.ImageUri -> storyCrossfader.setSourceView(state.crossfadeSource.imageUri, state.crossfadeSource.imageBlur)
|
|
StoryViewerState.CrossfadeSource.None -> Unit
|
|
}
|
|
|
|
if (state.crossfadeTarget is StoryViewerState.CrossfadeTarget.Record) {
|
|
storyCrossfader.setTargetView(state.crossfadeTarget.messageRecord)
|
|
requireActivity().supportStartPostponedEnterTransition()
|
|
}
|
|
|
|
if (state.skipCrossfade) {
|
|
viewModel.setCrossfaderIsReady(true)
|
|
}
|
|
|
|
if (state.loadState.isReady()) {
|
|
storyCrossfader.alpha = 0f
|
|
}
|
|
}
|
|
|
|
if (savedInstanceState != null && savedInstanceState.containsKey(HIDDEN)) {
|
|
val ids: List<RecipientId> = savedInstanceState.getParcelableArrayListCompat(HIDDEN, RecipientId::class.java)!!
|
|
viewModel.addHiddenAndRefresh(ids.toSet())
|
|
} else {
|
|
viewModel.refresh()
|
|
|
|
if (!SignalStore.storyValues().userHasSeenFirstNavView) {
|
|
StoryFirstTimeNavigationFragment().show(childFragmentManager, null)
|
|
}
|
|
}
|
|
|
|
if (Build.VERSION.SDK_INT >= 31) {
|
|
lifecycleDisposable += viewModel.isFirstTimeNavigationShowing.subscribe {
|
|
if (it) {
|
|
requireView().rootView.setRenderEffect(RenderEffect.createBlurEffect(100f, 100f, Shader.TileMode.CLAMP))
|
|
} else {
|
|
requireView().rootView.setRenderEffect(null)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onSaveInstanceState(outState: Bundle) {
|
|
outState.putParcelableArrayList(HIDDEN, ArrayList(viewModel.getHidden()))
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
viewModel.setIsScrolling(false)
|
|
storyPager.registerOnPageChangeCallback(onPageChanged)
|
|
}
|
|
|
|
override fun onPause() {
|
|
super.onPause()
|
|
viewModel.setIsScrolling(false)
|
|
storyPager.unregisterOnPageChangeCallback(onPageChanged)
|
|
}
|
|
|
|
override fun onGoToPreviousStory(recipientId: RecipientId) {
|
|
viewModel.onGoToPrevious(recipientId)
|
|
}
|
|
|
|
override fun onFinishedPosts(recipientId: RecipientId) {
|
|
viewModel.onGoToNext(recipientId)
|
|
}
|
|
|
|
override fun onStoryHidden(recipientId: RecipientId) {
|
|
viewModel.addHiddenAndRefresh(setOf(recipientId))
|
|
}
|
|
|
|
override fun onContentTranslation(x: Float, y: Float) {
|
|
storyCrossfader.translationX = x
|
|
storyCrossfader.translationY = y
|
|
}
|
|
|
|
override fun onReadyToAnimate() {
|
|
}
|
|
|
|
override fun onAnimationStarted() {
|
|
viewModel.setCrossfaderIsReady(false)
|
|
}
|
|
|
|
override fun onAnimationFinished() {
|
|
viewModel.setCrossfaderIsReady(true)
|
|
}
|
|
|
|
inner class OnPageChanged : ViewPager2.OnPageChangeCallback() {
|
|
override fun onPageSelected(position: Int) {
|
|
if (!pagerOnPageSelectedLock) {
|
|
viewModel.setSelectedPage(position)
|
|
}
|
|
}
|
|
|
|
override fun onPageScrollStateChanged(state: Int) {
|
|
viewModel.setIsScrolling(state == ViewPager2.SCROLL_STATE_DRAGGING)
|
|
if (state == ViewPager2.SCROLL_STATE_IDLE) {
|
|
storyPager.isUserInputEnabled = true
|
|
}
|
|
}
|
|
}
|
|
|
|
companion object {
|
|
private const val ARGS = "args"
|
|
private const val HIDDEN = "hidden"
|
|
|
|
fun create(storyViewerArgs: StoryViewerArgs): Fragment {
|
|
return StoryViewerFragment().apply {
|
|
arguments = Bundle().apply {
|
|
putParcelable(ARGS, storyViewerArgs)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|