fix queue delete latency, reorder hitch and open scroll

This commit is contained in:
austinried
2026-10-04 00:29:52 +00:00
parent bb42975bb4
commit 147916ff06
2 changed files with 79 additions and 41 deletions
@@ -244,19 +244,40 @@ class PlaybackController(
this.snapshot = queueRepository.snapshot()
val target = movedCursor(current, from, to)
windowJob?.cancel()
val inWindow = from in windowStart..windowEnd && to in windowStart..windowEnd
val fromInWindow = from in windowStart..windowEnd
val toInWindow = to in windowStart..windowEnd
lastEdit =
if (inWindow) {
if (fromInWindow == toInWindow) {
MovedUndo(entries, cursor, target, from, to)
} else {
ReloadUndo(entries, cursor)
}
if (inWindow) {
updating = true
player.move((from - windowStart).toInt(), (to - windowStart).toInt())
updating = false
} else {
loadWindow(target, autoplay = player.playWhenReady, startPositionMs = player.currentPositionMs)
when {
fromInWindow && toInWindow -> {
updating = true
player.move((from - windowStart).toInt(), (to - windowStart).toInt())
updating = false
}
!fromInWindow && !toInWindow -> {
// The moved track never enters the window, so the playlist is unchanged; only
// the window's positions shift, and a move that spans it shifts them by one.
when {
from < windowStart && to > windowEnd -> {
windowStart--
windowEnd--
}
from > windowEnd && to < windowStart -> {
windowStart++
windowEnd++
}
}
}
else -> {
loadWindow(target, autoplay = player.playWhenReady, startPositionMs = player.currentPositionMs)
}
}
shiftWindowLocked(target)
queueRepository.setCursor(target)
@@ -311,6 +332,21 @@ class PlaybackController(
true
}
undo is MovedUndo && undo.from !in windowStart..windowEnd && undo.to !in windowStart..windowEnd -> {
when {
undo.from < windowStart && undo.to > windowEnd -> {
windowStart++
windowEnd++
}
undo.from > windowEnd && undo.to < windowStart -> {
windowStart--
windowEnd--
}
}
true
}
else -> {
false
}
@@ -52,15 +52,18 @@ import com.subtracks.playback.PlaybackController
import com.subtracks.ui.components.EmptyState
import com.subtracks.ui.components.LoadingState
import com.subtracks.ui.library.SongRow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import org.koin.compose.koinInject
import org.koin.compose.viewmodel.koinViewModel
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
private const val LOAD_THRESHOLD = 5
private const val POSITION_TIMEOUT_MS = 1_000L
data class QueueRow(
val id: Long,
@@ -94,7 +97,10 @@ class QueueViewModel(
first = 0
last = -1
if (size > 0L) {
val cursor = (playbackController.state.value.position ?: 0L).coerceIn(0, size - 1)
val cursor =
withTimeoutOrNull(POSITION_TIMEOUT_MS) {
playbackController.state.first { it.position != null }.position
}?.coerceIn(0, size - 1) ?: 0L
val start = (cursor - QUEUE_CHUNK / 2).coerceAtLeast(0)
val end = (start + QUEUE_CHUNK - 1).coerceAtMost(size - 1)
rows.addAll(queueRepository.range(snapshot, start, end).map(::newRow))
@@ -172,8 +178,31 @@ class QueueViewModel(
fun remove(position: Long) {
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 { removeRow(position) }
mutex.withLock {
if (rows.isEmpty()) {
size = 0
first = 0
last = -1
return@withLock
}
val snapshot = queueRepository.snapshot()
size = snapshot.size
if (next <= size - 1) {
queueRepository.range(snapshot, next, next).firstOrNull()?.let { rows.add(newRow(it)) }
}
first = rows.first().position
last = rows.last().position
}
}
}
@@ -184,33 +213,6 @@ class QueueViewModel(
}
}
private suspend fun removeRow(position: Long) {
val index = rows.indexOfFirst { it.position == position }
if (index < 0) {
reload()
return
}
rows.removeAt(index)
for (i in index until rows.size) {
val row = rows[i]
rows[i] = row.copy(position = row.position - 1)
}
val snapshot = queueRepository.snapshot()
size = snapshot.size
if (rows.isEmpty()) {
first = 0
last = -1
return
}
first = rows.first().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
}
}
private suspend fun reload() {
val snapshot = queueRepository.snapshot()
size = snapshot.size
@@ -292,10 +294,10 @@ fun QueueScreen(
if (centered || !ready || rows.isEmpty()) return@LaunchedEffect
val position = currentPosition ?: return@LaunchedEffect
val index = rows.indexOfFirst { it.position == position }
if (index >= 0) {
listState.scrollToItem(index)
centered = true
}
if (index < 0) return@LaunchedEffect
snapshotFlow { listState.layoutInfo.totalItemsCount }.first { it > 0 }
listState.scrollToItem(index)
centered = true
}
val reorderState =