1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.compose.animation.scene
18 
19 import androidx.compose.animation.core.Animatable
20 import androidx.compose.animation.core.AnimationVector1D
21 import androidx.compose.animation.core.SpringSpec
22 import com.android.compose.animation.scene.content.state.TransitionState
23 import kotlinx.coroutines.CoroutineScope
24 import kotlinx.coroutines.Job
25 
animateContentnull26 internal fun CoroutineScope.animateContent(
27     layoutState: MutableSceneTransitionLayoutStateImpl,
28     transition: TransitionState.Transition,
29     oneOffAnimation: OneOffAnimation,
30     targetProgress: Float,
31     chain: Boolean = true,
32 ): Job {
33     oneOffAnimation.onRun = {
34         // Animate the progress to its target value.
35         val animationSpec = transition.transformationSpec.progressSpec
36         val visibilityThreshold =
37             (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
38         val replacedTransition = transition.replacedTransition
39         val initialProgress = replacedTransition?.progress ?: 0f
40         val initialVelocity = replacedTransition?.progressVelocity ?: 0f
41         val animatable =
42             Animatable(initialProgress, visibilityThreshold = visibilityThreshold).also {
43                 oneOffAnimation.animatable = it
44             }
45 
46         animatable.animateTo(targetProgress, animationSpec, initialVelocity)
47     }
48 
49     return layoutState.startTransitionImmediately(animationScope = this, transition, chain)
50 }
51 
52 internal class OneOffAnimation {
53     /**
54      * The animatable used to animate this transition.
55      *
56      * Note: This is lateinit because we need to first create this object so that
57      * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to
58      * the transition, which is needed to initialize this Animatable.
59      */
60     lateinit var animatable: Animatable<Float, AnimationVector1D>
61 
62     /** The runnable to run for this animation. */
63     lateinit var onRun: suspend () -> Unit
64 
65     val progress: Float
66         get() = animatable.value
67 
68     val progressVelocity: Float
69         get() = animatable.velocity
70 
runnull71     suspend fun run() {
72         onRun()
73     }
74 
freezeAndAnimateToCurrentStatenull75     fun freezeAndAnimateToCurrentState() {
76         // Do nothing, the state of one-off animations never change and we directly animate to it.
77     }
78 }
79 
80 // TODO(b/290184746): Compute a good default visibility threshold that depends on the layout size
81 // and screen density.
82 internal const val ProgressVisibilityThreshold = 1e-3f
83