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 404c2144..03262f7b 100644 --- a/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt +++ b/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt @@ -97,6 +97,17 @@ internal const val PLAYLISTS_FILTER = "WHERE playlist_search MATCH '\"' || replace(:search, '\"', '\"\"') || '\"')) " + "OR (length(:search) < 3 AND instr(lower(name), lower(:search)) > 0)) " +// With the Downloaded filter on, count the songs that actually have a download so the number under +// a playlist matches the songs its detail screen lists. +internal const val PLAYLISTS_SELECT = + "SELECT playlists.sourceId, playlists.id, playlists.name, playlists.comment, playlists.coverArt, " + + "CASE WHEN :downloadedFilter = 1 THEN (" + + "SELECT COUNT(DISTINCT ps.songId) FROM playlist_songs ps " + + "JOIN song_downloads sd ON sd.sourceId = ps.sourceId AND sd.songId = ps.songId AND sd.status = 'Completed' " + + "WHERE ps.sourceId = playlists.sourceId AND ps.playlistId = playlists.id " + + ") ELSE playlists.songCount END AS songCount, " + + "playlists.created, playlists.changed, playlists.duration " + internal const val PLAYLIST_ORDER_BY_NAME = "name COLLATE NOCASE, id" internal const val PLAYLIST_ORDER_BY_NAME_REVERSED = "name COLLATE NOCASE DESC, id DESC" internal const val PLAYLIST_ORDER_BY_ADDED = "created DESC, name COLLATE NOCASE, id" @@ -364,42 +375,42 @@ interface LibraryDao { downloadedFilter: Int = 0, ): PagingSource - @Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_NAME") + @Query("$PLAYLISTS_SELECT $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_NAME") fun playlistsByName( sourceId: Long, search: String, downloadedFilter: Int = 0, ): PagingSource - @Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_NAME_REVERSED") + @Query("$PLAYLISTS_SELECT $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_NAME_REVERSED") fun playlistsByNameReversed( sourceId: Long, search: String, downloadedFilter: Int = 0, ): PagingSource - @Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_ADDED") + @Query("$PLAYLISTS_SELECT $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_ADDED") fun playlistsByAdded( sourceId: Long, search: String, downloadedFilter: Int = 0, ): PagingSource - @Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_ADDED_REVERSED") + @Query("$PLAYLISTS_SELECT $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_ADDED_REVERSED") fun playlistsByAddedReversed( sourceId: Long, search: String, downloadedFilter: Int = 0, ): PagingSource - @Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_UPDATED") + @Query("$PLAYLISTS_SELECT $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_UPDATED") fun playlistsByUpdated( sourceId: Long, search: String, downloadedFilter: Int = 0, ): PagingSource - @Query("SELECT * $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_UPDATED_REVERSED") + @Query("$PLAYLISTS_SELECT $PLAYLISTS_FILTER ORDER BY $PLAYLIST_ORDER_BY_UPDATED_REVERSED") fun playlistsByUpdatedReversed( sourceId: Long, search: String, diff --git a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt index 1678d420..b8ae3392 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt @@ -51,6 +51,7 @@ fun PlaylistDetailRoute( val playlist by viewModel.playlist.collectAsStateWithLifecycle(initialValue = null) val downloads by viewModel.downloads.collectAsStateWithLifecycle() val downloadStatus by viewModel.downloadStatus.collectAsStateWithLifecycle() + val offline by viewModel.offline.collectAsStateWithLifecycle() val scope = rememberCoroutineScope() var pendingDelete by remember { mutableStateOf(null) } val playback by playbackController.state.collectAsStateWithLifecycle() @@ -81,6 +82,7 @@ fun PlaylistDetailRoute( artwork = rememberArtworkColors(viewModel.coverArt(playlist?.coverArt, true), THEME_TRANSITION_MS), downloads = downloads, downloadStatus = downloadStatus, + offline = offline, onDownloadAction = requestDownloadAction, onBack = onBack, onSongClick = viewModel::play, @@ -111,6 +113,7 @@ fun PlaylistDetailScreen( artwork: ArtworkColors?, downloads: Map = emptyMap(), downloadStatus: ListDownloadStatus = ListDownloadStatus(), + offline: Boolean = false, onDownloadAction: (BulkDownloadAction) -> Unit = {}, onBack: () -> Unit, onSongClick: (Long) -> Unit, @@ -130,7 +133,7 @@ fun PlaylistDetailScreen( art = coverArt(playlist?.coverArt, false), thumbnailRef = coverArt(playlist?.coverArt, true), name = playlist?.name.orEmpty(), - subtitle = playlistSummary(playlist), + subtitle = playlistSummary(playlist, offline, downloadStatus.downloaded), comment = playlist?.comment, hasSongs = songs.itemCount > 0, onPlay = onPlay, @@ -168,10 +171,15 @@ fun PlaylistDetailScreen( ) } -private fun playlistSummary(playlist: Playlist?): String { +private fun playlistSummary( + playlist: Playlist?, + offline: Boolean, + downloadedSongs: Long, +): String { playlist ?: return "" + val songs = if (offline) downloadedSongs else playlist.songCount return listOfNotNull( - "${playlist.songCount} ${if (playlist.songCount == 1L) "song" else "songs"}", + "$songs ${if (songs == 1L) "song" else "songs"}", formatDuration(playlist.duration), ).joinToString(" $DOT ") } diff --git a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailViewModel.kt index 3dfb8029..dd29bbd4 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailViewModel.kt @@ -47,6 +47,8 @@ class PlaylistDetailViewModel( val downloads: StateFlow> = downloadRepository.states() + val offline: StateFlow = sourceRepository.offline + val downloadStatus: StateFlow = sourceId .flatMapLatest { downloadRepository.status(it, DownloadList.Playlist, playlistId) } diff --git a/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt b/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt index 53528e78..dd828eab 100644 --- a/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt @@ -419,6 +419,40 @@ class LibraryDaoTest { ) } + @Test + fun downloadedFilterReportsTheDownloadedSongCountForPlaylists() = + runTest { + val sourceId = source() + val dao = db.libraryDao() + dao.upsertPlaylists(listOf(playlist(sourceId, "pl-1", "List", created = 0, changed = 0).copy(songCount = 5))) + dao.upsertSongs( + listOf( + song(sourceId, "s1", "One", starred = null), + song(sourceId, "s2", "Two", starred = null), + ), + ) + dao.upsertPlaylistSongs( + listOf( + PlaylistSong(sourceId, "pl-1", "s1", 0), + PlaylistSong(sourceId, "pl-1", "s2", 1), + ), + ) + db.downloadDao().upsert(SongDownload(sourceId, "s1", DownloadStatus.Completed)) + + val filtered = dao.playlistsByName(sourceId, "", downloadedFilter = 1).page() + assertEquals(1, filtered.size) + assertEquals(1L, filtered.first().songCount) + + assertEquals( + 5L, + dao + .playlistsByName(sourceId, "") + .page() + .first { it.id == "pl-1" } + .songCount, + ) + } + @Test fun searchMatchesInfixCaseInsensitivelyAndFallsBackToAScanBelowThreeCharacters() = runTest {