Add quick picks tab (#172)
This commit is contained in:
parent
d07af511eb
commit
6f222ac564
6 changed files with 212 additions and 90 deletions
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue