mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: dragDropItemsIndexed back to use item index
This commit is contained in:
parent
3dd0f8ceed
commit
15861c1389
3 changed files with 45 additions and 57 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<Any?>(null)
|
||||
private set
|
||||
private var draggingItemIndex by mutableStateOf<Int?>(null)
|
||||
val adjustedItemIndex get() = draggingItemIndex?.minus(headerCount)
|
||||
|
||||
internal val scrollChannel = Channel<Float>()
|
||||
|
||||
|
|
@ -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<Any?>(null)
|
||||
internal var previousIndexOfDraggedItem by mutableStateOf<Int?>(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 <T> LazyListScope.dragDropItemsIndexed(
|
||||
items: List<T>,
|
||||
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) }
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue