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