mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +01:00
fix the queue paging refresh key and drag edge detection
This commit is contained in:
@@ -9,19 +9,27 @@ class QueuePagingSource(
|
|||||||
private val repository: QueueRepository,
|
private val repository: QueueRepository,
|
||||||
private val initialPosition: Long,
|
private val initialPosition: Long,
|
||||||
) : PagingSource<Long, QueueWindowItem>() {
|
) : PagingSource<Long, QueueWindowItem>() {
|
||||||
override fun getRefreshKey(state: PagingState<Long, QueueWindowItem>): Long? =
|
override fun getRefreshKey(state: PagingState<Long, QueueWindowItem>): Long? {
|
||||||
state.anchorPosition?.let { (it / QUEUE_PAGE_SIZE).toLong() }
|
val anchor = state.anchorPosition ?: return null
|
||||||
|
val first =
|
||||||
|
state.pages
|
||||||
|
.firstOrNull()
|
||||||
|
?.data
|
||||||
|
?.firstOrNull()
|
||||||
|
?.position ?: return null
|
||||||
|
return (first + anchor) / QUEUE_PAGE_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, QueueWindowItem> {
|
override suspend fun load(params: LoadParams<Long>): LoadResult<Long, QueueWindowItem> {
|
||||||
val snapshot = repository.snapshot()
|
val snapshot = repository.snapshot()
|
||||||
val page = params.key ?: (initialPosition.coerceAtLeast(0) / QUEUE_PAGE_SIZE)
|
if (snapshot.size == 0L) return LoadResult.Page(emptyList(), null, null)
|
||||||
|
val lastPage = (snapshot.size - 1) / QUEUE_PAGE_SIZE
|
||||||
|
val page = (params.key ?: (initialPosition.coerceAtLeast(0) / QUEUE_PAGE_SIZE)).coerceIn(0, lastPage)
|
||||||
val offset = page * QUEUE_PAGE_SIZE
|
val offset = page * QUEUE_PAGE_SIZE
|
||||||
val previousKey = if (page > 0) page - 1 else null
|
|
||||||
if (offset >= snapshot.size) return LoadResult.Page(emptyList(), previousKey, null)
|
|
||||||
val end = (offset + params.loadSize).coerceAtMost(snapshot.size)
|
val end = (offset + params.loadSize).coerceAtMost(snapshot.size)
|
||||||
return LoadResult.Page(
|
return LoadResult.Page(
|
||||||
data = repository.range(snapshot, offset, end - 1),
|
data = repository.range(snapshot, offset, end - 1),
|
||||||
prevKey = previousKey,
|
prevKey = if (page > 0) page - 1 else null,
|
||||||
nextKey = if (end < snapshot.size) end / QUEUE_PAGE_SIZE else null,
|
nextKey = if (end < snapshot.size) end / QUEUE_PAGE_SIZE else null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,11 +49,13 @@ data class PlaybackState(
|
|||||||
private sealed interface QueueUndo {
|
private sealed interface QueueUndo {
|
||||||
val entries: List<QueueEntry>
|
val entries: List<QueueEntry>
|
||||||
val cursor: Long
|
val cursor: Long
|
||||||
|
val current: Long?
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class RemovedUndo(
|
private data class RemovedUndo(
|
||||||
override val entries: List<QueueEntry>,
|
override val entries: List<QueueEntry>,
|
||||||
override val cursor: Long,
|
override val cursor: Long,
|
||||||
|
override val current: Long,
|
||||||
val position: Long,
|
val position: Long,
|
||||||
val item: QueueItem?,
|
val item: QueueItem?,
|
||||||
) : QueueUndo
|
) : QueueUndo
|
||||||
@@ -61,6 +63,7 @@ private data class RemovedUndo(
|
|||||||
private data class MovedUndo(
|
private data class MovedUndo(
|
||||||
override val entries: List<QueueEntry>,
|
override val entries: List<QueueEntry>,
|
||||||
override val cursor: Long,
|
override val cursor: Long,
|
||||||
|
override val current: Long,
|
||||||
val from: Long,
|
val from: Long,
|
||||||
val to: Long,
|
val to: Long,
|
||||||
) : QueueUndo
|
) : QueueUndo
|
||||||
@@ -68,7 +71,9 @@ private data class MovedUndo(
|
|||||||
private data class ReloadUndo(
|
private data class ReloadUndo(
|
||||||
override val entries: List<QueueEntry>,
|
override val entries: List<QueueEntry>,
|
||||||
override val cursor: Long,
|
override val cursor: Long,
|
||||||
) : QueueUndo
|
) : QueueUndo {
|
||||||
|
override val current: Long? = null
|
||||||
|
}
|
||||||
|
|
||||||
class PlaybackController(
|
class PlaybackController(
|
||||||
private val sourceRepository: SourceRepository,
|
private val sourceRepository: SourceRepository,
|
||||||
@@ -195,7 +200,7 @@ class PlaybackController(
|
|||||||
if (removed == null) {
|
if (removed == null) {
|
||||||
ReloadUndo(entries, cursor)
|
ReloadUndo(entries, cursor)
|
||||||
} else {
|
} else {
|
||||||
RemovedUndo(entries, cursor, position, removed)
|
RemovedUndo(entries, cursor, target, position, removed)
|
||||||
}
|
}
|
||||||
when {
|
when {
|
||||||
position == current -> {
|
position == current -> {
|
||||||
@@ -242,7 +247,7 @@ class PlaybackController(
|
|||||||
val inWindow = from in windowStart..windowEnd && to in windowStart..windowEnd
|
val inWindow = from in windowStart..windowEnd && to in windowStart..windowEnd
|
||||||
lastEdit =
|
lastEdit =
|
||||||
if (inWindow) {
|
if (inWindow) {
|
||||||
MovedUndo(entries, cursor, from, to)
|
MovedUndo(entries, cursor, target, from, to)
|
||||||
} else {
|
} else {
|
||||||
ReloadUndo(entries, cursor)
|
ReloadUndo(entries, cursor)
|
||||||
}
|
}
|
||||||
@@ -275,7 +280,11 @@ class PlaybackController(
|
|||||||
val target = undo.cursor.coerceIn(0, restored.size - 1)
|
val target = undo.cursor.coerceIn(0, restored.size - 1)
|
||||||
val restoredId = queueRepository.itemAt(restored, target)?.song?.id
|
val restoredId = queueRepository.itemAt(restored, target)?.song?.id
|
||||||
windowJob?.cancel()
|
windowJob?.cancel()
|
||||||
val canMirror = player.currentItem?.id != null && player.currentItem?.id == restoredId
|
val canMirror =
|
||||||
|
undo.current != null &&
|
||||||
|
player.currentItem?.id != null &&
|
||||||
|
player.currentItem?.id == restoredId &&
|
||||||
|
currentPosition() == undo.current
|
||||||
val mirrored =
|
val mirrored =
|
||||||
when {
|
when {
|
||||||
!canMirror -> {
|
!canMirror -> {
|
||||||
|
|||||||
@@ -180,10 +180,11 @@ fun QueueScreen(
|
|||||||
val from = dragging ?: break
|
val from = dragging ?: break
|
||||||
val info = listState.layoutInfo.visibleItemsInfo.firstOrNull { items.peek(it.index)?.position == from }
|
val info = listState.layoutInfo.visibleItemsInfo.firstOrNull { items.peek(it.index)?.position == from }
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
|
val top = info.offset + dragOffset
|
||||||
val edge =
|
val edge =
|
||||||
when {
|
when {
|
||||||
info.offset < listState.layoutInfo.viewportStartOffset + threshold -> -step
|
top < listState.layoutInfo.viewportStartOffset + threshold -> -step
|
||||||
info.offset + info.size > listState.layoutInfo.viewportEndOffset - threshold -> step
|
top + info.size > listState.layoutInfo.viewportEndOffset - threshold -> step
|
||||||
else -> 0f
|
else -> 0f
|
||||||
}
|
}
|
||||||
if (edge != 0f) {
|
if (edge != 0f) {
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package com.subtracks.data.repo
|
package com.subtracks.data.repo
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.paging.PagingConfig
|
||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
|
import androidx.paging.PagingState
|
||||||
import androidx.room3.Room
|
import androidx.room3.Room
|
||||||
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.subtracks.data.db.SubtracksDatabase
|
import com.subtracks.data.db.SubtracksDatabase
|
||||||
import com.subtracks.data.model.Song
|
import com.subtracks.data.model.Song
|
||||||
|
import com.subtracks.data.model.SongListItem
|
||||||
import com.subtracks.data.model.Source
|
import com.subtracks.data.model.Source
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
@@ -74,6 +77,21 @@ class QueuePagingSourceTest {
|
|||||||
assertEquals(0L, first.prevKey)
|
assertEquals(0L, first.prevKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun theRefreshKeyUsesTheLoadedPositions() =
|
||||||
|
runTest {
|
||||||
|
val source = repository.pagingSource(initialPosition = 100)
|
||||||
|
val state =
|
||||||
|
PagingState(
|
||||||
|
pages = listOf(PagingSource.LoadResult.Page(listOf(itemAt(60)), prevKey = 0L, nextKey = 2L)),
|
||||||
|
anchorPosition = 5,
|
||||||
|
config = PagingConfig(pageSize = QUEUE_PAGE_SIZE, enablePlaceholders = false),
|
||||||
|
leadingPlaceholderCount = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(1L, source.getRefreshKey(state))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun anEmptyQueueLoadsNothing() =
|
fun anEmptyQueueLoadsNothing() =
|
||||||
runTest {
|
runTest {
|
||||||
@@ -84,6 +102,29 @@ class QueuePagingSourceTest {
|
|||||||
assertEquals(null, result.prevKey)
|
assertEquals(null, result.prevKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun itemAt(position: Long) =
|
||||||
|
QueueWindowItem(
|
||||||
|
position,
|
||||||
|
SongListItem(
|
||||||
|
song =
|
||||||
|
Song(
|
||||||
|
sourceId = 1,
|
||||||
|
id = "s$position",
|
||||||
|
albumId = "al1",
|
||||||
|
artistId = "ar1",
|
||||||
|
title = "Song $position",
|
||||||
|
album = "Album",
|
||||||
|
artist = "Artist",
|
||||||
|
duration = 100,
|
||||||
|
track = position,
|
||||||
|
disc = 1,
|
||||||
|
starred = null,
|
||||||
|
genre = null,
|
||||||
|
),
|
||||||
|
coverArt = null,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
private suspend fun load(
|
private suspend fun load(
|
||||||
source: PagingSource<Long, QueueWindowItem>,
|
source: PagingSource<Long, QueueWindowItem>,
|
||||||
key: Long?,
|
key: Long?,
|
||||||
|
|||||||
Reference in New Issue
Block a user