Merge "Add optional labels to icon tiles." into main
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index d161c6b..7b67993 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor
 import com.android.systemui.qs.panels.shared.model.GridConsistencyLog
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog
 import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
 import com.android.systemui.qs.panels.shared.model.PartitionedGridLayoutType
 import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType
@@ -35,6 +36,12 @@
 import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
 import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
 import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.IconLabelVisibilityViewModelImpl
+import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModelImpl
+import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModelImpl
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -53,6 +60,15 @@
         impl: NoopGridConsistencyInteractor
     ): GridTypeConsistencyInteractor
 
+    @Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel
+
+    @Binds fun bindGridSizeViewModel(impl: InfiniteGridSizeViewModelImpl): InfiniteGridSizeViewModel
+
+    @Binds
+    fun bindIconLabelVisibilityViewModel(
+        impl: IconLabelVisibilityViewModelImpl
+    ): IconLabelVisibilityViewModel
+
     @Binds @Named("Default") fun bindDefaultGridLayout(impl: PartitionedGridLayout): GridLayout
 
     companion object {
@@ -64,6 +80,13 @@
         }
 
         @Provides
+        @SysUISingleton
+        @IconLabelVisibilityLog
+        fun providesIconTileLabelVisibilityLog(factory: LogBufferFactory): LogBuffer {
+            return factory.create("IconLabelVisibilityLog", 50)
+        }
+
+        @Provides
         @IntoSet
         fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> {
             return Pair(InfiniteGridLayoutType, gridLayout)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepository.kt
new file mode 100644
index 0000000..686e5f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepository.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Repository for whether to show the labels of icon tiles. */
+@SysUISingleton
+class IconLabelVisibilityRepository @Inject constructor() {
+    // TODO(b/341735914): Persist and back up showLabels
+    private val _showLabels = MutableStateFlow(false)
+    val showLabels: StateFlow<Boolean> = _showLabels.asStateFlow()
+
+    fun setShowLabels(showLabels: Boolean) {
+        _showLabels.value = showLabels
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt
new file mode 100644
index 0000000..a871531
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 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.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.qs.panels.data.repository.IconLabelVisibilityRepository
+import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class IconLabelVisibilityInteractor
+@Inject
+constructor(
+    private val repo: IconLabelVisibilityRepository,
+    @IconLabelVisibilityLog private val logBuffer: LogBuffer,
+    @Application scope: CoroutineScope,
+) {
+    val showLabels: StateFlow<Boolean> =
+        repo.showLabels
+            .onEach { logChange(it) }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), repo.showLabels.value)
+
+    fun setShowLabels(showLabels: Boolean) {
+        repo.setShowLabels(showLabels)
+    }
+
+    private fun logChange(showLabels: Boolean) {
+        logBuffer.log(
+            LOG_BUFFER_ICON_TILE_LABEL_VISIBILITY_CHANGE_TAG,
+            LogLevel.DEBUG,
+            { bool1 = showLabels },
+            { "Icon tile label visibility changed: $bool1" }
+        )
+    }
+
+    private companion object {
+        const val LOG_BUFFER_ICON_TILE_LABEL_VISIBILITY_CHANGE_TAG = "IconLabelVisibilityChange"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
new file mode 100644
index 0000000..c92234c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/IconLabelVisibilityLog.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.shared.model
+
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class IconLabelVisibilityLog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index f5ee720..4aeaa7d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -26,9 +26,9 @@
 import androidx.compose.ui.res.dimensionResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
-import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.res.R
@@ -38,8 +38,8 @@
 class InfiniteGridLayout
 @Inject
 constructor(
-    private val iconTilesInteractor: IconTilesInteractor,
-    private val gridSizeInteractor: InfiniteGridSizeInteractor
+    private val iconTilesViewModel: IconTilesViewModel,
+    private val gridSizeViewModel: InfiniteGridSizeViewModel,
 ) : GridLayout {
 
     @Composable
@@ -52,8 +52,8 @@
             tiles.forEach { it.startListening(token) }
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
-        val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
-        val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+        val iconTilesSpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
+        val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
 
         TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
             items(
@@ -68,9 +68,9 @@
                 }
             ) { index ->
                 Tile(
-                    tiles[index],
-                    iconTilesSpecs.contains(tiles[index].spec),
-                    Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+                    tile = tiles[index],
+                    iconOnly = iconTilesSpecs.contains(tiles[index].spec),
+                    modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
                 )
             }
         }
@@ -83,8 +83,8 @@
         onAddTile: (TileSpec, Int) -> Unit,
         onRemoveTile: (TileSpec) -> Unit,
     ) {
-        val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
-        val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+        val iconOnlySpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
+        val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
 
         DefaultEditTileGrid(
             tiles = tiles,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
index 8d0b386..708ef0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -32,6 +33,8 @@
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Switch
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
@@ -39,14 +42,14 @@
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.modifiers.background
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
-import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.PartitionedGridViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -54,12 +57,8 @@
 import javax.inject.Inject
 
 @SysUISingleton
-class PartitionedGridLayout
-@Inject
-constructor(
-    private val iconTilesInteractor: IconTilesInteractor,
-    private val gridSizeInteractor: InfiniteGridSizeInteractor,
-) : GridLayout {
+class PartitionedGridLayout @Inject constructor(private val viewModel: PartitionedGridViewModel) :
+    GridLayout {
     @Composable
     override fun TileGrid(tiles: List<TileViewModel>, modifier: Modifier) {
         DisposableEffect(tiles) {
@@ -67,9 +66,11 @@
             tiles.forEach { it.startListening(token) }
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
-        val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
-        val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
-        val tileHeight = dimensionResource(id = R.dimen.qs_tile_height)
+        val iconTilesSpecs by viewModel.iconTilesSpecs.collectAsStateWithLifecycle()
+        val columns by viewModel.columns.collectAsStateWithLifecycle()
+        val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
+        val largeTileHeight = tileHeight()
+        val iconTileHeight = tileHeight(showLabels)
         val (smallTiles, largeTiles) = tiles.partition { iconTilesSpecs.contains(it.spec) }
 
         TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
@@ -78,7 +79,7 @@
                 Tile(
                     tile = largeTiles[index],
                     iconOnly = false,
-                    modifier = Modifier.height(tileHeight)
+                    modifier = Modifier.height(largeTileHeight)
                 )
             }
             fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
@@ -88,7 +89,8 @@
                 Tile(
                     tile = smallTiles[index],
                     iconOnly = true,
-                    modifier = Modifier.height(tileHeight)
+                    showLabels = showLabels,
+                    modifier = Modifier.height(iconTileHeight)
                 )
             }
         }
@@ -101,8 +103,9 @@
         onAddTile: (TileSpec, Int) -> Unit,
         onRemoveTile: (TileSpec) -> Unit
     ) {
-        val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
-        val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+        val iconOnlySpecs by viewModel.iconTilesSpecs.collectAsStateWithLifecycle()
+        val columns by viewModel.columns.collectAsStateWithLifecycle()
+        val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
 
         val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
         val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
@@ -110,27 +113,56 @@
         }
         val isIconOnly: (TileSpec) -> Boolean =
             remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
-        val tileHeight = dimensionResource(id = R.dimen.qs_tile_height)
+        val largeTileHeight = tileHeight()
+        val iconTileHeight = tileHeight(showLabels)
         val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
 
         Column(
             verticalArrangement = Arrangement.spacedBy(tilePadding),
             modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState())
         ) {
+            Row(
+                modifier =
+                    Modifier.background(
+                            color = MaterialTheme.colorScheme.surfaceVariant,
+                            alpha = { 1f },
+                            shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))
+                        )
+                        .padding(tilePadding)
+            ) {
+                Column(Modifier.padding(start = tilePadding)) {
+                    Text(
+                        text = "Show text labels",
+                        color = MaterialTheme.colorScheme.onBackground,
+                        fontWeight = FontWeight.Bold
+                    )
+                    Text(
+                        text = "Display names under each tile",
+                        color = MaterialTheme.colorScheme.onBackground
+                    )
+                }
+                Spacer(modifier = Modifier.weight(1f))
+                Switch(checked = showLabels, onCheckedChange = { viewModel.setShowLabels(it) })
+            }
+
             CurrentTiles(
                 tiles = currentTiles,
-                tileHeight = tileHeight,
+                largeTileHeight = largeTileHeight,
+                iconTileHeight = iconTileHeight,
                 tilePadding = tilePadding,
                 onRemoveTile = onRemoveTile,
                 isIconOnly = isIconOnly,
                 columns = columns,
+                showLabels = showLabels,
             )
             AvailableTiles(
                 tiles = otherTiles,
-                tileHeight = tileHeight,
+                largeTileHeight = largeTileHeight,
+                iconTileHeight = iconTileHeight,
                 tilePadding = tilePadding,
                 addTileToEnd = addTileToEnd,
                 isIconOnly = isIconOnly,
+                showLabels = showLabels,
                 columns = columns,
             )
         }
@@ -139,23 +171,31 @@
     @Composable
     private fun CurrentTiles(
         tiles: List<EditTileViewModel>,
-        tileHeight: Dp,
+        largeTileHeight: Dp,
+        iconTileHeight: Dp,
         tilePadding: Dp,
         onRemoveTile: (TileSpec) -> Unit,
         isIconOnly: (TileSpec) -> Boolean,
+        showLabels: Boolean,
         columns: Int,
     ) {
         val (smallTiles, largeTiles) = tiles.partition { isIconOnly(it.tileSpec) }
 
-        val largeGridHeight = gridHeight(largeTiles.size, tileHeight, columns / 2, tilePadding)
-        val smallGridHeight = gridHeight(smallTiles.size, tileHeight, columns, tilePadding)
+        val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding)
+        val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding)
 
         CurrentTilesContainer {
             TileLazyGrid(
                 columns = GridCells.Fixed(columns),
                 modifier = Modifier.height(largeGridHeight),
             ) {
-                editTiles(largeTiles, ClickAction.REMOVE, onRemoveTile, { false }, true)
+                editTiles(
+                    largeTiles,
+                    ClickAction.REMOVE,
+                    onRemoveTile,
+                    { false },
+                    indicatePosition = true
+                )
             }
         }
         CurrentTilesContainer {
@@ -163,7 +203,14 @@
                 columns = GridCells.Fixed(columns),
                 modifier = Modifier.height(smallGridHeight),
             ) {
-                editTiles(smallTiles, ClickAction.REMOVE, onRemoveTile, { true }, true)
+                editTiles(
+                    smallTiles,
+                    ClickAction.REMOVE,
+                    onRemoveTile,
+                    { true },
+                    showLabels = showLabels,
+                    indicatePosition = true
+                )
             }
         }
     }
