Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +0 −1 Original line number Diff line number Diff line Loading @@ -517,7 +517,6 @@ private fun BoxScope.CommunalHubLazyGrid( gridState = gridState, contentListState = contentListState, contentOffset = contentOffset, updateDragPositionForRemove = updateDragPositionForRemove ) // A full size box in background that listens to widget drops from the picker. Loading packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt +55 −71 Original line number Diff line number Diff line Loading @@ -18,17 +18,13 @@ package com.android.systemui.communal.ui.compose import android.content.ClipDescription import android.view.DragEvent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.draganddrop.dragAndDropTarget import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.lazy.grid.LazyGridItemInfo import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState Loading @@ -45,8 +41,7 @@ import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset import com.android.systemui.communal.util.WidgetPickerIntentUtils import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.launch /** Loading @@ -59,32 +54,22 @@ internal fun rememberDragAndDropTargetState( gridState: LazyGridState, contentOffset: Offset, contentListState: ContentListState, updateDragPositionForRemove: (offset: Offset) -> Boolean, ): DragAndDropTargetState { val scope = rememberCoroutineScope() val autoScrollSpeed = remember { mutableFloatStateOf(0f) } // Threshold of distance from edges that should start auto-scroll - chosen to be a narrow value // that allows differentiating intention of scrolling from intention of dragging over the first // visible item. val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() } val state = remember(gridState, contentListState) { remember(gridState, contentOffset, contentListState, autoScrollThreshold, scope) { DragAndDropTargetState( state = gridState, contentOffset = contentOffset, contentListState = contentListState, scope = scope, autoScrollSpeed = autoScrollSpeed, autoScrollThreshold = autoScrollThreshold, updateDragPositionForRemove = updateDragPositionForRemove, scope = scope, ) } LaunchedEffect(autoScrollSpeed.floatValue) { if (autoScrollSpeed.floatValue != 0f) { while (isActive) { gridState.scrollBy(autoScrollSpeed.floatValue) delay(10) } LaunchedEffect(state) { for (diff in state.scrollChannel) { gridState.scrollBy(diff) } } return state Loading @@ -96,7 +81,6 @@ internal fun rememberDragAndDropTargetState( * @see androidx.compose.foundation.draganddrop.dragAndDropTarget * @see DragEvent */ @OptIn(ExperimentalFoundationApi::class) @Composable internal fun Modifier.dragAndDropTarget( dragDropTargetState: DragAndDropTargetState, Loading @@ -122,6 +106,10 @@ internal fun Modifier.dragAndDropTarget( return state.onDrop(event) } override fun onExited(event: DragAndDropEvent) { state.onExited() } override fun onEnded(event: DragAndDropEvent) { state.onEnded() } Loading Loading @@ -149,19 +137,17 @@ internal class DragAndDropTargetState( private val state: LazyGridState, private val contentOffset: Offset, private val contentListState: ContentListState, private val scope: CoroutineScope, private val autoScrollSpeed: MutableState<Float>, private val autoScrollThreshold: Float, private val updateDragPositionForRemove: (offset: Offset) -> Boolean, private val scope: CoroutineScope, ) { /** * The placeholder item that is treated as if it is being dragged across the grid. It is added * to grid once drag and drop event is started and removed when event ends. */ private var placeHolder = CommunalContentModel.WidgetPlaceholder() private var placeHolderIndex: Int? = null private var isOnRemoveButton = false internal val scrollChannel = Channel<Float>() fun onStarted() { // assume item will be added to the end. Loading @@ -170,10 +156,15 @@ internal class DragAndDropTargetState( } fun onMoved(event: DragAndDropEvent) { val dragEvent = event.toAndroidDragEvent() isOnRemoveButton = updateDragPositionForRemove(Offset(dragEvent.x, dragEvent.y)) if (!isOnRemoveButton) { findTargetItem(dragEvent)?.apply { val dragOffset = event.toOffset() val targetItem = state.layoutInfo.visibleItemsInfo .asSequence() .filter { item -> contentListState.isItemEditable(item.index) } .firstItemAtOffset(dragOffset - contentOffset) if (targetItem != null) { var scrollIndex: Int? = null var scrollOffset: Int? = null if (placeHolderIndex == state.firstVisibleItemIndex) { Loading @@ -183,26 +174,21 @@ internal class DragAndDropTargetState( scrollOffset = state.firstVisibleItemScrollOffset } autoScrollIfNearEdges(dragEvent) if (contentListState.isItemEditable(this.index)) { movePlaceholderTo(this.index) placeHolderIndex = this.index if (contentListState.isItemEditable(targetItem.index)) { movePlaceholderTo(targetItem.index) placeHolderIndex = targetItem.index } if (scrollIndex != null && scrollOffset != null) { // this is needed to neutralize automatic keeping the first item first. scope.launch { state.scrollToItem(scrollIndex, scrollOffset) } } } } else { computeAutoscroll(dragOffset).takeIf { it != 0f }?.let { scrollChannel.trySend(it) } } } fun onDrop(event: DragAndDropEvent): Boolean { autoScrollSpeed.value = 0f if (isOnRemoveButton) { return false } return placeHolderIndex?.let { dropIndex -> val widgetExtra = event.maybeWidgetExtra() ?: return false val (componentName, user) = widgetExtra Loading @@ -221,40 +207,36 @@ internal class DragAndDropTargetState( } fun onEnded() { autoScrollSpeed.value = 0f placeHolderIndex = null contentListState.list.remove(placeHolder) isOnRemoveButton = updateDragPositionForRemove(Offset.Zero) } private fun autoScrollIfNearEdges(dragEvent: DragEvent) { fun onExited() { onEnded() } private fun computeAutoscroll(dragOffset: Offset): Float { val orientation = state.layoutInfo.orientation val distanceFromStart = if (orientation == Orientation.Horizontal) { dragEvent.x dragOffset.x } else { dragEvent.y dragOffset.y } val distanceFromEnd = if (orientation == Orientation.Horizontal) { state.layoutInfo.viewportSize.width - dragEvent.x state.layoutInfo.viewportEndOffset - dragOffset.x } else { state.layoutInfo.viewportSize.height - dragEvent.y state.layoutInfo.viewportEndOffset - dragOffset.y } autoScrollSpeed.value = when { return when { distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd distanceFromStart < autoScrollThreshold -> -(autoScrollThreshold - distanceFromStart) distanceFromStart < autoScrollThreshold -> distanceFromStart - autoScrollThreshold else -> 0f } } private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? = state.layoutInfo.visibleItemsInfo.firstItemAtOffset( Offset(dragEvent.x, dragEvent.y) - contentOffset ) private fun movePlaceholderTo(index: Int) { val currentIndex = contentListState.list.indexOf(placeHolder) if (currentIndex != index) { Loading @@ -271,4 +253,6 @@ internal class DragAndDropTargetState( val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 } return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) } } private fun DragAndDropEvent.toOffset() = this.toAndroidDragEvent().run { Offset(x, y) } } Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +0 −1 Original line number Diff line number Diff line Loading @@ -517,7 +517,6 @@ private fun BoxScope.CommunalHubLazyGrid( gridState = gridState, contentListState = contentListState, contentOffset = contentOffset, updateDragPositionForRemove = updateDragPositionForRemove ) // A full size box in background that listens to widget drops from the picker. Loading
packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt +55 −71 Original line number Diff line number Diff line Loading @@ -18,17 +18,13 @@ package com.android.systemui.communal.ui.compose import android.content.ClipDescription import android.view.DragEvent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.draganddrop.dragAndDropTarget import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.lazy.grid.LazyGridItemInfo import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState Loading @@ -45,8 +41,7 @@ import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset import com.android.systemui.communal.util.WidgetPickerIntentUtils import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.launch /** Loading @@ -59,32 +54,22 @@ internal fun rememberDragAndDropTargetState( gridState: LazyGridState, contentOffset: Offset, contentListState: ContentListState, updateDragPositionForRemove: (offset: Offset) -> Boolean, ): DragAndDropTargetState { val scope = rememberCoroutineScope() val autoScrollSpeed = remember { mutableFloatStateOf(0f) } // Threshold of distance from edges that should start auto-scroll - chosen to be a narrow value // that allows differentiating intention of scrolling from intention of dragging over the first // visible item. val autoScrollThreshold = with(LocalDensity.current) { 60.dp.toPx() } val state = remember(gridState, contentListState) { remember(gridState, contentOffset, contentListState, autoScrollThreshold, scope) { DragAndDropTargetState( state = gridState, contentOffset = contentOffset, contentListState = contentListState, scope = scope, autoScrollSpeed = autoScrollSpeed, autoScrollThreshold = autoScrollThreshold, updateDragPositionForRemove = updateDragPositionForRemove, scope = scope, ) } LaunchedEffect(autoScrollSpeed.floatValue) { if (autoScrollSpeed.floatValue != 0f) { while (isActive) { gridState.scrollBy(autoScrollSpeed.floatValue) delay(10) } LaunchedEffect(state) { for (diff in state.scrollChannel) { gridState.scrollBy(diff) } } return state Loading @@ -96,7 +81,6 @@ internal fun rememberDragAndDropTargetState( * @see androidx.compose.foundation.draganddrop.dragAndDropTarget * @see DragEvent */ @OptIn(ExperimentalFoundationApi::class) @Composable internal fun Modifier.dragAndDropTarget( dragDropTargetState: DragAndDropTargetState, Loading @@ -122,6 +106,10 @@ internal fun Modifier.dragAndDropTarget( return state.onDrop(event) } override fun onExited(event: DragAndDropEvent) { state.onExited() } override fun onEnded(event: DragAndDropEvent) { state.onEnded() } Loading Loading @@ -149,19 +137,17 @@ internal class DragAndDropTargetState( private val state: LazyGridState, private val contentOffset: Offset, private val contentListState: ContentListState, private val scope: CoroutineScope, private val autoScrollSpeed: MutableState<Float>, private val autoScrollThreshold: Float, private val updateDragPositionForRemove: (offset: Offset) -> Boolean, private val scope: CoroutineScope, ) { /** * The placeholder item that is treated as if it is being dragged across the grid. It is added * to grid once drag and drop event is started and removed when event ends. */ private var placeHolder = CommunalContentModel.WidgetPlaceholder() private var placeHolderIndex: Int? = null private var isOnRemoveButton = false internal val scrollChannel = Channel<Float>() fun onStarted() { // assume item will be added to the end. Loading @@ -170,10 +156,15 @@ internal class DragAndDropTargetState( } fun onMoved(event: DragAndDropEvent) { val dragEvent = event.toAndroidDragEvent() isOnRemoveButton = updateDragPositionForRemove(Offset(dragEvent.x, dragEvent.y)) if (!isOnRemoveButton) { findTargetItem(dragEvent)?.apply { val dragOffset = event.toOffset() val targetItem = state.layoutInfo.visibleItemsInfo .asSequence() .filter { item -> contentListState.isItemEditable(item.index) } .firstItemAtOffset(dragOffset - contentOffset) if (targetItem != null) { var scrollIndex: Int? = null var scrollOffset: Int? = null if (placeHolderIndex == state.firstVisibleItemIndex) { Loading @@ -183,26 +174,21 @@ internal class DragAndDropTargetState( scrollOffset = state.firstVisibleItemScrollOffset } autoScrollIfNearEdges(dragEvent) if (contentListState.isItemEditable(this.index)) { movePlaceholderTo(this.index) placeHolderIndex = this.index if (contentListState.isItemEditable(targetItem.index)) { movePlaceholderTo(targetItem.index) placeHolderIndex = targetItem.index } if (scrollIndex != null && scrollOffset != null) { // this is needed to neutralize automatic keeping the first item first. scope.launch { state.scrollToItem(scrollIndex, scrollOffset) } } } } else { computeAutoscroll(dragOffset).takeIf { it != 0f }?.let { scrollChannel.trySend(it) } } } fun onDrop(event: DragAndDropEvent): Boolean { autoScrollSpeed.value = 0f if (isOnRemoveButton) { return false } return placeHolderIndex?.let { dropIndex -> val widgetExtra = event.maybeWidgetExtra() ?: return false val (componentName, user) = widgetExtra Loading @@ -221,40 +207,36 @@ internal class DragAndDropTargetState( } fun onEnded() { autoScrollSpeed.value = 0f placeHolderIndex = null contentListState.list.remove(placeHolder) isOnRemoveButton = updateDragPositionForRemove(Offset.Zero) } private fun autoScrollIfNearEdges(dragEvent: DragEvent) { fun onExited() { onEnded() } private fun computeAutoscroll(dragOffset: Offset): Float { val orientation = state.layoutInfo.orientation val distanceFromStart = if (orientation == Orientation.Horizontal) { dragEvent.x dragOffset.x } else { dragEvent.y dragOffset.y } val distanceFromEnd = if (orientation == Orientation.Horizontal) { state.layoutInfo.viewportSize.width - dragEvent.x state.layoutInfo.viewportEndOffset - dragOffset.x } else { state.layoutInfo.viewportSize.height - dragEvent.y state.layoutInfo.viewportEndOffset - dragOffset.y } autoScrollSpeed.value = when { return when { distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd distanceFromStart < autoScrollThreshold -> -(autoScrollThreshold - distanceFromStart) distanceFromStart < autoScrollThreshold -> distanceFromStart - autoScrollThreshold else -> 0f } } private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? = state.layoutInfo.visibleItemsInfo.firstItemAtOffset( Offset(dragEvent.x, dragEvent.y) - contentOffset ) private fun movePlaceholderTo(index: Int) { val currentIndex = contentListState.list.indexOf(placeHolder) if (currentIndex != index) { Loading @@ -271,4 +253,6 @@ internal class DragAndDropTargetState( val clipData = this.toAndroidDragEvent().clipData.takeIf { it.itemCount != 0 } return clipData?.getItemAt(0)?.intent?.let { intent -> getWidgetExtraFromIntent(intent) } } private fun DragAndDropEvent.toOffset() = this.toAndroidDragEvent().run { Offset(x, y) } }