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

Commit cbf32139 authored by Fabián Kozynski's avatar Fabián Kozynski
Browse files

Create a QSSceneInteractor

This interactor bridges between the Scene system and QSImpl.

In this CL, the following is working (behind the corresponding flags):
* Expanding the shade allows access to fully functioning QQS, QS and
  edit mode.
* Interacting with QQS, QS and edit mode works as expected.

What is not working yet:
* Transition animation in expansions
* Visual glitches when expanding for the first time.
* QS in lockscreen
* Re-inflation when theme changes.

Test: atest SystemUITests
Test: manual
Bug: 280887232
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT

Change-Id: I096db66b3b5a8d56d78ec50448e7faa612f9ce8c
parent 8621fe01
Loading
Loading
Loading
Loading
+130 −0
Original line number Diff line number Diff line
@@ -12,27 +12,39 @@
 * 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.footer.ui.compose
package com.android.systemui.qs.ui.composable

import android.view.ContextThemeWrapper
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.compose.theme.colorAttr
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.res.R

object QuickSettings {
    object Elements {
@@ -43,9 +55,19 @@ object QuickSettings {
    }
}

@Composable
private fun QuickSettingsTheme(content: @Composable () -> Unit) {
    val context = LocalContext.current
    val themedContext =
        remember(context) { ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) }
    CompositionLocalProvider(LocalContext provides themedContext) { content() }
}

