mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +01:00
load the queue around the current track and scroll to it
This commit is contained in:
@@ -66,6 +66,7 @@ import sh.calvin.reorderable.ReorderableItem
|
|||||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||||
|
|
||||||
private const val LOAD_THRESHOLD = 5
|
private const val LOAD_THRESHOLD = 5
|
||||||
|
private const val OLDER_LOAD = 30
|
||||||
private const val POSITION_TIMEOUT_MS = 1_000L
|
private const val POSITION_TIMEOUT_MS = 1_000L
|
||||||
|
|
||||||
data class QueueRow(
|
data class QueueRow(
|
||||||
@@ -83,6 +84,9 @@ class QueueViewModel(
|
|||||||
var ready by mutableStateOf(false)
|
var ready by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var initialIndex by mutableStateOf(0)
|
||||||
|
private set
|
||||||
|
|
||||||
private var nextId = 0L
|
private var nextId = 0L
|
||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
private var size = 0L
|
private var size = 0L
|
||||||
@@ -104,11 +108,12 @@ class QueueViewModel(
|
|||||||
withTimeoutOrNull(POSITION_TIMEOUT_MS) {
|
withTimeoutOrNull(POSITION_TIMEOUT_MS) {
|
||||||
playbackController.state.first { it.position != null }.position
|
playbackController.state.first { it.position != null }.position
|
||||||
}?.coerceIn(0, size - 1) ?: 0L
|
}?.coerceIn(0, size - 1) ?: 0L
|
||||||
val start = cursor
|
val start = (cursor - OLDER_LOAD).coerceAtLeast(0)
|
||||||
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.firstOrNull()?.position ?: 0
|
first = rows.firstOrNull()?.position ?: 0
|
||||||
last = rows.lastOrNull()?.position ?: -1
|
last = rows.lastOrNull()?.position ?: -1
|
||||||
|
initialIndex = (cursor - first).toInt().coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
ready = true
|
ready = true
|
||||||
}
|
}
|
||||||
@@ -254,6 +259,7 @@ fun QueueRoute(
|
|||||||
QueueScreen(
|
QueueScreen(
|
||||||
rows = viewModel.rows,
|
rows = viewModel.rows,
|
||||||
ready = viewModel.ready,
|
ready = viewModel.ready,
|
||||||
|
initialIndex = viewModel.initialIndex,
|
||||||
currentSongId = playback.item?.id,
|
currentSongId = playback.item?.id,
|
||||||
coverArt = sourceRepository::coverArt,
|
coverArt = sourceRepository::coverArt,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
@@ -284,6 +290,7 @@ fun QueueScreen(
|
|||||||
onLoadNewer: () -> Unit,
|
onLoadNewer: () -> Unit,
|
||||||
onUndo: () -> Unit,
|
onUndo: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
initialIndex: Int = 0,
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val fill = rememberViewportFill(listState)
|
val fill = rememberViewportFill(listState)
|
||||||
@@ -299,6 +306,7 @@ fun QueueScreen(
|
|||||||
|
|
||||||
LaunchedEffect(listState, ready) {
|
LaunchedEffect(listState, ready) {
|
||||||
if (!ready) return@LaunchedEffect
|
if (!ready) return@LaunchedEffect
|
||||||
|
if (initialIndex > 0) listState.scrollToItem(initialIndex)
|
||||||
snapshotFlow {
|
snapshotFlow {
|
||||||
val info = listState.layoutInfo
|
val info = listState.layoutInfo
|
||||||
val firstVisible = info.visibleItemsInfo.firstOrNull()?.index ?: -1
|
val firstVisible = info.visibleItemsInfo.firstOrNull()?.index ?: -1
|
||||||
|
|||||||
@@ -72,19 +72,20 @@ class QueueViewModelTest {
|
|||||||
viewModel.open()
|
viewModel.open()
|
||||||
await { viewModel.ready }
|
await { viewModel.ready }
|
||||||
|
|
||||||
assertEquals((100L..159L).toList(), viewModel.rows.map { it.position })
|
assertEquals((70L..129L).toList(), viewModel.rows.map { it.position })
|
||||||
|
assertEquals(30, viewModel.initialIndex)
|
||||||
|
|
||||||
viewModel.loadNewer()
|
viewModel.loadNewer()
|
||||||
await { viewModel.rows.lastOrNull()?.position == 199L }
|
await { viewModel.rows.lastOrNull()?.position == 189L }
|
||||||
assertEquals((100L..199L).toList(), viewModel.rows.map { it.position })
|
assertEquals((70L..189L).toList(), viewModel.rows.map { it.position })
|
||||||
|
|
||||||
viewModel.loadOlder()
|
viewModel.loadOlder()
|
||||||
await { viewModel.rows.firstOrNull()?.position == 40L }
|
await { viewModel.rows.firstOrNull()?.position == 10L }
|
||||||
assertEquals((40L..199L).toList(), viewModel.rows.map { it.position })
|
assertEquals((10L..189L).toList(), viewModel.rows.map { it.position })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun openingAtTheEndLoadsASingleRowAndExtendsOlder() {
|
fun openingAtTheEndLoadsAWindowAndExtendsOlder() {
|
||||||
runBlocking { seedSongs(200) }
|
runBlocking { seedSongs(200) }
|
||||||
controller.playSongs(1, 199)
|
controller.playSongs(1, 199)
|
||||||
await { controller.state.value.position == 199L }
|
await { controller.state.value.position == 199L }
|
||||||
@@ -92,11 +93,12 @@ class QueueViewModelTest {
|
|||||||
viewModel.open()
|
viewModel.open()
|
||||||
await { viewModel.ready }
|
await { viewModel.ready }
|
||||||
|
|
||||||
assertEquals(listOf(199L), viewModel.rows.map { it.position })
|
assertEquals((169L..199L).toList(), viewModel.rows.map { it.position })
|
||||||
|
assertEquals(30, viewModel.initialIndex)
|
||||||
|
|
||||||
viewModel.loadOlder()
|
viewModel.loadOlder()
|
||||||
await { viewModel.rows.firstOrNull()?.position == 139L }
|
await { viewModel.rows.firstOrNull()?.position == 109L }
|
||||||
assertEquals((139L..199L).toList(), viewModel.rows.map { it.position })
|
assertEquals((109L..199L).toList(), viewModel.rows.map { it.position })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -118,7 +120,7 @@ class QueueViewModelTest {
|
|||||||
viewModel.reorder(fromIndex = 0, toIndex = 2)
|
viewModel.reorder(fromIndex = 0, toIndex = 2)
|
||||||
|
|
||||||
val positions = viewModel.rows.map { it.position }
|
val positions = viewModel.rows.map { it.position }
|
||||||
assertEquals(listOf(101L, 102L, 100L), positions.take(3))
|
assertEquals(listOf(71L, 72L, 70L), positions.take(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun row(position: Int) =
|
private fun row(position: Int) =
|
||||||
|
|||||||
Reference in New Issue
Block a user