• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2023 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 package com.android.systemui.util.ui
17 
18 import com.android.systemui.util.ui.AnimatedValue.Animating
19 import com.android.systemui.util.ui.AnimatedValue.NotAnimating
20 import kotlinx.coroutines.CompletableDeferred
21 import kotlinx.coroutines.flow.Flow
22 import kotlinx.coroutines.flow.transformLatest
23 
24 /**
25  * A state comprised of a [value] of type [T] paired with a boolean indicating whether or not the
26  * value [isAnimating][isAnimating] in the UI.
27  */
28 sealed interface AnimatedValue<out T> {
29 
30     /** A [state][value] that is not actively animating in the UI. */
31     data class NotAnimating<out T>(val value: T) : AnimatedValue<T>
32 
33     /**
34      * A [state][value] that is actively animating in the UI. Invoking [onStopAnimating] will signal
35      * the source of the state to stop animating.
36      */
37     data class Animating<out T>(
38         val value: T,
39         val onStopAnimating: () -> Unit,
40     ) : AnimatedValue<T>
41 }
42 
43 /** The state held in this [AnimatedValue]. */
44 inline val <T> AnimatedValue<T>.value: T
45     get() =
46         when (this) {
47             is Animating -> value
48             is NotAnimating -> value
49         }
50 
51 /** Returns whether or not this [AnimatedValue] is animating or not. */
52 inline val <T> AnimatedValue<T>.isAnimating: Boolean
53     get() = this is Animating<T>
54 
55 /**
56  * If this [AnimatedValue] [isAnimating], then signal that the animation should be stopped.
57  * Otherwise, do nothing.
58  */
59 @Suppress("NOTHING_TO_INLINE")
stopAnimatingnull60 inline fun AnimatedValue<*>.stopAnimating() {
61     if (this is Animating) onStopAnimating()
62 }
63 
64 /**
65  * An event comprised of a [value] of type [T] paired with a [boolean][startAnimating] indicating
66  * whether or not this event should start an animation.
67  */
68 data class AnimatableEvent<out T>(
69     val value: T,
70     val startAnimating: Boolean,
71 )
72 
73 /**
74  * Returns a [Flow] that tracks an [AnimatedValue] state. The input [Flow] is used to update the
75  * [AnimatedValue.value], as well as [AnimatedValue.isAnimating] if the event's
76  * [AnimatableEvent.startAnimating] value is `true`. When [completionEvents] emits a value, the
77  * [AnimatedValue.isAnimating] will flip to `false`.
78  */
toAnimatedValueFlownull79 fun <T> Flow<AnimatableEvent<T>>.toAnimatedValueFlow(): Flow<AnimatedValue<T>> =
80     transformLatest { (value, startAnimating) ->
81         if (startAnimating) {
82             val onCompleted = CompletableDeferred<Unit>()
83             emit(Animating(value) { onCompleted.complete(Unit) })
84             // Wait for a completion now that we've started animating
85             onCompleted.await()
86         }
87         emit(NotAnimating(value))
88     }
89 
90 /**
91  * Zip two [AnimatedValue]s together into a single [AnimatedValue], using [block] to combine the
92  * [value]s of each.
93  *
94  * If either [AnimatedValue] [isAnimating], then the result is also animating. Invoking
95  * [stopAnimating] on the result is equivalent to invoking [stopAnimating] on each input.
96  */
zipnull97 inline fun <A, B, Z> zip(
98     valueA: AnimatedValue<A>,
99     valueB: AnimatedValue<B>,
100     block: (A, B) -> Z,
101 ): AnimatedValue<Z> {
102     val zippedValue = block(valueA.value, valueB.value)
103     return when (valueA) {
104         is Animating ->
105             when (valueB) {
106                 is Animating ->
107                     Animating(zippedValue) {
108                         valueA.onStopAnimating()
109                         valueB.onStopAnimating()
110                     }
111                 is NotAnimating -> Animating(zippedValue, valueA.onStopAnimating)
112             }
113         is NotAnimating ->
114             when (valueB) {
115                 is Animating -> Animating(zippedValue, valueB.onStopAnimating)
116                 is NotAnimating -> NotAnimating(zippedValue)
117             }
118     }
119 }
120 
121 /**
122  * Flattens a nested [AnimatedValue], the result of which holds the [value] of the inner
123  * [AnimatedValue].
124  *
125  * If either the outer or inner [AnimatedValue] [isAnimating], then the flattened result is also
126  * animating. Invoking [stopAnimating] on the result is equivalent to invoking [stopAnimating] on
127  * both the outer and inner values.
128  */
129 @Suppress("NOTHING_TO_INLINE")
flattennull130 inline fun <T> AnimatedValue<AnimatedValue<T>>.flatten(): AnimatedValue<T> = flatMap { it }
131 
132 /**
133  * Returns an [AnimatedValue], the [value] of which is the result of the given [value] applied to
134  * [block].
135  */
mapnull136 inline fun <A, B> AnimatedValue<A>.map(block: (A) -> B): AnimatedValue<B> =
137     when (this) {
138         is Animating -> Animating(block(value), ::stopAnimating)
139         is NotAnimating -> NotAnimating(block(value))
140     }
141 
142 /**
143  * Returns an [AnimatedValue] from the result of [block] being invoked on the [value] original
144  * [AnimatedValue].
145  *
146  * If either the input [AnimatedValue] or the result of [block] [isAnimating], then the flattened
147  * result is also animating. Invoking [stopAnimating] on the result is equivalent to invoking
148  * [stopAnimating] on both values.
149  */
flatMapnull150 inline fun <A, B> AnimatedValue<A>.flatMap(block: (A) -> AnimatedValue<B>): AnimatedValue<B> =
151     when (this) {
152         is NotAnimating -> block(value)
153         is Animating ->
154             when (val inner = block(value)) {
155                 is Animating ->
156                     Animating(inner.value) {
157                         onStopAnimating()
158                         inner.onStopAnimating()
159                     }
160                 is NotAnimating -> Animating(inner.value, onStopAnimating)
161             }
162     }
163