@Composable
fun SceneScope.QuickSettings(
    modifier: Modifier = Modifier,
    qsSceneAdapter: QSSceneAdapter,
    state: QSSceneAdapter.State
) {
    // TODO(b/272780058): implement.
    Column(
@@ -56,24 +78,53 @@ fun SceneScope.QuickSettings(
                .defaultMinSize(minHeight = 300.dp)
                .clip(RoundedCornerShape(32.dp))
                .background(MaterialTheme.colorScheme.primary)
                .padding(16.dp),
                .padding(1.dp),
    ) {
        Text(
            text = "Quick settings grid",
            modifier =
                Modifier.element(QuickSettings.Elements.CollapsedGrid)
                    .align(Alignment.CenterHorizontally),
            style = MaterialTheme.typography.titleLarge,
            color = MaterialTheme.colorScheme.onPrimary,
        QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, state)
    }
}

@Composable
private fun QuickSettingsContent(
    qsSceneAdapter: QSSceneAdapter,
    state: QSSceneAdapter.State,
    modifier: Modifier = Modifier,
) {
    val qsView by qsSceneAdapter.qsView.collectAsState(null)
    QuickSettingsTheme {
        val context = LocalContext.current

        val frame by remember(context) { mutableStateOf(FrameLayout(context)) }

        LaunchedEffect(key1 = context) {
            if (qsView == null) {
                qsSceneAdapter.inflate(context, frame)
            }
        }
        qsView?.let {
            it.attachToParent(frame)
            AndroidView(
                modifier = modifier.fillMaxSize().background(colorAttr(R.attr.underSurface)),
                factory = { _ ->
                    qsSceneAdapter.setState(state)
                    frame
                },
                onRelease = { frame.removeAllViews() },
                update = { qsSceneAdapter.setState(state) }
            )
        Spacer(modifier = Modifier.weight(1f))
        Text(
            text = "QS footer actions",
            modifier =
                Modifier.element(QuickSettings.Elements.FooterActions)
                    .align(Alignment.CenterHorizontally),
            style = MaterialTheme.typography.titleSmall,
            color = MaterialTheme.colorScheme.onPrimary,
        }
    }
}

private fun View.attachToParent(parent: ViewGroup) {
    if (this.parent != null && this.parent != parent) {
        (this.parent as ViewGroup).removeView(this)
    }
    if (this.parent != parent) {
        parent.addView(
            this,
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT,
        )
    }
}
+52 −22
Original line number Diff line number Diff line
@@ -17,43 +17,53 @@
package com.android.systemui.qs.ui.composable

import android.view.ViewGroup
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
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.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.footer.ui.compose.QuickSettings
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
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 com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager
import com.android.systemui.statusbar.phone.StatusBarLocation
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn

/** The Quick Settings (AKA "QS") scene shows the quick setting tiles. */
@SysUISingleton
class QuickSettingsScene
@Inject
constructor(
    @Application private val applicationScope: CoroutineScope,
    private val viewModel: QuickSettingsSceneViewModel,
    private val tintedIconManagerFactory: TintedIconManager.Factory,
    private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
@@ -61,14 +71,12 @@ constructor(
) : ComposableScene {
    override val key = SceneKey.QuickSettings

    private val _destinationScenes =
        MutableStateFlow<Map<UserAction, SceneModel>>(
                mapOf(
                    UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
    override val destinationScenes =
        viewModel.destinationScenes.stateIn(
            scope = applicationScope,
            started = SharingStarted.WhileSubscribed(),
            initialValue = emptyMap(),
        )
            )
            .asStateFlow()
    override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> = _destinationScenes

    @Composable
    override fun SceneScope.Content(
@@ -93,6 +101,9 @@ private fun SceneScope.QuickSettingsScene(
    modifier: Modifier = Modifier,
) {
    // TODO(b/280887232): implement the real UI.
    val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState()
    val collapsedHeaderHeight =
        with(LocalDensity.current) { ShadeHeader.Dimensions.CollapsedHeight.roundToPx() }
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier =
@@ -103,12 +114,27 @@ private fun SceneScope.QuickSettingsScene(
    ) {
        when (LocalWindowSizeClass.current.widthSizeClass) {
            WindowWidthSizeClass.Compact ->
                AnimatedVisibility(
                    visible = !isCustomizing,
                    enter =
                        expandVertically(
                            animationSpec = tween(1000),
                            initialHeight = { collapsedHeaderHeight },
                        ) + fadeIn(tween(1000)),
                    exit =
                        shrinkVertically(
                            animationSpec = tween(1000),
                            targetHeight = { collapsedHeaderHeight },
                            shrinkTowards = Alignment.Top,
                        ) + fadeOut(tween(1000)),
                ) {
                    ExpandedShadeHeader(
                        viewModel = viewModel.shadeHeaderViewModel,
                        createTintedIconManager = createTintedIconManager,
                        createBatteryMeterViewController = createBatteryMeterViewController,
                        statusBarIconController = statusBarIconController,
                    )
                }
            else ->
                CollapsedShadeHeader(
                    viewModel = viewModel.shadeHeaderViewModel,
@@ -118,6 +144,10 @@ private fun SceneScope.QuickSettingsScene(
                )
        }
        Spacer(modifier = Modifier.height(16.dp))
        QuickSettings()
        QuickSettings(
            modifier = Modifier.fillMaxHeight(),
            viewModel.qsSceneAdapter,
            QSSceneAdapter.State.QS
        )
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ 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.qs.ui.composable.QuickSettings
import com.android.systemui.shade.ui.composable.Shade

fun TransitionBuilder.lockscreenToShadeTransition() {
+1 −1
Original line number Diff line number Diff line
@@ -4,7 +4,7 @@ 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.qs.ui.composable.QuickSettings
import com.android.systemui.shade.ui.composable.ShadeHeader

fun TransitionBuilder.shadeToQuickSettingsTransition() {
+8 −2
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ 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.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -37,7 +38,8 @@ import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.qs.footer.ui.compose.QuickSettings
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -152,7 +154,11 @@ private fun SceneScope.ShadeScene(
                statusBarIconController = statusBarIconController,
            )
            Spacer(modifier = Modifier.height(16.dp))
            QuickSettings(modifier = Modifier.height(160.dp))
            QuickSettings(
                modifier = Modifier.wrapContentHeight(),
                viewModel.qsSceneAdapter,
                QSSceneAdapter.State.QQS
            )
            Spacer(modifier = Modifier.height(16.dp))
            Notifications(modifier = Modifier.weight(1f))
        }
Loading