From 2e3d437c1536513ccbf3dd2ba56bbfb056f3e9f1 Mon Sep 17 00:00:00 2001 From: vfsfitvnm Date: Tue, 27 Sep 2022 15:17:27 +0200 Subject: [PATCH] Redesign LocalPlaylistScreen (#172) --- .../vimusic/models/PlaylistWithSongs.kt | 7 +- .../vimusic/savers/DetailedSongSaver.kt | 24 +- .../it/vfsfitvnm/vimusic/savers/ListSaver.kt | 3 + .../vimusic/savers/PlaylistWithSongsSaver.kt | 20 ++ .../vimusic/ui/screens/LocalPlaylistScreen.kt | 333 ------------------ .../vimusic/ui/screens/home/HomeScreen.kt | 2 +- .../localplaylist/LocalPlaylistScreen.kt | 40 +++ .../localplaylist/LocalPlaylistSongList.kt | 302 ++++++++++++++++ 8 files changed, 378 insertions(+), 353 deletions(-) create mode 100644 app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/PlaylistWithSongsSaver.kt delete mode 100644 app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/LocalPlaylistScreen.kt create mode 100644 app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistScreen.kt create mode 100644 app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistSongList.kt diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/PlaylistWithSongs.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/PlaylistWithSongs.kt index cb3f47e..ac26fda 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/PlaylistWithSongs.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/models/PlaylistWithSongs.kt @@ -19,9 +19,4 @@ data class PlaylistWithSongs( ) ) val songs: List -) { - companion object { - val Empty = PlaylistWithSongs(Playlist(-1, ""), emptyList()) - val NotFound = PlaylistWithSongs(Playlist(-2, "Not found"), emptyList()) - } -} +) diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/DetailedSongSaver.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/DetailedSongSaver.kt index cfede7b..db11ff5 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/DetailedSongSaver.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/DetailedSongSaver.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.saveable.SaverScope import it.vfsfitvnm.vimusic.models.DetailedSong object DetailedSongSaver : Saver> { - override fun SaverScope.save(value: DetailedSong): List = + override fun SaverScope.save(value: DetailedSong) = listOf( value.id, value.title, @@ -18,16 +18,14 @@ object DetailedSongSaver : Saver> { ) @Suppress("UNCHECKED_CAST") - override fun restore(value: List): DetailedSong? { - return if (value.size == 8) DetailedSong( - id = value[0] as String, - title = value[1] as String, - artistsText = value[2] as String?, - durationText = value[3] as String, - thumbnailUrl = value[4] as String?, - totalPlayTimeMs = value[5] as Long, - albumId = value[6] as String?, - artists = InfoListSaver.restore(value[7] as List>) - ) else null - } + override fun restore(value: List) = DetailedSong( + id = value[0] as String, + title = value[1] as String, + artistsText = value[2] as String?, + durationText = value[3] as String, + thumbnailUrl = value[4] as String?, + totalPlayTimeMs = value[5] as Long, + albumId = value[6] as String?, + artists = InfoListSaver.restore(value[7] as List>) + ) } diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/ListSaver.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/ListSaver.kt index 3b7f617..62e8621 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/ListSaver.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/ListSaver.kt @@ -4,6 +4,9 @@ import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.SaverScope interface ListSaver : Saver, List> { + override fun SaverScope.save(value: List): List + override fun restore(value: List): List + companion object { fun of(saver: Saver): ListSaver { return object : ListSaver { diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/PlaylistWithSongsSaver.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/PlaylistWithSongsSaver.kt new file mode 100644 index 0000000..c6b4a9d --- /dev/null +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/savers/PlaylistWithSongsSaver.kt @@ -0,0 +1,20 @@ +package it.vfsfitvnm.vimusic.savers + +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.SaverScope +import it.vfsfitvnm.vimusic.models.PlaylistWithSongs + +object PlaylistWithSongsSaver : Saver> { + override fun SaverScope.save(value: PlaylistWithSongs?) = value?.let { + listOf( + with(PlaylistSaver) { save(value.playlist) }, + with(DetailedSongListSaver) { save(value.songs) }, + ) + } + + @Suppress("UNCHECKED_CAST") + override fun restore(value: List): PlaylistWithSongs = PlaylistWithSongs( + playlist = PlaylistSaver.restore(value[0] as List), + songs = DetailedSongListSaver.restore(value[1] as List>) + ) +} diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/LocalPlaylistScreen.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/LocalPlaylistScreen.kt deleted file mode 100644 index 66f4754..0000000 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/LocalPlaylistScreen.kt +++ /dev/null @@ -1,333 +0,0 @@ -package it.vfsfitvnm.vimusic.ui.screens - -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.layout.Arrangement -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.lazy.itemsIndexed -import androidx.compose.foundation.lazy.rememberLazyListState -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.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex -import it.vfsfitvnm.reordering.ReorderingLazyColumn -import it.vfsfitvnm.reordering.animateItemPlacement -import it.vfsfitvnm.reordering.draggedItem -import it.vfsfitvnm.reordering.rememberReorderingState -import it.vfsfitvnm.reordering.reorder -import it.vfsfitvnm.route.RouteHandler -import it.vfsfitvnm.vimusic.Database -import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues -import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder -import it.vfsfitvnm.vimusic.R -import it.vfsfitvnm.vimusic.models.DetailedSong -import it.vfsfitvnm.vimusic.models.PlaylistWithSongs -import it.vfsfitvnm.vimusic.models.SongPlaylistMap -import it.vfsfitvnm.vimusic.query -import it.vfsfitvnm.vimusic.transaction -import it.vfsfitvnm.vimusic.ui.components.LocalMenuState -import it.vfsfitvnm.vimusic.ui.components.TopAppBar -import it.vfsfitvnm.vimusic.ui.components.themed.ConfirmationDialog -import it.vfsfitvnm.vimusic.ui.components.themed.InPlaylistMediaItemMenu -import it.vfsfitvnm.vimusic.ui.components.themed.Menu -import it.vfsfitvnm.vimusic.ui.components.themed.MenuEntry -import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog -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 -import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning -import it.vfsfitvnm.vimusic.utils.secondary -import it.vfsfitvnm.vimusic.utils.semiBold -import it.vfsfitvnm.vimusic.utils.toMediaItem -import it.vfsfitvnm.youtubemusic.YouTube -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext - -@ExperimentalFoundationApi -@ExperimentalAnimationApi -@Composable -fun LocalPlaylistScreen(playlistId: Long) { - val playlistWithSongs by remember(playlistId) { - Database.playlistWithSongs(playlistId).map { it ?: PlaylistWithSongs.NotFound } - }.collectAsState(initial = PlaylistWithSongs.Empty, context = Dispatchers.IO) - - val lazyListState = rememberLazyListState() - - RouteHandler(listenToGlobalEmitter = true) { - globalRoutes() - - host { - val (colorPalette, typography) = LocalAppearance.current - val menuState = LocalMenuState.current - val binder = LocalPlayerServiceBinder.current - - val thumbnailSize = Dimensions.thumbnails.song.px - - val reorderingState = rememberReorderingState( - lazyListState = lazyListState, - key = playlistWithSongs.songs, - onDragEnd = { fromIndex, toIndex -> - query { - Database.move(playlistWithSongs.playlist.id, fromIndex, toIndex) - } - }, - extraItemCount = 1 - ) - - var isRenaming by rememberSaveable { - mutableStateOf(false) - } - - if (isRenaming) { - TextFieldDialog( - hintText = "Enter the playlist name", - initialTextInput = playlistWithSongs.playlist.name, - onDismiss = { isRenaming = false }, - onDone = { text -> - query { - Database.update(playlistWithSongs.playlist.copy(name = text)) - } - } - ) - } - - var isDeleting by rememberSaveable { - mutableStateOf(false) - } - - if (isDeleting) { - ConfirmationDialog( - text = "Do you really want to delete this playlist?", - onDismiss = { isDeleting = false }, - onConfirm = { - query { - Database.delete(playlistWithSongs.playlist) - } - pop() - } - ) - } - - ReorderingLazyColumn( - reorderingState = reorderingState, - contentPadding = LocalPlayerAwarePaddingValues.current, - modifier = Modifier - .background(colorPalette.background0) - .fillMaxSize() - ) { - item { - Column { - TopAppBar( - modifier = Modifier - .height(52.dp) - ) { - Image( - painter = painterResource(R.drawable.chevron_back), - contentDescription = null, - colorFilter = ColorFilter.tint(colorPalette.text), - modifier = Modifier - .clickable(onClick = pop) - .padding(vertical = 8.dp, horizontal = 16.dp) - .size(24.dp) - ) - } - - Column( - modifier = Modifier - .padding(top = 16.dp, bottom = 8.dp) - .padding(horizontal = 16.dp) - ) { - BasicText( - text = playlistWithSongs.playlist.name, - style = typography.m.semiBold - ) - - BasicText( - text = "${playlistWithSongs.songs.size} songs", - style = typography.xxs.semiBold.secondary - ) - } - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.End, - modifier = Modifier - .fillMaxWidth() - .zIndex(1f) - .padding(horizontal = 8.dp) - ) { - Image( - painter = painterResource(R.drawable.shuffle), - contentDescription = null, - colorFilter = ColorFilter.tint(colorPalette.text), - modifier = Modifier - .clickable(enabled = playlistWithSongs.songs.isNotEmpty()) { - binder?.stopRadio() - binder?.player?.forcePlayFromBeginning( - playlistWithSongs.songs - .shuffled() - .map(DetailedSong::asMediaItem) - ) - } - .padding(horizontal = 8.dp, vertical = 8.dp) - .size(20.dp) - ) - - Image( - painter = painterResource(R.drawable.ellipsis_horizontal), - contentDescription = null, - colorFilter = ColorFilter.tint(colorPalette.text), - modifier = Modifier - .clickable { - menuState.display { - Menu { - MenuEntry( - icon = R.drawable.enqueue, - text = "Enqueue", - isEnabled = playlistWithSongs.songs.isNotEmpty(), - onClick = { - menuState.hide() - binder?.player?.enqueue( - playlistWithSongs.songs.map( - DetailedSong::asMediaItem - ) - ) - } - ) - - MenuEntry( - icon = R.drawable.pencil, - text = "Rename", - onClick = { - menuState.hide() - isRenaming = true - } - ) - - playlistWithSongs.playlist.browseId?.let { browseId -> - MenuEntry( - icon = R.drawable.sync, - text = "Sync", - onClick = { - menuState.hide() - transaction { - runBlocking(Dispatchers.IO) { - withContext(Dispatchers.IO) { - YouTube.playlist(browseId)?.map { - it.next() - }?.map { playlist -> - playlist.copy(items = playlist.items?.filter { it.info.endpoint != null }) - } - } - }?.getOrNull()?.let { remotePlaylist -> - Database.clearPlaylist(playlistWithSongs.playlist.id) - - remotePlaylist.items?.forEachIndexed { index, song -> - song.toMediaItem(browseId, remotePlaylist)?.let { mediaItem -> - Database.insert(mediaItem) - - Database.insert( - SongPlaylistMap( - songId = mediaItem.mediaId, - playlistId = playlistId, - position = index - ) - ) - } - } - } - } - } - ) - } - - MenuEntry( - icon = R.drawable.trash, - text = "Delete", - onClick = { - menuState.hide() - isDeleting = true - } - ) - } - } - } - .padding(horizontal = 8.dp, vertical = 8.dp) - .size(20.dp) - ) - } - } - } - - itemsIndexed( - items = playlistWithSongs.songs, - key = { _, song -> song.id }, - contentType = { _, song -> song }, - ) { index, song -> - SongItem( - song = song, - thumbnailSize = thumbnailSize, - onClick = { - binder?.stopRadio() - binder?.player?.forcePlayAtIndex( - playlistWithSongs.songs.map( - DetailedSong::asMediaItem - ), index - ) - }, - menuContent = { - InPlaylistMediaItemMenu( - playlistId = playlistId, - positionInPlaylist = index, - song = song - ) - }, - trailingContent = { - Image( - painter = painterResource(R.drawable.reorder), - contentDescription = null, - colorFilter = ColorFilter.tint(colorPalette.textSecondary), - modifier = Modifier - .clickable { } - .reorder( - reorderingState = reorderingState, - index = index - ) - .padding(horizontal = 8.dp, vertical = 4.dp) - .size(20.dp) - ) - }, - modifier = Modifier - .animateItemPlacement(reorderingState = reorderingState) - .draggedItem(reorderingState = reorderingState, index = index) - ) - } - } - } - } -} diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeScreen.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeScreen.kt index 1b7192a..8140ab5 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeScreen.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/home/HomeScreen.kt @@ -13,7 +13,7 @@ import it.vfsfitvnm.vimusic.query import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold import it.vfsfitvnm.vimusic.ui.screens.BuiltInPlaylistScreen import it.vfsfitvnm.vimusic.ui.screens.IntentUriScreen -import it.vfsfitvnm.vimusic.ui.screens.LocalPlaylistScreen +import it.vfsfitvnm.vimusic.ui.screens.localplaylist.LocalPlaylistScreen import it.vfsfitvnm.vimusic.ui.screens.albumRoute import it.vfsfitvnm.vimusic.ui.screens.artistRoute import it.vfsfitvnm.vimusic.ui.screens.builtInPlaylistRoute diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistScreen.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistScreen.kt new file mode 100644 index 0000000..a1c4581 --- /dev/null +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistScreen.kt @@ -0,0 +1,40 @@ +package it.vfsfitvnm.vimusic.ui.screens.localplaylist + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.saveable.rememberSaveableStateHolder +import it.vfsfitvnm.route.RouteHandler +import it.vfsfitvnm.vimusic.R +import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold +import it.vfsfitvnm.vimusic.ui.screens.globalRoutes + +@ExperimentalFoundationApi +@ExperimentalAnimationApi +@Composable +fun LocalPlaylistScreen(playlistId: Long) { + val saveableStateHolder = rememberSaveableStateHolder() + + RouteHandler(listenToGlobalEmitter = true) { + globalRoutes() + + host { + Scaffold( + topIconButtonId = R.drawable.chevron_back, + onTopIconButtonClick = pop, + tabIndex = 0, + onTabChanged = { }, + tabColumnContent = { Item -> + Item(0, "Songs", R.drawable.musical_notes) + } + ) { currentTabIndex -> + saveableStateHolder.SaveableStateProvider(key = currentTabIndex) { + LocalPlaylistSongList( + playlistId = playlistId, + onDelete = pop + ) + } + } + } + } +} diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistSongList.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistSongList.kt new file mode 100644 index 0000000..a8c17be --- /dev/null +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/ui/screens/localplaylist/LocalPlaylistSongList.kt @@ -0,0 +1,302 @@ +package it.vfsfitvnm.vimusic.ui.screens.localplaylist + +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.layout.Box +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.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicText +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +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.res.painterResource +import androidx.compose.ui.unit.dp +import it.vfsfitvnm.reordering.ReorderingLazyColumn +import it.vfsfitvnm.reordering.animateItemPlacement +import it.vfsfitvnm.reordering.draggedItem +import it.vfsfitvnm.reordering.rememberReorderingState +import it.vfsfitvnm.reordering.reorder +import it.vfsfitvnm.vimusic.Database +import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues +import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder +import it.vfsfitvnm.vimusic.R +import it.vfsfitvnm.vimusic.models.DetailedSong +import it.vfsfitvnm.vimusic.models.SongPlaylistMap +import it.vfsfitvnm.vimusic.query +import it.vfsfitvnm.vimusic.savers.PlaylistWithSongsSaver +import it.vfsfitvnm.vimusic.transaction +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.TextFieldDialog +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 +import it.vfsfitvnm.vimusic.utils.forcePlayFromBeginning +import it.vfsfitvnm.vimusic.utils.medium +import it.vfsfitvnm.vimusic.utils.produceSaveableState +import it.vfsfitvnm.vimusic.utils.toMediaItem +import it.vfsfitvnm.youtubemusic.YouTube +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext + +@ExperimentalAnimationApi +@ExperimentalFoundationApi +@Composable +fun LocalPlaylistSongList( + playlistId: Long, + onDelete: () -> Unit, +) { + val (colorPalette, typography) = LocalAppearance.current + val binder = LocalPlayerServiceBinder.current + + val playlistWithSongs by produceSaveableState( + initialValue = null, + stateSaver = PlaylistWithSongsSaver + ) { + Database + .playlistWithSongs(playlistId) + .flowOn(Dispatchers.IO) + .collect { value = it } + } + + val lazyListState = rememberLazyListState() + + val reorderingState = rememberReorderingState( + lazyListState = lazyListState, + key = playlistWithSongs?.songs ?: emptyList(), + onDragEnd = { fromIndex, toIndex -> + query { + Database.move(playlistId, fromIndex, toIndex) + } + }, + extraItemCount = 1 + ) + + var isRenaming by rememberSaveable { + mutableStateOf(false) + } + + if (isRenaming) { + TextFieldDialog( + hintText = "Enter the playlist name", + initialTextInput = playlistWithSongs?.playlist?.name ?: "", + onDismiss = { isRenaming = false }, + onDone = { text -> + query { + playlistWithSongs?.playlist?.copy(name = text)?.let(Database::update) + } + } + ) + } + + var isDeleting by rememberSaveable { + mutableStateOf(false) + } + + if (isDeleting) { + ConfirmationDialog( + text = "Do you really want to delete this playlist?", + onDismiss = { isDeleting = false }, + onConfirm = { + query { + playlistWithSongs?.playlist?.let(Database::delete) + } + onDelete() + } + ) + } + + val thumbnailSize = Dimensions.thumbnails.song.px + + Box { + ReorderingLazyColumn( + reorderingState = reorderingState, + contentPadding = LocalPlayerAwarePaddingValues.current, + modifier = Modifier + .background(colorPalette.background0) + .fillMaxSize() + ) { + item( + key = "header", + contentType = 0 + ) { + Header(title = playlistWithSongs?.playlist?.name ?: "Unknown") { + BasicText( + text = "Enqueue", + style = typography.xxs.medium, + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .clickable(enabled = playlistWithSongs?.songs?.isNotEmpty() == true) { + playlistWithSongs?.songs + ?.map(DetailedSong::asMediaItem) + ?.let { mediaItems -> + binder?.player?.enqueue(mediaItems) + } + } + .background(colorPalette.background2) + .padding(all = 8.dp) + .padding(horizontal = 8.dp) + ) + + Spacer( + modifier = Modifier + .weight(1f) + ) + + playlistWithSongs?.playlist?.browseId?.let { browseId -> + Image( + painter = painterResource(R.drawable.sync), + contentDescription = null, + colorFilter = ColorFilter.tint(colorPalette.text), + modifier = Modifier + .clickable { + transaction { + runBlocking(Dispatchers.IO) { + withContext(Dispatchers.IO) { + YouTube.playlist(browseId)?.map { + it.next() + }?.map { playlist -> + playlist.copy(items = playlist.items?.filter { it.info.endpoint != null }) + } + } + }?.getOrNull()?.let { remotePlaylist -> + Database.clearPlaylist(playlistId) + + remotePlaylist.items?.forEachIndexed { index, song -> + song.toMediaItem(browseId, remotePlaylist)?.let { mediaItem -> + Database.insert(mediaItem) + + Database.insert( + SongPlaylistMap( + songId = mediaItem.mediaId, + playlistId = playlistId, + position = index + ) + ) + } + } + } + } + } + .padding(all = 4.dp) + .size(18.dp) + ) + } + + Image( + painter = painterResource(R.drawable.pencil), + contentDescription = null, + colorFilter = ColorFilter.tint(colorPalette.text), + modifier = Modifier + .clickable { isRenaming = true } + .padding(all = 4.dp) + .size(18.dp) + ) + + + Image( + painter = painterResource(R.drawable.trash), + contentDescription = null, + colorFilter = ColorFilter.tint(colorPalette.text), + modifier = Modifier + .clickable { isDeleting = true } + .padding(all = 4.dp) + .size(18.dp) + ) + } + } + + itemsIndexed( + items = playlistWithSongs?.songs ?: emptyList(), + key = { _, song -> song.id }, + contentType = { _, song -> song }, + ) { index, song -> + SongItem( + song = song, + thumbnailSize = thumbnailSize, + onClick = { + playlistWithSongs?.songs?.map(DetailedSong::asMediaItem) + ?.let { mediaItems -> + binder?.stopRadio() + binder?.player?.forcePlayAtIndex(mediaItems, index) + } + }, + menuContent = { + InPlaylistMediaItemMenu( + playlistId = playlistId, + positionInPlaylist = index, + song = song + ) + }, + trailingContent = { + Image( + painter = painterResource(R.drawable.reorder), + contentDescription = null, + colorFilter = ColorFilter.tint(colorPalette.textSecondary), + modifier = Modifier + .clickable { } + .reorder( + reorderingState = reorderingState, + index = index + ) + .padding(horizontal = 8.dp, vertical = 4.dp) + .size(20.dp) + ) + }, + modifier = Modifier + .animateItemPlacement(reorderingState = reorderingState) + .draggedItem(reorderingState = reorderingState, index = index) + ) + } + } + + Box( + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(all = 16.dp) + .padding(LocalPlayerAwarePaddingValues.current) + .clip(RoundedCornerShape(16.dp)) + .clickable(enabled = playlistWithSongs?.songs?.isNotEmpty() == true) { + playlistWithSongs?.songs + ?.shuffled() + ?.map(DetailedSong::asMediaItem) + ?.let { mediaItems -> + binder?.stopRadio() + binder?.player?.forcePlayFromBeginning(mediaItems) + } + } + .background(colorPalette.background2) + .size(62.dp) + ) { + Image( + painter = painterResource(R.drawable.shuffle), + contentDescription = null, + colorFilter = ColorFilter.tint(colorPalette.text), + modifier = Modifier + .align(Alignment.Center) + .size(20.dp) + ) + } + } +}