summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt46
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt100
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt67
5 files changed, 218 insertions, 6 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 476cced6a03d..e329aaee9a06 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
@@ -181,9 +181,11 @@ import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.ResizeInfo
+import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel
import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp
import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
import com.android.systemui.communal.widgets.WidgetConfigurator
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
import kotlin.math.max
@@ -665,6 +667,7 @@ private fun ResizableItemFrameWrapper(
maxHeightPx: Int,
modifier: Modifier = Modifier,
alpha: () -> Float = { 1f },
+ viewModel: ResizeableItemFrameViewModel,
onResize: (info: ResizeInfo) -> Unit = {},
content: @Composable (modifier: Modifier) -> Unit,
) {
@@ -680,6 +683,7 @@ private fun ResizableItemFrameWrapper(
enabled = enabled,
alpha = alpha,
modifier = modifier,
+ viewModel = viewModel,
onResize = onResize,
minHeightPx = minHeightPx,
maxHeightPx = maxHeightPx,
@@ -796,6 +800,14 @@ private fun BoxScope.CommunalHubLazyGrid(
false
}
+ val resizeableItemFrameViewModel =
+ rememberViewModel(
+ key = item.size.span,
+ traceName = "ResizeableItemFrame.viewModel.$index",
+ ) {
+ ResizeableItemFrameViewModel()
+ }
+
if (viewModel.isEditMode && dragDropState != null) {
val isItemDragging = dragDropState.draggingItemKey == item.key
val outlineAlpha by
@@ -821,6 +833,7 @@ private fun BoxScope.CommunalHubLazyGrid(
)
}
.thenIf(isItemDragging) { Modifier.zIndex(1f) },
+ viewModel = resizeableItemFrameViewModel,
onResize = { resizeInfo -> contentListState.resize(index, resizeInfo) },
minHeightPx = widgetSizeInfo.minHeightPx,
maxHeightPx = widgetSizeInfo.maxHeightPx,
@@ -843,6 +856,7 @@ private fun BoxScope.CommunalHubLazyGrid(
contentListState = contentListState,
interactionHandler = interactionHandler,
widgetSection = widgetSection,
+ resizeableItemFrameViewModel = resizeableItemFrameViewModel,
)
}
}
@@ -857,6 +871,7 @@ private fun BoxScope.CommunalHubLazyGrid(
contentListState = contentListState,
interactionHandler = interactionHandler,
widgetSection = widgetSection,
+ resizeableItemFrameViewModel = resizeableItemFrameViewModel,
)
}
}
@@ -1080,6 +1095,7 @@ private fun CommunalContent(
contentListState: ContentListState,
interactionHandler: RemoteViews.InteractionHandler?,
widgetSection: CommunalAppWidgetSection,
+ resizeableItemFrameViewModel: ResizeableItemFrameViewModel,
) {
when (model) {
is CommunalContentModel.WidgetContent.Widget ->
@@ -1093,6 +1109,7 @@ private fun CommunalContent(
index,
contentListState,
widgetSection,
+ resizeableItemFrameViewModel,
)
is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
is CommunalContentModel.WidgetContent.DisabledWidget ->
@@ -1223,7 +1240,9 @@ private fun WidgetContent(
index: Int,
contentListState: ContentListState,
widgetSection: CommunalAppWidgetSection,
+ resizeableItemFrameViewModel: ResizeableItemFrameViewModel,
) {
+ val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val accessibilityLabel =
remember(model, context) {
@@ -1234,6 +1253,10 @@ private fun WidgetContent(
val placeWidgetActionLabel = stringResource(R.string.accessibility_action_label_place_widget)
val unselectWidgetActionLabel =
stringResource(R.string.accessibility_action_label_unselect_widget)
+
+ val shrinkWidgetLabel = stringResource(R.string.accessibility_action_label_shrink_widget)
+ val expandWidgetLabel = stringResource(R.string.accessibility_action_label_expand_widget)
+
val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle()
val selectedIndex =
selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } }
@@ -1292,6 +1315,29 @@ private fun WidgetContent(
true
}
val actions = mutableListOf(deleteAction)
+
+ if (communalWidgetResizing() && resizeableItemFrameViewModel.canShrink()) {
+ actions.add(
+ CustomAccessibilityAction(shrinkWidgetLabel) {
+ coroutineScope.launch {
+ resizeableItemFrameViewModel.shrinkToNextAnchor()
+ }
+ true
+ }
+ )
+ }
+
+ if (communalWidgetResizing() && resizeableItemFrameViewModel.canExpand()) {
+ actions.add(
+ CustomAccessibilityAction(expandWidgetLabel) {
+ coroutineScope.launch {
+ resizeableItemFrameViewModel.expandToNextAnchor()
+ }
+ true
+ }
+ )
+ }
+
if (selectedIndex != null && selectedIndex != index) {
actions.add(
CustomAccessibilityAction(placeWidgetActionLabel) {
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 521330f60fa8..8e85432f4f36 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
@@ -56,7 +56,6 @@ import com.android.compose.modifiers.thenIf
import com.android.systemui.communal.ui.viewmodel.DragHandle
import com.android.systemui.communal.ui.viewmodel.ResizeInfo
import com.android.systemui.communal.ui.viewmodel.ResizeableItemFrameViewModel
-import com.android.systemui.lifecycle.rememberViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
@@ -192,16 +191,12 @@ fun ResizableItemFrame(
maxHeightPx: Int = Int.MAX_VALUE,
resizeMultiple: Int = 1,
alpha: () -> Float = { 1f },
+ viewModel: ResizeableItemFrameViewModel,
onResize: (info: ResizeInfo) -> Unit = {},
content: @Composable () -> Unit,
) {
val brush = SolidColor(outlineColor)
val onResizeUpdated by rememberUpdatedState(onResize)
- val viewModel =
- rememberViewModel(key = currentSpan, traceName = "ResizeableItemFrame.viewModel") {
- ResizeableItemFrameViewModel()
- }
-
val dragHandleHeight = verticalArrangement.spacing - outlinePadding * 2
val isDragging by
remember(viewModel) {
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 22b114c632cd..0269577af789 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
@@ -366,6 +366,106 @@ class ResizeableItemFrameViewModelTest : SysuiTestCase() {
assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f)
}
+ @Test
+ fun testCanExpand_atTopPosition_withMultipleAnchors_returnsTrue() =
+ testScope.runTest {
+ val twoRowGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 0)
+
+ updateGridLayout(twoRowGrid)
+ assertThat(underTest.canExpand()).isTrue()
+ assertThat(underTest.bottomDragState.anchors.toList())
+ .containsAtLeast(0 to 0f, 1 to 45f)
+ }
+
+ @Test
+ fun testCanExpand_atTopPosition_withSingleAnchors_returnsFalse() =
+ testScope.runTest {
+ val oneRowGrid = singleSpanGrid.copy(totalSpans = 1, currentSpan = 1, currentRow = 0)
+ updateGridLayout(oneRowGrid)
+ assertThat(underTest.canExpand()).isFalse()
+ }
+
+ @Test
+ fun testCanExpand_atBottomPosition_withMultipleAnchors_returnsTrue() =
+ testScope.runTest {
+ val twoRowGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 1)
+ updateGridLayout(twoRowGrid)
+ assertThat(underTest.canExpand()).isTrue()
+ assertThat(underTest.topDragState.anchors.toList()).containsAtLeast(0 to 0f, -1 to -45f)
+ }
+
+ @Test
+ fun testCanShrink_atMinimumHeight_returnsFalse() =
+ testScope.runTest {
+ val oneRowGrid = singleSpanGrid.copy(totalSpans = 1, currentSpan = 1, currentRow = 0)
+ updateGridLayout(oneRowGrid)
+ assertThat(underTest.canShrink()).isFalse()
+ }
+
+ @Test
+ fun testCanShrink_atFullSize_checksBottomDragState() = runTestWithSnapshots {
+ val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 2, currentRow = 0)
+ updateGridLayout(twoSpanGrid)
+
+ assertThat(underTest.canShrink()).isTrue()
+ assertThat(underTest.bottomDragState.anchors.toList()).containsAtLeast(0 to 0f, -1 to -45f)
+ }
+
+ @Test
+ fun testResizeByAccessibility_expandFromBottom_usesTopDragState() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+
+ val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 1)
+ updateGridLayout(twoSpanGrid)
+
+ underTest.expandToNextAnchor()
+
+ assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.TOP))
+ }
+
+ @Test
+ fun testResizeByAccessibility_expandFromTop_usesBottomDragState() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+
+ val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 0)
+ updateGridLayout(twoSpanGrid)
+
+ underTest.expandToNextAnchor()
+
+ assertThat(resizeInfo).isEqualTo(ResizeInfo(1, DragHandle.BOTTOM))
+ }
+
+ @Test
+ fun testResizeByAccessibility_shrinkFromFull_usesBottomDragState() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+
+ val twoSpanGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 2, currentRow = 0)
+ updateGridLayout(twoSpanGrid)
+
+ underTest.shrinkToNextAnchor()
+
+ assertThat(resizeInfo).isEqualTo(ResizeInfo(-1, DragHandle.BOTTOM))
+ }
+
+ @Test
+ fun testResizeByAccessibility_cannotResizeAtMinSize() = runTestWithSnapshots {
+ val resizeInfo by collectLastValue(underTest.resizeInfo)
+
+ // Set up grid at minimum size
+ val minSizeGrid =
+ singleSpanGrid.copy(
+ totalSpans = 2,
+ currentSpan = 1,
+ minHeightPx = singleSpanGrid.minHeightPx,
+ currentRow = 0,
+ )
+ updateGridLayout(minSizeGrid)
+
+ underTest.shrinkToNextAnchor()
+
+ assertThat(resizeInfo).isNull()
+ }
+
@Test(expected = IllegalArgumentException::class)
fun testIllegalState_maxHeightLessThanMinHeight() =
testScope.runTest {
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0aa5ccf7a2b4..c838180f9541 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1311,6 +1311,10 @@
<string name="communal_widget_picker_description">Anyone can view widgets on your lock screen, even if your tablet\'s locked.</string>
<!-- Label for accessibility action to unselect a widget in edit mode. [CHAR LIMIT=NONE] -->
<string name="accessibility_action_label_unselect_widget">unselect widget</string>
+ <!-- Label for accessibility action to shrink a widget in edit mode. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_shrink_widget">Decrease height</string>
+ <!-- Label for accessibility action to expand a widget in edit mode. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_label_expand_widget">Increase height</string>
<!-- Title shown above information regarding lock screen widgets. [CHAR LIMIT=50] -->
<string name="communal_widgets_disclaimer_title">Lock screen widgets</string>
<!-- Information about lock screen widgets presented to the user. [CHAR LIMIT=NONE] -->
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 db4bee781a58..bde5d0f87a66 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
@@ -17,6 +17,7 @@ package com.android.systemui.communal.ui.viewmodel
import androidx.compose.foundation.gestures.AnchoredDraggableState
import androidx.compose.foundation.gestures.DraggableAnchors
+import androidx.compose.foundation.gestures.snapTo
import androidx.compose.runtime.snapshotFlow
import com.android.app.tracing.coroutines.coroutineScopeTraced as coroutineScope
import com.android.systemui.lifecycle.ExclusiveActivatable
@@ -81,6 +82,72 @@ class ResizeableItemFrameViewModel : ExclusiveActivatable() {
get() = roundDownToMultiple(getSpansForPx(minHeightPx))
}
+ /** Check if widget can expanded based on current drag states */
+ fun canExpand(): Boolean {
+ return getNextAnchor(bottomDragState, moveUp = false) != null ||
+ getNextAnchor(topDragState, moveUp = true) != null
+ }
+
+ /** Check if widget can shrink based on current drag states */
+ fun canShrink(): Boolean {
+ return getNextAnchor(bottomDragState, moveUp = true) != null ||
+ getNextAnchor(topDragState, moveUp = false) != null
+ }
+
+ /** Get the next anchor value in the specified direction */
+ private fun getNextAnchor(state: AnchoredDraggableState<Int>, moveUp: Boolean): Int? {
+ var nextAnchor: Int? = null
+ var nextAnchorDiff = Int.MAX_VALUE
+ val currentValue = state.currentValue
+
+ for (i in 0 until state.anchors.size) {
+ val anchor = state.anchors.anchorAt(i) ?: continue
+ if (anchor == currentValue) continue
+
+ val diff =
+ if (moveUp) {
+ currentValue - anchor
+ } else {
+ anchor - currentValue
+ }
+
+ if (diff in 1..<nextAnchorDiff) {
+ nextAnchor = anchor
+ nextAnchorDiff = diff
+ }
+ }
+
+ return nextAnchor
+ }
+
+ /** Handle expansion to the next anchor */
+ suspend fun expandToNextAnchor() {
+ if (!canExpand()) return
+ val bottomAnchor = getNextAnchor(state = bottomDragState, moveUp = false)
+ if (bottomAnchor != null) {
+ bottomDragState.snapTo(bottomAnchor)
+ return
+ }
+ val topAnchor =
+ getNextAnchor(
+ state = topDragState,
+ moveUp = true, // Moving up to expand
+ )
+ topAnchor?.let { topDragState.snapTo(it) }
+ }
+
+ /** Handle shrinking to the next anchor */
+ suspend fun shrinkToNextAnchor() {
+ if (!canShrink()) return
+ val topAnchor = getNextAnchor(state = topDragState, moveUp = false)
+ if (topAnchor != null) {
+ topDragState.snapTo(topAnchor)
+ return
+ }
+ val bottomAnchor = getNextAnchor(state = bottomDragState, moveUp = true)
+ bottomAnchor?.let { bottomDragState.snapTo(it) }
+ }
+
/**
* The layout information necessary in order to calculate the pixel offsets of the drag anchor
* points.