diff --git a/app/src/main/kotlin/com/subtracks/ui/components/HeroDetailScaffold.kt b/app/src/main/kotlin/com/subtracks/ui/components/HeroDetailScaffold.kt index 2fd58b7f..3dd43b0d 100644 --- a/app/src/main/kotlin/com/subtracks/ui/components/HeroDetailScaffold.kt +++ b/app/src/main/kotlin/com/subtracks/ui/components/HeroDetailScaffold.kt @@ -73,6 +73,13 @@ import com.subtracks.ui.theme.heroDarkenAt private const val GRADIENT_SCREENS = 2.0f private const val FADE_DISTANCE_DP = 64 +/** + * A themed detail screen: endless artwork gradient, a fading top bar, and a lazy list whose first + * item is [header]. The header and its controls are measured so the bar fades in as they scroll off. + * + * [content] emits the rows after the header and assumes they share a uniform height, which is used + * to estimate the scroll offset for the bar. + */ @OptIn(ExperimentalMaterial3Api::class) @Composable fun HeroDetailScaffold( 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 447eb440..d41f8c0e 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt @@ -8,6 +8,7 @@ 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 @@ -39,12 +40,24 @@ class ArtistDetailViewModel( artist .filterNotNull() .collectLatest { loaded -> - _art.value = sourceRepository.coverArt(loaded.coverArt) - _artThumbnail.value = sourceRepository.coverArt(loaded.coverArt, thumbnail = true) + val (full, thumbnail) = resolveArt(loaded) + _art.value = full + _artThumbnail.value = thumbnail } } } + private suspend fun resolveArt(artist: Artist): Pair { + repeat(3) { attempt -> + val full = sourceRepository.coverArt(artist.coverArt) + if (full != null) { + return full to sourceRepository.coverArt(artist.coverArt, thumbnail = true) + } + if (attempt < 2) delay(300) + } + return null to null + } + fun coverArt( coverArt: String?, thumbnail: Boolean, diff --git a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt index 87cf0c85..5a563526 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt @@ -103,9 +103,13 @@ private fun playlistSummary(playlist: Playlist?): String { } private fun formatDuration(seconds: Long): String? { - if (seconds <= 0) return null + if (seconds < 60) return null val totalMinutes = seconds / 60 val hours = totalMinutes / 60 val minutes = totalMinutes % 60 - return if (hours > 0) "$hours hr $minutes min" else "$totalMinutes min" + return when { + hours == 0L -> "$totalMinutes min" + minutes == 0L -> "$hours hr" + else -> "$hours hr $minutes min" + } } diff --git a/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt b/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt index bf931ec6..d8402b1d 100644 --- a/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt @@ -75,10 +75,7 @@ fun NowPlayingRoute( val thumbnail = controller.coverArt(state.item, thumbnail = true) LaunchedEffect(state.item?.id, state.hasNext) { if (!state.hasNext) return@LaunchedEffect - controller.upcomingCoverArt(thumbnail = true)?.let { - ArtworkSeedCache.prefetch(context, it) - prefetchImage(context, it) - } + controller.upcomingCoverArt(thumbnail = true)?.let { ArtworkSeedCache.prefetch(context, it) } controller.upcomingCoverArt()?.let { prefetchImage(context, it) } } NowPlayingScreen(