1 /*
2  * Copyright 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 androidx.compose.material3.adaptive.layout
18 
19 import androidx.annotation.FloatRange
20 import androidx.compose.animation.core.FiniteAnimationSpec
21 import androidx.compose.animation.core.SeekableTransitionState
22 import androidx.compose.animation.core.Transition
23 import androidx.compose.animation.core.rememberTransition
24 import androidx.compose.foundation.MutatorMutex
25 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
26 import androidx.compose.runtime.Composable
27 import androidx.compose.runtime.Stable
28 import androidx.compose.runtime.getValue
29 import androidx.compose.runtime.mutableStateOf
30 import androidx.compose.runtime.setValue
31 
32 /**
33  * A read-only state of a three pane scaffold. It provides information about the [Transition]
34  * between [ThreePaneScaffoldValue]s.
35  */
36 @ExperimentalMaterial3AdaptiveApi
37 @Stable
38 sealed class ThreePaneScaffoldState {
39     /**
40      * Current [ThreePaneScaffoldValue] state of the transition. If there is an active transition,
41      * [currentState] and [targetState] are different.
42      */
43     abstract val currentState: ThreePaneScaffoldValue
44 
45     /**
46      * Target [ThreePaneScaffoldValue] state of the transition. If this is the same as
47      * [currentState], no transition is active.
48      */
49     abstract val targetState: ThreePaneScaffoldValue
50 
51     /**
52      * The progress of the transition from [currentState] to [targetState] as a fraction of the
53      * entire duration.
54      *
55      * If [targetState] and [currentState] are the same, [progressFraction] will be 0.
56      */
57     @get:FloatRange(from = 0.0, to = 1.0) abstract val progressFraction: Float
58 
59     /** Whether a predictive back navigation is currently in progress. */
60     abstract val isPredictiveBackInProgress: Boolean
61 
rememberTransitionnull62     @Composable internal abstract fun rememberTransition(): Transition<ThreePaneScaffoldValue>
63 }
64 
65 /**
66  * The seekable state of a three pane scaffold. It serves as the [SeekableTransitionState] to
67  * manipulate the [Transition] between [ThreePaneScaffoldValue]s.
68  */
69 @ExperimentalMaterial3AdaptiveApi
70 @Stable
71 class MutableThreePaneScaffoldState(initialScaffoldValue: ThreePaneScaffoldValue) :
72     ThreePaneScaffoldState() {
73     private val transitionState = SeekableTransitionState(initialScaffoldValue)
74 
75     override val currentState
76         get() = transitionState.currentState
77 
78     override val targetState
79         get() = transitionState.targetState
80 
81     @get:FloatRange(from = 0.0, to = 1.0)
82     override val progressFraction
83         get() = transitionState.fraction
84 
85     override var isPredictiveBackInProgress by mutableStateOf(false)
86         private set
87 
88     private val mutatorMutex = MutatorMutex()
89 
90     /**
91      * Creates a [Transition] and puts it in the [currentState] of this
92      * [MutableThreePaneScaffoldState]. If [targetState] changes, the [Transition] will change where
93      * it will animate to.
94      */
95     @Composable
96     override fun rememberTransition(): Transition<ThreePaneScaffoldValue> =
97         rememberTransition(transitionState, label = "ThreePaneScaffoldState")
98 
99     /**
100      * Sets [currentState] and [targetState][MutableThreePaneScaffoldState.targetState] to
101      * [targetState] and snaps all values to those at that state. The transition will not have any
102      * animations running after running [snapTo].
103      *
104      * @param targetState The [ThreePaneScaffoldValue] state to snap to.
105      * @see SeekableTransitionState.snapTo
106      */
107     suspend fun snapTo(targetState: ThreePaneScaffoldValue) {
108         mutatorMutex.mutate {
109             this.isPredictiveBackInProgress = false
110             transitionState.snapTo(targetState)
111         }
112     }
113 
114     /**
115      * Seeks the transition to [targetState] with [fraction] used to indicate the progress towards
116      * [targetState].
117      *
118      * @param fraction The fractional progress of the transition.
119      * @param targetState The [ThreePaneScaffoldValue] state to seek to.
120      * @param isPredictiveBackInProgress whether this seek is associated with a predictive back
121      *   gesture on the scaffold.
122      * @see SeekableTransitionState.seekTo
123      */
124     suspend fun seekTo(
125         @FloatRange(from = 0.0, to = 1.0) fraction: Float,
126         targetState: ThreePaneScaffoldValue = this.targetState,
127         isPredictiveBackInProgress: Boolean = false,
128     ) {
129         mutatorMutex.mutate {
130             this.isPredictiveBackInProgress = isPredictiveBackInProgress
131             transitionState.seekTo(fraction, targetState)
132         }
133     }
134 
135     /**
136      * Updates the current [targetState][MutableThreePaneScaffoldState.targetState] to [targetState]
137      * with an animation to the new state.
138      *
139      * @param targetState The [ThreePaneScaffoldValue] state to animate towards.
140      * @param animationSpec If provided, is used to animate the animation fraction. If `null`, the
141      *   transition is linearly traversed based on the duration of the transition.
142      * @param isPredictiveBackInProgress whether this animation is associated with a predictive back
143      *   gesture on the scaffold.
144      * @see SeekableTransitionState.animateTo
145      */
146     suspend fun animateTo(
147         targetState: ThreePaneScaffoldValue = this.targetState,
148         animationSpec: FiniteAnimationSpec<Float>? = null,
149         isPredictiveBackInProgress: Boolean = false,
150     ) {
151         mutatorMutex.mutate {
152             try {
153                 this.isPredictiveBackInProgress = isPredictiveBackInProgress
154                 transitionState.animateTo(targetState, animationSpec)
155             } finally {
156                 this.isPredictiveBackInProgress = false
157             }
158         }
159     }
160 }
161