Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9cbc2654 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

[Flexiglas] Split shade - UI layer

UI layer changes to support the split shade.

Split shade is the single panel shade that's shown for large screens and
unfolded devices where quick settings is one the left and notifications
is on the right.

Includes changes to the view-models for the various
scenes. Because split shade is a single panel, we only use the Shade
scene and do not allow navigation to the QS scene.

Adds a side-by-side layout to ShadeScene and displays a rather broken QS
on one side and a rather broken notifications stack on the other.
Followup CLs will fix both and add much-needed transition animations.

What's broken and missing:
- Notifications aren't showing
- The second page of QS tiles peeks into the first
- Scrolling inside the QS editing mode doesn't work
- QS tiles seems unresonsive and don't update themselves properly
- Didn't test media
- No transition animation work yet
- When leaving the scene while in QS editing mode and returning to the
  scene, we're incorrectly still in editing mode
- Other things

Bug: 328473018
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Test: unit and integration test for view-models
Test: manually tested on a foldable to see folded and unfolded more
quickly. The current state is https://screenshot.googleplex.com/BzzBhdwwWX5CdAt
Change-Id: I4dbace8238043677e9e2ff9b47a1f9f87f5e3ddb

Change-Id: I56575230d36c92105ef6ee4c506c5ad126711ef8
parent c4a4dd79
Loading
Loading
Loading
Loading
+17 −5
Original line number Diff line number Diff line
@@ -39,7 +39,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

