Use LazyColumn in PlaylistOrAlbumScreen
This commit is contained in:
@@ -3,8 +3,13 @@ package it.vfsfitvnm.vimusic.ui.screens
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
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.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.*
|
||||||
@@ -51,7 +56,7 @@ import kotlinx.coroutines.withContext
|
|||||||
fun PlaylistOrAlbumScreen(
|
fun PlaylistOrAlbumScreen(
|
||||||
browseId: String,
|
browseId: String,
|
||||||
) {
|
) {
|
||||||
val scrollState = rememberScrollState()
|
val lazyListState = rememberLazyListState()
|
||||||
|
|
||||||
var playlistOrAlbum by remember {
|
var playlistOrAlbum by remember {
|
||||||
mutableStateOf<Outcome<YouTube.PlaylistOrAlbum>>(Outcome.Loading)
|
mutableStateOf<Outcome<YouTube.PlaylistOrAlbum>>(Outcome.Loading)
|
||||||
@@ -101,103 +106,104 @@ fun PlaylistOrAlbumScreen(
|
|||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
Column(
|
LazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
|
contentPadding = PaddingValues(bottom = 72.dp),
|
||||||
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)
|
||||||
|
)
|
||||||
|
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(R.drawable.ellipsis_horizontal),
|
painter = painterResource(R.drawable.ellipsis_horizontal),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
menuState.display {
|
menuState.display {
|
||||||
Menu {
|
Menu {
|
||||||
MenuCloseButton(onClick = menuState::hide)
|
MenuCloseButton(onClick = menuState::hide)
|
||||||
|
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.time,
|
icon = R.drawable.time,
|
||||||
text = "Enqueue",
|
text = "Enqueue",
|
||||||
enabled = player?.playbackState == Player.STATE_READY,
|
enabled = player?.playbackState == Player.STATE_READY,
|
||||||
onClick = {
|
onClick = {
|
||||||
menuState.hide()
|
menuState.hide()
|
||||||
playlistOrAlbum.valueOrNull?.let { album ->
|
playlistOrAlbum.valueOrNull?.let { album ->
|
||||||
album.items
|
album.items
|
||||||
?.mapNotNull { song ->
|
?.mapNotNull { song ->
|
||||||
song.toMediaItem(browseId, album)
|
song.toMediaItem(browseId, album)
|
||||||
}
|
}
|
||||||
?.let { mediaItems ->
|
?.let { mediaItems ->
|
||||||
player?.mediaController?.enqueue(
|
player?.mediaController?.enqueue(
|
||||||
mediaItems
|
mediaItems
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
|
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.list,
|
icon = R.drawable.list,
|
||||||
text = "Import as playlist",
|
text = "Import as playlist",
|
||||||
onClick = {
|
onClick = {
|
||||||
menuState.hide()
|
menuState.hide()
|
||||||
|
|
||||||
playlistOrAlbum.valueOrNull?.let { album ->
|
playlistOrAlbum.valueOrNull?.let { album ->
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
Database.internal.runInTransaction {
|
Database.internal.runInTransaction {
|
||||||
val playlistId =
|
val playlistId =
|
||||||
Database.insert(Playlist(name = album.title ?: "Unknown"))
|
Database.insert(Playlist(name = album.title ?: "Unknown"))
|
||||||
|
|
||||||
|
album.items?.forEachIndexed { index, song ->
|
||||||
|
song
|
||||||
|
.toMediaItem(browseId, album)
|
||||||
|
?.let { mediaItem ->
|
||||||
|
if (Database.song(mediaItem.mediaId) == null) {
|
||||||
|
Database.insert(
|
||||||
|
mediaItem
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
album.items?.forEachIndexed { index, song ->
|
|
||||||
song
|
|
||||||
.toMediaItem(browseId, album)
|
|
||||||
?.let { mediaItem ->
|
|
||||||
if (Database.song(mediaItem.mediaId) == null) {
|
|
||||||
Database.insert(
|
Database.insert(
|
||||||
mediaItem
|
SongInPlaylist(
|
||||||
|
songId = mediaItem.mediaId,
|
||||||
|
playlistId = playlistId,
|
||||||
|
position = index
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Database.insert(
|
|
||||||
SongInPlaylist(
|
|
||||||
songId = mediaItem.mediaId,
|
|
||||||
playlistId = playlistId,
|
|
||||||
position = index
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
|
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.share_social,
|
icon = R.drawable.share_social,
|
||||||
text = "Share",
|
text = "Share",
|
||||||
onClick = {
|
onClick = {
|
||||||
menuState.hide()
|
menuState.hide()
|
||||||
|
|
||||||
(playlistOrAlbum.valueOrNull?.url
|
(playlistOrAlbum.valueOrNull?.url
|
||||||
?: "https://music.youtube.com/playlist?list=${browseId.removePrefix("VL")}").let { url ->
|
?: "https://music.youtube.com/playlist?list=${browseId.removePrefix("VL")}").let { url ->
|
||||||
val sendIntent = Intent().apply {
|
val sendIntent = Intent().apply {
|
||||||
action = Intent.ACTION_SEND
|
action = Intent.ACTION_SEND
|
||||||
type = "text/plain"
|
type = "text/plain"
|
||||||
@@ -206,170 +212,176 @@ fun PlaylistOrAlbumScreen(
|
|||||||
|
|
||||||
context.startActivity(Intent.createChooser(sendIntent, null))
|
context.startActivity(Intent.createChooser(sendIntent, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
.size(24.dp)
|
||||||
.size(24.dp)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OutcomeItem(
|
item {
|
||||||
outcome = playlistOrAlbum,
|
OutcomeItem(
|
||||||
onRetry = onLoad,
|
outcome = playlistOrAlbum,
|
||||||
onLoading = {
|
onRetry = onLoad,
|
||||||
Loading()
|
onLoading = {
|
||||||
}
|
Loading()
|
||||||
) { playlistOrAlbum ->
|
}
|
||||||
Row(
|
) { playlistOrAlbum ->
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
Row(
|
||||||
modifier = Modifier
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
.fillMaxWidth()
|
|
||||||
.height(IntrinsicSize.Max)
|
|
||||||
.padding(vertical = 8.dp, horizontal = 16.dp)
|
|
||||||
.padding(bottom = 16.dp)
|
|
||||||
) {
|
|
||||||
AsyncImage(
|
|
||||||
model = playlistOrAlbum.thumbnail?.size(thumbnailSizePx),
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(ThumbnailRoundness.shape)
|
.fillMaxWidth()
|
||||||
.size(thumbnailSizeDp)
|
.height(IntrinsicSize.Max)
|
||||||
)
|
.padding(vertical = 8.dp, horizontal = 16.dp)
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.SpaceEvenly,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
) {
|
||||||
Column {
|
AsyncImage(
|
||||||
BasicText(
|
model = playlistOrAlbum.thumbnail?.size(thumbnailSizePx),
|
||||||
text = playlistOrAlbum.title ?: "Unknown",
|
contentDescription = null,
|
||||||
style = typography.m.semiBold
|
contentScale = ContentScale.Crop,
|
||||||
)
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = buildString {
|
|
||||||
val authors = playlistOrAlbum.authors?.joinToString("") { it.name }
|
|
||||||
append(authors)
|
|
||||||
if (authors?.isNotEmpty() == true && playlistOrAlbum.year != null) {
|
|
||||||
append(" • ")
|
|
||||||
}
|
|
||||||
append(playlistOrAlbum.year)
|
|
||||||
},
|
|
||||||
style = typography.xs.secondary.semiBold,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.End)
|
.clip(ThumbnailRoundness.shape)
|
||||||
.padding(horizontal = 16.dp)
|
.size(thumbnailSizeDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Image(
|
Column {
|
||||||
painter = painterResource(R.drawable.shuffle),
|
BasicText(
|
||||||
contentDescription = null,
|
text = playlistOrAlbum.title ?: "Unknown",
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
style = typography.m.semiBold
|
||||||
|
)
|
||||||
|
|
||||||
|
BasicText(
|
||||||
|
text = buildString {
|
||||||
|
val authors = playlistOrAlbum.authors?.joinToString("") { it.name }
|
||||||
|
append(authors)
|
||||||
|
if (authors?.isNotEmpty() == true && playlistOrAlbum.year != null) {
|
||||||
|
append(" • ")
|
||||||
|
}
|
||||||
|
append(playlistOrAlbum.year)
|
||||||
|
},
|
||||||
|
style = typography.xs.secondary.semiBold,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.align(Alignment.End)
|
||||||
player?.mediaController?.let {
|
.padding(horizontal = 16.dp)
|
||||||
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
) {
|
||||||
playlistOrAlbum.items
|
Image(
|
||||||
?.shuffled()
|
painter = painterResource(R.drawable.shuffle),
|
||||||
?.mapNotNull { song ->
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
player?.mediaController?.let {
|
||||||
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
|
playlistOrAlbum.items
|
||||||
|
?.shuffled()
|
||||||
|
?.mapNotNull { song ->
|
||||||
|
song.toMediaItem(browseId, playlistOrAlbum)
|
||||||
|
}?.let { mediaItems ->
|
||||||
|
it.forcePlayFromBeginning(mediaItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.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.play),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
player?.mediaController?.let {
|
||||||
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
|
playlistOrAlbum.items?.mapNotNull { song ->
|
||||||
song.toMediaItem(browseId, playlistOrAlbum)
|
song.toMediaItem(browseId, playlistOrAlbum)
|
||||||
}?.let { mediaItems ->
|
}?.let { mediaItems ->
|
||||||
it.forcePlayFromBeginning(mediaItems)
|
it.forcePlayFromBeginning(mediaItems)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
.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.play),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable {
|
|
||||||
player?.mediaController?.let {
|
|
||||||
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
|
||||||
playlistOrAlbum.items?.mapNotNull { song ->
|
|
||||||
song.toMediaItem(browseId, playlistOrAlbum)
|
|
||||||
}?.let { mediaItems ->
|
|
||||||
it.forcePlayFromBeginning(mediaItems)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.shadow(elevation = 2.dp, shape = CircleShape)
|
||||||
.shadow(elevation = 2.dp, shape = CircleShape)
|
.background(
|
||||||
.background(
|
color = colorPalette.elevatedBackground,
|
||||||
color = colorPalette.elevatedBackground,
|
shape = CircleShape
|
||||||
shape = CircleShape
|
)
|
||||||
)
|
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
.size(20.dp)
|
||||||
.size(20.dp)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
playlistOrAlbum.items?.forEachIndexed { index, song ->
|
itemsIndexed(
|
||||||
SongItem(
|
items = playlistOrAlbum.valueOrNull?.items ?: emptyList(),
|
||||||
title = song.info.name,
|
contentType = { _, song -> song }
|
||||||
authors = (song.authors ?: playlistOrAlbum.authors)?.joinToString("") { it.name },
|
) { index, song ->
|
||||||
durationText = song.durationText,
|
SongItem(
|
||||||
onClick = {
|
title = song.info.name,
|
||||||
player?.mediaController?.let {
|
authors = (song.authors ?: playlistOrAlbum.valueOrNull?.authors)?.joinToString("") { it.name },
|
||||||
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
durationText = song.durationText,
|
||||||
playlistOrAlbum.items?.mapNotNull { song ->
|
onClick = {
|
||||||
song.toMediaItem(browseId, playlistOrAlbum)
|
player?.mediaController?.let {
|
||||||
}?.let { mediaItems ->
|
it.sendCustomCommand(StopRadioCommand, Bundle.EMPTY)
|
||||||
it.forcePlayAtIndex(mediaItems, index)
|
playlistOrAlbum.valueOrNull?.items?.mapNotNull { song ->
|
||||||
}
|
song.toMediaItem(browseId, playlistOrAlbum.valueOrNull!!)
|
||||||
|
}?.let { mediaItems ->
|
||||||
|
it.forcePlayAtIndex(mediaItems, index)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
startContent = {
|
},
|
||||||
if (song.thumbnail == null) {
|
startContent = {
|
||||||
BasicText(
|
if (song.thumbnail == null) {
|
||||||
text = "${index + 1}",
|
BasicText(
|
||||||
style = typography.xs.secondary.bold.center,
|
text = "${index + 1}",
|
||||||
maxLines = 1,
|
style = typography.xs.secondary.bold.center,
|
||||||
overflow = TextOverflow.Ellipsis,
|
maxLines = 1,
|
||||||
modifier = Modifier
|
overflow = TextOverflow.Ellipsis,
|
||||||
.width(36.dp)
|
modifier = Modifier
|
||||||
)
|
.width(36.dp)
|
||||||
} else {
|
)
|
||||||
AsyncImage(
|
} else {
|
||||||
model = song.thumbnail!!.size(songThumbnailSizePx),
|
AsyncImage(
|
||||||
contentDescription = null,
|
model = song.thumbnail!!.size(songThumbnailSizePx),
|
||||||
contentScale = ContentScale.Crop,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
contentScale = ContentScale.Crop,
|
||||||
.clip(ThumbnailRoundness.shape)
|
modifier = Modifier
|
||||||
.size(songThumbnailSizeDp)
|
.clip(ThumbnailRoundness.shape)
|
||||||
)
|
.size(songThumbnailSizeDp)
|
||||||
}
|
|
||||||
},
|
|
||||||
menuContent = {
|
|
||||||
NonQueuedMediaItemMenu(
|
|
||||||
mediaItem = song.toMediaItem(browseId, playlistOrAlbum)
|
|
||||||
?: return@SongItem,
|
|
||||||
onDismiss = menuState::hide,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
},
|
||||||
}
|
menuContent = {
|
||||||
|
NonQueuedMediaItemMenu(
|
||||||
|
mediaItem = song.toMediaItem(browseId, playlistOrAlbum.valueOrNull!!)
|
||||||
|
?: return@SongItem,
|
||||||
|
onDismiss = menuState::hide,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user