mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +01:00
fix drag drop target, serialize queue edits, guard empty ranges
This commit is contained in:
@@ -104,8 +104,8 @@ class QueueViewModel(
|
|||||||
val start = cursor
|
val start = cursor
|
||||||
val end = (start + QUEUE_CHUNK - 1).coerceAtMost(size - 1)
|
val end = (start + QUEUE_CHUNK - 1).coerceAtMost(size - 1)
|
||||||
rows.addAll(queueRepository.range(snapshot, start, end).map(::newRow))
|
rows.addAll(queueRepository.range(snapshot, start, end).map(::newRow))
|
||||||
first = rows.first().position
|
first = rows.firstOrNull()?.position ?: 0
|
||||||
last = rows.last().position
|
last = rows.lastOrNull()?.position ?: -1
|
||||||
}
|
}
|
||||||
ready = true
|
ready = true
|
||||||
}
|
}
|
||||||
@@ -178,30 +178,29 @@ class QueueViewModel(
|
|||||||
|
|
||||||
fun remove(position: Long) {
|
fun remove(position: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val index = rows.indexOfFirst { it.position == position }
|
|
||||||
if (index >= 0) {
|
|
||||||
rows.removeAt(index)
|
|
||||||
for (i in index until rows.size) {
|
|
||||||
val row = rows[i]
|
|
||||||
rows[i] = row.copy(position = row.position - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val next = (rows.lastOrNull()?.position ?: (first - 1)) + 1
|
|
||||||
playbackController.removeAt(position)
|
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
if (rows.isEmpty()) {
|
val index = rows.indexOfFirst { it.position == position }
|
||||||
size = 0
|
if (index >= 0) {
|
||||||
first = 0
|
rows.removeAt(index)
|
||||||
last = -1
|
for (i in index until rows.size) {
|
||||||
return@withLock
|
val row = rows[i]
|
||||||
|
rows[i] = row.copy(position = row.position - 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
playbackController.removeAt(position)
|
||||||
val snapshot = queueRepository.snapshot()
|
val snapshot = queueRepository.snapshot()
|
||||||
size = snapshot.size
|
size = snapshot.size
|
||||||
if (next <= size - 1) {
|
if (rows.isEmpty()) {
|
||||||
queueRepository.range(snapshot, next, next).firstOrNull()?.let { rows.add(newRow(it)) }
|
reload()
|
||||||
|
return@withLock
|
||||||
}
|
}
|
||||||
first = rows.first().position
|
first = rows.first().position
|
||||||
last = rows.last().position
|
last = rows.last().position
|
||||||
|
val next = last + 1
|
||||||
|
if (next <= size - 1) {
|
||||||
|
queueRepository.range(snapshot, next, next).firstOrNull()?.let { rows.add(newRow(it)) }
|
||||||
|
last = rows.last().position
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,8 +233,8 @@ class QueueViewModel(
|
|||||||
}
|
}
|
||||||
rows.clear()
|
rows.clear()
|
||||||
rows.addAll(updated)
|
rows.addAll(updated)
|
||||||
first = rows.first().position
|
first = rows.firstOrNull()?.position ?: 0
|
||||||
last = rows.last().position
|
last = rows.lastOrNull()?.position ?: -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,16 +285,12 @@ fun QueueScreen(
|
|||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
var dragId by remember { mutableStateOf<Long?>(null) }
|
||||||
var dragFrom by remember { mutableStateOf<Long?>(null) }
|
var dragFrom by remember { mutableStateOf<Long?>(null) }
|
||||||
var dragTo by remember { mutableStateOf<Long?>(null) }
|
|
||||||
|
|
||||||
val reorderState =
|
val reorderState =
|
||||||
rememberReorderableLazyListState(listState) { from, to ->
|
rememberReorderableLazyListState(listState) { from, to ->
|
||||||
val fromPosition = rows.getOrNull(from.index)?.position
|
|
||||||
val toPosition = rows.getOrNull(to.index)?.position
|
|
||||||
onReorder(from.index, to.index)
|
onReorder(from.index, to.index)
|
||||||
dragFrom = fromPosition
|
|
||||||
dragTo = toPosition
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(listState, ready) {
|
LaunchedEffect(listState, ready) {
|
||||||
@@ -368,16 +363,22 @@ fun QueueScreen(
|
|||||||
dragHandle =
|
dragHandle =
|
||||||
Modifier.draggableHandle(
|
Modifier.draggableHandle(
|
||||||
onDragStarted = {
|
onDragStarted = {
|
||||||
|
dragId = row.id
|
||||||
dragFrom = row.position
|
dragFrom = row.position
|
||||||
dragTo = row.position
|
|
||||||
},
|
},
|
||||||
onDragStopped = {
|
onDragStopped = {
|
||||||
|
val id = dragId
|
||||||
val from = dragFrom
|
val from = dragFrom
|
||||||
val to = dragTo
|
if (id != null && from != null) {
|
||||||
if (from != null && to != null && from != to) {
|
val index = rows.indexOfFirst { it.id == id }
|
||||||
onMove(from, to)
|
val to = if (index < 0) from else dropTarget(rows, index, from)
|
||||||
showUndo("Queue reordered")
|
if (to != from) {
|
||||||
|
onMove(from, to)
|
||||||
|
showUndo("Queue reordered")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
dragId = null
|
||||||
|
dragFrom = null
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
onClick = { onPlay(row.position) },
|
onClick = { onPlay(row.position) },
|
||||||
@@ -394,6 +395,17 @@ fun QueueScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun dropTarget(
|
||||||
|
rows: List<QueueRow>,
|
||||||
|
index: Int,
|
||||||
|
from: Long,
|
||||||
|
): Long {
|
||||||
|
val next = rows.getOrNull(index + 1)
|
||||||
|
if (next != null) return next.position - if (next.position > from) 1 else 0
|
||||||
|
val before = rows.getOrNull(index - 1)?.position ?: return from
|
||||||
|
return (before - if (before > from) 1 else 0) + 1
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun QueueRowItem(
|
private fun QueueRowItem(
|
||||||
row: QueueRow,
|
row: QueueRow,
|
||||||
|
|||||||
@@ -437,6 +437,49 @@ class PlaybackControllerTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun movingTracksFarFromThePlayingOneDoesNotReloadTheWindow() {
|
||||||
|
seedAlbum(100, sourceId = 1)
|
||||||
|
|
||||||
|
controller.playAlbum(1, "al1", 50)
|
||||||
|
await {
|
||||||
|
controller.state.value.item
|
||||||
|
?.id == "s51"
|
||||||
|
}
|
||||||
|
handle.operations.clear()
|
||||||
|
|
||||||
|
runBlocking { controller.move(80, 90) }
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"s51",
|
||||||
|
controller.state.value.item
|
||||||
|
?.id,
|
||||||
|
)
|
||||||
|
assertEquals(50L, controller.state.value.position)
|
||||||
|
assertFalse(handle.operations.any { it.startsWith("setWindow") })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun movingATrackAcrossTheWindowKeepsPlaying() {
|
||||||
|
seedAlbum(100, sourceId = 1)
|
||||||
|
|
||||||
|
controller.playAlbum(1, "al1", 50)
|
||||||
|
await {
|
||||||
|
controller.state.value.item
|
||||||
|
?.id == "s51"
|
||||||
|
}
|
||||||
|
handle.operations.clear()
|
||||||
|
|
||||||
|
runBlocking { controller.move(10, 90) }
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"s51",
|
||||||
|
controller.state.value.item
|
||||||
|
?.id,
|
||||||
|
)
|
||||||
|
assertFalse(handle.operations.any { it.startsWith("setWindow") })
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun undoRestoresARemovedTrack() {
|
fun undoRestoresARemovedTrack() {
|
||||||
seedAlbum(5, sourceId = 1)
|
seedAlbum(5, sourceId = 1)
|
||||||
|
|||||||
@@ -82,6 +82,22 @@ class QueueViewModelTest {
|
|||||||
assertEquals((40L..199L).toList(), viewModel.rows.map { it.position })
|
assertEquals((40L..199L).toList(), viewModel.rows.map { it.position })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun openingAtTheEndLoadsASingleRowAndExtendsOlder() {
|
||||||
|
runBlocking { seedSongs(200) }
|
||||||
|
controller.playSongs(1, 199)
|
||||||
|
await { controller.state.value.position == 199L }
|
||||||
|
|
||||||
|
viewModel.open()
|
||||||
|
await { viewModel.ready }
|
||||||
|
|
||||||
|
assertEquals(listOf(199L), viewModel.rows.map { it.position })
|
||||||
|
|
||||||
|
viewModel.loadOlder()
|
||||||
|
await { viewModel.rows.firstOrNull()?.position == 139L }
|
||||||
|
assertEquals((139L..199L).toList(), viewModel.rows.map { it.position })
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun reorderingMovesTheRowLocally() {
|
fun reorderingMovesTheRowLocally() {
|
||||||
runBlocking { seedSongs(200) }
|
runBlocking { seedSongs(200) }
|
||||||
|
|||||||
Reference in New Issue
Block a user