Tweak code

This commit is contained in:
vfsfitvnm
2022-10-04 11:37:10 +02:00
parent b7046e3217
commit d2ce356d10
25 changed files with 1468 additions and 1210 deletions

View 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)
)
}
}
}

View 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)
)
}
}
}

View File

@@ -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
)
}

View File

@@ -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()
}
}
}

View 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()
}
}
}

View 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)
)
}
}
}

View File

@@ -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

View File

@@ -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)
}
)
) )
} }

View File

@@ -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") {

View File

@@ -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)
)
}
) )
}
) )
} }
} }

View File

@@ -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 = {

View File

@@ -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()
) )
} }

View File

@@ -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)
)
}
}
}
} }
} }
} }

View File

@@ -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
)
}
} }
} }
} }

View File

@@ -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),

View File

@@ -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()
) )
} }

View File

@@ -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)
) )
} }

View File

@@ -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)
) )

View File

@@ -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)
) )
} }

View File

@@ -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)
}
) )
} }
} }

View File

@@ -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()
) )
} }

View File

@@ -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) }
) )

View File

@@ -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
)
}

View File

@@ -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
)
}
}

View File

@@ -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()
}
}