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") @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?>> 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( @androidx.room.Database(

View File

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