Improve MediaItemMenu UI
This commit is contained in:
parent
3364bb9f30
commit
b2ad011cfd
13 changed files with 440 additions and 431 deletions
|
@ -60,7 +60,8 @@ fun BoxScope.FloatingActionsContainerWithScrollToTop(
|
||||||
) {
|
) {
|
||||||
val transitionState = remember {
|
val transitionState = remember {
|
||||||
MutableTransitionState<ScrollingInfo?>(ScrollingInfo())
|
MutableTransitionState<ScrollingInfo?>(ScrollingInfo())
|
||||||
}.apply { targetState = if (visible) lazyListState.scrollingInfo() else null }
|
}.apply { targetState = lazyListState.scrollingInfo() }
|
||||||
|
// }.apply { targetState = if (visible) lazyListState.scrollingInfo() else null }
|
||||||
|
|
||||||
FloatingActions(
|
FloatingActions(
|
||||||
transitionState = transitionState,
|
transitionState = transitionState,
|
||||||
|
@ -125,7 +126,7 @@ fun BoxScope.FloatingActions(
|
||||||
onScrollToTop()
|
onScrollToTop()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled = transition.targetState?.isScrollingDown == false && transition.targetState?.isFar == true,
|
// enabled = transition.targetState?.isScrollingDown == false && transition.targetState?.isFar == true,
|
||||||
iconId = R.drawable.chevron_up,
|
iconId = R.drawable.chevron_up,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = 16.dp)
|
.padding(bottom = 16.dp)
|
||||||
|
|
|
@ -2,20 +2,23 @@ package it.vfsfitvnm.vimusic.ui.components.themed
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedContentScope
|
import androidx.compose.animation.AnimatedContentScope
|
||||||
import androidx.compose.animation.EnterTransition
|
|
||||||
import androidx.compose.animation.ExitTransition
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.with
|
import androidx.compose.animation.with
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.requiredHeight
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
@ -30,11 +33,13 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.layout.onPlaced
|
||||||
import androidx.compose.ui.platform.LocalContext
|
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.compose.ui.unit.dp
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
|
||||||
import it.vfsfitvnm.vimusic.Database
|
import it.vfsfitvnm.vimusic.Database
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
||||||
import it.vfsfitvnm.vimusic.R
|
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.enums.SortOrder
|
||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
import it.vfsfitvnm.vimusic.models.Playlist
|
import it.vfsfitvnm.vimusic.models.Playlist
|
||||||
|
import it.vfsfitvnm.vimusic.models.Song
|
||||||
import it.vfsfitvnm.vimusic.models.SongPlaylistMap
|
import it.vfsfitvnm.vimusic.models.SongPlaylistMap
|
||||||
import it.vfsfitvnm.vimusic.query
|
import it.vfsfitvnm.vimusic.query
|
||||||
import it.vfsfitvnm.vimusic.transaction
|
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.albumRoute
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.artistRoute
|
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.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.addNext
|
||||||
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
import it.vfsfitvnm.vimusic.utils.asMediaItem
|
||||||
import it.vfsfitvnm.vimusic.utils.enqueue
|
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.vimusic.utils.semiBold
|
||||||
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
|
import it.vfsfitvnm.youtubemusic.models.NavigationEndpoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flowOf
|
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
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun InHistoryMediaItemMenu(
|
fun InHistoryMediaItemMenu(
|
||||||
|
@ -143,7 +134,6 @@ fun NonQueuedMediaItemMenu(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onRemoveFromPlaylist: (() -> Unit)? = null,
|
onRemoveFromPlaylist: (() -> Unit)? = null,
|
||||||
onHideFromDatabase: (() -> Unit)? = null,
|
onHideFromDatabase: (() -> Unit)? = null,
|
||||||
onRemoveFromFavorites: (() -> Unit)? = null,
|
|
||||||
) {
|
) {
|
||||||
val binder = LocalPlayerServiceBinder.current
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
|
||||||
|
@ -164,7 +154,6 @@ fun NonQueuedMediaItemMenu(
|
||||||
onEnqueue = { binder?.player?.enqueue(mediaItem) },
|
onEnqueue = { binder?.player?.enqueue(mediaItem) },
|
||||||
onRemoveFromPlaylist = onRemoveFromPlaylist,
|
onRemoveFromPlaylist = onRemoveFromPlaylist,
|
||||||
onHideFromDatabase = onHideFromDatabase,
|
onHideFromDatabase = onHideFromDatabase,
|
||||||
onRemoveFromFavorites = onRemoveFromFavorites,
|
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -203,7 +192,6 @@ fun BaseMediaItemMenu(
|
||||||
onRemoveFromQueue: (() -> Unit)? = null,
|
onRemoveFromQueue: (() -> Unit)? = null,
|
||||||
onRemoveFromPlaylist: (() -> Unit)? = null,
|
onRemoveFromPlaylist: (() -> Unit)? = null,
|
||||||
onHideFromDatabase: (() -> Unit)? = null,
|
onHideFromDatabase: (() -> Unit)? = null,
|
||||||
onRemoveFromFavorites: (() -> Unit)? = null
|
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
@ -228,7 +216,6 @@ fun BaseMediaItemMenu(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onHideFromDatabase = onHideFromDatabase,
|
onHideFromDatabase = onHideFromDatabase,
|
||||||
onRemoveFromFavorites = onRemoveFromFavorites,
|
|
||||||
onRemoveFromPlaylist = onRemoveFromPlaylist,
|
onRemoveFromPlaylist = onRemoveFromPlaylist,
|
||||||
onRemoveFromQueue = onRemoveFromQueue,
|
onRemoveFromQueue = onRemoveFromQueue,
|
||||||
onGoToAlbum = albumRoute::global,
|
onGoToAlbum = albumRoute::global,
|
||||||
|
@ -262,385 +249,425 @@ fun MediaItemMenu(
|
||||||
onEnqueue: (() -> Unit)? = null,
|
onEnqueue: (() -> Unit)? = null,
|
||||||
onHideFromDatabase: (() -> Unit)? = null,
|
onHideFromDatabase: (() -> Unit)? = null,
|
||||||
onRemoveFromQueue: (() -> Unit)? = null,
|
onRemoveFromQueue: (() -> Unit)? = null,
|
||||||
onRemoveFromFavorites: (() -> Unit)? = null,
|
|
||||||
onRemoveFromPlaylist: (() -> Unit)? = null,
|
onRemoveFromPlaylist: (() -> Unit)? = null,
|
||||||
onAddToPlaylist: ((Playlist, Int) -> Unit)? = null,
|
onAddToPlaylist: ((Playlist, Int) -> Unit)? = null,
|
||||||
onGoToAlbum: ((String) -> Unit)? = null,
|
onGoToAlbum: ((String) -> Unit)? = null,
|
||||||
onGoToArtist: ((String) -> Unit)? = null,
|
onGoToArtist: ((String) -> Unit)? = null,
|
||||||
onShare: (() -> Unit)? = null
|
onShare: () -> Unit
|
||||||
) {
|
) {
|
||||||
Menu(modifier = modifier) {
|
val (colorPalette) = LocalAppearance.current
|
||||||
RouteHandler(
|
val density = LocalDensity.current
|
||||||
transitionSpec = {
|
|
||||||
when (targetState.route) {
|
|
||||||
viewPlaylistsRoute -> slideIntoContainer(AnimatedContentScope.SlideDirection.Left) with
|
|
||||||
slideOutOfContainer(AnimatedContentScope.SlideDirection.Left)
|
|
||||||
|
|
||||||
else -> when (initialState.route) {
|
var isViewingPlaylists by remember {
|
||||||
viewPlaylistsRoute -> slideIntoContainer(AnimatedContentScope.SlideDirection.Right) with
|
mutableStateOf(false)
|
||||||
slideOutOfContainer(AnimatedContentScope.SlideDirection.Right)
|
}
|
||||||
|
|
||||||
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
} else {
|
||||||
viewPlaylistsRoute {
|
Menu(
|
||||||
val playlistPreviews by remember {
|
modifier = modifier
|
||||||
Database.playlistPreviews(PlaylistSortBy.DateAdded, SortOrder.Descending)
|
.onPlaced { height = with(density) { it.size.height.toDp() } }
|
||||||
}.collectAsState(initial = emptyList(), context = Dispatchers.IO)
|
) {
|
||||||
|
val thumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
|
val thumbnailSizePx = thumbnailSizeDp.px
|
||||||
|
|
||||||
var isCreatingNewPlaylist by rememberSaveable {
|
SongItem(
|
||||||
mutableStateOf(false)
|
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) {
|
Spacer(
|
||||||
TextFieldDialog(
|
modifier = Modifier
|
||||||
hintText = "Enter the playlist name",
|
.height(8.dp)
|
||||||
onDismiss = {
|
)
|
||||||
isCreatingNewPlaylist = false
|
|
||||||
},
|
Spacer(
|
||||||
onDone = { text ->
|
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()
|
onDismiss()
|
||||||
onAddToPlaylist(Playlist(name = text), 0)
|
onStartRadio()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
onPlayNext?.let { onPlayNext ->
|
||||||
Row(
|
MenuEntry(
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
icon = R.drawable.play_skip_forward,
|
||||||
modifier = Modifier
|
text = "Play next",
|
||||||
.fillMaxWidth()
|
onClick = {
|
||||||
) {
|
onDismiss()
|
||||||
MenuBackButton(onClick = pop)
|
onPlayNext()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (onAddToPlaylist != null) {
|
onEnqueue?.let { onEnqueue ->
|
||||||
MenuIconButton(
|
MenuEntry(
|
||||||
icon = R.drawable.add,
|
icon = R.drawable.enqueue,
|
||||||
onClick = {
|
text = "Enqueue",
|
||||||
isCreatingNewPlaylist = true
|
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 ->
|
MenuEntry(
|
||||||
if (onRemoveFromFavorites == null) {
|
icon = R.drawable.alarm,
|
||||||
MenuEntry(
|
text = "Sleep timer",
|
||||||
icon = R.drawable.heart,
|
secondaryText = sleepTimerMillisLeft?.let {
|
||||||
text = "Favorites",
|
"${
|
||||||
onClick = {
|
DateUtils.formatElapsedTime(
|
||||||
onDismiss()
|
it / 1000
|
||||||
query {
|
)
|
||||||
Database.insert(mediaItem)
|
} left"
|
||||||
Database.like(mediaItem.mediaId, System.currentTimeMillis())
|
},
|
||||||
}
|
onClick = {
|
||||||
}
|
isShowingSleepTimerDialog = true
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
playlistPreviews.forEach { playlistPreview ->
|
if (onAddToPlaylist != null) {
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.playlist,
|
icon = R.drawable.playlist,
|
||||||
text = playlistPreview.playlist.name,
|
text = "Add to playlist",
|
||||||
secondaryText = "${playlistPreview.songCount} songs",
|
onClick = { isViewingPlaylists = true },
|
||||||
onClick = {
|
trailingContent = {
|
||||||
onDismiss()
|
Image(
|
||||||
onAddToPlaylist(
|
painter = painterResource(R.drawable.chevron_forward),
|
||||||
playlistPreview.playlist,
|
contentDescription = null,
|
||||||
playlistPreview.songCount
|
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 {
|
onGoToArtist?.let { onGoToArtist ->
|
||||||
Column(
|
mediaItem.mediaMetadata.extras?.getStringArrayList("artistNames")
|
||||||
modifier = Modifier
|
?.let { artistNames ->
|
||||||
.pointerInput(Unit) {
|
mediaItem.mediaMetadata.extras?.getStringArrayList("artistIds")
|
||||||
detectTapGestures { }
|
?.let { artistIds ->
|
||||||
}
|
artistNames.zip(artistIds)
|
||||||
) {
|
.forEach { (authorName, authorId) ->
|
||||||
onStartRadio?.let { onStartRadio ->
|
if (authorId != null) {
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.radio,
|
icon = R.drawable.person,
|
||||||
text = "Start radio",
|
text = "More of $authorName",
|
||||||
onClick = {
|
onClick = {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
onStartRadio()
|
onGoToArtist(authorId)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
DialogTextButton(
|
|
||||||
text = "Set",
|
|
||||||
enabled = amount > 0,
|
|
||||||
onClick = {
|
|
||||||
binder?.startSleepTimer(amount * 10 * 60 * 1000L)
|
|
||||||
isShowingSleepTimerDialog = false
|
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MenuEntry(
|
onRemoveFromQueue?.let { onRemoveFromQueue ->
|
||||||
icon = R.drawable.alarm,
|
MenuEntry(
|
||||||
text = "Sleep timer",
|
icon = R.drawable.trash,
|
||||||
secondaryText = sleepTimerMillisLeft?.let {
|
text = "Remove from queue",
|
||||||
"${
|
onClick = {
|
||||||
DateUtils.formatElapsedTime(
|
onDismiss()
|
||||||
it / 1000
|
onRemoveFromQueue()
|
||||||
)
|
|
||||||
} 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)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
onGoToArtist?.let { onGoToArtist ->
|
onRemoveFromPlaylist?.let { onRemoveFromPlaylist ->
|
||||||
mediaItem.mediaMetadata.extras?.getStringArrayList("artistNames")
|
MenuEntry(
|
||||||
?.let { artistNames ->
|
icon = R.drawable.trash,
|
||||||
mediaItem.mediaMetadata.extras?.getStringArrayList("artistIds")
|
text = "Remove from playlist",
|
||||||
?.let { artistIds ->
|
onClick = {
|
||||||
artistNames.zip(artistIds)
|
onDismiss()
|
||||||
.forEach { (authorName, authorId) ->
|
onRemoveFromPlaylist()
|
||||||
if (authorId != null) {
|
}
|
||||||
MenuEntry(
|
)
|
||||||
icon = R.drawable.person,
|
}
|
||||||
text = "More of $authorName",
|
|
||||||
onClick = {
|
|
||||||
onDismiss()
|
|
||||||
onGoToArtist(authorId)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onShare?.let { onShare ->
|
onHideFromDatabase?.let { onHideFromDatabase ->
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.share_social,
|
icon = R.drawable.trash,
|
||||||
text = "Share",
|
text = "Hide",
|
||||||
onClick = {
|
onClick = onHideFromDatabase
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package it.vfsfitvnm.vimusic.ui.components.themed
|
package it.vfsfitvnm.vimusic.ui.components.themed
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
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.graphics.ColorFilter
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import it.vfsfitvnm.vimusic.R
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.utils.medium
|
import it.vfsfitvnm.vimusic.utils.medium
|
||||||
import it.vfsfitvnm.vimusic.utils.secondary
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
|
@ -54,7 +53,8 @@ fun MenuEntry(
|
||||||
text: String,
|
text: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
secondaryText: String? = null,
|
secondaryText: String? = null,
|
||||||
isEnabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
|
trailingContent: (@Composable () -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
|
|
||||||
|
@ -62,9 +62,9 @@ fun MenuEntry(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable(enabled = isEnabled, onClick = onClick)
|
.clickable(enabled = enabled, onClick = onClick)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.alpha(if (isEnabled) 1f else 0.4f)
|
.alpha(if (enabled) 1f else 0.4f)
|
||||||
.padding(horizontal = 24.dp, vertical = 16.dp)
|
.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
|
@ -75,7 +75,10 @@ fun MenuEntry(
|
||||||
.size(15.dp)
|
.size(15.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Column {
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
BasicText(
|
BasicText(
|
||||||
text = text,
|
text = text,
|
||||||
style = typography.xs.medium
|
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,7 +17,8 @@ fun SecondaryTextButton(
|
||||||
text: String,
|
text: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
isEnabled: Boolean = true
|
enabled: Boolean = true,
|
||||||
|
alternative: Boolean = false
|
||||||
) {
|
) {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
|
|
||||||
|
@ -26,8 +27,8 @@ fun SecondaryTextButton(
|
||||||
style = typography.xxs.medium,
|
style = typography.xxs.medium,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
.clickable(enabled = isEnabled, onClick = onClick)
|
.clickable(enabled = enabled, onClick = onClick)
|
||||||
.background(colorPalette.background2)
|
.background(if (alternative) colorPalette.background0 else colorPalette.background2)
|
||||||
.padding(all = 8.dp)
|
.padding(all = 8.dp)
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp)
|
||||||
)
|
)
|
||||||
|
|
|
@ -152,7 +152,7 @@ fun SongItem(
|
||||||
text = title ?: "",
|
text = title ?: "",
|
||||||
style = typography.xs.semiBold,
|
style = typography.xs.semiBold,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Clip,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,7 +20,6 @@ val playlistRoute = Route1<String?>("playlistRoute")
|
||||||
val searchResultRoute = Route1<String>("searchResultRoute")
|
val searchResultRoute = Route1<String>("searchResultRoute")
|
||||||
val searchRoute = Route1<String>("searchRoute")
|
val searchRoute = Route1<String>("searchRoute")
|
||||||
val settingsRoute = Route0("settingsRoute")
|
val settingsRoute = Route0("settingsRoute")
|
||||||
val viewPlaylistsRoute = Route0("createPlaylistRoute")
|
|
||||||
|
|
||||||
@SuppressLint("ComposableNaming")
|
@SuppressLint("ComposableNaming")
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
|
|
@ -88,7 +88,7 @@ fun AlbumSongs(
|
||||||
headerContent {
|
headerContent {
|
||||||
SecondaryTextButton(
|
SecondaryTextButton(
|
||||||
text = "Enqueue",
|
text = "Enqueue",
|
||||||
isEnabled = songs.isNotEmpty(),
|
enabled = songs.isNotEmpty(),
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem))
|
binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem))
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ fun ArtistLocalSongs(
|
||||||
headerContent {
|
headerContent {
|
||||||
SecondaryTextButton(
|
SecondaryTextButton(
|
||||||
text = "Enqueue",
|
text = "Enqueue",
|
||||||
isEnabled = !songs.isNullOrEmpty(),
|
enabled = !songs.isNullOrEmpty(),
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.player?.enqueue(songs!!.map(DetailedSong::asMediaItem))
|
binder?.player?.enqueue(songs!!.map(DetailedSong::asMediaItem))
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,8 @@ import it.vfsfitvnm.vimusic.savers.DetailedSongListSaver
|
||||||
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
import it.vfsfitvnm.vimusic.ui.components.LocalMenuState
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
import it.vfsfitvnm.vimusic.ui.components.themed.FloatingActionsContainerWithScrollToTop
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InFavoritesMediaItemMenu
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
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.components.themed.SecondaryTextButton
|
||||||
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
import it.vfsfitvnm.vimusic.ui.items.SongItem
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
|
@ -94,7 +93,7 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
|
||||||
) {
|
) {
|
||||||
SecondaryTextButton(
|
SecondaryTextButton(
|
||||||
text = "Enqueue",
|
text = "Enqueue",
|
||||||
isEnabled = songs.isNotEmpty(),
|
enabled = songs.isNotEmpty(),
|
||||||
onClick = {
|
onClick = {
|
||||||
binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem))
|
binder?.player?.enqueue(songs.map(DetailedSong::asMediaItem))
|
||||||
}
|
}
|
||||||
|
@ -121,8 +120,8 @@ fun BuiltInPlaylistSongs(builtInPlaylist: BuiltInPlaylist) {
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
menuState.display {
|
menuState.display {
|
||||||
when (builtInPlaylist) {
|
when (builtInPlaylist) {
|
||||||
BuiltInPlaylist.Favorites -> InFavoritesMediaItemMenu(
|
BuiltInPlaylist.Favorites -> NonQueuedMediaItemMenu(
|
||||||
song = song,
|
mediaItem = song.asMediaItem,
|
||||||
onDismiss = menuState::hide
|
onDismiss = menuState::hide
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -150,7 +150,7 @@ fun LocalPlaylistSongs(
|
||||||
Header(title = playlistWithSongs?.playlist?.name ?: "Unknown") {
|
Header(title = playlistWithSongs?.playlist?.name ?: "Unknown") {
|
||||||
SecondaryTextButton(
|
SecondaryTextButton(
|
||||||
text = "Enqueue",
|
text = "Enqueue",
|
||||||
isEnabled = playlistWithSongs?.songs?.isNotEmpty() == true,
|
enabled = playlistWithSongs?.songs?.isNotEmpty() == true,
|
||||||
onClick = {
|
onClick = {
|
||||||
playlistWithSongs?.songs
|
playlistWithSongs?.songs
|
||||||
?.map(DetailedSong::asMediaItem)
|
?.map(DetailedSong::asMediaItem)
|
||||||
|
|
|
@ -351,7 +351,7 @@ fun Lyrics(
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon = R.drawable.download,
|
icon = R.drawable.download,
|
||||||
text = "Fetch lyrics again",
|
text = "Fetch lyrics again",
|
||||||
isEnabled = lyrics != null,
|
enabled = lyrics != null,
|
||||||
onClick = {
|
onClick = {
|
||||||
menuState.hide()
|
menuState.hide()
|
||||||
query {
|
query {
|
||||||
|
|
|
@ -125,7 +125,7 @@ fun PlaylistSongList(
|
||||||
Header(title = playlistPage?.title ?: "Unknown") {
|
Header(title = playlistPage?.title ?: "Unknown") {
|
||||||
SecondaryTextButton(
|
SecondaryTextButton(
|
||||||
text = "Enqueue",
|
text = "Enqueue",
|
||||||
isEnabled = playlistPage?.songsPage?.items?.isNotEmpty() == true,
|
enabled = playlistPage?.songsPage?.items?.isNotEmpty() == true,
|
||||||
onClick = {
|
onClick = {
|
||||||
playlistPage?.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
|
playlistPage?.songsPage?.items?.map(Innertube.SongItem::asMediaItem)?.let { mediaItems ->
|
||||||
binder?.player?.enqueue(mediaItems)
|
binder?.player?.enqueue(mediaItems)
|
||||||
|
|
13
app/src/main/res/drawable/chevron_forward.xml
Normal file
13
app/src/main/res/drawable/chevron_forward.xml
Normal 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>
|
Loading…
Reference in a new issue