Add animation when you directly react to a story.

fork-5.53.8
Alex Hart 2022-03-21 10:39:06 -03:00 zatwierdzone przez Greyson Parrelli
rodzic c0f843061e
commit cdef21d6c0
8 zmienionych plików z 579 dodań i 3 usunięć

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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) }
}

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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()
}

Wyświetl plik

@ -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()
}
}

Wyświetl plik

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/motion_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutDirection="ltr"
app:layoutDescription="@xml/on_reaction_sent_view_scene"
tools:showPaths="true">
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/emoji_1"
android:layout_width="56dp"
android:layout_height="56dp"
app:forceJumbo="true"
app:layout_constraintTag="emoji"
tools:background="@drawable/ic_heart_24" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/emoji_2"
android:layout_width="80dp"
android:layout_height="80dp"
app:forceJumbo="true"
app:layout_constraintTag="emoji"
tools:background="@drawable/ic_heart_24" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/emoji_3"
android:layout_width="56dp"
android:layout_height="56dp"
app:forceJumbo="true"
app:layout_constraintTag="emoji"
tools:background="@drawable/ic_heart_24" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/emoji_4"
android:layout_width="70dp"
android:layout_height="70dp"
app:forceJumbo="true"
app:layout_constraintTag="emoji"
tools:background="@drawable/ic_heart_24" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/emoji_5"
android:layout_width="64dp"
android:layout_height="64dp"
app:forceJumbo="true"
app:layout_constraintTag="emoji"
tools:background="@drawable/ic_heart_24" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/emoji_6"
android:layout_width="70dp"
android:layout_height="70dp"
app:forceJumbo="true"
app:layout_constraintTag="emoji"
tools:background="@drawable/ic_heart_24" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/emoji_7"
android:layout_width="40dp"
android:layout_height="40dp"
app:forceJumbo="true"
app:layout_constraintTag="emoji"
tools:background="@drawable/ic_heart_24" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/emoji_8"
android:layout_width="70dp"
android:layout_height="70dp"
app:forceJumbo="true"
app:layout_constraintTag="emoji"
tools:background="@drawable/ic_heart_24"
tools:layout_editor_absoluteX="20dp"
tools:layout_editor_absoluteY="591dp" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/emoji_9"
android:layout_width="100dp"
android:layout_height="100dp"
app:forceJumbo="true"
app:layout_constraintTag="emoji"
tools:background="@drawable/ic_heart_24" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/emoji_10"
android:layout_width="60dp"
android:layout_height="60dp"
app:forceJumbo="true"
app:layout_constraintTag="emoji"
tools:background="@drawable/ic_heart_24" />
<org.thoughtcrime.securesms.components.emoji.EmojiImageView
android:id="@+id/emoji_11"
android:layout_width="48dp"
android:layout_height="48dp"
app:forceJumbo="true"
app:layout_constraintTag="emoji"
tools:background="@drawable/ic_heart_24" />
</androidx.constraintlayout.motion.widget.MotionLayout>

Wyświetl plik

