diff --git a/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt b/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt index e15c527b..a6bca62d 100644 --- a/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt +++ b/app/src/main/kotlin/com/subtracks/ui/AppRoot.kt @@ -60,6 +60,8 @@ import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import com.subtracks.data.repo.SourceRepository import com.subtracks.playback.PlaybackController +import com.subtracks.ui.components.ContextMenuHost +import com.subtracks.ui.components.ItemContextMenu import com.subtracks.ui.components.LoadingState import com.subtracks.ui.components.statusBarScrim import com.subtracks.ui.library.AlbumDetailRoute @@ -93,6 +95,7 @@ class RootViewModel( private const val NAVIGATION_DURATION_MS = 260 private const val OVERLAY_DURATION_MS = 200 +private const val FADE_OUT_MS = 150 private const val EXPAND_FADE = 0.1f private const val FLING_VELOCITY = 1000f private const val MINI_PLAYER_ANIM_MS = 200 @@ -145,11 +148,16 @@ private fun MainNavigation() { var nowPlayingOpen by rememberSaveable { mutableStateOf(false) } var nowPlayingProgress by remember { mutableFloatStateOf(0f) } var miniPlayerTopPx by remember { mutableFloatStateOf(0f) } + var nowPlayingFadeOut by remember { mutableStateOf(false) } var settleJob by remember { mutableStateOf(null) } val scope = rememberCoroutineScope() + val contextMenuHost = remember { ContextMenuHost() } fun settleNowPlaying(open: Boolean) { - if (open) nowPlayingOpen = true + if (open) { + nowPlayingOpen = true + nowPlayingFadeOut = false + } settleJob?.cancel() settleJob = scope.launch { @@ -159,6 +167,30 @@ private fun MainNavigation() { if (!open) nowPlayingOpen = false } } + + fun fadeNowPlaying() { + nowPlayingFadeOut = true + settleJob?.cancel() + settleJob = + scope.launch { + animate(nowPlayingProgress, 0f, animationSpec = tween(FADE_OUT_MS)) { value, _ -> + nowPlayingProgress = value + } + nowPlayingOpen = false + nowPlayingFadeOut = false + } + } + + fun navigateDetail( + pattern: String, + argument: String, + value: String, + route: String, + ) { + val top = navController.currentBackStackEntry + if (top?.destination?.route == pattern && top.arguments?.getString(argument) == value) return + navController.navigate(route) + } LaunchedEffect(Unit) { playbackController.connect() } LaunchedEffect(Unit) { if (nowPlayingOpen) nowPlayingProgress = 1f } @@ -216,12 +248,21 @@ private fun MainNavigation() { ) { composable(Routes.LIBRARY) { LibraryRoute( - onAlbumClick = { album -> navController.navigate(Routes.album(album.id, album.coverArt)) }, - onArtistClick = { artist -> navController.navigate(Routes.artist(artist.id, artist.coverArt)) }, - onPlaylistClick = { playlist -> navController.navigate(Routes.playlist(playlist.id)) }, + onAlbumClick = { album -> + navigateDetail(Routes.ALBUM_DETAIL, "albumId", album.id, Routes.album(album.id, album.coverArt)) + }, + onArtistClick = { artist -> + navigateDetail(Routes.ARTIST_DETAIL, "artistId", artist.id, Routes.artist(artist.id, artist.coverArt)) + }, + onPlaylistClick = { playlist -> + navigateDetail(Routes.PLAYLIST_DETAIL, "playlistId", playlist.id, Routes.playlist(playlist.id)) + }, onOpenSettings = { navController.navigate(Routes.SETTINGS) }, - onViewAlbum = { albumId -> navController.navigate(Routes.album(albumId)) }, - onViewArtist = { artistId -> navController.navigate(Routes.artist(artistId)) }, + onViewAlbum = { albumId -> navigateDetail(Routes.ALBUM_DETAIL, "albumId", albumId, Routes.album(albumId)) }, + onViewArtist = { artistId -> + navigateDetail(Routes.ARTIST_DETAIL, "artistId", artistId, Routes.artist(artistId)) + }, + contextMenuHost = contextMenuHost, bottomInset = bottomInset, ) } @@ -250,7 +291,10 @@ private fun MainNavigation() { .orEmpty() .ifEmpty { null }, onBack = { navController.popBackStack() }, - onViewArtist = { artistId -> navController.navigate(Routes.artist(artistId)) }, + onViewArtist = { artistId -> + navigateDetail(Routes.ARTIST_DETAIL, "artistId", artistId, Routes.artist(artistId)) + }, + contextMenuHost = contextMenuHost, ) } composable( @@ -272,8 +316,11 @@ private fun MainNavigation() { .orEmpty() .ifEmpty { null }, onBack = { navController.popBackStack() }, - onAlbumClick = { album -> navController.navigate(Routes.album(album.id, album.coverArt)) }, - onViewAlbum = { albumId -> navController.navigate(Routes.album(albumId)) }, + onAlbumClick = { album -> + navigateDetail(Routes.ALBUM_DETAIL, "albumId", album.id, Routes.album(album.id, album.coverArt)) + }, + onViewAlbum = { albumId -> navigateDetail(Routes.ALBUM_DETAIL, "albumId", albumId, Routes.album(albumId)) }, + contextMenuHost = contextMenuHost, ) } composable( @@ -283,8 +330,11 @@ private fun MainNavigation() { PlaylistDetailRoute( playlistId = entry.arguments?.getString("playlistId").orEmpty(), onBack = { navController.popBackStack() }, - onViewAlbum = { albumId -> navController.navigate(Routes.album(albumId)) }, - onViewArtist = { artistId -> navController.navigate(Routes.artist(artistId)) }, + onViewAlbum = { albumId -> navigateDetail(Routes.ALBUM_DETAIL, "albumId", albumId, Routes.album(albumId)) }, + onViewArtist = { artistId -> + navigateDetail(Routes.ARTIST_DETAIL, "artistId", artistId, Routes.artist(artistId)) + }, + contextMenuHost = contextMenuHost, ) } composable(Routes.ADD_SERVER) { @@ -330,8 +380,8 @@ private fun MainNavigation() { Modifier .fillMaxSize() .graphicsLayer { - translationY = (1f - nowPlayingProgress) * miniPlayerTopPx - alpha = (nowPlayingProgress / EXPAND_FADE).coerceAtMost(1f) + translationY = if (nowPlayingFadeOut) 0f else (1f - nowPlayingProgress) * miniPlayerTopPx + alpha = if (nowPlayingFadeOut) nowPlayingProgress else (nowPlayingProgress / EXPAND_FADE).coerceAtMost(1f) }.draggable( orientation = Orientation.Vertical, state = @@ -348,13 +398,14 @@ private fun MainNavigation() { onBack = { settleNowPlaying(false) }, onQueue = { showingQueue = true }, onViewAlbum = { albumId -> - settleNowPlaying(false) - navController.navigate(Routes.album(albumId)) + navigateDetail(Routes.ALBUM_DETAIL, "albumId", albumId, Routes.album(albumId)) + fadeNowPlaying() }, onViewArtist = { artistId -> - settleNowPlaying(false) - navController.navigate(Routes.artist(artistId)) + navigateDetail(Routes.ARTIST_DETAIL, "artistId", artistId, Routes.artist(artistId)) + fadeNowPlaying() }, + contextMenuHost = contextMenuHost, modifier = Modifier.fillMaxSize(), ) } @@ -389,6 +440,10 @@ private fun MainNavigation() { ) } } + + contextMenuHost.target?.let { target -> + ItemContextMenu(target = target, actions = contextMenuHost.actions, onDismiss = { contextMenuHost.dismiss() }) + } } } diff --git a/app/src/main/kotlin/com/subtracks/ui/components/ItemContextMenu.kt b/app/src/main/kotlin/com/subtracks/ui/components/ItemContextMenu.kt index 6642d90b..b48e9d0f 100644 --- a/app/src/main/kotlin/com/subtracks/ui/components/ItemContextMenu.kt +++ b/app/src/main/kotlin/com/subtracks/ui/components/ItemContextMenu.kt @@ -1,7 +1,14 @@ package com.subtracks.ui.components import androidx.compose.foundation.clickable +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.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -25,6 +32,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope 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.Color @@ -92,6 +100,25 @@ class ItemActions( val viewArtist: ((String) -> Unit)? = null, ) +class ContextMenuHost { + var target by mutableStateOf(null) + private set + var actions by mutableStateOf(ItemActions()) + private set + + fun show( + target: MenuTarget, + actions: ItemActions, + ) { + this.target = target + this.actions = actions + } + + fun dismiss() { + target = null + } +} + @OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemContextMenu( @@ -103,9 +130,9 @@ fun ItemContextMenu( val scope = rememberCoroutineScope() fun dismiss(after: () -> Unit = {}) { + after() scope.launch { sheetState.hide() - after() onDismiss() } } @@ -113,6 +140,7 @@ fun ItemContextMenu( ModalBottomSheet( onDismissRequest = { dismiss() }, sheetState = sheetState, + dragHandle = null, ) { MenuHeader(target) when (target) { @@ -159,39 +187,40 @@ fun ItemContextMenu( MenuItem(Icons.Rounded.Shuffle, "Shuffle") { dismiss { actions.shufflePlaylist(target.playlist) } } } } + Spacer(Modifier.height(16.dp)) } } @Composable private fun MenuHeader(target: MenuTarget) { - ListItem( - headlineContent = { + Row( + modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 16.dp, end = 16.dp, bottom = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + CoverArt( + ref = target.coverArt, + name = target.title, + modifier = Modifier.size(48.dp).clip(if (target is MenuTarget.Artist) CircleShape else RoundedCornerShape(8.dp)), + ) + Spacer(Modifier.width(16.dp)) + Column(Modifier.weight(1f)) { Text( text = target.title, style = MaterialTheme.typography.titleMedium, maxLines = 1, overflow = TextOverflow.Ellipsis, ) - }, - supportingContent = target.subtitle?.takeIf { it.isNotBlank() }?.let { subtitle -> - { - Text( - text = subtitle, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - }, - leadingContent = { - CoverArt( - ref = target.coverArt, - name = target.title, - modifier = Modifier.size(48.dp).clip(if (target is MenuTarget.Artist) CircleShape else RoundedCornerShape(8.dp)), - ) - }, - colors = ListItemDefaults.colors(containerColor = Color.Transparent), - ) + Text( + text = subtitle, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + } } @Composable diff --git a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt index 09ecc7bb..baa1e801 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt @@ -13,9 +13,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight @@ -28,10 +26,10 @@ import com.subtracks.data.model.Disc import com.subtracks.data.model.QueueKind import com.subtracks.data.model.Song import com.subtracks.playback.PlaybackController +import com.subtracks.ui.components.ContextMenuHost import com.subtracks.ui.components.HeroDetailScaffold import com.subtracks.ui.components.HeroHeader import com.subtracks.ui.components.ItemActions -import com.subtracks.ui.components.ItemContextMenu import com.subtracks.ui.components.MenuTarget import com.subtracks.ui.theme.ArtworkColors import com.subtracks.ui.theme.rememberArtworkColors @@ -48,6 +46,7 @@ fun AlbumDetailRoute( coverArtId: String?, onBack: () -> Unit, onViewArtist: (String) -> Unit, + contextMenuHost: ContextMenuHost? = null, viewModel: AlbumDetailViewModel = koinViewModel(key = albumId) { parametersOf(albumId) }, playbackController: PlaybackController = koinInject(), ) { @@ -57,7 +56,6 @@ fun AlbumDetailRoute( val playback by playbackController.state.collectAsStateWithLifecycle() val context = playback.context val shortcutArt = remember(coverArtId) { coverArtId?.let { viewModel.coverArt(it, true) } } - var menuTarget by remember { mutableStateOf(null) } val actions = ItemActions( playSong = { song -> songs.indexOfFirst { it.id == song.id }.takeIf { it >= 0 }?.let(viewModel::play) }, @@ -74,15 +72,12 @@ fun AlbumDetailRoute( artwork = rememberArtworkColors(shortcutArt ?: viewModel.coverArt(album?.coverArt, true), THEME_TRANSITION_MS), onBack = onBack, onSongClick = viewModel::play, - onSongLongClick = { menuTarget = it }, + onSongLongClick = { contextMenuHost?.show(it, actions) }, onShuffle = viewModel::shuffle, onPlay = viewModel::playAll, - onMore = { album?.let { menuTarget = MenuTarget.Album(it, viewModel.coverArt(it.coverArt, true)) } }, + onMore = { album?.let { contextMenuHost?.show(MenuTarget.Album(it, viewModel.coverArt(it.coverArt, true)), actions) } }, playingSongId = playback.item?.id.takeIf { context?.kind == QueueKind.Album && context.refId == albumId }, ) - menuTarget?.let { target -> - ItemContextMenu(target = target, actions = actions, onDismiss = { menuTarget = null }) - } } @Composable diff --git a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt index efd6ca57..2de30b00 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt @@ -61,9 +61,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.subtracks.data.model.Album import com.subtracks.data.model.Artist import com.subtracks.data.model.CoverArtRef +import com.subtracks.ui.components.ContextMenuHost import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.ItemActions -import com.subtracks.ui.components.ItemContextMenu import com.subtracks.ui.components.MenuTarget import com.subtracks.ui.components.rememberViewportFill import com.subtracks.ui.theme.ArtworkColors @@ -91,6 +91,7 @@ fun ArtistDetailRoute( onBack: () -> Unit, onAlbumClick: (Album) -> Unit, onViewAlbum: (String) -> Unit, + contextMenuHost: ContextMenuHost? = null, viewModel: ArtistDetailViewModel = koinViewModel(key = artistId) { parametersOf(artistId) }, ) { val artist by viewModel.artist.collectAsStateWithLifecycle(initialValue = null) @@ -98,7 +99,6 @@ fun ArtistDetailRoute( val art by viewModel.art.collectAsStateWithLifecycle() val artThumbnail by viewModel.artThumbnail.collectAsStateWithLifecycle() val shortcutArt = remember(coverArtId) { coverArtId?.let { viewModel.coverArt(it, true) } } - var menuTarget by remember { mutableStateOf(null) } val actions = ItemActions( playAlbum = { viewModel.playAlbum(it.id) }, @@ -115,12 +115,9 @@ fun ArtistDetailRoute( coverArt = viewModel::coverArt, onBack = onBack, onAlbumClick = onAlbumClick, - onAlbumLongClick = { menuTarget = it }, - onMore = { artist?.let { menuTarget = MenuTarget.Artist(it, artThumbnail ?: art) } }, + onAlbumLongClick = { contextMenuHost?.show(it, actions) }, + onMore = { artist?.let { contextMenuHost?.show(MenuTarget.Artist(it, artThumbnail ?: art), actions) } }, ) - menuTarget?.let { target -> - ItemContextMenu(target = target, actions = actions, onDismiss = { menuTarget = null }) - } } @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt index 47374770..4ba142bd 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt @@ -114,8 +114,8 @@ import com.subtracks.data.prefs.PlaylistSort import com.subtracks.data.prefs.SongSort import com.subtracks.data.prefs.StarredFilter import com.subtracks.playback.PlaybackController +import com.subtracks.ui.components.ContextMenuHost import com.subtracks.ui.components.ItemActions -import com.subtracks.ui.components.ItemContextMenu import com.subtracks.ui.components.MenuTarget import com.subtracks.ui.components.statusBarScrim import com.subtracks.ui.theme.ArtworkColors @@ -194,13 +194,13 @@ fun LibraryRoute( onOpenSettings: () -> Unit, onViewAlbum: (String) -> Unit, onViewArtist: (String) -> Unit, + contextMenuHost: ContextMenuHost? = null, bottomInset: Dp, viewModel: LibraryViewModel = koinViewModel(), playbackController: PlaybackController = koinInject(), ) { var selectedTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) } var previousTab by rememberSaveable { mutableStateOf(LibraryTab.Albums) } - var menuTarget by remember { mutableStateOf(null) } val syncing by viewModel.syncing.collectAsStateWithLifecycle() val playingSongId by viewModel.playingSongId.collectAsStateWithLifecycle() val listTab = selectedTab.listTab() @@ -220,6 +220,17 @@ fun LibraryRoute( } val playback by playbackController.state.collectAsStateWithLifecycle() val artwork = rememberArtworkColors(playbackController.coverArt(playback.item, thumbnail = true)) + val itemActions = + ItemActions( + playSong = { viewModel.playSong(it.id) }, + playAlbum = { viewModel.playAlbum(it.id) }, + shuffleAlbum = { viewModel.shuffleAlbum(it.id) }, + playPlaylist = { viewModel.playPlaylist(it.id) }, + shufflePlaylist = { viewModel.shufflePlaylist(it.id) }, + setStar = viewModel::setStar, + viewAlbum = onViewAlbum, + viewArtist = onViewArtist, + ) LibraryScreen( selectedTab = selectedTab, onTabSelected = { selectedTab = it }, @@ -237,7 +248,7 @@ fun LibraryRoute( onSongClick = viewModel::playSong, onSync = viewModel::sync, onOpenSettings = onOpenSettings, - onItemLongClick = { menuTarget = it }, + onItemLongClick = { contextMenuHost?.show(it, itemActions) }, playingSongId = playingSongId, listQuery = listQuery, resetKeys = resetKeys, @@ -261,23 +272,6 @@ fun LibraryRoute( search = search, onSearchChange = { viewModel.setSearch(listTab, it) }, ) - menuTarget?.let { target -> - ItemContextMenu( - target = target, - actions = - ItemActions( - playSong = { viewModel.playSong(it.id) }, - playAlbum = { viewModel.playAlbum(it.id) }, - shuffleAlbum = { viewModel.shuffleAlbum(it.id) }, - playPlaylist = { viewModel.playPlaylist(it.id) }, - shufflePlaylist = { viewModel.shufflePlaylist(it.id) }, - setStar = viewModel::setStar, - viewAlbum = onViewAlbum, - viewArtist = onViewArtist, - ), - onDismiss = { menuTarget = null }, - ) - } } @OptIn(ExperimentalMaterial3Api::class) @@ -531,7 +525,10 @@ fun LibraryScreen( } if (showOptions) { - ModalBottomSheet(onDismissRequest = { showOptions = false }) { + ModalBottomSheet( + onDismissRequest = { showOptions = false }, + dragHandle = null, + ) { ListOptionsSheet( listQuery = listQuery, sortOptions = sortOptions, diff --git a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt index 088770a4..692d3cae 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/PlaylistDetailScreen.kt @@ -16,10 +16,10 @@ import com.subtracks.data.model.Playlist import com.subtracks.data.model.QueueKind import com.subtracks.data.model.SongListItem import com.subtracks.playback.PlaybackController +import com.subtracks.ui.components.ContextMenuHost import com.subtracks.ui.components.HeroDetailScaffold import com.subtracks.ui.components.HeroHeader import com.subtracks.ui.components.ItemActions -import com.subtracks.ui.components.ItemContextMenu import com.subtracks.ui.components.MenuTarget import com.subtracks.ui.theme.ArtworkColors import com.subtracks.ui.theme.rememberArtworkColors @@ -36,13 +36,13 @@ fun PlaylistDetailRoute( onBack: () -> Unit, onViewAlbum: (String) -> Unit, onViewArtist: (String) -> Unit, + contextMenuHost: ContextMenuHost? = null, viewModel: PlaylistDetailViewModel = koinViewModel(key = playlistId) { parametersOf(playlistId) }, playbackController: PlaybackController = koinInject(), ) { val playlist by viewModel.playlist.collectAsStateWithLifecycle(initialValue = null) val playback by playbackController.state.collectAsStateWithLifecycle() val context = playback.context - var menuTarget by remember { mutableStateOf(null) } val actions = ItemActions( playPlaylist = { viewModel.playAll() }, @@ -58,15 +58,12 @@ fun PlaylistDetailRoute( artwork = rememberArtworkColors(viewModel.coverArt(playlist?.coverArt, true), THEME_TRANSITION_MS), onBack = onBack, onSongClick = viewModel::play, - onSongLongClick = { menuTarget = it }, + onSongLongClick = { contextMenuHost?.show(it, actions) }, onShuffle = viewModel::shuffle, onPlay = viewModel::playAll, - onMore = { playlist?.let { menuTarget = MenuTarget.Playlist(it, viewModel.coverArt(it.coverArt, true)) } }, + onMore = { playlist?.let { contextMenuHost?.show(MenuTarget.Playlist(it, viewModel.coverArt(it.coverArt, true)), actions) } }, playingSongId = playback.item?.id.takeIf { context?.kind == QueueKind.Playlist && context.refId == playlistId }, ) - menuTarget?.let { target -> - ItemContextMenu(target = target, actions = actions, onDismiss = { menuTarget = null }) - } } @Composable diff --git a/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt b/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt index 9fc4919d..a04f42ec 100644 --- a/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/playback/NowPlayingScreen.kt @@ -69,9 +69,9 @@ import com.subtracks.data.repo.LibraryRepository import com.subtracks.playback.PlaybackController import com.subtracks.playback.PlaybackState import com.subtracks.playback.RepeatMode +import com.subtracks.ui.components.ContextMenuHost import com.subtracks.ui.components.CoverArt import com.subtracks.ui.components.ItemActions -import com.subtracks.ui.components.ItemContextMenu import com.subtracks.ui.components.MenuTarget import com.subtracks.ui.theme.ArtworkColors import com.subtracks.ui.theme.ArtworkSeedCache @@ -87,6 +87,7 @@ fun NowPlayingRoute( onQueue: () -> Unit, onViewAlbum: (String) -> Unit, onViewArtist: (String) -> Unit, + contextMenuHost: ContextMenuHost? = null, modifier: Modifier = Modifier, controller: PlaybackController = koinInject(), libraryRepository: LibraryRepository = koinInject(), @@ -101,9 +102,14 @@ fun NowPlayingRoute( libraryRepository.song(sourceId, songId).collect { value = it } } } - var menuTarget by remember { mutableStateOf(null) } - val scope = rememberCoroutineScope() val context = LocalPlatformContext.current + val scope = rememberCoroutineScope() + val actions = + ItemActions( + setStar = { type, id, starred -> scope.launch { libraryRepository.setStar(type, id, starred) } }, + viewAlbum = onViewAlbum, + viewArtist = onViewArtist, + ) val art = controller.coverArt(state.item) val thumbnail = controller.coverArt(state.item, thumbnail = true) val queueContext = state.context @@ -138,22 +144,10 @@ fun NowPlayingRoute( onPrevious = controller::previous, onShuffle = controller::toggleShuffle, onRepeat = controller::cycleRepeat, - onMore = { song?.let { menuTarget = MenuTarget.Song(it, thumbnail) } }, + onMore = { song?.let { contextMenuHost?.show(MenuTarget.Song(it, thumbnail), actions) } }, onSeek = controller::seekTo, modifier = modifier, ) - menuTarget?.let { target -> - ItemContextMenu( - target = target, - actions = - ItemActions( - setStar = { type, id, starred -> scope.launch { libraryRepository.setStar(type, id, starred) } }, - viewAlbum = onViewAlbum, - viewArtist = onViewArtist, - ), - onDismiss = { menuTarget = null }, - ) - } } internal fun prefetchImage(