From 7d04125f58d74e533f98ad59bddc0824a809f70b Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Mon, 28 Sep 2026 07:34:04 +0000 Subject: [PATCH] add the Downloaded filter to the library tabs One toggle per tab, persisted with the rest of the list query, narrowing to entities that have at least one completed download. It rides the existing filter SQL: every album, artist and playlist paging query takes a flag and the shared filter constant adds an EXISTS over song_downloads, so it composes with the sort, search and starred filter rather than being a separate mode. The filters sheet now renders on every tab, since playlists support Downloaded but not Starred. --- .../com/subtracks/data/db/LibraryDao.kt | 32 +++++++++ .../subtracks/data/prefs/UserPreferences.kt | 7 +- .../subtracks/data/repo/LibraryRepository.kt | 50 +++++++------- .../com/subtracks/ui/library/LibraryScreen.kt | 66 ++++++++++++------- .../subtracks/ui/library/LibraryViewModel.kt | 6 +- .../com/subtracks/data/db/LibraryDaoTest.kt | 50 ++++++++++++++ 6 files changed, 162 insertions(+), 49 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 658dd693..ccfff12c 100644 --- a/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt +++ b/app/src/main/kotlin/com/subtracks/data/db/LibraryDao.kt @@ -29,6 +29,9 @@ internal const val PLAYLIST_SONGS_SQL = PLAYLIST_SONGS_SELECT + PLAYLIST_SONGS_O internal const val ALBUMS_FILTER = "FROM albums WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + + "AND (:downloadedFilter = 0 OR EXISTS (SELECT 1 FROM songs " + + "JOIN song_downloads sd ON sd.sourceId = songs.sourceId AND sd.songId = songs.id " + + "WHERE songs.sourceId = albums.sourceId AND songs.albumId = albums.id AND sd.status = 'Completed')) " + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0 OR instr(lower(albumArtist), lower(:search)) > 0) " internal const val ALBUM_ORDER_BY_NAME = "name COLLATE NOCASE, id" @@ -48,6 +51,10 @@ internal const val ALBUM_ORDER_BY_STARRED_REVERSED = "starred ASC NULLS LAST, na internal const val ARTISTS_FILTER = "FROM artists WHERE sourceId = :sourceId " + "AND (:starredFilter = 0 OR (:starredFilter = 1 AND starred IS NOT NULL) OR (:starredFilter = 2 AND starred IS NULL)) " + + "AND (:downloadedFilter = 0 OR EXISTS (SELECT 1 FROM songs " + + "JOIN albums dl ON dl.sourceId = songs.sourceId AND dl.id = songs.albumId " + + "JOIN song_downloads sd ON sd.sourceId = songs.sourceId AND sd.songId = songs.id " + + "WHERE songs.sourceId = artists.sourceId AND dl.artistId = artists.id AND sd.status = 'Completed')) " + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " internal const val ARTIST_ORDER_BY_NAME = "name COLLATE NOCASE, id" @@ -59,6 +66,9 @@ internal const val ARTIST_ORDER_BY_STARRED_REVERSED = "starred ASC NULLS LAST, n internal const val PLAYLISTS_FILTER = "FROM playlists WHERE sourceId = :sourceId " + + "AND (:downloadedFilter = 0 OR EXISTS (SELECT 1 FROM playlist_songs ps " + + "JOIN song_downloads sd ON sd.sourceId = ps.sourceId AND sd.songId = ps.songId " + + "WHERE ps.sourceId = playlists.sourceId AND ps.playlistId = playlists.id AND sd.status = 'Completed')) " + "AND (:search = '' OR instr(lower(name), lower(:search)) > 0) " internal const val PLAYLIST_ORDER_BY_NAME = "name COLLATE NOCASE, id" @@ -205,6 +215,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_NAME_REVERSED") @@ -212,6 +223,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_ARTIST") @@ -219,6 +231,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_ARTIST_REVERSED") @@ -226,6 +239,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_YEAR") @@ -233,6 +247,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_YEAR_REVERSED") @@ -240,6 +255,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_ADDED") @@ -247,6 +263,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_ADDED_REVERSED") @@ -254,6 +271,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_STARRED") @@ -261,6 +279,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ALBUMS_FILTER ORDER BY $ALBUM_ORDER_BY_STARRED_REVERSED") @@ -268,6 +287,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_NAME") @@ -275,6 +295,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_NAME_REVERSED") @@ -282,6 +303,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_ALBUM_COUNT") @@ -289,6 +311,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_ALBUM_COUNT_REVERSED") @@ -296,6 +319,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_STARRED") @@ -303,6 +327,7 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("SELECT * $ARTISTS_FILTER ORDER BY $ARTIST_ORDER_BY_STARRED_REVERSED") @@ -310,42 +335,49 @@ interface LibraryDao { sourceId: Long, starredFilter: Int, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("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") fun playlistsByNameReversed( sourceId: Long, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("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") fun playlistsByAddedReversed( sourceId: Long, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query("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") fun playlistsByUpdatedReversed( sourceId: Long, search: String, + downloadedFilter: Int = 0, ): PagingSource @Query(PLAYLIST_SONGS_SQL) diff --git a/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt b/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt index f4366f5e..cf260e19 100644 --- a/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt +++ b/app/src/main/kotlin/com/subtracks/data/prefs/UserPreferences.kt @@ -35,6 +35,7 @@ data class ListQuery( val sort: String, val descending: Boolean = false, val starred: StarredFilter = StarredFilter.Any, + val downloaded: Boolean = false, ) data class StreamQuality( @@ -107,7 +108,8 @@ class UserPreferences( private fun listQueryKey(tab: LibraryListTab) = stringPreferencesKey("list_query_${tab.key}") - private fun encode(query: ListQuery) = "${query.sort}|${if (query.descending) 1 else 0}|${query.starred.ordinal}" + private fun encode(query: ListQuery) = + "${query.sort}|${if (query.descending) 1 else 0}|${query.starred.ordinal}|${if (query.downloaded) 1 else 0}" private fun encode(quality: StreamQuality) = "${quality.maxBitrate}|${quality.format.orEmpty()}" @@ -130,11 +132,12 @@ class UserPreferences( defaultSort: String, ): ListQuery { val parts = stored?.split("|") - if (parts == null || parts.size != 3) return ListQuery(defaultSort) + if (parts == null || parts.size < 3) return ListQuery(defaultSort) return ListQuery( sort = parts[0].ifEmpty { defaultSort }, descending = parts[1] == "1", starred = StarredFilter.entries.getOrElse(parts[2].toIntOrNull() ?: 0) { StarredFilter.Any }, + downloaded = parts.getOrNull(3) == "1", ) } } 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 8b85a653..e6623bb4 100644 --- a/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt +++ b/app/src/main/kotlin/com/subtracks/data/repo/LibraryRepository.kt @@ -41,47 +41,49 @@ class LibraryRepository( descending: Boolean, starred: StarredFilter, search: String, + downloaded: Boolean, ): Flow> = pager(40) { val dao = db.libraryDao() + val downloadedFilter = if (downloaded) 1 else 0 when (sort) { AlbumSort.Name -> { if (descending) { - dao.albumsByNameReversed(sourceId, starred.ordinal, search) + dao.albumsByNameReversed(sourceId, starred.ordinal, search, downloadedFilter) } else { - dao.albumsByName(sourceId, starred.ordinal, search) + dao.albumsByName(sourceId, starred.ordinal, search, downloadedFilter) } } AlbumSort.Artist -> { if (descending) { - dao.albumsByArtistReversed(sourceId, starred.ordinal, search) + dao.albumsByArtistReversed(sourceId, starred.ordinal, search, downloadedFilter) } else { - dao.albumsByArtist(sourceId, starred.ordinal, search) + dao.albumsByArtist(sourceId, starred.ordinal, search, downloadedFilter) } } AlbumSort.Year -> { if (descending) { - dao.albumsByYearReversed(sourceId, starred.ordinal, search) + dao.albumsByYearReversed(sourceId, starred.ordinal, search, downloadedFilter) } else { - dao.albumsByYear(sourceId, starred.ordinal, search) + dao.albumsByYear(sourceId, starred.ordinal, search, downloadedFilter) } } AlbumSort.Added -> { if (descending) { - dao.albumsByRecentlyAddedReversed(sourceId, starred.ordinal, search) + dao.albumsByRecentlyAddedReversed(sourceId, starred.ordinal, search, downloadedFilter) } else { - dao.albumsByRecentlyAdded(sourceId, starred.ordinal, search) + dao.albumsByRecentlyAdded(sourceId, starred.ordinal, search, downloadedFilter) } } AlbumSort.Starred -> { if (descending) { - dao.albumsByStarredReversed(sourceId, starred.ordinal, search) + dao.albumsByStarredReversed(sourceId, starred.ordinal, search, downloadedFilter) } else { - dao.albumsByStarred(sourceId, starred.ordinal, search) + dao.albumsByStarred(sourceId, starred.ordinal, search, downloadedFilter) } } } @@ -93,31 +95,33 @@ class LibraryRepository( descending: Boolean, starred: StarredFilter, search: String, + downloaded: Boolean, ): Flow> = pager(60) { val dao = db.libraryDao() + val downloadedFilter = if (downloaded) 1 else 0 when (sort) { ArtistSort.Name -> { if (descending) { - dao.artistsByNameReversed(sourceId, starred.ordinal, search) + dao.artistsByNameReversed(sourceId, starred.ordinal, search, downloadedFilter) } else { - dao.artistsByName(sourceId, starred.ordinal, search) + dao.artistsByName(sourceId, starred.ordinal, search, downloadedFilter) } } ArtistSort.AlbumCount -> { if (descending) { - dao.artistsByAlbumCountReversed(sourceId, starred.ordinal, search) + dao.artistsByAlbumCountReversed(sourceId, starred.ordinal, search, downloadedFilter) } else { - dao.artistsByAlbumCount(sourceId, starred.ordinal, search) + dao.artistsByAlbumCount(sourceId, starred.ordinal, search, downloadedFilter) } } ArtistSort.Starred -> { if (descending) { - dao.artistsByStarredReversed(sourceId, starred.ordinal, search) + dao.artistsByStarredReversed(sourceId, starred.ordinal, search, downloadedFilter) } else { - dao.artistsByStarred(sourceId, starred.ordinal, search) + dao.artistsByStarred(sourceId, starred.ordinal, search, downloadedFilter) } } } @@ -128,31 +132,33 @@ class LibraryRepository( sort: PlaylistSort, descending: Boolean, search: String, + downloaded: Boolean, ): Flow> = pager(40) { val dao = db.libraryDao() + val downloadedFilter = if (downloaded) 1 else 0 when (sort) { PlaylistSort.Name -> { if (descending) { - dao.playlistsByNameReversed(sourceId, search) + dao.playlistsByNameReversed(sourceId, search, downloadedFilter) } else { - dao.playlistsByName(sourceId, search) + dao.playlistsByName(sourceId, search, downloadedFilter) } } PlaylistSort.Added -> { if (descending) { - dao.playlistsByAddedReversed(sourceId, search) + dao.playlistsByAddedReversed(sourceId, search, downloadedFilter) } else { - dao.playlistsByAdded(sourceId, search) + dao.playlistsByAdded(sourceId, search, downloadedFilter) } } PlaylistSort.Updated -> { if (descending) { - dao.playlistsByUpdatedReversed(sourceId, search) + dao.playlistsByUpdatedReversed(sourceId, search, downloadedFilter) } else { - dao.playlistsByUpdated(sourceId, search) + dao.playlistsByUpdated(sourceId, search, downloadedFilter) } } } 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 17a9d9ed..df2fa3df 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.material.icons.rounded.Album 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.DownloadDone import androidx.compose.material.icons.rounded.FilterAltOff import androidx.compose.material.icons.rounded.Person import androidx.compose.material.icons.rounded.Remove @@ -212,7 +213,7 @@ fun LibraryRoute( 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" + "${query.sort}|${query.descending}|${query.starred}|${query.downloaded}|$term" } val playback by playbackController.state.collectAsStateWithLifecycle() val artwork = rememberArtworkColors(playbackController.coverArt(playback.item, thumbnail = true)) @@ -271,11 +272,12 @@ fun LibraryRoute( viewModel.setListQuery(listTab, listQuery.copy(starred = next)) }, onClearFilters = { - viewModel.setListQuery(listTab, listQuery.copy(starred = StarredFilter.Any)) + viewModel.setListQuery(listTab, listQuery.copy(starred = StarredFilter.Any, downloaded = false)) viewModel.setSearch(listTab, "") }, search = search, onSearchChange = { viewModel.setSearch(listTab, it) }, + onToggleDownloaded = { viewModel.setListQuery(listTab, listQuery.copy(downloaded = !listQuery.downloaded)) }, albumDownloads = downloads.takeIf { listTab == LibraryListTab.Albums }.orEmpty(), artistDownloads = downloads.takeIf { listTab == LibraryListTab.Artists }.orEmpty(), playlistDownloads = downloads.takeIf { listTab == LibraryListTab.Playlists }.orEmpty(), @@ -315,6 +317,7 @@ fun LibraryScreen( onSortChange: (String) -> Unit = {}, onToggleSortDirection: () -> Unit = {}, onCycleStarred: () -> Unit = {}, + onToggleDownloaded: () -> Unit = {}, onClearFilters: () -> Unit = {}, search: String = "", onSearchChange: (String) -> Unit = {}, @@ -355,7 +358,7 @@ fun LibraryScreen( val density = LocalDensity.current val statusBarTop = with(density) { WindowInsets.statusBars.getTop(this).toDp() } - val filtersActive = listQuery.starred != StarredFilter.Any || search.isNotEmpty() + val filtersActive = listQuery.starred != StarredFilter.Any || listQuery.downloaded || search.isNotEmpty() var tabBarHeightPx by remember { mutableFloatStateOf(with(density) { (TAB_BAR_CONTENT_HEIGHT + bottomInset).toPx() }) } val tabBarHeight = with(density) { tabBarHeightPx.toDp() } val listTopInset = statusBarTop @@ -540,6 +543,7 @@ fun LibraryScreen( onSortChange = onSortChange, onToggleSortDirection = onToggleSortDirection, onCycleStarred = onCycleStarred, + onToggleDownloaded = onToggleDownloaded, onClearFilters = onClearFilters, onSearch = { showOptions = false @@ -560,6 +564,7 @@ private fun ListOptionsSheet( onSortChange: (String) -> Unit, onToggleSortDirection: () -> Unit, onCycleStarred: () -> Unit, + onToggleDownloaded: () -> Unit, onClearFilters: () -> Unit, onSearch: () -> Unit, ) { @@ -615,27 +620,44 @@ private fun ListOptionsSheet( } } - if (starredSupported) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 8.dp, top = 16.dp), + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 8.dp, top = 16.dp), + ) { + Text( + text = "Filters", + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.weight(1f), + ) + IconButton( + onClick = onClearFilters, + enabled = canClearFilters, ) { - Text( - text = "Filters", - style = MaterialTheme.typography.titleMedium, - modifier = Modifier.weight(1f), - ) - IconButton( - onClick = onClearFilters, - enabled = canClearFilters, - ) { - Icon(Icons.Rounded.FilterAltOff, contentDescription = "Clear filters") - } + Icon(Icons.Rounded.FilterAltOff, contentDescription = "Clear filters") } - FlowRow( - horizontalArrangement = Arrangement.spacedBy(8.dp), - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - ) { + } + FlowRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + ) { + FilterChip( + selected = listQuery.downloaded, + onClick = onToggleDownloaded, + label = { Text("Downloaded") }, + leadingIcon = + if (listQuery.downloaded) { + { + Icon( + Icons.Rounded.DownloadDone, + contentDescription = null, + modifier = Modifier.size(FilterChipDefaults.IconSize), + ) + } + } else { + null + }, + ) + if (starredSupported) { FilterChip( selected = listQuery.starred != StarredFilter.Any, onClick = onCycleStarred, 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 1c2654cb..71afa02a 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryViewModel.kt @@ -62,7 +62,7 @@ class LibraryViewModel( combine(listQueries.getValue(LibraryListTab.Albums), searches.getValue(LibraryListTab.Albums)) { query, search -> query to search }.flatMapLatest { (query, search) -> - libraryRepository.albums(sourceId, query.albumSort(), query.descending, query.starred, search) + libraryRepository.albums(sourceId, query.albumSort(), query.descending, query.starred, search, query.downloaded) } }.cachedIn(viewModelScope) @@ -73,7 +73,7 @@ class LibraryViewModel( combine(listQueries.getValue(LibraryListTab.Artists), searches.getValue(LibraryListTab.Artists)) { query, search -> query to search }.flatMapLatest { (query, search) -> - libraryRepository.artists(sourceId, query.artistSort(), query.descending, query.starred, search) + libraryRepository.artists(sourceId, query.artistSort(), query.descending, query.starred, search, query.downloaded) } }.cachedIn(viewModelScope) @@ -84,7 +84,7 @@ class LibraryViewModel( combine(listQueries.getValue(LibraryListTab.Playlists), searches.getValue(LibraryListTab.Playlists)) { query, search -> query to search }.flatMapLatest { (query, search) -> - libraryRepository.playlists(sourceId, query.playlistSort(), query.descending, search) + libraryRepository.playlists(sourceId, query.playlistSort(), query.descending, search, query.downloaded) } }.cachedIn(viewModelScope) 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 b0f347e2..7782910e 100644 --- a/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt +++ b/app/src/test/kotlin/com/subtracks/data/db/LibraryDaoTest.kt @@ -9,8 +9,11 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.subtracks.data.model.Album import com.subtracks.data.model.Artist +import com.subtracks.data.model.DownloadStatus import com.subtracks.data.model.Playlist +import com.subtracks.data.model.PlaylistSong import com.subtracks.data.model.Song +import com.subtracks.data.model.SongDownload import com.subtracks.data.model.Source import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest @@ -290,6 +293,53 @@ class LibraryDaoTest { assertTrue(plan, plan.contains("TEMP B-TREE")) } + @Test + fun theDownloadedFilterKeepsOnlyEntitiesWithACompletedDownload() = + runTest { + val sourceId = source() + val dao = db.libraryDao() + dao.upsertAlbums( + listOf( + album(sourceId, "al-1", "Downloaded", year = 2000, starred = null), + album(sourceId, "al-2", "Empty", year = 2001, starred = null), + ), + ) + dao.upsertArtists(listOf(artist(sourceId, "ar-1", "Artist", albumCount = 2))) + dao.upsertSongs( + listOf( + song(sourceId, "s1", "One", starred = null), + song(sourceId, "s2", "Two", starred = null).copy(albumId = "al-2"), + ), + ) + dao.upsertPlaylists( + listOf( + playlist(sourceId, "pl-1", "Has download", created = 0, changed = 0), + playlist(sourceId, "pl-2", "No download", created = 1, changed = 1), + ), + ) + dao.upsertPlaylistSongs( + listOf( + PlaylistSong(sourceId, "pl-1", "s1", 0), + PlaylistSong(sourceId, "pl-2", "s2", 0), + ), + ) + db.downloadDao().upsert(SongDownload(sourceId, "s1", DownloadStatus.Completed)) + + assertEquals( + listOf("Downloaded"), + dao.albumsByName(sourceId, 0, "", downloadedFilter = 1).page().map { it.name }, + ) + assertEquals( + listOf("ar-1"), + dao.artistsByName(sourceId, 0, "", downloadedFilter = 1).page().map { it.id }, + ) + assertEquals( + listOf("pl-1"), + dao.playlistsByName(sourceId, "", downloadedFilter = 1).page().map { it.id }, + ) + assertEquals(2, dao.albumsByName(sourceId, 0, "").page().size) + } + private suspend fun plan(sql: String): String = db.useReaderConnection { connection -> connection.usePrepared("EXPLAIN QUERY PLAN $sql") { statement ->