From c2ecc63fe51a621540836edf48f7bc51776e9c36 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Wed, 23 Sep 2026 00:55:48 +0000 Subject: [PATCH] fix the queue paging refresh key and drag edge detection --- .../subtracks/data/repo/QueuePagingSource.kt | 20 ++++++--- .../subtracks/playback/PlaybackController.kt | 17 ++++++-- .../com/subtracks/ui/playback/QueueScreen.kt | 5 ++- .../data/repo/QueuePagingSourceTest.kt | 41 +++++++++++++++++++ 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/data/repo/QueuePagingSource.kt b/app/src/main/kotlin/com/subtracks/data/repo/QueuePagingSource.kt index 45387c43..0a7eb8f9 100644 --- a/app/src/main/kotlin/com/subtracks/data/repo/QueuePagingSource.kt +++ b/app/src/main/kotlin/com/subtracks/data/repo/QueuePagingSource.kt @@ -9,19 +9,27 @@ class QueuePagingSource( private val repository: QueueRepository, private val initialPosition: Long, ) : PagingSource() { - override fun getRefreshKey(state: PagingState): Long? = - state.anchorPosition?.let { (it / QUEUE_PAGE_SIZE).toLong() } + override fun getRefreshKey(state: PagingState): Long? { + 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): LoadResult { 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 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) return LoadResult.Page( 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, ) } diff --git a/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt b/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt index d0a142a5..6e690f7a 100644 --- a/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt +++ b/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt @@ -49,11 +49,13 @@ data class PlaybackState( private sealed interface QueueUndo { val entries: List val cursor: Long + val current: Long? } private data class RemovedUndo( override val entries: List, override val cursor: Long, + override val current: Long, val position: Long, val item: QueueItem?, ) : QueueUndo @@ -61,6 +63,7 @@ private data class RemovedUndo( private data class MovedUndo( override val entries: List, override val cursor: Long, + override val current: Long, val from: Long, val to: Long, ) : QueueUndo @@ -68,7 +71,9 @@ private data class MovedUndo( private data class ReloadUndo( override val entries: List, override val cursor: Long, -) : QueueUndo +) : QueueUndo { + override val current: Long? = null +} class PlaybackController( private val sourceRepository: SourceRepository, @@ -195,7 +200,7 @@ class PlaybackController( if (removed == null) { ReloadUndo(entries, cursor) } else { - RemovedUndo(entries, cursor, position, removed) + RemovedUndo(entries, cursor, target, position, removed) } when { position == current -> { @@ -242,7 +247,7 @@ class PlaybackController( val inWindow = from in windowStart..windowEnd && to in windowStart..windowEnd lastEdit = if (inWindow) { - MovedUndo(entries, cursor, from, to) + MovedUndo(entries, cursor, target, from, to) } else { ReloadUndo(entries, cursor) } @@ -275,7 +280,11 @@ class PlaybackController( val target = undo.cursor.coerceIn(0, restored.size - 1) val restoredId = queueRepository.itemAt(restored, target)?.song?.id 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 = when { !canMirror -> { diff --git a/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt b/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt index d94fe0c8..4d38de0d 100644 --- a/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/playback/QueueScreen.kt @@ -180,10 +180,11 @@ fun QueueScreen( val from = dragging ?: break val info = listState.layoutInfo.visibleItemsInfo.firstOrNull { items.peek(it.index)?.position == from } if (info != null) { + val top = info.offset + dragOffset val edge = when { - info.offset < listState.layoutInfo.viewportStartOffset + threshold -> -step - info.offset + info.size > listState.layoutInfo.viewportEndOffset - threshold -> step + top < listState.layoutInfo.viewportStartOffset + threshold -> -step + top + info.size > listState.layoutInfo.viewportEndOffset - threshold -> step else -> 0f } if (edge != 0f) { diff --git a/app/src/test/kotlin/com/subtracks/data/repo/QueuePagingSourceTest.kt b/app/src/test/kotlin/com/subtracks/data/repo/QueuePagingSourceTest.kt index 39214b47..73a4eb3b 100644 --- a/app/src/test/kotlin/com/subtracks/data/repo/QueuePagingSourceTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/repo/QueuePagingSourceTest.kt @@ -1,13 +1,16 @@ package com.subtracks.data.repo import android.content.Context +import androidx.paging.PagingConfig import androidx.paging.PagingSource +import androidx.paging.PagingState import androidx.room3.Room import androidx.sqlite.driver.bundled.BundledSQLiteDriver import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.data.db.SubtracksDatabase import com.subtracks.data.model.Song +import com.subtracks.data.model.SongListItem import com.subtracks.data.model.Source import kotlinx.coroutines.test.runTest import org.junit.After @@ -74,6 +77,21 @@ class QueuePagingSourceTest { 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 fun anEmptyQueueLoadsNothing() = runTest { @@ -84,6 +102,29 @@ class QueuePagingSourceTest { 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( source: PagingSource, key: Long?,