[flexiglass] Scalable scene transition definitions in Flexiglass.
All inter-scene transitions are now defined in `SceneContainerTransitions`. The actual transition spec is defined in separate DSL files under `com.android.systemui.scene.ui.composable.transitions`. This CL also adds a default cross-fade transition for any scenes whose transitions aren't defined yet. Fix: 294911992 Test: Manually tested the different transitions on a device. Change-Id: I0613830edd71fcd0bc140356cff54fc79348ac2d
This commit is contained in:
@@ -37,7 +37,14 @@ sealed class Key(val name: String, val identity: Any) {
|
||||
}
|
||||
|
||||
/** Key for a scene. */
|
||||
class SceneKey(name: String, identity: Any = Object()) : Key(name, identity) {
|
||||
class SceneKey(
|
||||
name: String,
|
||||
identity: Any = Object(),
|
||||
) : Key(name, identity) {
|
||||
|
||||
/** The unique [ElementKey] identifying this scene's root element. */
|
||||
val rootElementKey = ElementKey(name, identity)
|
||||
|
||||
override fun toString(): String {
|
||||
return "SceneKey(name=$name)"
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import com.android.compose.ui.util.fastMap
|
||||
|
||||
/** The transitions configuration of a [SceneTransitionLayout]. */
|
||||
class SceneTransitions(
|
||||
val transitionSpecs: List<TransitionSpec>,
|
||||
private val transitionSpecs: List<TransitionSpec>,
|
||||
) {
|
||||
private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
private class TransitionBuilderImpl : TransitionBuilder {
|
||||
internal class TransitionBuilderImpl : TransitionBuilder {
|
||||
val transformations = mutableListOf<Transformation>()
|
||||
override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import android.content.DialogInterface
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.snap
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -46,6 +46,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.compose.animation.scene.ElementKey
|
||||
import com.android.compose.animation.scene.SceneScope
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
|
||||
@@ -63,6 +64,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
object Bouncer {
|
||||
object Elements {
|
||||
val Background = ElementKey("BouncerBackground")
|
||||
val Content = ElementKey("BouncerContent")
|
||||
}
|
||||
}
|
||||
|
||||
/** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
|
||||
@SysUISingleton
|
||||
class BouncerScene
|
||||
@@ -88,7 +96,7 @@ constructor(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BouncerScene(
|
||||
private fun SceneScope.BouncerScene(
|
||||
viewModel: BouncerViewModel,
|
||||
dialogFactory: BouncerSceneDialogFactory,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -97,84 +105,90 @@ private fun BouncerScene(
|
||||
val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState()
|
||||
val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
|
||||
var dialog: Dialog? by remember { mutableStateOf(null) }
|
||||
val backgroundColor = MaterialTheme.colorScheme.surface
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(60.dp),
|
||||
modifier =
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
|
||||
) {
|
||||
Crossfade(
|
||||
targetState = message,
|
||||
label = "Bouncer message",
|
||||
animationSpec = if (message.isUpdateAnimated) tween() else snap(),
|
||||
) { message ->
|
||||
Text(
|
||||
text = message.text,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
Box(modifier) {
|
||||
Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
|
||||
drawRect(color = backgroundColor)
|
||||
}
|
||||
|
||||
Box(Modifier.weight(1f)) {
|
||||
when (val nonNullViewModel = authMethodViewModel) {
|
||||
is PinBouncerViewModel ->
|
||||
PinBouncer(
|
||||
viewModel = nonNullViewModel,
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
)
|
||||
is PasswordBouncerViewModel ->
|
||||
PasswordBouncer(
|
||||
viewModel = nonNullViewModel,
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
)
|
||||
is PatternBouncerViewModel ->
|
||||
PatternBouncer(
|
||||
viewModel = nonNullViewModel,
|
||||
modifier =
|
||||
Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
|
||||
.align(Alignment.BottomCenter),
|
||||
)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = viewModel::onEmergencyServicesButtonClicked,
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
),
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(60.dp),
|
||||
modifier =
|
||||
Modifier.element(Bouncer.Elements.Content)
|
||||
.fillMaxSize()
|
||||
.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
|
||||
if (dialogMessage != null) {
|
||||
if (dialog == null) {
|
||||
dialog =
|
||||
dialogFactory().apply {
|
||||
setMessage(dialogMessage)
|
||||
setButton(
|
||||
DialogInterface.BUTTON_NEUTRAL,
|
||||
context.getString(R.string.ok),
|
||||
) { _, _ ->
|
||||
viewModel.onThrottlingDialogDismissed()
|
||||
}
|
||||
setCancelable(false)
|
||||
setCanceledOnTouchOutside(false)
|
||||
show()
|
||||
}
|
||||
Crossfade(
|
||||
targetState = message,
|
||||
label = "Bouncer message",
|
||||
animationSpec = if (message.isUpdateAnimated) tween() else snap(),
|
||||
) { message ->
|
||||
Text(
|
||||
text = message.text,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
|
||||
Box(Modifier.weight(1f)) {
|
||||
when (val nonNullViewModel = authMethodViewModel) {
|
||||
is PinBouncerViewModel ->
|
||||
PinBouncer(
|
||||
viewModel = nonNullViewModel,
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
)
|
||||
is PasswordBouncerViewModel ->
|
||||
PasswordBouncer(
|
||||
viewModel = nonNullViewModel,
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
)
|
||||
is PatternBouncerViewModel ->
|
||||
PatternBouncer(
|
||||
viewModel = nonNullViewModel,
|
||||
modifier =
|
||||
Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
|
||||
.align(Alignment.BottomCenter),
|
||||
)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = viewModel::onEmergencyServicesButtonClicked,
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
|
||||
if (dialogMessage != null) {
|
||||
if (dialog == null) {
|
||||
dialog =
|
||||
dialogFactory().apply {
|
||||
setMessage(dialogMessage)
|
||||
setButton(
|
||||
DialogInterface.BUTTON_NEUTRAL,
|
||||
context.getString(R.string.ok),
|
||||
) { _, _ ->
|
||||
viewModel.onThrottlingDialogDismissed()
|
||||
}
|
||||
setCancelable(false)
|
||||
setCanceledOnTouchOutside(false)
|
||||
show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dialog?.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
} else {
|
||||
dialog?.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,17 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.compose.animation.scene.ElementKey
|
||||
import com.android.compose.animation.scene.SceneScope
|
||||
|
||||
object Notifications {
|
||||
object Elements {
|
||||
val Notifications = ElementKey("Notifications")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Notifications(
|
||||
fun SceneScope.Notifications(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// TODO(b/272779828): implement.
|
||||
|
||||
@@ -31,15 +31,27 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.compose.animation.scene.ElementKey
|
||||
import com.android.compose.animation.scene.SceneScope
|
||||
|
||||
object QuickSettings {
|
||||
object Elements {
|
||||
// TODO RENAME
|
||||
val Content = ElementKey("QuickSettingsContent")
|
||||
val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
|
||||
val FooterActions = ElementKey("QuickSettingsFooterActions")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun QuickSettings(
|
||||
fun SceneScope.QuickSettings(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// TODO(b/272780058): implement.
|
||||
Column(
|
||||
modifier =
|
||||
modifier
|
||||
.element(QuickSettings.Elements.Content)
|
||||
.fillMaxWidth()
|
||||
.defaultMinSize(minHeight = 300.dp)
|
||||
.clip(RoundedCornerShape(32.dp))
|
||||
@@ -47,15 +59,19 @@ fun QuickSettings(
|
||||
.padding(16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "Quick settings",
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
text = "Quick settings grid",
|
||||
modifier =
|
||||
Modifier.element(QuickSettings.Elements.CollapsedGrid)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Text(
|
||||
text = "QS footer actions",
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
modifier =
|
||||
Modifier.element(QuickSettings.Elements.FooterActions)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
)
|
||||
|
||||
@@ -16,19 +16,17 @@
|
||||
|
||||
package com.android.systemui.qs.ui.composable
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.compose.animation.scene.SceneScope
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.qs.footer.ui.compose.QuickSettings
|
||||
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
|
||||
import com.android.systemui.scene.shared.model.Direction
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
@@ -69,23 +67,18 @@ constructor(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun QuickSettingsScene(
|
||||
private fun SceneScope.QuickSettingsScene(
|
||||
viewModel: QuickSettingsSceneViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// TODO(b/280887232): implement the real UI.
|
||||
|
||||
Box(modifier = modifier) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Text("Quick settings", style = MaterialTheme.typography.headlineMedium)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") }
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.clickable(onClick = { viewModel.onContentClicked() })
|
||||
.padding(horizontal = 16.dp, vertical = 48.dp)
|
||||
) {
|
||||
QuickSettings(modifier = Modifier.fillMaxHeight())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import com.android.compose.animation.scene.SceneTransitionLayoutState
|
||||
import com.android.compose.animation.scene.Swipe
|
||||
import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
|
||||
import com.android.compose.animation.scene.observableTransitionState
|
||||
import com.android.compose.animation.scene.transitions
|
||||
import com.android.systemui.scene.shared.model.Direction
|
||||
import com.android.systemui.scene.shared.model.ObservableTransitionState
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
@@ -78,7 +77,7 @@ fun SceneContainer(
|
||||
SceneTransitionLayout(
|
||||
currentScene = currentSceneKey.toTransitionSceneKey(),
|
||||
onChangeScene = viewModel::onSceneChanged,
|
||||
transitions = transitions {},
|
||||
transitions = SceneContainerTransitions,
|
||||
state = state,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
@@ -98,7 +97,9 @@ fun SceneContainer(
|
||||
) {
|
||||
with(composableScene) {
|
||||
this@scene.Content(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier =
|
||||
Modifier.element(sceneKey.toTransitionSceneKey().rootElementKey)
|
||||
.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -128,14 +129,6 @@ private fun toTransitionModels(
|
||||
return userAction.toTransitionUserAction() to sceneModel.key.toTransitionSceneKey()
|
||||
}
|
||||
|
||||
// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
|
||||
private fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
|
||||
return SceneTransitionSceneKey(
|
||||
name = toString(),
|
||||
identity = this,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
|
||||
private fun SceneTransitionSceneKey.toModel(): SceneModel {
|
||||
return SceneModel(key = identity as SceneKey)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.android.systemui.scene.ui.composable
|
||||
|
||||
import com.android.compose.animation.scene.transitions
|
||||
import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
|
||||
import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
|
||||
import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
|
||||
import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
|
||||
import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
|
||||
import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
|
||||
import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
|
||||
import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
|
||||
|
||||
/**
|
||||
* Comprehensive definition of all transitions between scenes in [SceneContainer].
|
||||
*
|
||||
* Transitions are automatically reversible, so define only one transition per scene pair. By
|
||||
* convention, use the more common transition direction when defining the pair order, e.g.
|
||||
* Lockscreen to Bouncer rather than Bouncer to Lockscreen.
|
||||
*
|
||||
* The actual transition DSL must be placed in a separate file under the package
|
||||
* [com.android.systemui.scene.ui.composable.transitions].
|
||||
*
|
||||
* Please keep the list sorted alphabetically.
|
||||
*/
|
||||
val SceneContainerTransitions = transitions {
|
||||
from(Bouncer, to = Gone) { bouncerToGoneTransition() }
|
||||
from(Gone, to = Shade) { goneToShadeTransition() }
|
||||
from(Gone, to = QuickSettings) { goneToQuickSettingsTransition() }
|
||||
from(Lockscreen, to = Bouncer) { lockscreenToBouncerTransition() }
|
||||
from(Lockscreen, to = Shade) { lockscreenToShadeTransition() }
|
||||
from(Lockscreen, to = QuickSettings) { lockscreenToQuickSettingsTransition() }
|
||||
from(Lockscreen, to = Gone) { lockscreenToGoneTransition() }
|
||||
from(Shade, to = QuickSettings) { shadeToQuickSettingsTransition() }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.android.systemui.scene.ui.composable
|
||||
|
||||
import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
|
||||
val Lockscreen = SceneKey.Lockscreen.toTransitionSceneKey()
|
||||
val Bouncer = SceneKey.Bouncer.toTransitionSceneKey()
|
||||
val Shade = SceneKey.Shade.toTransitionSceneKey()
|
||||
val QuickSettings = SceneKey.QuickSettings.toTransitionSceneKey()
|
||||
val Gone = SceneKey.Gone.toTransitionSceneKey()
|
||||
|
||||
// TODO(b/293899074): Remove this file once we can use the scene keys from SceneTransitionLayout.
|
||||
fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
|
||||
return SceneTransitionSceneKey(name = toString(), identity = this)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.android.systemui.scene.ui.composable.transitions
|
||||
|
||||
import androidx.compose.animation.core.tween
|
||||
import com.android.compose.animation.scene.TransitionBuilder
|
||||
import com.android.systemui.scene.ui.composable.Bouncer
|
||||
|
||||
fun TransitionBuilder.bouncerToGoneTransition() {
|
||||
spec = tween(durationMillis = 500)
|
||||
|
||||
fade(Bouncer.rootElementKey)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.android.systemui.scene.ui.composable.transitions
|
||||
|
||||
import androidx.compose.animation.core.tween
|
||||
import com.android.compose.animation.scene.TransitionBuilder
|
||||
import com.android.systemui.scene.ui.composable.QuickSettings
|
||||
|
||||
fun TransitionBuilder.goneToQuickSettingsTransition() {
|
||||
spec = tween(durationMillis = 500)
|
||||
|
||||
fade(QuickSettings.rootElementKey)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.android.systemui.scene.ui.composable.transitions
|
||||
|
||||
import androidx.compose.animation.core.tween
|
||||
import com.android.compose.animation.scene.TransitionBuilder
|
||||
import com.android.systemui.scene.ui.composable.Shade
|
||||
|
||||
fun TransitionBuilder.goneToShadeTransition() {
|
||||
spec = tween(durationMillis = 500)
|
||||
|
||||
fade(Shade.rootElementKey)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.android.systemui.scene.ui.composable.transitions
|
||||
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.compose.animation.scene.TransitionBuilder
|
||||
import com.android.systemui.bouncer.ui.composable.Bouncer
|
||||
|
||||
fun TransitionBuilder.lockscreenToBouncerTransition() {
|
||||
spec = tween(durationMillis = 500)
|
||||
|
||||
translate(Bouncer.Elements.Content, y = 300.dp)
|
||||
fractionRange(end = 0.5f) { fade(Bouncer.Elements.Background) }
|
||||
fractionRange(start = 0.5f) { fade(Bouncer.Elements.Content) }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.android.systemui.scene.ui.composable.transitions
|
||||
|
||||
import androidx.compose.animation.core.tween
|
||||
import com.android.compose.animation.scene.TransitionBuilder
|
||||
import com.android.systemui.scene.ui.composable.Lockscreen
|
||||
|
||||
fun TransitionBuilder.lockscreenToGoneTransition() {
|
||||
spec = tween(durationMillis = 500)
|
||||
|
||||
fade(Lockscreen.rootElementKey)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.android.systemui.scene.ui.composable.transitions
|
||||
|
||||
import androidx.compose.animation.core.tween
|
||||
import com.android.compose.animation.scene.TransitionBuilder
|
||||
import com.android.systemui.scene.ui.composable.QuickSettings
|
||||
|
||||
fun TransitionBuilder.lockscreenToQuickSettingsTransition() {
|
||||
spec = tween(durationMillis = 500)
|
||||
|
||||
fade(QuickSettings.rootElementKey)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.android.systemui.scene.ui.composable.transitions
|
||||
|
||||
import androidx.compose.animation.core.tween
|
||||
import com.android.compose.animation.scene.Edge
|
||||
import com.android.compose.animation.scene.TransitionBuilder
|
||||
import com.android.systemui.notifications.ui.composable.Notifications
|
||||
import com.android.systemui.qs.footer.ui.compose.QuickSettings
|
||||
import com.android.systemui.shade.ui.composable.Shade
|
||||
|
||||
fun TransitionBuilder.lockscreenToShadeTransition() {
|
||||
spec = tween(durationMillis = 500)
|
||||
|
||||
punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim)
|
||||
translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
|
||||
fractionRange(end = 0.5f) {
|
||||
fade(Shade.Elements.ScrimBackground)
|
||||
translate(
|
||||
QuickSettings.Elements.CollapsedGrid,
|
||||
Edge.Top,
|
||||
startsOutsideLayoutBounds = false,
|
||||
)
|
||||
}
|
||||
fractionRange(start = 0.5f) { fade(Notifications.Elements.Notifications) }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.android.systemui.scene.ui.composable.transitions
|
||||
|
||||
import androidx.compose.animation.core.tween
|
||||
import com.android.compose.animation.scene.Edge
|
||||
import com.android.compose.animation.scene.TransitionBuilder
|
||||
import com.android.systemui.notifications.ui.composable.Notifications
|
||||
import com.android.systemui.qs.footer.ui.compose.QuickSettings
|
||||
|
||||
fun TransitionBuilder.shadeToQuickSettingsTransition() {
|
||||
spec = tween(durationMillis = 500)
|
||||
|
||||
translate(Notifications.Elements.Notifications, Edge.Bottom)
|
||||
fade(Notifications.Elements.Notifications)
|
||||
timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
|
||||
}
|
||||
@@ -16,16 +16,22 @@
|
||||
|
||||
package com.android.systemui.shade.ui.composable
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.compose.animation.scene.ElementKey
|
||||
import com.android.compose.animation.scene.SceneScope
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
@@ -44,6 +50,26 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
object Shade {
|
||||
object Elements {
|
||||
val QuickSettings = ElementKey("ShadeQuickSettings")
|
||||
val Scrim = ElementKey("ShadeScrim")
|
||||
val ScrimBackground = ElementKey("ShadeScrimBackground")
|
||||
}
|
||||
|
||||
object Dimensions {
|
||||
val ScrimCornerSize = 32.dp
|
||||
}
|
||||
|
||||
object Shapes {
|
||||
val Scrim =
|
||||
RoundedCornerShape(
|
||||
topStart = Dimensions.ScrimCornerSize,
|
||||
topEnd = Dimensions.ScrimCornerSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
|
||||
@SysUISingleton
|
||||
class ShadeScene
|
||||
@@ -79,20 +105,28 @@ constructor(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShadeScene(
|
||||
private fun SceneScope.ShadeScene(
|
||||
viewModel: ShadeSceneViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier =
|
||||
modifier
|
||||
.fillMaxSize()
|
||||
.clickable(onClick = { viewModel.onContentClicked() })
|
||||
.padding(horizontal = 16.dp, vertical = 48.dp)
|
||||
) {
|
||||
QuickSettings(modifier = Modifier.height(160.dp))
|
||||
Notifications(modifier = Modifier.weight(1f))
|
||||
Box(modifier.element(Shade.Elements.Scrim)) {
|
||||
Spacer(
|
||||
modifier =
|
||||
Modifier.element(Shade.Elements.ScrimBackground)
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
|
||||
)
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier =
|
||||
Modifier.fillMaxSize()
|
||||
.clickable(onClick = { viewModel.onContentClicked() })
|
||||
.padding(horizontal = 16.dp, vertical = 48.dp)
|
||||
) {
|
||||
QuickSettings(modifier = Modifier.height(160.dp))
|
||||
Notifications(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user