• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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