diff --git a/app/src/main/java/com/theveloper/pixelplay/data/database/MusicDao.kt b/app/src/main/java/com/theveloper/pixelplay/data/database/MusicDao.kt index 8a141e350e..95826d5724 100644 --- a/app/src/main/java/com/theveloper/pixelplay/data/database/MusicDao.kt +++ b/app/src/main/java/com/theveloper/pixelplay/data/database/MusicDao.kt @@ -242,7 +242,8 @@ interface MusicDao { suspend fun getAllTelegramSongIds(): List @Query(""" - SELECT id FROM songs + SELECT songs.id FROM songs + LEFT JOIN song_engagements ON CAST(songs.id AS TEXT) = song_engagements.song_id WHERE source_type = 1 AND (telegram_chat_id = :chatId OR content_uri_string LIKE 'telegram://' || :chatId || '/%') @@ -680,19 +681,21 @@ interface MusicDao { ) ) ORDER BY - CASE WHEN :sortOrder = 'song_default_order' THEN track_number END ASC, - CASE WHEN :sortOrder = 'song_title_az' THEN title END COLLATE NOCASE ASC, - CASE WHEN :sortOrder = 'song_title_za' THEN title END COLLATE NOCASE DESC, - CASE WHEN :sortOrder = 'song_artist' THEN artist_name END COLLATE NOCASE ASC, - CASE WHEN :sortOrder = 'song_artist_desc' THEN artist_name END COLLATE NOCASE DESC, - CASE WHEN :sortOrder = 'song_album' THEN album_name END COLLATE NOCASE ASC, - CASE WHEN :sortOrder = 'song_album_desc' THEN album_name END COLLATE NOCASE DESC, - CASE WHEN :sortOrder = 'song_date_added' THEN date_added END DESC, - CASE WHEN :sortOrder = 'song_date_added_asc' THEN date_added END ASC, - CASE WHEN :sortOrder = 'song_duration' THEN duration END DESC, - CASE WHEN :sortOrder = 'song_duration_asc' THEN duration END ASC, - title COLLATE NOCASE ASC, - id ASC + CASE WHEN :sortOrder = 'song_default_order' THEN songs.track_number END ASC, + CASE WHEN :sortOrder = 'song_title_az' THEN songs.title END COLLATE NOCASE ASC, + CASE WHEN :sortOrder = 'song_title_za' THEN songs.title END COLLATE NOCASE DESC, + CASE WHEN :sortOrder = 'song_artist' THEN songs.artist_name END COLLATE NOCASE ASC, + CASE WHEN :sortOrder = 'song_artist_desc' THEN songs.artist_name END COLLATE NOCASE DESC, + CASE WHEN :sortOrder = 'song_album' THEN songs.album_name END COLLATE NOCASE ASC, + CASE WHEN :sortOrder = 'song_album_desc' THEN songs.album_name END COLLATE NOCASE DESC, + CASE WHEN :sortOrder = 'song_date_added' THEN songs.date_added END DESC, + CASE WHEN :sortOrder = 'song_date_added_asc' THEN songs.date_added END ASC, + CASE WHEN :sortOrder = 'song_duration' THEN songs.duration END DESC, + CASE WHEN :sortOrder = 'song_duration_asc' THEN songs.duration END ASC, + CASE WHEN :sortOrder = 'song_listening_time' THEN COALESCE(song_engagements.total_play_duration_ms, 0) END DESC, + CASE WHEN :sortOrder = 'song_listening_time_asc' THEN COALESCE(song_engagements.total_play_duration_ms, 0) END ASC, + songs.title COLLATE NOCASE ASC, + songs.id ASC """) suspend fun getSongIdsSorted( allowedParentDirs: List, @@ -704,6 +707,7 @@ interface MusicDao { @Query(""" SELECT songs.id FROM songs INNER JOIN favorites ON songs.id = favorites.songId AND favorites.isFavorite = 1 + LEFT JOIN song_engagements ON CAST(songs.id AS TEXT) = song_engagements.song_id WHERE (:applyDirectoryFilter = 0 OR songs.id < 0 OR songs.parent_directory_path IN (:allowedParentDirs)) AND ( :filterMode = 0 @@ -717,14 +721,10 @@ interface MusicDao { ) ) ORDER BY + CASE WHEN :sortOrder = 'liked_play_time' THEN COALESCE(song_engagements.total_play_duration_ms, 0) END DESC, + CASE WHEN :sortOrder = 'liked_play_time_asc' THEN COALESCE(song_engagements.total_play_duration_ms, 0) END ASC, CASE WHEN :sortOrder = 'liked_title_az' THEN songs.title END COLLATE NOCASE ASC, CASE WHEN :sortOrder = 'liked_title_za' THEN songs.title END COLLATE NOCASE DESC, - CASE WHEN :sortOrder = 'liked_artist' THEN songs.artist_name END COLLATE NOCASE ASC, - CASE WHEN :sortOrder = 'liked_artist_desc' THEN songs.artist_name END COLLATE NOCASE DESC, - CASE WHEN :sortOrder = 'liked_album' THEN songs.album_name END COLLATE NOCASE ASC, - CASE WHEN :sortOrder = 'liked_album_desc' THEN songs.album_name END COLLATE NOCASE DESC, - CASE WHEN :sortOrder = 'liked_date_liked' THEN favorites.timestamp END DESC, - CASE WHEN :sortOrder = 'liked_date_liked_asc' THEN favorites.timestamp END ASC, songs.title COLLATE NOCASE ASC, songs.id ASC """) @@ -741,7 +741,8 @@ interface MusicDao { * Room auto-generates the PagingSource implementation. */ @Query(""" - SELECT * FROM songs + SELECT songs.* FROM songs + LEFT JOIN song_engagements ON CAST(songs.id AS TEXT) = song_engagements.song_id WHERE (:applyDirectoryFilter = 0 OR id < 0 OR parent_directory_path IN (:allowedParentDirs)) AND ( :filterMode = 0 @@ -766,6 +767,8 @@ interface MusicDao { CASE WHEN :sortOrder = 'song_date_added_asc' THEN date_added END ASC, CASE WHEN :sortOrder = 'song_duration' THEN duration END DESC, CASE WHEN :sortOrder = 'song_duration_asc' THEN duration END ASC, + CASE WHEN :sortOrder = 'song_listening_time' THEN COALESCE(song_engagements.total_play_duration_ms, 0) END DESC, + CASE WHEN :sortOrder = 'song_listening_time_asc' THEN COALESCE(song_engagements.total_play_duration_ms, 0) END ASC, -- Secondary sort falls back to title for consistency (case-insensitive) title COLLATE NOCASE ASC, @@ -781,6 +784,7 @@ interface MusicDao { @Query(""" SELECT """ + SONG_LIST_PROJECTION + """ FROM songs + LEFT JOIN song_engagements ON CAST(songs.id AS TEXT) = song_engagements.song_id WHERE (:applyDirectoryFilter = 0 OR id < 0 OR parent_directory_path IN (:allowedParentDirs)) AND ( :filterMode = 0 @@ -805,6 +809,8 @@ interface MusicDao { CASE WHEN :sortOrder = 'song_date_added_asc' THEN date_added END ASC, CASE WHEN :sortOrder = 'song_duration' THEN duration END DESC, CASE WHEN :sortOrder = 'song_duration_asc' THEN duration END ASC, + CASE WHEN :sortOrder = 'song_listening_time' THEN COALESCE(song_engagements.total_play_duration_ms, 0) END DESC, + CASE WHEN :sortOrder = 'song_listening_time_asc' THEN COALESCE(song_engagements.total_play_duration_ms, 0) END ASC, title COLLATE NOCASE ASC, id ASC LIMIT :limit OFFSET :offset diff --git a/app/src/main/java/com/theveloper/pixelplay/data/model/SortOption.kt b/app/src/main/java/com/theveloper/pixelplay/data/model/SortOption.kt index 39d492ff29..4788054478 100644 --- a/app/src/main/java/com/theveloper/pixelplay/data/model/SortOption.kt +++ b/app/src/main/java/com/theveloper/pixelplay/data/model/SortOption.kt @@ -117,6 +117,24 @@ sealed class SortOption( methodKey = "song_duration", direction = SortDirection.Ascending ) + object SongListeningTime : SortOption( + storageKey = "song_listening_time", + displayName = "Listening Time", + displayNameRes = R.string.sort_display_listening_time, + methodLabel = "Listening Time", + methodLabelRes = R.string.sort_method_listening_time, + methodKey = "song_listening_time", + direction = SortDirection.Descending + ) + object SongListeningTimeAsc : SortOption( + storageKey = "song_listening_time_asc", + displayName = "Listening Time (Least First)", + displayNameRes = R.string.sort_display_listening_time_least, + methodLabel = "Listening Time", + methodLabelRes = R.string.sort_method_listening_time, + methodKey = "song_listening_time", + direction = SortDirection.Ascending + ) // Album Sort Options object AlbumTitleAZ : SortOption( @@ -442,7 +460,9 @@ sealed class SortOption( SongDateAdded, SongDateAddedAsc, SongDuration, - SongDurationAsc + SongDurationAsc, + SongListeningTime, + SongListeningTimeAsc ) } @@ -539,4 +559,4 @@ sealed class SortOption( return sanitized.firstOrNull { option -> option.displayName == rawValue } ?: fallback } } -} \ No newline at end of file +} diff --git a/app/src/main/res/values/strings_library.xml b/app/src/main/res/values/strings_library.xml index 8b7c6c2c60..ba32ed622d 100644 --- a/app/src/main/res/values/strings_library.xml +++ b/app/src/main/res/values/strings_library.xml @@ -209,6 +209,8 @@ Date Added (Oldest First) Duration Duration (Shortest First) + Listening Time + Listening Time (Least First) Release Year Release Year (Oldest First) Fewest Songs @@ -230,6 +232,7 @@ Album Date Added Duration + Listening Time Release Year Song Count Name @@ -563,4 +566,4 @@ Daily Mix updated with AI Could not update: %s AI couldn\'t find songs for this mix - \ No newline at end of file + diff --git a/app/src/test/java/com/theveloper/pixelplay/data/model/SortOptionTest.kt b/app/src/test/java/com/theveloper/pixelplay/data/model/SortOptionTest.kt index 39bf29b9a3..a14fcebd17 100644 --- a/app/src/test/java/com/theveloper/pixelplay/data/model/SortOptionTest.kt +++ b/app/src/test/java/com/theveloper/pixelplay/data/model/SortOptionTest.kt @@ -10,6 +10,7 @@ class SortOptionTest { assertEquals(SortOption.SongTitleAZ, SortOption.SongTitleZA.methodOption()) assertEquals(SortOption.SongArtist, SortOption.SongArtistDesc.methodOption()) assertEquals(SortOption.SongDateAdded, SortOption.SongDateAddedAsc.methodOption()) + assertEquals(SortOption.SongListeningTime, SortOption.SongListeningTimeAsc.methodOption()) assertEquals(SortOption.SongDefaultOrder, SortOption.SongDefaultOrder.methodOption()) } @@ -39,6 +40,8 @@ class SortOptionTest { assertEquals(SortOption.SongTitleAZ, SortOption.SongTitleZA.flipDirection()) assertEquals(SortOption.LikedSongDateLikedAsc, SortOption.LikedSongDateLiked.flipDirection()) assertEquals(SortOption.FolderSongCountAsc, SortOption.FolderSongCountDesc.flipDirection()) + assertEquals(SortOption.SongListeningTimeAsc, SortOption.SongListeningTime.flipDirection()) + assertEquals(SortOption.SongListeningTime, SortOption.SongListeningTimeAsc.flipDirection()) assertEquals(SortOption.SongDefaultOrder, SortOption.SongDefaultOrder.flipDirection()) }