1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.qs.panels.ui.compose.infinitegrid 18 19 import androidx.compose.runtime.Composable 20 import androidx.compose.runtime.derivedStateOf 21 import androidx.compose.runtime.getValue 22 import androidx.compose.runtime.remember 23 import androidx.compose.runtime.rememberCoroutineScope 24 import androidx.compose.ui.Modifier 25 import androidx.compose.ui.res.dimensionResource 26 import androidx.compose.ui.util.fastMap 27 import androidx.lifecycle.compose.collectAsStateWithLifecycle 28 import com.android.compose.animation.scene.ContentScope 29 import com.android.systemui.dagger.SysUISingleton 30 import com.android.systemui.grid.ui.compose.VerticalSpannedGrid 31 import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider 32 import com.android.systemui.lifecycle.rememberViewModel 33 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS 34 import com.android.systemui.qs.panels.shared.model.SizedTileImpl 35 import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout 36 import com.android.systemui.qs.panels.ui.compose.TileListener 37 import com.android.systemui.qs.panels.ui.compose.bounceableInfo 38 import com.android.systemui.qs.panels.ui.compose.rememberEditListState 39 import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel 40 import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel 41 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel 42 import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel 43 import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridViewModel 44 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel 45 import com.android.systemui.qs.pipeline.shared.TileSpec 46 import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey 47 import com.android.systemui.res.R 48 import javax.inject.Inject 49 50 @SysUISingleton 51 class InfiniteGridLayout 52 @Inject 53 constructor( 54 private val detailsViewModel: DetailsViewModel, 55 private val iconTilesViewModel: IconTilesViewModel, 56 private val viewModelFactory: InfiniteGridViewModel.Factory, 57 private val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider, 58 ) : PaginatableGridLayout { 59 60 @Composable 61 override fun ContentScope.TileGrid( 62 tiles: List<TileViewModel>, 63 modifier: Modifier, 64 listening: () -> Boolean, 65 ) { 66 val viewModel = 67 rememberViewModel(traceName = "InfiniteGridLayout.TileGrid") { 68 viewModelFactory.create() 69 } 70 val iconTilesViewModel = 71 rememberViewModel(traceName = "InfiniteGridLayout.TileGrid") { 72 viewModel.dynamicIconTilesViewModelFactory.create() 73 } 74 val columnsWithMediaViewModel = 75 rememberViewModel(traceName = "InfiniteGridLAyout.TileGrid") { 76 viewModel.columnsWithMediaViewModelFactory.create(LOCATION_QS) 77 } 78 79 val columns = columnsWithMediaViewModel.columns 80 val largeTiles by iconTilesViewModel.largeTilesState 81 val largeTilesSpan by iconTilesViewModel.largeTilesSpanState 82 // Tiles or largeTiles may be updated while this is composed, so listen to any changes 83 val sizedTiles = 84 remember(tiles, largeTiles, largeTilesSpan) { 85 tiles.map { 86 SizedTileImpl(it, if (largeTiles.contains(it.spec)) largeTilesSpan else 1) 87 } 88 } 89 val bounceables = 90 remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } 91 val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle() 92 val scope = rememberCoroutineScope() 93 val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } } 94 95 VerticalSpannedGrid( 96 columns = columns, 97 columnSpacing = dimensionResource(R.dimen.qs_tile_margin_horizontal), 98 rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical), 99 spans = spans, 100 keys = { sizedTiles[it].tile.spec }, 101 ) { spanIndex, column, isFirstInColumn, isLastInColumn -> 102 val it = sizedTiles[spanIndex] 103 104 Element(it.tile.spec.toElementKey(spanIndex), Modifier) { 105 Tile( 106 tile = it.tile, 107 iconOnly = iconTilesViewModel.isIconTile(it.tile.spec), 108 squishiness = { squishiness }, 109 tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider, 110 coroutineScope = scope, 111 bounceableInfo = 112 bounceables.bounceableInfo( 113 it, 114 index = spanIndex, 115 column = column, 116 columns = columns, 117 isFirstInRow = isFirstInColumn, 118 isLastInRow = isLastInColumn, 119 ), 120 detailsViewModel = detailsViewModel, 121 isVisible = listening, 122 ) 123 } 124 } 125 126 TileListener(tiles, listening) 127 } 128 129 @Composable 130 override fun EditTileGrid( 131 tiles: List<EditTileViewModel>, 132 modifier: Modifier, 133 onAddTile: (TileSpec, Int) -> Unit, 134 onRemoveTile: (TileSpec) -> Unit, 135 onSetTiles: (List<TileSpec>) -> Unit, 136 onStopEditing: () -> Unit, 137 ) { 138 val viewModel = 139 rememberViewModel(traceName = "InfiniteGridLayout.EditTileGrid") { 140 viewModelFactory.create() 141 } 142 val iconTilesViewModel = 143 rememberViewModel(traceName = "InfiniteGridLayout.EditTileGrid") { 144 viewModel.dynamicIconTilesViewModelFactory.create() 145 } 146 val columnsViewModel = 147 rememberViewModel(traceName = "InfiniteGridLayout.EditTileGrid") { 148 viewModel.columnsWithMediaViewModelFactory.createWithoutMediaTracking() 149 } 150 val columns = columnsViewModel.columns 151 val largeTilesSpan by iconTilesViewModel.largeTilesSpanState 152 val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle() 153 154 // Non-current tiles should always be displayed as icon tiles. 155 val sizedTiles = 156 remember(tiles, largeTiles, largeTilesSpan) { 157 tiles.map { 158 SizedTileImpl( 159 it, 160 if (!it.isCurrent || !largeTiles.contains(it.tileSpec)) 1 161 else largeTilesSpan, 162 ) 163 } 164 } 165 166 val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent } 167 val currentListState = rememberEditListState(currentTiles, columns, largeTilesSpan) 168 DefaultEditTileGrid( 169 listState = currentListState, 170 otherTiles = otherTiles, 171 columns = columns, 172 modifier = modifier, 173 onAddTile = onAddTile, 174 onRemoveTile = onRemoveTile, 175 onSetTiles = onSetTiles, 176 onResize = iconTilesViewModel::resize, 177 onStopEditing = onStopEditing, 178 onReset = viewModel::showResetDialog, 179 largeTilesSpan = largeTilesSpan, 180 ) 181 } 182 183 override fun splitIntoPages( 184 tiles: List<TileViewModel>, 185 rows: Int, 186 columns: Int, 187 ): List<List<TileViewModel>> { 188 189 return PaginatableGridLayout.splitInRows( 190 tiles.map { SizedTileImpl(it, it.spec.width()) }, 191 columns, 192 ) 193 .chunked(rows) 194 .map { it.flatten().map { it.tile } } 195 } 196 197 private fun TileSpec.width(largeSize: Int = iconTilesViewModel.largeTilesSpan.value): Int { 198 return if (iconTilesViewModel.isIconTile(this)) 1 else largeSize 199 } 200 } 201