diff options
| author | 2024-10-09 17:14:44 +0000 | |
|---|---|---|
| committer | 2024-10-18 17:53:36 +0000 | |
| commit | 4f94f0d5acf22c989ae7d21d23272527e2543209 (patch) | |
| tree | 0910300131a1ffdf2bb3912258e1773839457f80 | |
| parent | 19131b50f3875935a5cc8d163075cab7cb95f5a7 (diff) | |
Only allow vertical resizing if it's supported by the widget
Bug: b/368056266
Test: atest ResizeableItemFrameViewModelTest
Flag: com.android.systemui.communal_widget_resizing
Change-Id: I6a72e68a69af744e2043d75b9cab578770d57777
4 files changed, 273 insertions, 107 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 7fb4c537641b..f96217a1331b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.ui.compose +import android.appwidget.AppWidgetProviderInfo import android.content.Context import android.content.res.Configuration import android.graphics.drawable.Icon @@ -184,6 +185,9 @@ import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.res.R import com.android.systemui.statusbar.phone.SystemUIDialogFactory +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToInt import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -646,10 +650,11 @@ private fun ObserveNewWidgetAddedEffect( private fun ResizableItemFrameWrapper( key: String, gridState: LazyGridState, - minItemSpan: Int, gridContentPadding: PaddingValues, verticalArrangement: Arrangement.Vertical, enabled: Boolean, + minHeightPx: Int, + maxHeightPx: Int, modifier: Modifier = Modifier, alpha: () -> Float = { 1f }, onResize: (info: ResizeInfo) -> Unit = {}, @@ -661,19 +666,46 @@ private fun ResizableItemFrameWrapper( ResizableItemFrame( key = key, gridState = gridState, - minItemSpan = minItemSpan, gridContentPadding = gridContentPadding, verticalArrangement = verticalArrangement, enabled = enabled, alpha = alpha, modifier = modifier, onResize = onResize, + minHeightPx = minHeightPx, + maxHeightPx = maxHeightPx, + resizeMultiple = CommunalContentSize.HALF.span, ) { content(Modifier) } } } +@Composable +fun calculateWidgetSize(item: CommunalContentModel, isResizable: Boolean): WidgetSizeInfo { + val density = LocalDensity.current + + return if (isResizable && item is CommunalContentModel.WidgetContent.Widget) { + with(density) { + val minHeightPx = + (min(item.providerInfo.minResizeHeight, item.providerInfo.minHeight) + .coerceAtLeast(CommunalContentSize.HALF.dp().toPx().roundToInt())) + + val maxHeightPx = + (if (item.providerInfo.maxResizeHeight > 0) { + max(item.providerInfo.maxResizeHeight, item.providerInfo.minHeight) + } else { + Int.MAX_VALUE + }) + .coerceIn(minHeightPx, CommunalContentSize.FULL.dp().toPx().roundToInt()) + + WidgetSizeInfo(minHeightPx, maxHeightPx) + } + } else { + WidgetSizeInfo(0, Int.MAX_VALUE) + } +} + @OptIn(ExperimentalFoundationApi::class) @Composable private fun BoxScope.CommunalHubLazyGrid( @@ -748,6 +780,12 @@ private fun BoxScope.CommunalHubLazyGrid( val size = SizeF(Dimensions.CardWidth.value, item.size.dp().value) val selected = item.key == selectedKey.value val dpSize = DpSize(size.width.dp, size.height.dp) + val isResizable = + if (item is CommunalContentModel.WidgetContent.Widget) { + item.providerInfo.resizeMode and AppWidgetProviderInfo.RESIZE_VERTICAL != 0 + } else { + false + } if (viewModel.isEditMode && dragDropState != null) { val outlineAlpha by @@ -756,10 +794,10 @@ private fun BoxScope.CommunalHubLazyGrid( animationSpec = spring(stiffness = Spring.StiffnessMediumLow), label = "Widget resizing outline alpha", ) + val widgetSizeInfo = calculateWidgetSize(item, isResizable) ResizableItemFrameWrapper( key = item.key, gridState = gridState, - minItemSpan = CommunalContentSize.HALF.span, gridContentPadding = contentPadding, verticalArrangement = itemArrangement, enabled = selected, @@ -773,6 +811,8 @@ private fun BoxScope.CommunalHubLazyGrid( ) }, onResize = { resizeInfo -> contentListState.resize(index, resizeInfo) }, + minHeightPx = widgetSizeInfo.minHeightPx, + maxHeightPx = widgetSizeInfo.maxHeightPx, ) { modifier -> DraggableItem( modifier = modifier, @@ -1657,6 +1697,8 @@ class Dimensions(val context: Context, val config: Configuration) { } } +data class WidgetSizeInfo(val minHeightPx: Int, val maxHeightPx: Int) + private object Colors { val DisabledColorFilter by lazy { disabledColorMatrix() } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt index ef62eb726e2b..9ffc1a626345 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt @@ -60,9 +60,11 @@ private fun UpdateGridLayoutInfo( viewModel: ResizeableItemFrameViewModel, key: String, gridState: LazyGridState, - minItemSpan: Int, gridContentPadding: PaddingValues, verticalArrangement: Arrangement.Vertical, + minHeightPx: Int, + maxHeightPx: Int, + resizeMultiple: Int, ) { val density = LocalDensity.current LaunchedEffect( @@ -70,9 +72,11 @@ private fun UpdateGridLayoutInfo( viewModel, key, gridState, - minItemSpan, gridContentPadding, verticalArrangement, + minHeightPx, + maxHeightPx, + resizeMultiple, ) { val verticalItemSpacingPx = with(density) { verticalArrangement.spacing.toPx() } val verticalContentPaddingPx = @@ -92,13 +96,15 @@ private fun UpdateGridLayoutInfo( ) .collectLatest { (maxItemSpan, viewportHeightPx, itemInfo) -> viewModel.setGridLayoutInfo( - verticalItemSpacingPx, - verticalContentPaddingPx, - viewportHeightPx, - maxItemSpan, - minItemSpan, - itemInfo?.row, - itemInfo?.span, + verticalItemSpacingPx = verticalItemSpacingPx, + currentRow = itemInfo?.row, + maxHeightPx = maxHeightPx, + minHeightPx = minHeightPx, + currentSpan = itemInfo?.span, + resizeMultiple = resizeMultiple, + totalSpans = maxItemSpan, + viewportHeightPx = viewportHeightPx, + verticalContentPaddingPx = verticalContentPaddingPx, ) } } @@ -163,7 +169,6 @@ private fun BoxScope.DragHandle( fun ResizableItemFrame( key: String, gridState: LazyGridState, - minItemSpan: Int, gridContentPadding: PaddingValues, verticalArrangement: Arrangement.Vertical, modifier: Modifier = Modifier, @@ -172,6 +177,9 @@ fun ResizableItemFrame( outlineColor: Color = LocalAndroidColorScheme.current.primary, cornerRadius: Dp = 37.dp, strokeWidth: Dp = 3.dp, + minHeightPx: Int = 0, + maxHeightPx: Int = Int.MAX_VALUE, + resizeMultiple: Int = 1, alpha: () -> Float = { 1f }, onResize: (info: ResizeInfo) -> Unit = {}, content: @Composable () -> Unit, @@ -230,12 +238,14 @@ fun ResizableItemFrame( } UpdateGridLayoutInfo( - viewModel, - key, - gridState, - minItemSpan, - gridContentPadding, - verticalArrangement, + viewModel = viewModel, + key = key, + gridState = gridState, + gridContentPadding = gridContentPadding, + verticalArrangement = verticalArrangement, + minHeightPx = minHeightPx, + maxHeightPx = maxHeightPx, + resizeMultiple = resizeMultiple, ) LaunchedEffect(viewModel) { viewModel.resizeInfo.collectLatest { info -> onResizeUpdated(info) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt index f0d88ab41ad4..55d68e67d461 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt @@ -26,7 +26,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat -import kotlin.time.Duration import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent @@ -53,10 +52,12 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { verticalItemSpacingPx = 10f, verticalContentPaddingPx = verticalContentPaddingPx, viewportHeightPx = viewportHeightPx, - maxItemSpan = 1, - minItemSpan = 1, - currentSpan = 1, currentRow = 0, + currentSpan = 1, + maxHeightPx = Int.MAX_VALUE, + minHeightPx = 0, + resizeMultiple = 1, + totalSpans = 1, ) @Before @@ -79,7 +80,7 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { @Test fun testSingleSpanGrid() = - testScope.runTest(timeout = Duration.INFINITE) { + testScope.runTest { updateGridLayout(singleSpanGrid) val topState = underTest.topDragState @@ -98,8 +99,7 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { @Test fun testTwoSpanGrid_elementInFirstRow_sizeSingleSpan() = testScope.runTest { - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2)) - + updateGridLayout(singleSpanGrid.copy(currentRow = 0, totalSpans = 2)) val topState = underTest.topDragState assertThat(topState.currentValue).isEqualTo(0) assertThat(topState.anchors.toList()).containsExactly(0 to 0f) @@ -116,8 +116,7 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { @Test fun testTwoSpanGrid_elementInSecondRow_sizeSingleSpan() = testScope.runTest { - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentRow = 1)) - + updateGridLayout(singleSpanGrid.copy(currentRow = 1, totalSpans = 2)) val topState = underTest.topDragState assertThat(topState.currentValue).isEqualTo(0) assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f) @@ -134,15 +133,17 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { @Test fun testTwoSpanGrid_elementInFirstRow_sizeTwoSpan() = testScope.runTest { - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentSpan = 2)) + val adjustedGridLayout = singleSpanGrid.copy(currentSpan = 2, totalSpans = 2) + + updateGridLayout(adjustedGridLayout) val topState = underTest.topDragState - assertThat(topState.currentValue).isEqualTo(0) assertThat(topState.anchors.toList()).containsExactly(0 to 0f) + assertThat(topState.currentValue).isEqualTo(0) val bottomState = underTest.bottomDragState + assertThat(bottomState.anchors.toList()).containsExactly(-1 to -45f, 0 to 0f) assertThat(bottomState.currentValue).isEqualTo(0) - assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f) } /** @@ -151,7 +152,10 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { @Test fun testThreeSpanGrid_elementInMiddleRow_sizeOneSpan() = testScope.runTest { - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3, currentRow = 1)) + val adjustedGridLayout = + singleSpanGrid.copy(currentRow = 1, currentSpan = 1, totalSpans = 3) + + updateGridLayout(adjustedGridLayout) val topState = underTest.topDragState assertThat(topState.currentValue).isEqualTo(0) @@ -165,7 +169,10 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { @Test fun testThreeSpanGrid_elementInTopRow_sizeOneSpan() = testScope.runTest { - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3)) + val adjustedGridLayout = + singleSpanGrid.copy(currentRow = 0, currentSpan = 1, totalSpans = 3) + + updateGridLayout(adjustedGridLayout) val topState = underTest.topDragState assertThat(topState.currentValue).isEqualTo(0) @@ -177,16 +184,17 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { } @Test - fun testSixSpanGrid_minSpanThree_itemInThirdRow_sizeThreeSpans() = + fun testSixSpanGrid_minSpanThree_itemInFourthRow_sizeThreeSpans() = testScope.runTest { - updateGridLayout( + val adjustedGridLayout = singleSpanGrid.copy( - maxItemSpan = 6, currentRow = 3, currentSpan = 3, - minItemSpan = 3, + resizeMultiple = 3, + totalSpans = 6, ) - ) + + updateGridLayout(adjustedGridLayout) val topState = underTest.topDragState assertThat(topState.currentValue).isEqualTo(0) @@ -200,7 +208,14 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { @Test fun testTwoSpanGrid_elementMovesFromFirstRowToSecondRow() = testScope.runTest { - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2)) + val firstRowLayout = + singleSpanGrid.copy( + currentRow = 0, + currentSpan = 1, + resizeMultiple = 1, + totalSpans = 2, + ) + updateGridLayout(firstRowLayout) val topState = underTest.topDragState val bottomState = underTest.bottomDragState @@ -208,7 +223,8 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { assertThat(topState.anchors.toList()).containsExactly(0 to 0f) assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 45f) - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentRow = 1)) + val secondRowLayout = firstRowLayout.copy(currentRow = 1) + updateGridLayout(secondRowLayout) assertThat(topState.anchors.toList()).containsExactly(0 to 0f, -1 to -45f) assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f) @@ -217,19 +233,21 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { @Test fun testTwoSpanGrid_expandElementFromBottom() = runTestWithSnapshots { val resizeInfo by collectLastValue(underTest.resizeInfo) - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2)) - assertThat(resizeInfo).isNull() + val adjustedGridLayout = singleSpanGrid.copy(resizeMultiple = 1, totalSpans = 2) + + updateGridLayout(adjustedGridLayout) + underTest.bottomDragState.anchoredDrag { dragTo(45f) } + assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.BOTTOM)) } @Test fun testThreeSpanGrid_expandMiddleElementUpwards() = runTestWithSnapshots { val resizeInfo by collectLastValue(underTest.resizeInfo) - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3, currentRow = 1)) + updateGridLayout(singleSpanGrid.copy(currentRow = 1, totalSpans = 3)) - assertThat(resizeInfo).isNull() underTest.topDragState.anchoredDrag { dragTo(-30f) } assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.TOP)) } @@ -237,7 +255,7 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { @Test fun testThreeSpanGrid_expandTopElementDownBy2Spans() = runTestWithSnapshots { val resizeInfo by collectLastValue(underTest.resizeInfo) - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 3)) + updateGridLayout(singleSpanGrid.copy(totalSpans = 3)) assertThat(resizeInfo).isNull() underTest.bottomDragState.anchoredDrag { dragTo(60f) } @@ -247,7 +265,7 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { @Test fun testTwoSpanGrid_shrinkElementFromBottom() = runTestWithSnapshots { val resizeInfo by collectLastValue(underTest.resizeInfo) - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, currentSpan = 2)) + updateGridLayout(singleSpanGrid.copy(totalSpans = 2, currentSpan = 2)) assertThat(resizeInfo).isNull() underTest.bottomDragState.anchoredDrag { dragTo(-45f) } @@ -257,7 +275,7 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { @Test fun testRowInfoBecomesNull_revertsBackToDefault() = testScope.runTest { - val gridLayout = singleSpanGrid.copy(maxItemSpan = 3, currentRow = 1) + val gridLayout = singleSpanGrid.copy(currentRow = 1, resizeMultiple = 1, totalSpans = 3) updateGridLayout(gridLayout) val topState = underTest.topDragState @@ -266,44 +284,113 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { val bottomState = underTest.bottomDragState assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, 1 to 30f) + // Set currentRow to null to simulate the row info becoming null updateGridLayout(gridLayout.copy(currentRow = null)) assertThat(topState.anchors.toList()).containsExactly(0 to 0f) assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f) } - @Test(expected = IllegalArgumentException::class) - fun testIllegalState_maxSpanSmallerThanMinSpan() = + @Test + fun testEqualMaxAndMinHeight_cannotResize() = testScope.runTest { - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, minItemSpan = 3)) + val heightPx = 20 + updateGridLayout( + singleSpanGrid.copy(maxHeightPx = heightPx, minHeightPx = heightPx, totalSpans = 2) + ) + + val topState = underTest.topDragState + val bottomState = underTest.bottomDragState + + assertThat(topState.anchors.toList()).containsExactly(0 to 0f) + assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f) } - @Test(expected = IllegalArgumentException::class) - fun testIllegalState_minSpanOfZero() = + @Test + fun testMinHeightTwoRows_canExpandButNotShrink() = testScope.runTest { - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 2, minItemSpan = 0)) + val threeRowGrid = + singleSpanGrid.copy( + maxHeightPx = 80, + minHeightPx = 50, + totalSpans = 3, + currentSpan = 2, + currentRow = 0, + ) + + updateGridLayout(threeRowGrid) + + val topState = underTest.topDragState + val bottomState = underTest.bottomDragState + assertThat(topState.anchors.toList()).containsExactly(0 to 0f) + assertThat(bottomState.anchors.toList()).containsAtLeast(0 to 0f, 1 to 30f) } - @Test(expected = IllegalArgumentException::class) - fun testIllegalState_maxSpanOfZero() = + @Test + fun testMaxHeightTwoRows_canShrinkButNotExpand() = testScope.runTest { - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 0, minItemSpan = 0)) + val threeRowGrid = + singleSpanGrid.copy( + maxHeightPx = 50, + minHeightPx = 20, + totalSpans = 3, + currentSpan = 2, + currentRow = 0, + ) + + updateGridLayout(threeRowGrid) + + val topState = underTest.topDragState + val bottomState = underTest.bottomDragState + + assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, -1 to -30f) + + assertThat(topState.anchors.toList()).containsExactly(0 to 0f) + } + + @Test + fun testMinHeightEqualToAvailableSpan_cannotResize() = + testScope.runTest { + val twoRowGrid = + singleSpanGrid.copy( + minHeightPx = (viewportHeightPx - verticalContentPaddingPx.toInt()), + totalSpans = 2, + currentSpan = 2, + ) + + updateGridLayout(twoRowGrid) + + val topState = underTest.topDragState + val bottomState = underTest.bottomDragState + + assertThat(topState.anchors.toList()).containsExactly(0 to 0f) + assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f) } @Test(expected = IllegalArgumentException::class) - fun testIllegalState_currentRowNotMultipleOfMinSpan() = + fun testIllegalState_maxHeightLessThanMinHeight() = testScope.runTest { - updateGridLayout(singleSpanGrid.copy(maxItemSpan = 6, minItemSpan = 3, currentSpan = 2)) + updateGridLayout(singleSpanGrid.copy(maxHeightPx = 50, minHeightPx = 100)) } + @Test(expected = IllegalArgumentException::class) + fun testIllegalState_currentSpanExceedsTotalSpans() = + testScope.runTest { updateGridLayout(singleSpanGrid.copy(currentSpan = 3, totalSpans = 2)) } + + @Test(expected = IllegalArgumentException::class) + fun testIllegalState_resizeMultipleZeroOrNegative() = + testScope.runTest { updateGridLayout(singleSpanGrid.copy(resizeMultiple = 0)) } + private fun TestScope.updateGridLayout(gridLayout: GridLayout) { underTest.setGridLayoutInfo( - gridLayout.verticalItemSpacingPx, - gridLayout.verticalContentPaddingPx, - gridLayout.viewportHeightPx, - gridLayout.maxItemSpan, - gridLayout.minItemSpan, - gridLayout.currentRow, - gridLayout.currentSpan, + verticalItemSpacingPx = gridLayout.verticalItemSpacingPx, + currentRow = gridLayout.currentRow, + maxHeightPx = gridLayout.maxHeightPx, + minHeightPx = gridLayout.minHeightPx, + currentSpan = gridLayout.currentSpan, + resizeMultiple = gridLayout.resizeMultiple, + totalSpans = gridLayout.totalSpans, + viewportHeightPx = gridLayout.viewportHeightPx, + verticalContentPaddingPx = gridLayout.verticalContentPaddingPx, ) runCurrent() } @@ -332,9 +419,11 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() { val verticalItemSpacingPx: Float, val verticalContentPaddingPx: Float, val viewportHeightPx: Int, - val maxItemSpan: Int, - val minItemSpan: Int, val currentRow: Int?, val currentSpan: Int?, + val maxHeightPx: Int, + val minHeightPx: Int, + val resizeMultiple: Int, + val totalSpans: Int, ) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt index 87fcdd7b97ee..b4057c775341 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.systemui.communal.ui.viewmodel import androidx.compose.foundation.gestures.AnchoredDraggableState @@ -21,6 +20,10 @@ import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.runtime.snapshotFlow import com.android.app.tracing.coroutines.coroutineScope import com.android.systemui.lifecycle.ExclusiveActivatable +import kotlin.math.abs +import kotlin.math.ceil +import kotlin.math.floor +import kotlin.math.sign import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -50,14 +53,33 @@ data class ResizeInfo( } class ResizeableItemFrameViewModel : ExclusiveActivatable() { - private data class GridLayoutInfo( - val minSpan: Int, - val maxSpan: Int, - val heightPerSpanPx: Float, - val verticalItemSpacingPx: Float, + data class GridLayoutInfo( val currentRow: Int, val currentSpan: Int, - ) + val maxHeightPx: Int, + val minHeightPx: Int, + val resizeMultiple: Int, + val totalSpans: Int, + private val heightPerSpanPx: Float, + private val verticalItemSpacingPx: Float, + ) { + fun getPxOffsetForResize(spans: Int): Int = + (spans * (heightPerSpanPx + verticalItemSpacingPx)).toInt() + + private fun getSpansForPx(height: Int): Int = + ceil((height + verticalItemSpacingPx) / (heightPerSpanPx + verticalItemSpacingPx)) + .toInt() + .coerceIn(resizeMultiple, totalSpans) + + private fun roundDownToMultiple(spans: Int): Int = + floor(spans.toDouble() / resizeMultiple).toInt() * resizeMultiple + + val maxSpans: Int + get() = roundDownToMultiple(getSpansForPx(maxHeightPx)) + + val minSpans: Int + get() = roundDownToMultiple(getSpansForPx(minHeightPx)) + } /** * The layout information necessary in order to calculate the pixel offsets of the drag anchor @@ -84,37 +106,44 @@ class ResizeableItemFrameViewModel : ExclusiveActivatable() { */ fun setGridLayoutInfo( verticalItemSpacingPx: Float, - verticalContentPaddingPx: Float, - viewportHeightPx: Int, - maxItemSpan: Int, - minItemSpan: Int, currentRow: Int?, + maxHeightPx: Int, + minHeightPx: Int, currentSpan: Int?, + resizeMultiple: Int, + totalSpans: Int, + viewportHeightPx: Int, + verticalContentPaddingPx: Float, ) { - if (currentSpan == null || currentRow == null) { + if (currentRow == null || currentSpan == null) { gridLayoutInfo.value = null return } - require(maxItemSpan >= minItemSpan) { - "Maximum item span of $maxItemSpan cannot be less than the minimum span of $minItemSpan" + require(maxHeightPx >= minHeightPx) { + "Maximum item span of $maxHeightPx cannot be less than the minimum span of $minHeightPx" } - require(minItemSpan in 1..maxItemSpan) { - "Minimum span must be between 1 and $maxItemSpan, but was $minItemSpan" + + require(currentSpan <= totalSpans) { + "Current span ($currentSpan) cannot exceed the total number of spans ($totalSpans)" } - require(currentSpan % minItemSpan == 0) { - "Current span of $currentSpan is not a multiple of the minimum span of $minItemSpan" + + require(resizeMultiple > 0) { + "Resize multiple ($resizeMultiple) must be a positive integer" } val availableHeight = viewportHeightPx - verticalContentPaddingPx - val totalSpacing = verticalItemSpacingPx * ((maxItemSpan / minItemSpan) - 1) - val heightPerSpanPx = (availableHeight - totalSpacing) / maxItemSpan + val heightPerSpanPx = + (availableHeight - (totalSpans - 1) * verticalItemSpacingPx) / totalSpans + gridLayoutInfo.value = GridLayoutInfo( - minSpan = minItemSpan, - maxSpan = maxItemSpan, heightPerSpanPx = heightPerSpanPx, verticalItemSpacingPx = verticalItemSpacingPx, currentRow = currentRow, currentSpan = currentSpan, + maxHeightPx = maxHeightPx.coerceAtMost(availableHeight.toInt()), + minHeightPx = minHeightPx, + resizeMultiple = resizeMultiple, + totalSpans = totalSpans, ) } @@ -123,50 +152,46 @@ class ResizeableItemFrameViewModel : ExclusiveActivatable() { layoutInfo: GridLayoutInfo?, ): DraggableAnchors<Int> { - if (layoutInfo == null || !isDragAllowed(handle, layoutInfo)) { + if (layoutInfo == null || (!isDragAllowed(handle, layoutInfo))) { return DraggableAnchors { 0 at 0f } } - - val ( - minItemSpan, - maxItemSpan, - heightPerSpanPx, - verticalSpacingPx, - currentRow, - currentSpan, - ) = layoutInfo + val currentRow = layoutInfo.currentRow + val currentSpan = layoutInfo.currentSpan + val minItemSpan = layoutInfo.minSpans + val maxItemSpan = layoutInfo.maxSpans + val totalSpans = layoutInfo.totalSpans // The maximum row this handle can be dragged to. val maxRow = if (handle == DragHandle.TOP) { (currentRow + currentSpan - minItemSpan).coerceAtLeast(0) } else { - maxItemSpan + (currentRow + maxItemSpan).coerceAtMost(totalSpans) } // The minimum row this handle can be dragged to. val minRow = if (handle == DragHandle.TOP) { - 0 + (currentRow + currentSpan - maxItemSpan).coerceAtLeast(0) } else { - (currentRow + minItemSpan).coerceAtMost(maxItemSpan) + (currentRow + minItemSpan).coerceAtMost(totalSpans) } // The current row position of this handle val currentPosition = if (handle == DragHandle.TOP) currentRow else currentRow + currentSpan return DraggableAnchors { - for (targetRow in minRow..maxRow step minItemSpan) { + for (targetRow in minRow..maxRow step layoutInfo.resizeMultiple) { val diff = targetRow - currentPosition - val spacing = diff / minItemSpan * verticalSpacingPx - diff at diff * heightPerSpanPx + spacing + val pixelOffset = (layoutInfo.getPxOffsetForResize(abs(diff)) * diff.sign).toFloat() + diff at pixelOffset } } } private fun isDragAllowed(handle: DragHandle, layoutInfo: GridLayoutInfo): Boolean { - val minItemSpan = layoutInfo.minSpan - val maxItemSpan = layoutInfo.maxSpan + val minItemSpan = layoutInfo.minSpans + val maxItemSpan = layoutInfo.maxSpans val currentRow = layoutInfo.currentRow val currentSpan = layoutInfo.currentSpan val atMinSize = currentSpan == minItemSpan |