summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Prince <princedonkor@google.com> 2024-10-09 17:14:44 +0000
committer Prince <princedonkor@google.com> 2024-10-18 17:53:36 +0000
commit4f94f0d5acf22c989ae7d21d23272527e2543209 (patch)
tree0910300131a1ffdf2bb3912258e1773839457f80
parent19131b50f3875935a5cc8d163075cab7cb95f5a7 (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
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt48
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt185
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt105
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