feat(android): skeleton bodies on Library + detail screens (audit v2 #16, slice 2)
Wraps each cold-load branch in a Crossfade and replaces the centered spinner with skeleton tile grids/lists that match the real layout's geometry so the reveal doesn't reflow: * LibraryScreen Artists tab → SkeletonArtistsGrid (12 SkeletonArtistTile in adaptive 144dp grid). * LibraryScreen Albums tab → SkeletonAlbumsGrid (12 SkeletonAlbumTile in adaptive 176dp grid). * AlbumDetail → SkeletonTrackList (8 SkeletonTrackRow). * ArtistDetail → SkeletonArtistAlbumsGrid (9 SkeletonAlbumTile, 176dp adaptive grid mirroring ArtistBody's albums grid). * PlaylistDetail → SkeletonPlaylistTrackList (8 SkeletonTrackRow). Drops now-unused LoadingCentered helpers from Library / AlbumDetail / ArtistDetail / PlaylistDetail per the no-dead-code rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+37
-26
@@ -13,13 +13,13 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -57,6 +57,7 @@ import com.fabledsword.minstrel.nav.ArtistDetail
|
|||||||
import com.fabledsword.minstrel.shared.widgets.EmptyState
|
import com.fabledsword.minstrel.shared.widgets.EmptyState
|
||||||
import com.fabledsword.minstrel.shared.widgets.LikeButton
|
import com.fabledsword.minstrel.shared.widgets.LikeButton
|
||||||
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
|
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
|
||||||
|
import com.fabledsword.minstrel.shared.widgets.SkeletonTrackRow
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
|
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
|
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
|
||||||
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
|
import com.fabledsword.minstrel.theme.FabledSwordFlatTokens
|
||||||
@@ -94,29 +95,37 @@ fun AlbumDetailScreen(
|
|||||||
onRefresh = { viewModel.refresh().join() },
|
onRefresh = { viewModel.refresh().join() },
|
||||||
modifier = Modifier.fillMaxSize().padding(inner),
|
modifier = Modifier.fillMaxSize().padding(inner),
|
||||||
) {
|
) {
|
||||||
when (val s = state) {
|
Crossfade(targetState = state, label = "album-detail") { s ->
|
||||||
is AlbumDetailUiState.Loading -> LoadingCentered()
|
when (s) {
|
||||||
is AlbumDetailUiState.Error -> EmptyState(
|
is AlbumDetailUiState.Loading -> SkeletonTrackList()
|
||||||
title = "Couldn't load album",
|
is AlbumDetailUiState.Error -> EmptyState(
|
||||||
body = s.message,
|
title = "Couldn't load album",
|
||||||
)
|
body = s.message,
|
||||||
is AlbumDetailUiState.Success -> {
|
|
||||||
val albumLiked by viewModel.albumLiked.collectAsStateWithLifecycle()
|
|
||||||
val likedTrackIds by viewModel.likedTrackIds.collectAsStateWithLifecycle()
|
|
||||||
val playerState by playerViewModel.uiState.collectAsStateWithLifecycle()
|
|
||||||
AlbumBody(
|
|
||||||
detail = s.detail,
|
|
||||||
albumLiked = albumLiked,
|
|
||||||
likedTrackIds = likedTrackIds,
|
|
||||||
playingTrackId = playerState.currentTrack?.id,
|
|
||||||
onPlayAll = { viewModel.play(startTrackId = null) },
|
|
||||||
onShuffleAll = viewModel::shuffle,
|
|
||||||
onTrackClick = { id -> viewModel.play(startTrackId = id) },
|
|
||||||
onToggleAlbumLike = viewModel::toggleLikeAlbum,
|
|
||||||
onToggleTrackLike = viewModel::toggleLikeTrack,
|
|
||||||
onNavigateToAlbum = { id -> navController.navigate(AlbumDetail(id)) },
|
|
||||||
onNavigateToArtist = { id -> navController.navigate(ArtistDetail(id)) },
|
|
||||||
)
|
)
|
||||||
|
is AlbumDetailUiState.Success -> {
|
||||||
|
val albumLiked by viewModel.albumLiked.collectAsStateWithLifecycle()
|
||||||
|
val likedTrackIds by viewModel.likedTrackIds
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
val playerState by playerViewModel.uiState
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
AlbumBody(
|
||||||
|
detail = s.detail,
|
||||||
|
albumLiked = albumLiked,
|
||||||
|
likedTrackIds = likedTrackIds,
|
||||||
|
playingTrackId = playerState.currentTrack?.id,
|
||||||
|
onPlayAll = { viewModel.play(startTrackId = null) },
|
||||||
|
onShuffleAll = viewModel::shuffle,
|
||||||
|
onTrackClick = { id -> viewModel.play(startTrackId = id) },
|
||||||
|
onToggleAlbumLike = viewModel::toggleLikeAlbum,
|
||||||
|
onToggleTrackLike = viewModel::toggleLikeTrack,
|
||||||
|
onNavigateToAlbum = { id ->
|
||||||
|
navController.navigate(AlbumDetail(id))
|
||||||
|
},
|
||||||
|
onNavigateToArtist = { id ->
|
||||||
|
navController.navigate(ArtistDetail(id))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,10 +346,12 @@ private fun TrackRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val SKELETON_TRACK_ROWS = 8
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoadingCentered() {
|
private fun SkeletonTrackList() {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
items(SKELETON_TRACK_ROWS) { SkeletonTrackRow() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+30
-18
@@ -1,5 +1,6 @@
|
|||||||
package com.fabledsword.minstrel.library.ui
|
package com.fabledsword.minstrel.library.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -19,7 +20,6 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -50,6 +50,7 @@ import com.fabledsword.minstrel.nav.AlbumDetail
|
|||||||
import com.fabledsword.minstrel.shared.widgets.EmptyState
|
import com.fabledsword.minstrel.shared.widgets.EmptyState
|
||||||
import com.fabledsword.minstrel.shared.widgets.LikeButton
|
import com.fabledsword.minstrel.shared.widgets.LikeButton
|
||||||
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
|
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
|
||||||
|
import com.fabledsword.minstrel.shared.widgets.SkeletonAlbumTile
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -75,21 +76,24 @@ fun ArtistDetailScreen(
|
|||||||
onRefresh = { viewModel.refresh().join() },
|
onRefresh = { viewModel.refresh().join() },
|
||||||
modifier = Modifier.fillMaxSize().padding(inner),
|
modifier = Modifier.fillMaxSize().padding(inner),
|
||||||
) {
|
) {
|
||||||
when (val s = state) {
|
Crossfade(targetState = state, label = "artist-detail") { s ->
|
||||||
is ArtistDetailUiState.Loading -> LoadingCentered()
|
when (s) {
|
||||||
is ArtistDetailUiState.Error -> EmptyState(
|
is ArtistDetailUiState.Loading -> SkeletonArtistAlbumsGrid()
|
||||||
title = "Couldn't load artist",
|
is ArtistDetailUiState.Error -> EmptyState(
|
||||||
body = s.message,
|
title = "Couldn't load artist",
|
||||||
)
|
body = s.message,
|
||||||
is ArtistDetailUiState.Success -> {
|
|
||||||
val artistLiked by viewModel.artistLiked.collectAsStateWithLifecycle()
|
|
||||||
ArtistBody(
|
|
||||||
detail = s.detail,
|
|
||||||
artistLiked = artistLiked,
|
|
||||||
onPlay = viewModel::playArtist,
|
|
||||||
onToggleLike = viewModel::toggleLikeArtist,
|
|
||||||
onAlbumClick = { id -> navController.navigate(AlbumDetail(id)) },
|
|
||||||
)
|
)
|
||||||
|
is ArtistDetailUiState.Success -> {
|
||||||
|
val artistLiked by viewModel.artistLiked
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
ArtistBody(
|
||||||
|
detail = s.detail,
|
||||||
|
artistLiked = artistLiked,
|
||||||
|
onPlay = viewModel::playArtist,
|
||||||
|
onToggleLike = viewModel::toggleLikeArtist,
|
||||||
|
onAlbumClick = { id -> navController.navigate(AlbumDetail(id)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,9 +233,17 @@ private fun EmptyAlbumsHint() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val SKELETON_ARTIST_GRID_TILES = 9
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoadingCentered() {
|
private fun SkeletonArtistAlbumsGrid() {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
LazyVerticalGrid(
|
||||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
columns = GridCells.Adaptive(minSize = 176.dp),
|
||||||
|
contentPadding = PaddingValues(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
items(SKELETON_ARTIST_GRID_TILES) { SkeletonAlbumTile() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
@file:Suppress("TooManyFunctions") // Compose screen + tab helpers + skeleton helpers
|
||||||
|
|
||||||
package com.fabledsword.minstrel.library.ui
|
package com.fabledsword.minstrel.library.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -9,7 +12,6 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -27,7 +29,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
@@ -49,6 +50,8 @@ import com.fabledsword.minstrel.shared.widgets.EmptyState
|
|||||||
import com.fabledsword.minstrel.shared.widgets.ErrorRetry
|
import com.fabledsword.minstrel.shared.widgets.ErrorRetry
|
||||||
import com.fabledsword.minstrel.shared.widgets.MainAppBarActions
|
import com.fabledsword.minstrel.shared.widgets.MainAppBarActions
|
||||||
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
|
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
|
||||||
|
import com.fabledsword.minstrel.shared.widgets.SkeletonAlbumTile
|
||||||
|
import com.fabledsword.minstrel.shared.widgets.SkeletonArtistTile
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
|
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,17 +149,20 @@ private fun ArtistsTab(
|
|||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
PullToRefreshScaffold(onRefresh = { viewModel.refresh().join() }) {
|
PullToRefreshScaffold(onRefresh = { viewModel.refresh().join() }) {
|
||||||
when (val s = state) {
|
Crossfade(targetState = state, label = "library-artists") { s ->
|
||||||
LibraryUiState.Loading -> LoadingCentered()
|
when (s) {
|
||||||
LibraryUiState.Empty -> EmptyState(
|
LibraryUiState.Loading -> SkeletonArtistsGrid()
|
||||||
title = "No artists yet",
|
LibraryUiState.Empty -> EmptyState(
|
||||||
body = "Scan a folder in your server settings to populate the library.",
|
title = "No artists yet",
|
||||||
)
|
body = "Scan a folder in your server settings to " +
|
||||||
is LibraryUiState.Error -> ErrorRetry(message = s.message, onRetry = {})
|
"populate the library.",
|
||||||
is LibraryUiState.Success -> ArtistsGrid(
|
)
|
||||||
artists = s.artists,
|
is LibraryUiState.Error -> ErrorRetry(message = s.message, onRetry = {})
|
||||||
onArtistClick = { id -> navController.navigate(ArtistDetail(id)) },
|
is LibraryUiState.Success -> ArtistsGrid(
|
||||||
)
|
artists = s.artists,
|
||||||
|
onArtistClick = { id -> navController.navigate(ArtistDetail(id)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,17 +174,20 @@ private fun AlbumsTab(
|
|||||||
) {
|
) {
|
||||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
PullToRefreshScaffold(onRefresh = { viewModel.refresh().join() }) {
|
PullToRefreshScaffold(onRefresh = { viewModel.refresh().join() }) {
|
||||||
when (val s = state) {
|
Crossfade(targetState = state, label = "library-albums") { s ->
|
||||||
LibraryUiState.Loading -> LoadingCentered()
|
when (s) {
|
||||||
LibraryUiState.Empty -> EmptyState(
|
LibraryUiState.Loading -> SkeletonAlbumsGrid()
|
||||||
title = "No albums yet",
|
LibraryUiState.Empty -> EmptyState(
|
||||||
body = "Scan a folder in your server settings to populate the library.",
|
title = "No albums yet",
|
||||||
)
|
body = "Scan a folder in your server settings to " +
|
||||||
is LibraryUiState.Error -> ErrorRetry(message = s.message, onRetry = {})
|
"populate the library.",
|
||||||
is LibraryUiState.Success -> AlbumsGrid(
|
)
|
||||||
albums = s.albums,
|
is LibraryUiState.Error -> ErrorRetry(message = s.message, onRetry = {})
|
||||||
onAlbumClick = { id -> navController.navigate(AlbumDetail(id)) },
|
is LibraryUiState.Success -> AlbumsGrid(
|
||||||
)
|
albums = s.albums,
|
||||||
|
onAlbumClick = { id -> navController.navigate(AlbumDetail(id)) },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,9 +229,30 @@ private fun AlbumsGrid(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val SKELETON_GRID_TILES = 12
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoadingCentered() {
|
private fun SkeletonArtistsGrid() {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
LazyVerticalGrid(
|
||||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
columns = GridCells.Adaptive(minSize = 144.dp),
|
||||||
|
contentPadding = PaddingValues(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
items(SKELETON_GRID_TILES) { SkeletonArtistTile() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SkeletonAlbumsGrid() {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
columns = GridCells.Adaptive(minSize = 176.dp),
|
||||||
|
contentPadding = PaddingValues(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
items(SKELETON_GRID_TILES) { SkeletonAlbumTile() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-12
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
package com.fabledsword.minstrel.playlists.ui
|
package com.fabledsword.minstrel.playlists.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -21,7 +22,6 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -73,6 +73,7 @@ import com.fabledsword.minstrel.playlists.data.PlaylistDetailRef
|
|||||||
import com.fabledsword.minstrel.playlists.data.PlaylistsRepository
|
import com.fabledsword.minstrel.playlists.data.PlaylistsRepository
|
||||||
import com.fabledsword.minstrel.shared.widgets.LikeButton
|
import com.fabledsword.minstrel.shared.widgets.LikeButton
|
||||||
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
|
import com.fabledsword.minstrel.shared.widgets.PullToRefreshScaffold
|
||||||
|
import com.fabledsword.minstrel.shared.widgets.SkeletonTrackRow
|
||||||
import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb
|
import com.fabledsword.minstrel.shared.widgets.TrackCoverThumb
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
|
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsButton
|
||||||
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
|
import com.fabledsword.minstrel.shared.widgets.trackactions.TrackActionsViewModel
|
||||||
@@ -296,30 +297,30 @@ private fun PlaylistDetailContent(
|
|||||||
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel,
|
playerViewModel: com.fabledsword.minstrel.player.ui.PlayerViewModel,
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
) {
|
) {
|
||||||
when (state) {
|
Crossfade(targetState = state, label = "playlist-detail") { s -> when (s) {
|
||||||
is PlaylistDetailUiState.Loading -> LoadingCentered()
|
is PlaylistDetailUiState.Loading -> SkeletonPlaylistTrackList()
|
||||||
is PlaylistDetailUiState.Error -> ErrorBlock(state.message, viewModel::refresh)
|
is PlaylistDetailUiState.Error -> ErrorBlock(s.message, viewModel::refresh)
|
||||||
is PlaylistDetailUiState.Success -> {
|
is PlaylistDetailUiState.Success -> {
|
||||||
val likedTrackIds by viewModel.likedTrackIds.collectAsState()
|
val likedTrackIds by viewModel.likedTrackIds.collectAsState()
|
||||||
val playerState by playerViewModel.uiState.collectAsState()
|
val playerState by playerViewModel.uiState.collectAsState()
|
||||||
PlaylistDetailBody(
|
PlaylistDetailBody(
|
||||||
detail = state.detail,
|
detail = s.detail,
|
||||||
likedTrackIds = likedTrackIds,
|
likedTrackIds = likedTrackIds,
|
||||||
playingTrackId = playerState.currentTrack?.id,
|
playingTrackId = playerState.currentTrack?.id,
|
||||||
onPlayAll = { viewModel.play(state.detail.tracks, startTrackId = null) },
|
onPlayAll = { viewModel.play(s.detail.tracks, startTrackId = null) },
|
||||||
onShuffleAll = {
|
onShuffleAll = {
|
||||||
viewModel.play(state.detail.tracks.shuffled(), startTrackId = null)
|
viewModel.play(s.detail.tracks.shuffled(), startTrackId = null)
|
||||||
},
|
},
|
||||||
onRegenerate = viewModel::regenerate,
|
onRegenerate = viewModel::regenerate,
|
||||||
onTrackClick = { id ->
|
onTrackClick = { id ->
|
||||||
viewModel.play(state.detail.tracks, startTrackId = id)
|
viewModel.play(s.detail.tracks, startTrackId = id)
|
||||||
},
|
},
|
||||||
onToggleTrackLike = viewModel::toggleLikeTrack,
|
onToggleTrackLike = viewModel::toggleLikeTrack,
|
||||||
onNavigateToAlbum = { id -> navController.navigate(AlbumDetail(id)) },
|
onNavigateToAlbum = { id -> navController.navigate(AlbumDetail(id)) },
|
||||||
onNavigateToArtist = { id -> navController.navigate(ArtistDetail(id)) },
|
onNavigateToArtist = { id -> navController.navigate(ArtistDetail(id)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
} }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun currentTitle(state: PlaylistDetailUiState): String = when (state) {
|
private fun currentTitle(state: PlaylistDetailUiState): String = when (state) {
|
||||||
@@ -543,9 +544,12 @@ private fun TrackRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoadingCentered() {
|
private const val SKELETON_PLAYLIST_ROWS = 8
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
||||||
CircularProgressIndicator(color = MaterialTheme.colorScheme.primary)
|
@Composable
|
||||||
|
private fun SkeletonPlaylistTrackList() {
|
||||||
|
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||||
|
items(SKELETON_PLAYLIST_ROWS) { SkeletonTrackRow() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user