diff --git a/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicSource.kt b/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicSource.kt index 019bb3d5..a7417791 100644 --- a/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicSource.kt +++ b/app/src/main/kotlin/com/subtracks/data/source/subsonic/SubsonicSource.kt @@ -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> = fetchRank("frequent") to fetchRank("recent") diff --git a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt index 41c5a843..fa8d21fa 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt @@ -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, + ) {}, ) } } diff --git a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt index 4e92a537..f7b3a607 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt @@ -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,