@@ -171,19 +218,21 @@
     @Composable
     private fun AvailableTiles(
         tiles: List<EditTileViewModel>,
-        tileHeight: Dp,
+        largeTileHeight: Dp,
+        iconTileHeight: Dp,
         tilePadding: Dp,
         addTileToEnd: (TileSpec) -> Unit,
         isIconOnly: (TileSpec) -> Boolean,
+        showLabels: Boolean,
         columns: Int,
     ) {
         val (tilesStock, tilesCustom) = tiles.partition { it.appName == null }
         val (smallTiles, largeTiles) = tilesStock.partition { isIconOnly(it.tileSpec) }
 
-        val largeGridHeight = gridHeight(largeTiles.size, tileHeight, columns / 2, tilePadding)
-        val smallGridHeight = gridHeight(smallTiles.size, tileHeight, columns, tilePadding)
+        val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding)
+        val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding)
         val largeGridHeightCustom =
-            gridHeight(tilesCustom.size, tileHeight, columns / 2, tilePadding)
+            gridHeight(tilesCustom.size, largeTileHeight, columns / 2, tilePadding)
 
         // Add up the height of all three grids and add padding in between
         val gridHeight =
@@ -199,7 +248,13 @@
                 fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
 
                 // Small tiles
-                editTiles(smallTiles, ClickAction.ADD, addTileToEnd, isIconOnly)
+                editTiles(
+                    smallTiles,
+                    ClickAction.ADD,
+                    addTileToEnd,
+                    isIconOnly,
+                    showLabels = showLabels
+                )
                 fillUpRow(nTiles = smallTiles.size, columns = columns)
 
                 // Custom tiles, all large
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
index ddd97c2..70d629f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
@@ -27,11 +27,11 @@
 import androidx.compose.ui.res.dimensionResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
