Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +46 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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, ) { Loading @@ -680,6 +683,7 @@ private fun ResizableItemFrameWrapper( enabled = enabled, alpha = alpha, modifier = modifier, viewModel = viewModel, onResize = onResize, minHeightPx = minHeightPx, maxHeightPx = maxHeightPx, Loading Loading @@ -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 Loading @@ -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, Loading @@ -843,6 +856,7 @@ private fun BoxScope.CommunalHubLazyGrid( contentListState = contentListState, interactionHandler = interactionHandler, widgetSection = widgetSection, resizeableItemFrameViewModel = resizeableItemFrameViewModel, ) } } Loading @@ -857,6 +871,7 @@ private fun BoxScope.CommunalHubLazyGrid( contentListState = contentListState, interactionHandler = interactionHandler, widgetSection = widgetSection, resizeableItemFrameViewModel = resizeableItemFrameViewModel, ) } } Loading Loading @@ -1080,6 +1095,7 @@ private fun CommunalContent( contentListState: ContentListState, interactionHandler: RemoteViews.InteractionHandler?, widgetSection: CommunalAppWidgetSection, resizeableItemFrameViewModel: ResizeableItemFrameViewModel, ) { when (model) { is CommunalContentModel.WidgetContent.Widget -> Loading @@ -1093,6 +1109,7 @@ private fun CommunalContent( index, contentListState, widgetSection, resizeableItemFrameViewModel, ) is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier) is CommunalContentModel.WidgetContent.DisabledWidget -> Loading Loading @@ -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) { Loading @@ -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 } } Loading Loading @@ -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) { Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt +1 −6 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt +100 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading packages/SystemUI/res/values/strings.xml +4 −0 Original line number Diff line number Diff line Loading @@ -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] --> Loading packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt +67 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +46 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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, ) { Loading @@ -680,6 +683,7 @@ private fun ResizableItemFrameWrapper( enabled = enabled, alpha = alpha, modifier = modifier, viewModel = viewModel, onResize = onResize, minHeightPx = minHeightPx, maxHeightPx = maxHeightPx, Loading Loading @@ -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 Loading @@ -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, Loading @@ -843,6 +856,7 @@ private fun BoxScope.CommunalHubLazyGrid( contentListState = contentListState, interactionHandler = interactionHandler, widgetSection = widgetSection, resizeableItemFrameViewModel = resizeableItemFrameViewModel, ) } } Loading @@ -857,6 +871,7 @@ private fun BoxScope.CommunalHubLazyGrid( contentListState = contentListState, interactionHandler = interactionHandler, widgetSection = widgetSection, resizeableItemFrameViewModel = resizeableItemFrameViewModel, ) } } Loading Loading @@ -1080,6 +1095,7 @@ private fun CommunalContent( contentListState: ContentListState, interactionHandler: RemoteViews.InteractionHandler?, widgetSection: CommunalAppWidgetSection, resizeableItemFrameViewModel: ResizeableItemFrameViewModel, ) { when (model) { is CommunalContentModel.WidgetContent.Widget -> Loading @@ -1093,6 +1109,7 @@ private fun CommunalContent( index, contentListState, widgetSection, resizeableItemFrameViewModel, ) is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier) is CommunalContentModel.WidgetContent.DisabledWidget -> Loading Loading @@ -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) { Loading @@ -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 } } Loading Loading @@ -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) { Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ResizeableItemFrame.kt +1 −6 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt +100 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading
packages/SystemUI/res/values/strings.xml +4 −0 Original line number Diff line number Diff line Loading @@ -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] --> Loading
packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt +67 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. Loading