mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
show the downloaded song count on a playlist offline
This commit is contained in:
@@ -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<Int, Artist>
|
||||
|
||||
@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<Int, Playlist>
|
||||
|
||||
@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<Int, Playlist>
|
||||
|
||||
@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<Int, Playlist>
|
||||
|
||||
@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<Int, Playlist>
|
||||
|
||||
@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<Int, Playlist>
|
||||
|
||||
@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,
|
||||
|
||||
@@ -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<Long?>(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<String, SongDownload> = 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 ")
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ class PlaylistDetailViewModel(
|
||||
val downloads: StateFlow<Map<String, SongDownload>> =
|
||||
downloadRepository.states()
|
||||
|
||||
val offline: StateFlow<Boolean> = sourceRepository.offline
|
||||
|
||||
val downloadStatus: StateFlow<ListDownloadStatus> =
|
||||
sourceId
|
||||
.flatMapLatest { downloadRepository.status(it, DownloadList.Playlist, playlistId) }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user