From f1c030f0e1b8df4d7797bd91bb6ec5676a776c36 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Thu, 24 Sep 2026 17:53:44 +0000 Subject: [PATCH] address review: play songs by id, reverse album sort, tab-scoped scroll reset, literal search and stable tiebreakers --- .../com/subtracks/data/db/LibraryDao.kt | 143 ++++++++++-------- .../subtracks/data/repo/LibraryRepository.kt | 6 +- .../subtracks/data/repo/QueueRepository.kt | 10 ++ .../data/source/subsonic/SubsonicXml.kt | 2 +- .../subtracks/playback/PlaybackController.kt | 10 ++ .../com/subtracks/ui/library/LibraryScreen.kt | 16 +- .../subtracks/ui/library/LibraryViewModel.kt | 4 +- .../com/subtracks/ui/library/SongsContent.kt | 4 +- 8 files changed, 123 insertions(+), 72 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt b/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt index 621152eb..97ddc51b 100644 --- a/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt +++ b/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt @@ -92,8 +92,8 @@ interface LibraryDao { @Query( "SELECT * FROM albums WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%' OR albumArtist LIKE '%' || :search || '%') " + - "ORDER BY name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " + + "ORDER BY name COLLATE NOCASE, id", ) fun albumsByName( sourceId: Long, @@ -104,8 +104,8 @@ interface LibraryDao { @Query( "SELECT * FROM albums WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%' OR albumArtist LIKE '%' || :search || '%') " + - "ORDER BY name COLLATE NOCASE DESC", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " + + "ORDER BY name COLLATE NOCASE DESC, id", ) fun albumsByNameReversed( sourceId: Long, @@ -116,8 +116,8 @@ interface LibraryDao { @Query( "SELECT * FROM albums WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%' OR albumArtist LIKE '%' || :search || '%') " + - "ORDER BY albumArtist COLLATE NOCASE, year, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " + + "ORDER BY albumArtist COLLATE NOCASE, year, name COLLATE NOCASE, id", ) fun albumsByArtist( sourceId: Long, @@ -128,8 +128,8 @@ interface LibraryDao { @Query( "SELECT * FROM albums WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%' OR albumArtist LIKE '%' || :search || '%') " + - "ORDER BY albumArtist COLLATE NOCASE DESC, year DESC, name COLLATE NOCASE DESC", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " + + "ORDER BY albumArtist COLLATE NOCASE DESC, year DESC, name COLLATE NOCASE DESC, id", ) fun albumsByArtistReversed( sourceId: Long, @@ -140,8 +140,8 @@ interface LibraryDao { @Query( "SELECT * FROM albums WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%' OR albumArtist LIKE '%' || :search || '%') " + - "ORDER BY year DESC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " + + "ORDER BY year DESC, name COLLATE NOCASE, id", ) fun albumsByYear( sourceId: Long, @@ -152,8 +152,8 @@ interface LibraryDao { @Query( "SELECT * FROM albums WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%' OR albumArtist LIKE '%' || :search || '%') " + - "ORDER BY year ASC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " + + "ORDER BY year ASC, name COLLATE NOCASE, id", ) fun albumsByYearReversed( sourceId: Long, @@ -164,8 +164,8 @@ interface LibraryDao { @Query( "SELECT * FROM albums WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%' OR albumArtist LIKE '%' || :search || '%') " + - "ORDER BY created DESC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " + + "ORDER BY created DESC, name COLLATE NOCASE, id", ) fun albumsByRecentlyAdded( sourceId: Long, @@ -176,8 +176,8 @@ interface LibraryDao { @Query( "SELECT * FROM albums WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%' OR albumArtist LIKE '%' || :search || '%') " + - "ORDER BY created ASC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " + + "ORDER BY created ASC, name COLLATE NOCASE, id", ) fun albumsByRecentlyAddedReversed( sourceId: Long, @@ -188,8 +188,8 @@ interface LibraryDao { @Query( "SELECT * FROM albums WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%' OR albumArtist LIKE '%' || :search || '%') " + - "ORDER BY starred IS NULL, starred DESC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " + + "ORDER BY starred IS NULL, starred DESC, name COLLATE NOCASE, id", ) fun albumsByStarred( sourceId: Long, @@ -200,8 +200,8 @@ interface LibraryDao { @Query( "SELECT * FROM albums WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%' OR albumArtist LIKE '%' || :search || '%') " + - "ORDER BY starred IS NULL, starred ASC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " + + "ORDER BY starred IS NULL, starred ASC, name COLLATE NOCASE, id", ) fun albumsByStarredReversed( sourceId: Long, @@ -212,8 +212,8 @@ interface LibraryDao { @Query( "SELECT * FROM artists WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY name COLLATE NOCASE, id", ) fun artistsByName( sourceId: Long, @@ -224,8 +224,8 @@ interface LibraryDao { @Query( "SELECT * FROM artists WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY name COLLATE NOCASE DESC", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY name COLLATE NOCASE DESC, id", ) fun artistsByNameReversed( sourceId: Long, @@ -236,8 +236,8 @@ interface LibraryDao { @Query( "SELECT * FROM artists WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY albumCount DESC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY albumCount DESC, name COLLATE NOCASE, id", ) fun artistsByAlbumCount( sourceId: Long, @@ -248,8 +248,8 @@ interface LibraryDao { @Query( "SELECT * FROM artists WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY albumCount ASC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY albumCount ASC, name COLLATE NOCASE, id", ) fun artistsByAlbumCountReversed( sourceId: Long, @@ -260,8 +260,8 @@ interface LibraryDao { @Query( "SELECT * FROM artists WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY starred IS NULL, starred DESC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY starred IS NULL, starred DESC, name COLLATE NOCASE, id", ) fun artistsByStarred( sourceId: Long, @@ -272,8 +272,8 @@ interface LibraryDao { @Query( "SELECT * FROM artists WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY starred IS NULL, starred ASC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY starred IS NULL, starred ASC, name COLLATE NOCASE, id", ) fun artistsByStarredReversed( sourceId: Long, @@ -283,8 +283,8 @@ interface LibraryDao { @Query( "SELECT * FROM playlists WHERE sourceId = :sourceId " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY name COLLATE NOCASE, id", ) fun playlistsByName( sourceId: Long, @@ -293,8 +293,8 @@ interface LibraryDao { @Query( "SELECT * FROM playlists WHERE sourceId = :sourceId " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY name COLLATE NOCASE DESC", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY name COLLATE NOCASE DESC, id", ) fun playlistsByNameReversed( sourceId: Long, @@ -303,8 +303,8 @@ interface LibraryDao { @Query( "SELECT * FROM playlists WHERE sourceId = :sourceId " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY created DESC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY created DESC, name COLLATE NOCASE, id", ) fun playlistsByAdded( sourceId: Long, @@ -313,8 +313,8 @@ interface LibraryDao { @Query( "SELECT * FROM playlists WHERE sourceId = :sourceId " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY created ASC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY created ASC, name COLLATE NOCASE, id", ) fun playlistsByAddedReversed( sourceId: Long, @@ -323,8 +323,8 @@ interface LibraryDao { @Query( "SELECT * FROM playlists WHERE sourceId = :sourceId " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY changed DESC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY changed DESC, name COLLATE NOCASE, id", ) fun playlistsByUpdated( sourceId: Long, @@ -333,8 +333,8 @@ interface LibraryDao { @Query( "SELECT * FROM playlists WHERE sourceId = :sourceId " + - "AND (:search = '' OR name LIKE '%' || :search || '%') " + - "ORDER BY changed ASC, name COLLATE NOCASE", + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " + + "ORDER BY changed ASC, name COLLATE NOCASE, id", ) fun playlistsByUpdatedReversed( sourceId: Long, @@ -347,8 +347,8 @@ interface LibraryDao { "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 songs.title LIKE '%' || :search || '%' " + - "OR songs.artist LIKE '%' || :search || '%' OR songs.album LIKE '%' || :search || '%') " + + "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", ) @@ -364,8 +364,25 @@ interface LibraryDao { "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 songs.title LIKE '%' || :search || '%' " + - "OR songs.artist LIKE '%' || :search || '%' OR songs.album LIKE '%' || :search || '%') " + + "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.id DESC", + ) + fun songsByAlbumReversed( + sourceId: Long, + starredFilter: Int, + search: String, + ): PagingSource + + @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.id", ) fun songsByTitle( @@ -380,8 +397,8 @@ interface LibraryDao { "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 songs.title LIKE '%' || :search || '%' " + - "OR songs.artist LIKE '%' || :search || '%' OR songs.album LIKE '%' || :search || '%') " + + "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.id", ) fun songsByTitleReversed( @@ -396,8 +413,8 @@ interface LibraryDao { "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 songs.title LIKE '%' || :search || '%' " + - "OR songs.artist LIKE '%' || :search || '%' OR songs.album LIKE '%' || :search || '%') " + + "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( @@ -412,8 +429,8 @@ interface LibraryDao { "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 songs.title LIKE '%' || :search || '%' " + - "OR songs.artist LIKE '%' || :search || '%' OR songs.album LIKE '%' || :search || '%') " + + "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( @@ -428,8 +445,8 @@ interface LibraryDao { "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 songs.title LIKE '%' || :search || '%' " + - "OR songs.artist LIKE '%' || :search || '%' OR songs.album LIKE '%' || :search || '%') " + + "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.title COLLATE NOCASE, songs.id", ) fun songsByStarred( @@ -444,8 +461,8 @@ interface LibraryDao { "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 songs.title LIKE '%' || :search || '%' " + - "OR songs.artist LIKE '%' || :search || '%' OR songs.album LIKE '%' || :search || '%') " + + "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.title COLLATE NOCASE, songs.id", ) fun songsByStarredReversed( @@ -460,8 +477,8 @@ interface LibraryDao { "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 songs.title LIKE '%' || :search || '%' " + - "OR songs.artist LIKE '%' || :search || '%' OR songs.album LIKE '%' || :search || '%') " + + "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.title COLLATE NOCASE, songs.id", ) fun songsByAdded( @@ -476,8 +493,8 @@ interface LibraryDao { "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 songs.title LIKE '%' || :search || '%' " + - "OR songs.artist LIKE '%' || :search || '%' OR songs.album LIKE '%' || :search || '%') " + + "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.title COLLATE NOCASE, songs.id", ) fun songsByAddedReversed( @@ -504,7 +521,7 @@ interface LibraryDao { artistId: String, ): Flow - @Query("SELECT * FROM albums WHERE sourceId = :sourceId AND artistId = :artistId ORDER BY year DESC, name COLLATE NOCASE") + @Query("SELECT * FROM albums WHERE sourceId = :sourceId AND artistId = :artistId ORDER BY year DESC, name COLLATE NOCASE, id") fun albumsForArtist( sourceId: Long, artistId: String, diff --git a/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt b/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt index 4834bc9e..61da0431 100644 --- a/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt +++ b/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt @@ -157,7 +157,11 @@ class LibraryRepository( val dao = db.libraryDao() when (sort) { SongSort.Album -> { - dao.songs(sourceId, starred.ordinal, search) + if (descending) { + dao.songsByAlbumReversed(sourceId, starred.ordinal, search) + } else { + dao.songs(sourceId, starred.ordinal, search) + } } SongSort.Title -> { diff --git a/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt b/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt index 530505d8..6ef24b34 100644 --- a/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt +++ b/app/src/main/kotlin/com/subtracks/data/repo/QueueRepository.kt @@ -92,6 +92,16 @@ class QueueRepository( fun songsEntry(sourceId: Long) = QueueEntry(position = 0, sourceId = sourceId, kind = QueueKind.Songs, refId = "") + suspend fun songsIndexOf( + sourceId: Long, + songId: String, + ): Long? = + dao + .songIds(sourceId) + .indexOf(songId) + .takeIf { it >= 0 } + ?.toLong() + suspend fun replace( entries: List, shuffleOrder: LongArray? = null, diff --git a/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicXml.kt b/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicXml.kt index b654743e..7094129e 100644 --- a/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicXml.kt +++ b/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicXml.kt @@ -136,7 +136,7 @@ internal object IsoDate { private val timezone = Regex("(Z|[+-]\\d{2}:?\\d{2})$", RegexOption.IGNORE_CASE) fun parse(value: String): Long? { - val text = value.trim() + val text = value.trim().let { if (it.endsWith("z")) it.dropLast(1) + "Z" else it } return try { val zoned = if (timezone.containsMatchIn(text)) text else text + "Z" OffsetDateTime.parse(zoned).toEpochSecond() diff --git a/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt b/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt index 8f36d2d9..9cb61867 100644 --- a/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt +++ b/app/src/main/kotlin/com/subtracks/playback/PlaybackController.kt @@ -192,6 +192,16 @@ class PlaybackController( startPosition: Long, ) = play(listOf(queueRepository.songsEntry(sourceId)), startPosition) + fun playSong( + sourceId: Long, + songId: String, + ) { + scope.launch { + val index = queueRepository.songsIndexOf(sourceId, songId) ?: return@launch + play(listOf(queueRepository.songsEntry(sourceId)), index) + } + } + fun shuffleAlbum( sourceId: Long, albumId: String, diff --git a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt index 3c2c1dbb..ef887304 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt @@ -173,6 +173,12 @@ fun LibraryRoute( val listTab = selectedTab.listTab() val listQuery by viewModel.listQuery(listTab).collectAsStateWithLifecycle() val search by viewModel.search(listTab).collectAsStateWithLifecycle() + val resetKeys = + LibraryTab.entries.associateWith { tab -> + val query by viewModel.listQuery(tab.listTab()).collectAsStateWithLifecycle() + val term by viewModel.search(tab.listTab()).collectAsStateWithLifecycle() + "${query.sort}|${query.descending}|${query.starred}|$term" + } LibraryScreen( selectedTab = selectedTab, onTabSelected = { selectedTab = it }, @@ -191,6 +197,7 @@ fun LibraryRoute( onOpenSettings = onOpenSettings, playingSongId = playingSongId, listQuery = listQuery, + resetKeys = resetKeys, sortOptions = sortOptionsFor(selectedTab), starredSupported = listTab.supportsStarred, onSortChange = { viewModel.setListQuery(listTab, listQuery.copy(sort = it)) }, @@ -226,13 +233,14 @@ fun LibraryScreen( onAlbumClick: (Album) -> Unit, onArtistClick: (Artist) -> Unit, onPlaylistClick: (Playlist) -> Unit, - onSongClick: (Int) -> Unit, + onSongClick: (String) -> Unit, onSync: () -> Unit, onOpenSettings: () -> Unit, syncing: Boolean = false, playingSongId: String? = null, bottomInset: Dp = 0.dp, listQuery: ListQuery = ListQuery(""), + resetKeys: Map = emptyMap(), sortOptions: List = emptyList(), starredSupported: Boolean = false, onSortChange: (String) -> Unit = {}, @@ -262,6 +270,7 @@ fun LibraryScreen( if (pagerState.currentPage != selectedTab.ordinal) { pagerState.animateScrollToPage(selectedTab.ordinal) } + searchActive = false } val density = LocalDensity.current @@ -279,7 +288,6 @@ fun LibraryScreen( val statusBarTop = with(density) { WindowInsets.statusBars.getTop(this).toDp() } val filtersActive = listQuery.starred != StarredFilter.Any || search.isNotEmpty() val listBottomInset = bottomInset + if (searchActive) SEARCH_BAR_CLEARANCE else FAB_CLEARANCE - val resetKey = listOf(listQuery.sort, listQuery.descending, listQuery.starred, search) Box(modifier.fillMaxSize()) { Column( @@ -327,7 +335,9 @@ fun LibraryScreen( state = pagerState, modifier = Modifier.fillMaxWidth().weight(1f), ) { page -> - when (LibraryTab.entries[page]) { + val pageTab = LibraryTab.entries[page] + val resetKey = resetKeys[pageTab] + when (pageTab) { LibraryTab.Albums -> { AlbumsContent( albums, diff --git a/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt index 6364c5b3..37a45730 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt @@ -129,10 +129,10 @@ class LibraryViewModel( thumbnail: Boolean, ): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail) - fun playSong(position: Int) { + fun playSong(songId: String) { viewModelScope.launch { val sourceId = sourceRepository.activeSourceIdOnce() ?: return@launch - playbackController.playSongs(sourceId, position.toLong()) + playbackController.playSong(sourceId, songId) } } diff --git a/app/src/main/kotlin/com/subtracks/ui/library/SongsContent.kt b/app/src/main/kotlin/com/subtracks/ui/library/SongsContent.kt index 3a3314af..377b4d60 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/SongsContent.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/SongsContent.kt @@ -47,7 +47,7 @@ fun SongsContent( items: LazyPagingItems, coverArt: (String?, Boolean) -> CoverArtRef?, bottomInset: Dp, - onSongClick: (Int) -> Unit, + onSongClick: (String) -> Unit, playingSongId: String? = null, filtered: Boolean = false, onClearFilters: () -> Unit = {}, @@ -84,7 +84,7 @@ fun SongsContent( coverArtId = item.coverArt, coverArt = coverArt, isPlaying = item.song.id == playingSongId, - modifier = Modifier.clickable { onSongClick(index) }, + modifier = Modifier.clickable { onSongClick(item.song.id) }, ) } }