mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 07:29:14 +01:00
library: address artist art review findings
Cap the resolver at four covers, re-rank after deduplication so duplicate covers do not consume the grid budget, carry a thumbnail per fallback step, and advance to the grid when both the full image and its thumbnail fail. Negative-cache failed provider-image fetches per artist URL, clear materialized downloaded covers when all downloads are removed, and drop redundant plumbing.
This commit is contained in:
@@ -209,8 +209,10 @@ internal const val ARTIST_ART_UPDATE_SQL =
|
|||||||
"FROM credits JOIN albums ON albums.sourceId = :sourceId AND albums.id = credits.albumId " +
|
"FROM credits JOIN albums ON albums.sourceId = :sourceId AND albums.id = credits.albumId " +
|
||||||
"WHERE albums.coverArt IS NOT NULL), " +
|
"WHERE albums.coverArt IS NOT NULL), " +
|
||||||
"unique_ranked AS (SELECT artistId, coverArt, MIN(coverRank) AS coverRank FROM ranked GROUP BY artistId, coverArt), " +
|
"unique_ranked AS (SELECT artistId, coverArt, MIN(coverRank) AS coverRank FROM ranked GROUP BY artistId, coverArt), " +
|
||||||
|
"deduped AS (SELECT artistId, coverArt, " +
|
||||||
|
"ROW_NUMBER() OVER (PARTITION BY artistId ORDER BY coverRank) AS coverRank FROM unique_ranked), " +
|
||||||
"covers AS (SELECT artistId, group_concat(coverArt, char(31)) AS coverArts FROM (" +
|
"covers AS (SELECT artistId, group_concat(coverArt, char(31)) AS coverArts FROM (" +
|
||||||
"SELECT artistId, coverArt, coverRank FROM unique_ranked WHERE coverRank <= 4 ORDER BY artistId, coverRank" +
|
"SELECT artistId, coverArt, coverRank FROM deduped WHERE coverRank <= 4 ORDER BY artistId, coverRank" +
|
||||||
") GROUP BY artistId), " +
|
") GROUP BY artistId), " +
|
||||||
"downloaded_albums AS (SELECT DISTINCT artistId, albumId FROM ($DOWNLOADED_COVER_ALBUM_ROWS)), " +
|
"downloaded_albums AS (SELECT DISTINCT artistId, albumId FROM ($DOWNLOADED_COVER_ALBUM_ROWS)), " +
|
||||||
"downloaded AS (SELECT dl.artistId AS artistId, albums.coverArt AS coverArt, " +
|
"downloaded AS (SELECT dl.artistId AS artistId, albums.coverArt AS coverArt, " +
|
||||||
@@ -219,8 +221,10 @@ internal const val ARTIST_ART_UPDATE_SQL =
|
|||||||
"FROM downloaded_albums dl JOIN albums ON albums.sourceId = :sourceId AND albums.id = dl.albumId " +
|
"FROM downloaded_albums dl JOIN albums ON albums.sourceId = :sourceId AND albums.id = dl.albumId " +
|
||||||
"WHERE albums.coverArt IS NOT NULL), " +
|
"WHERE albums.coverArt IS NOT NULL), " +
|
||||||
"unique_downloaded AS (SELECT artistId, coverArt, MIN(coverRank) AS coverRank FROM downloaded GROUP BY artistId, coverArt), " +
|
"unique_downloaded AS (SELECT artistId, coverArt, MIN(coverRank) AS coverRank FROM downloaded GROUP BY artistId, coverArt), " +
|
||||||
|
"downloaded_deduped AS (SELECT artistId, coverArt, " +
|
||||||
|
"ROW_NUMBER() OVER (PARTITION BY artistId ORDER BY coverRank) AS coverRank FROM unique_downloaded), " +
|
||||||
"downloaded_covers AS (SELECT artistId, group_concat(coverArt, char(31)) AS coverArts FROM (" +
|
"downloaded_covers AS (SELECT artistId, group_concat(coverArt, char(31)) AS coverArts FROM (" +
|
||||||
"SELECT artistId, coverArt, coverRank FROM unique_downloaded WHERE coverRank <= 4 ORDER BY artistId, coverRank" +
|
"SELECT artistId, coverArt, coverRank FROM downloaded_deduped WHERE coverRank <= 4 ORDER BY artistId, coverRank" +
|
||||||
") GROUP BY artistId) " +
|
") GROUP BY artistId) " +
|
||||||
"UPDATE artists SET " +
|
"UPDATE artists SET " +
|
||||||
"coverArts = COALESCE((SELECT coverArts FROM covers WHERE covers.artistId = artists.id), ''), " +
|
"coverArts = COALESCE((SELECT coverArts FROM covers WHERE covers.artistId = artists.id), ''), " +
|
||||||
@@ -237,8 +241,10 @@ internal const val DOWNLOADED_ARTIST_ART_UPDATE_SQL =
|
|||||||
"FROM downloaded_albums dl JOIN albums ON albums.sourceId = :sourceId AND albums.id = dl.albumId " +
|
"FROM downloaded_albums dl JOIN albums ON albums.sourceId = :sourceId AND albums.id = dl.albumId " +
|
||||||
"WHERE albums.coverArt IS NOT NULL), " +
|
"WHERE albums.coverArt IS NOT NULL), " +
|
||||||
"unique_downloaded AS (SELECT artistId, coverArt, MIN(coverRank) AS coverRank FROM downloaded GROUP BY artistId, coverArt), " +
|
"unique_downloaded AS (SELECT artistId, coverArt, MIN(coverRank) AS coverRank FROM downloaded GROUP BY artistId, coverArt), " +
|
||||||
|
"downloaded_deduped AS (SELECT artistId, coverArt, " +
|
||||||
|
"ROW_NUMBER() OVER (PARTITION BY artistId ORDER BY coverRank) AS coverRank FROM unique_downloaded), " +
|
||||||
"downloaded_covers AS (SELECT artistId, group_concat(coverArt, char(31)) AS coverArts FROM (" +
|
"downloaded_covers AS (SELECT artistId, group_concat(coverArt, char(31)) AS coverArts FROM (" +
|
||||||
"SELECT artistId, coverArt, coverRank FROM unique_downloaded WHERE coverRank <= 4 ORDER BY artistId, coverRank" +
|
"SELECT artistId, coverArt, coverRank FROM downloaded_deduped WHERE coverRank <= 4 ORDER BY artistId, coverRank" +
|
||||||
") GROUP BY artistId) " +
|
") GROUP BY artistId) " +
|
||||||
"UPDATE artists SET downloadedCoverArts = COALESCE((SELECT coverArts FROM downloaded_covers " +
|
"UPDATE artists SET downloadedCoverArts = COALESCE((SELECT coverArts FROM downloaded_covers " +
|
||||||
"WHERE downloaded_covers.artistId = artists.id), '') " +
|
"WHERE downloaded_covers.artistId = artists.id), '') " +
|
||||||
@@ -406,6 +412,9 @@ interface LibraryDao {
|
|||||||
artistIds: List<String>,
|
artistIds: List<String>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Query("UPDATE artists SET downloadedCoverArts = '' WHERE sourceId = :sourceId AND downloadedCoverArts != ''")
|
||||||
|
suspend fun clearDownloadedArtistArt(sourceId: Long)
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
suspend fun recomputeAggregates(sourceId: Long) {
|
suspend fun recomputeAggregates(sourceId: Long) {
|
||||||
recomputeAlbumPlayData(sourceId)
|
recomputeAlbumPlayData(sourceId)
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class DownloadRepository(
|
|||||||
|
|
||||||
private val statesFlow = MutableStateFlow<Map<String, SongDownload>>(emptyMap())
|
private val statesFlow = MutableStateFlow<Map<String, SongDownload>>(emptyMap())
|
||||||
private val attemptedBios = ConcurrentHashMap.newKeySet<Pair<Long, String>>()
|
private val attemptedBios = ConcurrentHashMap.newKeySet<Pair<Long, String>>()
|
||||||
|
private val failedArtistImages = ConcurrentHashMap.newKeySet<String>()
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
if (started) return
|
if (started) return
|
||||||
@@ -377,6 +378,7 @@ class DownloadRepository(
|
|||||||
.mapNotNull { it.engineId }
|
.mapNotNull { it.engineId }
|
||||||
.let(engine::cancel)
|
.let(engine::cancel)
|
||||||
db.downloadDao().deleteSource(sourceId)
|
db.downloadDao().deleteSource(sourceId)
|
||||||
|
db.libraryDao().clearDownloadedArtistArt(sourceId)
|
||||||
dir(sourceId).deleteRecursively()
|
dir(sourceId).deleteRecursively()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,13 +424,9 @@ class DownloadRepository(
|
|||||||
val albumArtist = album?.artistId?.let { db.libraryDao().artistOnce(sourceId, it) }
|
val albumArtist = album?.artistId?.let { db.libraryDao().artistOnce(sourceId, it) }
|
||||||
listOfNotNull(song?.artistId, album?.artistId).forEach(artistIds::add)
|
listOfNotNull(song?.artistId, album?.artistId).forEach(artistIds::add)
|
||||||
listOfNotNull(songArtist, albumArtist).forEach { storeArtistImage(sourceId, it) }
|
listOfNotNull(songArtist, albumArtist).forEach { storeArtistImage(sourceId, it) }
|
||||||
val artistFallbackCovers =
|
|
||||||
listOfNotNull(songArtist, albumArtist)
|
|
||||||
.filter { it.artistImageUrl.isNullOrBlank() }
|
|
||||||
.mapNotNull { it.coverArt }
|
|
||||||
val coverArt =
|
val coverArt =
|
||||||
(
|
(
|
||||||
listOfNotNull(album?.coverArt) + artistFallbackCovers +
|
listOfNotNull(album?.coverArt) +
|
||||||
db.downloadDao().playlistCoversForSong(sourceId, songId)
|
db.downloadDao().playlistCoversForSong(sourceId, songId)
|
||||||
).distinct()
|
).distinct()
|
||||||
coverArt.forEach { cover ->
|
coverArt.forEach { cover ->
|
||||||
@@ -445,17 +443,21 @@ class DownloadRepository(
|
|||||||
if (!imageUrl.isNullOrBlank()) {
|
if (!imageUrl.isNullOrBlank()) {
|
||||||
val cacheKey = artistImageKey(sourceId, artist.id)
|
val cacheKey = artistImageKey(sourceId, artist.id)
|
||||||
if (artworkStore.file(sourceId, cacheKey).exists()) return
|
if (artworkStore.file(sourceId, cacheKey).exists()) return
|
||||||
val bytes =
|
val attemptKey = "$sourceId:${artist.id}:$imageUrl"
|
||||||
try {
|
if (attemptKey !in failedArtistImages) {
|
||||||
artworkFetcher.fetch(imageUrl)
|
val bytes =
|
||||||
} catch (e: CancellationException) {
|
try {
|
||||||
throw e
|
artworkFetcher.fetch(imageUrl)
|
||||||
} catch (_: Exception) {
|
} catch (e: CancellationException) {
|
||||||
null
|
throw e
|
||||||
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
if (bytes != null && bytes.isNotEmpty()) {
|
||||||
|
artworkStore.write(sourceId, cacheKey, bytes)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if (bytes != null && bytes.isNotEmpty()) {
|
failedArtistImages += attemptKey
|
||||||
artworkStore.write(sourceId, cacheKey, bytes)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
artist.coverArt?.let {
|
artist.coverArt?.let {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import kotlin.math.sin
|
|||||||
|
|
||||||
data class ArtistArt(
|
data class ArtistArt(
|
||||||
val image: CoverArtRef? = null,
|
val image: CoverArtRef? = null,
|
||||||
|
val thumbnail: CoverArtRef? = null,
|
||||||
val covers: List<CoverArtRef> = emptyList(),
|
val covers: List<CoverArtRef> = emptyList(),
|
||||||
val fallback: ArtistArt? = null,
|
val fallback: ArtistArt? = null,
|
||||||
) {
|
) {
|
||||||
@@ -57,15 +58,22 @@ internal fun resolveArtistArt(
|
|||||||
val coverIds = if (offline) artist.downloadedCoverArts else artist.coverArts
|
val coverIds = if (offline) artist.downloadedCoverArts else artist.coverArts
|
||||||
val grid =
|
val grid =
|
||||||
ArtistArt(
|
ArtistArt(
|
||||||
covers = coverIds.mapNotNull { coverArt(it, forIcon || coverIds.size > 1) },
|
thumbnail = coverIds.singleOrNull()?.let { coverArt(it, true) },
|
||||||
|
covers = coverIds.take(4).mapNotNull { coverArt(it, forIcon || coverIds.size > 1) },
|
||||||
)
|
)
|
||||||
val own =
|
val own =
|
||||||
artist.coverArt
|
artist.coverArt?.let { coverId ->
|
||||||
?.let { coverArt(it, forIcon) }
|
coverArt(coverId, forIcon)?.let { image ->
|
||||||
?.let { ArtistArt(image = it, fallback = grid) }
|
ArtistArt(
|
||||||
|
image = image,
|
||||||
|
thumbnail = if (forIcon) image else coverArt(coverId, true),
|
||||||
|
fallback = grid,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
return artist.artistImageUrl
|
return artist.artistImageUrl
|
||||||
?.let { artistImage(artist.id, it) }
|
?.let { artistImage(artist.id, it) }
|
||||||
?.let { ArtistArt(image = it, fallback = own ?: grid) }
|
?.let { ArtistArt(image = it, thumbnail = it, fallback = own ?: grid) }
|
||||||
?: own
|
?: own
|
||||||
?: grid
|
?: grid
|
||||||
}
|
}
|
||||||
@@ -104,7 +112,6 @@ fun ArtistIcon(
|
|||||||
fun ArtistHeroArt(
|
fun ArtistHeroArt(
|
||||||
art: ArtistArt,
|
art: ArtistArt,
|
||||||
name: String,
|
name: String,
|
||||||
thumbnail: CoverArtRef?,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
var shown by remember(art) { mutableStateOf(art) }
|
var shown by remember(art) { mutableStateOf(art) }
|
||||||
@@ -116,7 +123,7 @@ fun ArtistHeroArt(
|
|||||||
CoverArt(
|
CoverArt(
|
||||||
ref = shown.image,
|
ref = shown.image,
|
||||||
name = name,
|
name = name,
|
||||||
thumbnailRef = thumbnail,
|
thumbnailRef = shown.thumbnail,
|
||||||
showPlaceholder = false,
|
showPlaceholder = false,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onError = showFallback,
|
onError = showFallback,
|
||||||
@@ -127,7 +134,7 @@ fun ArtistHeroArt(
|
|||||||
ArtistArtGrid(
|
ArtistArtGrid(
|
||||||
covers = shown.covers,
|
covers = shown.covers,
|
||||||
name = name,
|
name = name,
|
||||||
singleThumbnail = thumbnail,
|
singleThumbnail = shown.thumbnail,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ fun CoverArt(
|
|||||||
val hasThumbnailFallback = thumbnailRef != null && thumbnailRef != ref
|
val hasThumbnailFallback = thumbnailRef != null && thumbnailRef != ref
|
||||||
var failed by remember(refKey, thumbnailKey) { mutableStateOf(false) }
|
var failed by remember(refKey, thumbnailKey) { mutableStateOf(false) }
|
||||||
var thumbnailLoaded by remember(refKey, thumbnailKey) { mutableStateOf(false) }
|
var thumbnailLoaded by remember(refKey, thumbnailKey) { mutableStateOf(false) }
|
||||||
|
var thumbnailFailed by remember(refKey, thumbnailKey) { mutableStateOf(false) }
|
||||||
var thumbnailRatio by remember(refKey, thumbnailKey) { mutableStateOf(thumbnailKey?.let(ArtworkRatioCache::get)) }
|
var thumbnailRatio by remember(refKey, thumbnailKey) { mutableStateOf(thumbnailKey?.let(ArtworkRatioCache::get)) }
|
||||||
val letterScheme =
|
val letterScheme =
|
||||||
if (showPlaceholder || (failed && !thumbnailLoaded)) {
|
if (showPlaceholder || (failed && !thumbnailLoaded)) {
|
||||||
@@ -117,6 +118,10 @@ fun CoverArt(
|
|||||||
thumbnailLoaded = thumbnailLoaded,
|
thumbnailLoaded = thumbnailLoaded,
|
||||||
onThumbnailLoaded = { thumbnailLoaded = true },
|
onThumbnailLoaded = { thumbnailLoaded = true },
|
||||||
onThumbnailRatio = { thumbnailRatio = it },
|
onThumbnailRatio = { thumbnailRatio = it },
|
||||||
|
onThumbnailError = {
|
||||||
|
thumbnailFailed = true
|
||||||
|
if (failed) onError?.invoke()
|
||||||
|
},
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
colorFilter = colorFilter,
|
colorFilter = colorFilter,
|
||||||
placeholderContentColor = letterScheme?.onPrimaryContainer,
|
placeholderContentColor = letterScheme?.onPrimaryContainer,
|
||||||
@@ -135,7 +140,7 @@ fun CoverArt(
|
|||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
failed = true
|
failed = true
|
||||||
if (!hasThumbnailFallback) onError?.invoke()
|
if (!hasThumbnailFallback || thumbnailFailed) onError?.invoke()
|
||||||
},
|
},
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
)
|
)
|
||||||
@@ -167,7 +172,7 @@ fun CoverArt(
|
|||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
failed = true
|
failed = true
|
||||||
if (!hasThumbnailFallback) onError?.invoke()
|
if (!hasThumbnailFallback || thumbnailFailed) onError?.invoke()
|
||||||
},
|
},
|
||||||
contentScale = ContentScale.Fit,
|
contentScale = ContentScale.Fit,
|
||||||
filterQuality = FilterQuality.High,
|
filterQuality = FilterQuality.High,
|
||||||
@@ -187,6 +192,10 @@ fun CoverArt(
|
|||||||
thumbnailLoaded = thumbnailLoaded,
|
thumbnailLoaded = thumbnailLoaded,
|
||||||
onThumbnailLoaded = { thumbnailLoaded = true },
|
onThumbnailLoaded = { thumbnailLoaded = true },
|
||||||
onThumbnailRatio = { thumbnailRatio = it },
|
onThumbnailRatio = { thumbnailRatio = it },
|
||||||
|
onThumbnailError = {
|
||||||
|
thumbnailFailed = true
|
||||||
|
if (failed) onError?.invoke()
|
||||||
|
},
|
||||||
contentScale = ContentScale.Fit,
|
contentScale = ContentScale.Fit,
|
||||||
colorFilter = colorFilter,
|
colorFilter = colorFilter,
|
||||||
placeholderContentColor = letterScheme?.onPrimaryContainer,
|
placeholderContentColor = letterScheme?.onPrimaryContainer,
|
||||||
@@ -216,6 +225,7 @@ private fun BoxScope.CoverArtContent(
|
|||||||
thumbnailLoaded: Boolean,
|
thumbnailLoaded: Boolean,
|
||||||
onThumbnailLoaded: () -> Unit,
|
onThumbnailLoaded: () -> Unit,
|
||||||
onThumbnailRatio: (Float) -> Unit,
|
onThumbnailRatio: (Float) -> Unit,
|
||||||
|
onThumbnailError: () -> Unit,
|
||||||
contentScale: ContentScale,
|
contentScale: ContentScale,
|
||||||
colorFilter: ColorFilter?,
|
colorFilter: ColorFilter?,
|
||||||
placeholderContentColor: Color?,
|
placeholderContentColor: Color?,
|
||||||
@@ -254,6 +264,7 @@ private fun BoxScope.CoverArtContent(
|
|||||||
ArtworkRatioCache.put(thumbnailRef.cacheKey, it)
|
ArtworkRatioCache.put(thumbnailRef.cacheKey, it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onError = { onThumbnailError() },
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ fun OfflineHomeScreen(
|
|||||||
) {
|
) {
|
||||||
homeTitle()
|
homeTitle()
|
||||||
downloadedAlbumRow(albums, coverArt, onAlbumClick, onLongClick) { onMore(OfflineListKind.Albums) }
|
downloadedAlbumRow(albums, coverArt, onAlbumClick, onLongClick) { onMore(OfflineListKind.Albums) }
|
||||||
downloadedArtistRow(artists, coverArt, artistArt, onArtistClick, onLongClick) { onMore(OfflineListKind.Artists) }
|
downloadedArtistRow(artists, artistArt, onArtistClick, onLongClick) { onMore(OfflineListKind.Artists) }
|
||||||
downloadedPlaylistRow(playlists, coverArt, onPlaylistClick, onLongClick) { onMore(OfflineListKind.Playlists) }
|
downloadedPlaylistRow(playlists, coverArt, onPlaylistClick, onLongClick) { onMore(OfflineListKind.Playlists) }
|
||||||
songList(
|
songList(
|
||||||
key = "downloaded-songs",
|
key = "downloaded-songs",
|
||||||
@@ -372,7 +372,6 @@ private fun LazyListScope.downloadedAlbumRow(
|
|||||||
|
|
||||||
private fun LazyListScope.downloadedArtistRow(
|
private fun LazyListScope.downloadedArtistRow(
|
||||||
artists: LazyPagingItems<Artist>,
|
artists: LazyPagingItems<Artist>,
|
||||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
|
||||||
artistArt: (Artist) -> ArtistArt,
|
artistArt: (Artist) -> ArtistArt,
|
||||||
onArtistClick: (Artist) -> Unit,
|
onArtistClick: (Artist) -> Unit,
|
||||||
onLongClick: (MenuTarget) -> Unit,
|
onLongClick: (MenuTarget) -> Unit,
|
||||||
|
|||||||
@@ -286,7 +286,6 @@ fun ArtistDetailScreen(
|
|||||||
bioExpanded: Boolean = false,
|
bioExpanded: Boolean = false,
|
||||||
art: CoverArtRef?,
|
art: CoverArtRef?,
|
||||||
artThumbnail: CoverArtRef? = null,
|
artThumbnail: CoverArtRef? = null,
|
||||||
artCovers: List<CoverArtRef> = emptyList(),
|
|
||||||
heroArt: ArtistArt? = null,
|
heroArt: ArtistArt? = null,
|
||||||
artwork: ArtworkColors? = null,
|
artwork: ArtworkColors? = null,
|
||||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||||
@@ -406,13 +405,8 @@ fun ArtistDetailScreen(
|
|||||||
) {
|
) {
|
||||||
Box(Modifier.fillMaxWidth()) {
|
Box(Modifier.fillMaxWidth()) {
|
||||||
ArtistHeroArt(
|
ArtistHeroArt(
|
||||||
art =
|
art = heroArt ?: ArtistArt(image = art, thumbnail = artThumbnail),
|
||||||
heroArt ?: ArtistArt(
|
|
||||||
image = art.takeIf { artCovers.isEmpty() },
|
|
||||||
covers = artCovers,
|
|
||||||
),
|
|
||||||
name = displayName,
|
name = displayName,
|
||||||
thumbnail = artThumbnail,
|
|
||||||
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
|
modifier = Modifier.fillMaxWidth().height(ART_HEIGHT),
|
||||||
)
|
)
|
||||||
if (nameScrimAlpha > 0f) {
|
if (nameScrimAlpha > 0f) {
|
||||||
|
|||||||
@@ -682,6 +682,27 @@ class LibraryDaoTest {
|
|||||||
assertEquals(listOf("art-2", "art-1"), dao.artistOnce(sourceId, "ar-1")!!.downloadedCoverArts)
|
assertEquals(listOf("art-2", "art-1"), dao.artistOnce(sourceId, "ar-1")!!.downloadedCoverArts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun artistArtDeduplicatesCoversBeforeApplyingTheFourCoverBudget() =
|
||||||
|
runTest {
|
||||||
|
val sourceId = source()
|
||||||
|
val dao = db.libraryDao()
|
||||||
|
dao.upsertArtists(listOf(artist(sourceId, "ar-1", "Artist", albumCount = 6)))
|
||||||
|
dao.upsertAlbums(
|
||||||
|
listOf("A", "A", "O", "B", "C", "D").mapIndexed { index, cover ->
|
||||||
|
album(sourceId, "al-$index", "Album $index", year = 2021L - index, starred = null)
|
||||||
|
.copy(artistId = "ar-1", coverArt = cover)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
dao.recomputeArtistArt(sourceId)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf("A", "O", "B", "C"),
|
||||||
|
dao.artistOnce(sourceId, "ar-1")!!.coverArts,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun artistArtMaterializationUsesTheCreditIndexes() =
|
fun artistArtMaterializationUsesTheCreditIndexes() =
|
||||||
runTest {
|
runTest {
|
||||||
|
|||||||
@@ -346,7 +346,10 @@ class DownloadRepositoryTest {
|
|||||||
runTest {
|
runTest {
|
||||||
seedLibrary()
|
seedLibrary()
|
||||||
writeFile(1, "s1", "audio")
|
writeFile(1, "s1", "audio")
|
||||||
runBlocking { repository.download(1, "s1") }
|
runBlocking {
|
||||||
|
repository.download(1, "s1")
|
||||||
|
db.libraryDao().upsertArtists(listOf(artist(ARTIST_ART).copy(downloadedCoverArts = listOf(ALBUM_ART))))
|
||||||
|
}
|
||||||
val id = engine.requests.single().first
|
val id = engine.requests.single().first
|
||||||
|
|
||||||
runBlocking { repository.removeSource(1) }
|
runBlocking { repository.removeSource(1) }
|
||||||
@@ -354,6 +357,13 @@ class DownloadRepositoryTest {
|
|||||||
assertTrue(engine.cancelled.contains(id))
|
assertTrue(engine.cancelled.contains(id))
|
||||||
assertFalse(file(1, "s1").exists())
|
assertFalse(file(1, "s1").exists())
|
||||||
assertTrue(allRows().isEmpty())
|
assertTrue(allRows().isEmpty())
|
||||||
|
assertTrue(
|
||||||
|
db
|
||||||
|
.libraryDao()
|
||||||
|
.artistOnce(1, "ar1")!!
|
||||||
|
.downloadedCoverArts
|
||||||
|
.isEmpty(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -334,11 +334,14 @@ class AppScreenshotTest {
|
|||||||
),
|
),
|
||||||
art = CoverArtRef("art-al-kid-a", "test:art-al-kid-a"),
|
art = CoverArtRef("art-al-kid-a", "test:art-al-kid-a"),
|
||||||
artThumbnail = CoverArtRef("art-al-kid-a", "test:art-al-kid-a"),
|
artThumbnail = CoverArtRef("art-al-kid-a", "test:art-al-kid-a"),
|
||||||
artCovers =
|
heroArt =
|
||||||
Fixtures.albums
|
ArtistArt(
|
||||||
.take(3)
|
covers =
|
||||||
.mapNotNull { it.coverArt }
|
Fixtures.albums
|
||||||
.map { CoverArtRef(it, "test:$it") },
|
.take(3)
|
||||||
|
.mapNotNull { it.coverArt }
|
||||||
|
.map { CoverArtRef(it, "test:$it") },
|
||||||
|
),
|
||||||
artwork = artworkColorsFromSeed(Color.rgb(120, 80, 200)),
|
artwork = artworkColorsFromSeed(Color.rgb(120, 80, 200)),
|
||||||
coverArt = { id, _ -> id?.let { CoverArtRef(it, "test:$it") } },
|
coverArt = { id, _ -> id?.let { CoverArtRef(it, "test:$it") } },
|
||||||
onBack = {},
|
onBack = {},
|
||||||
|
|||||||
@@ -76,8 +76,16 @@ class ArtistArtTest {
|
|||||||
}
|
}
|
||||||
val artistImage: (String, String?) -> CoverArtRef? = { _, _ -> null }
|
val artistImage: (String, String?) -> CoverArtRef? = { _, _ -> null }
|
||||||
|
|
||||||
resolveArtistArt(artist(coverArts = listOf("a")), coverArt, artistImage, offline = false, forIcon = false)
|
val single =
|
||||||
assertEquals(listOf(false), thumbnails)
|
resolveArtistArt(
|
||||||
|
artist(coverArts = listOf("a")),
|
||||||
|
coverArt,
|
||||||
|
artistImage,
|
||||||
|
offline = false,
|
||||||
|
forIcon = false,
|
||||||
|
)
|
||||||
|
assertEquals(listOf(true, false), thumbnails)
|
||||||
|
assertEquals("a", single.thumbnail?.url)
|
||||||
|
|
||||||
thumbnails.clear()
|
thumbnails.clear()
|
||||||
resolveArtistArt(artist(coverArts = listOf("a", "b")), coverArt, artistImage, offline = false, forIcon = false)
|
resolveArtistArt(artist(coverArts = listOf("a", "b")), coverArt, artistImage, offline = false, forIcon = false)
|
||||||
|
|||||||
Reference in New Issue
Block a user