Show local tracks in ArtistScreen

This commit is contained in:
vfsfitvnm 2022-06-18 16:53:44 +02:00
parent 4da4e4faff
commit 7219f9bd39
3 changed files with 185 additions and 81 deletions

View file

@ -110,6 +110,11 @@ interface Database {
@Query("SELECT thumbnailUrl FROM Song JOIN SongInPlaylist ON id = songId WHERE playlistId = :id ORDER BY position LIMIT 4")
fun playlistThumbnailUrls(id: Long): Flow<List<String?>>
@Transaction
@RewriteQueriesToDropUnusedColumns
@Query("SELECT * FROM Info JOIN SongWithAuthors ON Info.id = SongWithAuthors.authorInfoId JOIN Song ON SongWithAuthors.songId = Song.id WHERE browseId = :artistId ORDER BY Song.ROWID DESC")
fun artistSongs(artistId: String): Flow<List<SongWithInfo>>
}
@androidx.room.Database(

View file

@ -1,9 +1,14 @@
package it.vfsfitvnm.vimusic.ui.screens
import android.os.Bundle
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.*
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.runtime.*
@ -17,18 +22,22 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import coil.compose.AsyncImage
import com.valentinilk.shimmer.shimmer
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.models.SongWithInfo
import it.vfsfitvnm.vimusic.services.StartArtistRadioCommand
import it.vfsfitvnm.vimusic.ui.components.ExpandableText
import it.vfsfitvnm.vimusic.ui.components.Message
import it.vfsfitvnm.vimusic.services.StopRadioCommand
import it.vfsfitvnm.vimusic.ui.components.OutcomeItem
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
import it.vfsfitvnm.vimusic.ui.styling.LocalColorPalette
import it.vfsfitvnm.vimusic.ui.styling.LocalTypography
import it.vfsfitvnm.vimusic.ui.views.SongItem
import it.vfsfitvnm.vimusic.utils.*
import it.vfsfitvnm.youtubemusic.Outcome
import it.vfsfitvnm.youtubemusic.YouTube
@ -41,7 +50,7 @@ import kotlinx.coroutines.withContext
fun ArtistScreen(
browseId: String,
) {
val scrollState = rememberScrollState()
val lazyListState = rememberLazyListState()
var artist by remember {
mutableStateOf<Outcome<YouTube.Artist>>(Outcome.Loading)
@ -81,107 +90,196 @@ fun ArtistScreen(
}
}
Column(
val songThumbnailSizePx = remember {
density.run {
54.dp.roundToPx()
}
}
val songs by remember(browseId) {
Database.artistSongs(browseId)
}.collectAsState(initial = emptyList(), context = Dispatchers.IO)
LazyColumn(
state = lazyListState,
contentPadding = PaddingValues(bottom = 72.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.background(colorPalette.background)
.fillMaxSize()
.verticalScroll(scrollState)
.padding(bottom = 72.dp)
) {
TopAppBar(
modifier = Modifier
.height(52.dp)
) {
Image(
painter = painterResource(R.drawable.chevron_back),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
item {
TopAppBar(
modifier = Modifier
.clickable(onClick = pop)
.padding(vertical = 8.dp)
.padding(horizontal = 16.dp)
.size(24.dp)
)
.height(52.dp)
) {
Image(
painter = painterResource(R.drawable.chevron_back),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable(onClick = pop)
.padding(vertical = 8.dp)
.padding(horizontal = 16.dp)
.size(24.dp)
)
}
}
OutcomeItem(
outcome = artist,
onRetry = onLoad,
onLoading = {
Loading()
item {
OutcomeItem(
outcome = artist,
onRetry = onLoad,
onLoading = {
Loading()
}
) { artist ->
AsyncImage(
model = artist.thumbnail?.size(thumbnailSizePx),
contentDescription = null,
modifier = Modifier
.clip(CircleShape)
.size(thumbnailSizeDp)
)
BasicText(
text = artist.name,
style = typography.l.semiBold,
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Image(
painter = painterResource(R.drawable.shuffle),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.mediaController?.sendCustomCommand(
StartArtistRadioCommand,
artist.shuffleEndpoint.asBundle
)
}
.shadow(elevation = 2.dp, shape = CircleShape)
.background(
color = colorPalette.elevatedBackground,
shape = CircleShape
)
.padding(horizontal = 16.dp, vertical = 16.dp)
.size(20.dp)
)
Image(
painter = painterResource(R.drawable.radio),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.mediaController?.sendCustomCommand(
StartArtistRadioCommand,
artist.radioEndpoint.asBundle
)
}
.shadow(elevation = 2.dp, shape = CircleShape)
.background(
color = colorPalette.elevatedBackground,
shape = CircleShape
)
.padding(horizontal = 16.dp, vertical = 16.dp)
.size(20.dp)
)
}
}
) { artist ->
AsyncImage(
model = artist.thumbnail?.size(thumbnailSizePx),
contentDescription = null,
modifier = Modifier
.clip(CircleShape)
.size(thumbnailSizeDp)
}
)
BasicText(
text = artist.name,
style = typography.l.semiBold,
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.dp)
)
item {
if (songs.isEmpty()) return@item
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.zIndex(1f)
.fillMaxWidth()
.padding(horizontal = 8.dp)
.padding(top = 32.dp)
) {
BasicText(
text = "Local tracks",
style = typography.m.semiBold,
modifier = Modifier
.padding(horizontal = 8.dp)
)
Image(
painter = painterResource(R.drawable.shuffle),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.mediaController?.sendCustomCommand(StartArtistRadioCommand, artist.shuffleEndpoint.asBundle)
.clickable(enabled = songs.isNotEmpty()) {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
it.forcePlayFromBeginning(
songs
.shuffled()
.map(SongWithInfo::asMediaItem)
)
}
}
.shadow(elevation = 2.dp, shape = CircleShape)
.background(color = colorPalette.elevatedBackground, shape = CircleShape)
.padding(horizontal = 16.dp, vertical = 16.dp)
.size(20.dp)
)
Image(
painter = painterResource(R.drawable.radio),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable {
player?.mediaController?.sendCustomCommand(StartArtistRadioCommand, artist.radioEndpoint.asBundle)
}
.shadow(elevation = 2.dp, shape = CircleShape)
.background(color = colorPalette.elevatedBackground, shape = CircleShape)
.padding(horizontal = 16.dp, vertical = 16.dp)
.padding(horizontal = 8.dp, vertical = 8.dp)
.size(20.dp)
)
}
}
artist.description?.let { description ->
ExpandableText(
text = description,
style = typography.xxs.secondary.align(TextAlign.Justify),
minimizedMaxLines = 4,
backgroundColor = colorPalette.background,
showMoreTextStyle = typography.xxs.bold,
modifier = Modifier
.animateContentSize()
.padding(horizontal = 16.dp)
)
}
Message(
text = "Page under construction",
icon = R.drawable.sad,
modifier = Modifier
.padding(vertical = 64.dp)
itemsIndexed(
items = songs,
key = { _, song -> song.song.id },
contentType = { _, song -> song },
) { index, song ->
SongItem(
song = song,
thumbnailSize = songThumbnailSizePx,
onClick = {
player?.mediaController?.let {
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
it.forcePlayAtIndex(songs.map(SongWithInfo::asMediaItem), index)
}
},
menuContent = {
InHistoryMediaItemMenu(song = song)
}
)
}
artist.valueOrNull?.description?.let { description ->
item {
Column(
modifier = Modifier
.padding(top = 32.dp)
.padding(horizontal = 16.dp, vertical = 16.dp)
.background(colorPalette.lightBackground)
.padding(horizontal = 16.dp, vertical = 16.dp)
) {
BasicText(
text = "Information",
style = typography.xxs.semiBold
)
BasicText(
text = description,
style = typography.xxs.secondary.align(TextAlign.Justify)
)
}
}
}
}
}
}

View file

@ -820,7 +820,8 @@ object YouTube {
.header
?.musicImmersiveHeaderRenderer
?.description
?.text,
?.text
?.substringBeforeLast("\n\nFrom Wikipedia"),
thumbnail = body
.header
?.musicImmersiveHeaderRenderer