diff --git a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt index 3aded58b6..969bedb61 100644 --- a/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt +++ b/app/src/main/java/io/legado/app/ui/replace/ReplaceRuleScreen.kt @@ -106,14 +106,6 @@ fun ReplaceRuleScreen( var selectedTabIndex by remember { mutableIntStateOf(0) } val tabItems = remember(groups) { listOf("全部") + groups } - val filteredRules = remember(uiState.items, selectedTabIndex, tabItems) { - val targetGroup = tabItems.getOrNull(selectedTabIndex) - if (targetGroup == null || selectedTabIndex == 0) { - uiState.items - } else { - uiState.items.filter { it.group == targetGroup } - } - } val reorderableState = rememberReorderableLazyListState(listState) { from, to -> viewModel.moveItemInList(from.index, to.index) @@ -407,7 +399,7 @@ fun ReplaceRuleScreen( ), verticalArrangement = Arrangement.spacedBy(8.dp) ) { - items(filteredRules, key = { it.id }) { ui -> + items(rules, key = { it.id }) { ui -> ReorderableSelectionItem( state = reorderableState, key = ui.id, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/DraggableSelectionHandler.kt b/app/src/main/java/io/legado/app/ui/widget/components/DraggableSelectionHandler.kt index 7b47bc16c..fece125a1 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/DraggableSelectionHandler.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/DraggableSelectionHandler.kt @@ -16,8 +16,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedback import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalHapticFeedback -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch +import androidx.compose.ui.util.fastFirstOrNull @Composable fun DraggableSelectionHandler( @@ -30,69 +29,87 @@ fun DraggableSelectionHandler( haptic: HapticFeedback = LocalHapticFeedback.current, ) { val latestSelectedIds by rememberUpdatedState(selectedIds) + val latestOnSelectionChange by rememberUpdatedState(onSelectionChange) + val latestIdProvider by rememberUpdatedState(idProvider) + val latestItems by rememberUpdatedState(items) + var isAddingMode by remember { mutableStateOf(true) } var lastProcessedIndex by remember { mutableIntStateOf(-1) } - fun findItemAtOffset(offsetY: Float): Pair? { - val itemInfo = listState.layoutInfo.visibleItemsInfo - .firstOrNull { item -> - offsetY >= item.offset && offsetY <= item.offset + item.size - } + fun findItemAtOffset(offsetY: Float): Pair? { + val layoutInfo = listState.layoutInfo + val itemsInfo = layoutInfo.visibleItemsInfo + if (itemsInfo.isEmpty()) return null - return itemInfo?.let { info -> - items.getOrNull(info.index)?.let { item -> - info.index to item - } + // 核心修复:校准坐标系。 + // offsetY 是相对于此 Box 顶部的。 + // item.offset 是相对于视口起始位置(通常包含 contentPadding)的。 + // 当开启模糊时,LazyColumn 占据全屏但有 topContentPadding。 + // item.offset 在 Compose 的 LazyListLayoutInfo 中是相对于“内容区域”开始计算的。 + val adjustedY = offsetY - layoutInfo.beforeContentPadding + + val itemInfo = itemsInfo.fastFirstOrNull { item -> + adjustedY >= item.offset && adjustedY <= (item.offset + item.size) + } ?: return null + + @Suppress("UNCHECKED_CAST") + val id = try { + itemInfo.key as ID + } catch (e: Exception) { + latestItems.getOrNull(itemInfo.index)?.let { latestIdProvider(it) } ?: return null } + + return itemInfo.index to id } fun applySelection(id: ID, add: Boolean) { val current = latestSelectedIds - onSelectionChange( - if (add) current + id else current - id - ) + if (add) { + if (!current.contains(id)) { + latestOnSelectionChange(current + id) + } + } else { + if (current.contains(id)) { + latestOnSelectionChange(current - id) + } + } } Box( - modifier = modifier.pointerInput(Unit) { - coroutineScope { - launch { - detectTapGestures( - onTap = { offset -> - findItemAtOffset(offset.y)?.let { (_, item) -> - val id = idProvider(item) - applySelection(id, !latestSelectedIds.contains(id)) + modifier = modifier + .pointerInput(listState) { + detectTapGestures( + onTap = { offset -> + findItemAtOffset(offset.y)?.let { (_, id) -> + applySelection(id, !latestSelectedIds.contains(id)) + haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) + } + } + ) + } + .pointerInput(listState) { + detectDragGestures( + onDragStart = { offset -> + findItemAtOffset(offset.y)?.let { (index, id) -> + lastProcessedIndex = index + isAddingMode = !latestSelectedIds.contains(id) + applySelection(id, isAddingMode) + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + } + }, + onDrag = { change, _ -> + findItemAtOffset(change.position.y)?.let { (index, id) -> + if (index != lastProcessedIndex) { + lastProcessedIndex = index + applySelection(id, isAddingMode) haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) } } - ) - } - - launch { - detectDragGestures( - onDragStart = { offset -> - findItemAtOffset(offset.y)?.let { (index, item) -> - lastProcessedIndex = index - val id = idProvider(item) - isAddingMode = !latestSelectedIds.contains(id) - applySelection(id, isAddingMode) - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - } - }, - onDrag = { change, _ -> - findItemAtOffset(change.position.y)?.let { (index, item) -> - if (index != lastProcessedIndex) { - lastProcessedIndex = index - applySelection(idProvider(item), isAddingMode) - haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) - } - } - }, - onDragEnd = { lastProcessedIndex = -1 }, - onDragCancel = { lastProcessedIndex = -1 } - ) - } + if (change.pressed) change.consume() + }, + onDragEnd = { lastProcessedIndex = -1 }, + onDragCancel = { lastProcessedIndex = -1 } + ) } - } ) -} \ No newline at end of file +}