-import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.shared.model.TileRow
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridSizeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.res.R
@@ -41,8 +41,8 @@
 class StretchedGridLayout
 @Inject
 constructor(
-    private val iconTilesInteractor: IconTilesInteractor,
-    private val gridSizeInteractor: InfiniteGridSizeInteractor,
+    private val iconTilesViewModel: IconTilesViewModel,
+    private val gridSizeViewModel: InfiniteGridSizeViewModel,
 ) : GridLayout {
 
     @Composable
@@ -60,7 +60,7 @@
         // Icon [3 | 4]
         // Large [6 | 8]
         val columns = 12
-        val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+        val iconTilesSpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
         val stretchedTiles =
             remember(tiles) {
                 val sizedTiles =
@@ -80,9 +80,9 @@
         TileLazyGrid(columns = GridCells.Fixed(columns), modifier = modifier) {
             items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index ->
                 Tile(
-                    stretchedTiles[index].tile,
-                    iconTilesSpecs.contains(stretchedTiles[index].tile.spec),
-                    Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+                    tile = stretchedTiles[index].tile,
+                    iconOnly = iconTilesSpecs.contains(stretchedTiles[index].tile.spec),
+                    modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
                 )
             }
         }
@@ -95,8 +95,8 @@
         onAddTile: (TileSpec, Int) -> Unit,
         onRemoveTile: (TileSpec) -> Unit
     ) {
-        val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
-        val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+        val iconOnlySpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
+        val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
 
         DefaultEditTileGrid(
             tiles = tiles,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index e8c65a5..a6838c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -69,6 +69,9 @@
 import androidx.compose.ui.semantics.onClick
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.Expandable
@@ -98,6 +101,7 @@
 fun Tile(
     tile: TileViewModel,
     iconOnly: Boolean,
+    showLabels: Boolean = false,
     modifier: Modifier,
 ) {
     val state: TileUiState by
@@ -136,7 +140,8 @@
                 secondaryLabel = state.secondaryLabel.toString(),
                 icon = icon,
                 colors = state.colors,
-                iconOnly = iconOnly
+                iconOnly = iconOnly,
+                showLabels = showLabels,
             )
         }
     }
@@ -213,6 +218,7 @@
     clickAction: ClickAction,
     onClick: (TileSpec) -> Unit,
     isIconOnly: (TileSpec) -> Boolean,
+    showLabels: Boolean = false,
     indicatePosition: Boolean = false,
 ) {
     items(
@@ -250,10 +256,13 @@
                         this.stateDescription = stateDescription
                     }
         ) {
+            val iconOnly = isIconOnly(viewModel.tileSpec)
+            val tileHeight = tileHeight(iconOnly && showLabels)
             EditTile(
                 tileViewModel = viewModel,
-                isIconOnly(viewModel.tileSpec),
-                modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+                iconOnly = iconOnly,
+                showLabels = showLabels,
+                modifier = Modifier.height(tileHeight)
             )
             if (canClick) {
                 Badge(clickAction, Modifier.align(Alignment.TopEnd))
@@ -281,6 +290,7 @@
 fun EditTile(
     tileViewModel: EditTileViewModel,
     iconOnly: Boolean,
+    showLabels: Boolean,
     modifier: Modifier = Modifier,
 ) {
     val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
@@ -297,6 +307,7 @@
             colors = colors,
             icon = tileViewModel.icon,
             iconOnly = iconOnly,
+            showLabels = showLabels,
             animateIconToEnd = true,
         )
     }
@@ -380,9 +391,26 @@
     icon: Icon,
     colors: TileColorAttributes,
     iconOnly: Boolean,
+    showLabels: Boolean = false,
     animateIconToEnd: Boolean = false,
 ) {
-    TileIcon(icon, colorAttr(colors.icon), animateIconToEnd)
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center,
+        modifier = Modifier.fillMaxHeight()
+    ) {
+        TileIcon(icon, colorAttr(colors.icon), animateIconToEnd)
+
+        if (iconOnly && showLabels) {
+            Text(
+                label,
+                maxLines = 2,
+                color = colorAttr(colors.label),
+                overflow = TextOverflow.Ellipsis,
+                textAlign = TextAlign.Center,
+            )
+        }
+    }
 
     if (!iconOnly) {
         Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
@@ -401,3 +429,16 @@
         }
     }
 }
