From 15861c13896ffa911b93b4e9e7c1bbf347ee941a Mon Sep 17 00:00:00 2001 From: andrekir Date: Thu, 4 Jul 2024 09:23:13 -0300 Subject: [PATCH] refactor: `dragDropItemsIndexed` back to use item `index` --- .../com/geeksville/mesh/ui/ChannelFragment.kt | 4 +- .../components/LazyColumnDragAndDropDemo.kt | 87 +++++++++---------- .../config/ChannelSettingsItemList.kt | 11 +-- 3 files changed, 45 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index 9aa28bd2b..279c63807 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -314,8 +314,8 @@ fun ChannelScreen( } val listState = rememberLazyListState() - val dragDropState = rememberDragDropState(listState) { from, to -> - updateSettingsList { add(to.index, removeAt(from.index)) } + val dragDropState = rememberDragDropState(listState) { fromIndex, toIndex -> + updateSettingsList { add(toIndex, removeAt(fromIndex)) } } LazyColumn( diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/LazyColumnDragAndDropDemo.kt b/app/src/main/java/com/geeksville/mesh/ui/components/LazyColumnDragAndDropDemo.kt index f7c253608..e2891f36e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/LazyColumnDragAndDropDemo.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/LazyColumnDragAndDropDemo.kt @@ -69,8 +69,10 @@ fun LazyColumnDragAndDropDemo() { var list by remember { mutableStateOf(List(50) { it }) } val listState = rememberLazyListState() - val dragDropState = rememberDragDropState(listState) { from, to -> - list = list.toMutableList().apply { add(to.index, removeAt(from.index)) } + val dragDropState = rememberDragDropState(listState, headerCount = 1) { fromIndex, toIndex -> + if (fromIndex in list.indices && toIndex in list.indices) { + list = list.toMutableList().apply { add(toIndex, removeAt(fromIndex)) } + } } LazyColumn( @@ -82,29 +84,33 @@ fun LazyColumnDragAndDropDemo() { contentPadding = PaddingValues(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { + item { + Text("Header", Modifier.fillMaxWidth().padding(20.dp)) + } + itemsIndexed(list, key = { _, item -> item }) { index, item -> - DraggableItem(dragDropState, index) { isDragging -> + DraggableItem(dragDropState, index + 1) { isDragging -> val elevation by animateDpAsState(if (isDragging) 4.dp else 1.dp) Card(elevation = elevation) { - Text("Item $item", - Modifier - .fillMaxWidth() - .padding(20.dp)) + Text("Item $item", Modifier.fillMaxWidth().padding(20.dp)) } } } + + item { + Text("Footer", Modifier.fillMaxWidth().padding(20.dp)) + } } } @Composable fun rememberDragDropState( lazyListState: LazyListState, - onMove: (LazyListItemInfo, LazyListItemInfo) -> Unit, + headerCount: Int = 0, + onMove: (Int, Int) -> Unit ): DragDropState { val scope = rememberCoroutineScope() - val state = remember(lazyListState) { - DragDropState(state = lazyListState, onMove = onMove, scope = scope) - } + val state = remember(lazyListState) { DragDropState(lazyListState, headerCount, scope, onMove) } LaunchedEffect(state) { while (true) { val diff = state.scrollChannel.receive() @@ -117,11 +123,12 @@ fun rememberDragDropState( class DragDropState internal constructor( private val state: LazyListState, + private val headerCount: Int, private val scope: CoroutineScope, - private val onMove: (LazyListItemInfo, LazyListItemInfo) -> Unit + private val onMove: (Int, Int) -> Unit ) { - var draggingItemKey by mutableStateOf(null) - private set + private var draggingItemIndex by mutableStateOf(null) + val adjustedItemIndex get() = draggingItemIndex?.minus(headerCount) internal val scrollChannel = Channel() @@ -133,29 +140,26 @@ internal constructor( } ?: 0f private val draggingItemLayoutInfo: LazyListItemInfo? - get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.key == draggingItemKey } + get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex } - internal var previousKeyOfDraggedItem by mutableStateOf(null) + internal var previousIndexOfDraggedItem by mutableStateOf(null) private set internal var previousItemOffset = Animatable(0f) private set - internal fun gridItemKeyAtPosition(offset: Offset): Int? = state.layoutInfo.visibleItemsInfo - .find { item -> offset.y.toInt() in item.offset..(item.offset + item.size) }?.key as? Int - - internal fun onDragStart(key: Int) { + internal fun onDragStart(offset: Offset) { state.layoutInfo.visibleItemsInfo - .firstOrNull { item -> item.key == key } + .firstOrNull { item -> offset.y.toInt() in item.offset..(item.offset + item.size) } ?.also { - draggingItemKey = it.key + draggingItemIndex = it.index draggingItemInitialOffset = it.offset } } internal fun onDragInterrupted() { - if (draggingItemKey != null) { - previousKeyOfDraggedItem = draggingItemKey + if (draggingItemIndex != null) { + previousIndexOfDraggedItem = draggingItemIndex val startOffset = draggingItemOffset scope.launch { previousItemOffset.snapTo(startOffset) @@ -163,11 +167,11 @@ internal constructor( 0f, spring(stiffness = Spring.StiffnessMediumLow, visibilityThreshold = 1f) ) - previousKeyOfDraggedItem = null + previousIndexOfDraggedItem = null } } draggingItemDraggedDelta = 0f - draggingItemKey = null + draggingItemIndex = null draggingItemInitialOffset = 0 } @@ -180,7 +184,6 @@ internal constructor( val middleOffset = startOffset + (endOffset - startOffset) / 2f val targetItem = state.layoutInfo.visibleItemsInfo - .filter { it.key is Int } .find { item -> middleOffset.toInt() in item.offset..item.offsetEnd && draggingItem.index != item.index @@ -204,7 +207,8 @@ internal constructor( ) } } - onMove.invoke(draggingItem, targetItem) + onMove.invoke(draggingItem.index - headerCount, targetItem.index - headerCount) + draggingItemIndex = targetItem.index } else { val overscroll = when { draggingItemDraggedDelta > 0 -> @@ -236,10 +240,8 @@ fun Modifier.dragContainer( dragDropState.onDrag(offset = offset) }, onDragStart = { offset -> - dragDropState.gridItemKeyAtPosition(offset)?.let { key -> - dragDropState.onDragStart(key) - haptics.performHapticFeedback(HapticFeedbackType.LongPress) - } + dragDropState.onDragStart(offset) + haptics.performHapticFeedback(HapticFeedbackType.LongPress) }, onDragEnd = { dragDropState.onDragInterrupted() }, onDragCancel = { dragDropState.onDragInterrupted() } @@ -251,16 +253,16 @@ fun Modifier.dragContainer( @Composable fun LazyItemScope.DraggableItem( dragDropState: DragDropState, - key: Int, + index: Int, modifier: Modifier = Modifier, content: @Composable ColumnScope.(isDragging: Boolean) -> Unit ) { - val dragging = key == dragDropState.draggingItemKey + val dragging = index == dragDropState.adjustedItemIndex val draggingModifier = if (dragging) { Modifier .zIndex(1f) .graphicsLayer { translationY = dragDropState.draggingItemOffset } - } else if (key == dragDropState.previousKeyOfDraggedItem) { + } else if (index == dragDropState.previousIndexOfDraggedItem) { Modifier .zIndex(1f) .graphicsLayer { translationY = dragDropState.previousItemOffset.value } @@ -275,30 +277,21 @@ fun LazyItemScope.DraggableItem( * * Wraps [itemsIndexed] function with [detectDragGesturesAfterLongPress] to enable long-press * drag gestures and allow items in the list to be reordered using the provided [DragDropState]. - * - * Uses the item's `hashCode()` key internally (instead of the index) to allow handling headers, - * footers, and other non-draggable items. - * - * @param items The list of items to be displayed in the [LazyColumn]. - * @param dragDropState The state object managing drag-and-drop interactions. - * @param contentType A function providing the content type of each item, used for item recycling - * optimizations. Defaults to null. - * @param itemContent A composable function defining the UI for each item. It provides the index, - * the item itself, and a boolean indicating if the item is currently being dragged. */ inline fun LazyListScope.dragDropItemsIndexed( items: List, dragDropState: DragDropState, + noinline key: ((index: Int, item: T) -> Any)? = null, crossinline contentType: (index: Int, item: T) -> Any? = { _, _ -> null }, crossinline itemContent: @Composable LazyItemScope.(index: Int, item: T, isDragging: Boolean) -> Unit ) = itemsIndexed( items = items, - key = { _, item -> item.hashCode() }, + key = key, contentType = contentType, itemContent = { index, item -> DraggableItem( dragDropState = dragDropState, - key = item.hashCode(), + index = index, content = { isDragging -> itemContent(index, item, isDragging) } ) } diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/ChannelSettingsItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/ChannelSettingsItemList.kt index 916a48592..14f3e7754 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/config/ChannelSettingsItemList.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/ChannelSettingsItemList.kt @@ -18,7 +18,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.Card import androidx.compose.material.Chip -import androidx.compose.material.ContentAlpha import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.FloatingActionButton import androidx.compose.material.Icon @@ -36,7 +35,6 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource @@ -78,7 +76,6 @@ fun ChannelCard( Text( text = title, style = MaterialTheme.typography.body1, - color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else Color.Unspecified, modifier = Modifier.weight(1f) ) IconButton(onClick = { onDeleteClick() }) { @@ -105,11 +102,9 @@ fun ChannelSettingsItemList( val settingsListInput = remember(settingsList) { settingsList.toMutableStateList() } val listState = rememberLazyListState() - val dragDropState = rememberDragDropState(listState) { from, to -> - settingsListInput.apply { - val fromIndex = indexOfFirst { it.hashCode() == from.key } - val toIndex = indexOfFirst { it.hashCode() == to.key } - add(toIndex, removeAt(fromIndex)) + val dragDropState = rememberDragDropState(listState, headerCount = 1) { fromIndex, toIndex -> + if (toIndex in settingsListInput.indices && fromIndex in settingsListInput.indices) { + settingsListInput.apply { add(toIndex, removeAt(fromIndex)) } } }