split the song projections; the queue carries the playlist key

`SongListItem` carried a nullable `position` that only playlist rows have,
so the album/song queries projected a fake `NULL AS position` just to map
the shared type — a leak of the playlist concept into unrelated reads.

Split it into `AlbumSongItem` (song, coverArt) and `PlaylistSongItem` (song,
coverArt, position), plus a `SongItem` interface so the queue stays generic
over kinds. The queue's `QueueWindowItem` now carries the optional
`playlistPosition` for the keyset cursor, so no projection fabricates it.
Playlist detail and its paging use `PlaylistSongItem`.
This commit is contained in:
austinried
2026-10-04 00:30:01 +00:00
parent 649528e338
commit 87dc4499ca
12 changed files with 75 additions and 49 deletions
@@ -146,7 +146,7 @@ class QueueKeysetTest {
val actual =
db
.queueDao()
.playlistSongsFrom(1, "pl1", anchor.position!!, skip = 0, limit = 5)
.playlistSongsFrom(1, "pl1", anchor.position, skip = 0, limit = 5)
.map { it.song.id }
assertEquals(expected, actual)
}
@@ -234,7 +234,7 @@ class QueueKeysetTest {
seedLibrary()
val plan =
queryPlan(
"SELECT songs.*, albums.coverArt AS coverArt, NULL AS position FROM songs " +
"SELECT songs.*, albums.coverArt AS coverArt FROM songs " +
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
"WHERE songs.sourceId = 1 AND songs.albumId = 'big' " +
"AND (songs.disc, songs.track, songs.id) >= (1, 1, 'b0') " +
@@ -33,8 +33,8 @@ import com.subtracks.data.model.Album
import com.subtracks.data.model.Artist
import com.subtracks.data.model.CoverArtRef
import com.subtracks.data.model.Playlist
import com.subtracks.data.model.PlaylistSongItem
import com.subtracks.data.model.Song
import com.subtracks.data.model.SongListItem
import com.subtracks.data.model.Source
import com.subtracks.data.prefs.StreamQuality
import com.subtracks.playback.PlaybackState
@@ -530,8 +530,12 @@ private object Fixtures {
)
val songItems =
songs.map { song ->
SongListItem(song = song, coverArt = albums.firstOrNull { it.id == song.albumId }?.coverArt)
songs.mapIndexed { index, song ->
PlaylistSongItem(
song = song,
coverArt = albums.firstOrNull { it.id == song.albumId }?.coverArt,
position = index.toLong(),
)
}
val playlists =
@@ -8,9 +8,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.subtracks.data.db.SubtracksDatabase
import com.subtracks.data.download.ArtworkStore
import com.subtracks.data.download.FakeDownloadEngine
import com.subtracks.data.model.AlbumSongItem
import com.subtracks.data.model.QueueKind
import com.subtracks.data.model.Song
import com.subtracks.data.model.SongListItem
import com.subtracks.data.model.Source
import com.subtracks.data.prefs.fakeUserPreferences
import com.subtracks.data.repo.DownloadRepository
@@ -408,7 +408,7 @@ class QueueViewModelTest {
QueueRow(
position.toLong(),
position.toLong(),
SongListItem(
AlbumSongItem(
song =
Song(
sourceId = 1,