Implement stretch option for QS tiles
Rows with holes with stretch tiles to fill in empty spaces (except last row).
Edit mode reuses the same composable as InfiniteGridLayout, and the grid consistency interactor doesn't do any reordering.
This change also adds a layout selector to swap between prototypes.
Fix: 340246005
Flag: ACONFIG com.android.systemui.qs_ui_refactor DEVELOPMENT
Test: manually using layout selector on QSActivity
Change-Id: Ie4dbb81f99e3e6f0c00233974d518ec8b8612282
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 0696fbe..2cc3985 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
@@ -29,8 +29,10 @@
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.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.StretchedGridLayoutType
import com.android.systemui.qs.panels.ui.compose.GridLayout
import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -63,6 +65,14 @@
}
@Provides
+ @IntoSet
+ fun provideStretchedGridLayout(
+ gridLayout: StretchedGridLayout
+ ): Pair<GridLayoutType, GridLayout> {
+ return Pair(StretchedGridLayoutType, gridLayout)
+ }
+
+ @Provides
fun provideGridLayoutMap(
entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>>
): Map<GridLayoutType, GridLayout> {
@@ -70,6 +80,13 @@
}
@Provides
+ fun provideGridLayoutTypes(
+ entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>>
+ ): Set<GridLayoutType> {
+ return entries.map { it.first }.toSet()
+ }
+
+ @Provides
@IntoSet
fun provideGridConsistencyInteractor(
consistencyInteractor: InfiniteGridConsistencyInteractor
@@ -78,6 +95,14 @@
}
@Provides
+ @IntoSet
+ fun provideStretchedGridConsistencyInteractor(
+ consistencyInteractor: NoopGridConsistencyInteractor
+ ): Pair<GridLayoutType, GridTypeConsistencyInteractor> {
+ return Pair(StretchedGridLayoutType, consistencyInteractor)
+ }
+
+ @Provides
fun provideGridConsistencyInteractorMap(
entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>>
): Map<GridLayoutType, GridTypeConsistencyInteractor> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
index 542d0cb..31795d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
@@ -26,10 +26,17 @@
interface GridLayoutTypeRepository {
val layout: StateFlow<GridLayoutType>
+ fun setLayout(type: GridLayoutType)
}
@SysUISingleton
class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository {
private val _layout: MutableStateFlow<GridLayoutType> = MutableStateFlow(InfiniteGridLayoutType)
override val layout = _layout.asStateFlow()
+
+ override fun setLayout(type: GridLayoutType) {
+ if (_layout.value != type) {
+ _layout.value = type
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
index b6be578..4af1b22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
@@ -20,9 +20,13 @@
import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
import com.android.systemui.qs.panels.shared.model.GridLayoutType
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
@SysUISingleton
-class GridLayoutTypeInteractor @Inject constructor(repo: GridLayoutTypeRepository) {
- val layout: Flow<GridLayoutType> = repo.layout
+class GridLayoutTypeInteractor @Inject constructor(private val repo: GridLayoutTypeRepository) {
+ val layout: StateFlow<GridLayoutType> = repo.layout
+
+ fun setLayoutType(type: GridLayoutType) {
+ repo.setLayout(type)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
index 74e906c..b437f64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -18,6 +18,8 @@
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.TileRow
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
@@ -35,7 +37,7 @@
*/
override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
val newTiles: MutableList<TileSpec> = mutableListOf()
- val row = TileRow(columns = gridSizeInteractor.columns.value)
+ val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value
val tilesQueue =
ArrayDeque(
@@ -54,7 +56,7 @@
while (tilesQueue.isNotEmpty()) {
if (row.isFull()) {
- newTiles.addAll(row.tileSpecs())
+ newTiles.addAll(row.tiles.map { it.tile })
row.clear()
}
@@ -66,13 +68,13 @@
// We'll try to either add an icon tile from the queue to complete the row, or
// remove an icon tile from the current row to free up space.
- val iconTile: SizedTile? = tilesQueue.firstOrNull { it.width == 1 }
+ val iconTile: SizedTile<TileSpec>? = tilesQueue.firstOrNull { it.width == 1 }
if (iconTile != null) {
tilesQueue.remove(iconTile)
tilesQueue.addFirst(tile)
row.maybeAddTile(iconTile)
} else {
- val tileToRemove: SizedTile? = row.findLastIconTile()
+ val tileToRemove: SizedTile<TileSpec>? = row.findLastIconTile()
if (tileToRemove != null) {
row.removeTile(tileToRemove)
row.maybeAddTile(tile)
@@ -84,7 +86,7 @@
// If the row does not have an icon tile, add the incomplete row.
// Note: this shouldn't happen because an icon tile is guaranteed to be in a
// row that doesn't have enough space for a large tile.
- val tileSpecs = row.tileSpecs()
+ val tileSpecs = row.tiles.map { it.tile }
Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs")
newTiles.addAll(tileSpecs)
row.clear()
@@ -95,48 +97,11 @@
}
// Add last row that might be incomplete
- newTiles.addAll(row.tileSpecs())
+ newTiles.addAll(row.tiles.map { it.tile })
return newTiles.toList()
}
- /** Tile with a width representing the number of columns it should take. */
- private data class SizedTile(val spec: TileSpec, val width: Int)
-
- private class TileRow(private val columns: Int) {
- private var availableColumns = columns
- private val tiles: MutableList<SizedTile> = mutableListOf()
-
- fun tileSpecs(): List<TileSpec> {
- return tiles.map { it.spec }
- }
-
- fun maybeAddTile(tile: SizedTile): Boolean {
- if (availableColumns - tile.width >= 0) {
- tiles.add(tile)
- availableColumns -= tile.width
- return true
- }
- return false
- }
-
- fun findLastIconTile(): SizedTile? {
- return tiles.findLast { it.width == 1 }
- }
-
- fun removeTile(tile: SizedTile) {
- tiles.remove(tile)
- availableColumns += tile.width
- }
-
- fun clear() {
- tiles.clear()
- availableColumns = columns
- }
-
- fun isFull(): Boolean = availableColumns == 0
- }
-
private companion object {
const val TAG = "InfiniteGridConsistencyInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt
new file mode 100644
index 0000000..97ceacc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+
+@SysUISingleton
+class NoopConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor {
+ override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
index 23110dc..501730a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt
@@ -25,3 +25,9 @@
/** Grid type representing a scrollable vertical grid. */
data object InfiniteGridLayoutType : GridLayoutType
+
+/**
+ * Grid type representing a scrollable vertical grid where tiles will stretch to fill in empty
+ * spaces.
+ */
+data object StretchedGridLayoutType : GridLayoutType
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
new file mode 100644
index 0000000..7e4381b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+/** Represents a tile of type [T] associated with a width */
+data class SizedTile<T>(val tile: T, val width: Int)
+
+/** Represents a row of [SizedTile] with a maximum width of [columns] */
+class TileRow<T>(private val columns: Int) {
+ private var availableColumns = columns
+ private val _tiles: MutableList<SizedTile<T>> = mutableListOf()
+ val tiles: List<SizedTile<T>>
+ get() = _tiles.toList()
+
+ fun maybeAddTile(tile: SizedTile<T>): Boolean {
+ if (availableColumns - tile.width >= 0) {
+ _tiles.add(tile)
+ availableColumns -= tile.width
+ return true
+ }
+ return false
+ }
+
+ fun findLastIconTile(): SizedTile<T>? {
+ return _tiles.findLast { it.width == 1 }
+ }
+
+ fun removeTile(tile: SizedTile<T>) {
+ _tiles.remove(tile)
+ availableColumns += tile.width
+ }
+
+ fun clear() {
+ _tiles.clear()
+ availableColumns = columns
+ }
+
+ fun isFull(): Boolean = availableColumns == 0
+}
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 bac0f60..f5ee720 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
@@ -16,85 +16,23 @@
package com.android.systemui.qs.panels.ui.compose
-import android.graphics.drawable.Animatable
-import android.text.TextUtils
-import androidx.appcompat.content.res.AppCompatResources
-import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
-import androidx.compose.animation.graphics.res.animatedVectorResource
-import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
-import androidx.compose.animation.graphics.vector.AnimatedImageVector
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.basicMarquee
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.combinedClickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Arrangement.spacedBy
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
-import androidx.compose.foundation.lazy.grid.LazyGridScope
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Remove
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.onClick
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.stateDescription
-import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.compose.animation.Expandable
-import com.android.compose.theme.colorAttr
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.ui.compose.Icon
-import com.android.systemui.common.ui.compose.load
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.ActiveTileColorAttributes
-import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.TileColorAttributes
-import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.toUiState
-import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.res.R
import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.mapLatest
@SysUISingleton
class InfiniteGridLayout
@@ -104,8 +42,6 @@
private val gridSizeInteractor: InfiniteGridSizeInteractor
) : GridLayout {
- private object TileType
-
@Composable
override fun TileGrid(
tiles: List<TileViewModel>,
@@ -140,55 +76,6 @@
}
}
- @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
- @Composable
- private fun Tile(
- tile: TileViewModel,
- iconOnly: Boolean,
- modifier: Modifier,
- ) {
- val state: TileUiState by
- tile.state
- .mapLatest { it.toUiState() }
- .collectAsStateWithLifecycle(initialValue = tile.currentState.toUiState())
- val context = LocalContext.current
-
- Expandable(
- color = colorAttr(state.colors.background),
- shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
- ) {
- Row(
- modifier =
- modifier
- .combinedClickable(
- onClick = { tile.onClick(it) },
- onLongClick = { tile.onLongClick(it) }
- )
- .tileModifier(state.colors),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(iconOnly),
- ) {
- val icon =
- remember(state.icon) {
- state.icon.get().let {
- if (it is QSTileImpl.ResourceIcon) {
- Icon.Resource(it.resId, null)
- } else {
- Icon.Loaded(it.getDrawable(context), null)
- }
- }
- }
- TileContent(
- label = state.label.toString(),
- secondaryLabel = state.secondaryLabel?.toString(),
- icon = icon,
- colors = state.colors,
- iconOnly = iconOnly
- )
- }
- }
- }
-
@Composable
override fun EditTileGrid(
tiles: List<EditTileViewModel>,
@@ -196,262 +83,16 @@
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit,
) {
- val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
- val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null }
- val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
- onAddTile(it, POSITION_AT_END)
- }
- val iconOnlySpecs by
- iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle(
- initialValue = emptySet()
- )
- val isIconOnly: (TileSpec) -> Boolean =
- remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
+ val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
- TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
- // These Text are just placeholders to see the different sections. Not final UI.
- item(span = { GridItemSpan(maxLineSpan) }) {
- Text("Current tiles", color = Color.White)
- }
-
- editTiles(
- currentTiles,
- ClickAction.REMOVE,
- onRemoveTile,
- isIconOnly,
- indicatePosition = true,
- )
-
- item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) }
-
- editTiles(
- otherTilesStock,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- )
-
- item(span = { GridItemSpan(maxLineSpan) }) {
- Text("Custom tiles to add", color = Color.White)
- }
-
- editTiles(
- otherTilesCustom,
- ClickAction.ADD,
- addTileToEnd,
- isIconOnly,
- )
- }
- }
-
- private fun LazyGridScope.editTiles(
- tiles: List<EditTileViewModel>,
- clickAction: ClickAction,
- onClick: (TileSpec) -> Unit,
- isIconOnly: (TileSpec) -> Boolean,
- indicatePosition: Boolean = false,
- ) {
- items(
- count = tiles.size,
- key = { tiles[it].tileSpec.spec },
- span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) },
- contentType = { TileType }
- ) {
- val viewModel = tiles[it]
- val canClick =
- when (clickAction) {
- ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions
- ClickAction.REMOVE ->
- AvailableEditActions.REMOVE in viewModel.availableEditActions
- }
- val onClickActionName =
- when (clickAction) {
- ClickAction.ADD ->
- stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
- ClickAction.REMOVE ->
- stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
- }
- val stateDescription =
- if (indicatePosition) {
- stringResource(id = R.string.accessibility_qs_edit_position, it + 1)
- } else {
- ""
- }
-
- Box(
- modifier =
- Modifier.clickable(enabled = canClick) { onClick.invoke(viewModel.tileSpec) }
- .animateItem()
- .semantics {
- onClick(onClickActionName) { false }
- this.stateDescription = stateDescription
- }
- ) {
- EditTile(
- tileViewModel = viewModel,
- isIconOnly(viewModel.tileSpec),
- modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
- )
- if (canClick) {
- Badge(clickAction, Modifier.align(Alignment.TopEnd))
- }
- }
- }
- }
-
- @Composable
- private fun Badge(action: ClickAction, modifier: Modifier = Modifier) {
- Box(modifier = modifier.size(16.dp).background(Color.Cyan, shape = CircleShape)) {
- Icon(
- imageVector =
- when (action) {
- ClickAction.ADD -> Icons.Filled.Add
- ClickAction.REMOVE -> Icons.Filled.Remove
- },
- "",
- tint = Color.Black,
- )
- }
- }
-
- @Composable
- private fun EditTile(
- tileViewModel: EditTileViewModel,
- iconOnly: Boolean,
- modifier: Modifier = Modifier,
- ) {
- val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
- val colors = ActiveTileColorAttributes
-
- Row(
- modifier = modifier.tileModifier(colors).semantics { this.contentDescription = label },
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = tileHorizontalArrangement(iconOnly)
- ) {
- TileContent(
- label = label,
- secondaryLabel = tileViewModel.appName?.load(),
- colors = colors,
- icon = tileViewModel.icon,
- iconOnly = iconOnly,
- animateIconToEnd = true,
- )
- }
- }
-
- private enum class ClickAction {
- ADD,
- REMOVE,
- }
-}
-
-@OptIn(ExperimentalAnimationGraphicsApi::class)
-@Composable
-private fun TileIcon(
- icon: Icon,
- color: Color,
- animateToEnd: Boolean = false,
-) {
- val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
- val context = LocalContext.current
- val loadedDrawable =
- remember(icon, context) {
- when (icon) {
- is Icon.Loaded -> icon.drawable
- is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
- }
- }
- if (loadedDrawable !is Animatable) {
- Icon(
- icon = icon,
- tint = color,
+ DefaultEditTileGrid(
+ tiles = tiles,
+ iconOnlySpecs = iconOnlySpecs,
+ columns = GridCells.Fixed(columns),
modifier = modifier,
+ onAddTile = onAddTile,
+ onRemoveTile = onRemoveTile,
)
- } else if (icon is Icon.Resource) {
- val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
- val painter =
- if (animateToEnd) {
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
- } else {
- var atEnd by remember(icon.res) { mutableStateOf(false) }
- LaunchedEffect(key1 = icon.res) {
- delay(350)
- atEnd = true
- }
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
- }
- Image(
- painter = painter,
- contentDescription = null,
- colorFilter = ColorFilter.tint(color = color),
- modifier = modifier
- )
- }
-}
-
-@Composable
-private fun TileLazyGrid(
- modifier: Modifier = Modifier,
- columns: GridCells,
- content: LazyGridScope.() -> Unit,
-) {
- LazyVerticalGrid(
- columns = columns,
- verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
- horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
- modifier = modifier,
- content = content,
- )
-}
-
-@Composable
-private fun Modifier.tileModifier(colors: TileColorAttributes): Modifier {
- return fillMaxWidth()
- .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)))
- .background(colorAttr(colors.background))
- .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin))
-}
-
-@Composable
-private fun tileHorizontalArrangement(iconOnly: Boolean): Arrangement.Horizontal {
- val horizontalAlignment =
- if (iconOnly) {
- Alignment.CenterHorizontally
- } else {
- Alignment.Start
- }
- return spacedBy(
- space = dimensionResource(id = R.dimen.qs_label_container_margin),
- alignment = horizontalAlignment
- )
-}
-
-@Composable
-private fun TileContent(
- label: String,
- secondaryLabel: String?,
- icon: Icon,
- colors: TileColorAttributes,
- iconOnly: Boolean,
- animateIconToEnd: Boolean = false,
-) {
- TileIcon(icon, colorAttr(colors.icon), animateIconToEnd)
-
- if (!iconOnly) {
- Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
- Text(
- label,
- color = colorAttr(colors.label),
- modifier = Modifier.basicMarquee(),
- )
- if (!TextUtils.isEmpty(secondaryLabel)) {
- Text(
- secondaryLabel ?: "",
- color = colorAttr(colors.secondaryLabel),
- modifier = Modifier.basicMarquee(),
- )
- }
- }
}
}
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
new file mode 100644
index 0000000..ddd97c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.compose
+
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+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.TileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+@SysUISingleton
+class StretchedGridLayout
+@Inject
+constructor(
+ private val iconTilesInteractor: IconTilesInteractor,
+ private val gridSizeInteractor: InfiniteGridSizeInteractor,
+) : GridLayout {
+
+ @Composable
+ override fun TileGrid(
+ tiles: List<TileViewModel>,
+ modifier: Modifier,
+ ) {
+ DisposableEffect(tiles) {
+ val token = Any()
+ tiles.forEach { it.startListening(token) }
+ onDispose { tiles.forEach { it.stopListening(token) } }
+ }
+
+ // Tile widths [normal|stretched]
+ // Icon [3 | 4]
+ // Large [6 | 8]
+ val columns = 12
+ val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+ val stretchedTiles =
+ remember(tiles) {
+ val sizedTiles =
+ tiles.map {
+ SizedTile(
+ it,
+ if (iconTilesSpecs.contains(it.spec)) {
+ 3
+ } else {
+ 6
+ }
+ )
+ }
+ splitInRows(sizedTiles, columns)
+ }
+
+ 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))
+ )
+ }
+ }
+ }
+
+ @Composable
+ override fun EditTileGrid(
+ tiles: List<EditTileViewModel>,
+ modifier: Modifier,
+ onAddTile: (TileSpec, Int) -> Unit,
+ onRemoveTile: (TileSpec) -> Unit
+ ) {
+ val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle()
+ val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle()
+
+ DefaultEditTileGrid(
+ tiles = tiles,
+ iconOnlySpecs = iconOnlySpecs,
+ columns = GridCells.Fixed(columns),
+ modifier = modifier,
+ onAddTile = onAddTile,
+ onRemoveTile = onRemoveTile,
+ )
+ }
+
+ private fun splitInRows(
+ tiles: List<SizedTile<TileViewModel>>,
+ columns: Int
+ ): List<SizedTile<TileViewModel>> {
+ val row = TileRow<TileViewModel>(columns)
+
+ return buildList {
+ for (tile in tiles) {
+ if (row.maybeAddTile(tile)) {
+ if (row.isFull()) {
+ // Row is full, no need to stretch tiles
+ addAll(row.tiles)
+ row.clear()
+ }
+ } else {
+ if (row.isFull()) {
+ addAll(row.tiles)
+ } else {
+ // Stretching tiles when row isn't full
+ addAll(row.tiles.map { it.copy(width = it.width + (it.width / 3)) })
+ }
+ row.clear()
+ row.maybeAddTile(tile)
+ }
+ }
+ addAll(row.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
new file mode 100644
index 0000000..eb45110
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -0,0 +1,403 @@
+/*
+ * 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.compose
+
+import android.graphics.drawable.Animatable
+import android.text.TextUtils
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
+import androidx.compose.animation.graphics.res.animatedVectorResource
+import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
+import androidx.compose.animation.graphics.vector.AnimatedImageVector
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.basicMarquee
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Arrangement.spacedBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Remove
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.Expandable
+import com.android.compose.theme.colorAttr
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.load
+import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes
+import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileColorAttributes
+import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toUiState
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.res.R
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.mapLatest
+
+object TileType
+
+@OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class)
+@Composable
+fun Tile(
+ tile: TileViewModel,
+ iconOnly: Boolean,
+ modifier: Modifier,
+) {
+ val state: TileUiState by
+ tile.state
+ .mapLatest { it.toUiState() }
+ .collectAsStateWithLifecycle(tile.currentState.toUiState())
+ val context = LocalContext.current
+
+ Expandable(
+ color = colorAttr(state.colors.background),
+ shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)),
+ ) {
+ Row(
+ modifier =
+ modifier
+ .combinedClickable(
+ onClick = { tile.onClick(it) },
+ onLongClick = { tile.onLongClick(it) }
+ )
+ .tileModifier(state.colors),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(iconOnly),
+ ) {
+ val icon =
+ remember(state.icon) {
+ state.icon.get().let {
+ if (it is QSTileImpl.ResourceIcon) {
+ Icon.Resource(it.resId, null)
+ } else {
+ Icon.Loaded(it.getDrawable(context), null)
+ }
+ }
+ }
+ TileContent(
+ label = state.label.toString(),
+ secondaryLabel = state.secondaryLabel.toString(),
+ icon = icon,
+ colors = state.colors,
+ iconOnly = iconOnly
+ )
+ }
+ }
+}
+
+@Composable
+fun TileLazyGrid(
+ modifier: Modifier = Modifier,
+ columns: GridCells,
+ content: LazyGridScope.() -> Unit,
+) {
+ LazyVerticalGrid(
+ columns = columns,
+ verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
+ horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
+ modifier = modifier,
+ content = content,
+ )
+}
+
+@Composable
+fun DefaultEditTileGrid(
+ tiles: List<EditTileViewModel>,
+ iconOnlySpecs: Set<TileSpec>,
+ columns: GridCells,
+ modifier: Modifier,
+ onAddTile: (TileSpec, Int) -> Unit,
+ onRemoveTile: (TileSpec) -> Unit,
+) {
+ val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
+ val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null }
+ val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
+ onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
+ }
+ val isIconOnly: (TileSpec) -> Boolean =
+ remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
+
+ TileLazyGrid(modifier = modifier, columns = columns) {
+ // These Text are just placeholders to see the different sections. Not final UI.
+ item(span = { GridItemSpan(maxLineSpan) }) { Text("Current tiles", color = Color.White) }
+
+ editTiles(
+ currentTiles,
+ ClickAction.REMOVE,
+ onRemoveTile,
+ isIconOnly,
+ indicatePosition = true,
+ )
+
+ item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) }
+
+ editTiles(
+ otherTilesStock,
+ ClickAction.ADD,
+ addTileToEnd,
+ isIconOnly,
+ )
+
+ item(span = { GridItemSpan(maxLineSpan) }) {
+ Text("Custom tiles to add", color = Color.White)
+ }
+
+ editTiles(
+ otherTilesCustom,
+ ClickAction.ADD,
+ addTileToEnd,
+ isIconOnly,
+ )
+ }
+}
+
+private fun LazyGridScope.editTiles(
+ tiles: List<EditTileViewModel>,
+ clickAction: ClickAction,
+ onClick: (TileSpec) -> Unit,
+ isIconOnly: (TileSpec) -> Boolean,
+ indicatePosition: Boolean = false,
+) {
+ items(
+ count = tiles.size,
+ key = { tiles[it].tileSpec.spec },
+ span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) },
+ contentType = { TileType }
+ ) {
+ val viewModel = tiles[it]
+ val canClick =
+ when (clickAction) {
+ ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions
+ ClickAction.REMOVE -> AvailableEditActions.REMOVE in viewModel.availableEditActions
+ }
+ val onClickActionName =
+ when (clickAction) {
+ ClickAction.ADD ->
+ stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
+ ClickAction.REMOVE ->
+ stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
+ }
+ val stateDescription =
+ if (indicatePosition) {
+ stringResource(id = R.string.accessibility_qs_edit_position, it + 1)
+ } else {
+ ""
+ }
+
+ Box(
+ modifier =
+ Modifier.clickable(enabled = canClick) { onClick.invoke(viewModel.tileSpec) }
+ .animateItem()
+ .semantics {
+ onClick(onClickActionName) { false }
+ this.stateDescription = stateDescription
+ }
+ ) {
+ EditTile(
+ tileViewModel = viewModel,
+ isIconOnly(viewModel.tileSpec),
+ modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
+ )
+ if (canClick) {
+ Badge(clickAction, Modifier.align(Alignment.TopEnd))
+ }
+ }
+ }
+}
+
+@Composable
+fun Badge(action: ClickAction, modifier: Modifier = Modifier) {
+ Box(modifier = modifier.size(16.dp).background(Color.Cyan, shape = CircleShape)) {
+ Icon(
+ imageVector =
+ when (action) {
+ ClickAction.ADD -> Icons.Filled.Add
+ ClickAction.REMOVE -> Icons.Filled.Remove
+ },
+ "",
+ tint = Color.Black,
+ )
+ }
+}
+
+@Composable
+fun EditTile(
+ tileViewModel: EditTileViewModel,
+ iconOnly: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
+ val colors = ActiveTileColorAttributes
+
+ Row(
+ modifier = modifier.tileModifier(colors).semantics { this.contentDescription = label },
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = tileHorizontalArrangement(iconOnly)
+ ) {
+ TileContent(
+ label = label,
+ secondaryLabel = tileViewModel.appName?.load(),
+ colors = colors,
+ icon = tileViewModel.icon,
+ iconOnly = iconOnly,
+ animateIconToEnd = true,
+ )
+ }
+}
+
+enum class ClickAction {
+ ADD,
+ REMOVE,
+}
+
+@OptIn(ExperimentalAnimationGraphicsApi::class)
+@Composable
+private fun TileIcon(
+ icon: Icon,
+ color: Color,
+ animateToEnd: Boolean = false,
+) {
+ val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size))
+ val context = LocalContext.current
+ val loadedDrawable =
+ remember(icon, context) {
+ when (icon) {
+ is Icon.Loaded -> icon.drawable
+ is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
+ }
+ }
+ if (loadedDrawable !is Animatable) {
+ Icon(
+ icon = icon,
+ tint = color,
+ modifier = modifier,
+ )
+ } else if (icon is Icon.Resource) {
+ val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
+ val painter =
+ if (animateToEnd) {
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
+ } else {
+ var atEnd by remember(icon.res) { mutableStateOf(false) }
+ LaunchedEffect(key1 = icon.res) {
+ delay(350)
+ atEnd = true
+ }
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
+ }
+ Image(
+ painter = painter,
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(color = color),
+ modifier = modifier
+ )
+ }
+}
+
+@Composable
+private fun Modifier.tileModifier(colors: TileColorAttributes): Modifier {
+ return fillMaxWidth()
+ .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)))
+ .background(colorAttr(colors.background))
+ .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin))
+}
+
+@Composable
+private fun tileHorizontalArrangement(iconOnly: Boolean): Arrangement.Horizontal {
+ val horizontalAlignment =
+ if (iconOnly) {
+ Alignment.CenterHorizontally
+ } else {
+ Alignment.Start
+ }
+ return spacedBy(
+ space = dimensionResource(id = R.dimen.qs_label_container_margin),
+ alignment = horizontalAlignment
+ )
+}
+
+@Composable
+private fun TileContent(
+ label: String,
+ secondaryLabel: String?,
+ icon: Icon,
+ colors: TileColorAttributes,
+ iconOnly: Boolean,
+ animateIconToEnd: Boolean = false,
+) {
+ TileIcon(icon, colorAttr(colors.icon), animateIconToEnd)
+
+ if (!iconOnly) {
+ Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
+ Text(
+ label,
+ color = colorAttr(colors.label),
+ modifier = Modifier.basicMarquee(),
+ )
+ if (!TextUtils.isEmpty(secondaryLabel)) {
+ Text(
+ secondaryLabel ?: "",
+ color = colorAttr(colors.secondaryLabel),
+ modifier = Modifier.basicMarquee(),
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
index db752dd..d15cfbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -20,7 +20,6 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
-import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
import com.android.systemui.qs.panels.data.repository.IconTilesRepository
import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
import com.android.systemui.qs.panels.data.repository.iconTilesRepository
@@ -48,9 +47,6 @@
data object TestGridLayoutType : GridLayoutType
- private val gridLayout: MutableStateFlow<GridLayoutType> =
- MutableStateFlow(InfiniteGridLayoutType)
-
private val iconOnlyTiles =
MutableStateFlow(
setOf(
@@ -74,17 +70,13 @@
Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor),
Pair(TestGridLayoutType, noopGridConsistencyInteractor)
)
- gridLayoutTypeRepository =
- object : GridLayoutTypeRepository {
- override val layout: StateFlow<GridLayoutType> = gridLayout.asStateFlow()
- }
}
private val underTest = with(kosmos) { gridConsistencyInteractor }
@Before
fun setUp() {
- gridLayout.value = InfiniteGridLayoutType
+ with(kosmos) { gridLayoutTypeRepository.setLayout(InfiniteGridLayoutType) }
underTest.start()
}
@@ -94,7 +86,7 @@
with(kosmos) {
testScope.runTest {
// Using the no-op grid consistency interactor
- gridLayout.value = TestGridLayoutType
+ gridLayoutTypeRepository.setLayout(TestGridLayoutType)
// Setting an invalid layout with holes
// [ Large A ] [ sa ]