/** The lock screen scene shows when the device is locked. */
@@ -54,8 +53,17 @@ constructor(
    override val key = Scenes.Lockscreen

    override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
        combine(viewModel.upDestinationSceneKey, viewModel.leftDestinationSceneKey, ::Pair)
            .map { (upKey, leftKey) -> destinationScenes(up = upKey, left = leftKey) }
        combine(
                viewModel.upDestinationSceneKey,
                viewModel.leftDestinationSceneKey,
                viewModel.downFromTopEdgeDestinationSceneKey,
            ) { upKey, leftKey, downFromTopEdgeKey ->
                destinationScenes(
                    up = upKey,
                    left = leftKey,
                    downFromTopEdge = downFromTopEdgeKey,
                )
            }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.Eagerly,
@@ -63,6 +71,7 @@ constructor(
                    destinationScenes(
                        up = viewModel.upDestinationSceneKey.value,
                        left = viewModel.leftDestinationSceneKey.value,
                        downFromTopEdge = viewModel.downFromTopEdgeDestinationSceneKey.value,
                    )
            )

@@ -79,12 +88,15 @@ constructor(
    private fun destinationScenes(
        up: SceneKey?,
        left: SceneKey?,
        downFromTopEdge: SceneKey?,
    ): Map<UserAction, UserActionResult> {
        return buildMap {
            up?.let { this[Swipe(SwipeDirection.Up)] = UserActionResult(up) }
            left?.let { this[Swipe(SwipeDirection.Left)] = UserActionResult(left) }
            downFromTopEdge?.let {
                this[Swipe(fromSource = Edge.Top, direction = SwipeDirection.Down)] =
                UserActionResult(Scenes.QuickSettings)
                    UserActionResult(downFromTopEdge)
            }
            this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade)
        }
    }
+23 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.qs.footer.ui.compose

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.LocalIndication
@@ -76,9 +77,31 @@ import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
import com.android.systemui.res.R
import kotlinx.coroutines.launch

@Composable
fun FooterActionsWithAnimatedVisibility(
    viewModel: FooterActionsViewModel,
    isCustomizing: Boolean,
    lifecycleOwner: LifecycleOwner,
    footerActionsModifier: (Modifier) -> Modifier,
    modifier: Modifier = Modifier,
) {
    AnimatedVisibility(visible = !isCustomizing, modifier = modifier.fillMaxWidth()) {
        QuickSettingsTheme {
            // This view has its own horizontal padding
            // TODO(b/321716470) This should use a lifecycle tied to the scene.
            FooterActions(
                viewModel = viewModel,
                qsVisibilityLifecycleOwner = lifecycleOwner,
                modifier = footerActionsModifier(Modifier),
            )
        }
    }
}

/** The Quick Settings footer actions row. */
@Composable
fun FooterActions(
+6 −2
Original line number Diff line number Diff line
@@ -63,12 +63,14 @@ object QuickSettings {
}

private fun SceneScope.stateForQuickSettingsContent(
    isSplitShade: Boolean,
    squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default
): QSSceneAdapter.State {
    return when (val transitionState = layoutState.transitionState) {
        is TransitionState.Idle -> {
            when (transitionState.currentScene) {
                Scenes.Shade -> QSSceneAdapter.State.QQS
                Scenes.Shade -> QSSceneAdapter.State.QQS.takeUnless { isSplitShade }
                        ?: QSSceneAdapter.State.QS
                Scenes.QuickSettings -> QSSceneAdapter.State.QS
                else -> QSSceneAdapter.State.CLOSED
            }
@@ -76,6 +78,7 @@ private fun SceneScope.stateForQuickSettingsContent(
        is TransitionState.Transition ->
            with(transitionState) {
                when {
                    isSplitShade -> QSSceneAdapter.State.QS
                    fromScene == Scenes.Shade && toScene == Scenes.QuickSettings ->
                        Expanding(progress)
                    fromScene == Scenes.QuickSettings && toScene == Scenes.Shade ->
@@ -111,10 +114,11 @@ private fun SceneScope.stateForQuickSettingsContent(
fun SceneScope.QuickSettings(
    qsSceneAdapter: QSSceneAdapter,
    heightProvider: () -> Int,
    isSplitShade: Boolean,
    modifier: Modifier = Modifier,
    squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default,
) {
    val contentState = stateForQuickSettingsContent(squishiness)
    val contentState = stateForQuickSettingsContent(isSplitShade, squishiness)

    MovableElement(
        key = QuickSettings.Elements.Content,
+12 −14
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
@@ -238,24 +239,21 @@ private fun SceneScope.QuickSettingsScene(
                    QuickSettings(
                        viewModel.qsSceneAdapter,
                        { viewModel.qsSceneAdapter.qsHeight },
                        isSplitShade = false,
                        modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"),
                    )
                }
            }
            AnimatedVisibility(
                visible = !isCustomizing,
                modifier = Modifier.align(Alignment.CenterHorizontally).fillMaxWidth()
            ) {
                QuickSettingsTheme {
                    // This view has its own horizontal padding
                    // TODO(b/321716470) This should use a lifecycle tied to the scene.
                    FooterActions(

            FooterActionsWithAnimatedVisibility(
                viewModel = footerActionsViewModel,
                        qsVisibilityLifecycleOwner = lifecycleOwner,
                        modifier = Modifier.element(QuickSettings.Elements.FooterActions)
                isCustomizing = isCustomizing,
                lifecycleOwner = lifecycleOwner,
                footerActionsModifier = { modifier ->
                    modifier.element(QuickSettings.Elements.FooterActions)
                },
                modifier = Modifier.align(Alignment.CenterHorizontally),
            )
        }
    }
}
    }
}
+3 −18
Original line number Diff line number Diff line
@@ -20,21 +20,16 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.scene.ui.viewmodel.GoneSceneViewModel
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
@@ -44,22 +39,12 @@ import kotlinx.coroutines.flow.asStateFlow
class GoneScene
@Inject
constructor(
    private val notificationsViewModel: NotificationsPlaceholderViewModel,
    private val viewModel: GoneSceneViewModel,
) : ComposableScene {
    override val key = Scenes.Gone

    override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
        MutableStateFlow<Map<UserAction, UserActionResult>>(
                mapOf(
                    Swipe(
                        pointerCount = 2,
                        fromSource = Edge.Top,
                        direction = SwipeDirection.Down,
                    ) to UserActionResult(Scenes.QuickSettings),
                    Swipe(direction = SwipeDirection.Down) to UserActionResult(Scenes.Shade),
                )
            )
            .asStateFlow()
        viewModel.destinationScenes

    @Composable
    override fun SceneScope.Content(
Loading