+
+@Composable
+fun tileHeight(iconWithLabel: Boolean = false): Dp {
+    return if (iconWithLabel) {
+        TileDimensions.IconTileWithLabelHeight
+    } else {
+        dimensionResource(id = R.dimen.qs_tile_height)
+    }
+}
+
+private object TileDimensions {
+    val IconTileWithLabelHeight = 100.dp
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt
new file mode 100644
index 0000000..5d4b8f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.domain.interactor.IconLabelVisibilityInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+interface IconLabelVisibilityViewModel {
+    val showLabels: StateFlow<Boolean>
+
+    fun setShowLabels(showLabels: Boolean)
+}
+
+@SysUISingleton
+class IconLabelVisibilityViewModelImpl
+@Inject
+constructor(private val interactor: IconLabelVisibilityInteractor) : IconLabelVisibilityViewModel {
+    override val showLabels: StateFlow<Boolean> = interactor.showLabels
+
+    override fun setShowLabels(showLabels: Boolean) {
+        interactor.setShowLabels(showLabels)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
new file mode 100644
index 0000000..9ad00c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+interface IconTilesViewModel {
+    val iconTilesSpecs: StateFlow<Set<TileSpec>>
+}
+
+@SysUISingleton
+class IconTilesViewModelImpl @Inject constructor(interactor: IconTilesInteractor) :
+    IconTilesViewModel {
+    override val iconTilesSpecs = interactor.iconTilesSpecs
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModel.kt
new file mode 100644
index 0000000..a4ee58f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+interface InfiniteGridSizeViewModel {
+    val columns: StateFlow<Int>
+}
+
+@SysUISingleton
+class InfiniteGridSizeViewModelImpl @Inject constructor(interactor: InfiniteGridSizeInteractor) :
+    InfiniteGridSizeViewModel {
+    override val columns: StateFlow<Int> = interactor.columns
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt
new file mode 100644
index 0000000..730cf63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModel.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 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.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class PartitionedGridViewModel
+@Inject
+constructor(
+    iconTilesViewModel: IconTilesViewModel,
+    gridSizeViewModel: InfiniteGridSizeViewModel,
+    iconLabelVisibilityViewModel: IconLabelVisibilityViewModel,
+) :
+    IconTilesViewModel by iconTilesViewModel,
+    InfiniteGridSizeViewModel by gridSizeViewModel,
+    IconLabelVisibilityViewModel by iconLabelVisibilityViewModel
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepositoryKosmos.kt
new file mode 100644
index 0000000..277dbb7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.iconLabelVisibilityRepository by Kosmos.Fixture { IconLabelVisibilityRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt
new file mode 100644
index 0000000..7b9e4a1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.qs.panels.data.repository.iconLabelVisibilityRepository
+
+val Kosmos.iconLabelVisibilityInteractor by
+    Kosmos.Fixture {
+        IconLabelVisibilityInteractor(
+            iconLabelVisibilityRepository,
+            FakeLogBuffer.Factory.create(),
+            applicationCoroutineScope
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index 34b266a..82cfaf5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -18,6 +18,8 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridSizeViewModel
 
 val Kosmos.infiniteGridLayout by
-    Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor, infiniteGridSizeInteractor) }
+    Kosmos.Fixture { InfiniteGridLayout(iconTilesViewModel, infiniteGridSizeViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
index 4febfe91..37c9552 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/PartitionedGridLayoutKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.ui.compose.PartitionedGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.partitionedGridViewModel
 
 val Kosmos.partitionedGridLayout by
-    Kosmos.Fixture { PartitionedGridLayout(iconTilesInteractor, infiniteGridSizeInteractor) }
+    Kosmos.Fixture { PartitionedGridLayout(partitionedGridViewModel) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModelKosmos.kt
new file mode 100644
index 0000000..daf6087
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModelKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.domain.interactor.iconLabelVisibilityInteractor
+
+val Kosmos.iconLabelVisibilityViewModel by
+    Kosmos.Fixture { IconLabelVisibilityViewModelImpl(iconLabelVisibilityInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModelKosmos.kt
new file mode 100644
index 0000000..89b42a6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModelKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.domain.interactor.iconTilesInteractor
+
+val Kosmos.iconTilesViewModel by Kosmos.Fixture { IconTilesViewModelImpl(iconTilesInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModelKosmos.kt
new file mode 100644
index 0000000..f6dfb8b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridSizeViewModelKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridSizeInteractor
+
+val Kosmos.infiniteGridSizeViewModel by
+    Kosmos.Fixture { InfiniteGridSizeViewModelImpl(infiniteGridSizeInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt
new file mode 100644
index 0000000..b07cc7d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PartitionedGridViewModelKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.partitionedGridViewModel by
+    Kosmos.Fixture {
+        PartitionedGridViewModel(
+            iconTilesViewModel,
+            infiniteGridSizeViewModel,
+            iconLabelVisibilityViewModel,
+        )
+    }