diff options
2 files changed, 55 insertions, 72 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 70b3efc812e2..ef6eec88bf6d 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 @@ -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. diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt index 9e6f22a69dbc..0c293948dd3c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt @@ -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 @@ -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 /** @@ -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 @@ -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, @@ -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() } @@ -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. @@ -170,39 +156,39 @@ internal class DragAndDropTargetState( } fun onMoved(event: DragAndDropEvent) { - val dragEvent = event.toAndroidDragEvent() - isOnRemoveButton = updateDragPositionForRemove(Offset(dragEvent.x, dragEvent.y)) - if (!isOnRemoveButton) { - findTargetItem(dragEvent)?.apply { - var scrollIndex: Int? = null - var scrollOffset: Int? = null - if (placeHolderIndex == state.firstVisibleItemIndex) { - // Save info about the first item before the move, to neutralize the automatic - // keeping first item first. - scrollIndex = placeHolderIndex - scrollOffset = state.firstVisibleItemScrollOffset - } + val dragOffset = event.toOffset() - autoScrollIfNearEdges(dragEvent) + val targetItem = + state.layoutInfo.visibleItemsInfo + .asSequence() + .filter { item -> contentListState.isItemEditable(item.index) } + .firstItemAtOffset(dragOffset - contentOffset) - if (contentListState.isItemEditable(this.index)) { - movePlaceholderTo(this.index) - placeHolderIndex = this.index - } + if (targetItem != null) { + var scrollIndex: Int? = null + var scrollOffset: Int? = null + if (placeHolderIndex == state.firstVisibleItemIndex) { + // Save info about the first item before the move, to neutralize the automatic + // keeping first item first. + scrollIndex = placeHolderIndex + scrollOffset = state.firstVisibleItemScrollOffset + } - if (scrollIndex != null && scrollOffset != null) { - // this is needed to neutralize automatic keeping the first item first. - scope.launch { state.scrollToItem(scrollIndex, scrollOffset) } - } + 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 @@ -221,39 +207,35 @@ 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 { - distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd - distanceFromStart < autoScrollThreshold -> - -(autoScrollThreshold - distanceFromStart) - else -> 0f - } - } - private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? = - state.layoutInfo.visibleItemsInfo.firstItemAtOffset( - Offset(dragEvent.x, dragEvent.y) - contentOffset - ) + return when { + distanceFromEnd < autoScrollThreshold -> autoScrollThreshold - distanceFromEnd + distanceFromStart < autoScrollThreshold -> distanceFromStart - autoScrollThreshold + else -> 0f + } + } private fun movePlaceholderTo(index: Int) { val currentIndex = contentListState.list.indexOf(placeHolder) @@ -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) } } |