diff options
7 files changed, 129 insertions, 29 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index 5e7e0c97a147..9aedf0d1ee31 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -63,6 +63,7 @@ import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.approachLayout +import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot @@ -250,10 +251,23 @@ constructor( private fun Content() { PlatformTheme(isDarkTheme = true) { ProvideShortcutHelperIndication(interactionsConfig = interactionsConfig()) { - if (viewModel.isQsVisibleAndAnyShadeExpanded) { + // TODO(b/389985793): Make sure that there is no coroutine work or recompositions + // happening when alwaysCompose is true but isQsVisibleAndAnyShadeExpanded is false. + if (alwaysCompose || viewModel.isQsVisibleAndAnyShadeExpanded) { Box( modifier = - Modifier.graphicsLayer { alpha = viewModel.viewAlpha } + Modifier.thenIf(alwaysCompose) { + Modifier.layout { measurable, constraints -> + measurable.measure(constraints).run { + layout(width, height) { + if (viewModel.isQsVisibleAndAnyShadeExpanded) { + place(0, 0) + } + } + } + } + } + .graphicsLayer { alpha = viewModel.viewAlpha } .thenIf(notificationScrimClippingParams.isEnabled) { Modifier.notificationScrimClip { notificationScrimClippingParams.params @@ -331,12 +345,12 @@ constructor( } SceneTransitionLayout(state = sceneState, modifier = Modifier.fillMaxSize()) { - scene(QuickSettings) { + scene(QuickSettings, alwaysCompose = alwaysCompose) { LaunchedEffect(Unit) { viewModel.onQSOpen() } Element(QuickSettings.rootElementKey, Modifier) { QuickSettingsElement() } } - scene(QuickQuickSettings) { + scene(QuickQuickSettings, alwaysCompose = alwaysCompose) { LaunchedEffect(Unit) { viewModel.onQQSOpen() } // Cannot pass the element modifier in because the top element has a `testTag` // and this would overwrite it. @@ -626,7 +640,20 @@ constructor( ) { val Tiles = @Composable { - QuickQuickSettings(viewModel = viewModel.quickQuickSettingsViewModel) + QuickQuickSettings( + viewModel = viewModel.quickQuickSettingsViewModel, + listening = { + /* + * When always compose is false, this will always be true, and we'll be + * listening whenever this is composed. + * When always compose is true, we listen if we are visible and not + * fully expanded + */ + !alwaysCompose || + (viewModel.isQsVisibleAndAnyShadeExpanded && + viewModel.expansionState.progress < 1f) + }, + ) } val Media = @Composable { @@ -726,6 +753,18 @@ constructor( TileGrid( viewModel = containerViewModel.tileGridViewModel, modifier = Modifier.fillMaxWidth(), + listening = { + /* + * When always compose is false, this will always be true, + * and we'll be listening whenever this is composed. + * When always compose is true, we look a the second + * condition and we'll listen if QS is visible AND we are + * not fully collapsed. + */ + !alwaysCompose || + (viewModel.isQsVisibleAndAnyShadeExpanded && + viewModel.expansionState.progress > 0f) + }, ) } } @@ -830,6 +869,7 @@ constructor( println("qqsPositionOnScreen", rect) } println("QQS visible", qqsVisible.value) + println("Always composed", alwaysCompose) if (::viewModel.isInitialized) { printSection("View Model") { viewModel.dump(this@run, args) } } @@ -1177,3 +1217,6 @@ private fun interactionsConfig() = // we are OK using this as our content is clipped and all corner radius are larger than this surfaceCornerRadius = 28.dp, ) + +private inline val alwaysCompose + get() = Flags.alwaysComposeQsUiFragment() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt index 185ea93387a3..1fb884de620f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt @@ -27,7 +27,17 @@ import com.android.systemui.qs.pipeline.shared.TileSpec /** A layout of tiles, indicating how they should be composed when showing in QS or in edit mode. */ interface GridLayout { - @Composable fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) + + /** + * [listening] can be used to compose the grid but limit when tiles should be listening. It + * should be a function tracking a snapshot state. + */ + @Composable + fun ContentScope.TileGrid( + tiles: List<TileViewModel>, + modifier: Modifier, + listening: () -> Boolean, + ) @Composable fun EditTileGrid( diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index a07120629d2b..feb4d41e49b6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -32,7 +32,6 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CornerSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow @@ -65,17 +64,16 @@ constructor( @PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout, ) : GridLayout by delegateGridLayout { @Composable - override fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { + override fun ContentScope.TileGrid( + tiles: List<TileViewModel>, + modifier: Modifier, + listening: () -> Boolean, + ) { val viewModel = rememberViewModel(traceName = "PaginatedGridLayout-TileGrid") { viewModelFactory.create() } - DisposableEffect(tiles) { - val token = Any() - tiles.forEach { it.startListening(token) } - onDispose { tiles.forEach { it.stopListening(token) } } - } val columns = viewModel.columns val rows = integerResource(R.integer.quick_settings_paginated_grid_num_rows) @@ -122,7 +120,7 @@ constructor( ) { val page = pages[it] - with(delegateGridLayout) { TileGrid(tiles = page, modifier = Modifier) } + with(delegateGridLayout) { TileGrid(tiles = page, modifier = Modifier, listening) } } FooterBar( buildNumberViewModelFactory = viewModel.buildNumberViewModelFactory, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index cdc03bb9be35..d20b360756d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -18,7 +18,6 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -41,6 +40,7 @@ import com.android.systemui.res.R fun ContentScope.QuickQuickSettings( viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier, + listening: () -> Boolean, ) { val sizedTiles = viewModel.tileViewModels @@ -51,11 +51,6 @@ fun ContentScope.QuickQuickSettings( val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } } - DisposableEffect(tiles) { - val token = Any() - tiles.forEach { it.startListening(token) } - onDispose { tiles.forEach { it.stopListening(token) } } - } val columns = viewModel.columns Box(modifier = modifier) { GridAnchor() @@ -91,4 +86,6 @@ fun ContentScope.QuickQuickSettings( } } } + + TileListener(tiles, listening) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt index fd10f917106b..1858825f91ef 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt @@ -22,9 +22,13 @@ import com.android.compose.animation.scene.ContentScope import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel @Composable -fun ContentScope.TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) { +fun ContentScope.TileGrid( + viewModel: TileGridViewModel, + modifier: Modifier = Modifier, + listening: () -> Boolean = { true }, +) { val gridLayout = viewModel.gridLayout val tiles = viewModel.tileViewModels - with(gridLayout) { TileGrid(tiles, modifier) } + with(gridLayout) { TileGrid(tiles, modifier, listening) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileListener.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileListener.kt new file mode 100644 index 000000000000..6fb8f1bb79a1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileListener.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2025 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.panels.ui.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.snapshotFlow +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel + +/** + * Use to have tiles start/stop listening when this is composed. Additionally, the listening state + * will be gated by [listeningEnabled]. + */ +@Composable +fun TileListener(tiles: List<TileViewModel>, listeningEnabled: () -> Boolean) { + LaunchedEffect(tiles) { + val token = Any() + try { + snapshotFlow { listeningEnabled() } + .collect { listening -> + tiles.forEach { + if (listening) { + it.startListening(token) + } else { + it.stopListening(token) + } + } + } + } finally { + tiles.forEach { it.stopListening(token) } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 0503049382a8..6236ada46374 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -17,7 +17,6 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -34,6 +33,7 @@ import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout +import com.android.systemui.qs.panels.ui.compose.TileListener import com.android.systemui.qs.panels.ui.compose.bounceableInfo import com.android.systemui.qs.panels.ui.compose.rememberEditListState import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel @@ -59,12 +59,11 @@ constructor( ) : PaginatableGridLayout { @Composable - override fun ContentScope.TileGrid(tiles: List<TileViewModel>, modifier: Modifier) { - DisposableEffect(tiles) { - val token = Any() - tiles.forEach { it.startListening(token) } - onDispose { tiles.forEach { it.stopListening(token) } } - } + override fun ContentScope.TileGrid( + tiles: List<TileViewModel>, + modifier: Modifier, + listening: () -> Boolean, + ) { val viewModel = rememberViewModel(traceName = "InfiniteGridLayout.TileGrid") { viewModelFactory.create() @@ -116,6 +115,8 @@ constructor( ) } } + + TileListener(tiles, listening) } @Composable |