From 42840cd9add8154caa09ec5079059d4d1861b2ce Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Sun, 27 Sep 2026 02:09:53 +0000 Subject: [PATCH] queue view: stop context drags crossing the block, stabilise the title - split the context into a reorder state either side of the up-next block. A context drag can no longer target across the block, so dragging from the context over a long queued block stops at the block instead of jumping over it and force-dropping. - the context header's fallback is now the playing track's album, so it no longer flashes "Next from here" while the async name lookup runs when tapping between manually queued and context tracks. --- .../com/subtracks/ui/playback/QueueScreen.kt | 29 ++++++++++++++----- 1 file changed, 22 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 21ecd826..11c6ad65 100644 --- a/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt @@ -269,7 +269,9 @@ fun QueueRoute( ) { val playback by controller.state.collectAsStateWithLifecycle() val queueContext = playback.context - val fallbackTitle = "Next from here" + // The item's album keeps the header stable while the async name lookup runs, so it does not + // flash the fallback when tapping between context and manually queued tracks. + val fallbackTitle = playback.item?.album?.takeIf { it.isNotBlank() } ?: "Next from here" val sourceTitle = remember(queueContext) { mutableStateOf(fallbackTitle) } LaunchedEffect(queueContext) { sourceTitle.value = controller.sourceTitle(queueContext)?.takeIf { it.isNotBlank() } ?: fallbackTitle @@ -327,15 +329,21 @@ fun QueueScreen( var dragId by remember { mutableStateOf(null) } var dragFrom by remember { mutableStateOf(null) } - // 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. - // 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. + // One reorder state per run of rows: sh.calvin.reorderable only targets keys registered with + // the state that started the drag, so a drag can never reach a row in another run. The context + // sits on both sides of the up-next block, and splitting it there stops a context drag crossing + // the block (which would move a track over the queued songs and relocate the anchor). The + // up-next state disables auto-scroll (scrollThreshold 0): its block is followed by context + // rows the scroller would otherwise keep scrolling towards with nowhere to drop. val upNextReorder = rememberReorderableLazyListState(listState, scrollThreshold = 0.dp) { from, to -> onReorder(rows.rowIndexOf(from.key), rows.rowIndexOf(to.key)) } - val contextReorder = + val contextBeforeReorder = + rememberReorderableLazyListState(listState) { from, to -> + onReorder(rows.rowIndexOf(from.key), rows.rowIndexOf(to.key)) + } + val contextAfterReorder = rememberReorderableLazyListState(listState) { from, to -> onReorder(rows.rowIndexOf(from.key), rows.rowIndexOf(to.key)) } @@ -391,6 +399,7 @@ fun QueueScreen( modifier = Modifier.padding(padding).fillMaxSize(), contentPadding = PaddingValues(bottom = 16.dp), ) { + val blockFirst = rows.indexOfFirst { it.upNext } rows.forEachIndexed { index, row -> if (index == 0 || rows[index - 1].upNext != row.upNext) { // Keyed by index, not section: the context sits on both sides of the @@ -403,9 +412,15 @@ fun QueueScreen( } } val enabled = row.upNext || !shuffle + val section = + when { + row.upNext -> upNextReorder + blockFirst < 0 || index < blockFirst -> contextBeforeReorder + else -> contextAfterReorder + } item(key = row.id) { ReorderableItem( - state = if (row.upNext) upNextReorder else contextReorder, + state = section, key = row.id, enabled = enabled, animateItemModifier =