mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
drop the songs tab and the whole-library song queue
This commit is contained in:
@@ -23,27 +23,6 @@ internal const val PLAYLIST_SONGS_SQL =
|
||||
"WHERE playlist_songs.sourceId = :sourceId AND playlist_songs.playlistId = :playlistId " +
|
||||
"ORDER BY playlist_songs.position"
|
||||
|
||||
internal const val SONGS_SQL =
|
||||
"SELECT songs.*, albums.coverArt AS coverArt FROM songs " +
|
||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||
"WHERE songs.sourceId = :sourceId " +
|
||||
"ORDER BY albums.albumArtist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, " +
|
||||
"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)) "
|
||||
|
||||
internal const val SONGS_LIST_SELECT =
|
||||
"SELECT songs.*, albums.coverArt AS coverArt 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
|
||||
@DaoReturnTypeConverters(PagingSourceDaoReturnTypeConverter::class)
|
||||
interface LibraryDao {
|
||||
@@ -431,168 +410,6 @@ interface LibraryDao {
|
||||
search: String,
|
||||
): PagingSource<Int, Playlist>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt 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)) " +
|
||||
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
|
||||
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
|
||||
"ORDER BY albums.albumArtist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, " +
|
||||
"songs.title COLLATE NOCASE, songs.id",
|
||||
)
|
||||
fun songs(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
search: String,
|
||||
): PagingSource<Int, SongListItem>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt 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)) " +
|
||||
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
|
||||
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
|
||||
"ORDER BY albums.albumArtist COLLATE NOCASE DESC, songs.album COLLATE NOCASE DESC, songs.disc DESC, " +
|
||||
"songs.track DESC, songs.title COLLATE NOCASE DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id DESC",
|
||||
)
|
||||
fun songsByAlbumReversed(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
search: String,
|
||||
): PagingSource<Int, SongListItem>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt 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)) " +
|
||||
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
|
||||
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
|
||||
"ORDER BY songs.title COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
|
||||
)
|
||||
fun songsByTitle(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
search: String,
|
||||
): PagingSource<Int, SongListItem>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt 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)) " +
|
||||
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
|
||||
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
|
||||
"ORDER BY songs.title COLLATE NOCASE DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
|
||||
)
|
||||
fun songsByTitleReversed(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
search: String,
|
||||
): PagingSource<Int, SongListItem>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt 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)) " +
|
||||
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
|
||||
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
|
||||
"ORDER BY songs.artist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
|
||||
)
|
||||
fun songsByArtist(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
search: String,
|
||||
): PagingSource<Int, SongListItem>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt 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)) " +
|
||||
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
|
||||
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
|
||||
"ORDER BY songs.artist COLLATE NOCASE DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
|
||||
)
|
||||
fun songsByArtistReversed(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
search: String,
|
||||
): PagingSource<Int, SongListItem>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt 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)) " +
|
||||
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
|
||||
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
|
||||
"ORDER BY songs.starred IS NULL, songs.starred DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
|
||||
)
|
||||
fun songsByStarred(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
search: String,
|
||||
): PagingSource<Int, SongListItem>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt 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)) " +
|
||||
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
|
||||
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
|
||||
"ORDER BY songs.starred IS NULL, songs.starred ASC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
|
||||
)
|
||||
fun songsByStarredReversed(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
search: String,
|
||||
): PagingSource<Int, SongListItem>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt 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)) " +
|
||||
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
|
||||
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
|
||||
"ORDER BY songs.created DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
|
||||
)
|
||||
fun songsByAdded(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
search: String,
|
||||
): PagingSource<Int, SongListItem>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt 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)) " +
|
||||
"AND (:search = '' OR instr(lower(songs.title), lower(:search)) > 0 " +
|
||||
"OR instr(lower(songs.artist), lower(:search)) > 0 OR instr(lower(songs.album), lower(:search)) > 0) " +
|
||||
"ORDER BY songs.created ASC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id",
|
||||
)
|
||||
fun songsByAddedReversed(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
search: String,
|
||||
): PagingSource<Int, SongListItem>
|
||||
|
||||
@Query(PLAYLIST_SONGS_SQL)
|
||||
fun playlistSongs(
|
||||
sourceId: Long,
|
||||
@@ -640,181 +457,4 @@ interface LibraryDao {
|
||||
sourceId: Long,
|
||||
albumId: String,
|
||||
): 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>
|
||||
|
||||
@Query(
|
||||
"${SONGS_LIST_SELECT}ORDER BY albums.albumArtist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.title COLLATE NOCASE, songs.id LIMIT :limit OFFSET :offset",
|
||||
)
|
||||
suspend fun songsPageByAlbum(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
limit: Int,
|
||||
offset: Long,
|
||||
): List<SongListItem>
|
||||
|
||||
@Query(
|
||||
"${SONGS_LIST_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 LIMIT :limit OFFSET :offset",
|
||||
)
|
||||
suspend fun songsPageByAlbumReversed(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
limit: Int,
|
||||
offset: Long,
|
||||
): List<SongListItem>
|
||||
|
||||
@Query(
|
||||
"${SONGS_LIST_SELECT}ORDER BY songs.title COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id LIMIT :limit OFFSET :offset",
|
||||
)
|
||||
suspend fun songsPageByTitle(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
limit: Int,
|
||||
offset: Long,
|
||||
): List<SongListItem>
|
||||
|
||||
@Query(
|
||||
"${SONGS_LIST_SELECT}ORDER BY songs.title COLLATE NOCASE DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id LIMIT :limit OFFSET :offset",
|
||||
)
|
||||
suspend fun songsPageByTitleReversed(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
limit: Int,
|
||||
offset: Long,
|
||||
): List<SongListItem>
|
||||
|
||||
@Query(
|
||||
"${SONGS_LIST_SELECT}ORDER BY songs.artist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id LIMIT :limit OFFSET :offset",
|
||||
)
|
||||
suspend fun songsPageByArtist(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
limit: Int,
|
||||
offset: Long,
|
||||
): List<SongListItem>
|
||||
|
||||
@Query(
|
||||
"${SONGS_LIST_SELECT}ORDER BY songs.artist COLLATE NOCASE DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id LIMIT :limit OFFSET :offset",
|
||||
)
|
||||
suspend fun songsPageByArtistReversed(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
limit: Int,
|
||||
offset: Long,
|
||||
): List<SongListItem>
|
||||
|
||||
@Query(
|
||||
"${SONGS_LIST_SELECT}ORDER BY songs.starred IS NULL, songs.starred DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id LIMIT :limit OFFSET :offset",
|
||||
)
|
||||
suspend fun songsPageByStarred(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
limit: Int,
|
||||
offset: Long,
|
||||
): List<SongListItem>
|
||||
|
||||
@Query(
|
||||
"${SONGS_LIST_SELECT}ORDER BY songs.starred IS NULL, songs.starred ASC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id LIMIT :limit OFFSET :offset",
|
||||
)
|
||||
suspend fun songsPageByStarredReversed(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
limit: Int,
|
||||
offset: Long,
|
||||
): List<SongListItem>
|
||||
|
||||
@Query(
|
||||
"${SONGS_LIST_SELECT}ORDER BY songs.created DESC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id LIMIT :limit OFFSET :offset",
|
||||
)
|
||||
suspend fun songsPageByAdded(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
limit: Int,
|
||||
offset: Long,
|
||||
): List<SongListItem>
|
||||
|
||||
@Query(
|
||||
"${SONGS_LIST_SELECT}ORDER BY songs.created ASC, songs.album COLLATE NOCASE, songs.disc, songs.track, songs.id LIMIT :limit OFFSET :offset",
|
||||
)
|
||||
suspend fun songsPageByAddedReversed(
|
||||
sourceId: Long,
|
||||
starredFilter: Int,
|
||||
limit: Int,
|
||||
offset: Long,
|
||||
): List<SongListItem>
|
||||
}
|
||||
|
||||
@@ -62,9 +62,6 @@ interface QueueDao {
|
||||
albumId: String,
|
||||
): Long
|
||||
|
||||
@Query("SELECT COUNT(*) FROM songs WHERE sourceId = :sourceId")
|
||||
suspend fun songsLength(sourceId: Long): Long
|
||||
|
||||
@Query("SELECT COUNT(*) FROM songs WHERE sourceId = :sourceId AND id = :songId")
|
||||
suspend fun songLength(
|
||||
sourceId: Long,
|
||||
@@ -87,13 +84,6 @@ interface QueueDao {
|
||||
limit: Int,
|
||||
): List<SongListItem>
|
||||
|
||||
@Query("$SONGS_SQL LIMIT :limit OFFSET :offset")
|
||||
suspend fun songs(
|
||||
sourceId: Long,
|
||||
offset: Long,
|
||||
limit: Int,
|
||||
): List<SongListItem>
|
||||
|
||||
@Query("$SONG_SQL LIMIT :limit OFFSET :offset")
|
||||
suspend fun song(
|
||||
sourceId: Long,
|
||||
@@ -111,15 +101,6 @@ interface QueueDao {
|
||||
albumId: String,
|
||||
): List<String>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.id FROM songs " +
|
||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||
"WHERE songs.sourceId = :sourceId " +
|
||||
"ORDER BY albums.albumArtist COLLATE NOCASE, songs.album COLLATE NOCASE, songs.disc, songs.track, " +
|
||||
"songs.title COLLATE NOCASE, songs.id",
|
||||
)
|
||||
suspend fun songIds(sourceId: Long): List<String>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.id FROM playlist_songs " +
|
||||
"JOIN songs ON songs.sourceId = playlist_songs.sourceId AND songs.id = playlist_songs.songId " +
|
||||
|
||||
@@ -191,7 +191,7 @@ data class DiscKey(
|
||||
val disc: Long,
|
||||
)
|
||||
|
||||
enum class QueueKind { Playlist, Album, Song, Songs }
|
||||
enum class QueueKind { Playlist, Album, Song }
|
||||
|
||||
@Entity(
|
||||
tableName = "queue_entries",
|
||||
@@ -261,5 +261,5 @@ class QueueKindConverter {
|
||||
fun fromQueueKind(kind: QueueKind): String = kind.name
|
||||
|
||||
@ColumnTypeConverter
|
||||
fun toQueueKind(value: String): QueueKind = QueueKind.valueOf(value)
|
||||
fun toQueueKind(value: String): QueueKind = QueueKind.entries.firstOrNull { it.name == value } ?: QueueKind.Song
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ enum class ArtistSort { Name, AlbumCount, Starred }
|
||||
|
||||
enum class PlaylistSort { Name, Added, Updated }
|
||||
|
||||
enum class SongSort { Album, Title, Artist, Starred, Added }
|
||||
|
||||
enum class LibraryListTab(
|
||||
val key: String,
|
||||
val defaultSort: String,
|
||||
@@ -26,7 +24,6 @@ enum class LibraryListTab(
|
||||
Albums("albums", AlbumSort.Name.name, true),
|
||||
Artists("artists", ArtistSort.Name.name, true),
|
||||
Playlists("playlists", PlaylistSort.Name.name, false),
|
||||
Songs("songs", SongSort.Album.name, true),
|
||||
}
|
||||
|
||||
enum class StarredFilter { Any, Starred, NotStarred }
|
||||
|
||||
@@ -14,7 +14,6 @@ import com.subtracks.data.model.SongListItem
|
||||
import com.subtracks.data.prefs.AlbumSort
|
||||
import com.subtracks.data.prefs.ArtistSort
|
||||
import com.subtracks.data.prefs.PlaylistSort
|
||||
import com.subtracks.data.prefs.SongSort
|
||||
import com.subtracks.data.prefs.StarredFilter
|
||||
import com.subtracks.data.source.StarType
|
||||
import kotlinx.coroutines.CancellationException
|
||||
@@ -155,58 +154,6 @@ class LibraryRepository(
|
||||
}
|
||||
}
|
||||
|
||||
fun songs(
|
||||
sourceId: Long,
|
||||
sort: SongSort,
|
||||
descending: Boolean,
|
||||
starred: StarredFilter,
|
||||
search: String,
|
||||
): Flow<PagingData<SongListItem>> =
|
||||
pager(60) {
|
||||
val dao = db.libraryDao()
|
||||
when (sort) {
|
||||
SongSort.Album -> {
|
||||
if (descending) {
|
||||
dao.songsByAlbumReversed(sourceId, starred.ordinal, search)
|
||||
} else {
|
||||
dao.songs(sourceId, starred.ordinal, search)
|
||||
}
|
||||
}
|
||||
|
||||
SongSort.Title -> {
|
||||
if (descending) {
|
||||
dao.songsByTitleReversed(sourceId, starred.ordinal, search)
|
||||
} else {
|
||||
dao.songsByTitle(sourceId, starred.ordinal, search)
|
||||
}
|
||||
}
|
||||
|
||||
SongSort.Artist -> {
|
||||
if (descending) {
|
||||
dao.songsByArtistReversed(sourceId, starred.ordinal, search)
|
||||
} else {
|
||||
dao.songsByArtist(sourceId, starred.ordinal, search)
|
||||
}
|
||||
}
|
||||
|
||||
SongSort.Starred -> {
|
||||
if (descending) {
|
||||
dao.songsByStarredReversed(sourceId, starred.ordinal, search)
|
||||
} else {
|
||||
dao.songsByStarred(sourceId, starred.ordinal, search)
|
||||
}
|
||||
}
|
||||
|
||||
SongSort.Added -> {
|
||||
if (descending) {
|
||||
dao.songsByAddedReversed(sourceId, starred.ordinal, search)
|
||||
} else {
|
||||
dao.songsByAdded(sourceId, starred.ordinal, search)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun album(
|
||||
sourceId: Long,
|
||||
albumId: String,
|
||||
|
||||
@@ -4,15 +4,12 @@ import androidx.room3.deferredTransaction
|
||||
import androidx.room3.immediateTransaction
|
||||
import androidx.room3.useReaderConnection
|
||||
import androidx.room3.useWriterConnection
|
||||
import com.subtracks.data.db.LibraryDao
|
||||
import com.subtracks.data.db.SubtracksDatabase
|
||||
import com.subtracks.data.model.PlaybackCursor
|
||||
import com.subtracks.data.model.QueueEntry
|
||||
import com.subtracks.data.model.QueueKind
|
||||
import com.subtracks.data.model.ShuffleOrder
|
||||
import com.subtracks.data.model.SongListItem
|
||||
import com.subtracks.data.prefs.SongSort
|
||||
import com.subtracks.data.prefs.StarredFilter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
@@ -20,12 +17,6 @@ import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
const val QUEUE_CHUNK = 60
|
||||
|
||||
data class QueueSongQuery(
|
||||
val sort: SongSort,
|
||||
val descending: Boolean,
|
||||
val starred: StarredFilter,
|
||||
)
|
||||
|
||||
data class ResolvedQueueEntry(
|
||||
val entry: QueueEntry,
|
||||
val length: Long,
|
||||
@@ -100,26 +91,6 @@ class QueueRepository(
|
||||
songId: String,
|
||||
) = QueueEntry(position = 0, sourceId = sourceId, kind = QueueKind.Song, refId = songId)
|
||||
|
||||
fun songsEntry(sourceId: Long) = QueueEntry(position = 0, sourceId = sourceId, kind = QueueKind.Songs, refId = "")
|
||||
|
||||
fun songsEntry(
|
||||
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,
|
||||
): Long? {
|
||||
val index = songIdsFor(entry).indexOf(songId)
|
||||
return if (index >= 0) index.toLong() else null
|
||||
}
|
||||
|
||||
suspend fun sourceName(
|
||||
kind: QueueKind,
|
||||
sourceId: Long,
|
||||
@@ -143,117 +114,12 @@ class QueueRepository(
|
||||
?.takeIf { it.isNotBlank() }
|
||||
}
|
||||
|
||||
QueueKind.Songs, QueueKind.Song -> {
|
||||
QueueKind.Song -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun LibraryDao.songsPage(
|
||||
sourceId: Long,
|
||||
query: QueueSongQuery,
|
||||
limit: Int,
|
||||
offset: Long,
|
||||
): List<SongListItem> {
|
||||
val starred = query.starred.ordinal
|
||||
return when (query.sort) {
|
||||
SongSort.Album -> {
|
||||
if (query.descending) {
|
||||
songsPageByAlbumReversed(sourceId, starred, limit, offset)
|
||||
} else {
|
||||
songsPageByAlbum(sourceId, starred, limit, offset)
|
||||
}
|
||||
}
|
||||
|
||||
SongSort.Title -> {
|
||||
if (query.descending) {
|
||||
songsPageByTitleReversed(sourceId, starred, limit, offset)
|
||||
} else {
|
||||
songsPageByTitle(sourceId, starred, limit, offset)
|
||||
}
|
||||
}
|
||||
|
||||
SongSort.Artist -> {
|
||||
if (query.descending) {
|
||||
songsPageByArtistReversed(sourceId, starred, limit, offset)
|
||||
} else {
|
||||
songsPageByArtist(sourceId, starred, limit, offset)
|
||||
}
|
||||
}
|
||||
|
||||
SongSort.Starred -> {
|
||||
if (query.descending) {
|
||||
songsPageByStarredReversed(sourceId, starred, limit, offset)
|
||||
} else {
|
||||
songsPageByStarred(sourceId, starred, limit, offset)
|
||||
}
|
||||
}
|
||||
|
||||
SongSort.Added -> {
|
||||
if (query.descending) {
|
||||
songsPageByAddedReversed(sourceId, starred, limit, offset)
|
||||
} else {
|
||||
songsPageByAdded(sourceId, starred, limit, offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun replace(
|
||||
entries: List<QueueEntry>,
|
||||
shuffleOrder: LongArray? = null,
|
||||
@@ -476,7 +342,6 @@ class QueueRepository(
|
||||
when (entry.kind) {
|
||||
QueueKind.Song -> listOf(entry.refId)
|
||||
QueueKind.Album -> dao.albumSongIds(entry.sourceId, entry.refId)
|
||||
QueueKind.Songs -> songIdsFor(entry)
|
||||
QueueKind.Playlist -> dao.playlistSongIds(entry.sourceId, entry.refId)
|
||||
}
|
||||
val from = entry.offset.toInt().coerceIn(0, entryIds.size)
|
||||
@@ -502,12 +367,6 @@ class QueueRepository(
|
||||
dao.albumSongs(entry.sourceId, entry.refId, offset, limit)
|
||||
}
|
||||
|
||||
QueueKind.Songs -> {
|
||||
songsQuery(entry.refId)?.let { query ->
|
||||
db.libraryDao().songsPage(entry.sourceId, query, limit, offset)
|
||||
} ?: dao.songs(entry.sourceId, offset, limit)
|
||||
}
|
||||
|
||||
QueueKind.Song -> {
|
||||
dao.song(entry.sourceId, entry.refId, offset, limit)
|
||||
}
|
||||
@@ -527,11 +386,6 @@ class QueueRepository(
|
||||
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)
|
||||
return count?.coerceAtMost(available) ?: available
|
||||
|
||||
@@ -46,7 +46,7 @@ fun appModule(
|
||||
single<PlayerConnection> { MediaSessionConnection(context.applicationContext, get()) }
|
||||
single { PlaybackController(get(), get(), get()) }
|
||||
viewModel { RootViewModel(get()) }
|
||||
viewModel { LibraryViewModel(get(), get(), get(), get(), get()) }
|
||||
viewModel { LibraryViewModel(get(), get(), get(), get()) }
|
||||
viewModel { SettingsViewModel(get(), get()) }
|
||||
viewModel { AddSourceViewModel(get(), get()) }
|
||||
viewModel { params -> AlbumDetailViewModel(get(), get(), get(), params.get()) }
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.subtracks.data.model.QueueEntry
|
||||
import com.subtracks.data.model.QueueKind
|
||||
import com.subtracks.data.repo.QueueRepository
|
||||
import com.subtracks.data.repo.QueueSnapshot
|
||||
import com.subtracks.data.repo.QueueSongQuery
|
||||
import com.subtracks.data.repo.QueueWindowItem
|
||||
import com.subtracks.data.repo.SourceRepository
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
@@ -190,23 +189,6 @@ class PlaybackController(
|
||||
playlistId: String,
|
||||
) = play(listOf(queueRepository.playlistEntry(sourceId, playlistId)), 0, disableShuffle = true)
|
||||
|
||||
fun playSongs(
|
||||
sourceId: Long,
|
||||
startPosition: Long,
|
||||
) = play(listOf(queueRepository.songsEntry(sourceId)), startPosition)
|
||||
|
||||
fun playSong(
|
||||
sourceId: Long,
|
||||
songId: String,
|
||||
query: QueueSongQuery,
|
||||
) {
|
||||
scope.launch {
|
||||
val entry = queueRepository.songsEntry(sourceId, query)
|
||||
val index = queueRepository.songsIndexOf(entry, songId) ?: return@launch
|
||||
play(listOf(entry), index)
|
||||
}
|
||||
}
|
||||
|
||||
fun shuffleAlbum(
|
||||
sourceId: Long,
|
||||
albumId: String,
|
||||
|
||||
@@ -33,7 +33,6 @@ import androidx.compose.material.icons.rounded.ArrowDownward
|
||||
import androidx.compose.material.icons.rounded.ArrowUpward
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
import androidx.compose.material.icons.rounded.FilterAltOff
|
||||
import androidx.compose.material.icons.rounded.MusicNote
|
||||
import androidx.compose.material.icons.rounded.Person
|
||||
import androidx.compose.material.icons.rounded.Remove
|
||||
import androidx.compose.material.icons.rounded.Search
|
||||
@@ -104,13 +103,11 @@ 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.SongListItem
|
||||
import com.subtracks.data.prefs.AlbumSort
|
||||
import com.subtracks.data.prefs.ArtistSort
|
||||
import com.subtracks.data.prefs.LibraryListTab
|
||||
import com.subtracks.data.prefs.ListQuery
|
||||
import com.subtracks.data.prefs.PlaylistSort
|
||||
import com.subtracks.data.prefs.SongSort
|
||||
import com.subtracks.data.prefs.StarredFilter
|
||||
import com.subtracks.data.source.StarType
|
||||
import com.subtracks.playback.PlaybackController
|
||||
@@ -135,7 +132,6 @@ enum class LibraryTab(
|
||||
Albums("Albums", Icons.Rounded.Album),
|
||||
Artists("Artists", Icons.Rounded.Person),
|
||||
Playlists("Playlists", Icons.AutoMirrored.Rounded.PlaylistPlay),
|
||||
Songs("Songs", Icons.Rounded.MusicNote),
|
||||
}
|
||||
|
||||
data class SortOption(
|
||||
@@ -173,16 +169,6 @@ fun sortOptionsFor(tab: LibraryTab): List<SortOption> =
|
||||
SortOption(PlaylistSort.Updated.name, "Updated", descendingByDefault = true),
|
||||
)
|
||||
}
|
||||
|
||||
LibraryTab.Songs -> {
|
||||
listOf(
|
||||
SortOption(SongSort.Album.name, "Album"),
|
||||
SortOption(SongSort.Title.name, "Title"),
|
||||
SortOption(SongSort.Artist.name, "Artist"),
|
||||
SortOption(SongSort.Starred.name, "Starred", descendingByDefault = true),
|
||||
SortOption(SongSort.Added.name, "Added", descendingByDefault = true),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -203,7 +189,6 @@ fun LibraryRoute(
|
||||
var selectedTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) }
|
||||
var previousTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) }
|
||||
val syncing by viewModel.syncing.collectAsStateWithLifecycle()
|
||||
val playingSongId by viewModel.playingSongId.collectAsStateWithLifecycle()
|
||||
val listTab = selectedTab.listTab()
|
||||
LaunchedEffect(selectedTab) {
|
||||
if (previousTab != selectedTab) {
|
||||
@@ -240,17 +225,14 @@ fun LibraryRoute(
|
||||
artwork = artwork,
|
||||
albums = viewModel.albums,
|
||||
artists = viewModel.artists,
|
||||
songs = viewModel.songs,
|
||||
playlists = viewModel.playlists,
|
||||
coverArt = viewModel::coverArt,
|
||||
onAlbumClick = onAlbumClick,
|
||||
onArtistClick = onArtistClick,
|
||||
onPlaylistClick = onPlaylistClick,
|
||||
onSongClick = viewModel::playSong,
|
||||
onSync = viewModel::sync,
|
||||
onOpenSettings = onOpenSettings,
|
||||
onItemLongClick = { contextMenuHost?.show(it, itemActions) },
|
||||
playingSongId = playingSongId,
|
||||
listQuery = listQuery,
|
||||
resetKeys = resetKeys,
|
||||
sortOptions = sortOptionsFor(selectedTab),
|
||||
@@ -282,18 +264,15 @@ fun LibraryScreen(
|
||||
onTabSelected: (LibraryTab) -> Unit,
|
||||
albums: Flow<PagingData<Album>>,
|
||||
artists: Flow<PagingData<Artist>>,
|
||||
songs: Flow<PagingData<SongListItem>>,
|
||||
playlists: Flow<PagingData<Playlist>>,
|
||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||
onAlbumClick: (Album) -> Unit,
|
||||
onArtistClick: (Artist) -> Unit,
|
||||
onPlaylistClick: (Playlist) -> Unit,
|
||||
onSongClick: (String) -> Unit,
|
||||
onSync: () -> Unit,
|
||||
onOpenSettings: () -> Unit,
|
||||
onItemLongClick: (MenuTarget) -> Unit = {},
|
||||
syncing: Boolean = false,
|
||||
playingSongId: String? = null,
|
||||
bottomInset: Dp = 0.dp,
|
||||
listQuery: ListQuery = ListQuery(""),
|
||||
resetKeys: Map<LibraryTab, Any?> = emptyMap(),
|
||||
@@ -411,25 +390,6 @@ fun LibraryScreen(
|
||||
)
|
||||
}
|
||||
|
||||
LibraryTab.Songs -> {
|
||||
SongsContent(
|
||||
songs.collectAsLazyPagingItems(),
|
||||
coverArt,
|
||||
listBottomInset,
|
||||
{
|
||||
dismissSearch()
|
||||
onSongClick(it)
|
||||
},
|
||||
onLongClick = onItemLongClick,
|
||||
playingSongId,
|
||||
filtered = filtersActive,
|
||||
onClearFilters = onClearFilters,
|
||||
resetKey = resetKey,
|
||||
topInset = listTopInset,
|
||||
onSync = onSync,
|
||||
)
|
||||
}
|
||||
|
||||
LibraryTab.Playlists -> {
|
||||
PlaylistsContent(
|
||||
playlists.collectAsLazyPagingItems(),
|
||||
|
||||
@@ -8,21 +8,16 @@ 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.QueueKind
|
||||
import com.subtracks.data.model.SongListItem
|
||||
import com.subtracks.data.prefs.AlbumSort
|
||||
import com.subtracks.data.prefs.ArtistSort
|
||||
import com.subtracks.data.prefs.LibraryListTab
|
||||
import com.subtracks.data.prefs.ListQuery
|
||||
import com.subtracks.data.prefs.PlaylistSort
|
||||
import com.subtracks.data.prefs.SongSort
|
||||
import com.subtracks.data.prefs.UserPreferences
|
||||
import com.subtracks.data.repo.LibraryRepository
|
||||
import com.subtracks.data.repo.QueueSongQuery
|
||||
import com.subtracks.data.repo.SourceRepository
|
||||
import com.subtracks.data.sync.SyncManager
|
||||
import com.subtracks.data.sync.SyncStatus
|
||||
import com.subtracks.playback.PlaybackController
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -40,7 +35,6 @@ class LibraryViewModel(
|
||||
private val libraryRepository: LibraryRepository,
|
||||
private val sourceRepository: SourceRepository,
|
||||
private val syncManager: SyncManager,
|
||||
private val playbackController: PlaybackController,
|
||||
private val userPreferences: UserPreferences,
|
||||
) : ViewModel() {
|
||||
private val listQueries: Map<LibraryListTab, StateFlow<ListQuery>> =
|
||||
@@ -86,27 +80,11 @@ class LibraryViewModel(
|
||||
}
|
||||
}.cachedIn(viewModelScope)
|
||||
|
||||
val songs: Flow<PagingData<SongListItem>> =
|
||||
libraryRepository.activeSourceId
|
||||
.filterNotNull()
|
||||
.flatMapLatest { sourceId ->
|
||||
combine(listQueries.getValue(LibraryListTab.Songs), searches.getValue(LibraryListTab.Songs)) { query, search ->
|
||||
query to search
|
||||
}.flatMapLatest { (query, search) ->
|
||||
libraryRepository.songs(sourceId, query.songSort(), query.descending, query.starred, search)
|
||||
}
|
||||
}.cachedIn(viewModelScope)
|
||||
|
||||
val syncing: StateFlow<Boolean> =
|
||||
syncManager.status
|
||||
.map { it == SyncStatus.Running }
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false)
|
||||
|
||||
val playingSongId: StateFlow<String?> =
|
||||
playbackController.state
|
||||
.map { state -> state.item?.id?.takeIf { state.context?.kind == QueueKind.Songs } }
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
|
||||
|
||||
fun listQuery(tab: LibraryListTab): StateFlow<ListQuery> = listQueries.getValue(tab)
|
||||
|
||||
fun setListQuery(
|
||||
@@ -130,14 +108,6 @@ class LibraryViewModel(
|
||||
thumbnail: Boolean,
|
||||
): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail)
|
||||
|
||||
fun playSong(songId: String) {
|
||||
viewModelScope.launch {
|
||||
val sourceId = sourceRepository.activeSourceIdOnce() ?: return@launch
|
||||
val query = listQueries.getValue(LibraryListTab.Songs).value
|
||||
playbackController.playSong(sourceId, songId, QueueSongQuery(query.songSort(), query.descending, query.starred))
|
||||
}
|
||||
}
|
||||
|
||||
fun playAlbum(albumId: String) {
|
||||
viewModelScope.launch {
|
||||
sourceRepository.activeSourceIdOnce()?.let { playbackController.playAlbum(it, albumId, 0) }
|
||||
@@ -170,5 +140,3 @@ private fun ListQuery.albumSort(): AlbumSort = AlbumSort.entries.firstOrNull { i
|
||||
private fun ListQuery.artistSort(): ArtistSort = ArtistSort.entries.firstOrNull { it.name == sort } ?: ArtistSort.Name
|
||||
|
||||
private fun ListQuery.playlistSort(): PlaylistSort = PlaylistSort.entries.firstOrNull { it.name == sort } ?: PlaylistSort.Name
|
||||
|
||||
private fun ListQuery.songSort(): SongSort = SongSort.entries.firstOrNull { it.name == sort } ?: SongSort.Album
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
package com.subtracks.ui.library
|
||||
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.PlayArrow
|
||||
@@ -27,80 +20,10 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.itemKey
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.data.model.SongListItem
|
||||
import com.subtracks.ui.components.CoverArt
|
||||
import com.subtracks.ui.components.EmptyState
|
||||
import com.subtracks.ui.components.FilteredEmptyState
|
||||
import com.subtracks.ui.components.LoadingState
|
||||
import com.subtracks.ui.components.MenuTarget
|
||||
import com.subtracks.ui.components.ResetScrollOnChange
|
||||
import com.subtracks.ui.components.rememberViewportFill
|
||||
|
||||
@Composable
|
||||
fun SongsContent(
|
||||
items: LazyPagingItems<SongListItem>,
|
||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||
bottomInset: Dp,
|
||||
onSongClick: (String) -> Unit,
|
||||
onLongClick: (MenuTarget) -> Unit = {},
|
||||
playingSongId: String? = null,
|
||||
filtered: Boolean = false,
|
||||
onClearFilters: () -> Unit = {},
|
||||
resetKey: Any? = null,
|
||||
topInset: Dp = 0.dp,
|
||||
onSync: () -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when {
|
||||
items.itemCount == 0 && items.loadState.refresh is LoadState.Loading -> {
|
||||
LoadingState(modifier)
|
||||
}
|
||||
|
||||
items.itemCount == 0 -> {
|
||||
if (filtered) {
|
||||
FilteredEmptyState(onClearFilters, modifier)
|
||||
} else {
|
||||
EmptyState("No songs yet.", modifier, actionLabel = "Sync", onAction = onSync)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
val listState = rememberLazyListState()
|
||||
ResetScrollOnChange(resetKey, { items.loadState.refresh }) { listState.scrollToItem(0) }
|
||||
val fill = rememberViewportFill(listState)
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(top = topInset, bottom = bottomInset),
|
||||
) {
|
||||
items(count = items.itemCount, key = items.itemKey { it.song.id }) { index ->
|
||||
val item = items[index]
|
||||
if (item != null) {
|
||||
SongRow(
|
||||
song = item.song,
|
||||
coverArtId = item.coverArt,
|
||||
coverArt = coverArt,
|
||||
isPlaying = item.song.id == playingSongId,
|
||||
modifier =
|
||||
Modifier.combinedClickable(
|
||||
onClick = { onSongClick(item.song.id) },
|
||||
onLongClick = { onLongClick(MenuTarget.Song(item.song, coverArt(item.coverArt, true))) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
item { Spacer(Modifier.height(fill)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SongRow(
|
||||
|
||||
@@ -112,12 +112,7 @@ fun NowPlayingRoute(
|
||||
val art = controller.coverArt(state.item)
|
||||
val thumbnail = controller.coverArt(state.item, thumbnail = true)
|
||||
val queueContext = state.context
|
||||
val fallbackTitle =
|
||||
if (queueContext?.kind == QueueKind.Songs) {
|
||||
"Library"
|
||||
} else {
|
||||
state.item?.album?.takeIf { it.isNotBlank() } ?: "Library"
|
||||
}
|
||||
val fallbackTitle = state.item?.album?.takeIf { it.isNotBlank() } ?: "Library"
|
||||
val sourceTitle = remember(queueContext) { mutableStateOf(fallbackTitle) }
|
||||
LaunchedEffect(queueContext) {
|
||||
sourceTitle.value = controller.sourceTitle(queueContext)?.takeIf { it.isNotBlank() } ?: fallbackTitle
|
||||
@@ -443,7 +438,6 @@ private fun QueueKind.label(): String =
|
||||
QueueKind.Album -> "album"
|
||||
QueueKind.Playlist -> "playlist"
|
||||
QueueKind.Song -> "song"
|
||||
QueueKind.Songs -> "songs"
|
||||
}
|
||||
|
||||
private fun formatTime(milliseconds: Long): String {
|
||||
|
||||
Reference in New Issue
Block a user