mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 23:21:59 +01:00
group the downloads tree by the album's artist
The tree read the track artist off each song, so a downloaded album split into one group per guest performer and every song without a track artist landed in "Unknown artist". It reads the album's artist now, which is also the artist the node's delete resolves through, so the two agree. The badges are white glyphs with a dark outline and a progress ring with a visible track, after a look at the rendered result; larger and clearer than the first pass, which the art swallowed.
This commit is contained in:
@@ -99,11 +99,11 @@ interface DownloadDao {
|
||||
|
||||
@Query(
|
||||
"SELECT d.songId AS songId, s.title AS title, s.albumId AS albumId, al.name AS albumName, " +
|
||||
"s.artistId AS artistId, ar.name AS artistName, d.status AS status, d.total AS size " +
|
||||
"al.artistId AS artistId, ar.name AS artistName, d.status AS status, d.total AS size " +
|
||||
"FROM song_downloads d " +
|
||||
"JOIN songs s ON s.sourceId = d.sourceId AND s.id = d.songId " +
|
||||
"LEFT JOIN albums al ON al.sourceId = s.sourceId AND al.id = s.albumId " +
|
||||
"LEFT JOIN artists ar ON ar.sourceId = s.sourceId AND ar.id = s.artistId " +
|
||||
"LEFT JOIN artists ar ON ar.sourceId = al.sourceId AND ar.id = al.artistId " +
|
||||
"WHERE d.sourceId = :sourceId",
|
||||
)
|
||||
fun downloadedSongs(sourceId: Long): Flow<List<DownloadedSong>>
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -38,36 +39,42 @@ fun ListDownloadIndicator(
|
||||
val current = status ?: return
|
||||
val kind = current.indicator()
|
||||
if (kind == DownloadIndicator.Hidden) return
|
||||
Box(modifier = modifier.size(20.dp), contentAlignment = Alignment.Center) {
|
||||
Box(modifier = modifier.size(26.dp), contentAlignment = Alignment.Center) {
|
||||
when (kind) {
|
||||
DownloadIndicator.Complete -> {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DownloadDone,
|
||||
contentDescription = null,
|
||||
tint = ICON_SHADOW,
|
||||
modifier = Modifier.size(18.dp).blur(3.dp).offset(y = 1.dp),
|
||||
tint = Color.Black.copy(alpha = 0.5f),
|
||||
modifier = Modifier.size(22.dp).blur(4.dp).offset(y = 1.dp),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DownloadDone,
|
||||
contentDescription = null,
|
||||
tint = Color.Black.copy(alpha = 0.85f),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(22.dp)
|
||||
.graphicsLayer {
|
||||
scaleX = 1.18f
|
||||
scaleY = 1.18f
|
||||
}.blur(1.dp),
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DownloadDone,
|
||||
contentDescription = DOWNLOADED_INDICATOR_TAG,
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(18.dp),
|
||||
modifier = Modifier.size(22.dp),
|
||||
)
|
||||
}
|
||||
|
||||
DownloadIndicator.InProgress -> {
|
||||
val progress = { current.downloaded.toFloat() / current.total }
|
||||
CircularProgressIndicator(
|
||||
progress = progress,
|
||||
color = ICON_SHADOW,
|
||||
strokeWidth = 2.dp,
|
||||
modifier = Modifier.size(16.dp).blur(3.dp).offset(y = 1.dp),
|
||||
)
|
||||
CircularProgressIndicator(
|
||||
progress = progress,
|
||||
progress = { current.downloaded.toFloat() / current.total },
|
||||
color = Color.White,
|
||||
strokeWidth = 2.dp,
|
||||
modifier = Modifier.size(16.dp).semantics { contentDescription = DOWNLOADING_INDICATOR_TAG },
|
||||
trackColor = Color.Black.copy(alpha = 0.45f),
|
||||
strokeWidth = 2.5.dp,
|
||||
modifier = Modifier.size(20.dp).semantics { contentDescription = DOWNLOADING_INDICATOR_TAG },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -77,5 +84,3 @@ fun ListDownloadIndicator(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val ICON_SHADOW = Color.Black.copy(alpha = 0.45f)
|
||||
|
||||
@@ -816,6 +816,41 @@ class DownloadRepositoryTest {
|
||||
assertFalse(file(1, "s2").exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theTreeGroupsAnAlbumUnderItsAlbumArtistNotEachTrackArtist() =
|
||||
runTest {
|
||||
seedLibrary()
|
||||
runBlocking {
|
||||
db.libraryDao().upsertSongs(
|
||||
listOf(
|
||||
Song(
|
||||
sourceId = 1,
|
||||
id = "s3",
|
||||
albumId = "al1",
|
||||
artistId = "ar-guest",
|
||||
title = "Song 3",
|
||||
album = "Album",
|
||||
artist = "Guest",
|
||||
duration = 100,
|
||||
track = 3,
|
||||
disc = 1,
|
||||
starred = null,
|
||||
genre = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
db.libraryDao().upsertArtists(
|
||||
listOf(Artist(sourceId = 1, id = "ar-guest", name = "Guest", albumCount = 0, starred = null, coverArt = null)),
|
||||
)
|
||||
db.downloadDao().upsert(SongDownload(1, "s1", DownloadStatus.Completed))
|
||||
db.downloadDao().upsert(SongDownload(1, "s3", DownloadStatus.Completed))
|
||||
}
|
||||
|
||||
val songs = runBlocking { repository.downloadedSongs(1).first() }
|
||||
|
||||
assertEquals(setOf("ar1" to "Artist"), songs.map { it.artistId to it.artistName }.toSet())
|
||||
}
|
||||
|
||||
private fun requestedSongIds(): List<String> = engine.requests.map { it.second.path.substringAfterLast('/') }
|
||||
|
||||
private fun seedSongs(tracks: IntRange) {
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.github.takahirom.roborazzi.captureRoboImage
|
||||
import com.subtracks.data.model.Album
|
||||
import com.subtracks.data.model.Artist
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
import com.subtracks.data.model.ListDownloadStatus
|
||||
import com.subtracks.data.model.Playlist
|
||||
import com.subtracks.data.model.PlaylistSongItem
|
||||
import com.subtracks.data.model.Song
|
||||
@@ -125,6 +126,37 @@ class AppScreenshotTest {
|
||||
composeRule.onRoot().captureRoboImage("src/test/screenshots/library_scrolled_under_header.png")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun libraryAlbumsWithDownloads() {
|
||||
val ids = Fixtures.albums.take(3).map { it.id }
|
||||
composeRule.setContent {
|
||||
SubtracksTheme {
|
||||
LibraryScreen(
|
||||
selectedTab = LibraryTab.Albums,
|
||||
onTabSelected = {},
|
||||
albums = remember { flowOf(PagingData.from(Fixtures.albums)) },
|
||||
artists = remember { flowOf(PagingData.empty<Artist>()) },
|
||||
playlists = remember { flowOf(PagingData.empty<Playlist>()) },
|
||||
coverArt = { id, _ -> id?.let { CoverArtRef(it, "test:$it") } },
|
||||
onAlbumClick = {},
|
||||
onArtistClick = {},
|
||||
onPlaylistClick = {},
|
||||
onSync = {},
|
||||
onOpenSettings = {},
|
||||
artwork = artworkColorsFromSeed(Color.rgb(120, 80, 200)),
|
||||
albumDownloads =
|
||||
mapOf(
|
||||
ids[0] to ListDownloadStatus(total = 10, downloaded = 10),
|
||||
ids[1] to ListDownloadStatus(total = 10, downloaded = 4, downloading = 1),
|
||||
ids[2] to ListDownloadStatus(total = 10, downloaded = 4),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
awaitTag(ALBUM_COVER_TAG)
|
||||
composeRule.onRoot().captureRoboImage("src/test/screenshots/library_albums_downloads.png")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun libraryArtists() {
|
||||
setLibraryContent(LibraryTab.Artists)
|
||||
|
||||
Reference in New Issue
Block a user