Loading packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt +77 −64 Original line number Diff line number Diff line Loading @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.grid.ui.compose import androidx.collection.IntIntPair import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope Loading @@ -24,7 +24,6 @@ import androidx.compose.runtime.key import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Placeable import androidx.compose.ui.semantics.CollectionInfo import androidx.compose.ui.semantics.CollectionItemInfo import androidx.compose.ui.semantics.collectionInfo Loading @@ -34,6 +33,8 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.util.fastMapIndexed import kotlin.math.max /** Loading Loading @@ -65,7 +66,11 @@ fun HorizontalSpannedGrid( spans: List<Int>, modifier: Modifier = Modifier, keys: (spanIndex: Int) -> Any = { it }, composables: @Composable BoxScope.(spanIndex: Int) -> Unit, composables: @Composable BoxScope.( spanIndex: Int, row: Int, isFirstInColumn: Boolean, isLastInColumn: Boolean, ) -> Unit, ) { SpannedGrid( primarySpaces = rows, Loading @@ -80,7 +85,7 @@ fun HorizontalSpannedGrid( } /** * Horizontal (non lazy) grid that supports [spans] for its elements. * Vertical (non lazy) grid that supports [spans] for its elements. * * The elements will be laid down horizontally first, and then by rows. So assuming LTR layout, it * will be (for a span list `[2, 1, 2, 1, 1, 1, 1, 1]` and 4 columns): Loading @@ -107,7 +112,9 @@ fun VerticalSpannedGrid( spans: List<Int>, modifier: Modifier = Modifier, keys: (spanIndex: Int) -> Any = { it }, composables: @Composable BoxScope.(spanIndex: Int) -> Unit, composables: @Composable BoxScope.(spanIndex: Int, column: Int, isFirstInRow: Boolean, isLastInRow: Boolean) -> Unit, ) { SpannedGrid( primarySpaces = columns, Loading @@ -130,7 +137,9 @@ private fun SpannedGrid( isVertical: Boolean, modifier: Modifier = Modifier, keys: (spanIndex: Int) -> Any = { it }, composables: @Composable BoxScope.(spanIndex: Int) -> Unit, composables: @Composable BoxScope.(spanIndex: Int, secondaryAxis: Int, isFirst: Boolean, isLast: Boolean) -> Unit, ) { val crossAxisArrangement = Arrangement.spacedBy(crossAxisSpacing) spans.forEachIndexed { index, span -> Loading @@ -139,7 +148,6 @@ private fun SpannedGrid( "expected rance of [1, $primarySpaces]" } } if (isVertical) { check(crossAxisSpacing >= 0.dp) { "Negative columnSpacing $crossAxisSpacing" } check(mainAxisSpacing >= 0.dp) { "Negative rowSpacing $mainAxisSpacing" } Loading @@ -147,29 +155,30 @@ private fun SpannedGrid( check(mainAxisSpacing >= 0.dp) { "Negative columnSpacing $mainAxisSpacing" } check(crossAxisSpacing >= 0.dp) { "Negative rowSpacing $crossAxisSpacing" } } val totalMainAxisGroups: Int = // List of primary axis index to secondary axis index // This is keyed to the size of the spans list for performance reasons as we don't expect the // spans value to change outside of edit mode. val positions = remember(spans.size) { Array(spans.size) { IntIntPair(0, 0) } } val totalMainAxisGroups = remember(primarySpaces, spans) { var currentAccumulated = 0 var groups = 1 spans.forEach { span -> if (currentAccumulated + span <= primarySpaces) { currentAccumulated += span } else { groups += 1 currentAccumulated = span var mainAxisGroup = 0 var currentSlot = 0 spans.fastForEachIndexed { index, span -> if (currentSlot + span > primarySpaces) { currentSlot = 0 mainAxisGroup += 1 } positions[index] = IntIntPair(mainAxisGroup, currentSlot) currentSlot += span } groups mainAxisGroup + 1 } val slotPositionsAndSizesCache = remember { object { var sizes = IntArray(0) var positions = IntArray(0) } } Layout( { (0 until spans.size).map { spanIndex -> Loading @@ -184,7 +193,13 @@ private fun SpannedGrid( } } ) { composables(spanIndex) val position = positions[spanIndex] composables( spanIndex, position.second, position.second == 0, positions.getOrNull(spanIndex + 1)?.first != position.first, ) } } } Loading @@ -205,7 +220,6 @@ private fun SpannedGrid( slotPositionsAndSizesCache.sizes, ) val cellSizesInCrossAxis = slotPositionsAndSizesCache.sizes // with is needed because of the double receiver (Density, Arrangement). with(crossAxisArrangement) { arrange( Loading @@ -216,68 +230,73 @@ private fun SpannedGrid( ) } val startPositions = slotPositionsAndSizesCache.positions val mainAxisSpacingPx = mainAxisSpacing.roundToPx() val mainAxisTotalGaps = (totalMainAxisGroups - 1) * mainAxisSpacingPx val mainAxisSize = if (isVertical) constraints.maxHeight else constraints.maxWidth val mainAxisMaxSize = if (isVertical) constraints.maxHeight else constraints.maxWidth val mainAxisElementConstraint = if (mainAxisSize == Constraints.Infinity) { if (mainAxisMaxSize == Constraints.Infinity) { Constraints.Infinity } else { max(0, (mainAxisSize - mainAxisTotalGaps) / totalMainAxisGroups) max(0, (mainAxisMaxSize - mainAxisTotalGaps) / totalMainAxisGroups) } val mainAxisSizes = IntArray(totalMainAxisGroups) { 0 } var currentSlot = 0 var mainAxisGroup = 0 var mainAxisTotalSize = mainAxisTotalGaps var currentMainAxis = 0 var currentMainAxisMax = 0 val placeables = measurables.mapIndexed { index, measurable -> measurables.fastMapIndexed { index, measurable -> val span = spans[index] if (currentSlot + span > primarySpaces) { currentSlot = 0 mainAxisGroup += 1 } val position = positions[index] val crossAxisConstraint = calculateWidth(cellSizesInCrossAxis, startPositions, currentSlot, span) PlaceResult( measurable.measure( makeConstraint( isVertical, mainAxisElementConstraint, crossAxisConstraint, ) ), currentSlot, mainAxisGroup, calculateWidth(cellSizesInCrossAxis, startPositions, position.second, span) measurable .measure( makeConstraint(isVertical, mainAxisElementConstraint, crossAxisConstraint) ) .also { currentSlot += span mainAxisSizes[mainAxisGroup] = max( mainAxisSizes[mainAxisGroup], if (isVertical) it.placeable.height else it.placeable.width, ) val placeableSize = if (isVertical) it.height else it.width if (position.first != currentMainAxis) { // New row -- Add the max size to the total and reset the max mainAxisTotalSize += currentMainAxisMax currentMainAxisMax = placeableSize currentMainAxis = position.first } else { currentMainAxisMax = max(currentMainAxisMax, placeableSize) } } } mainAxisTotalSize += currentMainAxisMax val mainAxisTotalSize = mainAxisTotalGaps + mainAxisSizes.sum() val mainAxisStartingPoints = mainAxisSizes.runningFold(0) { acc, value -> acc + value + mainAxisSpacingPx } val height = if (isVertical) mainAxisTotalSize else crossAxisSize val width = if (isVertical) crossAxisSize else mainAxisTotalSize layout(width, height) { placeables.forEach { (placeable, slot, mainAxisGroup) -> var previousMainAxis = 0 var currentMainAxisPosition = 0 var currentMainAxisMax = 0 placeables.forEachIndexed { index, placeable -> val slot = positions[index].second val mainAxisSize = if (isVertical) placeable.height else placeable.width if (positions[index].first != previousMainAxis) { // Move up a row + padding currentMainAxisPosition += currentMainAxisMax + mainAxisSpacingPx currentMainAxisMax = mainAxisSize previousMainAxis = positions[index].first } else { currentMainAxisMax = max(currentMainAxisMax, mainAxisSize) } val x = if (isVertical) { startPositions[slot] } else { mainAxisStartingPoints[mainAxisGroup] currentMainAxisPosition } val y = if (isVertical) { mainAxisStartingPoints[mainAxisGroup] currentMainAxisPosition } else { startPositions[slot] } Loading Loading @@ -321,9 +340,3 @@ private fun calculateCellsCrossAxisSize( outArray[index] = slotSize + if (index < remainingPixels) 1 else 0 } } private data class PlaceResult( val placeable: Placeable, val slotIndex: Int, val mainAxisGroup: Int, ) packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt +8 −3 Original line number Diff line number Diff line Loading @@ -35,11 +35,16 @@ fun List<BounceableTileViewModel>.bounceableInfo( index: Int, column: Int, columns: Int, isFirstInRow: Boolean, isLastInRow: Boolean, ): BounceableInfo { // Only look for neighbor bounceables if they are on the same row // A tile may be the last in the row without being on the last column val onLastColumn = sizedTile.onLastColumn(column, columns) val previousTile = getOrNull(index - 1)?.takeIf { column != 0 } val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn } // Only look for neighbor bounceables if they are on the same row val previousTile = getOrNull(index - 1)?.takeIf { !isFirstInRow } val nextTile = getOrNull(index + 1)?.takeIf { !isLastInRow } return BounceableInfo(this[index], previousTile, nextTile, !onLastColumn) } Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +10 −5 Original line number Diff line number Diff line Loading @@ -57,7 +57,6 @@ fun ContentScope.QuickQuickSettings( onDispose { tiles.forEach { it.stopListening(token) } } } val columns = viewModel.columns var cellIndex = 0 Box(modifier = modifier) { GridAnchor() VerticalSpannedGrid( Loading @@ -67,17 +66,23 @@ fun ContentScope.QuickQuickSettings( spans = spans, modifier = Modifier.sysuiResTag("qqs_tile_layout"), keys = { sizedTiles[it].tile.spec }, ) { spanIndex -> ) { spanIndex, column, isFirstInColumn, isLastInColumn -> val it = sizedTiles[spanIndex] val column = cellIndex % columns cellIndex += it.width Element(it.tile.spec.toElementKey(spanIndex), Modifier) { Tile( tile = it.tile, iconOnly = it.isIcon, squishiness = { squishiness }, coroutineScope = scope, bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), bounceableInfo = bounceables.bounceableInfo( it, index = spanIndex, column = column, columns = columns, isFirstInRow = isFirstInColumn, isLastInRow = isLastInColumn, ), tileHapticsViewModelFactoryProvider = viewModel.tileHapticsViewModelFactoryProvider, // There should be no QuickQuickSettings when the details view is enabled. Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +11 −6 Original line number Diff line number Diff line Loading @@ -85,8 +85,6 @@ constructor( remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle() val scope = rememberCoroutineScope() var cellIndex = 0 val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } } VerticalSpannedGrid( Loading @@ -95,10 +93,9 @@ constructor( rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical), spans = spans, keys = { sizedTiles[it].tile.spec }, ) { spanIndex -> ) { spanIndex, column, isFirstInColumn, isLastInColumn -> val it = sizedTiles[spanIndex] val column = cellIndex % columns cellIndex += it.width Element(it.tile.spec.toElementKey(spanIndex), Modifier) { Tile( tile = it.tile, Loading @@ -106,7 +103,15 @@ constructor( squishiness = { squishiness }, tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider, coroutineScope = scope, bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), bounceableInfo = bounceables.bounceableInfo( it, index = spanIndex, column = column, columns = columns, isFirstInRow = isFirstInColumn, isLastInRow = isLastInColumn, ), detailsViewModel = detailsViewModel, ) } Loading Loading
packages/SystemUI/src/com/android/systemui/grid/ui/compose/SpannedGrids.kt +77 −64 Original line number Diff line number Diff line Loading @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.grid.ui.compose import androidx.collection.IntIntPair import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope Loading @@ -24,7 +24,6 @@ import androidx.compose.runtime.key import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Placeable import androidx.compose.ui.semantics.CollectionInfo import androidx.compose.ui.semantics.CollectionItemInfo import androidx.compose.ui.semantics.collectionInfo Loading @@ -34,6 +33,8 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.util.fastMapIndexed import kotlin.math.max /** Loading Loading @@ -65,7 +66,11 @@ fun HorizontalSpannedGrid( spans: List<Int>, modifier: Modifier = Modifier, keys: (spanIndex: Int) -> Any = { it }, composables: @Composable BoxScope.(spanIndex: Int) -> Unit, composables: @Composable BoxScope.( spanIndex: Int, row: Int, isFirstInColumn: Boolean, isLastInColumn: Boolean, ) -> Unit, ) { SpannedGrid( primarySpaces = rows, Loading @@ -80,7 +85,7 @@ fun HorizontalSpannedGrid( } /** * Horizontal (non lazy) grid that supports [spans] for its elements. * Vertical (non lazy) grid that supports [spans] for its elements. * * The elements will be laid down horizontally first, and then by rows. So assuming LTR layout, it * will be (for a span list `[2, 1, 2, 1, 1, 1, 1, 1]` and 4 columns): Loading @@ -107,7 +112,9 @@ fun VerticalSpannedGrid( spans: List<Int>, modifier: Modifier = Modifier, keys: (spanIndex: Int) -> Any = { it }, composables: @Composable BoxScope.(spanIndex: Int) -> Unit, composables: @Composable BoxScope.(spanIndex: Int, column: Int, isFirstInRow: Boolean, isLastInRow: Boolean) -> Unit, ) { SpannedGrid( primarySpaces = columns, Loading @@ -130,7 +137,9 @@ private fun SpannedGrid( isVertical: Boolean, modifier: Modifier = Modifier, keys: (spanIndex: Int) -> Any = { it }, composables: @Composable BoxScope.(spanIndex: Int) -> Unit, composables: @Composable BoxScope.(spanIndex: Int, secondaryAxis: Int, isFirst: Boolean, isLast: Boolean) -> Unit, ) { val crossAxisArrangement = Arrangement.spacedBy(crossAxisSpacing) spans.forEachIndexed { index, span -> Loading @@ -139,7 +148,6 @@ private fun SpannedGrid( "expected rance of [1, $primarySpaces]" } } if (isVertical) { check(crossAxisSpacing >= 0.dp) { "Negative columnSpacing $crossAxisSpacing" } check(mainAxisSpacing >= 0.dp) { "Negative rowSpacing $mainAxisSpacing" } Loading @@ -147,29 +155,30 @@ private fun SpannedGrid( check(mainAxisSpacing >= 0.dp) { "Negative columnSpacing $mainAxisSpacing" } check(crossAxisSpacing >= 0.dp) { "Negative rowSpacing $crossAxisSpacing" } } val totalMainAxisGroups: Int = // List of primary axis index to secondary axis index // This is keyed to the size of the spans list for performance reasons as we don't expect the // spans value to change outside of edit mode. val positions = remember(spans.size) { Array(spans.size) { IntIntPair(0, 0) } } val totalMainAxisGroups = remember(primarySpaces, spans) { var currentAccumulated = 0 var groups = 1 spans.forEach { span -> if (currentAccumulated + span <= primarySpaces) { currentAccumulated += span } else { groups += 1 currentAccumulated = span var mainAxisGroup = 0 var currentSlot = 0 spans.fastForEachIndexed { index, span -> if (currentSlot + span > primarySpaces) { currentSlot = 0 mainAxisGroup += 1 } positions[index] = IntIntPair(mainAxisGroup, currentSlot) currentSlot += span } groups mainAxisGroup + 1 } val slotPositionsAndSizesCache = remember { object { var sizes = IntArray(0) var positions = IntArray(0) } } Layout( { (0 until spans.size).map { spanIndex -> Loading @@ -184,7 +193,13 @@ private fun SpannedGrid( } } ) { composables(spanIndex) val position = positions[spanIndex] composables( spanIndex, position.second, position.second == 0, positions.getOrNull(spanIndex + 1)?.first != position.first, ) } } } Loading @@ -205,7 +220,6 @@ private fun SpannedGrid( slotPositionsAndSizesCache.sizes, ) val cellSizesInCrossAxis = slotPositionsAndSizesCache.sizes // with is needed because of the double receiver (Density, Arrangement). with(crossAxisArrangement) { arrange( Loading @@ -216,68 +230,73 @@ private fun SpannedGrid( ) } val startPositions = slotPositionsAndSizesCache.positions val mainAxisSpacingPx = mainAxisSpacing.roundToPx() val mainAxisTotalGaps = (totalMainAxisGroups - 1) * mainAxisSpacingPx val mainAxisSize = if (isVertical) constraints.maxHeight else constraints.maxWidth val mainAxisMaxSize = if (isVertical) constraints.maxHeight else constraints.maxWidth val mainAxisElementConstraint = if (mainAxisSize == Constraints.Infinity) { if (mainAxisMaxSize == Constraints.Infinity) { Constraints.Infinity } else { max(0, (mainAxisSize - mainAxisTotalGaps) / totalMainAxisGroups) max(0, (mainAxisMaxSize - mainAxisTotalGaps) / totalMainAxisGroups) } val mainAxisSizes = IntArray(totalMainAxisGroups) { 0 } var currentSlot = 0 var mainAxisGroup = 0 var mainAxisTotalSize = mainAxisTotalGaps var currentMainAxis = 0 var currentMainAxisMax = 0 val placeables = measurables.mapIndexed { index, measurable -> measurables.fastMapIndexed { index, measurable -> val span = spans[index] if (currentSlot + span > primarySpaces) { currentSlot = 0 mainAxisGroup += 1 } val position = positions[index] val crossAxisConstraint = calculateWidth(cellSizesInCrossAxis, startPositions, currentSlot, span) PlaceResult( measurable.measure( makeConstraint( isVertical, mainAxisElementConstraint, crossAxisConstraint, ) ), currentSlot, mainAxisGroup, calculateWidth(cellSizesInCrossAxis, startPositions, position.second, span) measurable .measure( makeConstraint(isVertical, mainAxisElementConstraint, crossAxisConstraint) ) .also { currentSlot += span mainAxisSizes[mainAxisGroup] = max( mainAxisSizes[mainAxisGroup], if (isVertical) it.placeable.height else it.placeable.width, ) val placeableSize = if (isVertical) it.height else it.width if (position.first != currentMainAxis) { // New row -- Add the max size to the total and reset the max mainAxisTotalSize += currentMainAxisMax currentMainAxisMax = placeableSize currentMainAxis = position.first } else { currentMainAxisMax = max(currentMainAxisMax, placeableSize) } } } mainAxisTotalSize += currentMainAxisMax val mainAxisTotalSize = mainAxisTotalGaps + mainAxisSizes.sum() val mainAxisStartingPoints = mainAxisSizes.runningFold(0) { acc, value -> acc + value + mainAxisSpacingPx } val height = if (isVertical) mainAxisTotalSize else crossAxisSize val width = if (isVertical) crossAxisSize else mainAxisTotalSize layout(width, height) { placeables.forEach { (placeable, slot, mainAxisGroup) -> var previousMainAxis = 0 var currentMainAxisPosition = 0 var currentMainAxisMax = 0 placeables.forEachIndexed { index, placeable -> val slot = positions[index].second val mainAxisSize = if (isVertical) placeable.height else placeable.width if (positions[index].first != previousMainAxis) { // Move up a row + padding currentMainAxisPosition += currentMainAxisMax + mainAxisSpacingPx currentMainAxisMax = mainAxisSize previousMainAxis = positions[index].first } else { currentMainAxisMax = max(currentMainAxisMax, mainAxisSize) } val x = if (isVertical) { startPositions[slot] } else { mainAxisStartingPoints[mainAxisGroup] currentMainAxisPosition } val y = if (isVertical) { mainAxisStartingPoints[mainAxisGroup] currentMainAxisPosition } else { startPositions[slot] } Loading Loading @@ -321,9 +340,3 @@ private fun calculateCellsCrossAxisSize( outArray[index] = slotSize + if (index < remainingPixels) 1 else 0 } } private data class PlaceResult( val placeable: Placeable, val slotIndex: Int, val mainAxisGroup: Int, )
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt +8 −3 Original line number Diff line number Diff line Loading @@ -35,11 +35,16 @@ fun List<BounceableTileViewModel>.bounceableInfo( index: Int, column: Int, columns: Int, isFirstInRow: Boolean, isLastInRow: Boolean, ): BounceableInfo { // Only look for neighbor bounceables if they are on the same row // A tile may be the last in the row without being on the last column val onLastColumn = sizedTile.onLastColumn(column, columns) val previousTile = getOrNull(index - 1)?.takeIf { column != 0 } val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn } // Only look for neighbor bounceables if they are on the same row val previousTile = getOrNull(index - 1)?.takeIf { !isFirstInRow } val nextTile = getOrNull(index + 1)?.takeIf { !isLastInRow } return BounceableInfo(this[index], previousTile, nextTile, !onLastColumn) } Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +10 −5 Original line number Diff line number Diff line Loading @@ -57,7 +57,6 @@ fun ContentScope.QuickQuickSettings( onDispose { tiles.forEach { it.stopListening(token) } } } val columns = viewModel.columns var cellIndex = 0 Box(modifier = modifier) { GridAnchor() VerticalSpannedGrid( Loading @@ -67,17 +66,23 @@ fun ContentScope.QuickQuickSettings( spans = spans, modifier = Modifier.sysuiResTag("qqs_tile_layout"), keys = { sizedTiles[it].tile.spec }, ) { spanIndex -> ) { spanIndex, column, isFirstInColumn, isLastInColumn -> val it = sizedTiles[spanIndex] val column = cellIndex % columns cellIndex += it.width Element(it.tile.spec.toElementKey(spanIndex), Modifier) { Tile( tile = it.tile, iconOnly = it.isIcon, squishiness = { squishiness }, coroutineScope = scope, bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), bounceableInfo = bounceables.bounceableInfo( it, index = spanIndex, column = column, columns = columns, isFirstInRow = isFirstInColumn, isLastInRow = isLastInColumn, ), tileHapticsViewModelFactoryProvider = viewModel.tileHapticsViewModelFactoryProvider, // There should be no QuickQuickSettings when the details view is enabled. Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +11 −6 Original line number Diff line number Diff line Loading @@ -85,8 +85,6 @@ constructor( remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle() val scope = rememberCoroutineScope() var cellIndex = 0 val spans by remember(sizedTiles) { derivedStateOf { sizedTiles.fastMap { it.width } } } VerticalSpannedGrid( Loading @@ -95,10 +93,9 @@ constructor( rowSpacing = dimensionResource(R.dimen.qs_tile_margin_vertical), spans = spans, keys = { sizedTiles[it].tile.spec }, ) { spanIndex -> ) { spanIndex, column, isFirstInColumn, isLastInColumn -> val it = sizedTiles[spanIndex] val column = cellIndex % columns cellIndex += it.width Element(it.tile.spec.toElementKey(spanIndex), Modifier) { Tile( tile = it.tile, Loading @@ -106,7 +103,15 @@ constructor( squishiness = { squishiness }, tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider, coroutineScope = scope, bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), bounceableInfo = bounceables.bounceableInfo( it, index = spanIndex, column = column, columns = columns, isFirstInRow = isFirstInColumn, isLastInRow = isLastInColumn, ), detailsViewModel = detailsViewModel, ) } Loading