Add animation when you directly react to a story.

This commit is contained in:
Alex Hart
2022-03-21 10:39:06 -03:00
committed by Greyson Parrelli
parent c0f843061e
commit cdef21d6c0
8 changed files with 579 additions and 3 deletions

View File

@@ -18,6 +18,7 @@ import androidx.core.view.GestureDetectorCompat
import androidx.core.view.doOnNextLayout
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResultListener
import androidx.fragment.app.viewModels
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
@@ -45,6 +46,7 @@ import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu
import org.thoughtcrime.securesms.stories.viewer.StoryViewerViewModel
import org.thoughtcrime.securesms.stories.viewer.reply.direct.StoryDirectReplyDialogFragment
import org.thoughtcrime.securesms.stories.viewer.reply.group.StoryGroupReplyBottomSheetDialogFragment
import org.thoughtcrime.securesms.stories.viewer.reply.reaction.OnReactionSentView
import org.thoughtcrime.securesms.stories.viewer.reply.tabs.StoryViewsAndRepliesDialogFragment
import org.thoughtcrime.securesms.stories.viewer.text.StoryTextPostPreviewFragment
import org.thoughtcrime.securesms.stories.viewer.views.StoryViewsBottomSheetDialogFragment
@@ -113,6 +115,7 @@ class StoryViewerPageFragment :
val caption: TextView = view.findViewById(R.id.story_caption)
val largeCaption: TextView = view.findViewById(R.id.story_large_caption)
val largeCaptionOverlay: View = view.findViewById(R.id.story_large_caption_overlay)
val reactionAnimationView: OnReactionSentView = view.findViewById(R.id.on_reaction_sent_view)
storySlate = view.findViewById(R.id.story_slate)
progressBar = view.findViewById(R.id.progress)
@@ -204,6 +207,12 @@ class StoryViewerPageFragment :
}
}
reactionAnimationView.callback = object : OnReactionSentView.Callback {
override fun onFinished() {
viewModel.setIsDisplayingReactionAnimation(false)
}
}
sharedViewModel.isScrolling.observe(viewLifecycleOwner) { isScrolling ->
viewModel.setIsUserScrollingParent(isScrolling)
}
@@ -276,6 +285,14 @@ class StoryViewerPageFragment :
}
adjustConstraintsForScreenDimensions(viewsAndReplies, cardWrapper, card)
childFragmentManager.setFragmentResultListener(StoryDirectReplyDialogFragment.REQUEST_EMOJI, viewLifecycleOwner) { _, bundle ->
val emoji = bundle.getString(StoryDirectReplyDialogFragment.REQUEST_EMOJI)
if (emoji != null) {
reactionAnimationView.playForEmoji(emoji)
viewModel.setIsDisplayingReactionAnimation(true)
}
}
}
override fun onResume() {

View File

@@ -135,6 +135,10 @@ class StoryViewerPageViewModel(
storyViewerPlaybackStore.update { it.copy(isSelectedPage = isSelectedPage) }
}
fun setIsDisplayingReactionAnimation(isDisplayingReactionAnimation: Boolean) {
storyViewerPlaybackStore.update { it.copy(isDisplayingReactionAnimation = isDisplayingReactionAnimation) }
}
fun setIsDisplayingContextMenu(isDisplayingContextMenu: Boolean) {
storyViewerPlaybackStore.update { it.copy(isDisplayingContextMenu = isDisplayingContextMenu) }
}

View File

@@ -13,7 +13,8 @@ data class StoryViewerPlaybackState(
val isSelectedPage: Boolean = false,
val isDisplayingSlate: Boolean = false,
val isFragmentResumed: Boolean = false,
val isDisplayingLinkPreviewTooltip: Boolean = false
val isDisplayingLinkPreviewTooltip: Boolean = false,
val isDisplayingReactionAnimation: Boolean = false
) {
val isPaused: Boolean = !areSegmentsInitialized ||
isUserTouching ||
@@ -28,5 +29,6 @@ data class StoryViewerPlaybackState(
!isSelectedPage ||
isDisplayingSlate ||
!isFragmentResumed ||
isDisplayingLinkPreviewTooltip
isDisplayingLinkPreviewTooltip ||
isDisplayingReactionAnimation
}

View File

@@ -6,6 +6,7 @@ import android.view.KeyEvent
import android.view.View
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.viewModels
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import org.thoughtcrime.securesms.R
@@ -146,6 +147,7 @@ class StoryDirectReplyDialogFragment :
}
companion object {
const val REQUEST_EMOJI = "request.code.emoji"
private const val ARG_STORY_ID = "arg.story.id"
private const val ARG_RECIPIENT_ID = "arg.recipient.id"
@@ -180,7 +182,12 @@ class StoryDirectReplyDialogFragment :
lifecycleDisposable += viewModel.sendReaction(emoji)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
// TODO [alex] -- Reaction explosion animation instead of toast.
setFragmentResult(
REQUEST_EMOJI,
Bundle().apply {
putString(REQUEST_EMOJI, emoji)
}
)
Toast.makeText(requireContext(), R.string.StoryDirectReplyDialogFragment__reaction_sent, Toast.LENGTH_LONG).show()
dismissAllowingStateLoss()
}

View File

@@ -0,0 +1,64 @@
package org.thoughtcrime.securesms.stories.viewer.reply.reaction
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.motion.widget.TransitionAdapter
import androidx.core.view.doOnNextLayout
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.emoji.EmojiImageView
import org.thoughtcrime.securesms.util.visible
class OnReactionSentView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
var callback: Callback? = null
init {
inflate(context, R.layout.on_reaction_sent_view, this)
}
private val motionLayout: MotionLayout = findViewById(R.id.motion_layout)
init {
motionLayout.addTransitionListener(object : TransitionAdapter() {
override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) {
visible = false
callback?.onFinished()
}
})
}
fun playForEmoji(emoji: CharSequence) {
motionLayout.progress = 0f
motionLayout.visible = true
listOf(
R.id.emoji_1,
R.id.emoji_2,
R.id.emoji_3,
R.id.emoji_4,
R.id.emoji_5,
R.id.emoji_6,
R.id.emoji_7,
R.id.emoji_8,
R.id.emoji_9,
R.id.emoji_10,
R.id.emoji_11,
).forEach {
findViewById<EmojiImageView>(it).setImageEmoji(emoji)
}
motionLayout.requestLayout()
motionLayout.doOnNextLayout {
motionLayout.transitionToEnd()
}
}
interface Callback {
fun onFinished()
}
}