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