mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
home: swap to downloaded sections in offline mode
This commit is contained in:
@@ -710,6 +710,23 @@ interface LibraryDao {
|
||||
)
|
||||
suspend fun starredSongIds(sourceId: Long): List<String>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt FROM songs " +
|
||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||
"JOIN song_downloads d ON d.sourceId = songs.sourceId AND d.songId = songs.id " +
|
||||
"WHERE songs.sourceId = :sourceId AND d.status = 'Completed' " +
|
||||
"ORDER BY songs.title COLLATE NOCASE, songs.id",
|
||||
)
|
||||
fun downloadedSongs(sourceId: Long): Flow<List<AlbumSongItem>>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.id FROM songs " +
|
||||
"JOIN song_downloads d ON d.sourceId = songs.sourceId AND d.songId = songs.id " +
|
||||
"WHERE songs.sourceId = :sourceId AND d.status = 'Completed' " +
|
||||
"ORDER BY songs.title COLLATE NOCASE, songs.id",
|
||||
)
|
||||
suspend fun downloadedSongIds(sourceId: Long): List<String>
|
||||
|
||||
@Query(
|
||||
"SELECT songs.*, albums.coverArt AS coverArt FROM songs " +
|
||||
"LEFT JOIN albums ON albums.sourceId = songs.sourceId AND albums.id = songs.albumId " +
|
||||
|
||||
@@ -90,6 +90,10 @@ class LibraryRepository(
|
||||
|
||||
suspend fun starredSongIds(sourceId: Long): List<String> = db.libraryDao().starredSongIds(sourceId)
|
||||
|
||||
fun downloadedSongs(sourceId: Long): Flow<List<AlbumSongItem>> = db.libraryDao().downloadedSongs(sourceId)
|
||||
|
||||
suspend fun downloadedSongIds(sourceId: Long): List<String> = db.libraryDao().downloadedSongIds(sourceId)
|
||||
|
||||
fun songsByGenre(
|
||||
sourceId: Long,
|
||||
genre: String,
|
||||
|
||||
@@ -37,11 +37,15 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.itemKey
|
||||
import com.subtracks.R
|
||||
import com.subtracks.data.model.Album
|
||||
import com.subtracks.data.model.AlbumSongItem
|
||||
import com.subtracks.data.model.Artist
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
import com.subtracks.data.model.Playlist
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.data.source.StarType
|
||||
import com.subtracks.playback.PlaybackController
|
||||
@@ -78,6 +82,7 @@ enum class HomeSection(
|
||||
fun HomeRoute(
|
||||
onAlbumClick: (Album) -> Unit,
|
||||
onArtistClick: (Artist) -> Unit,
|
||||
onPlaylistClick: (Playlist) -> Unit,
|
||||
onViewAlbum: (String) -> Unit,
|
||||
onViewArtist: (String) -> Unit,
|
||||
onMore: (HomeSection) -> Unit,
|
||||
@@ -90,7 +95,7 @@ fun HomeRoute(
|
||||
viewModel: HomeViewModel = koinViewModel(),
|
||||
playbackController: PlaybackController = koinInject(),
|
||||
) {
|
||||
val feed by viewModel.feed.collectAsStateWithLifecycle()
|
||||
val offline by viewModel.offline.collectAsStateWithLifecycle()
|
||||
val playback by playbackController.state.collectAsStateWithLifecycle()
|
||||
val actions =
|
||||
ItemActions(
|
||||
@@ -100,21 +105,41 @@ fun HomeRoute(
|
||||
viewAlbum = onViewAlbum,
|
||||
viewArtist = onViewArtist,
|
||||
)
|
||||
HomeScreen(
|
||||
feed = feed,
|
||||
coverArt = viewModel::coverArt,
|
||||
playingSongId = playback.item?.id,
|
||||
topInset = topInset,
|
||||
bottomInset = bottomInset,
|
||||
onAlbumClick = onAlbumClick,
|
||||
onArtistClick = onArtistClick,
|
||||
onPlayStarred = viewModel::playStarred,
|
||||
onLongClick = { contextMenuHost?.show(it, actions) },
|
||||
onMore = onMore,
|
||||
onGenreClick = onGenreClick,
|
||||
onDecadeClick = onDecadeClick,
|
||||
onSync = viewModel::sync,
|
||||
)
|
||||
val onLongClick: (MenuTarget) -> Unit = { contextMenuHost?.show(it, actions) }
|
||||
if (offline) {
|
||||
OfflineHomeScreen(
|
||||
albums = viewModel.downloadedAlbums.collectAsLazyPagingItems(),
|
||||
artists = viewModel.downloadedArtists.collectAsLazyPagingItems(),
|
||||
playlists = viewModel.downloadedPlaylists.collectAsLazyPagingItems(),
|
||||
songs = viewModel.downloadedSongs.collectAsStateWithLifecycle(initialValue = emptyList()).value,
|
||||
coverArt = viewModel::coverArt,
|
||||
playingSongId = playback.item?.id,
|
||||
topInset = topInset,
|
||||
bottomInset = bottomInset,
|
||||
onAlbumClick = onAlbumClick,
|
||||
onArtistClick = onArtistClick,
|
||||
onPlaylistClick = onPlaylistClick,
|
||||
onSongPlay = viewModel::playDownloaded,
|
||||
onLongClick = onLongClick,
|
||||
)
|
||||
} else {
|
||||
val feed by viewModel.feed.collectAsStateWithLifecycle()
|
||||
HomeScreen(
|
||||
feed = feed,
|
||||
coverArt = viewModel::coverArt,
|
||||
playingSongId = playback.item?.id,
|
||||
topInset = topInset,
|
||||
bottomInset = bottomInset,
|
||||
onAlbumClick = onAlbumClick,
|
||||
onArtistClick = onArtistClick,
|
||||
onPlayStarred = viewModel::playStarred,
|
||||
onLongClick = onLongClick,
|
||||
onMore = onMore,
|
||||
onGenreClick = onGenreClick,
|
||||
onDecadeClick = onDecadeClick,
|
||||
onSync = viewModel::sync,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -152,24 +177,7 @@ fun HomeScreen(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(top = topInset + 8.dp, bottom = bottomInset + 24.dp),
|
||||
) {
|
||||
item(key = "home-title") {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 12.dp, bottom = 0.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_subtracks_logo),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(width = 34.dp, height = 25.dp),
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(text = "subtracks", style = MaterialTheme.typography.headlineMedium)
|
||||
}
|
||||
}
|
||||
homeTitle()
|
||||
albumRow(
|
||||
HomeSection.RecentlyAddedAlbums,
|
||||
feed.recentlyAddedAlbums,
|
||||
@@ -233,6 +241,188 @@ fun HomeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.homeTitle() {
|
||||
item(key = "home-title") {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 12.dp, bottom = 0.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_subtracks_logo),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(width = 34.dp, height = 25.dp),
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(text = "subtracks", style = MaterialTheme.typography.headlineMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OfflineHomeScreen(
|
||||
albums: LazyPagingItems<Album>,
|
||||
artists: LazyPagingItems<Artist>,
|
||||
playlists: LazyPagingItems<Playlist>,
|
||||
songs: List<AlbumSongItem>,
|
||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||
playingSongId: String?,
|
||||
topInset: Dp = 0.dp,
|
||||
bottomInset: Dp = 0.dp,
|
||||
onAlbumClick: (Album) -> Unit = {},
|
||||
onArtistClick: (Artist) -> Unit = {},
|
||||
onPlaylistClick: (Playlist) -> Unit = {},
|
||||
onSongPlay: (Song) -> Unit = {},
|
||||
onLongClick: (MenuTarget) -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(top = topInset + 8.dp, bottom = bottomInset + 24.dp),
|
||||
) {
|
||||
homeTitle()
|
||||
downloadedAlbumRow(albums, coverArt, onAlbumClick, onLongClick)
|
||||
downloadedArtistRow(artists, coverArt, onArtistClick, onLongClick)
|
||||
downloadedPlaylistRow(playlists, coverArt, onPlaylistClick, onLongClick)
|
||||
songList(
|
||||
key = "downloaded-songs",
|
||||
title = "Downloaded songs",
|
||||
songs = songs,
|
||||
coverArt = coverArt,
|
||||
playingSongId = playingSongId,
|
||||
onSongPlay = onSongPlay,
|
||||
onLongClick = onLongClick,
|
||||
onMore = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.downloadedAlbumRow(
|
||||
albums: LazyPagingItems<Album>,
|
||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||
onAlbumClick: (Album) -> Unit,
|
||||
onLongClick: (MenuTarget) -> Unit,
|
||||
) {
|
||||
if (albums.itemCount == 0) return
|
||||
item(key = "downloaded-albums-header") {
|
||||
HomeSectionHeader("Downloaded albums", null)
|
||||
}
|
||||
item(key = "downloaded-albums-row") {
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(TILE_SPACING),
|
||||
modifier = Modifier.padding(top = SECTION_CONTENT_TOP),
|
||||
) {
|
||||
items(count = albums.itemCount, key = albums.itemKey { it.id }) { index ->
|
||||
val album = albums[index]
|
||||
if (album != null) {
|
||||
val ref = coverArt(album.coverArt, true)
|
||||
AlbumTile(
|
||||
album = album,
|
||||
ref = ref,
|
||||
onClick = { onAlbumClick(album) },
|
||||
onLongClick = { onLongClick(MenuTarget.Album(album, ref)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.downloadedArtistRow(
|
||||
artists: LazyPagingItems<Artist>,
|
||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||
onArtistClick: (Artist) -> Unit,
|
||||
onLongClick: (MenuTarget) -> Unit,
|
||||
) {
|
||||
if (artists.itemCount == 0) return
|
||||
item(key = "downloaded-artists-header") {
|
||||
HomeSectionHeader("Downloaded artists", null)
|
||||
}
|
||||
item(key = "downloaded-artists-row") {
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(ARTIST_SPACING),
|
||||
modifier = Modifier.padding(top = SECTION_CONTENT_TOP),
|
||||
) {
|
||||
items(count = artists.itemCount, key = artists.itemKey { it.id }) { index ->
|
||||
val artist = artists[index]
|
||||
if (artist != null) {
|
||||
val ref = coverArt(artist.coverArt, true)
|
||||
ArtistTile(
|
||||
artist = artist,
|
||||
ref = ref,
|
||||
onClick = { onArtistClick(artist) },
|
||||
onLongClick = { onLongClick(MenuTarget.Artist(artist, ref)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.downloadedPlaylistRow(
|
||||
playlists: LazyPagingItems<Playlist>,
|
||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||
onPlaylistClick: (Playlist) -> Unit,
|
||||
onLongClick: (MenuTarget) -> Unit,
|
||||
) {
|
||||
if (playlists.itemCount == 0) return
|
||||
item(key = "downloaded-playlists-header") {
|
||||
HomeSectionHeader("Downloaded playlists", null)
|
||||
}
|
||||
item(key = "downloaded-playlists-row") {
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(TILE_SPACING),
|
||||
modifier = Modifier.padding(top = SECTION_CONTENT_TOP),
|
||||
) {
|
||||
items(count = playlists.itemCount, key = playlists.itemKey { it.id }) { index ->
|
||||
val playlist = playlists[index]
|
||||
if (playlist != null) {
|
||||
val ref = coverArt(playlist.coverArt, true)
|
||||
PlaylistTile(
|
||||
playlist = playlist,
|
||||
ref = ref,
|
||||
onClick = { onPlaylistClick(playlist) },
|
||||
onLongClick = { onLongClick(MenuTarget.Playlist(playlist, ref)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlaylistTile(
|
||||
playlist: Playlist,
|
||||
ref: CoverArtRef?,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.width(TILE_SIZE).combinedClickable(onClick = onClick, onLongClick = onLongClick),
|
||||
) {
|
||||
CoverArt(
|
||||
ref = ref,
|
||||
name = playlist.name,
|
||||
modifier = Modifier.size(TILE_SIZE).clip(RoundedCornerShape(2.dp)),
|
||||
)
|
||||
Spacer(Modifier.height(6.dp))
|
||||
Text(
|
||||
text = playlist.name,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun HomeFeed.isEmpty(): Boolean =
|
||||
recentlyPlayedAlbums.isEmpty() &&
|
||||
recentlyPlayedArtists.isEmpty() &&
|
||||
@@ -400,10 +590,30 @@ private fun LazyListScope.homeSongs(
|
||||
onSongPlay: (Song) -> Unit,
|
||||
onLongClick: (MenuTarget) -> Unit,
|
||||
onMore: (HomeSection) -> Unit,
|
||||
) = songList(
|
||||
key = section.name,
|
||||
title = section.title,
|
||||
songs = songs,
|
||||
coverArt = coverArt,
|
||||
playingSongId = playingSongId,
|
||||
onSongPlay = onSongPlay,
|
||||
onLongClick = onLongClick,
|
||||
onMore = { onMore(section) },
|
||||
)
|
||||
|
||||
private fun LazyListScope.songList(
|
||||
key: String,
|
||||
title: String,
|
||||
songs: List<AlbumSongItem>,
|
||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||
playingSongId: String?,
|
||||
onSongPlay: (Song) -> Unit,
|
||||
onLongClick: (MenuTarget) -> Unit,
|
||||
onMore: (() -> Unit)?,
|
||||
) {
|
||||
if (songs.isEmpty()) return
|
||||
item(key = "${section.name}-header") {
|
||||
HomeSectionHeader(section.title) { onMore(section) }
|
||||
item(key = "$key-header") {
|
||||
HomeSectionHeader(title, onMore)
|
||||
}
|
||||
items(songs, key = { it.song.id }) { item ->
|
||||
SongRow(
|
||||
|
||||
@@ -2,11 +2,18 @@ package com.subtracks.ui.home
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.subtracks.data.model.Album
|
||||
import com.subtracks.data.model.AlbumSongItem
|
||||
import com.subtracks.data.model.Artist
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
import com.subtracks.data.model.Playlist
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.data.prefs.AlbumSort
|
||||
import com.subtracks.data.prefs.ArtistSort
|
||||
import com.subtracks.data.prefs.PlaylistSort
|
||||
import com.subtracks.data.prefs.StarredFilter
|
||||
import com.subtracks.data.repo.LibraryRepository
|
||||
import com.subtracks.data.repo.SourceRepository
|
||||
import com.subtracks.data.repo.rediscoverCutoff
|
||||
@@ -15,6 +22,7 @@ import com.subtracks.data.sync.SyncStatus
|
||||
import com.subtracks.playback.PlaybackController
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -52,6 +60,52 @@ class HomeViewModel(
|
||||
.map { it == SyncStatus.Running }
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false)
|
||||
|
||||
val offline: StateFlow<Boolean> =
|
||||
sourceRepository.offline.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), false)
|
||||
|
||||
private val activeSource = libraryRepository.activeSourceId.filterNotNull()
|
||||
|
||||
val downloadedAlbums: Flow<PagingData<Album>> =
|
||||
activeSource
|
||||
.flatMapLatest { id ->
|
||||
libraryRepository.albums(
|
||||
sourceId = id,
|
||||
sort = AlbumSort.Added,
|
||||
descending = false,
|
||||
starred = StarredFilter.Any,
|
||||
search = "",
|
||||
downloaded = true,
|
||||
)
|
||||
}.cachedIn(viewModelScope)
|
||||
|
||||
val downloadedArtists: Flow<PagingData<Artist>> =
|
||||
activeSource
|
||||
.flatMapLatest { id ->
|
||||
libraryRepository.artists(
|
||||
sourceId = id,
|
||||
sort = ArtistSort.Name,
|
||||
descending = false,
|
||||
starred = StarredFilter.Any,
|
||||
search = "",
|
||||
downloaded = true,
|
||||
)
|
||||
}.cachedIn(viewModelScope)
|
||||
|
||||
val downloadedPlaylists: Flow<PagingData<Playlist>> =
|
||||
activeSource
|
||||
.flatMapLatest { id ->
|
||||
libraryRepository.playlists(
|
||||
sourceId = id,
|
||||
sort = PlaylistSort.Name,
|
||||
descending = false,
|
||||
search = "",
|
||||
downloaded = true,
|
||||
)
|
||||
}.cachedIn(viewModelScope)
|
||||
|
||||
val downloadedSongs: Flow<List<AlbumSongItem>> =
|
||||
activeSource.flatMapLatest { libraryRepository.downloadedSongs(it) }
|
||||
|
||||
val feed: StateFlow<HomeFeed> =
|
||||
libraryRepository.activeSourceId
|
||||
.filterNotNull()
|
||||
@@ -105,6 +159,10 @@ class HomeViewModel(
|
||||
viewModelScope.playStarredList(libraryRepository, playbackController, song)
|
||||
}
|
||||
|
||||
fun playDownloaded(song: Song) {
|
||||
viewModelScope.playDownloadedList(libraryRepository, playbackController, song)
|
||||
}
|
||||
|
||||
fun sync() = syncManager.requestSync()
|
||||
|
||||
private data class PlayRows(
|
||||
@@ -185,3 +243,14 @@ internal fun CoroutineScope.playGenreList(
|
||||
playbackController.playSongs(song.sourceId, ids, ids.indexOf(song.id).coerceAtLeast(0))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun CoroutineScope.playDownloadedList(
|
||||
libraryRepository: LibraryRepository,
|
||||
playbackController: PlaybackController,
|
||||
song: Song,
|
||||
) {
|
||||
launch {
|
||||
val ids = libraryRepository.downloadedSongIds(song.sourceId)
|
||||
playbackController.playSongs(song.sourceId, ids, ids.indexOf(song.id).coerceAtLeast(0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +323,7 @@ fun LibraryRoute(
|
||||
HomeRoute(
|
||||
onAlbumClick = onAlbumClick,
|
||||
onArtistClick = onArtistClick,
|
||||
onPlaylistClick = onPlaylistClick,
|
||||
onViewAlbum = onViewAlbum,
|
||||
onViewArtist = onViewArtist,
|
||||
onMore = onHomeMore,
|
||||
|
||||
@@ -9,7 +9,9 @@ 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.Song
|
||||
import com.subtracks.data.model.SongDownload
|
||||
import com.subtracks.data.model.Source
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -163,6 +165,30 @@ class LibraryHomeDaoTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun downloadedSongsOnlyIncludeCompletedDownloadsWithCoverArt() =
|
||||
runTest {
|
||||
val sourceId = source()
|
||||
val dao = db.libraryDao()
|
||||
dao.upsertAlbums(listOf(album(sourceId, "al1", "Album", coverArt = "cover-1")))
|
||||
dao.upsertSongs(
|
||||
listOf(
|
||||
song(sourceId, "s1", "One"),
|
||||
song(sourceId, "s2", "Two"),
|
||||
song(sourceId, "s3", "Three"),
|
||||
),
|
||||
)
|
||||
val downloads = db.downloadDao()
|
||||
downloads.upsert(SongDownload(sourceId, "s1", DownloadStatus.Completed))
|
||||
downloads.upsert(SongDownload(sourceId, "s2", DownloadStatus.Queued))
|
||||
downloads.upsert(SongDownload(sourceId, "s3", DownloadStatus.Completed))
|
||||
|
||||
val downloaded = dao.downloadedSongs(sourceId).first()
|
||||
assertEquals(listOf("s1", "s3"), downloaded.map { it.song.id })
|
||||
assertEquals("cover-1", downloaded.first().coverArt)
|
||||
assertEquals(listOf("s1", "s3"), dao.downloadedSongIds(sourceId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun songsByGenreMatchesTheGenreRelationsWithFallback() =
|
||||
runTest {
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.github.takahirom.roborazzi.captureRoboImage
|
||||
import com.subtracks.data.model.Album
|
||||
import com.subtracks.data.model.AlbumSongItem
|
||||
import com.subtracks.data.model.Artist
|
||||
import com.subtracks.data.model.Playlist
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.ui.theme.SubtracksTheme
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
@@ -143,6 +144,28 @@ class HomeScreenshotTest {
|
||||
played = played,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun homeOffline() {
|
||||
composeRule.setContent {
|
||||
SubtracksTheme {
|
||||
Surface(modifier = Modifier.fillMaxSize()) {
|
||||
OfflineHomeScreen(
|
||||
albums = remember { flowOf(PagingData.from(listOf(listAlbum("Offline Album", 0)))) }.collectAsLazyPagingItems(),
|
||||
artists = remember { flowOf(PagingData.from(listOf(listArtist("Offline Artist", 0)))) }.collectAsLazyPagingItems(),
|
||||
playlists = remember { flowOf(PagingData.empty<Playlist>()) }.collectAsLazyPagingItems(),
|
||||
songs = songs(),
|
||||
coverArt = { _, _ -> null },
|
||||
playingSongId = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
composeRule.waitUntil(timeoutMillis = 5_000) {
|
||||
composeRule.onAllNodesWithText("Downloaded albums").fetchSemanticsNodes().isNotEmpty()
|
||||
}
|
||||
composeRule.onRoot().captureRoboImage("src/test/screenshots/home_offline.png")
|
||||
}
|
||||
|
||||
private fun listAlbum(
|
||||
name: String,
|
||||
created: Long,
|
||||
|
||||
Reference in New Issue
Block a user