diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumOverview.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumOverview.kt index 75a430d..ce7f5e8 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumOverview.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/album/AlbumOverview.kt @@ -276,7 +276,7 @@ fun AlbumOverview( ) { BasicText( text = "An error has occurred.", - style = typography.s.medium.secondary.center, + style = typography.s.secondary.center, modifier = Modifier .align(Alignment.Center) ) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeScreen.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeScreen.kt index 2be6d05..6a42c07 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeScreen.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeScreen.kt @@ -17,6 +17,7 @@ import it.vfsfitvnm.vimusic.ui.screens.builtinplaylist.BuiltInPlaylistScreen import it.vfsfitvnm.vimusic.ui.screens.globalRoutes import it.vfsfitvnm.vimusic.ui.screens.localPlaylistRoute import it.vfsfitvnm.vimusic.ui.screens.localplaylist.LocalPlaylistScreen +import it.vfsfitvnm.vimusic.ui.screens.playlistRoute import it.vfsfitvnm.vimusic.ui.screens.search.SearchScreen import it.vfsfitvnm.vimusic.ui.screens.searchResultRoute import it.vfsfitvnm.vimusic.ui.screens.searchRoute @@ -77,7 +78,7 @@ fun HomeScreen(onPlaylistUrl: (String) -> Unit) { host { val (tabIndex, onTabChanged) = rememberPreference( homeScreenTabIndexKey, - defaultValue = 0 + defaultValue = 1 ) Scaffold( @@ -99,6 +100,8 @@ fun HomeScreen(onPlaylistUrl: (String) -> Unit) { when (currentTabIndex) { 0 -> QuickPicks( onAlbumClick = { albumRoute(it) }, + onArtistClick = { artistRoute(it) }, + onPlaylistClick = { playlistRoute(it) }, ) 1 -> HomeSongList() 2 -> HomePlaylistList( diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/QuickPicks.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/QuickPicks.kt index 6321a37..d79a07b 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/QuickPicks.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/QuickPicks.kt @@ -1,24 +1,19 @@ package it.vfsfitvnm.vimusic.ui.screens.home import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.animation.animateContentSize 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.BoxWithConstraints import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.BasicText import androidx.compose.foundation.verticalScroll @@ -28,12 +23,13 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage +import com.valentinilk.shimmer.shimmer import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder @@ -43,19 +39,27 @@ import it.vfsfitvnm.vimusic.savers.nullableSaver import it.vfsfitvnm.vimusic.savers.resultSaver import it.vfsfitvnm.vimusic.ui.components.themed.Header import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu -import it.vfsfitvnm.vimusic.ui.screens.albumRoute +import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder import it.vfsfitvnm.vimusic.ui.styling.Dimensions import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance import it.vfsfitvnm.vimusic.ui.styling.px import it.vfsfitvnm.vimusic.ui.views.AlbumItem +import it.vfsfitvnm.vimusic.ui.views.AlbumItemShimmer +import it.vfsfitvnm.vimusic.ui.views.ArtistItem +import it.vfsfitvnm.vimusic.ui.views.ArtistItemShimmer +import it.vfsfitvnm.vimusic.ui.views.PlaylistItem +import it.vfsfitvnm.vimusic.ui.views.PlaylistItemShimmer import it.vfsfitvnm.vimusic.ui.views.SmallSongItem +import it.vfsfitvnm.vimusic.ui.views.SmallSongItemShimmer import it.vfsfitvnm.vimusic.ui.views.SongItem import it.vfsfitvnm.vimusic.utils.asMediaItem +import it.vfsfitvnm.vimusic.utils.center import it.vfsfitvnm.vimusic.utils.forcePlay import it.vfsfitvnm.vimusic.utils.produceSaveableOneShotState import it.vfsfitvnm.vimusic.utils.produceSaveableState import it.vfsfitvnm.vimusic.utils.secondary import it.vfsfitvnm.vimusic.utils.semiBold +import it.vfsfitvnm.vimusic.utils.thumbnail import it.vfsfitvnm.youtubemusic.YouTube import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint import kotlinx.coroutines.Dispatchers @@ -66,9 +70,11 @@ import kotlinx.coroutines.flow.flowOn @ExperimentalAnimationApi @Composable fun QuickPicks( - onAlbumClick: (String) -> Unit + onAlbumClick: (String) -> Unit, + onArtistClick: (String) -> Unit, + onPlaylistClick: (String) -> Unit, ) { - val (colorPalette, typography, thumbnailShape) = LocalAppearance.current + val (colorPalette, typography) = LocalAppearance.current val binder = LocalPlayerServiceBinder.current val trending by produceSaveableState( @@ -87,7 +93,6 @@ fun QuickPicks( stateSaver = resultSaver(nullableSaver(YouTubeRelatedSaver)), trending?.id ) { - println("trendingVideoId: ${trending?.id}") trending?.id?.let { trendingVideoId -> value = YouTube.related(trendingVideoId)?.map { related -> related?.copy( @@ -110,71 +115,96 @@ fun QuickPicks( } } - val songThumbnailSizePx = Dimensions.thumbnails.song.px + val songThumbnailSizeDp = Dimensions.thumbnails.song + val songThumbnailSizePx = songThumbnailSizeDp.px val albumThumbnailSizeDp = 108.dp val albumThumbnailSizePx = albumThumbnailSizeDp.px -// val itemInHorizontalGridWidth = (LocalConfiguration.current.screenWidthDp.dp) * 0.8f + val artistThumbnailSizeDp = 64.dp + val artistThumbnailSizePx = artistThumbnailSizeDp.px + val playlistThumbnailSizeDp = 108.dp + val playlistThumbnailSizePx = playlistThumbnailSizeDp.px - LazyColumn( - contentPadding = LocalPlayerAwarePaddingValues.current, - modifier = Modifier - .background(colorPalette.background0) - .fillMaxSize() - ) { - item( - key = "header", - contentType = 0 + val sectionTextModifier = Modifier + .padding(horizontal = 16.dp) + .padding(top = 24.dp, bottom = 8.dp) + + BoxWithConstraints { + val itemInHorizontalGridWidth = maxWidth * 0.9f + + Column( + modifier = Modifier + .background(colorPalette.background0) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(LocalPlayerAwarePaddingValues.current) ) { Header(title = "Quick picks") - } - trending?.let { song -> - item(key = song.id) { - SongItem( - song = song, - thumbnailSizePx = songThumbnailSizePx, - onClick = { - val mediaItem = song.asMediaItem - binder?.stopRadio() - binder?.player?.forcePlay(mediaItem) - binder?.setupRadio( - NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId) - ) - }, - menuContent = { - NonQueuedMediaItemMenu(mediaItem = song.asMediaItem) + relatedResult?.getOrNull()?.let { related -> + LazyHorizontalGrid( + rows = GridCells.Fixed(4), + modifier = Modifier + .fillMaxWidth() + .height((songThumbnailSizeDp + Dimensions.itemsVerticalPadding * 2) * 4) + ) { + trending?.let { song -> + item(key = song.id) { + SongItem( + thumbnailModel = song.thumbnailUrl?.thumbnail(songThumbnailSizePx), + title = song.title, + authors = song.artistsText, + durationText = null, + menuContent = { NonQueuedMediaItemMenu(mediaItem = song.asMediaItem) }, + onClick = { + val mediaItem = song.asMediaItem + binder?.stopRadio() + binder?.player?.forcePlay(mediaItem) + binder?.setupRadio( + NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId) + ) + }, + modifier = Modifier + .width(itemInHorizontalGridWidth) + ) + } } - ) - } - } - relatedResult?.getOrNull()?.let { related -> - items( - items = related.songs?.take(6) ?: emptyList(), - key = YouTube.Item::key - ) { song -> - SmallSongItem( - song = song, - thumbnailSizePx = songThumbnailSizePx, - onClick = { - val mediaItem = song.asMediaItem - binder?.stopRadio() - binder?.player?.forcePlay(mediaItem) - binder?.setupRadio( - NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId) + items( + items = related.songs ?: emptyList(), + key = YouTube.Item.Song::key + ) { song -> + SmallSongItem( + song = song, + thumbnailSizePx = songThumbnailSizePx, + onClick = { + val mediaItem = song.asMediaItem + binder?.stopRadio() + binder?.player?.forcePlay(mediaItem) + binder?.setupRadio( + NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId) + ) + }, + modifier = Modifier + .width(itemInHorizontalGridWidth) ) - }, - ) - } + } + } - item( - key = "albums", - contentType = "LazyRow" - ) { - LazyRow { + BasicText( + text = "Related albums", + style = typography.m.semiBold, + modifier = sectionTextModifier + ) + + LazyHorizontalGrid( + rows = GridCells.Fixed(2), + modifier = Modifier + .fillMaxWidth() + .height((albumThumbnailSizeDp + Dimensions.itemsVerticalPadding * 2) * 2) + ) { items( items = related.albums ?: emptyList(), - key = YouTube.Item::key + key = YouTube.Item.Album::key ) { album -> AlbumItem( album = album, @@ -186,28 +216,117 @@ fun QuickPicks( interactionSource = remember { MutableInteractionSource() }, onClick = { onAlbumClick(album.key) } ) - .fillMaxWidth() + .width(itemInHorizontalGridWidth) ) } } - } - items( - items = related.songs?.drop(6) ?: emptyList(), - key = YouTube.Item::key - ) { song -> - SmallSongItem( - song = song, - thumbnailSizePx = songThumbnailSizePx, - onClick = { - val mediaItem = song.asMediaItem - binder?.stopRadio() - binder?.player?.forcePlay(mediaItem) - binder?.setupRadio( - NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId) - ) - }, + BasicText( + text = "Similar artists", + style = typography.m.semiBold, + modifier = sectionTextModifier ) + + LazyHorizontalGrid( + rows = GridCells.Fixed(1), + modifier = Modifier + .fillMaxWidth() + .height((artistThumbnailSizeDp + Dimensions.itemsVerticalPadding * 2)) + ) { + items( + items = related.artists ?: emptyList(), + key = YouTube.Item.Artist::key, + ) { artist -> + ArtistItem( + artist = artist, + thumbnailSizePx = artistThumbnailSizePx, + thumbnailSizeDp = artistThumbnailSizeDp, + modifier = Modifier + .clickable( + indication = rememberRipple(bounded = true), + interactionSource = remember { MutableInteractionSource() }, + onClick = { onArtistClick(artist.key) } + ) + .width(itemInHorizontalGridWidth) + ) + } + } + + BasicText( + text = "Playlists you might like", + style = typography.m.semiBold, + modifier = Modifier + .padding(horizontal = 16.dp) + .padding(top = 24.dp, bottom = 8.dp) + ) + + LazyHorizontalGrid( + rows = GridCells.Fixed(2), + modifier = Modifier + .fillMaxWidth() + .height((playlistThumbnailSizeDp + Dimensions.itemsVerticalPadding * 2) * 2) + ) { + items( + items = related.playlists ?: emptyList(), + key = YouTube.Item.Playlist::key, + ) { playlist -> + PlaylistItem( + playlist = playlist, + thumbnailSizePx = playlistThumbnailSizePx, + thumbnailSizeDp = playlistThumbnailSizeDp, + modifier = Modifier + .clickable( + indication = rememberRipple(bounded = true), + interactionSource = remember { MutableInteractionSource() }, + onClick = { onPlaylistClick(playlist.key) } + ) + .width(itemInHorizontalGridWidth) + ) + } + } + } ?: relatedResult?.exceptionOrNull()?.let { + BasicText( + text = "An error has occurred", + style = typography.s.secondary.center, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(all = 16.dp) + ) + } ?: Column( + modifier = Modifier + .shimmer() + .graphicsLayer(alpha = 0.99f) + .drawWithContent { + drawContent() + drawRect( + brush = Brush.verticalGradient( + listOf(Color.Black, Color.Transparent) + ), + blendMode = BlendMode.DstIn + ) + } + ) { + repeat(4) { + SmallSongItemShimmer( + thumbnailSizeDp = songThumbnailSizeDp, + ) + } + + TextPlaceholder(modifier = sectionTextModifier) + + repeat(2) { + AlbumItemShimmer(thumbnailSizeDp = albumThumbnailSizeDp) + } + + TextPlaceholder(modifier = sectionTextModifier) + + ArtistItemShimmer(thumbnailSizeDp = artistThumbnailSizeDp) + + TextPlaceholder(modifier = sectionTextModifier) + + repeat(2) { + PlaylistItemShimmer(thumbnailSizeDp = playlistThumbnailSizeDp) + } } } } diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/playlist/PlaylistSongList.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/playlist/PlaylistSongList.kt index ca88584..2ecc49b 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/playlist/PlaylistSongList.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/playlist/PlaylistSongList.kt @@ -242,7 +242,7 @@ fun PlaylistSongList( ) { BasicText( text = "An error has occurred.\nTap to retry", - style = typography.s.medium.secondary.center, + style = typography.s.secondary.center, modifier = Modifier .align(Alignment.Center) ) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/OnlineSearch.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/OnlineSearch.kt index d2e21e3..fe41c28 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/OnlineSearch.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/search/OnlineSearch.kt @@ -287,7 +287,7 @@ fun OnlineSearch( ) { BasicText( text = "An error has occurred.", - style = typography.s.medium.secondary.center, + style = typography.s.secondary.center, modifier = Modifier .align(Alignment.Center) ) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/RingBuffer.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/RingBuffer.kt index 64be0c3..6d403fc 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/RingBuffer.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/utils/RingBuffer.kt @@ -1,7 +1,7 @@ package it.vfsfitvnm.vimusic.utils class RingBuffer(val size: Int, init: (index: Int) -> T) { - private val list = MutableList(2, init) + private val list = MutableList(size, init) private var index = 0