address artist detail review findings

This commit is contained in:
austinried
2026-10-04 00:29:52 +00:00
parent 754323bfdc
commit a78d093042
3 changed files with 54 additions and 13 deletions
@@ -6,6 +6,7 @@ import com.subtracks.data.model.Playlist
import com.subtracks.data.model.PlaylistSong
import com.subtracks.data.model.Song
import com.subtracks.data.source.MusicSource
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -98,9 +99,13 @@ class SubsonicSource(
thumbnail: Boolean = false,
): String? =
withContext(Dispatchers.IO) {
runCatching {
try {
SubsonicXml.artistImageUrl(client.get("getArtistInfo2", mapOf("id" to artistId)), thumbnail)
}.getOrNull()
} catch (e: CancellationException) {
throw e
} catch (_: Exception) {
null
}
}
private fun fetchRanks(): Pair<Map<String, Long>, Map<String, Long>> = fetchRank("frequent") to fetchRank("recent")
@@ -2,6 +2,7 @@ package com.subtracks.ui.library
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -11,6 +12,7 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.lazy.grid.GridCells
@@ -94,16 +96,30 @@ fun ArtistDetailScreen(
.toDp()
}
val statusBarTop = with(density) { WindowInsets.statusBars.getTop(this).toDp() }
val navBarBottom = with(density) { WindowInsets.navigationBars.getBottom(this).toDp() }
val barHeight = statusBarTop + TopAppBarDefaults.TopAppBarExpandedHeight
val imageTitleTop = ART_HEIGHT - TITLE_INSET - lineHeight
val fadeStartPx = with(density) { (imageTitleTop - barHeight - FADE_LEAD).toPx() }
val fadeEndPx = with(density) { (imageTitleTop + lineHeight / 2 - barHeight).toPx() }
val barFraction by remember {
val barFraction by remember(fadeStartPx, fadeEndPx) {
derivedStateOf {
if (listState.firstVisibleItemIndex > 0) {
1f
} else {
((listState.firstVisibleItemScrollOffset - fadeStartPx) / (fadeEndPx - fadeStartPx)).coerceIn(0f, 1f)
when {
listState.layoutInfo.visibleItemsInfo.isEmpty() -> {
0f
}
!listState.canScrollForward -> {
1f
}
listState.firstVisibleItemIndex > 0 -> {
1f
}
else -> {
((listState.firstVisibleItemScrollOffset - fadeStartPx) / (fadeEndPx - fadeStartPx))
.coerceIn(0f, 1f)
}
}
}
}
@@ -118,11 +134,11 @@ fun ArtistDetailScreen(
),
)
Box(modifier.fillMaxSize()) {
Box(modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
LazyVerticalGrid(
state = listState,
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(bottom = 16.dp),
contentPadding = PaddingValues(bottom = 16.dp + navBarBottom),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxSize(),
@@ -207,7 +223,14 @@ fun ArtistDetailScreen(
}
},
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
modifier = Modifier.align(Alignment.TopStart).background(Color.Black.copy(alpha = barFraction)),
modifier =
Modifier
.align(Alignment.TopStart)
.background(Color.Black.copy(alpha = barFraction))
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
) {},
)
}
}
@@ -8,11 +8,13 @@ import com.subtracks.data.model.CoverArtRef
import com.subtracks.data.repo.LibraryRepository
import com.subtracks.data.repo.SourceRepository
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
@@ -33,11 +35,22 @@ class ArtistDetailViewModel(
init {
viewModelScope.launch {
val loaded = artist.filterNotNull().first()
_art.value = sourceRepository.artistArt(loaded.id) ?: sourceRepository.coverArt(loaded.coverArt)
artist
.filterNotNull()
.distinctUntilChangedBy { it.id }
.collectLatest { loaded -> _art.value = resolveArt(loaded) }
}
}
private suspend fun resolveArt(artist: Artist): CoverArtRef? {
repeat(3) { attempt ->
val ref = sourceRepository.artistArt(artist.id) ?: sourceRepository.coverArt(artist.coverArt)
if (ref != null) return ref
if (attempt < 2) delay(300)
}
return null
}
fun coverArt(
coverArt: String?,
thumbnail: Boolean,