Add quick picks tab (#172)

This commit is contained in:
vfsfitvnm 2022-09-29 16:40:30 +02:00
parent d07af511eb
commit 6f222ac564
6 changed files with 212 additions and 90 deletions

View file

@ -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)
)

View file

@ -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(

View file

@ -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,29 +115,46 @@ 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,
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()
) {
item(
key = "header",
contentType = 0
.verticalScroll(rememberScrollState())
.padding(LocalPlayerAwarePaddingValues.current)
) {
Header(title = "Quick picks")
}
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(
song = song,
thumbnailSizePx = songThumbnailSizePx,
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()
@ -141,17 +163,15 @@ fun QuickPicks(
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
)
},
menuContent = {
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
}
modifier = Modifier
.width(itemInHorizontalGridWidth)
)
}
}
relatedResult?.getOrNull()?.let { related ->
items(
items = related.songs?.take(6) ?: emptyList(),
key = YouTube.Item::key
items = related.songs ?: emptyList(),
key = YouTube.Item.Song::key
) { song ->
SmallSongItem(
song = song,
@ -164,17 +184,27 @@ fun QuickPicks(
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
)
},
modifier = Modifier
.width(itemInHorizontalGridWidth)
)
}
}
item(
key = "albums",
contentType = "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)
) {
LazyRow {
items(
items = related.albums ?: emptyList(),
key = YouTube.Item::key
key = YouTube.Item.Album::key
) { album ->
AlbumItem(
album = album,
@ -186,29 +216,118 @@ fun QuickPicks(
interactionSource = remember { MutableInteractionSource() },
onClick = { onAlbumClick(album.key) }
)
.fillMaxWidth()
.width(itemInHorizontalGridWidth)
)
}
}
}
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.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)
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)
}
}
}
}
}

View file

@ -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)
)

View file

@ -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)
)

View file

@ -1,7 +1,7 @@
package it.vfsfitvnm.vimusic.utils
class RingBuffer<T>(val size: Int, init: (index: Int) -> T) {
private val list = MutableList(2, init)
private val list = MutableList(size, init)
private var index = 0