mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
address review: anchor relocation and empty-context positions
- clamp anchorPlay to the context size, so an empty context with a queued block puts the block at combined 0 (previously position 0 was unresolvable and the queue view emptied). - move/removeAt now derive the playing track's new position from its id after an edit, instead of assuming a fixed list delta; moving or removing the anchor relocates the whole block, which movedCursor ignored. - removing the currently playing track reloads at the model's next position (the anchor may have relocated) rather than removing it from the player in place. - the queue view's remove reconciles its rows from the repository instead of decrementing positions by one, so a block relocation stays in sync.
This commit is contained in:
@@ -58,10 +58,12 @@ data class QueueSnapshot(
|
||||
val shuffled: Boolean get() = shuffleOrder != null
|
||||
|
||||
val anchorPlay: Long =
|
||||
when (val order = shuffleOrder) {
|
||||
null -> upNextAnchor
|
||||
else -> order.indexOf(upNextAnchor).takeIf { it >= 0 }?.toLong() ?: 0L
|
||||
}
|
||||
(
|
||||
when (val order = shuffleOrder) {
|
||||
null -> upNextAnchor
|
||||
else -> order.indexOf(upNextAnchor).takeIf { it >= 0 }?.toLong() ?: 0L
|
||||
}
|
||||
).coerceAtMost(contextSize - 1L)
|
||||
|
||||
fun isUpNext(position: Long): Boolean = upNextSize > 0L && position > anchorPlay && position <= anchorPlay + upNextSize
|
||||
|
||||
|
||||
@@ -349,15 +349,8 @@ class PlaybackController(
|
||||
if (position != current) {
|
||||
rebuildWindow(queueRepository.window(updated, target, QUEUE_WINDOW_RADIUS), target)
|
||||
refresh(target)
|
||||
} else if (target - windowStart in 0 until player.itemCount) {
|
||||
updating = true
|
||||
player.removeAt((target - windowStart).toInt())
|
||||
updating = false
|
||||
windowEnd--
|
||||
shiftWindowLocked(target)
|
||||
refresh(target)
|
||||
} else {
|
||||
loadWindow(target, autoplay = playing)
|
||||
loadWindow(target, autoplay = playing, startPositionMs = 0)
|
||||
}
|
||||
return@withLock
|
||||
}
|
||||
@@ -400,10 +393,17 @@ class PlaybackController(
|
||||
val cursor = queueRepository.cursor()
|
||||
val entries = snapshot.entries.map { it.entry }
|
||||
val upNext = snapshot.upNext.map { it.entry }
|
||||
val wasUpNext = snapshot.isUpNext(current)
|
||||
if (!queueRepository.move(snapshot, from, to)) return@withLock
|
||||
val updated = readSnapshot()
|
||||
this.snapshot = updated
|
||||
val target = movedCursor(current, from, to)
|
||||
val currentId = player.currentItem?.id
|
||||
val target =
|
||||
(
|
||||
currentId?.let {
|
||||
if (wasUpNext) queueRepository.combinedIndexOf(updated, it) else queueRepository.combinedContextIndexOf(updated, it)
|
||||
} ?: movedCursor(current, from, to)
|
||||
).coerceIn(0, updated.size - 1)
|
||||
lastPosition = target
|
||||
windowJob?.cancel()
|
||||
val fromInWindow = from in windowStart..windowEnd
|
||||
|
||||
@@ -134,26 +134,26 @@ class QueueViewModel(
|
||||
}
|
||||
|
||||
fun reconcile() {
|
||||
viewModelScope.launch {
|
||||
mutex.withLock {
|
||||
if (!ready) return@withLock
|
||||
val snapshot = queueRepository.snapshot()
|
||||
if (snapshot.size == 0L) {
|
||||
rows.clear()
|
||||
return@withLock
|
||||
}
|
||||
val start = (rows.firstOrNull()?.position ?: 0L).coerceIn(0, snapshot.size - 1)
|
||||
val end = (start + rows.size.coerceAtLeast(QUEUE_CHUNK) - 1).coerceAtMost(snapshot.size - 1)
|
||||
val loaded = queueRepository.range(snapshot, start, end)
|
||||
val reused = reusedIds()
|
||||
val updated = loaded.map { rowFor(it, reused) }
|
||||
if (updated.size == rows.size) {
|
||||
updated.forEachIndexed { index, row -> if (rows[index] != row) rows[index] = row }
|
||||
} else {
|
||||
rows.clear()
|
||||
rows.addAll(updated)
|
||||
}
|
||||
}
|
||||
viewModelScope.launch { mutex.withLock { reconcileLocked() } }
|
||||
}
|
||||
|
||||
private suspend fun reconcileLocked() {
|
||||
if (!ready) return
|
||||
val snapshot = queueRepository.snapshot()
|
||||
if (snapshot.size == 0L) {
|
||||
rows.clear()
|
||||
return
|
||||
}
|
||||
val start = (rows.firstOrNull()?.position ?: 0L).coerceIn(0, snapshot.size - 1)
|
||||
val end = (start + rows.size.coerceAtLeast(QUEUE_CHUNK) - 1).coerceAtMost(snapshot.size - 1)
|
||||
val loaded = queueRepository.range(snapshot, start, end)
|
||||
val reused = reusedIds()
|
||||
val updated = loaded.map { rowFor(it, reused) }
|
||||
if (updated.size == rows.size) {
|
||||
updated.forEachIndexed { index, row -> if (rows[index] != row) rows[index] = row }
|
||||
} else {
|
||||
rows.clear()
|
||||
rows.addAll(updated)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,24 +228,9 @@ class QueueViewModel(
|
||||
viewModelScope.launch {
|
||||
mutex.withLock {
|
||||
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)
|
||||
}
|
||||
}
|
||||
if (index >= 0) rows.removeAt(index)
|
||||
playbackController.removeAt(position)
|
||||
val snapshot = queueRepository.snapshot()
|
||||
if (rows.isEmpty()) {
|
||||
reload()
|
||||
return@withLock
|
||||
}
|
||||
val next = rows.last().position + 1
|
||||
if (next <= snapshot.size - 1) {
|
||||
queueRepository.range(snapshot, next, next).firstOrNull()?.let { rows.add(newRow(it)) }
|
||||
}
|
||||
while (rows.size > QUEUE_WINDOW_ROWS) rows.removeAt(0)
|
||||
reconcileLocked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user