[flexiglass] Composables for scenes and scene container.
Implements the composable functions for scene placeholders and the scene container, which itself, is a placeholder. Instead of handling true swipes and real UI, the placeholders have buttons for triggering a swipe event and no transition animations between scenes. This entire system is meant purely for testing the scene switching logic in previous CLs in the Compose Gallery playground app. Bug: 279501596 Test: Manually verified all scene transitions using the testbed app. Change-Id: I4b81988b2ea6ef5388714674718e2f742dd1b3a4
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.scene.shared.page
|
||||
|
||||
import com.android.systemui.scene.shared.model.Scene
|
||||
import dagger.Module
|
||||
import dagger.multibindings.Multibinds
|
||||
|
||||
@Module
|
||||
interface SceneModule {
|
||||
@Multibinds fun scenes(): Set<Scene>
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.scene.ui.composable
|
||||
|
||||
import com.android.systemui.bouncer.ui.composable.BouncerScene
|
||||
import com.android.systemui.keyguard.ui.composable.LockScreenScene
|
||||
import com.android.systemui.qs.ui.composable.QuickSettingsScene
|
||||
import com.android.systemui.scene.shared.model.Scene
|
||||
import com.android.systemui.shade.ui.composable.ShadeScene
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
||||
@Module
|
||||
object SceneModule {
|
||||
@Provides
|
||||
fun scenes(
|
||||
bouncer: BouncerScene,
|
||||
gone: GoneScene,
|
||||
lockScreen: LockScreenScene,
|
||||
qs: QuickSettingsScene,
|
||||
shade: ShadeScene,
|
||||
): Set<Scene> {
|
||||
return setOf(
|
||||
bouncer,
|
||||
gone,
|
||||
lockScreen,
|
||||
qs,
|
||||
shade,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.bouncer.ui.composable
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.android.systemui.scene.shared.model.UserAction
|
||||
import com.android.systemui.scene.ui.composable.ComposableScene
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
|
||||
@SysUISingleton
|
||||
class BouncerScene
|
||||
@Inject
|
||||
constructor(
|
||||
private val viewModel: BouncerViewModel,
|
||||
) : ComposableScene {
|
||||
override val key = SceneKey.Bouncer
|
||||
|
||||
override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
|
||||
MutableStateFlow<Map<UserAction, SceneModel>>(
|
||||
mapOf(
|
||||
UserAction.Back to SceneModel(SceneKey.LockScreen),
|
||||
)
|
||||
)
|
||||
.asStateFlow()
|
||||
|
||||
@Composable
|
||||
override fun Content(
|
||||
modifier: Modifier,
|
||||
) {
|
||||
// TODO(b/280877228): implement the real UI.
|
||||
|
||||
Box(modifier = modifier) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Text("Bouncer", style = MaterialTheme.typography.headlineLarge)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Button(onClick = { viewModel.onAuthenticateButtonClicked() }) {
|
||||
Text("Authenticate")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.keyguard.ui.composable
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.common.ui.compose.Icon
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.keyguard.ui.viewmodel.LockScreenSceneViewModel
|
||||
import com.android.systemui.scene.shared.model.Direction
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.android.systemui.scene.shared.model.UserAction
|
||||
import com.android.systemui.scene.ui.composable.ComposableScene
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
/** The lock screen scene shows when the device is locked. */
|
||||
@SysUISingleton
|
||||
class LockScreenScene
|
||||
@Inject
|
||||
constructor(
|
||||
@Application private val applicationScope: CoroutineScope,
|
||||
private val viewModel: LockScreenSceneViewModel,
|
||||
) : ComposableScene {
|
||||
override val key = SceneKey.LockScreen
|
||||
|
||||
override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
|
||||
viewModel.upDestinationSceneKey
|
||||
.map { pageKey -> destinationScenes(up = pageKey) }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value)
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(
|
||||
modifier: Modifier,
|
||||
) {
|
||||
LockScreenScene(
|
||||
viewModel = viewModel,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
private fun destinationScenes(
|
||||
up: SceneKey,
|
||||
): Map<UserAction, SceneModel> {
|
||||
return mapOf(
|
||||
UserAction.Swipe(Direction.UP) to SceneModel(up),
|
||||
UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LockScreenScene(
|
||||
viewModel: LockScreenSceneViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// TODO(b/280879610): implement the real UI.
|
||||
|
||||
val lockButtonIcon: Icon by viewModel.lockButtonIcon.collectAsState()
|
||||
|
||||
Box(modifier = modifier) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Text("Lock screen", style = MaterialTheme.typography.headlineMedium)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Button(onClick = { viewModel.onLockButtonClicked() }) { Icon(lockButtonIcon) }
|
||||
|
||||
Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.qs.ui.composable
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
|
||||
import com.android.systemui.scene.shared.model.Direction
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.android.systemui.scene.shared.model.UserAction
|
||||
import com.android.systemui.scene.ui.composable.ComposableScene
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
|
||||
@SysUISingleton
|
||||
class QuickSettingsScene
|
||||
@Inject
|
||||
constructor(
|
||||
private val viewModel: QuickSettingsSceneViewModel,
|
||||
) : ComposableScene {
|
||||
override val key = SceneKey.QuickSettings
|
||||
|
||||
override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
|
||||
MutableStateFlow<Map<UserAction, SceneModel>>(
|
||||
mapOf(
|
||||
UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
|
||||
)
|
||||
)
|
||||
.asStateFlow()
|
||||
|
||||
@Composable
|
||||
override fun Content(
|
||||
modifier: Modifier,
|
||||
) {
|
||||
QuickSettingsScene(
|
||||
viewModel = viewModel,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun 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") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.scene.ui.composable
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.android.systemui.scene.shared.model.Scene
|
||||
|
||||
/** Compose-capable extension of [Scene]. */
|
||||
interface ComposableScene : Scene {
|
||||
@Composable fun Content(modifier: Modifier)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.scene.ui.composable
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.scene.shared.model.Direction
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.android.systemui.scene.shared.model.UserAction
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/**
|
||||
* "Gone" is not a real scene but rather the absence of scenes when we want to skip showing any
|
||||
* content from the scene framework.
|
||||
*/
|
||||
@SysUISingleton
|
||||
class GoneScene @Inject constructor() : ComposableScene {
|
||||
override val key = SceneKey.Gone
|
||||
|
||||
override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
|
||||
MutableStateFlow<Map<UserAction, SceneModel>>(
|
||||
mapOf(
|
||||
UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade),
|
||||
)
|
||||
)
|
||||
.asStateFlow()
|
||||
|
||||
@Composable
|
||||
override fun Content(
|
||||
modifier: Modifier,
|
||||
) {
|
||||
/*
|
||||
* TODO(b/279501596): once we start testing with the real Content Dynamics Framework,
|
||||
* replace this with an error to make sure it doesn't get rendered.
|
||||
*/
|
||||
Box(modifier = modifier) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Text("Gone", style = MaterialTheme.typography.headlineMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalAnimationApi::class)
|
||||
|
||||
package com.android.systemui.scene.ui.composable
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.systemui.scene.shared.model.Direction
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.android.systemui.scene.shared.model.UserAction
|
||||
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Renders a container of a collection of "scenes" that the user can switch between using certain
|
||||
* user actions (for instance, swiping up and down) or that can be switched automatically based on
|
||||
* application business logic in response to certain events (for example, the device unlocking).
|
||||
*
|
||||
* It's possible for the application to host several such scene containers, the configuration system
|
||||
* allows configuring each container with its own set of scenes. Scenes can be present in multiple
|
||||
* containers.
|
||||
*
|
||||
* @param viewModel The UI state holder for this container.
|
||||
* @param sceneByKey Mapping of [ComposableScene] by [SceneKey], ordered by z-order such that the
|
||||
* last scene is rendered on top of all other scenes. It's critical that this map contains exactly
|
||||
* and only the scenes on this container. In other words: (a) there should be no scene in this map
|
||||
* that is not in the configuration for this container and (b) all scenes in the configuration
|
||||
* must have entries in this map.
|
||||
* @param modifier A modifier.
|
||||
*/
|
||||
@Composable
|
||||
fun SceneContainer(
|
||||
viewModel: SceneContainerViewModel,
|
||||
sceneByKey: Map<SceneKey, ComposableScene>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val currentScene: SceneModel by viewModel.currentScene.collectAsState()
|
||||
|
||||
AnimatedContent(
|
||||
targetState = currentScene.key,
|
||||
label = "scene container",
|
||||
modifier = modifier,
|
||||
) { currentSceneKey ->
|
||||
sceneByKey.forEach { (key, composableScene) ->
|
||||
if (key == currentSceneKey) {
|
||||
Scene(
|
||||
scene = composableScene,
|
||||
onSceneChanged = viewModel::setCurrentScene,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Renders the given [ComposableScene]. */
|
||||
@Composable
|
||||
private fun Scene(
|
||||
scene: ComposableScene,
|
||||
onSceneChanged: (SceneModel) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// TODO(b/280880714): replace with the real UI and make sure to call onTransitionProgress.
|
||||
Box(modifier) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
) {
|
||||
scene.Content(
|
||||
modifier = Modifier,
|
||||
)
|
||||
|
||||
val destinationScenes: Map<UserAction, SceneModel> by
|
||||
scene.destinationScenes().collectAsState()
|
||||
val swipeLeftDestinationScene = destinationScenes[UserAction.Swipe(Direction.LEFT)]
|
||||
val swipeUpDestinationScene = destinationScenes[UserAction.Swipe(Direction.UP)]
|
||||
val swipeRightDestinationScene = destinationScenes[UserAction.Swipe(Direction.RIGHT)]
|
||||
val swipeDownDestinationScene = destinationScenes[UserAction.Swipe(Direction.DOWN)]
|
||||
val backDestinationScene = destinationScenes[UserAction.Back]
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
DirectionalButton(Direction.LEFT, swipeLeftDestinationScene, onSceneChanged)
|
||||
DirectionalButton(Direction.UP, swipeUpDestinationScene, onSceneChanged)
|
||||
DirectionalButton(Direction.RIGHT, swipeRightDestinationScene, onSceneChanged)
|
||||
DirectionalButton(Direction.DOWN, swipeDownDestinationScene, onSceneChanged)
|
||||
}
|
||||
|
||||
if (backDestinationScene != null) {
|
||||
BackHandler { onSceneChanged.invoke(backDestinationScene) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DirectionalButton(
|
||||
direction: Direction,
|
||||
destinationScene: SceneModel?,
|
||||
onSceneChanged: (SceneModel) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Button(
|
||||
onClick = { destinationScene?.let { onSceneChanged.invoke(it) } },
|
||||
enabled = destinationScene != null,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(direction.name.lowercase(Locale.getDefault()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.shade.ui.composable
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.scene.shared.model.Direction
|
||||
import com.android.systemui.scene.shared.model.SceneKey
|
||||
import com.android.systemui.scene.shared.model.SceneModel
|
||||
import com.android.systemui.scene.shared.model.UserAction
|
||||
import com.android.systemui.scene.ui.composable.ComposableScene
|
||||
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
/** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
|
||||
@SysUISingleton
|
||||
class ShadeScene
|
||||
@Inject
|
||||
constructor(
|
||||
@Application private val applicationScope: CoroutineScope,
|
||||
private val viewModel: ShadeSceneViewModel,
|
||||
) : ComposableScene {
|
||||
override val key = SceneKey.Shade
|
||||
|
||||
override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
|
||||
viewModel.upDestinationSceneKey
|
||||
.map { sceneKey -> destinationScenes(up = sceneKey) }
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value),
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(
|
||||
modifier: Modifier,
|
||||
) {
|
||||
ShadeScene(
|
||||
viewModel = viewModel,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
private fun destinationScenes(
|
||||
up: SceneKey,
|
||||
): Map<UserAction, SceneModel> {
|
||||
return mapOf(
|
||||
UserAction.Swipe(Direction.UP) to SceneModel(up),
|
||||
UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.QuickSettings),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ShadeScene(
|
||||
viewModel: ShadeSceneViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// TODO(b/280887022): implement the real UI.
|
||||
|
||||
Box(modifier = modifier) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Text("Shade", style = MaterialTheme.typography.headlineMedium)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Button(
|
||||
onClick = { viewModel.onContentClicked() },
|
||||
) {
|
||||
Text("Open some content")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ import com.android.systemui.qs.tileimpl.QSFactoryImpl;
|
||||
import com.android.systemui.recents.Recents;
|
||||
import com.android.systemui.recents.RecentsImplementation;
|
||||
import com.android.systemui.rotationlock.RotationLockModule;
|
||||
import com.android.systemui.scene.SceneContainerFrameworkModule;
|
||||
import com.android.systemui.screenshot.ReferenceScreenshotModule;
|
||||
import com.android.systemui.settings.dagger.MultiUserUtilsModule;
|
||||
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
|
||||
@@ -74,12 +75,12 @@ import com.android.systemui.statusbar.policy.SensorPrivacyController;
|
||||
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
|
||||
import com.android.systemui.volume.dagger.VolumeModule;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
/**
|
||||
* A dagger module for injecting default implementations of components of System UI.
|
||||
*
|
||||
@@ -105,6 +106,7 @@ import dagger.Provides;
|
||||
QSModule.class,
|
||||
ReferenceScreenshotModule.class,
|
||||
RotationLockModule.class,
|
||||
SceneContainerFrameworkModule.class,
|
||||
StatusBarEventsModule.class,
|
||||
StartCentralSurfacesModule.class,
|
||||
VolumeModule.class,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.scene
|
||||
|
||||
import com.android.systemui.scene.shared.page.SceneModule
|
||||
import dagger.Module
|
||||
|
||||
@Module(
|
||||
includes =
|
||||
[
|
||||
SceneModule::class,
|
||||
],
|
||||
)
|
||||
object SceneContainerFrameworkModule
|
||||
Reference in New Issue
Block a user