mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 15:11:58 +01:00
add item menus to the detail screens and now playing
Album, artist and playlist detail screens get a more button plus long-press on their song and album rows; now playing gets a more button for the current song. Song menus can reach the album and artist and star; the now-playing song is read from Room so its star state is current.
This commit is contained in:
@@ -611,6 +611,12 @@ interface LibraryDao {
|
||||
artistId: String,
|
||||
): Flow<Artist?>
|
||||
|
||||
@Query("SELECT * FROM songs WHERE sourceId = :sourceId AND id = :songId")
|
||||
fun song(
|
||||
sourceId: Long,
|
||||
songId: String,
|
||||
): Flow<Song?>
|
||||
|
||||
@Query("SELECT * FROM albums WHERE sourceId = :sourceId AND artistId = :artistId ORDER BY year DESC, name COLLATE NOCASE, id")
|
||||
fun albumsForArtist(
|
||||
sourceId: Long,
|
||||
|
||||
@@ -211,6 +211,11 @@ class LibraryRepository(
|
||||
artistId: String,
|
||||
): Flow<Artist?> = db.libraryDao().artist(sourceId, artistId)
|
||||
|
||||
fun song(
|
||||
sourceId: Long,
|
||||
songId: String,
|
||||
): Flow<Song?> = db.libraryDao().song(sourceId, songId)
|
||||
|
||||
fun artistAlbums(
|
||||
sourceId: Long,
|
||||
artistId: String,
|
||||
|
||||
@@ -50,7 +50,7 @@ fun appModule(
|
||||
viewModel { SettingsViewModel(get(), get()) }
|
||||
viewModel { AddSourceViewModel(get(), get()) }
|
||||
viewModel { params -> AlbumDetailViewModel(get(), get(), get(), params.get()) }
|
||||
viewModel { params -> ArtistDetailViewModel(get(), get(), params.get()) }
|
||||
viewModel { params -> ArtistDetailViewModel(get(), get(), get(), params.get()) }
|
||||
viewModel { params -> PlaylistDetailViewModel(get(), get(), get(), params.get()) }
|
||||
viewModel { QueueViewModel(get(), get()) }
|
||||
}
|
||||
|
||||
@@ -250,6 +250,7 @@ private fun MainNavigation() {
|
||||
.orEmpty()
|
||||
.ifEmpty { null },
|
||||
onBack = { navController.popBackStack() },
|
||||
onViewArtist = { artistId -> navController.navigate(Routes.artist(artistId)) },
|
||||
)
|
||||
}
|
||||
composable(
|
||||
@@ -272,6 +273,7 @@ private fun MainNavigation() {
|
||||
.ifEmpty { null },
|
||||
onBack = { navController.popBackStack() },
|
||||
onAlbumClick = { album -> navController.navigate(Routes.album(album.id, album.coverArt)) },
|
||||
onViewAlbum = { albumId -> navController.navigate(Routes.album(albumId)) },
|
||||
)
|
||||
}
|
||||
composable(
|
||||
@@ -281,6 +283,8 @@ 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)) },
|
||||
)
|
||||
}
|
||||
composable(Routes.ADD_SERVER) {
|
||||
@@ -343,6 +347,14 @@ private fun MainNavigation() {
|
||||
NowPlayingRoute(
|
||||
onBack = { settleNowPlaying(false) },
|
||||
onQueue = { showingQueue = true },
|
||||
onViewAlbum = { albumId ->
|
||||
settleNowPlaying(false)
|
||||
navController.navigate(Routes.album(albumId))
|
||||
},
|
||||
onViewArtist = { artistId ->
|
||||
settleNowPlaying(false)
|
||||
navController.navigate(Routes.artist(artistId))
|
||||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -64,14 +64,14 @@ sealed interface MenuTarget {
|
||||
}
|
||||
|
||||
class ItemActions(
|
||||
val playSong: (SongModel) -> Unit = {},
|
||||
val playSong: ((SongModel) -> Unit)? = null,
|
||||
val playAlbum: (AlbumModel) -> Unit = {},
|
||||
val shuffleAlbum: (AlbumModel) -> Unit = {},
|
||||
val playPlaylist: (PlaylistModel) -> Unit = {},
|
||||
val shufflePlaylist: (PlaylistModel) -> Unit = {},
|
||||
val setStar: (StarType, String, Boolean) -> Unit = { _, _, _ -> },
|
||||
val viewAlbum: (String) -> Unit = {},
|
||||
val viewArtist: (String) -> Unit = {},
|
||||
val viewAlbum: ((String) -> Unit)? = null,
|
||||
val viewArtist: ((String) -> Unit)? = null,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -89,15 +89,21 @@ fun ItemContextMenu(
|
||||
MenuHeader(target)
|
||||
when (target) {
|
||||
is MenuTarget.Song -> {
|
||||
MenuItem(Icons.Rounded.PlayArrow, "Play") { dismissThen { actions.playSong(target.song) } }
|
||||
actions.playSong?.let { play ->
|
||||
MenuItem(Icons.Rounded.PlayArrow, "Play") { dismissThen { play(target.song) } }
|
||||
}
|
||||
MenuItem(starIcon(target.song.starred), starLabel(target.song.starred)) {
|
||||
dismissThen { actions.setStar(StarType.Song, target.song.id, target.song.starred == null) }
|
||||
}
|
||||
target.song.albumId?.let { albumId ->
|
||||
MenuItem(Icons.Rounded.Album, "View album") { dismissThen { actions.viewAlbum(albumId) } }
|
||||
actions.viewAlbum?.let { view ->
|
||||
MenuItem(Icons.Rounded.Album, "View album") { dismissThen { view(albumId) } }
|
||||
}
|
||||
}
|
||||
target.song.artistId?.let { artistId ->
|
||||
MenuItem(Icons.Rounded.Person, "View artist") { dismissThen { actions.viewArtist(artistId) } }
|
||||
actions.viewArtist?.let { view ->
|
||||
MenuItem(Icons.Rounded.Person, "View artist") { dismissThen { view(artistId) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +114,9 @@ fun ItemContextMenu(
|
||||
dismissThen { actions.setStar(StarType.Album, target.album.id, target.album.starred == null) }
|
||||
}
|
||||
target.album.artistId?.let { artistId ->
|
||||
MenuItem(Icons.Rounded.Person, "View artist") { dismissThen { actions.viewArtist(artistId) } }
|
||||
actions.viewArtist?.let { view ->
|
||||
MenuItem(Icons.Rounded.Person, "View artist") { dismissThen { view(artistId) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.subtracks.ui.library
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -13,7 +13,9 @@ 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,6 +30,9 @@ import com.subtracks.data.model.Song
|
||||
import com.subtracks.playback.PlaybackController
|
||||
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
|
||||
import org.koin.compose.koinInject
|
||||
@@ -42,6 +47,7 @@ fun AlbumDetailRoute(
|
||||
albumId: String,
|
||||
coverArtId: String?,
|
||||
onBack: () -> Unit,
|
||||
onViewArtist: (String) -> Unit,
|
||||
viewModel: AlbumDetailViewModel = koinViewModel(key = albumId) { parametersOf(albumId) },
|
||||
playbackController: PlaybackController = koinInject(),
|
||||
) {
|
||||
@@ -51,6 +57,15 @@ 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<MenuTarget?>(null) }
|
||||
val actions =
|
||||
ItemActions(
|
||||
playSong = { song -> songs.indexOfFirst { it.id == song.id }.takeIf { it >= 0 }?.let(viewModel::play) },
|
||||
playAlbum = { viewModel.playAll() },
|
||||
shuffleAlbum = { viewModel.shuffle() },
|
||||
setStar = viewModel::setStar,
|
||||
viewArtist = onViewArtist,
|
||||
)
|
||||
AlbumDetailScreen(
|
||||
album = album,
|
||||
songs = songs,
|
||||
@@ -59,10 +74,15 @@ fun AlbumDetailRoute(
|
||||
artwork = rememberArtworkColors(shortcutArt ?: viewModel.coverArt(album?.coverArt, true), THEME_TRANSITION_MS),
|
||||
onBack = onBack,
|
||||
onSongClick = viewModel::play,
|
||||
onSongLongClick = { menuTarget = it },
|
||||
onShuffle = viewModel::shuffle,
|
||||
onPlay = viewModel::playAll,
|
||||
onMore = { album?.let { menuTarget = MenuTarget.Album(it) } },
|
||||
playingSongId = playback.item?.id.takeIf { context?.kind == QueueKind.Album && context.refId == albumId },
|
||||
)
|
||||
menuTarget?.let { target ->
|
||||
ItemContextMenu(target = target, actions = actions, onDismiss = { menuTarget = null })
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -74,6 +94,7 @@ fun AlbumDetailScreen(
|
||||
artwork: ArtworkColors?,
|
||||
onBack: () -> Unit,
|
||||
onSongClick: (Int) -> Unit,
|
||||
onSongLongClick: (MenuTarget) -> Unit = {},
|
||||
onShuffle: () -> Unit = {},
|
||||
onPlay: () -> Unit = { onSongClick(0) },
|
||||
onDownload: () -> Unit = {},
|
||||
@@ -137,7 +158,11 @@ fun AlbumDetailScreen(
|
||||
song = song,
|
||||
isPlaying = song.id == playingSongId,
|
||||
trackNumber = song.track,
|
||||
modifier = rowModifier.clickable { onSongClick(index) },
|
||||
modifier =
|
||||
rowModifier.combinedClickable(
|
||||
onClick = { onSongClick(index) },
|
||||
onLongClick = { onSongLongClick(MenuTarget.Song(song)) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.subtracks.data.model.Disc
|
||||
import com.subtracks.data.model.Song
|
||||
import com.subtracks.data.repo.LibraryRepository
|
||||
import com.subtracks.data.repo.SourceRepository
|
||||
import com.subtracks.data.source.StarType
|
||||
import com.subtracks.playback.PlaybackController
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -53,4 +54,12 @@ class AlbumDetailViewModel(
|
||||
playbackController.shuffleAlbum(sourceId.first(), albumId)
|
||||
}
|
||||
}
|
||||
|
||||
fun setStar(
|
||||
type: StarType,
|
||||
id: String,
|
||||
starred: Boolean,
|
||||
) {
|
||||
viewModelScope.launch { libraryRepository.setStar(type, id, starred) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -27,6 +28,7 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.MoreHoriz
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@@ -39,7 +41,9 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
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.draw.alpha
|
||||
@@ -58,6 +62,9 @@ import com.subtracks.data.model.Album
|
||||
import com.subtracks.data.model.Artist
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
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
|
||||
import com.subtracks.ui.theme.ArtworkTheme
|
||||
@@ -83,6 +90,7 @@ fun ArtistDetailRoute(
|
||||
coverArtId: String?,
|
||||
onBack: () -> Unit,
|
||||
onAlbumClick: (Album) -> Unit,
|
||||
onViewAlbum: (String) -> Unit,
|
||||
viewModel: ArtistDetailViewModel = koinViewModel(key = artistId) { parametersOf(artistId) },
|
||||
) {
|
||||
val artist by viewModel.artist.collectAsStateWithLifecycle(initialValue = null)
|
||||
@@ -90,6 +98,14 @@ 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<MenuTarget?>(null) }
|
||||
val actions =
|
||||
ItemActions(
|
||||
playAlbum = { viewModel.playAlbum(it.id) },
|
||||
shuffleAlbum = { viewModel.shuffleAlbum(it.id) },
|
||||
setStar = viewModel::setStar,
|
||||
viewAlbum = onViewAlbum,
|
||||
)
|
||||
ArtistDetailScreen(
|
||||
artist = artist,
|
||||
albums = albums,
|
||||
@@ -99,7 +115,12 @@ fun ArtistDetailRoute(
|
||||
coverArt = viewModel::coverArt,
|
||||
onBack = onBack,
|
||||
onAlbumClick = onAlbumClick,
|
||||
onAlbumLongClick = { menuTarget = it },
|
||||
onMore = { artist?.let { menuTarget = MenuTarget.Artist(it) } },
|
||||
)
|
||||
menuTarget?.let { target ->
|
||||
ItemContextMenu(target = target, actions = actions, onDismiss = { menuTarget = null })
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@@ -113,6 +134,8 @@ fun ArtistDetailScreen(
|
||||
coverArt: (String?, Boolean) -> CoverArtRef?,
|
||||
onBack: () -> Unit,
|
||||
onAlbumClick: (Album) -> Unit,
|
||||
onAlbumLongClick: (MenuTarget) -> Unit = {},
|
||||
onMore: () -> Unit = {},
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val listState = rememberLazyGridState()
|
||||
@@ -213,7 +236,10 @@ fun ArtistDetailScreen(
|
||||
.padding(
|
||||
start = if (index % 2 == 0) 16.dp else 0.dp,
|
||||
end = if (index % 2 == 1) 16.dp else 0.dp,
|
||||
).clickable { onAlbumClick(album) },
|
||||
).combinedClickable(
|
||||
onClick = { onAlbumClick(album) },
|
||||
onLongClick = { onAlbumLongClick(MenuTarget.Album(album)) },
|
||||
),
|
||||
) {
|
||||
CoverArt(
|
||||
ref = coverArt(album.coverArt, false),
|
||||
@@ -267,6 +293,11 @@ fun ArtistDetailScreen(
|
||||
Icon(Icons.AutoMirrored.Rounded.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onMore, modifier = Modifier.graphicsLayer { alpha = barFraction }) {
|
||||
Icon(Icons.Rounded.MoreHoriz, contentDescription = "More options")
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
|
||||
modifier =
|
||||
Modifier
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.subtracks.data.model.Artist
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
import com.subtracks.data.repo.LibraryRepository
|
||||
import com.subtracks.data.repo.SourceRepository
|
||||
import com.subtracks.data.source.StarType
|
||||
import com.subtracks.playback.PlaybackController
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -14,13 +16,15 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class ArtistDetailViewModel(
|
||||
libraryRepository: LibraryRepository,
|
||||
private val libraryRepository: LibraryRepository,
|
||||
private val sourceRepository: SourceRepository,
|
||||
private val playbackController: PlaybackController,
|
||||
private val artistId: String,
|
||||
) : ViewModel() {
|
||||
private val sourceId = libraryRepository.activeSourceId.filterNotNull()
|
||||
@@ -63,4 +67,24 @@ class ArtistDetailViewModel(
|
||||
coverArt: String?,
|
||||
thumbnail: Boolean,
|
||||
): CoverArtRef? = sourceRepository.coverArt(coverArt, thumbnail)
|
||||
|
||||
fun setStar(
|
||||
type: StarType,
|
||||
id: String,
|
||||
starred: Boolean,
|
||||
) {
|
||||
viewModelScope.launch { libraryRepository.setStar(type, id, starred) }
|
||||
}
|
||||
|
||||
fun playAlbum(albumId: String) {
|
||||
viewModelScope.launch {
|
||||
playbackController.playAlbum(sourceId.first(), albumId, 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun shuffleAlbum(albumId: String) {
|
||||
viewModelScope.launch {
|
||||
playbackController.shuffleAlbum(sourceId.first(), albumId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.subtracks.ui.library
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
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.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
@@ -15,6 +18,9 @@ import com.subtracks.data.model.SongListItem
|
||||
import com.subtracks.playback.PlaybackController
|
||||
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
|
||||
import org.koin.compose.koinInject
|
||||
@@ -28,12 +34,23 @@ private const val THEME_TRANSITION_MS = 100
|
||||
fun PlaylistDetailRoute(
|
||||
playlistId: String,
|
||||
onBack: () -> Unit,
|
||||
onViewAlbum: (String) -> Unit,
|
||||
onViewArtist: (String) -> Unit,
|
||||
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<MenuTarget?>(null) }
|
||||
val actions =
|
||||
ItemActions(
|
||||
playPlaylist = { viewModel.playAll() },
|
||||
shufflePlaylist = { viewModel.shuffle() },
|
||||
setStar = viewModel::setStar,
|
||||
viewAlbum = onViewAlbum,
|
||||
viewArtist = onViewArtist,
|
||||
)
|
||||
PlaylistDetailScreen(
|
||||
playlist = playlist,
|
||||
songs = viewModel.songs.collectAsLazyPagingItems(),
|
||||
@@ -41,10 +58,15 @@ fun PlaylistDetailRoute(
|
||||
artwork = rememberArtworkColors(viewModel.coverArt(playlist?.coverArt, true), THEME_TRANSITION_MS),
|
||||
onBack = onBack,
|
||||
onSongClick = viewModel::play,
|
||||
onSongLongClick = { menuTarget = it },
|
||||
onShuffle = viewModel::shuffle,
|
||||
onPlay = viewModel::playAll,
|
||||
onMore = { playlist?.let { menuTarget = MenuTarget.Playlist(it) } },
|
||||
playingSongId = playback.item?.id.takeIf { context?.kind == QueueKind.Playlist && context.refId == playlistId },
|
||||
)
|
||||
menuTarget?.let { target ->
|
||||
ItemContextMenu(target = target, actions = actions, onDismiss = { menuTarget = null })
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -55,8 +77,10 @@ fun PlaylistDetailScreen(
|
||||
artwork: ArtworkColors?,
|
||||
onBack: () -> Unit,
|
||||
onSongClick: (Int) -> Unit,
|
||||
onSongLongClick: (MenuTarget) -> Unit = {},
|
||||
onShuffle: () -> Unit = {},
|
||||
onPlay: () -> Unit = { onSongClick(0) },
|
||||
onMore: () -> Unit = {},
|
||||
playingSongId: String? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -75,7 +99,7 @@ fun PlaylistDetailScreen(
|
||||
onPlay = onPlay,
|
||||
onShuffle = onShuffle,
|
||||
onDownload = {},
|
||||
onMore = {},
|
||||
onMore = onMore,
|
||||
topInset = topInset,
|
||||
controlsModifier = controlsModifier,
|
||||
)
|
||||
@@ -89,7 +113,11 @@ fun PlaylistDetailScreen(
|
||||
coverArtId = item.coverArt,
|
||||
coverArt = coverArt,
|
||||
isPlaying = item.song.id == playingSongId,
|
||||
modifier = rowModifier.clickable { onSongClick(index) },
|
||||
modifier =
|
||||
rowModifier.combinedClickable(
|
||||
onClick = { onSongClick(index) },
|
||||
onLongClick = { onSongLongClick(MenuTarget.Song(item.song)) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.subtracks.data.model.Playlist
|
||||
import com.subtracks.data.model.SongListItem
|
||||
import com.subtracks.data.repo.LibraryRepository
|
||||
import com.subtracks.data.repo.SourceRepository
|
||||
import com.subtracks.data.source.StarType
|
||||
import com.subtracks.playback.PlaybackController
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -55,4 +56,12 @@ class PlaylistDetailViewModel(
|
||||
playbackController.shufflePlaylist(sourceId.first(), playlistId)
|
||||
}
|
||||
}
|
||||
|
||||
fun setStar(
|
||||
type: StarType,
|
||||
id: String,
|
||||
starred: Boolean,
|
||||
) {
|
||||
viewModelScope.launch { libraryRepository.setStar(type, id, starred) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
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
|
||||
@@ -62,26 +64,45 @@ import coil3.request.CachePolicy
|
||||
import coil3.request.ImageRequest
|
||||
import com.subtracks.data.model.CoverArtRef
|
||||
import com.subtracks.data.model.QueueKind
|
||||
import com.subtracks.data.model.Song
|
||||
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.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
|
||||
import com.subtracks.ui.theme.ArtworkTheme
|
||||
import com.subtracks.ui.theme.HeroGradient
|
||||
import com.subtracks.ui.theme.rememberArtworkColors
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@Composable
|
||||
fun NowPlayingRoute(
|
||||
onBack: () -> Unit,
|
||||
onQueue: () -> Unit,
|
||||
onViewAlbum: (String) -> Unit,
|
||||
onViewArtist: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
controller: PlaybackController = koinInject(),
|
||||
libraryRepository: LibraryRepository = koinInject(),
|
||||
) {
|
||||
val state by controller.state.collectAsStateWithLifecycle()
|
||||
val positionMs by controller.positionMs.collectAsStateWithLifecycle()
|
||||
val sourceId = state.context?.sourceId
|
||||
val songId = state.item?.id
|
||||
val song by
|
||||
produceState<Song?>(null, sourceId, songId) {
|
||||
if (sourceId != null && songId != null) {
|
||||
libraryRepository.song(sourceId, songId).collect { value = it }
|
||||
}
|
||||
}
|
||||
var menuTarget by remember { mutableStateOf<MenuTarget?>(null) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalPlatformContext.current
|
||||
val art = controller.coverArt(state.item)
|
||||
val thumbnail = controller.coverArt(state.item, thumbnail = true)
|
||||
@@ -117,9 +138,22 @@ fun NowPlayingRoute(
|
||||
onPrevious = controller::previous,
|
||||
onShuffle = controller::toggleShuffle,
|
||||
onRepeat = controller::cycleRepeat,
|
||||
onMore = { song?.let { menuTarget = MenuTarget.Song(it) } },
|
||||
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(
|
||||
@@ -159,6 +193,7 @@ fun NowPlayingScreen(
|
||||
onPrevious: () -> Unit,
|
||||
onShuffle: () -> Unit = {},
|
||||
onRepeat: () -> Unit = {},
|
||||
onMore: () -> Unit = {},
|
||||
onSeek: (Long) -> Unit,
|
||||
thumbnailRef: CoverArtRef? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -395,7 +430,7 @@ fun NowPlayingScreen(
|
||||
modifier = Modifier.size(30.dp),
|
||||
)
|
||||
}
|
||||
IconButton(onClick = {}, modifier = Modifier.size(40.dp)) {
|
||||
IconButton(onClick = onMore, modifier = Modifier.size(40.dp)) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.MoreHoriz,
|
||||
contentDescription = "More",
|
||||
|
||||
Reference in New Issue
Block a user