Playback errors slice + scrubber polish + various polish #74

Merged
bvandeusen merged 13 commits from dev into main 2026-06-02 14:14:11 -04:00
4 changed files with 23 additions and 9 deletions
Showing only changes of commit 1fdd785ee5 - Show all commits
@@ -109,8 +109,11 @@ private fun AlbumDetailStateContent(
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel,
navController: NavHostController,
) {
Crossfade(targetState = state, label = "album-detail") { s ->
when (s) {
// Crossfade keyed on `state::class` so a Success → fresh Success
// refresh (same kind, new detail) doesn't re-fade the whole body;
// only Loading ↔ Success ↔ Error transitions animate.
Crossfade(targetState = state::class, label = "album-detail") { _ ->
when (val s = state) {
is AlbumDetailUiState.Loading ->
if (s.seed != null) SeededAlbumLoading(s.seed) else SkeletonTrackList()
is AlbumDetailUiState.Error -> EmptyState(
@@ -87,8 +87,11 @@ fun ArtistDetailScreen(
onRefresh = { viewModel.refresh().join() },
modifier = Modifier.fillMaxSize().padding(inner),
) {
Crossfade(targetState = state, label = "artist-detail") { s ->
when (s) {
// Crossfade keyed on `state::class` so a Success → fresh
// Success refresh (same kind, new detail) doesn't re-fade
// the body; only Loading ↔ Success ↔ Error animate.
Crossfade(targetState = state::class, label = "artist-detail") { _ ->
when (val s = state) {
is ArtistDetailUiState.Loading ->
if (s.seed != null) {
SeededArtistLoading(s.seed)
@@ -142,8 +142,13 @@ private fun ArtistsTab(
) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
PullToRefreshScaffold(onRefresh = { viewModel.refresh().join() }) {
Crossfade(targetState = state, label = "library-artists") { s ->
when (s) {
// Crossfade is keyed on `state::class` (not the whole state)
// so a refresh that emits a fresh UiState.Success — same kind,
// new data — doesn't re-fade the entire grid. Only Loading ↔
// Success ↔ Error ↔ Empty transitions animate; row-level diff
// is owned by LazyVerticalGrid via its item keys.
Crossfade(targetState = state::class, label = "library-artists") { _ ->
when (val s = state) {
UiState.Loading -> SkeletonArtistsGrid()
UiState.Empty -> EmptyState(
title = "No artists yet",
@@ -170,8 +175,8 @@ private fun AlbumsTab(
) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
PullToRefreshScaffold(onRefresh = { viewModel.refresh().join() }) {
Crossfade(targetState = state, label = "library-albums") { s ->
when (s) {
Crossfade(targetState = state::class, label = "library-albums") { _ ->
when (val s = state) {
UiState.Loading -> SkeletonAlbumsGrid()
UiState.Empty -> EmptyState(
title = "No albums yet",
@@ -288,7 +288,10 @@ private fun PlaylistDetailContent(
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel,
navController: NavHostController,
) {
Crossfade(targetState = state, label = "playlist-detail") { s -> when (s) {
// Crossfade keyed on `state::class` so refreshes that emit a
// fresh Success (same kind, new detail) don't re-fade the body;
// only Loading ↔ Success ↔ Error transitions animate.
Crossfade(targetState = state::class, label = "playlist-detail") { _ -> when (val s = state) {
is PlaylistDetailUiState.Loading ->
s.seed?.let { SeededPlaylistLoading(it) } ?: SkeletonPlaylistTrackList()
is PlaylistDetailUiState.Error -> ErrorBlock(s.message, viewModel::refresh)