mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 23:21:59 +01:00
fix queue delete latency, reorder hitch and open scroll
This commit is contained in:
@@ -244,19 +244,40 @@ class PlaybackController(
|
|||||||
this.snapshot = queueRepository.snapshot()
|
this.snapshot = queueRepository.snapshot()
|
||||||
val target = movedCursor(current, from, to)
|
val target = movedCursor(current, from, to)
|
||||||
windowJob?.cancel()
|
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 =
|
lastEdit =
|
||||||
if (inWindow) {
|
if (fromInWindow == toInWindow) {
|
||||||
MovedUndo(entries, cursor, target, from, to)
|
MovedUndo(entries, cursor, target, from, to)
|
||||||
} else {
|
} else {
|
||||||
ReloadUndo(entries, cursor)
|
ReloadUndo(entries, cursor)
|
||||||
}
|
}
|
||||||
if (inWindow) {
|
when {
|
||||||
updating = true
|
fromInWindow && toInWindow -> {
|
||||||
player.move((from - windowStart).toInt(), (to - windowStart).toInt())
|
updating = true
|
||||||
updating = false
|
player.move((from - windowStart).toInt(), (to - windowStart).toInt())
|
||||||
} else {
|
updating = false
|
||||||
loadWindow(target, autoplay = player.playWhenReady, startPositionMs = player.currentPositionMs)
|
}
|
||||||
|
|
||||||
|
!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)
|
shiftWindowLocked(target)
|
||||||
queueRepository.setCursor(target)
|
queueRepository.setCursor(target)
|
||||||
@@ -311,6 +332,21 @@ class PlaybackController(
|
|||||||
true
|
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 -> {
|
else -> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,15 +52,18 @@ import com.subtracks.playback.PlaybackController
|
|||||||
import com.subtracks.ui.components.EmptyState
|
import com.subtracks.ui.components.EmptyState
|
||||||
import com.subtracks.ui.components.LoadingState
|
import com.subtracks.ui.components.LoadingState
|
||||||
import com.subtracks.ui.library.SongRow
|
import com.subtracks.ui.library.SongRow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
import sh.calvin.reorderable.ReorderableItem
|
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 POSITION_TIMEOUT_MS = 1_000L
|
||||||
|
|
||||||
data class QueueRow(
|
data class QueueRow(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
@@ -94,7 +97,10 @@ class QueueViewModel(
|
|||||||
first = 0
|
first = 0
|
||||||
last = -1
|
last = -1
|
||||||
if (size > 0L) {
|
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 start = (cursor - QUEUE_CHUNK / 2).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))
|
||||||
@@ -172,8 +178,31 @@ 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)
|
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() {
|
private suspend fun reload() {
|
||||||
val snapshot = queueRepository.snapshot()
|
val snapshot = queueRepository.snapshot()
|
||||||
size = snapshot.size
|
size = snapshot.size
|
||||||
@@ -292,10 +294,10 @@ fun QueueScreen(
|
|||||||
if (centered || !ready || rows.isEmpty()) return@LaunchedEffect
|
if (centered || !ready || rows.isEmpty()) return@LaunchedEffect
|
||||||
val position = currentPosition ?: return@LaunchedEffect
|
val position = currentPosition ?: return@LaunchedEffect
|
||||||
val index = rows.indexOfFirst { it.position == position }
|
val index = rows.indexOfFirst { it.position == position }
|
||||||
if (index >= 0) {
|
if (index < 0) return@LaunchedEffect
|
||||||
listState.scrollToItem(index)
|
snapshotFlow { listState.layoutInfo.totalItemsCount }.first { it > 0 }
|
||||||
centered = true
|
listState.scrollToItem(index)
|
||||||
}
|
centered = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val reorderState =
|
val reorderState =
|
||||||
|
|||||||
Reference in New Issue
Block a user