mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +01:00
songs queue follows the applied sort and starred filter; clear a tab's search when leaving it
This commit is contained in:
@@ -26,7 +26,14 @@ internal const val SONGS_SQL =
|
|||||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||||
"WHERE songs.sourceId = :sourceId " +
|
"WHERE songs.sourceId = :sourceId " +
|
||||||
"ORDER BY albums.albumArtist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, " +
|
"ORDER BY albums.albumArtist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, " +
|
||||||
"songs.title COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id"
|
"songs.title COLLATE NOCASE, songs.id"
|
||||||
|
|
||||||
|
internal const val SONGS_IDS_SELECT =
|
||||||
|
"SELECT songs.id FROM songs " +
|
||||||
|
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||||
|
"WHERE songs.sourceId = :sourceId " +
|
||||||
|
"AND (:starredFilter = 0 OR (:starredFilter = 1 AND songs.starred IS NOT NULL) OR " +
|
||||||
|
"(:starredFilter = 2 AND songs.starred IS NULL)) "
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
|
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
|
||||||
@@ -538,4 +545,81 @@ interface LibraryDao {
|
|||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
albumId: String,
|
albumId: String,
|
||||||
): Flow<List<Song>>
|
): Flow<List<Song>>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT COUNT(*) FROM songs WHERE songs.sourceId = :sourceId AND (:starredFilter = 0 OR (:starredFilter = 1 AND songs.starred IS NOT NULL) OR (:starredFilter = 2 AND songs.starred IS NULL))",
|
||||||
|
)
|
||||||
|
suspend fun songCountForFilter(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
): Long
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"${SONGS_IDS_SELECT}ORDER BY albums.albumArtist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.title COLLATE NOCASE, songs.id",
|
||||||
|
)
|
||||||
|
suspend fun songIdsByAlbum(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
): List<String>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"${SONGS_IDS_SELECT}ORDER BY albums.albumArtist COLLATE NOCASE DESC, songs.album COLLATE NOCASE DESC, " +
|
||||||
|
"songs.disc DESC, songs.track DESC, songs.title COLLATE NOCASE DESC, songs.id DESC",
|
||||||
|
)
|
||||||
|
suspend fun songIdsByAlbumReversed(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
): List<String>
|
||||||
|
|
||||||
|
@Query("${SONGS_IDS_SELECT}ORDER BY songs.title COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id")
|
||||||
|
suspend fun songIdsByTitle(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
): List<String>
|
||||||
|
|
||||||
|
@Query("${SONGS_IDS_SELECT}ORDER BY songs.title COLLATE NOCASE DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id")
|
||||||
|
suspend fun songIdsByTitleReversed(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
): List<String>
|
||||||
|
|
||||||
|
@Query("${SONGS_IDS_SELECT}ORDER BY songs.artist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id")
|
||||||
|
suspend fun songIdsByArtist(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
): List<String>
|
||||||
|
|
||||||
|
@Query("${SONGS_IDS_SELECT}ORDER BY songs.artist COLLATE NOCASE DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id")
|
||||||
|
suspend fun songIdsByArtistReversed(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
): List<String>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"${SONGS_IDS_SELECT}ORDER BY songs.starred IS NULL, songs.starred DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
|
||||||
|
)
|
||||||
|
suspend fun songIdsByStarred(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
): List<String>
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"${SONGS_IDS_SELECT}ORDER BY songs.starred IS NULL, songs.starred ASC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
|
||||||
|
)
|
||||||
|
suspend fun songIdsByStarredReversed(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
): List<String>
|
||||||
|
|
||||||
|
@Query("${SONGS_IDS_SELECT}ORDER BY songs.created DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id")
|
||||||
|
suspend fun songIdsByAdded(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
): List<String>
|
||||||
|
|
||||||
|
@Query("${SONGS_IDS_SELECT}ORDER BY songs.created ASC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id")
|
||||||
|
suspend fun songIdsByAddedReversed(
|
||||||
|
sourceId: Long,
|
||||||
|
starredFilter: Int,
|
||||||
|
): List<String>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,12 +10,20 @@ import com.subtracks.data.model.QueueEntry
|
|||||||
import com.subtracks.data.model.QueueKind
|
import com.subtracks.data.model.QueueKind
|
||||||
import com.subtracks.data.model.ShuffleOrder
|
import com.subtracks.data.model.ShuffleOrder
|
||||||
import com.subtracks.data.model.SongListItem
|
import com.subtracks.data.model.SongListItem
|
||||||
|
import com.subtracks.data.prefs.SongSort
|
||||||
|
import com.subtracks.data.prefs.StarredFilter
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
const val QUEUE_CHUNK = 60
|
const val QUEUE_CHUNK = 60
|
||||||
|
|
||||||
|
data class QueueSongQuery(
|
||||||
|
val sort: SongSort,
|
||||||
|
val descending: Boolean,
|
||||||
|
val starred: StarredFilter,
|
||||||
|
)
|
||||||
|
|
||||||
data class ResolvedQueueEntry(
|
data class ResolvedQueueEntry(
|
||||||
val entry: QueueEntry,
|
val entry: QueueEntry,
|
||||||
val length: Long,
|
val length: Long,
|
||||||
@@ -92,15 +100,78 @@ class QueueRepository(
|
|||||||
|
|
||||||
fun songsEntry(sourceId: Long) = QueueEntry(position = 0, sourceId = sourceId, kind = QueueKind.Songs, refId = "")
|
fun songsEntry(sourceId: Long) = QueueEntry(position = 0, sourceId = sourceId, kind = QueueKind.Songs, refId = "")
|
||||||
|
|
||||||
suspend fun songsIndexOf(
|
fun songsEntry(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
|
query: QueueSongQuery,
|
||||||
|
) = QueueEntry(
|
||||||
|
position = 0,
|
||||||
|
sourceId = sourceId,
|
||||||
|
kind = QueueKind.Songs,
|
||||||
|
refId = "${query.sort.name}|${if (query.descending) 1 else 0}|${query.starred.ordinal}",
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun songsIndexOf(
|
||||||
|
entry: QueueEntry,
|
||||||
songId: String,
|
songId: String,
|
||||||
): Long? =
|
): Long? {
|
||||||
dao
|
val index = songIdsFor(entry).indexOf(songId)
|
||||||
.songIds(sourceId)
|
return if (index >= 0) index.toLong() else null
|
||||||
.indexOf(songId)
|
}
|
||||||
.takeIf { it >= 0 }
|
|
||||||
?.toLong()
|
private fun songsQuery(refId: String): QueueSongQuery? {
|
||||||
|
val parts = refId.split("|")
|
||||||
|
if (parts.size != 3) return null
|
||||||
|
val sort = SongSort.entries.firstOrNull { it.name == parts[0] } ?: return null
|
||||||
|
val starred = StarredFilter.entries.getOrElse(parts[2].toIntOrNull() ?: 0) { StarredFilter.Any }
|
||||||
|
return QueueSongQuery(sort, parts[1] == "1", starred)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun songIdsFor(entry: QueueEntry): List<String> {
|
||||||
|
val query = songsQuery(entry.refId) ?: return dao.songIds(entry.sourceId)
|
||||||
|
val library = db.libraryDao()
|
||||||
|
val starred = query.starred.ordinal
|
||||||
|
return when (query.sort) {
|
||||||
|
SongSort.Album -> {
|
||||||
|
if (query.descending) {
|
||||||
|
library.songIdsByAlbumReversed(entry.sourceId, starred)
|
||||||
|
} else {
|
||||||
|
library.songIdsByAlbum(entry.sourceId, starred)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SongSort.Title -> {
|
||||||
|
if (query.descending) {
|
||||||
|
library.songIdsByTitleReversed(entry.sourceId, starred)
|
||||||
|
} else {
|
||||||
|
library.songIdsByTitle(entry.sourceId, starred)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SongSort.Artist -> {
|
||||||
|
if (query.descending) {
|
||||||
|
library.songIdsByArtistReversed(entry.sourceId, starred)
|
||||||
|
} else {
|
||||||
|
library.songIdsByArtist(entry.sourceId, starred)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SongSort.Starred -> {
|
||||||
|
if (query.descending) {
|
||||||
|
library.songIdsByStarredReversed(entry.sourceId, starred)
|
||||||
|
} else {
|
||||||
|
library.songIdsByStarred(entry.sourceId, starred)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SongSort.Added -> {
|
||||||
|
if (query.descending) {
|
||||||
|
library.songIdsByAddedReversed(entry.sourceId, starred)
|
||||||
|
} else {
|
||||||
|
library.songIdsByAdded(entry.sourceId, starred)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun replace(
|
suspend fun replace(
|
||||||
entries: List<QueueEntry>,
|
entries: List<QueueEntry>,
|
||||||
@@ -176,19 +247,15 @@ class QueueRepository(
|
|||||||
snapshot: QueueSnapshot,
|
snapshot: QueueSnapshot,
|
||||||
position: Long,
|
position: Long,
|
||||||
): SongListItem? {
|
): SongListItem? {
|
||||||
if (snapshot.shuffled) {
|
val flat = snapshot.flatPosition(position) ?: return null
|
||||||
|
val id = flatIds(snapshot).getOrNull(flat.toInt()) ?: return null
|
||||||
val sourceId =
|
val sourceId =
|
||||||
snapshot.entries
|
snapshot.entries
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.entry
|
?.entry
|
||||||
?.sourceId ?: return null
|
?.sourceId ?: return null
|
||||||
val flat = snapshot.flatPosition(position) ?: return null
|
|
||||||
val id = flatIds(snapshot).getOrNull(flat.toInt()) ?: return null
|
|
||||||
return dao.songsByIds(sourceId, listOf(id)).firstOrNull()
|
return dao.songsByIds(sourceId, listOf(id)).firstOrNull()
|
||||||
}
|
}
|
||||||
val (entry, offset) = snapshot.locate(position) ?: return null
|
|
||||||
return rows(entry, entry.offset + offset, 1).firstOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun flatIndexOf(
|
suspend fun flatIndexOf(
|
||||||
snapshot: QueueSnapshot,
|
snapshot: QueueSnapshot,
|
||||||
@@ -204,12 +271,7 @@ class QueueRepository(
|
|||||||
first: Long,
|
first: Long,
|
||||||
last: Long,
|
last: Long,
|
||||||
): List<QueueWindowItem> {
|
): List<QueueWindowItem> {
|
||||||
if (snapshot.shuffled) {
|
if (snapshot.size == 0L) return emptyList()
|
||||||
val sourceId =
|
|
||||||
snapshot.entries
|
|
||||||
.firstOrNull()
|
|
||||||
?.entry
|
|
||||||
?.sourceId ?: return emptyList()
|
|
||||||
val ids = flatIds(snapshot)
|
val ids = flatIds(snapshot)
|
||||||
val requested = mutableListOf<Pair<Long, String>>()
|
val requested = mutableListOf<Pair<Long, String>>()
|
||||||
for (position in first..last) {
|
for (position in first..last) {
|
||||||
@@ -218,24 +280,14 @@ class QueueRepository(
|
|||||||
requested += position to id
|
requested += position to id
|
||||||
}
|
}
|
||||||
if (requested.isEmpty()) return emptyList()
|
if (requested.isEmpty()) return emptyList()
|
||||||
|
val sourceId =
|
||||||
|
snapshot.entries
|
||||||
|
.firstOrNull()
|
||||||
|
?.entry
|
||||||
|
?.sourceId ?: return emptyList()
|
||||||
val byId = dao.songsByIds(sourceId, requested.map { it.second }.distinct()).associateBy { it.song.id }
|
val byId = dao.songsByIds(sourceId, requested.map { it.second }.distinct()).associateBy { it.song.id }
|
||||||
return requested.mapNotNull { (position, id) -> byId[id]?.let { QueueWindowItem(position, it) } }
|
return requested.mapNotNull { (position, id) -> byId[id]?.let { QueueWindowItem(position, it) } }
|
||||||
}
|
}
|
||||||
val items = mutableListOf<QueueWindowItem>()
|
|
||||||
var start = 0L
|
|
||||||
for (resolved in snapshot.entries) {
|
|
||||||
val end = start + resolved.length - 1
|
|
||||||
val entryStart = start
|
|
||||||
start += resolved.length
|
|
||||||
if (resolved.length == 0L || end < first || entryStart > last) continue
|
|
||||||
val from = maxOf(first, entryStart)
|
|
||||||
val to = minOf(last, end)
|
|
||||||
val offset = resolved.entry.offset + (from - entryStart)
|
|
||||||
rows(resolved.entry, offset, (to - from + 1).toInt())
|
|
||||||
.forEachIndexed { index, item -> items += QueueWindowItem(from + index, item) }
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun window(
|
suspend fun window(
|
||||||
snapshot: QueueSnapshot,
|
snapshot: QueueSnapshot,
|
||||||
@@ -322,7 +374,7 @@ class QueueRepository(
|
|||||||
when (entry.kind) {
|
when (entry.kind) {
|
||||||
QueueKind.Song -> listOf(entry.refId)
|
QueueKind.Song -> listOf(entry.refId)
|
||||||
QueueKind.Album -> dao.albumSongIds(entry.sourceId, entry.refId)
|
QueueKind.Album -> dao.albumSongIds(entry.sourceId, entry.refId)
|
||||||
QueueKind.Songs -> dao.songIds(entry.sourceId)
|
QueueKind.Songs -> songIdsFor(entry)
|
||||||
QueueKind.Playlist -> dao.playlistSongIds(entry.sourceId, entry.refId)
|
QueueKind.Playlist -> dao.playlistSongIds(entry.sourceId, entry.refId)
|
||||||
}
|
}
|
||||||
val from = entry.offset.toInt().coerceIn(0, entryIds.size)
|
val from = entry.offset.toInt().coerceIn(0, entryIds.size)
|
||||||
@@ -334,25 +386,25 @@ class QueueRepository(
|
|||||||
return ids
|
return ids
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun rows(
|
|
||||||
entry: QueueEntry,
|
|
||||||
offset: Long,
|
|
||||||
limit: Int,
|
|
||||||
): List<SongListItem> =
|
|
||||||
when (entry.kind) {
|
|
||||||
QueueKind.Playlist -> dao.playlistSongs(entry.sourceId, entry.refId, offset, limit)
|
|
||||||
QueueKind.Album -> dao.albumSongs(entry.sourceId, entry.refId, offset, limit)
|
|
||||||
QueueKind.Songs -> dao.songs(entry.sourceId, offset, limit)
|
|
||||||
QueueKind.Song -> dao.song(entry.sourceId, entry.refId, offset, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun QueueEntry.resolvedLength(): Long {
|
private suspend fun QueueEntry.resolvedLength(): Long {
|
||||||
val total =
|
val total =
|
||||||
when (kind) {
|
when (kind) {
|
||||||
QueueKind.Song -> dao.songLength(sourceId, refId)
|
QueueKind.Song -> {
|
||||||
QueueKind.Playlist -> dao.playlistLength(sourceId, refId)
|
dao.songLength(sourceId, refId)
|
||||||
QueueKind.Album -> dao.albumLength(sourceId, refId)
|
}
|
||||||
QueueKind.Songs -> dao.songsLength(sourceId)
|
|
||||||
|
QueueKind.Playlist -> {
|
||||||
|
dao.playlistLength(sourceId, refId)
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueKind.Album -> {
|
||||||
|
dao.albumLength(sourceId, refId)
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueKind.Songs -> {
|
||||||
|
songsQuery(refId)?.let { db.libraryDao().songCountForFilter(sourceId, it.starred.ordinal) }
|
||||||
|
?: dao.songsLength(sourceId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val available = (total - offset).coerceAtLeast(0)
|
val available = (total - offset).coerceAtLeast(0)
|
||||||
return count?.coerceAtMost(available) ?: available
|
return count?.coerceAtMost(available) ?: available
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.subtracks.data.model.QueueEntry
|
|||||||
import com.subtracks.data.model.QueueKind
|
import com.subtracks.data.model.QueueKind
|
||||||
import com.subtracks.data.repo.QueueRepository
|
import com.subtracks.data.repo.QueueRepository
|
||||||
import com.subtracks.data.repo.QueueSnapshot
|
import com.subtracks.data.repo.QueueSnapshot
|
||||||
|
import com.subtracks.data.repo.QueueSongQuery
|
||||||
import com.subtracks.data.repo.QueueWindowItem
|
import com.subtracks.data.repo.QueueWindowItem
|
||||||
import com.subtracks.data.repo.SourceRepository
|
import com.subtracks.data.repo.SourceRepository
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
@@ -195,10 +196,12 @@ class PlaybackController(
|
|||||||
fun playSong(
|
fun playSong(
|
||||||
sourceId: Long,
|
sourceId: Long,
|
||||||
songId: String,
|
songId: String,
|
||||||
|
query: QueueSongQuery,
|
||||||
) {
|
) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val index = queueRepository.songsIndexOf(sourceId, songId) ?: return@launch
|
val entry = queueRepository.songsEntry(sourceId, query)
|
||||||
play(listOf(queueRepository.songsEntry(sourceId)), index)
|
val index = queueRepository.songsIndexOf(entry, songId) ?: return@launch
|
||||||
|
play(listOf(entry), index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,9 +169,16 @@ fun LibraryRoute(
|
|||||||
viewModel: LibraryViewModel = koinViewModel(),
|
viewModel: LibraryViewModel = koinViewModel(),
|
||||||
) {
|
) {
|
||||||
var selectedTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) }
|
var selectedTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) }
|
||||||
|
var previousTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) }
|
||||||
val syncing by viewModel.syncing.collectAsStateWithLifecycle()
|
val syncing by viewModel.syncing.collectAsStateWithLifecycle()
|
||||||
val playingSongId by viewModel.playingSongId.collectAsStateWithLifecycle()
|
val playingSongId by viewModel.playingSongId.collectAsStateWithLifecycle()
|
||||||
val listTab = selectedTab.listTab()
|
val listTab = selectedTab.listTab()
|
||||||
|
LaunchedEffect(selectedTab) {
|
||||||
|
if (previousTab != selectedTab) {
|
||||||
|
viewModel.setSearch(previousTab.listTab(), "")
|
||||||
|
previousTab = selectedTab
|
||||||
|
}
|
||||||
|
}
|
||||||
val listQuery by viewModel.listQuery(listTab).collectAsStateWithLifecycle()
|
val listQuery by viewModel.listQuery(listTab).collectAsStateWithLifecycle()
|
||||||
val search by viewModel.search(listTab).collectAsStateWithLifecycle()
|
val search by viewModel.search(listTab).collectAsStateWithLifecycle()
|
||||||
val resetKeys =
|
val resetKeys =
|
||||||
@@ -271,7 +278,7 @@ fun LibraryScreen(
|
|||||||
if (pagerState.currentPage != selectedTab.ordinal) {
|
if (pagerState.currentPage != selectedTab.ordinal) {
|
||||||
pagerState.animateScrollToPage(selectedTab.ordinal)
|
pagerState.animateScrollToPage(selectedTab.ordinal)
|
||||||
}
|
}
|
||||||
searchActive = search.isNotEmpty()
|
searchActive = false
|
||||||
}
|
}
|
||||||
|
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import com.subtracks.data.prefs.PlaylistSort
|
|||||||
import com.subtracks.data.prefs.SongSort
|
import com.subtracks.data.prefs.SongSort
|
||||||
import com.subtracks.data.prefs.UserPreferences
|
import com.subtracks.data.prefs.UserPreferences
|
||||||
import com.subtracks.data.repo.LibraryRepository
|
import com.subtracks.data.repo.LibraryRepository
|
||||||
|
import com.subtracks.data.repo.QueueSongQuery
|
||||||
import com.subtracks.data.repo.SourceRepository
|
import com.subtracks.data.repo.SourceRepository
|
||||||
import com.subtracks.data.sync.SyncManager
|
import com.subtracks.data.sync.SyncManager
|
||||||
import com.subtracks.data.sync.SyncStatus
|
import com.subtracks.data.sync.SyncStatus
|
||||||
@@ -132,7 +133,8 @@ class LibraryViewModel(
|
|||||||
fun playSong(songId: String) {
|
fun playSong(songId: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val sourceId = sourceRepository.activeSourceIdOnce() ?: return@launch
|
val sourceId = sourceRepository.activeSourceIdOnce() ?: return@launch
|
||||||
playbackController.playSong(sourceId, songId)
|
val query = listQueries.getValue(LibraryListTab.Songs).value
|
||||||
|
playbackController.playSong(sourceId, songId, QueueSongQuery(query.songSort(), query.descending, query.starred))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import com.subtracks.data.model.QueueEntry
|
|||||||
import com.subtracks.data.model.QueueKind
|
import com.subtracks.data.model.QueueKind
|
||||||
import com.subtracks.data.model.Song
|
import com.subtracks.data.model.Song
|
||||||
import com.subtracks.data.model.Source
|
import com.subtracks.data.model.Source
|
||||||
|
import com.subtracks.data.prefs.SongSort
|
||||||
|
import com.subtracks.data.prefs.StarredFilter
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
@@ -312,6 +314,39 @@ class QueueRepositoryTest {
|
|||||||
assertEquals(listOf("s2", "s3", "s1"), resolveAll(repository.snapshot()))
|
assertEquals(listOf("s2", "s3", "s1"), resolveAll(repository.snapshot()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun theSongsEntryFollowsItsSortAndStarredFilter() =
|
||||||
|
runTest {
|
||||||
|
seedLibrary()
|
||||||
|
db.libraryDao().upsertSongs(
|
||||||
|
listOf(
|
||||||
|
song("s1", "al1", track = 1, album = "First Album", title = "Charlie", starred = 5),
|
||||||
|
song("s2", "al1", track = 2, album = "First Album", title = "Alpha"),
|
||||||
|
song("s3", "al1", track = 3, album = "First Album", title = "Bravo", starred = 7),
|
||||||
|
song("s4", "al2", track = 1, album = "Second Album", title = "Echo"),
|
||||||
|
song("s5", "al2", track = 2, album = "Second Album", title = "Delta", starred = 3),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val byTitle =
|
||||||
|
repository.snapshotAfter(
|
||||||
|
listOf(repository.songsEntry(1, QueueSongQuery(SongSort.Title, descending = false, StarredFilter.Any))),
|
||||||
|
)
|
||||||
|
assertEquals(listOf("s2", "s3", "s1", "s5", "s4"), resolveAll(byTitle))
|
||||||
|
|
||||||
|
val starredOnly =
|
||||||
|
repository.snapshotAfter(
|
||||||
|
listOf(repository.songsEntry(1, QueueSongQuery(SongSort.Starred, descending = false, StarredFilter.Starred))),
|
||||||
|
)
|
||||||
|
assertEquals(listOf("s3", "s1", "s5"), resolveAll(starredOnly))
|
||||||
|
|
||||||
|
val unstarred =
|
||||||
|
repository.snapshotAfter(
|
||||||
|
listOf(repository.songsEntry(1, QueueSongQuery(SongSort.Album, descending = false, StarredFilter.NotStarred))),
|
||||||
|
)
|
||||||
|
assertEquals(listOf("s2", "s4"), resolveAll(unstarred))
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun QueueRepository.snapshotAfter(entries: List<QueueEntry>): QueueSnapshot {
|
private suspend fun QueueRepository.snapshotAfter(entries: List<QueueEntry>): QueueSnapshot {
|
||||||
replace(entries)
|
replace(entries)
|
||||||
return snapshot()
|
return snapshot()
|
||||||
@@ -379,18 +414,22 @@ class QueueRepositoryTest {
|
|||||||
albumId: String,
|
albumId: String,
|
||||||
track: Long,
|
track: Long,
|
||||||
album: String = "Album",
|
album: String = "Album",
|
||||||
|
title: String = "Song $id",
|
||||||
|
starred: Long? = null,
|
||||||
|
created: Long = 0,
|
||||||
) = Song(
|
) = Song(
|
||||||
sourceId = 1,
|
sourceId = 1,
|
||||||
id = id,
|
id = id,
|
||||||
albumId = albumId,
|
albumId = albumId,
|
||||||
artistId = "ar1",
|
artistId = "ar1",
|
||||||
title = "Song $id",
|
title = title,
|
||||||
album = album,
|
album = album,
|
||||||
artist = "Artist",
|
artist = "Artist",
|
||||||
duration = 100,
|
duration = 100,
|
||||||
track = track,
|
track = track,
|
||||||
disc = 1,
|
disc = 1,
|
||||||
starred = null,
|
starred = starred,
|
||||||
genre = null,
|
genre = null,
|
||||||
|
created = created,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user