@ -243,4 +243,9 @@
app:totalSegments="0"
tools:totalSegments="5" />
<org.thoughtcrime.securesms.stories.viewer.reply.reaction.OnReactionSentView
android:id="@+id/on_reaction_sent_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -0,0 +1,375 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto">
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/emoji_1"
android:layout_width="56dp"
android:layout_height="56dp"
android:scaleX="0"
android:scaleY="0"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:id="@+id/emoji_2"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginEnd="80dp"
android:scaleX="0"
android:scaleY="0"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:id="@id/emoji_3"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginEnd="56dp"
android:scaleX="0"
android:scaleY="0"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:id="@+id/emoji_4"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginStart="12dp"
android:scaleX="0"
android:scaleY="0"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:id="@+id/emoji_5"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginStart="60dp"
android:scaleX="0"
android:scaleY="0"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:id="@+id/emoji_6"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginEnd="40dp"
android:scaleX="0"
android:scaleY="0"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:id="@+id/emoji_7"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="12dp"
android:scaleX="0"
android:scaleY="0"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:id="@+id/emoji_8"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginStart="20dp"
android:scaleX="0"
android:scaleY="0"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:id="@+id/emoji_9"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleX="0"
android:scaleY="0"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:id="@+id/emoji_10"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:scaleX="0"
android:scaleY="0"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:id="@+id/emoji_11"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="32dp"
android:scaleX="0"
android:scaleY="0"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintTop_toBottomOf="parent" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@id/emoji_1"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginBottom="16dp"
android:scaleX="1"
android:scaleY="1"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@id/emoji_2"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginEnd="80dp"
android:layout_marginBottom="16dp"
android:scaleX="1"
android:scaleY="1"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@id/emoji_3"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginEnd="56dp"
android:scaleX="1"
android:scaleY="1"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:id="@id/emoji_4"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginStart="12dp"
android:layout_marginBottom="16dp"
android:scaleX="1"
android:scaleY="1"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@id/emoji_5"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginStart="60dp"
android:layout_marginBottom="16dp"
android:scaleX="1"
android:scaleY="1"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@id/emoji_6"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginEnd="40dp"
android:layout_marginBottom="16dp"
android:scaleX="1"
android:scaleY="1"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent" />
<Constraint
android:id="@id/emoji_7"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="16dp"
android:scaleX="1"
android:scaleY="1"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@id/emoji_8"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="16dp"
android:scaleX="1"
android:scaleY="1"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@+id/emoji_9"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleX="1"
android:scaleY="1"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toBottomOf="parent" />
<Constraint
android:id="@id/emoji_10"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:scaleX="1"
android:scaleY="1"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintStart_toStartOf="parent" />
<Constraint
android:id="@id/emoji_11"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
android:scaleX="1"
android:scaleY="1"
motion:layout_constraintBottom_toTopOf="parent"
motion:layout_constraintEnd_toEndOf="parent" />
</ConstraintSet>
<Transition
motion:constraintSetEnd="@id/end"
motion:constraintSetStart="@id/start"
motion:duration="2500"
motion:motionInterpolator="cubic(0.25,0.25,0.5,1)">
<KeyFrameSet>
<KeyAttribute
android:scaleX="1"
motion:framePosition="40"
motion:motionTarget="emoji" />
<KeyAttribute
android:scaleY="1"
motion:framePosition="40"
motion:motionTarget="emoji" />
<KeyCycle
android:rotation="24"
motion:framePosition="0"
motion:motionTarget="@+id/emoji_1"
motion:waveOffset="0"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="24"
motion:framePosition="100"
motion:motionTarget="@+id/emoji_1"
motion:waveOffset="0"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="15"
motion:framePosition="0"
motion:motionTarget="@+id/emoji_2"
motion:waveOffset="-15"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="15"
motion:framePosition="100"
motion:motionTarget="@+id/emoji_2"
motion:waveOffset="-15"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="8"
motion:framePosition="0"
motion:motionTarget="@+id/emoji_3"
motion:waveOffset="0"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="8"
motion:framePosition="100"
motion:motionTarget="@+id/emoji_3"
motion:waveOffset="0"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="12"
motion:framePosition="0"
motion:motionTarget="@+id/emoji_4"
motion:waveOffset="0"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="12"
motion:framePosition="100"
motion:motionTarget="@+id/emoji_4"
motion:waveOffset="0"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="12"
motion:framePosition="0"
motion:motionTarget="@+id/emoji_5"
motion:waveOffset="0"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="12"
motion:framePosition="100"
motion:motionTarget="@+id/emoji_5"
motion:waveOffset="0"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="10"
motion:framePosition="0"
motion:motionTarget="@+id/emoji_8"
motion:waveOffset="2"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="10"
motion:framePosition="100"
motion:motionTarget="@+id/emoji_8"
motion:waveOffset="2"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="6"
motion:framePosition="0"
motion:motionTarget="@+id/emoji_11"
motion:waveOffset="2"
motion:wavePeriod="1" />
<KeyCycle
android:rotation="6"
motion:framePosition="100"
motion:motionTarget="@+id/emoji_11"
motion:waveOffset="2"
motion:wavePeriod="1" />
<KeyPosition
motion:framePosition="40"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@+id/emoji_1"
motion:percentY="1" />
<KeyPosition
motion:framePosition="44"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@+id/emoji_2"
motion:percentY="1" />
<KeyPosition
motion:framePosition="72"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@+id/emoji_3"
motion:percentY="1" />
<KeyPosition
motion:framePosition="52"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@+id/emoji_4"
motion:percentY="1" />
<KeyPosition
motion:framePosition="56"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@+id/emoji_5"
motion:percentY="1" />
<KeyPosition
motion:framePosition="48"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@+id/emoji_6"
motion:percentY="1" />
<KeyPosition
motion:framePosition="64"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@+id/emoji_7"
motion:percentY="1" />
<KeyPosition
motion:framePosition="68"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@+id/emoji_8"
motion:percentY="1" />
<KeyPosition
motion:framePosition="80"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@+id/emoji_9"
motion:percentY="1" />
<KeyPosition
motion:framePosition="88"
motion:keyPositionType="deltaRelative"
motion:motionTarget="@+id/emoji_11"
motion:percentY="1" />
</KeyFrameSet>
</Transition>
</MotionScene>