Improve MediaItemMenu UI

This commit is contained in:
vfsfitvnm 2022-10-07 12:08:52 +02:00
parent 3364bb9f30
commit b2ad011cfd
13 changed files with 440 additions and 431 deletions

View file

@ -60,7 +60,8 @@ fun BoxScope.FloatingActionsContainerWithScrollToTop(
) {
val transitionState = remember {
MutableTransitionState<ScrollingInfo?>(ScrollingInfo())
}.apply { targetState = if (visible) lazyListState.scrollingInfo() else null }
}.apply { targetState = lazyListState.scrollingInfo() }
// }.apply { targetState = if (visible) lazyListState.scrollingInfo() else null }
FloatingActions(
transitionState = transitionState,
@ -125,7 +126,7 @@ fun BoxScope.FloatingActions(
onScrollToTop()
}
},
enabled = transition.targetState?.isScrollingDown == false && transition.targetState?.isFar == true,
// enabled = transition.targetState?.isScrollingDown == false && transition.targetState?.isFar == true,
iconId = R.drawable.chevron_up,
modifier = Modifier
.padding(bottom = 16.dp)

View file

@ -2,20 +2,23 @@ package it.vfsfitvnm.vimusic.ui.components.themed
import android.content.Intent
import android.text.format.DateUtils
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.animation.with
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicText
@ -30,11 +33,13 @@ 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.input.pointer.pointerInput
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.media3.common.MediaItem
import it.vfsfitvnm.route.RouteHandler
import it.vfsfitvnm.vimusic.Database
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
import it.vfsfitvnm.vimusic.R
@ -42,13 +47,17 @@ import it.vfsfitvnm.vimusic.enums.PlaylistSortBy
import it.vfsfitvnm.vimusic.enums.SortOrder
import it.vfsfitvnm.vimusic.models.DetailedSong
import it.vfsfitvnm.vimusic.models.Playlist
import it.vfsfitvnm.vimusic.models.Song
import it.vfsfitvnm.vimusic.models.SongPlaylistMap
import it.vfsfitvnm.vimusic.query
import it.vfsfitvnm.vimusic.transaction
import it.vfsfitvnm.vimusic.ui.items.SongItem
import it.vfsfitvnm.vimusic.ui.screens.albumRoute
import it.vfsfitvnm.vimusic.ui.screens.artistRoute
import it.vfsfitvnm.vimusic.ui.screens.viewPlaylistsRoute
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.ui.styling.favoritesIcon
import it.vfsfitvnm.vimusic.ui.styling.px
import it.vfsfitvnm.vimusic.utils.addNext
import it.vfsfitvnm.vimusic.utils.asMediaItem
import it.vfsfitvnm.vimusic.utils.enqueue
@ -56,27 +65,9 @@ import it.vfsfitvnm.vimusic.utils.forcePlay
import it.vfsfitvnm.vimusic.utils.semiBold
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
@ExperimentalAnimationApi
@Composable
fun InFavoritesMediaItemMenu(
onDismiss: () -> Unit,
song: DetailedSong,
modifier: Modifier = Modifier,
) {
NonQueuedMediaItemMenu(
mediaItem = song.asMediaItem,
onDismiss = onDismiss,
onRemoveFromFavorites = {
query {
Database.like(song.id, null)
}
},
modifier = modifier
)
}
@ExperimentalAnimationApi
@Composable
fun InHistoryMediaItemMenu(
@ -143,7 +134,6 @@ fun NonQueuedMediaItemMenu(
modifier: Modifier = Modifier,
onRemoveFromPlaylist: (() -> Unit)? = null,
onHideFromDatabase: (() -> Unit)? = null,
onRemoveFromFavorites: (() -> Unit)? = null,
) {
val binder = LocalPlayerServiceBinder.current
@ -164,7 +154,6 @@ fun NonQueuedMediaItemMenu(
onEnqueue = { binder?.player?.enqueue(mediaItem) },
onRemoveFromPlaylist = onRemoveFromPlaylist,
onHideFromDatabase = onHideFromDatabase,
onRemoveFromFavorites = onRemoveFromFavorites,
modifier = modifier
)
}
@ -203,7 +192,6 @@ fun BaseMediaItemMenu(
onRemoveFromQueue: (() -> Unit)? = null,
onRemoveFromPlaylist: (() -> Unit)? = null,
onHideFromDatabase: (() -> Unit)? = null,
onRemoveFromFavorites: (() -> Unit)? = null
) {
val context = LocalContext.current
@ -228,7 +216,6 @@ fun BaseMediaItemMenu(
}
},
onHideFromDatabase = onHideFromDatabase,
onRemoveFromFavorites = onRemoveFromFavorites,
onRemoveFromPlaylist = onRemoveFromPlaylist,
onRemoveFromQueue = onRemoveFromQueue,
onGoToAlbum = albumRoute::global,
@ -262,385 +249,425 @@ fun MediaItemMenu(
onEnqueue: (() -> Unit)? = null,
onHideFromDatabase: (() -> Unit)? = null,
onRemoveFromQueue: (() -> Unit)? = null,
onRemoveFromFavorites: (() -> Unit)? = null,
onRemoveFromPlaylist: (() -> Unit)? = null,
onAddToPlaylist: ((Playlist, Int) -> Unit)? = null,
onGoToAlbum: ((String) -> Unit)? = null,
onGoToArtist: ((String) -> Unit)? = null,
onShare: (() -> Unit)? = null
onShare: () -> Unit
) {
Menu(modifier = modifier) {
RouteHandler(
transitionSpec = {
when (targetState.route) {
viewPlaylistsRoute -> slideIntoContainer(AnimatedContentScope.SlideDirection.Left) with
slideOutOfContainer(AnimatedContentScope.SlideDirection.Left)
val (colorPalette) = LocalAppearance.current
val density = LocalDensity.current
else -> when (initialState.route) {
viewPlaylistsRoute -> slideIntoContainer(AnimatedContentScope.SlideDirection.Right) with
slideOutOfContainer(AnimatedContentScope.SlideDirection.Right)
var isViewingPlaylists by remember {
mutableStateOf(false)
}
else -> EnterTransition.None with ExitTransition.None
var height by remember {
mutableStateOf(0.dp)
}
val likedAt by remember(mediaItem.mediaId) {
Database.likedAt(mediaItem.mediaId).distinctUntilChanged()
}.collectAsState(initial = null, context = Dispatchers.IO)
AnimatedContent(
targetState = isViewingPlaylists,
transitionSpec = {
val animationSpec = tween<IntOffset>(400)
val slideDirection = if (targetState) AnimatedContentScope.SlideDirection.Left else AnimatedContentScope.SlideDirection.Right
slideIntoContainer(slideDirection, animationSpec) with
slideOutOfContainer(slideDirection, animationSpec)
}
) { currentIsViewingPlaylists ->
if (currentIsViewingPlaylists) {
val playlistPreviews by remember {
Database.playlistPreviews(PlaylistSortBy.DateAdded, SortOrder.Descending)
}.collectAsState(initial = emptyList(), context = Dispatchers.IO)
var isCreatingNewPlaylist by rememberSaveable {
mutableStateOf(false)
}
if (isCreatingNewPlaylist && onAddToPlaylist != null) {
TextFieldDialog(
hintText = "Enter the playlist name",
onDismiss = { isCreatingNewPlaylist = false },
onDone = { text ->
onDismiss()
onAddToPlaylist(Playlist(name = text), 0)
}
)
}
BackHandler {
isViewingPlaylists = false
}
Menu(
modifier = modifier
.requiredHeight(height)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.fillMaxWidth()
) {
IconButton(
onClick = { isViewingPlaylists = false },
icon = R.drawable.chevron_back,
color = colorPalette.textSecondary,
modifier = Modifier
.padding(all = 4.dp)
.size(20.dp)
)
if (onAddToPlaylist != null) {
SecondaryTextButton(
text = "New playlist",
onClick = { isCreatingNewPlaylist = true },
alternative = true
)
}
}
onAddToPlaylist?.let { onAddToPlaylist ->
playlistPreviews.forEach { playlistPreview ->
MenuEntry(
icon = R.drawable.playlist,
text = playlistPreview.playlist.name,
secondaryText = "${playlistPreview.songCount} songs",
onClick = {
onDismiss()
onAddToPlaylist(
playlistPreview.playlist,
playlistPreview.songCount
)
}
)
}
}
}
) {
viewPlaylistsRoute {
val playlistPreviews by remember {
Database.playlistPreviews(PlaylistSortBy.DateAdded, SortOrder.Descending)
}.collectAsState(initial = emptyList(), context = Dispatchers.IO)
} else {
Menu(
modifier = modifier
.onPlaced { height = with(density) { it.size.height.toDp() } }
) {
val thumbnailSizeDp = Dimensions.thumbnails.song
val thumbnailSizePx = thumbnailSizeDp.px
var isCreatingNewPlaylist by rememberSaveable {
mutableStateOf(false)
}
SongItem(
song = mediaItem,
thumbnailSizeDp = thumbnailSizeDp,
thumbnailSizePx = thumbnailSizePx,
trailingContent = {
IconButton(
icon = if (likedAt == null) R.drawable.heart_outline else R.drawable.heart,
color = colorPalette.favoritesIcon,
onClick = {
query {
if (Database.like(
mediaItem.mediaId,
if (likedAt == null) System.currentTimeMillis() else null
) == 0
) {
Database.insert(mediaItem, Song::toggleLike)
}
}
},
modifier = Modifier
.padding(all = 4.dp)
.size(18.dp)
)
},
modifier = Modifier
.clickable(onClick = onShare)
)
if (isCreatingNewPlaylist && onAddToPlaylist != null) {
TextFieldDialog(
hintText = "Enter the playlist name",
onDismiss = {
isCreatingNewPlaylist = false
},
onDone = { text ->
Spacer(
modifier = Modifier
.height(8.dp)
)
Spacer(
modifier = Modifier
.alpha(0.5f)
.align(Alignment.CenterHorizontally)
.background(colorPalette.textDisabled)
.height(1.dp)
.fillMaxWidth(1f)
)
Spacer(
modifier = Modifier
.height(8.dp)
)
onStartRadio?.let { onStartRadio ->
MenuEntry(
icon = R.drawable.radio,
text = "Start radio",
onClick = {
onDismiss()
onAddToPlaylist(Playlist(name = text), 0)
onStartRadio()
}
)
}
Column {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxWidth()
) {
MenuBackButton(onClick = pop)
onPlayNext?.let { onPlayNext ->
MenuEntry(
icon = R.drawable.play_skip_forward,
text = "Play next",
onClick = {
onDismiss()
onPlayNext()
}
)
}
if (onAddToPlaylist != null) {
MenuIconButton(
icon = R.drawable.add,
onClick = {
isCreatingNewPlaylist = true
onEnqueue?.let { onEnqueue ->
MenuEntry(
icon = R.drawable.enqueue,
text = "Enqueue",
onClick = {
onDismiss()
onEnqueue()
}
)
}
onGoToEqualizer?.let { onGoToEqualizer ->
MenuEntry(
icon = R.drawable.equalizer,
text = "Equalizer",
onClick = {
onDismiss()
onGoToEqualizer()
}
)
}
// TODO: find solution to this shit
onShowSleepTimer?.let {
val binder = LocalPlayerServiceBinder.current
val (_, typography) = LocalAppearance.current
var isShowingSleepTimerDialog by remember {
mutableStateOf(false)
}
val sleepTimerMillisLeft by (binder?.sleepTimerMillisLeft
?: flowOf(null))
.collectAsState(initial = null)
if (isShowingSleepTimerDialog) {
if (sleepTimerMillisLeft != null) {
ConfirmationDialog(
text = "Do you want to stop the sleep timer?",
cancelText = "No",
confirmText = "Stop",
onDismiss = {
isShowingSleepTimerDialog = false
onDismiss()
},
onConfirm = {
binder?.cancelSleepTimer()
onDismiss()
}
)
} else {
DefaultDialog(onDismiss = {
isShowingSleepTimerDialog = false
}) {
var amount by remember {
mutableStateOf(1)
}
BasicText(
text = "Set sleep timer",
style = typography.s.semiBold,
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 24.dp)
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(
space = 16.dp,
alignment = Alignment.CenterHorizontally
),
modifier = Modifier
.padding(vertical = 16.dp)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.alpha(if (amount <= 1) 0.5f else 1f)
.clip(CircleShape)
.clickable(enabled = amount > 1) { amount-- }
.size(48.dp)
.background(colorPalette.background0)
) {
BasicText(
text = "-",
style = typography.xs.semiBold
)
}
Box(contentAlignment = Alignment.Center) {
BasicText(
text = "88h 88m",
style = typography.s.semiBold,
modifier = Modifier
.alpha(0f)
)
BasicText(
text = "${amount / 6}h ${(amount % 6) * 10}m",
style = typography.s.semiBold
)
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.alpha(if (amount >= 60) 0.5f else 1f)
.clip(CircleShape)
.clickable(enabled = amount < 60) { amount++ }
.size(48.dp)
.background(colorPalette.background0)
) {
BasicText(
text = "+",
style = typography.xs.semiBold
)
}
}
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier
.fillMaxWidth()
) {
DialogTextButton(
text = "Cancel",
onClick = {
isShowingSleepTimerDialog = false
onDismiss()
}
)
DialogTextButton(
text = "Set",
enabled = amount > 0,
onClick = {
binder?.startSleepTimer(amount * 10 * 60 * 1000L)
isShowingSleepTimerDialog = false
onDismiss()
}
)
}
}
}
}
onAddToPlaylist?.let { onAddToPlaylist ->
if (onRemoveFromFavorites == null) {
MenuEntry(
icon = R.drawable.heart,
text = "Favorites",
onClick = {
onDismiss()
query {
Database.insert(mediaItem)
Database.like(mediaItem.mediaId, System.currentTimeMillis())
}
}
)
MenuEntry(
icon = R.drawable.alarm,
text = "Sleep timer",
secondaryText = sleepTimerMillisLeft?.let {
"${
DateUtils.formatElapsedTime(
it / 1000
)
} left"
},
onClick = {
isShowingSleepTimerDialog = true
}
)
}
playlistPreviews.forEach { playlistPreview ->
MenuEntry(
icon = R.drawable.playlist,
text = playlistPreview.playlist.name,
secondaryText = "${playlistPreview.songCount} songs",
onClick = {
onDismiss()
onAddToPlaylist(
playlistPreview.playlist,
playlistPreview.songCount
)
}
if (onAddToPlaylist != null) {
MenuEntry(
icon = R.drawable.playlist,
text = "Add to playlist",
onClick = { isViewingPlaylists = true },
trailingContent = {
Image(
painter = painterResource(R.drawable.chevron_forward),
contentDescription = null,
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(colorPalette.textSecondary),
modifier = Modifier
.size(16.dp)
)
}
)
}
onGoToAlbum?.let { onGoToAlbum ->
mediaItem.mediaMetadata.extras?.getString("albumId")?.let { albumId ->
MenuEntry(
icon = R.drawable.disc,
text = "Go to album",
onClick = {
onDismiss()
onGoToAlbum(albumId)
}
)
}
}
}
host {
Column(
modifier = Modifier
.pointerInput(Unit) {
detectTapGestures { }
}
) {
onStartRadio?.let { onStartRadio ->
MenuEntry(
icon = R.drawable.radio,
text = "Start radio",
onClick = {
onDismiss()
onStartRadio()
}
)
}
onPlayNext?.let { onPlayNext ->
MenuEntry(
icon = R.drawable.play_skip_forward,
text = "Play next",
onClick = {
onDismiss()
onPlayNext()
}
)
}
onEnqueue?.let { onEnqueue ->
MenuEntry(
icon = R.drawable.enqueue,
text = "Enqueue",
onClick = {
onDismiss()
onEnqueue()
}
)
}
onGoToEqualizer?.let { onGoToEqualizer ->
MenuEntry(
icon = R.drawable.equalizer,
text = "Equalizer",
onClick = {
onDismiss()
onGoToEqualizer()
}
)
}
// TODO: find solution to this shit
onShowSleepTimer?.let {
val binder = LocalPlayerServiceBinder.current
val (colorPalette, typography) = LocalAppearance.current
var isShowingSleepTimerDialog by remember {
mutableStateOf(false)
}
val sleepTimerMillisLeft by (binder?.sleepTimerMillisLeft ?: flowOf(null))
.collectAsState(initial = null)
if (isShowingSleepTimerDialog) {
if (sleepTimerMillisLeft != null) {
ConfirmationDialog(
text = "Do you want to stop the sleep timer?",
cancelText = "No",
confirmText = "Stop",
onDismiss = {
isShowingSleepTimerDialog = false
onDismiss()
},
onConfirm = {
binder?.cancelSleepTimer()
onDismiss()
}
)
} else {
DefaultDialog(onDismiss = { isShowingSleepTimerDialog = false }) {
var amount by remember {
mutableStateOf(1)
}
BasicText(
text = "Set sleep timer",
style = typography.s.semiBold,
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 24.dp)
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(
space = 16.dp,
alignment = Alignment.CenterHorizontally
),
modifier = Modifier
.padding(vertical = 16.dp)
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.alpha(if (amount <= 1) 0.5f else 1f)
.clip(CircleShape)
.clickable(enabled = amount > 1) { amount-- }
.size(48.dp)
.background(colorPalette.background0)
) {
BasicText(
text = "-",
style = typography.xs.semiBold
)
}
Box(contentAlignment = Alignment.Center) {
BasicText(
text = "88h 88m",
style = typography.s.semiBold,
modifier = Modifier
.alpha(0f)
)
BasicText(
text = "${amount / 6}h ${(amount % 6) * 10}m",
style = typography.s.semiBold
)
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.alpha(if (amount >= 60) 0.5f else 1f)
.clip(CircleShape)
.clickable(enabled = amount < 60) { amount++ }
.size(48.dp)
.background(colorPalette.background0)
) {
BasicText(
text = "+",
style = typography.xs.semiBold
)
}
}
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier
.fillMaxWidth()
) {
DialogTextButton(
text = "Cancel",
onClick = {
isShowingSleepTimerDialog = false
onDismiss()
onGoToArtist?.let { onGoToArtist ->
mediaItem.mediaMetadata.extras?.getStringArrayList("artistNames")
?.let { artistNames ->
mediaItem.mediaMetadata.extras?.getStringArrayList("artistIds")
?.let { artistIds ->
artistNames.zip(artistIds)
.forEach { (authorName, authorId) ->
if (authorId != null) {
MenuEntry(
icon = R.drawable.person,
text = "More of $authorName",
onClick = {
onDismiss()
onGoToArtist(authorId)
}
)
}
)
DialogTextButton(
text = "Set",
enabled = amount > 0,
onClick = {
binder?.startSleepTimer(amount * 10 * 60 * 1000L)
isShowingSleepTimerDialog = false
onDismiss()
}
)
}
}
}
}
}
}
MenuEntry(
icon = R.drawable.alarm,
text = "Sleep timer",
secondaryText = sleepTimerMillisLeft?.let {
"${
DateUtils.formatElapsedTime(
it / 1000
)
} left"
},
onClick = {
isShowingSleepTimerDialog = true
}
)
}
if (onAddToPlaylist != null) {
MenuEntry(
icon = R.drawable.playlist,
text = "Add to playlist or favorites",
onClick = {
viewPlaylistsRoute()
}
)
}
onGoToAlbum?.let { onGoToAlbum ->
mediaItem.mediaMetadata.extras?.getString("albumId")?.let { albumId ->
MenuEntry(
icon = R.drawable.disc,
text = "Go to album",
onClick = {
onDismiss()
onGoToAlbum(albumId)
}
)
onRemoveFromQueue?.let { onRemoveFromQueue ->
MenuEntry(
icon = R.drawable.trash,
text = "Remove from queue",
onClick = {
onDismiss()
onRemoveFromQueue()
}
}
)
}
onGoToArtist?.let { onGoToArtist ->
mediaItem.mediaMetadata.extras?.getStringArrayList("artistNames")
?.let { artistNames ->
mediaItem.mediaMetadata.extras?.getStringArrayList("artistIds")
?.let { artistIds ->
artistNames.zip(artistIds)
.forEach { (authorName, authorId) ->
if (authorId != null) {
MenuEntry(
icon = R.drawable.person,
text = "More of $authorName",
onClick = {
onDismiss()
onGoToArtist(authorId)
}
)
}
}
}
}
}
onRemoveFromPlaylist?.let { onRemoveFromPlaylist ->
MenuEntry(
icon = R.drawable.trash,
text = "Remove from playlist",
onClick = {
onDismiss()
onRemoveFromPlaylist()
}
)
}
onShare?.let { onShare ->
MenuEntry(
icon = R.drawable.share_social,
text = "Share",
onClick = {
onDismiss()
onShare()
}
)
}
onRemoveFromQueue?.let { onRemoveFromQueue ->
MenuEntry(
icon = R.drawable.trash,
text = "Remove from queue",
onClick = {
onDismiss()
onRemoveFromQueue()
}
)
}
onRemoveFromFavorites?.let { onRemoveFromFavorites ->
MenuEntry(
icon = R.drawable.heart_dislike,
text = "Remove from favorites",
onClick = {
onDismiss()
onRemoveFromFavorites()
}
)
}
onRemoveFromPlaylist?.let { onRemoveFromPlaylist ->
MenuEntry(
icon = R.drawable.trash,
text = "Remove from playlist",
onClick = {
onDismiss()
onRemoveFromPlaylist()
}
)
}
onHideFromDatabase?.let { onHideFromDatabase ->
MenuEntry(
icon = R.drawable.trash,
text = "Hide",
onClick = onHideFromDatabase
)
}
onHideFromDatabase?.let { onHideFromDatabase ->
MenuEntry(
icon = R.drawable.trash,
text = "Hide",
onClick = onHideFromDatabase
)
}
}
}

View file

@ -1,11 +1,11 @@
package it.vfsfitvnm.vimusic.ui.components.themed
import androidx.annotation.DrawableRes
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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
@ -23,7 +23,6 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import it.vfsfitvnm.vimusic.R
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
import it.vfsfitvnm.vimusic.utils.medium
import it.vfsfitvnm.vimusic.utils.secondary
@ -54,7 +53,8 @@ fun MenuEntry(
text: String,
onClick: () -> Unit,
secondaryText: String? = null,
isEnabled: Boolean = true,
enabled: Boolean = true,
trailingContent: (@Composable () -> Unit)? = null
) {
val (colorPalette, typography) = LocalAppearance.current
@ -62,9 +62,9 @@ fun MenuEntry(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(24.dp),
modifier = Modifier
.clickable(enabled = isEnabled, onClick = onClick)
.clickable(enabled = enabled, onClick = onClick)
.fillMaxWidth()
.alpha(if (isEnabled) 1f else 0.4f)
.alpha(if (enabled) 1f else 0.4f)
.padding(horizontal = 24.dp, vertical = 16.dp)
) {
Image(
@ -75,7 +75,10 @@ fun MenuEntry(
.size(15.dp)
)
Column {
Column(
modifier = Modifier
.weight(1f)
) {
BasicText(
text = text,
style = typography.xs.medium
@ -88,41 +91,7 @@ fun MenuEntry(
)
}
}
trailingContent?.invoke()
}
}
@Composable
fun MenuIconButton(
@DrawableRes icon: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
val (colorPalette) = LocalAppearance.current
Box(
modifier = modifier
.padding(horizontal = 14.dp)
) {
Image(
painter = painterResource(icon),
contentDescription = null,
colorFilter = ColorFilter.tint(colorPalette.text),
modifier = Modifier
.clickable(onClick = onClick)
.padding(horizontal = 8.dp, vertical = 8.dp)
.size(20.dp)
)
}
}
@Composable
fun MenuBackButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
MenuIconButton(
icon = R.drawable.chevron_back,
onClick = onClick,
modifier = modifier
)
}

View file

@ -17,7 +17,8 @@ fun SecondaryTextButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
isEnabled: Boolean = true
enabled: Boolean = true,
alternative: Boolean = false
) {
val (colorPalette, typography) = LocalAppearance.current
@ -26,8 +27,8 @@ fun SecondaryTextButton(
style = typography.xxs.medium,
modifier = modifier
.clip(RoundedCornerShape(16.dp))
.clickable(enabled = isEnabled, onClick = onClick)
.background(colorPalette.background2)
.clickable(enabled = enabled, onClick = onClick)
.background(if (alternative) colorPalette.background0 else colorPalette.background2)
.padding(all = 8.dp)
.padding(horizontal = 8.dp)
)

View file

@ -152,7 +152,7 @@ fun SongItem(
text = title ?: "",
style = typography.xs.semiBold,
maxLines = 1,
overflow = TextOverflow.Clip,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.weight(1f)
)

View file

@ -20,7 +20,6 @@ val playlistRoute = Route1<String?>("playlistRoute")
val searchResultRoute = Route1<String>("searchResultRoute")
val searchRoute = Route1<String>("searchRoute")
val settingsRoute = Route0("settingsRoute")
val viewPlaylistsRoute = Route0("createPlaylistRoute")
@SuppressLint("ComposableNaming")
@Suppress("NOTHING_TO_INLINE")

View file

@ -88,7 +88,7 @@ fun AlbumSongs(
headerContent {
SecondaryTextButton(
text = "Enqueue",
isEnabled = songs.isNotEmpty(),
enabled = songs.isNotEmpty(),
onClick = {
binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem))
}

View file

@ -83,7 +83,7 @@ fun ArtistLocalSongs(
headerContent {
SecondaryTextButton(
text = "Enqueue",
isEnabled = !songs.isNullOrEmpty(),
enabled = !songs.isNullOrEmpty(),
onClick = {
binder?.player?.enqueue(songs!!.map(DetailedSong::asMediaItem))
}

View file

@ -23,9 +23,8 @@ import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
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.NonQueuedMediaItemMenu
import it.vfsfitvnm.vimusic.ui.components.themed.SecondaryTextButton
import it.vfsfitvnm.vimusic.ui.items.SongItem
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
@ -94,7 +93,7 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
) {
SecondaryTextButton(
text = "Enqueue",
isEnabled = songs.isNotEmpty(),
enabled = songs.isNotEmpty(),
onClick = {
binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem))
}
@ -121,8 +120,8 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
onLongClick = {
menuState.display {
when (builtInPlaylist) {
BuiltInPlaylist.Favorites -> InFavoritesMediaItemMenu(
song = song,
BuiltInPlaylist.Favorites -> NonQueuedMediaItemMenu(
mediaItem = song.asMediaItem,
onDismiss = menuState::hide
)

View file

@ -150,7 +150,7 @@ fun LocalPlaylistSongs(
Header(title = playlistWithSongs?.playlist?.name ?: "Unknown") {
SecondaryTextButton(
text = "Enqueue",
isEnabled = playlistWithSongs?.songs?.isNotEmpty() == true,
enabled = playlistWithSongs?.songs?.isNotEmpty() == true,
onClick = {
playlistWithSongs?.songs
?.map(DetailedSong::asMediaItem)

View file

@ -351,7 +351,7 @@ fun Lyrics(
MenuEntry(
icon = R.drawable.download,
text = "Fetch lyrics again",
isEnabled = lyrics != null,
enabled = lyrics != null,
onClick = {
menuState.hide()
query {

View file

@ -125,7 +125,7 @@ fun PlaylistSongList(
Header(title = playlistPage?.title ?: "Unknown") {
SecondaryTextButton(
text = "Enqueue",
isEnabled = playlistPage?.songsPage?.items?.isNotEmpty() == true,
enabled = playlistPage?.songsPage?.items?.isNotEmpty() == true,
onClick = {
playlistPage?.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
binder?.player?.enqueue(mediaItems)

View file

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M184,112l144,144l-144,144"
android:strokeLineJoin="round"
android:strokeWidth="48"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
</vector>