From b189ae0cc531243da8aa2ea094c28b3aa5f5fa62 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Sun, 27 Sep 2026 01:53:04 +0000 Subject: [PATCH] stop the queue view extending itself mid-drag; restore context drag scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - the visible-items flow fired loadOlder/loadNewer during a drag, and the live reorder leaves row positions out of order, so the window re-added rows it already had — the queue duplicated under the dragged item. Skip loading while a drag is active, and extend from the min/max loaded position rather than the first/last row. - auto-scroll is back for the context reorder state (a normal queue scrolls while dragging again); the up-next state keeps it disabled because its block is followed by context rows the scroller would run past with nowhere to drop. --- .../com/subtracks/ui/playback/QueueScreen.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt b/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt index eed06638..a2fb18fb 100644 --- a/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt @@ -158,11 +158,11 @@ class QueueViewModel( } fun loadOlder() { - val top = rows.firstOrNull()?.position ?: return + val top = rows.minOfOrNull { it.position } ?: return if (top <= 0L) return viewModelScope.launch { mutex.withLock { - val first = rows.firstOrNull()?.position ?: return@withLock + val first = rows.minOfOrNull { it.position } ?: return@withLock if (first <= 0L) return@withLock val snapshot = queueRepository.snapshot() val loaded = queueRepository.range(snapshot, (first - QUEUE_CHUNK).coerceAtLeast(0), first - 1) @@ -174,10 +174,10 @@ class QueueViewModel( } fun loadNewer() { - if (rows.lastOrNull() == null) return + if (rows.isEmpty()) return viewModelScope.launch { mutex.withLock { - val last = rows.lastOrNull()?.position ?: return@withLock + val last = rows.maxOfOrNull { it.position } ?: return@withLock val snapshot = queueRepository.snapshot() if (last >= snapshot.size - 1) return@withLock val to = (last + QUEUE_CHUNK).coerceAtMost(snapshot.size - 1) @@ -329,14 +329,14 @@ fun QueueScreen( // One reorder state per section: sh.calvin.reorderable only targets keys registered with the // state that started the drag, so an up-next drag cannot reach the context rows and vice versa. - // scrollThreshold 0 disables its auto-scroll, which otherwise keeps scrolling past the section - // looking for a target that does not exist. + // The up-next state disables auto-scroll (scrollThreshold 0): its block is followed by context + // rows, so the scroller keeps going past the last place the item can actually drop. val upNextReorder = rememberReorderableLazyListState(listState, scrollThreshold = 0.dp) { from, to -> onReorder(rows.rowIndexOf(from.key), rows.rowIndexOf(to.key)) } val contextReorder = - rememberReorderableLazyListState(listState, scrollThreshold = 0.dp) { from, to -> + rememberReorderableLazyListState(listState) { from, to -> onReorder(rows.rowIndexOf(from.key), rows.rowIndexOf(to.key)) } @@ -349,6 +349,9 @@ fun QueueScreen( val last = info.visibleItemsInfo.lastOrNull { it.key is Long }?.let { rows.rowIndexOf(it.key) } ?: -1 first to last }.collect { (first, last) -> + // Mid-drag the live reorder leaves row positions inconsistent, so extending here would + // re-add rows that are already loaded. + if (dragId != null) return@collect if (first in 0..LOAD_THRESHOLD) onLoadOlder() if (last >= 0 && last >= rows.size - 1 - LOAD_THRESHOLD) onLoadNewer() }