Tweak code
This commit is contained in:
155
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/AlbumItem.kt
Normal file
155
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/AlbumItem.kt
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import it.vfsfitvnm.vimusic.models.Album
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
||||||
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
|
import it.vfsfitvnm.vimusic.utils.thumbnail
|
||||||
|
import it.vfsfitvnm.youtubemusic.Innertube
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AlbumItem(
|
||||||
|
album: Album,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false
|
||||||
|
) {
|
||||||
|
AlbumItem(
|
||||||
|
thumbnailUrl = album.thumbnailUrl,
|
||||||
|
title = album.title,
|
||||||
|
authors = album.authorsText,
|
||||||
|
year = album.year,
|
||||||
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
alternative = alternative,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AlbumItem(
|
||||||
|
album: Innertube.AlbumItem,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false
|
||||||
|
) {
|
||||||
|
AlbumItem(
|
||||||
|
thumbnailUrl = album.thumbnail?.url,
|
||||||
|
title = album.info?.name,
|
||||||
|
authors = album.authors?.joinToString("") { it.name ?: "" },
|
||||||
|
year = album.year,
|
||||||
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
alternative = alternative,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AlbumItem(
|
||||||
|
thumbnailUrl: String?,
|
||||||
|
title: String?,
|
||||||
|
authors: String?,
|
||||||
|
year: String?,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false
|
||||||
|
) {
|
||||||
|
val (_, typography, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
ItemContainer(
|
||||||
|
alternative = alternative,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
model = thumbnailUrl?.thumbnail(thumbnailSizePx),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(thumbnailShape)
|
||||||
|
.size(thumbnailSizeDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
ItemInfoContainer {
|
||||||
|
BasicText(
|
||||||
|
text = title ?: "",
|
||||||
|
style = typography.xs.semiBold,
|
||||||
|
maxLines = if (alternative) 1 else 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!alternative) {
|
||||||
|
authors?.let {
|
||||||
|
BasicText(
|
||||||
|
text = authors,
|
||||||
|
style = typography.xs.semiBold.secondary,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicText(
|
||||||
|
text = year ?: "",
|
||||||
|
style = typography.xxs.semiBold.secondary,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AlbumItemPlaceholder(
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false
|
||||||
|
) {
|
||||||
|
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
ItemContainer(
|
||||||
|
alternative = alternative,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
||||||
|
.size(thumbnailSizeDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
ItemInfoContainer {
|
||||||
|
TextPlaceholder()
|
||||||
|
|
||||||
|
if (!alternative) {
|
||||||
|
TextPlaceholder()
|
||||||
|
}
|
||||||
|
|
||||||
|
TextPlaceholder(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
145
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/ArtistItem.kt
Normal file
145
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/ArtistItem.kt
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.requiredSize
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import it.vfsfitvnm.vimusic.models.Artist
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
||||||
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
|
import it.vfsfitvnm.vimusic.utils.thumbnail
|
||||||
|
import it.vfsfitvnm.youtubemusic.Innertube
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ArtistItem(
|
||||||
|
artist: Artist,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false,
|
||||||
|
) {
|
||||||
|
ArtistItem(
|
||||||
|
thumbnailUrl = artist.thumbnailUrl,
|
||||||
|
name = artist.name,
|
||||||
|
subscribersCount = null,
|
||||||
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier,
|
||||||
|
alternative = alternative
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ArtistItem(
|
||||||
|
artist: Innertube.ArtistItem,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false,
|
||||||
|
) {
|
||||||
|
ArtistItem(
|
||||||
|
thumbnailUrl = artist.thumbnail?.url,
|
||||||
|
name = artist.info?.name,
|
||||||
|
subscribersCount = artist.subscribersCountText,
|
||||||
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier,
|
||||||
|
alternative = alternative
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ArtistItem(
|
||||||
|
thumbnailUrl: String?,
|
||||||
|
name: String?,
|
||||||
|
subscribersCount: String?,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false,
|
||||||
|
) {
|
||||||
|
val (_, typography) = LocalAppearance.current
|
||||||
|
|
||||||
|
ItemContainer(
|
||||||
|
alternative = alternative,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
model = thumbnailUrl?.thumbnail(thumbnailSizePx),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.requiredSize(thumbnailSizeDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
ItemInfoContainer(
|
||||||
|
horizontalAlignment = if (alternative) Alignment.CenterHorizontally else Alignment.Start,
|
||||||
|
) {
|
||||||
|
BasicText(
|
||||||
|
text = name ?: "",
|
||||||
|
style = typography.xs.semiBold,
|
||||||
|
maxLines = if (alternative) 1 else 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
|
||||||
|
subscribersCount?.let {
|
||||||
|
BasicText(
|
||||||
|
text = subscribersCount,
|
||||||
|
style = typography.xxs.semiBold.secondary,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ArtistItemPlaceholder(
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false,
|
||||||
|
) {
|
||||||
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
|
ItemContainer(
|
||||||
|
alternative = alternative,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = colorPalette.shimmer, shape = CircleShape)
|
||||||
|
.size(thumbnailSizeDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
ItemInfoContainer(
|
||||||
|
horizontalAlignment = if (alternative) Alignment.CenterHorizontally else Alignment.Start,
|
||||||
|
) {
|
||||||
|
TextPlaceholder()
|
||||||
|
TextPlaceholder(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
inline fun ItemContainer(
|
||||||
|
alternative: Boolean,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||||
|
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
|
||||||
|
content: @Composable (centeredModifier: Modifier) -> Unit
|
||||||
|
) {
|
||||||
|
if (alternative) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = horizontalAlignment,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
||||||
|
.width(thumbnailSizeDp)
|
||||||
|
) {
|
||||||
|
content(
|
||||||
|
centeredModifier = Modifier
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = verticalAlignment,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
content(
|
||||||
|
centeredModifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
inline fun ItemInfoContainer(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||||
|
content: @Composable ColumnScope.() -> Unit
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = horizontalAlignment,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
modifier = modifier,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.items
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.requiredSize
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
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.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import it.vfsfitvnm.vimusic.Database
|
||||||
|
import it.vfsfitvnm.vimusic.models.PlaylistPreview
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.onOverlay
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.overlay
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
||||||
|
import it.vfsfitvnm.vimusic.utils.color
|
||||||
|
import it.vfsfitvnm.vimusic.utils.medium
|
||||||
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
|
import it.vfsfitvnm.vimusic.utils.thumbnail
|
||||||
|
import it.vfsfitvnm.youtubemusic.Innertube
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlaylistItem(
|
||||||
|
@DrawableRes icon: Int,
|
||||||
|
colorTint: Color,
|
||||||
|
name: String?,
|
||||||
|
songCount: Int?,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false,
|
||||||
|
) {
|
||||||
|
PlaylistItem(
|
||||||
|
thumbnailContent = {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(icon),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(colorTint),
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.size(24.dp)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
songCount = songCount,
|
||||||
|
name = name,
|
||||||
|
channelName = null,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier,
|
||||||
|
alternative = alternative
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlaylistItem(
|
||||||
|
playlist: PlaylistPreview,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false,
|
||||||
|
) {
|
||||||
|
val thumbnails by remember {
|
||||||
|
Database.playlistThumbnailUrls(playlist.playlist.id).distinctUntilChanged().map {
|
||||||
|
it.map { url ->
|
||||||
|
url.thumbnail(thumbnailSizePx / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.collectAsState(initial = emptyList(), context = Dispatchers.IO)
|
||||||
|
|
||||||
|
PlaylistItem(
|
||||||
|
thumbnailContent = {
|
||||||
|
if (thumbnails.toSet().size == 1) {
|
||||||
|
AsyncImage(
|
||||||
|
model = thumbnails.first().thumbnail(thumbnailSizePx),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = it
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box(
|
||||||
|
modifier = it
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
listOf(
|
||||||
|
Alignment.TopStart,
|
||||||
|
Alignment.TopEnd,
|
||||||
|
Alignment.BottomStart,
|
||||||
|
Alignment.BottomEnd
|
||||||
|
).forEachIndexed { index, alignment ->
|
||||||
|
AsyncImage(
|
||||||
|
model = thumbnails.getOrNull(index),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(alignment)
|
||||||
|
.size(thumbnailSizeDp / 2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
songCount = playlist.songCount,
|
||||||
|
name = playlist.playlist.name,
|
||||||
|
channelName = null,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier,
|
||||||
|
alternative = alternative
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlaylistItem(
|
||||||
|
playlist: Innertube.PlaylistItem,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false,
|
||||||
|
) {
|
||||||
|
PlaylistItem(
|
||||||
|
thumbnailUrl = playlist.thumbnail?.url,
|
||||||
|
songCount = playlist.songCount,
|
||||||
|
name = playlist.info?.name,
|
||||||
|
channelName = playlist.channel?.name,
|
||||||
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier,
|
||||||
|
alternative = alternative
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlaylistItem(
|
||||||
|
thumbnailUrl: String?,
|
||||||
|
songCount: Int?,
|
||||||
|
name: String?,
|
||||||
|
channelName: String?,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false,
|
||||||
|
) {
|
||||||
|
PlaylistItem(
|
||||||
|
thumbnailContent = {
|
||||||
|
AsyncImage(
|
||||||
|
model = thumbnailUrl?.thumbnail(thumbnailSizePx),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = it
|
||||||
|
)
|
||||||
|
},
|
||||||
|
songCount = songCount,
|
||||||
|
name = name,
|
||||||
|
channelName = channelName,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier,
|
||||||
|
alternative = alternative,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlaylistItem(
|
||||||
|
thumbnailContent: @Composable BoxScope.(modifier: Modifier) -> Unit,
|
||||||
|
songCount: Int?,
|
||||||
|
name: String?,
|
||||||
|
channelName: String?,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false,
|
||||||
|
) {
|
||||||
|
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
ItemContainer(
|
||||||
|
alternative = alternative,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier
|
||||||
|
) { centeredModifier ->
|
||||||
|
Box(
|
||||||
|
modifier = centeredModifier
|
||||||
|
.clip(thumbnailShape)
|
||||||
|
.background(color = colorPalette.background1)
|
||||||
|
.requiredSize(thumbnailSizeDp)
|
||||||
|
) {
|
||||||
|
thumbnailContent(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
)
|
||||||
|
|
||||||
|
songCount?.let {
|
||||||
|
BasicText(
|
||||||
|
text = "$songCount",
|
||||||
|
style = typography.xxs.medium.color(colorPalette.onOverlay),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 4.dp)
|
||||||
|
.background(color = colorPalette.overlay, shape = RoundedCornerShape(2.dp))
|
||||||
|
.padding(horizontal = 4.dp, vertical = 2.dp)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemInfoContainer(
|
||||||
|
horizontalAlignment = if (alternative && channelName == null) Alignment.CenterHorizontally else Alignment.Start,
|
||||||
|
) {
|
||||||
|
BasicText(
|
||||||
|
text = name ?: "",
|
||||||
|
style = typography.xs.semiBold,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
|
||||||
|
channelName?.let {
|
||||||
|
BasicText(
|
||||||
|
text = channelName,
|
||||||
|
style = typography.xs.semiBold.secondary,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlaylistItemPlaceholder(
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
alternative: Boolean = false,
|
||||||
|
) {
|
||||||
|
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
ItemContainer(
|
||||||
|
alternative = alternative,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
||||||
|
.size(thumbnailSizeDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
ItemInfoContainer(
|
||||||
|
horizontalAlignment = if (alternative) Alignment.CenterHorizontally else Alignment.Start,
|
||||||
|
) {
|
||||||
|
TextPlaceholder()
|
||||||
|
TextPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
204
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/SongItem.kt
Normal file
204
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/SongItem.kt
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
||||||
|
import it.vfsfitvnm.vimusic.utils.medium
|
||||||
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
|
import it.vfsfitvnm.vimusic.utils.thumbnail
|
||||||
|
import it.vfsfitvnm.youtubemusic.Innertube
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SongItem(
|
||||||
|
song: Innertube.SongItem,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
SongItem(
|
||||||
|
thumbnailUrl = song.thumbnail?.size(thumbnailSizePx),
|
||||||
|
title = song.info?.name,
|
||||||
|
authors = song.authors?.joinToString("") { it.name ?: "" },
|
||||||
|
duration = song.durationText,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SongItem(
|
||||||
|
song: MediaItem,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onThumbnailContent: (@Composable BoxScope.() -> Unit)? = null,
|
||||||
|
trailingContent: (@Composable () -> Unit)? = null
|
||||||
|
) {
|
||||||
|
SongItem(
|
||||||
|
thumbnailUrl = song.mediaMetadata.artworkUri.thumbnail(thumbnailSizePx)?.toString(),
|
||||||
|
title = song.mediaMetadata.title.toString(),
|
||||||
|
authors = song.mediaMetadata.artist.toString(),
|
||||||
|
duration = song.mediaMetadata.extras?.getString("durationText"),
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
onThumbnailContent = onThumbnailContent,
|
||||||
|
trailingContent = trailingContent,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SongItem(
|
||||||
|
song: DetailedSong,
|
||||||
|
thumbnailSizePx: Int,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onThumbnailContent: (@Composable BoxScope.() -> Unit)? = null,
|
||||||
|
trailingContent: (@Composable () -> Unit)? = null
|
||||||
|
) {
|
||||||
|
SongItem(
|
||||||
|
thumbnailUrl = song.thumbnailUrl?.thumbnail(thumbnailSizePx),
|
||||||
|
title = song.title,
|
||||||
|
authors = song.artistsText,
|
||||||
|
duration = song.durationText,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
onThumbnailContent = onThumbnailContent,
|
||||||
|
trailingContent = trailingContent,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SongItem(
|
||||||
|
thumbnailUrl: String?,
|
||||||
|
title: String?,
|
||||||
|
authors: String?,
|
||||||
|
duration: String?,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onThumbnailContent: (@Composable BoxScope.() -> Unit)? = null,
|
||||||
|
trailingContent: (@Composable () -> Unit)? = null
|
||||||
|
) {
|
||||||
|
SongItem(
|
||||||
|
title = title,
|
||||||
|
authors = authors,
|
||||||
|
duration = duration,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
thumbnailContent = {
|
||||||
|
AsyncImage(
|
||||||
|
model = thumbnailUrl,
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(LocalAppearance.current.thumbnailShape)
|
||||||
|
.fillMaxSize()
|
||||||
|
)
|
||||||
|
|
||||||
|
onThumbnailContent?.invoke(this)
|
||||||
|
},
|
||||||
|
modifier = modifier,
|
||||||
|
trailingContent = trailingContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SongItem(
|
||||||
|
thumbnailContent: @Composable BoxScope.() -> Unit,
|
||||||
|
title: String?,
|
||||||
|
authors: String?,
|
||||||
|
duration: String?,
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
trailingContent: @Composable (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val (_, typography) = LocalAppearance.current
|
||||||
|
|
||||||
|
ItemContainer(
|
||||||
|
alternative = false,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(thumbnailSizeDp)
|
||||||
|
) {
|
||||||
|
thumbnailContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemInfoContainer {
|
||||||
|
BasicText(
|
||||||
|
text = title ?: "",
|
||||||
|
style = typography.xs.semiBold,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
BasicText(
|
||||||
|
text = authors ?: "",
|
||||||
|
style = typography.xs.semiBold.secondary,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Clip,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
duration?.let {
|
||||||
|
BasicText(
|
||||||
|
text = duration,
|
||||||
|
style = typography.xxs.secondary.medium,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SongItemPlaceholder(
|
||||||
|
thumbnailSizeDp: Dp,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
ItemContainer(
|
||||||
|
alternative = false,
|
||||||
|
thumbnailSizeDp =thumbnailSizeDp,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
||||||
|
.size(thumbnailSizeDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
ItemInfoContainer {
|
||||||
|
TextPlaceholder()
|
||||||
|
TextPlaceholder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
149
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/VideoItem.kt
Normal file
149
app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/items/VideoItem.kt
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.onOverlay
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.overlay
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
||||||
|
import it.vfsfitvnm.vimusic.utils.color
|
||||||
|
import it.vfsfitvnm.vimusic.utils.medium
|
||||||
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
|
import it.vfsfitvnm.youtubemusic.Innertube
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VideoItem(
|
||||||
|
video: Innertube.VideoItem,
|
||||||
|
thumbnailHeightDp: Dp,
|
||||||
|
thumbnailWidthDp: Dp,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
VideoItem(
|
||||||
|
thumbnailUrl = video.thumbnail?.url,
|
||||||
|
duration = video.durationText,
|
||||||
|
title = video.info?.name,
|
||||||
|
uploader = video.authors?.joinToString("") { it.name ?: "" },
|
||||||
|
views = video.viewsText,
|
||||||
|
thumbnailHeightDp = thumbnailHeightDp,
|
||||||
|
thumbnailWidthDp = thumbnailWidthDp,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VideoItem(
|
||||||
|
thumbnailUrl: String?,
|
||||||
|
duration: String?,
|
||||||
|
title: String?,
|
||||||
|
uploader: String?,
|
||||||
|
views: String?,
|
||||||
|
thumbnailHeightDp: Dp,
|
||||||
|
thumbnailWidthDp: Dp,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
ItemContainer(
|
||||||
|
alternative = false,
|
||||||
|
thumbnailSizeDp = 0.dp,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
AsyncImage(
|
||||||
|
model = thumbnailUrl,
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(thumbnailShape)
|
||||||
|
.size(width = thumbnailWidthDp, height = thumbnailHeightDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
duration?.let {
|
||||||
|
BasicText(
|
||||||
|
text = duration,
|
||||||
|
style = typography.xxs.medium.color(colorPalette.onOverlay),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 4.dp)
|
||||||
|
.background(color = colorPalette.overlay, shape = RoundedCornerShape(2.dp))
|
||||||
|
.padding(horizontal = 4.dp, vertical = 2.dp)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemInfoContainer {
|
||||||
|
BasicText(
|
||||||
|
text = title ?: "",
|
||||||
|
style = typography.xs.semiBold,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
|
||||||
|
BasicText(
|
||||||
|
text = uploader ?: "",
|
||||||
|
style = typography.xs.semiBold.secondary,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
|
||||||
|
views?.let {
|
||||||
|
BasicText(
|
||||||
|
text = views,
|
||||||
|
style = typography.xxs.medium.secondary,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VideoItemPlaceholder(
|
||||||
|
thumbnailHeightDp: Dp,
|
||||||
|
thumbnailWidthDp: Dp,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
||||||
|
|
||||||
|
ItemContainer(
|
||||||
|
alternative = false,
|
||||||
|
thumbnailSizeDp = 0.dp,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
||||||
|
.size(width = thumbnailWidthDp, height = thumbnailHeightDp)
|
||||||
|
)
|
||||||
|
|
||||||
|
ItemInfoContainer {
|
||||||
|
TextPlaceholder()
|
||||||
|
TextPlaceholder()
|
||||||
|
TextPlaceholder(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,8 +51,8 @@ import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
|||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
||||||
import it.vfsfitvnm.vimusic.ui.views.AlbumItem
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
||||||
import it.vfsfitvnm.vimusic.ui.views.AlbumItemPlaceholder
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
import it.vfsfitvnm.vimusic.utils.thumbnail
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package it.vfsfitvnm.vimusic.ui.screens.album
|
|||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
@@ -11,8 +13,11 @@ import androidx.compose.foundation.layout.width
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
@@ -21,14 +26,15 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
|||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.ShimmerHost
|
import it.vfsfitvnm.vimusic.ui.components.themed.ShimmerHost
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItemPlaceholder
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.center
|
import it.vfsfitvnm.vimusic.utils.center
|
||||||
import it.vfsfitvnm.vimusic.utils.color
|
import it.vfsfitvnm.vimusic.utils.color
|
||||||
@@ -50,6 +56,9 @@ fun AlbumSongs(
|
|||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
val songs by produceSaveableState(
|
val songs by produceSaveableState(
|
||||||
initialValue = emptyList(),
|
initialValue = emptyList(),
|
||||||
@@ -61,6 +70,8 @@ fun AlbumSongs(
|
|||||||
.collect { value = it }
|
.collect { value = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
@@ -94,27 +105,33 @@ fun AlbumSongs(
|
|||||||
SongItem(
|
SongItem(
|
||||||
title = song.title,
|
title = song.title,
|
||||||
authors = song.artistsText,
|
authors = song.artistsText,
|
||||||
durationText = song.durationText,
|
duration = song.durationText,
|
||||||
onClick = {
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
binder?.stopRadio()
|
thumbnailContent = {
|
||||||
binder?.player?.forcePlayAtIndex(
|
|
||||||
songs.map(DetailedSong::asMediaItem),
|
|
||||||
index
|
|
||||||
)
|
|
||||||
},
|
|
||||||
startContent = {
|
|
||||||
BasicText(
|
BasicText(
|
||||||
text = "${index + 1}",
|
text = "${index + 1}",
|
||||||
style = typography.s.semiBold.center.color(colorPalette.textDisabled),
|
style = typography.s.semiBold.center.color(colorPalette.textDisabled),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(Dimensions.thumbnails.song)
|
.width(thumbnailSizeDp)
|
||||||
|
.align(Alignment.Center)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
menuContent = {
|
modifier = Modifier
|
||||||
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
.combinedClickable(
|
||||||
}
|
indication = rippleIndication,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlayAtIndex(songs.map(DetailedSong::asMediaItem), index)
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.artist
|
package it.vfsfitvnm.vimusic.ui.screens.artist
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
@@ -18,15 +23,16 @@ import it.vfsfitvnm.vimusic.R
|
|||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.ShimmerHost
|
import it.vfsfitvnm.vimusic.ui.components.themed.ShimmerHost
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.enqueue
|
import it.vfsfitvnm.vimusic.utils.enqueue
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
||||||
@@ -35,6 +41,7 @@ import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun ArtistLocalSongs(
|
fun ArtistLocalSongs(
|
||||||
@@ -44,6 +51,9 @@ fun ArtistLocalSongs(
|
|||||||
) {
|
) {
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
val (colorPalette) = LocalAppearance.current
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
val songs by produceSaveableState(
|
val songs by produceSaveableState(
|
||||||
initialValue = null,
|
initialValue = null,
|
||||||
@@ -55,7 +65,8 @@ fun ArtistLocalSongs(
|
|||||||
.collect { value = it }
|
.collect { value = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
val songThumbnailSizePx = Dimensions.thumbnails.song.px
|
val songThumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
|
val songThumbnailSizePx = songThumbnailSizeDp.px
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@@ -90,17 +101,22 @@ fun ArtistLocalSongs(
|
|||||||
) { index, song ->
|
) { index, song ->
|
||||||
SongItem(
|
SongItem(
|
||||||
song = song,
|
song = song,
|
||||||
|
thumbnailSizeDp = songThumbnailSizeDp,
|
||||||
thumbnailSizePx = songThumbnailSizePx,
|
thumbnailSizePx = songThumbnailSizePx,
|
||||||
onClick = {
|
modifier = Modifier
|
||||||
binder?.stopRadio()
|
.combinedClickable(
|
||||||
binder?.player?.forcePlayAtIndex(
|
indication = rippleIndication,
|
||||||
songs.map(DetailedSong::asMediaItem),
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
index
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlayAtIndex(songs.map(DetailedSong::asMediaItem), index)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
|
||||||
menuContent = {
|
|
||||||
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} ?: item(key = "loading") {
|
} ?: item(key = "loading") {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.artist
|
package it.vfsfitvnm.vimusic.ui.screens.artist
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -26,17 +28,19 @@ import androidx.compose.ui.unit.dp
|
|||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.ShimmerHost
|
import it.vfsfitvnm.vimusic.ui.components.themed.ShimmerHost
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItemPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.views.AlbumItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.AlbumItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlay
|
import it.vfsfitvnm.vimusic.utils.forcePlay
|
||||||
import it.vfsfitvnm.vimusic.utils.secondary
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
@@ -44,6 +48,7 @@ import it.vfsfitvnm.vimusic.utils.semiBold
|
|||||||
import it.vfsfitvnm.youtubemusic.Innertube
|
import it.vfsfitvnm.youtubemusic.Innertube
|
||||||
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
|
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
|
||||||
|
|
||||||
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun ArtistOverview(
|
fun ArtistOverview(
|
||||||
@@ -57,6 +62,9 @@ fun ArtistOverview(
|
|||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
val songThumbnailSizeDp = Dimensions.thumbnails.song
|
val songThumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
val songThumbnailSizePx = songThumbnailSizeDp.px
|
val songThumbnailSizePx = songThumbnailSizeDp.px
|
||||||
@@ -120,15 +128,26 @@ fun ArtistOverview(
|
|||||||
songs.forEach { song ->
|
songs.forEach { song ->
|
||||||
SongItem(
|
SongItem(
|
||||||
song = song,
|
song = song,
|
||||||
|
thumbnailSizeDp = songThumbnailSizeDp,
|
||||||
thumbnailSizePx = songThumbnailSizePx,
|
thumbnailSizePx = songThumbnailSizePx,
|
||||||
onClick = {
|
modifier = Modifier
|
||||||
val mediaItem = song.asMediaItem
|
.combinedClickable(
|
||||||
binder?.stopRadio()
|
indication = rippleIndication,
|
||||||
binder?.player?.forcePlay(mediaItem)
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
binder?.setupRadio(
|
onLongClick = {
|
||||||
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
|
menuState.display {
|
||||||
|
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
val mediaItem = song.asMediaItem
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlay(mediaItem)
|
||||||
|
binder?.setupRadio(
|
||||||
|
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package it.vfsfitvnm.vimusic.ui.screens.artist
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.LocalIndication
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
@@ -39,9 +42,15 @@ import it.vfsfitvnm.vimusic.savers.InnertubeAlbumsPageSaver
|
|||||||
import it.vfsfitvnm.vimusic.savers.InnertubeArtistPageSaver
|
import it.vfsfitvnm.vimusic.savers.InnertubeArtistPageSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.InnertubeSongsPageSaver
|
import it.vfsfitvnm.vimusic.savers.InnertubeSongsPageSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
|
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItemPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.albumRoute
|
import it.vfsfitvnm.vimusic.ui.screens.albumRoute
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.searchresult.ItemsPage
|
import it.vfsfitvnm.vimusic.ui.screens.searchresult.ItemsPage
|
||||||
@@ -49,10 +58,6 @@ import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
|||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
||||||
import it.vfsfitvnm.vimusic.ui.views.AlbumItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.AlbumItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.utils.artistScreenTabIndexKey
|
import it.vfsfitvnm.vimusic.utils.artistScreenTabIndexKey
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlay
|
import it.vfsfitvnm.vimusic.utils.forcePlay
|
||||||
@@ -69,6 +74,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun ArtistScreen(browseId: String) {
|
fun ArtistScreen(browseId: String) {
|
||||||
@@ -250,9 +256,12 @@ fun ArtistScreen(browseId: String) {
|
|||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
val thumbnailSizeDp = Dimensions.thumbnails.song
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
ItemsPage(
|
ItemsPage(
|
||||||
stateSaver = InnertubeSongsPageSaver,
|
stateSaver = InnertubeSongsPageSaver,
|
||||||
headerContent = headerContent,
|
headerContent = headerContent,
|
||||||
@@ -284,12 +293,23 @@ fun ArtistScreen(browseId: String) {
|
|||||||
itemContent = { song ->
|
itemContent = { song ->
|
||||||
SongItem(
|
SongItem(
|
||||||
song = song,
|
song = song,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
thumbnailSizePx = thumbnailSizePx,
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
onClick = {
|
modifier = Modifier
|
||||||
binder?.stopRadio()
|
.combinedClickable(
|
||||||
binder?.player?.forcePlay(song.asMediaItem)
|
indication = rippleIndication,
|
||||||
binder?.setupRadio(song.info?.endpoint)
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
}
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlay(song.asMediaItem)
|
||||||
|
binder?.setupRadio(song.info?.endpoint)
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
itemPlaceholderContent = {
|
itemPlaceholderContent = {
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ package it.vfsfitvnm.vimusic.ui.screens.builtinplaylist
|
|||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
@@ -18,15 +22,16 @@ import it.vfsfitvnm.vimusic.R
|
|||||||
import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist
|
import it.vfsfitvnm.vimusic.enums.BuiltInPlaylist
|
||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.enqueue
|
import it.vfsfitvnm.vimusic.utils.enqueue
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
||||||
@@ -42,6 +47,9 @@ import kotlinx.coroutines.flow.map
|
|||||||
fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
|
fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
|
||||||
val (colorPalette) = LocalAppearance.current
|
val (colorPalette) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
val songs by produceSaveableState(
|
val songs by produceSaveableState(
|
||||||
initialValue = emptyList(),
|
initialValue = emptyList(),
|
||||||
@@ -64,7 +72,8 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
|
|||||||
}.collect { value = it }
|
}.collect { value = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
val thumbnailSize = Dimensions.thumbnails.song.px
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
|
val thumbnailSize = thumbnailSizeDp.px
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@@ -105,21 +114,25 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
|
|||||||
) { index, song ->
|
) { index, song ->
|
||||||
SongItem(
|
SongItem(
|
||||||
song = song,
|
song = song,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
thumbnailSizePx = thumbnailSize,
|
thumbnailSizePx = thumbnailSize,
|
||||||
onClick = {
|
|
||||||
binder?.stopRadio()
|
|
||||||
binder?.player?.forcePlayAtIndex(
|
|
||||||
songs.map(DetailedSong::asMediaItem),
|
|
||||||
index
|
|
||||||
)
|
|
||||||
},
|
|
||||||
menuContent = {
|
|
||||||
when (builtInPlaylist) {
|
|
||||||
BuiltInPlaylist.Favorites -> InFavoritesMediaItemMenu(song = song)
|
|
||||||
BuiltInPlaylist.Offline -> InHistoryMediaItemMenu(song = song)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
indication = rippleIndication,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
when (builtInPlaylist) {
|
||||||
|
BuiltInPlaylist.Favorites -> InFavoritesMediaItemMenu(song = song)
|
||||||
|
BuiltInPlaylist.Offline -> InHistoryMediaItemMenu(song = song)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlayAtIndex(songs.map(DetailedSong::asMediaItem), index)
|
||||||
|
}
|
||||||
|
)
|
||||||
.animateItemPlacement()
|
.animateItemPlacement()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,33 +10,23 @@ import androidx.compose.foundation.Image
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.BasicText
|
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
@@ -45,6 +35,7 @@ import it.vfsfitvnm.vimusic.enums.SortOrder
|
|||||||
import it.vfsfitvnm.vimusic.models.Album
|
import it.vfsfitvnm.vimusic.models.Album
|
||||||
import it.vfsfitvnm.vimusic.savers.AlbumListSaver
|
import it.vfsfitvnm.vimusic.savers.AlbumListSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
@@ -52,9 +43,6 @@ import it.vfsfitvnm.vimusic.utils.albumSortByKey
|
|||||||
import it.vfsfitvnm.vimusic.utils.albumSortOrderKey
|
import it.vfsfitvnm.vimusic.utils.albumSortOrderKey
|
||||||
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
||||||
import it.vfsfitvnm.vimusic.utils.secondary
|
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
@@ -64,7 +52,7 @@ import kotlinx.coroutines.flow.flowOn
|
|||||||
fun HomeAlbums(
|
fun HomeAlbums(
|
||||||
onAlbumClick: (Album) -> Unit
|
onAlbumClick: (Album) -> Unit
|
||||||
) {
|
) {
|
||||||
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
var sortBy by rememberPreference(albumSortByKey, AlbumSortBy.DateAdded)
|
var sortBy by rememberPreference(albumSortByKey, AlbumSortBy.DateAdded)
|
||||||
var sortOrder by rememberPreference(albumSortOrderKey, SortOrder.Descending)
|
var sortOrder by rememberPreference(albumSortOrderKey, SortOrder.Descending)
|
||||||
@@ -154,55 +142,18 @@ fun HomeAlbums(
|
|||||||
items = items,
|
items = items,
|
||||||
key = Album::id
|
key = Album::id
|
||||||
) { album ->
|
) { album ->
|
||||||
Row(
|
AlbumItem(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
album = album,
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = rippleIndication,
|
indication = rippleIndication,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
onClick = { onAlbumClick(album) }
|
onClick = { onAlbumClick(album) }
|
||||||
)
|
)
|
||||||
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.animateItemPlacement()
|
.animateItemPlacement()
|
||||||
) {
|
)
|
||||||
AsyncImage(
|
|
||||||
model = album.thumbnailUrl?.thumbnail(thumbnailSizePx),
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(thumbnailShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
|
||||||
BasicText(
|
|
||||||
text = album.title ?: "",
|
|
||||||
style = typography.xs.semiBold,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = album.authorsText ?: "",
|
|
||||||
style = typography.xs.semiBold.secondary,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
|
|
||||||
album.year?.let { year ->
|
|
||||||
BasicText(
|
|
||||||
text = year,
|
|
||||||
style = typography.xxs.semiBold.secondary,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,20 +11,15 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredSize
|
|
||||||
import androidx.compose.foundation.layout.requiredWidth
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.text.BasicText
|
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -32,13 +27,10 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
@@ -47,16 +39,14 @@ import it.vfsfitvnm.vimusic.enums.SortOrder
|
|||||||
import it.vfsfitvnm.vimusic.models.Artist
|
import it.vfsfitvnm.vimusic.models.Artist
|
||||||
import it.vfsfitvnm.vimusic.savers.ArtistListSaver
|
import it.vfsfitvnm.vimusic.savers.ArtistListSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.ArtistItem
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.utils.artistSortByKey
|
import it.vfsfitvnm.vimusic.utils.artistSortByKey
|
||||||
import it.vfsfitvnm.vimusic.utils.artistSortOrderKey
|
import it.vfsfitvnm.vimusic.utils.artistSortOrderKey
|
||||||
import it.vfsfitvnm.vimusic.utils.center
|
|
||||||
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
@@ -66,7 +56,7 @@ import kotlinx.coroutines.flow.flowOn
|
|||||||
fun HomeArtistList(
|
fun HomeArtistList(
|
||||||
onArtistClick: (Artist) -> Unit
|
onArtistClick: (Artist) -> Unit
|
||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
var sortBy by rememberPreference(artistSortByKey, ArtistSortBy.DateAdded)
|
var sortBy by rememberPreference(artistSortByKey, ArtistSortBy.DateAdded)
|
||||||
var sortOrder by rememberPreference(artistSortOrderKey, SortOrder.Descending)
|
var sortOrder by rememberPreference(artistSortOrderKey, SortOrder.Descending)
|
||||||
@@ -154,39 +144,21 @@ fun HomeArtistList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items(
|
items(items = items, key = Artist::id) { artist ->
|
||||||
items = items,
|
ArtistItem(
|
||||||
key = Artist::id
|
artist = artist,
|
||||||
) { artist ->
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
Column(
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
alternative = true,
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.requiredWidth(thumbnailSizeDp)
|
.clickable(
|
||||||
|
indication = rippleIndication,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onClick = { onArtistClick(artist) }
|
||||||
|
)
|
||||||
|
// .requiredWidth(thumbnailSizeDp)
|
||||||
.animateItemPlacement()
|
.animateItemPlacement()
|
||||||
) {
|
)
|
||||||
AsyncImage(
|
|
||||||
model = artist.thumbnailUrl?.thumbnail(thumbnailSizePx),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(CircleShape)
|
|
||||||
.clickable(
|
|
||||||
indication = rippleIndication,
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
onClick = { onArtistClick(artist) }
|
|
||||||
)
|
|
||||||
.background(colorPalette.background1)
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
.requiredSize(thumbnailSizeDp),
|
|
||||||
)
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = artist.name ?: "",
|
|
||||||
style = typography.xxs.semiBold.center,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ import it.vfsfitvnm.vimusic.savers.PlaylistPreviewListSaver
|
|||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.PlaylistItem
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.views.BuiltInPlaylistItem
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.views.PlaylistPreviewItem
|
|
||||||
import it.vfsfitvnm.vimusic.utils.playlistSortByKey
|
import it.vfsfitvnm.vimusic.utils.playlistSortByKey
|
||||||
import it.vfsfitvnm.vimusic.utils.playlistSortOrderKey
|
import it.vfsfitvnm.vimusic.utils.playlistSortOrderKey
|
||||||
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
||||||
@@ -100,6 +100,9 @@ fun HomePlaylists(
|
|||||||
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
|
animationSpec = tween(durationMillis = 400, easing = LinearEasing)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val thumbnailSizeDp = 108.dp
|
||||||
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Adaptive(Dimensions.thumbnails.song * 2 + Dimensions.itemsVerticalPadding * 2),
|
columns = GridCells.Adaptive(Dimensions.thumbnails.song * 2 + Dimensions.itemsVerticalPadding * 2),
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
@@ -112,11 +115,7 @@ fun HomePlaylists(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(colorPalette.background0)
|
.background(colorPalette.background0)
|
||||||
) {
|
) {
|
||||||
item(
|
item(key = "header", contentType = 0, span = { GridItemSpan(maxLineSpan) }) {
|
||||||
key = "header",
|
|
||||||
contentType = 0,
|
|
||||||
span = { GridItemSpan(maxLineSpan) }
|
|
||||||
) {
|
|
||||||
Header(title = "Playlists") {
|
Header(title = "Playlists") {
|
||||||
@Composable
|
@Composable
|
||||||
fun Item(
|
fun Item(
|
||||||
@@ -178,24 +177,31 @@ fun HomePlaylists(
|
|||||||
}
|
}
|
||||||
|
|
||||||
item(key = "favorites") {
|
item(key = "favorites") {
|
||||||
BuiltInPlaylistItem(
|
PlaylistItem(
|
||||||
icon = R.drawable.heart,
|
icon = R.drawable.heart,
|
||||||
colorTint = colorPalette.red,
|
colorTint = colorPalette.red,
|
||||||
name = "Favorites",
|
name = "Favorites",
|
||||||
|
songCount = null,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
alternative = true,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = rememberRipple(bounded = true),
|
indication = rememberRipple(bounded = true),
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
onClick = { onBuiltInPlaylist(BuiltInPlaylist.Favorites) }
|
onClick = { onBuiltInPlaylist(BuiltInPlaylist.Favorites) }
|
||||||
)
|
)
|
||||||
|
.animateItemPlacement()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item(key = "offline") {
|
item(key = "offline") {
|
||||||
BuiltInPlaylistItem(
|
PlaylistItem(
|
||||||
icon = R.drawable.airplane,
|
icon = R.drawable.airplane,
|
||||||
colorTint = colorPalette.blue,
|
colorTint = colorPalette.blue,
|
||||||
name = "Offline",
|
name = "Offline",
|
||||||
|
songCount = null,
|
||||||
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
|
alternative = true,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = rememberRipple(bounded = true),
|
indication = rememberRipple(bounded = true),
|
||||||
@@ -206,12 +212,12 @@ fun HomePlaylists(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
items(
|
items(items = items, key = { it.playlist.id }) { playlistPreview ->
|
||||||
items = items,
|
PlaylistItem(
|
||||||
key = { it.playlist.id }
|
playlist = playlistPreview,
|
||||||
) { playlistPreview ->
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
PlaylistPreviewItem(
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
playlistPreview = playlistPreview,
|
alternative = true,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = rememberRipple(bounded = true),
|
indication = rememberRipple(bounded = true),
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.home
|
package it.vfsfitvnm.vimusic.ui.screens.home
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.animation.core.LinearEasing
|
import androidx.compose.animation.core.LinearEasing
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@@ -24,8 +23,10 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -44,13 +45,16 @@ import it.vfsfitvnm.vimusic.enums.SongSortBy
|
|||||||
import it.vfsfitvnm.vimusic.enums.SortOrder
|
import it.vfsfitvnm.vimusic.enums.SortOrder
|
||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.ScrollToTop
|
import it.vfsfitvnm.vimusic.ui.components.themed.ScrollToTop
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.onOverlay
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.overlay
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.center
|
import it.vfsfitvnm.vimusic.utils.center
|
||||||
import it.vfsfitvnm.vimusic.utils.color
|
import it.vfsfitvnm.vimusic.utils.color
|
||||||
@@ -69,8 +73,12 @@ import kotlinx.coroutines.flow.flowOn
|
|||||||
fun HomeSongs() {
|
fun HomeSongs() {
|
||||||
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
val thumbnailSize = Dimensions.thumbnails.song.px
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
var sortBy by rememberPreference(songSortByKey, SongSortBy.DateAdded)
|
var sortBy by rememberPreference(songSortByKey, SongSortBy.DateAdded)
|
||||||
var sortOrder by rememberPreference(songSortOrderKey, SortOrder.Descending)
|
var sortOrder by rememberPreference(songSortOrderKey, SortOrder.Descending)
|
||||||
@@ -162,46 +170,40 @@ fun HomeSongs() {
|
|||||||
) { index, song ->
|
) { index, song ->
|
||||||
SongItem(
|
SongItem(
|
||||||
song = song,
|
song = song,
|
||||||
thumbnailSizePx = thumbnailSize,
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
onClick = {
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
binder?.stopRadio()
|
onThumbnailContent = if (sortBy == SongSortBy.PlayTime) ({
|
||||||
binder?.player?.forcePlayAtIndex(items.map(DetailedSong::asMediaItem), index)
|
BasicText(
|
||||||
},
|
text = song.formattedTotalPlayTime,
|
||||||
menuContent = {
|
style = typography.xxs.semiBold.center.color(colorPalette.onOverlay),
|
||||||
InHistoryMediaItemMenu(song = song)
|
maxLines = 2,
|
||||||
},
|
overflow = TextOverflow.Ellipsis,
|
||||||
onThumbnailContent = {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = sortBy == SongSortBy.PlayTime,
|
|
||||||
enter = fadeIn(),
|
|
||||||
exit = fadeOut(),
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
colors = listOf(Color.Transparent, colorPalette.overlay)
|
||||||
|
),
|
||||||
|
shape = thumbnailShape
|
||||||
|
)
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
) {
|
)
|
||||||
BasicText(
|
}) else null,
|
||||||
text = song.formattedTotalPlayTime,
|
|
||||||
style = typography.xxs.semiBold.center.color(Color.White),
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(
|
|
||||||
brush = Brush.verticalGradient(
|
|
||||||
colors = listOf(
|
|
||||||
Color.Transparent,
|
|
||||||
Color.Black.copy(alpha = 0.75f)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
shape = thumbnailShape
|
|
||||||
)
|
|
||||||
.padding(
|
|
||||||
horizontal = 8.dp,
|
|
||||||
vertical = 4.dp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
indication = rippleIndication,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
InHistoryMediaItemMenu(song = song)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlayAtIndex(items.map(DetailedSong::asMediaItem), index)
|
||||||
|
}
|
||||||
|
)
|
||||||
.animateItemPlacement()
|
.animateItemPlacement()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.home
|
package it.vfsfitvnm.vimusic.ui.screens.home
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -40,20 +42,21 @@ import it.vfsfitvnm.vimusic.savers.DetailedSongSaver
|
|||||||
import it.vfsfitvnm.vimusic.savers.InnertubeRelatedPageSaver
|
import it.vfsfitvnm.vimusic.savers.InnertubeRelatedPageSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.resultSaver
|
import it.vfsfitvnm.vimusic.savers.resultSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItemPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.ArtistItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.ArtistItemPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.PlaylistItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.PlaylistItemPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.views.AlbumItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.AlbumItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.ArtistItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.ArtistItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.PlaylistItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.PlaylistItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.center
|
import it.vfsfitvnm.vimusic.utils.center
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlay
|
import it.vfsfitvnm.vimusic.utils.forcePlay
|
||||||
@@ -61,7 +64,6 @@ import it.vfsfitvnm.vimusic.utils.produceSaveableOneShotState
|
|||||||
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
||||||
import it.vfsfitvnm.vimusic.utils.secondary
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
import it.vfsfitvnm.vimusic.utils.semiBold
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
|
||||||
import it.vfsfitvnm.youtubemusic.Innertube
|
import it.vfsfitvnm.youtubemusic.Innertube
|
||||||
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
|
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
|
||||||
import it.vfsfitvnm.youtubemusic.models.bodies.NextBody
|
import it.vfsfitvnm.youtubemusic.models.bodies.NextBody
|
||||||
@@ -71,6 +73,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun QuickPicks(
|
fun QuickPicks(
|
||||||
@@ -80,6 +83,9 @@ fun QuickPicks(
|
|||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
val trending by produceSaveableState(
|
val trending by produceSaveableState(
|
||||||
initialValue = null,
|
initialValue = null,
|
||||||
@@ -135,20 +141,28 @@ fun QuickPicks(
|
|||||||
trending?.let { song ->
|
trending?.let { song ->
|
||||||
item {
|
item {
|
||||||
SongItem(
|
SongItem(
|
||||||
thumbnailModel = song.thumbnailUrl?.thumbnail(songThumbnailSizePx),
|
song = song,
|
||||||
title = song.title,
|
thumbnailSizePx = songThumbnailSizePx,
|
||||||
authors = song.artistsText,
|
thumbnailSizeDp = songThumbnailSizeDp,
|
||||||
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
|
modifier = Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
indication = rippleIndication,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
val mediaItem = song.asMediaItem
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlay(mediaItem)
|
||||||
|
binder?.setupRadio(
|
||||||
|
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.animateItemPlacement()
|
||||||
.width(itemInHorizontalGridWidth)
|
.width(itemInHorizontalGridWidth)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -161,15 +175,26 @@ fun QuickPicks(
|
|||||||
SongItem(
|
SongItem(
|
||||||
song = song,
|
song = song,
|
||||||
thumbnailSizePx = songThumbnailSizePx,
|
thumbnailSizePx = songThumbnailSizePx,
|
||||||
onClick = {
|
thumbnailSizeDp = songThumbnailSizeDp,
|
||||||
val mediaItem = song.asMediaItem
|
|
||||||
binder?.stopRadio()
|
|
||||||
binder?.player?.forcePlay(mediaItem)
|
|
||||||
binder?.setupRadio(
|
|
||||||
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
indication = rippleIndication,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
val mediaItem = song.asMediaItem
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlay(mediaItem)
|
||||||
|
binder?.setupRadio(
|
||||||
|
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.animateItemPlacement()
|
||||||
.width(itemInHorizontalGridWidth)
|
.width(itemInHorizontalGridWidth)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@@ -12,9 +14,11 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -36,16 +40,17 @@ import it.vfsfitvnm.vimusic.query
|
|||||||
import it.vfsfitvnm.vimusic.savers.PlaylistWithSongsSaver
|
import it.vfsfitvnm.vimusic.savers.PlaylistWithSongsSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
import it.vfsfitvnm.vimusic.savers.nullableSaver
|
||||||
import it.vfsfitvnm.vimusic.transaction
|
import it.vfsfitvnm.vimusic.transaction
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.ConfirmationDialog
|
import it.vfsfitvnm.vimusic.ui.components.themed.ConfirmationDialog
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InPlaylistMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.InPlaylistMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.completed
|
import it.vfsfitvnm.vimusic.utils.completed
|
||||||
import it.vfsfitvnm.vimusic.utils.enqueue
|
import it.vfsfitvnm.vimusic.utils.enqueue
|
||||||
@@ -69,6 +74,9 @@ fun LocalPlaylistSongs(
|
|||||||
) {
|
) {
|
||||||
val (colorPalette) = LocalAppearance.current
|
val (colorPalette) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
val playlistWithSongs by produceSaveableState(
|
val playlistWithSongs by produceSaveableState(
|
||||||
initialValue = null,
|
initialValue = null,
|
||||||
@@ -127,7 +135,8 @@ fun LocalPlaylistSongs(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val thumbnailSize = Dimensions.thumbnails.song.px
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
ReorderingLazyColumn(
|
ReorderingLazyColumn(
|
||||||
@@ -224,21 +233,8 @@ fun LocalPlaylistSongs(
|
|||||||
) { index, song ->
|
) { index, song ->
|
||||||
SongItem(
|
SongItem(
|
||||||
song = song,
|
song = song,
|
||||||
thumbnailSizePx = thumbnailSize,
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
onClick = {
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
playlistWithSongs?.songs?.map(DetailedSong::asMediaItem)
|
|
||||||
?.let { mediaItems ->
|
|
||||||
binder?.stopRadio()
|
|
||||||
binder?.player?.forcePlayAtIndex(mediaItems, index)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
menuContent = {
|
|
||||||
InPlaylistMediaItemMenu(
|
|
||||||
playlistId = playlistId,
|
|
||||||
positionInPlaylist = index,
|
|
||||||
song = song
|
|
||||||
)
|
|
||||||
},
|
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(R.drawable.reorder),
|
painter = painterResource(R.drawable.reorder),
|
||||||
@@ -255,6 +251,26 @@ fun LocalPlaylistSongs(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
indication = rippleIndication,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
InPlaylistMediaItemMenu(
|
||||||
|
playlistId = playlistId,
|
||||||
|
positionInPlaylist = index,
|
||||||
|
song = song
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
playlistWithSongs?.songs?.map(DetailedSong::asMediaItem)
|
||||||
|
?.let { mediaItems ->
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlayAtIndex(mediaItems, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
.animateItemPlacement(reorderingState = reorderingState)
|
.animateItemPlacement(reorderingState = reorderingState)
|
||||||
.draggedItem(reorderingState = reorderingState, index = index)
|
.draggedItem(reorderingState = reorderingState, index = index)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
@@ -51,14 +52,15 @@ import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
|||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
import it.vfsfitvnm.vimusic.ui.components.BottomSheet
|
import it.vfsfitvnm.vimusic.ui.components.BottomSheet
|
||||||
import it.vfsfitvnm.vimusic.ui.components.BottomSheetState
|
import it.vfsfitvnm.vimusic.ui.components.BottomSheetState
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.MusicBars
|
import it.vfsfitvnm.vimusic.ui.components.MusicBars
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.QueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.QueuedMediaItemMenu
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.onOverlay
|
import it.vfsfitvnm.vimusic.ui.styling.onOverlay
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.utils.medium
|
import it.vfsfitvnm.vimusic.utils.medium
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberMediaItemIndex
|
import it.vfsfitvnm.vimusic.utils.rememberMediaItemIndex
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberShouldBePlaying
|
import it.vfsfitvnm.vimusic.utils.rememberShouldBePlaying
|
||||||
@@ -106,7 +108,12 @@ fun PlayerBottomSheet(
|
|||||||
|
|
||||||
binder?.player ?: return@BottomSheet
|
binder?.player ?: return@BottomSheet
|
||||||
|
|
||||||
val thumbnailSize = Dimensions.thumbnails.song.px
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
val mediaItemIndex by rememberMediaItemIndex(binder.player)
|
val mediaItemIndex by rememberMediaItemIndex(binder.player)
|
||||||
val windows by rememberWindows(binder.player)
|
val windows by rememberWindows(binder.player)
|
||||||
@@ -149,26 +156,9 @@ fun PlayerBottomSheet(
|
|||||||
val isPlayingThisMediaItem = mediaItemIndex == window.firstPeriodIndex
|
val isPlayingThisMediaItem = mediaItemIndex == window.firstPeriodIndex
|
||||||
|
|
||||||
SongItem(
|
SongItem(
|
||||||
mediaItem = window.mediaItem,
|
song = window.mediaItem,
|
||||||
thumbnailSizePx = thumbnailSize,
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
onClick = {
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
if (isPlayingThisMediaItem) {
|
|
||||||
if (shouldBePlaying) {
|
|
||||||
binder.player.pause()
|
|
||||||
} else {
|
|
||||||
binder.player.play()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
binder.player.playWhenReady = true
|
|
||||||
binder.player.seekToDefaultPosition(window.firstPeriodIndex)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
menuContent = {
|
|
||||||
QueuedMediaItemMenu(
|
|
||||||
mediaItem = window.mediaItem,
|
|
||||||
indexInQueue = if (isPlayingThisMediaItem) null else window.firstPeriodIndex
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onThumbnailContent = {
|
onThumbnailContent = {
|
||||||
androidx.compose.animation.AnimatedVisibility(
|
androidx.compose.animation.AnimatedVisibility(
|
||||||
visible = isPlayingThisMediaItem,
|
visible = isPlayingThisMediaItem,
|
||||||
@@ -218,11 +208,32 @@ fun PlayerBottomSheet(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.animateItemPlacement(reorderingState)
|
.combinedClickable(
|
||||||
.draggedItem(
|
indication = rippleIndication,
|
||||||
reorderingState = reorderingState,
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
index = window.firstPeriodIndex
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
QueuedMediaItemMenu(
|
||||||
|
mediaItem = window.mediaItem,
|
||||||
|
indexInQueue = if (isPlayingThisMediaItem) null else window.firstPeriodIndex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
if (isPlayingThisMediaItem) {
|
||||||
|
if (shouldBePlaying) {
|
||||||
|
binder.player.pause()
|
||||||
|
} else {
|
||||||
|
binder.player.play()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binder.player.playWhenReady = true
|
||||||
|
binder.player.seekToDefaultPosition(window.firstPeriodIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
.animateItemPlacement(reorderingState = reorderingState)
|
||||||
|
.draggedItem(reorderingState = reorderingState, index = window.firstPeriodIndex)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package it.vfsfitvnm.vimusic.ui.screens.playlist
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
@@ -19,15 +22,16 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.autoSaver
|
import androidx.compose.runtime.saveable.autoSaver
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -42,17 +46,18 @@ import it.vfsfitvnm.vimusic.models.SongPlaylistMap
|
|||||||
import it.vfsfitvnm.vimusic.savers.InnertubePlaylistOrAlbumPageSaver
|
import it.vfsfitvnm.vimusic.savers.InnertubePlaylistOrAlbumPageSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.resultSaver
|
import it.vfsfitvnm.vimusic.savers.resultSaver
|
||||||
import it.vfsfitvnm.vimusic.transaction
|
import it.vfsfitvnm.vimusic.transaction
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.HeaderPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.PrimaryButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.center
|
import it.vfsfitvnm.vimusic.utils.center
|
||||||
import it.vfsfitvnm.vimusic.utils.completed
|
import it.vfsfitvnm.vimusic.utils.completed
|
||||||
@@ -68,6 +73,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun PlaylistSongList(
|
fun PlaylistSongList(
|
||||||
@@ -76,6 +82,9 @@ fun PlaylistSongList(
|
|||||||
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
val playlistPageResult by produceSaveableState(
|
val playlistPageResult by produceSaveableState(
|
||||||
initialValue = null,
|
initialValue = null,
|
||||||
@@ -201,28 +210,25 @@ fun PlaylistSongList(
|
|||||||
|
|
||||||
itemsIndexed(items = playlist.songsPage?.items ?: emptyList()) { index, song ->
|
itemsIndexed(items = playlist.songsPage?.items ?: emptyList()) { index, song ->
|
||||||
SongItem(
|
SongItem(
|
||||||
title = song.info?.name,
|
song = song,
|
||||||
authors = song.authors?.joinToString("") { it.name ?: "" },
|
thumbnailSizePx = songThumbnailSizePx,
|
||||||
durationText = song.durationText,
|
thumbnailSizeDp = songThumbnailSizeDp,
|
||||||
onClick = {
|
modifier = Modifier
|
||||||
playlist.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
|
.combinedClickable(
|
||||||
binder?.stopRadio()
|
indication = rippleIndication,
|
||||||
binder?.player?.forcePlayAtIndex(mediaItems, index)
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
}
|
onLongClick = {
|
||||||
},
|
menuState.display {
|
||||||
startContent = {
|
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
||||||
AsyncImage(
|
}
|
||||||
model = song.thumbnail?.size(songThumbnailSizePx),
|
},
|
||||||
contentDescription = null,
|
onClick = {
|
||||||
contentScale = ContentScale.Crop,
|
playlist.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
|
||||||
modifier = Modifier
|
binder?.stopRadio()
|
||||||
.clip(thumbnailShape)
|
binder?.player?.forcePlayAtIndex(mediaItems, index)
|
||||||
.size(Dimensions.thumbnails.song)
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
|
||||||
menuContent = {
|
|
||||||
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ package it.vfsfitvnm.vimusic.ui.screens.search
|
|||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
@@ -19,13 +23,14 @@ import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
|||||||
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.utils.align
|
import it.vfsfitvnm.vimusic.utils.align
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlay
|
import it.vfsfitvnm.vimusic.utils.forcePlay
|
||||||
@@ -45,6 +50,9 @@ fun LocalSongSearch(
|
|||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
|
|
||||||
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
val items by produceSaveableState(
|
val items by produceSaveableState(
|
||||||
initialValue = emptyList(),
|
initialValue = emptyList(),
|
||||||
@@ -59,7 +67,8 @@ fun LocalSongSearch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val thumbnailSize = Dimensions.thumbnails.song.px
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = LocalPlayerAwarePaddingValues.current,
|
contentPadding = LocalPlayerAwarePaddingValues.current,
|
||||||
@@ -100,17 +109,26 @@ fun LocalSongSearch(
|
|||||||
) { song ->
|
) { song ->
|
||||||
SongItem(
|
SongItem(
|
||||||
song = song,
|
song = song,
|
||||||
thumbnailSizePx = thumbnailSize,
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
onClick = {
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
val mediaItem = song.asMediaItem
|
|
||||||
binder?.stopRadio()
|
|
||||||
binder?.player?.forcePlay(mediaItem)
|
|
||||||
binder?.setupRadio(
|
|
||||||
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
menuContent = { InHistoryMediaItemMenu(song = song) },
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
indication = rippleIndication,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
InHistoryMediaItemMenu(song = song)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
val mediaItem = song.asMediaItem
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlay(mediaItem)
|
||||||
|
binder?.setupRadio(
|
||||||
|
NavigationEndpoint.Endpoint.Watch(videoId = mediaItem.mediaId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
.animateItemPlacement()
|
.animateItemPlacement()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package it.vfsfitvnm.vimusic.ui.screens.searchresult
|
|||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
@@ -21,24 +22,26 @@ import it.vfsfitvnm.vimusic.savers.InnertubePlaylistItemListSaver
|
|||||||
import it.vfsfitvnm.vimusic.savers.InnertubeSongsPageSaver
|
import it.vfsfitvnm.vimusic.savers.InnertubeSongsPageSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.InnertubeVideoItemListSaver
|
import it.vfsfitvnm.vimusic.savers.InnertubeVideoItemListSaver
|
||||||
import it.vfsfitvnm.vimusic.savers.innertubeItemsPageSaver
|
import it.vfsfitvnm.vimusic.savers.innertubeItemsPageSaver
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
|
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.AlbumItemPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.ArtistItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.ArtistItemPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.PlaylistItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.PlaylistItemPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.SongItemPlaceholder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.VideoItem
|
||||||
|
import it.vfsfitvnm.vimusic.ui.items.VideoItemPlaceholder
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.albumRoute
|
import it.vfsfitvnm.vimusic.ui.screens.albumRoute
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.artistRoute
|
import it.vfsfitvnm.vimusic.ui.screens.artistRoute
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.playlistRoute
|
import it.vfsfitvnm.vimusic.ui.screens.playlistRoute
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.px
|
import it.vfsfitvnm.vimusic.ui.styling.px
|
||||||
import it.vfsfitvnm.vimusic.ui.views.AlbumItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.AlbumItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.ArtistItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.ArtistItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.PlaylistItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.PlaylistItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.SongItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.VideoItem
|
|
||||||
import it.vfsfitvnm.vimusic.ui.views.VideoItemPlaceholder
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlay
|
import it.vfsfitvnm.vimusic.utils.forcePlay
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
||||||
@@ -74,6 +77,8 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
|||||||
|
|
||||||
val emptyItemsText = "No results found. Please try a different query or category"
|
val emptyItemsText = "No results found. Please try a different query or category"
|
||||||
|
|
||||||
|
val rippleIndication = rememberRipple(bounded = true)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topIconButtonId = R.drawable.chevron_back,
|
topIconButtonId = R.drawable.chevron_back,
|
||||||
onTopIconButtonClick = pop,
|
onTopIconButtonClick = pop,
|
||||||
@@ -92,6 +97,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
|||||||
when (tabIndex) {
|
when (tabIndex) {
|
||||||
0 -> {
|
0 -> {
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
val thumbnailSizeDp = Dimensions.thumbnails.song
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
val thumbnailSizePx = thumbnailSizeDp.px
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
@@ -116,11 +122,22 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
|||||||
SongItem(
|
SongItem(
|
||||||
song = song,
|
song = song,
|
||||||
thumbnailSizePx = thumbnailSizePx,
|
thumbnailSizePx = thumbnailSizePx,
|
||||||
onClick = {
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
binder?.stopRadio()
|
modifier = Modifier
|
||||||
binder?.player?.forcePlay(song.asMediaItem)
|
.combinedClickable(
|
||||||
binder?.setupRadio(song.info?.endpoint)
|
indication = rippleIndication,
|
||||||
}
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlay(song.asMediaItem)
|
||||||
|
binder?.setupRadio(song.info?.endpoint)
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
itemPlaceholderContent = {
|
itemPlaceholderContent = {
|
||||||
@@ -157,7 +174,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
|||||||
thumbnailSizeDp = thumbnailSizeDp,
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = rememberRipple(bounded = true),
|
indication = rippleIndication,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
onClick = { albumRoute(album.info?.endpoint?.browseId) }
|
onClick = { albumRoute(album.info?.endpoint?.browseId) }
|
||||||
)
|
)
|
||||||
@@ -198,7 +215,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
|||||||
thumbnailSizeDp = thumbnailSizeDp,
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = rememberRipple(bounded = true),
|
indication = rippleIndication,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
onClick = { artistRoute(artist.info?.endpoint?.browseId) }
|
onClick = { artistRoute(artist.info?.endpoint?.browseId) }
|
||||||
)
|
)
|
||||||
@@ -209,8 +226,10 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
3 -> {
|
3 -> {
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
val menuState = LocalMenuState.current
|
||||||
val thumbnailHeightDp = 72.dp
|
val thumbnailHeightDp = 72.dp
|
||||||
val thumbnailWidthDp = 128.dp
|
val thumbnailWidthDp = 128.dp
|
||||||
|
|
||||||
@@ -236,11 +255,21 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
|||||||
video = video,
|
video = video,
|
||||||
thumbnailWidthDp = thumbnailWidthDp,
|
thumbnailWidthDp = thumbnailWidthDp,
|
||||||
thumbnailHeightDp = thumbnailHeightDp,
|
thumbnailHeightDp = thumbnailHeightDp,
|
||||||
onClick = {
|
modifier = Modifier
|
||||||
binder?.stopRadio()
|
.combinedClickable(
|
||||||
binder?.player?.forcePlay(video.asMediaItem)
|
indication = rippleIndication,
|
||||||
binder?.setupRadio(video.info?.endpoint)
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
}
|
onLongClick = {
|
||||||
|
menuState.display {
|
||||||
|
NonQueuedMediaItemMenu(mediaItem = video.asMediaItem)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
binder?.stopRadio()
|
||||||
|
binder?.player?.forcePlay(video.asMediaItem)
|
||||||
|
binder?.setupRadio(video.info?.endpoint)
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
itemPlaceholderContent = {
|
itemPlaceholderContent = {
|
||||||
@@ -286,7 +315,7 @@ fun SearchResultScreen(query: String, onSearchAgain: () -> Unit) {
|
|||||||
thumbnailSizeDp = thumbnailSizeDp,
|
thumbnailSizeDp = thumbnailSizeDp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(
|
.clickable(
|
||||||
indication = rememberRipple(bounded = true),
|
indication = rippleIndication,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
onClick = { playlistRoute(playlist.info?.endpoint?.browseId) }
|
onClick = { playlistRoute(playlist.info?.endpoint?.browseId) }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,506 +0,0 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.views
|
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.text.BasicText
|
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
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.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.NonQueuedMediaItemMenu
|
|
||||||
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.onOverlay
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.overlay
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.shimmer
|
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
|
||||||
import it.vfsfitvnm.vimusic.utils.color
|
|
||||||
import it.vfsfitvnm.vimusic.utils.medium
|
|
||||||
import it.vfsfitvnm.vimusic.utils.secondary
|
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
|
||||||
import it.vfsfitvnm.youtubemusic.Innertube
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun SongItem(
|
|
||||||
song: Innertube.SongItem,
|
|
||||||
thumbnailSizePx: Int,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
SongItem(
|
|
||||||
thumbnailModel = song.thumbnail?.size(thumbnailSizePx),
|
|
||||||
title = song.info?.name,
|
|
||||||
authors = song.authors?.joinToString("") { it.name ?: "" },
|
|
||||||
durationText = song.durationText,
|
|
||||||
onClick = onClick,
|
|
||||||
menuContent = {
|
|
||||||
NonQueuedMediaItemMenu(mediaItem = song.asMediaItem)
|
|
||||||
},
|
|
||||||
modifier = modifier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SongItemPlaceholder(
|
|
||||||
thumbnailSizeDp: Dp,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp, vertical = Dimensions.itemsVerticalPadding)
|
|
||||||
) {
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Column {
|
|
||||||
TextPlaceholder()
|
|
||||||
TextPlaceholder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalFoundationApi
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun VideoItem(
|
|
||||||
video: Innertube.VideoItem,
|
|
||||||
thumbnailHeightDp: Dp,
|
|
||||||
thumbnailWidthDp: Dp,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
val menuState = LocalMenuState.current
|
|
||||||
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
|
||||||
|
|
||||||
ItemContainer(
|
|
||||||
alternative = false,
|
|
||||||
thumbnailSizeDp = 0.dp,
|
|
||||||
modifier = modifier
|
|
||||||
.combinedClickable(
|
|
||||||
indication = rememberRipple(bounded = true),
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
onLongClick = {
|
|
||||||
menuState.display {
|
|
||||||
NonQueuedMediaItemMenu(mediaItem = video.asMediaItem)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick = onClick
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Box {
|
|
||||||
AsyncImage(
|
|
||||||
model = video.thumbnail?.url,
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(thumbnailShape)
|
|
||||||
.size(width = thumbnailWidthDp, height = thumbnailHeightDp)
|
|
||||||
)
|
|
||||||
|
|
||||||
video.durationText?.let { durationText ->
|
|
||||||
BasicText(
|
|
||||||
text = durationText,
|
|
||||||
style = typography.xxs.medium.color(colorPalette.onOverlay),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(all = 4.dp)
|
|
||||||
.background(color = colorPalette.overlay, shape = RoundedCornerShape(2.dp))
|
|
||||||
.padding(horizontal = 4.dp, vertical = 2.dp)
|
|
||||||
.align(Alignment.BottomEnd)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemInfoContainer {
|
|
||||||
BasicText(
|
|
||||||
text = video.info?.name ?: "",
|
|
||||||
style = typography.xs.semiBold,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = video.authors?.joinToString("") { it.name ?: "" } ?: "",
|
|
||||||
style = typography.xs.semiBold.secondary,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
|
|
||||||
video.viewsText?.let { viewsText ->
|
|
||||||
BasicText(
|
|
||||||
text = viewsText,
|
|
||||||
style = typography.xxs.medium.secondary,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalFoundationApi
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun VideoItemPlaceholder(
|
|
||||||
thumbnailHeightDp: Dp,
|
|
||||||
thumbnailWidthDp: Dp,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
|
||||||
|
|
||||||
ItemContainer(
|
|
||||||
alternative = false,
|
|
||||||
thumbnailSizeDp = 0.dp,
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
|
||||||
.size(width = thumbnailWidthDp, height = thumbnailHeightDp)
|
|
||||||
)
|
|
||||||
|
|
||||||
ItemInfoContainer {
|
|
||||||
TextPlaceholder()
|
|
||||||
TextPlaceholder()
|
|
||||||
TextPlaceholder(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PlaylistItem(
|
|
||||||
playlist: Innertube.PlaylistItem,
|
|
||||||
thumbnailSizePx: Int,
|
|
||||||
thumbnailSizeDp: Dp,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
alternative: Boolean = false,
|
|
||||||
) {
|
|
||||||
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
|
||||||
|
|
||||||
ItemContainer(
|
|
||||||
alternative = alternative,
|
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
Box {
|
|
||||||
AsyncImage(
|
|
||||||
model = playlist.thumbnail?.size(thumbnailSizePx),
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(thumbnailShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
)
|
|
||||||
|
|
||||||
playlist.songCount?.let { songCount ->
|
|
||||||
BasicText(
|
|
||||||
text = "$songCount",
|
|
||||||
style = typography.xxs.medium.color(colorPalette.onOverlay),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(all = 4.dp)
|
|
||||||
.background(color = colorPalette.overlay, shape = RoundedCornerShape(2.dp))
|
|
||||||
.padding(horizontal = 4.dp, vertical = 2.dp)
|
|
||||||
.align(Alignment.BottomEnd)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemInfoContainer {
|
|
||||||
BasicText(
|
|
||||||
text = playlist.info?.name ?: "",
|
|
||||||
style = typography.xs.semiBold,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = playlist.channel?.name ?: "",
|
|
||||||
style = typography.xs.semiBold.secondary,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PlaylistItemPlaceholder(
|
|
||||||
thumbnailSizeDp: Dp,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
alternative: Boolean = false,
|
|
||||||
) {
|
|
||||||
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
|
||||||
|
|
||||||
ItemContainer(
|
|
||||||
alternative = alternative,
|
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
)
|
|
||||||
|
|
||||||
ItemInfoContainer {
|
|
||||||
TextPlaceholder()
|
|
||||||
TextPlaceholder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AlbumItem(
|
|
||||||
album: Innertube.AlbumItem,
|
|
||||||
thumbnailSizePx: Int,
|
|
||||||
thumbnailSizeDp: Dp,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
alternative: Boolean = false
|
|
||||||
) {
|
|
||||||
val (_, typography, thumbnailShape) = LocalAppearance.current
|
|
||||||
|
|
||||||
ItemContainer(
|
|
||||||
alternative = alternative,
|
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
AsyncImage(
|
|
||||||
model = album.thumbnail?.size(thumbnailSizePx),
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(thumbnailShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
)
|
|
||||||
|
|
||||||
ItemInfoContainer {
|
|
||||||
BasicText(
|
|
||||||
text = album.info?.name ?: "",
|
|
||||||
style = typography.xs.semiBold,
|
|
||||||
maxLines = if (alternative) 1 else 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!alternative) {
|
|
||||||
album.authors?.joinToString("") { it.name ?: "" }?.let { authorsText ->
|
|
||||||
BasicText(
|
|
||||||
text = authorsText,
|
|
||||||
style = typography.xs.semiBold.secondary,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = album.year ?: "",
|
|
||||||
style = typography.xxs.semiBold.secondary,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AlbumItemPlaceholder(
|
|
||||||
thumbnailSizeDp: Dp,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
alternative: Boolean = false
|
|
||||||
) {
|
|
||||||
val (colorPalette, _, thumbnailShape) = LocalAppearance.current
|
|
||||||
|
|
||||||
ItemContainer(
|
|
||||||
alternative = alternative,
|
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(color = colorPalette.shimmer, shape = thumbnailShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
)
|
|
||||||
|
|
||||||
ItemInfoContainer {
|
|
||||||
TextPlaceholder()
|
|
||||||
|
|
||||||
if (!alternative) {
|
|
||||||
TextPlaceholder()
|
|
||||||
}
|
|
||||||
|
|
||||||
TextPlaceholder(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ArtistItem(
|
|
||||||
artist: Innertube.ArtistItem,
|
|
||||||
thumbnailSizePx: Int,
|
|
||||||
thumbnailSizeDp: Dp,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
alternative: Boolean = false,
|
|
||||||
) {
|
|
||||||
val (_, typography) = LocalAppearance.current
|
|
||||||
|
|
||||||
ItemContainer(
|
|
||||||
alternative = alternative,
|
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
AsyncImage(
|
|
||||||
model = artist.thumbnail?.size(thumbnailSizePx),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(CircleShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
)
|
|
||||||
|
|
||||||
ItemInfoContainer(
|
|
||||||
horizontalAlignment = if (alternative) Alignment.CenterHorizontally else Alignment.Start,
|
|
||||||
) {
|
|
||||||
BasicText(
|
|
||||||
text = artist.info?.name ?: "",
|
|
||||||
style = typography.xs.semiBold,
|
|
||||||
maxLines = if (alternative) 1 else 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
|
|
||||||
artist.subscribersCountText?.let { subscribersCountText ->
|
|
||||||
BasicText(
|
|
||||||
text = subscribersCountText,
|
|
||||||
style = typography.xxs.semiBold.secondary,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ArtistItemPlaceholder(
|
|
||||||
thumbnailSizeDp: Dp,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
alternative: Boolean = false,
|
|
||||||
) {
|
|
||||||
val (colorPalette) = LocalAppearance.current
|
|
||||||
|
|
||||||
ItemContainer(
|
|
||||||
alternative = alternative,
|
|
||||||
thumbnailSizeDp = thumbnailSizeDp,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(color = colorPalette.shimmer, shape = CircleShape)
|
|
||||||
.size(thumbnailSizeDp)
|
|
||||||
)
|
|
||||||
|
|
||||||
ItemInfoContainer(
|
|
||||||
horizontalAlignment = if (alternative) Alignment.CenterHorizontally else Alignment.Start,
|
|
||||||
) {
|
|
||||||
TextPlaceholder()
|
|
||||||
TextPlaceholder(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(top = 4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private inline fun ItemContainer(
|
|
||||||
alternative: Boolean,
|
|
||||||
thumbnailSizeDp: Dp,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
|
||||||
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically,
|
|
||||||
content: @Composable () -> Unit
|
|
||||||
) {
|
|
||||||
if (alternative) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = horizontalAlignment,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
modifier = modifier
|
|
||||||
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
|
||||||
.width(thumbnailSizeDp)
|
|
||||||
) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = verticalAlignment,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
modifier = modifier
|
|
||||||
.padding(vertical = Dimensions.itemsVerticalPadding, horizontal = 16.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private inline fun ItemInfoContainer(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
|
||||||
content: @Composable ColumnScope.() -> Unit
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = horizontalAlignment,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
modifier = modifier,
|
|
||||||
content = content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.views
|
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.requiredSize
|
|
||||||
import androidx.compose.foundation.layout.requiredWidth
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.text.BasicText
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
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.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import it.vfsfitvnm.vimusic.Database
|
|
||||||
import it.vfsfitvnm.vimusic.models.PlaylistPreview
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
|
||||||
import it.vfsfitvnm.vimusic.utils.center
|
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PlaylistPreviewItem(
|
|
||||||
playlistPreview: PlaylistPreview,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
thumbnailSize: Dp = Dimensions.thumbnails.song
|
|
||||||
) {
|
|
||||||
val density = LocalDensity.current
|
|
||||||
val (_, _, thumbnailShape) = LocalAppearance.current
|
|
||||||
|
|
||||||
val thumbnailSizePx = with(density) {
|
|
||||||
thumbnailSize.roundToPx()
|
|
||||||
}
|
|
||||||
|
|
||||||
val thumbnails by remember(playlistPreview.playlist.id) {
|
|
||||||
Database.playlistThumbnailUrls(playlistPreview.playlist.id).distinctUntilChanged().map {
|
|
||||||
it.map { url ->
|
|
||||||
url.thumbnail(thumbnailSizePx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.collectAsState(initial = emptyList(), context = Dispatchers.IO)
|
|
||||||
|
|
||||||
PlaylistItem(
|
|
||||||
name = playlistPreview.playlist.name,
|
|
||||||
thumbnailSize = thumbnailSize,
|
|
||||||
imageContent = {
|
|
||||||
if (thumbnails.toSet().size == 1) {
|
|
||||||
AsyncImage(
|
|
||||||
model = thumbnails.first().thumbnail(thumbnailSizePx * 2),
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(thumbnailShape)
|
|
||||||
.fillMaxSize()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
listOf(
|
|
||||||
Alignment.TopStart,
|
|
||||||
Alignment.TopEnd,
|
|
||||||
Alignment.BottomStart,
|
|
||||||
Alignment.BottomEnd
|
|
||||||
).forEachIndexed { index, alignment ->
|
|
||||||
AsyncImage(
|
|
||||||
model = thumbnails.getOrNull(index),
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(thumbnailShape)
|
|
||||||
.align(alignment)
|
|
||||||
.size(thumbnailSize)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = modifier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BuiltInPlaylistItem(
|
|
||||||
@DrawableRes icon: Int,
|
|
||||||
colorTint: Color,
|
|
||||||
name: String,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
thumbnailSize: Dp = Dimensions.thumbnails.song
|
|
||||||
) {
|
|
||||||
PlaylistItem(
|
|
||||||
name = name,
|
|
||||||
thumbnailSize = thumbnailSize,
|
|
||||||
imageContent = {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(icon),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorTint),
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.Center)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PlaylistItem(
|
|
||||||
name: String,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
thumbnailSize: Dp = Dimensions.thumbnails.song,
|
|
||||||
imageContent: @Composable BoxScope.() -> Unit
|
|
||||||
) {
|
|
||||||
val (colorPalette, typography, thumbnailShape) = LocalAppearance.current
|
|
||||||
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
modifier = modifier
|
|
||||||
.requiredWidth(thumbnailSize * 2)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(thumbnailShape)
|
|
||||||
.background(colorPalette.background1)
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
.requiredSize(thumbnailSize * 2),
|
|
||||||
content = imageContent
|
|
||||||
)
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = name,
|
|
||||||
style = typography.xxs.semiBold.center,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.views
|
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.combinedClickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
|
||||||
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.size
|
|
||||||
import androidx.compose.foundation.text.BasicText
|
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.NonRestartableComposable
|
|
||||||
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.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
|
||||||
import it.vfsfitvnm.vimusic.utils.secondary
|
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnail
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
@NonRestartableComposable
|
|
||||||
fun SongItem(
|
|
||||||
mediaItem: MediaItem,
|
|
||||||
thumbnailSizePx: Int,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
menuContent: @Composable () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onThumbnailContent: (@Composable BoxScope.() -> Unit)? = null,
|
|
||||||
trailingContent: (@Composable () -> Unit)? = null
|
|
||||||
) {
|
|
||||||
SongItem(
|
|
||||||
thumbnailModel = mediaItem.mediaMetadata.artworkUri.thumbnail(thumbnailSizePx),
|
|
||||||
title = mediaItem.mediaMetadata.title.toString(),
|
|
||||||
authors = mediaItem.mediaMetadata.artist.toString(),
|
|
||||||
durationText = mediaItem.mediaMetadata.extras?.getString("durationText"),
|
|
||||||
menuContent = menuContent,
|
|
||||||
onClick = onClick,
|
|
||||||
onThumbnailContent = onThumbnailContent,
|
|
||||||
trailingContent = trailingContent,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
@NonRestartableComposable
|
|
||||||
fun SongItem(
|
|
||||||
song: DetailedSong,
|
|
||||||
thumbnailSizePx: Int,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
menuContent: @Composable () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onThumbnailContent: (@Composable BoxScope.() -> Unit)? = null,
|
|
||||||
trailingContent: (@Composable () -> Unit)? = null
|
|
||||||
) {
|
|
||||||
SongItem(
|
|
||||||
thumbnailModel = song.thumbnailUrl?.thumbnail(thumbnailSizePx),
|
|
||||||
title = song.title,
|
|
||||||
authors = song.artistsText,
|
|
||||||
durationText = song.durationText,
|
|
||||||
menuContent = menuContent,
|
|
||||||
onClick = onClick,
|
|
||||||
onThumbnailContent = onThumbnailContent,
|
|
||||||
trailingContent = trailingContent,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
@NonRestartableComposable
|
|
||||||
fun SongItem(
|
|
||||||
thumbnailModel: Any?,
|
|
||||||
title: String?,
|
|
||||||
authors: String?,
|
|
||||||
durationText: String?,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
menuContent: @Composable () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onThumbnailContent: (@Composable BoxScope.() -> Unit)? = null,
|
|
||||||
trailingContent: (@Composable () -> Unit)? = null
|
|
||||||
) {
|
|
||||||
SongItem(
|
|
||||||
title = title,
|
|
||||||
authors = authors,
|
|
||||||
durationText = durationText,
|
|
||||||
onClick = onClick,
|
|
||||||
startContent = {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(Dimensions.thumbnails.song)
|
|
||||||
) {
|
|
||||||
AsyncImage(
|
|
||||||
model = thumbnailModel,
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(LocalAppearance.current.thumbnailShape)
|
|
||||||
.fillMaxSize()
|
|
||||||
)
|
|
||||||
|
|
||||||
onThumbnailContent?.invoke(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
menuContent = menuContent,
|
|
||||||
trailingContent = trailingContent,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun SongItem(
|
|
||||||
title: String?,
|
|
||||||
authors: String?,
|
|
||||||
durationText: String?,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
startContent: @Composable () -> Unit,
|
|
||||||
menuContent: @Composable () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
trailingContent: (@Composable () -> Unit)? = null
|
|
||||||
) {
|
|
||||||
val menuState = LocalMenuState.current
|
|
||||||
val (_, typography) = LocalAppearance.current
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
modifier = modifier
|
|
||||||
.combinedClickable(
|
|
||||||
indication = rememberRipple(bounded = true),
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
onLongClick = { menuState.display(menuContent) },
|
|
||||||
onClick = onClick
|
|
||||||
)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = Dimensions.itemsVerticalPadding)
|
|
||||||
.padding(start = 16.dp, end = if (trailingContent == null) 16.dp else 8.dp)
|
|
||||||
.height(Dimensions.thumbnails.song)
|
|
||||||
) {
|
|
||||||
startContent()
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
BasicText(
|
|
||||||
text = title ?: "",
|
|
||||||
style = typography.xs.semiBold,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
BasicText(
|
|
||||||
text = authors ?: "",
|
|
||||||
style = typography.xs.semiBold.secondary,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
durationText?.let {
|
|
||||||
BasicText(
|
|
||||||
text = durationText,
|
|
||||||
style = typography.xxs.secondary,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
trailingContent?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user