From cdef21d6c0aa2b83f12e54796bd23ef7d70291ab Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Mon, 21 Mar 2022 10:39:06 -0300 Subject: [PATCH] Add animation when you directly react to a story. --- .../viewer/page/StoryViewerPageFragment.kt | 17 + .../viewer/page/StoryViewerPageViewModel.kt | 4 + .../viewer/page/StoryViewerPlaybackState.kt | 6 +- .../direct/StoryDirectReplyDialogFragment.kt | 9 +- .../reply/reaction/OnReactionSentView.kt | 64 +++ .../main/res/layout/on_reaction_sent_view.xml | 102 +++++ .../layout/stories_viewer_fragment_page.xml | 5 + .../res/xml/on_reaction_sent_view_scene.xml | 375 ++++++++++++++++++ 8 files changed, 579 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/reaction/OnReactionSentView.kt create mode 100644 app/src/main/res/layout/on_reaction_sent_view.xml create mode 100644 app/src/main/res/xml/on_reaction_sent_view_scene.xml 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 3c11770d19..166f37a8f0 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 @@ -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() { 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 5ee72b62fa..b59ce1f64a 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 @@ -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) } } 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 5bc6ee77d8..82498b0162 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 @@ -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 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/direct/StoryDirectReplyDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/direct/StoryDirectReplyDialogFragment.kt index 5c51d37d7a..c52a7cc125 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/direct/StoryDirectReplyDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/direct/StoryDirectReplyDialogFragment.kt @@ -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() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/reaction/OnReactionSentView.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/reaction/OnReactionSentView.kt new file mode 100644 index 0000000000..1d8bb78c1e --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/reaction/OnReactionSentView.kt @@ -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(it).setImageEmoji(emoji) + } + + motionLayout.requestLayout() + motionLayout.doOnNextLayout { + motionLayout.transitionToEnd() + } + } + + interface Callback { + fun onFinished() + } +} diff --git a/app/src/main/res/layout/on_reaction_sent_view.xml b/app/src/main/res/layout/on_reaction_sent_view.xml new file mode 100644 index 0000000000..4bad575377 --- /dev/null +++ b/app/src/main/res/layout/on_reaction_sent_view.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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 63a9367234..ff4f838052 100644 --- a/app/src/main/res/layout/stories_viewer_fragment_page.xml +++ b/app/src/main/res/layout/stories_viewer_fragment_page.xml @@ -243,4 +243,9 @@ app:totalSegments="0" tools:totalSegments="5" /> + + \ No newline at end of file diff --git a/app/src/main/res/xml/on_reaction_sent_view_scene.xml b/app/src/main/res/xml/on_reaction_sent_view_scene.xml new file mode 100644 index 0000000000..aac5f9d6a3 --- /dev/null +++ b/app/src/main/res/xml/on_reaction_sent_view_scene.xml @@ -0,0 +1,375 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +