mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
queue view: disable drag auto-scroll; queue plays next when its anchor is removed
- removing the context track the up-next block sits behind now moves the block back behind the preceding track (or to the front when the anchor was first), so the queued track plays next instead of the following context track. - disable sh.calvin.reorderable auto-scroll for every run (scrollThreshold 0). It keeps scrolling past a run's last valid target and can strand the dragged item off-screen; scroll first, then drag.
This commit is contained in:
@@ -61,7 +61,7 @@ data class QueueSnapshot(
|
|||||||
(
|
(
|
||||||
when (val order = shuffleOrder) {
|
when (val order = shuffleOrder) {
|
||||||
null -> upNextAnchor
|
null -> upNextAnchor
|
||||||
else -> order.indexOf(upNextAnchor).takeIf { it >= 0 }?.toLong() ?: 0L
|
else -> if (upNextAnchor < 0L) -1L else order.indexOf(upNextAnchor).takeIf { it >= 0 }?.toLong() ?: 0L
|
||||||
}
|
}
|
||||||
).coerceAtMost(contextSize - 1L)
|
).coerceAtMost(contextSize - 1L)
|
||||||
|
|
||||||
@@ -411,33 +411,28 @@ class QueueRepository(
|
|||||||
if (order == null) {
|
if (order == null) {
|
||||||
val removed = compact(removeEntry(snapshot.entries, play))
|
val removed = compact(removeEntry(snapshot.entries, play))
|
||||||
write(removed.map { it.entry })
|
write(removed.map { it.entry })
|
||||||
adjustAnchorOnRemove(play, removed.sumOf { it.length })
|
adjustAnchorOnRemove(play)
|
||||||
} else {
|
} else {
|
||||||
val flat = order.getOrNull(play.toInt()) ?: return snapshot
|
val flat = order.getOrNull(play.toInt()) ?: return snapshot
|
||||||
val removed = compact(removeEntry(snapshot.entries, flat))
|
write(compact(removeEntry(snapshot.entries, flat)).map { it.entry })
|
||||||
write(removed.map { it.entry })
|
|
||||||
val reordered = order.toMutableList().apply { removeAt(play.toInt()) }
|
val reordered = order.toMutableList().apply { removeAt(play.toInt()) }
|
||||||
for (index in reordered.indices) if (reordered[index] > flat) reordered[index] -= 1
|
for (index in reordered.indices) if (reordered[index] > flat) reordered[index] -= 1
|
||||||
setShuffle(true, reordered.toLongArray())
|
setShuffle(true, reordered.toLongArray())
|
||||||
adjustAnchorOnRemove(flat, removed.sumOf { it.length })
|
adjustAnchorOnRemove(flat)
|
||||||
}
|
}
|
||||||
return snapshot()
|
return snapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun adjustAnchorOnRemove(
|
private suspend fun adjustAnchorOnRemove(removedFlat: Long) =
|
||||||
removedFlat: Long,
|
cursorMutex.withLock {
|
||||||
newSize: Long,
|
val row = cursorRow()
|
||||||
) = cursorMutex.withLock {
|
val anchor = row.upNextAnchor
|
||||||
val row = cursorRow()
|
// Removing the track the block sits behind leaves the block next in line, so move the
|
||||||
val anchor = row.upNextAnchor
|
// anchor back to the preceding track (or in front of the whole context, -1, when the
|
||||||
val adjusted =
|
// anchor was the first track) rather than letting the following track play first.
|
||||||
when {
|
val adjusted = if (anchor >= removedFlat) anchor - 1 else anchor
|
||||||
anchor > removedFlat -> anchor - 1
|
if (adjusted != anchor) dao.setCursor(row.copy(upNextAnchor = adjusted))
|
||||||
anchor == removedFlat -> removedFlat.coerceIn(0, (newSize - 1).coerceAtLeast(0))
|
}
|
||||||
else -> anchor
|
|
||||||
}
|
|
||||||
if (adjusted != anchor) dao.setCursor(row.copy(upNextAnchor = adjusted))
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun adjustAnchorOnMove(
|
private suspend fun adjustAnchorOnMove(
|
||||||
flatFrom: Long,
|
flatFrom: Long,
|
||||||
|
|||||||
@@ -332,19 +332,20 @@ fun QueueScreen(
|
|||||||
// One reorder state per run of rows: sh.calvin.reorderable only targets keys registered with
|
// 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
|
// 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
|
// 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
|
// the block (which would move a track over the queued songs and relocate the anchor).
|
||||||
// up-next state disables auto-scroll (scrollThreshold 0): its block is followed by context
|
// scrollThreshold 0 disables auto-scroll: the library keeps scrolling past a run's last valid
|
||||||
// rows the scroller would otherwise keep scrolling towards with nowhere to drop.
|
// target with nowhere to drop, and the dragged item can be left off-screen. Scroll to where
|
||||||
|
// you want to drop first, then drag.
|
||||||
val upNextReorder =
|
val upNextReorder =
|
||||||
rememberReorderableLazyListState(listState, scrollThreshold = 0.dp) { from, to ->
|
rememberReorderableLazyListState(listState, scrollThreshold = 0.dp) { from, to ->
|
||||||
onReorder(rows.rowIndexOf(from.key), rows.rowIndexOf(to.key))
|
onReorder(rows.rowIndexOf(from.key), rows.rowIndexOf(to.key))
|
||||||
}
|
}
|
||||||
val contextBeforeReorder =
|
val contextBeforeReorder =
|
||||||
rememberReorderableLazyListState(listState) { from, to ->
|
rememberReorderableLazyListState(listState, scrollThreshold = 0.dp) { from, to ->
|
||||||
onReorder(rows.rowIndexOf(from.key), rows.rowIndexOf(to.key))
|
onReorder(rows.rowIndexOf(from.key), rows.rowIndexOf(to.key))
|
||||||
}
|
}
|
||||||
val contextAfterReorder =
|
val contextAfterReorder =
|
||||||
rememberReorderableLazyListState(listState) { from, to ->
|
rememberReorderableLazyListState(listState, scrollThreshold = 0.dp) { from, to ->
|
||||||
onReorder(rows.rowIndexOf(from.key), rows.rowIndexOf(to.key))
|
onReorder(rows.rowIndexOf(from.key), rows.rowIndexOf(to.key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -465,6 +465,19 @@ class QueueRepositoryTest {
|
|||||||
assertEquals(listOf("s1", "s4", "s2", "s3"), resolveAll(repository.snapshot()))
|
assertEquals(listOf("s1", "s4", "s2", "s3"), resolveAll(repository.snapshot()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun removingTheAnchorTrackPutsTheQueueFirst() =
|
||||||
|
runTest {
|
||||||
|
seedLibrary()
|
||||||
|
val snapshot = repository.snapshotAfter(listOf(repository.albumEntry(1, "al1")))
|
||||||
|
repository.addUpNext(snapshot, currentPosition = 0, entry = repository.songEntry(1, "s4"), playNext = false)
|
||||||
|
assertEquals(listOf("s1", "s4", "s2", "s3"), resolveAll(repository.snapshot()))
|
||||||
|
|
||||||
|
repository.removeAt(repository.snapshot(), 0)
|
||||||
|
|
||||||
|
assertEquals(listOf("s4", "s2", "s3"), resolveAll(repository.snapshot()))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun removingTheLastAnchorTrackKeepsTheQueueResolvable() =
|
fun removingTheLastAnchorTrackKeepsTheQueueResolvable() =
|
||||||
runTest {
|
runTest {
|
||||||
|
|||||||
@@ -519,7 +519,8 @@ class PlaybackControllerTest {
|
|||||||
?.id == "x1"
|
?.id == "x1"
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(1L, runBlocking { queues.cursor() })
|
val snapshot = runBlocking { queues.snapshot() }
|
||||||
|
assertEquals(runBlocking { queues.combinedIndexOf(snapshot, "x1") }, controller.state.value.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -979,7 +980,7 @@ class PlaybackControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun removingThePlayingAnchorTrackPlaysTheModelNextTrack() {
|
fun removingThePlayingAnchorTrackPlaysTheQueuedTrackNext() {
|
||||||
seedAlbum(4, sourceId = 1)
|
seedAlbum(4, sourceId = 1)
|
||||||
seedSong("x1", "al2")
|
seedSong("x1", "al2")
|
||||||
|
|
||||||
@@ -994,11 +995,11 @@ class PlaybackControllerTest {
|
|||||||
runBlocking { controller.removeAt(1) }
|
runBlocking { controller.removeAt(1) }
|
||||||
await {
|
await {
|
||||||
controller.state.value.item
|
controller.state.value.item
|
||||||
?.id == "s3"
|
?.id == "x1"
|
||||||
}
|
}
|
||||||
|
|
||||||
val snapshot = runBlocking { queues.snapshot() }
|
val snapshot = runBlocking { queues.snapshot() }
|
||||||
assertEquals(runBlocking { queues.combinedIndexOf(snapshot, "s3") }, controller.state.value.position)
|
assertEquals(runBlocking { queues.combinedIndexOf(snapshot, "x1") }, controller.state.value.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user