Show local tracks in ArtistScreen
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user