mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Add long click queue operations
This commit is contained in:
parent
6471b198b2
commit
73b626facd
10 changed files with 176 additions and 22 deletions
|
|
@ -9,6 +9,7 @@ import kotlin.time.Duration
|
|||
data class AudioItem(
|
||||
val id: UUID,
|
||||
val albumId: UUID?,
|
||||
val artistId: UUID?,
|
||||
val title: String?,
|
||||
val albumTitle: String?,
|
||||
val artistNames: String?,
|
||||
|
|
@ -24,6 +25,10 @@ data class AudioItem(
|
|||
AudioItem(
|
||||
id = item.id,
|
||||
albumId = item.data.albumId,
|
||||
artistId =
|
||||
item.data.artistItems
|
||||
?.firstOrNull()
|
||||
?.id,
|
||||
title = item.title,
|
||||
albumTitle = item.data.album,
|
||||
artistNames = item.data.albumArtist,
|
||||
|
|
|
|||
|
|
@ -244,12 +244,30 @@ class MusicService
|
|||
updateQueueSize()
|
||||
}
|
||||
|
||||
suspend fun moveQueue(
|
||||
index: Int,
|
||||
newIndex: Int,
|
||||
) = withContext(Dispatchers.Main) {
|
||||
player.moveMediaItem(index, newIndex)
|
||||
updateQueueSize()
|
||||
}
|
||||
|
||||
suspend fun playIndex(index: Int) {
|
||||
onMain { player.seekTo(index, 0L) }
|
||||
// MusicPlayerListener will update state
|
||||
}
|
||||
|
||||
suspend fun playNext(song: BaseItem) {
|
||||
val mediaItem = convert(song)
|
||||
onMain { player.addMediaItem(state.value.currentIndex + 1, mediaItem) }
|
||||
updateQueueSize()
|
||||
}
|
||||
|
||||
suspend fun removeFromQueue(index: Int) {
|
||||
onMain { player.removeMediaItem(index) }
|
||||
updateQueueSize()
|
||||
}
|
||||
|
||||
private suspend fun <T> loading(block: suspend () -> T): T {
|
||||
_state.update { it.copy(loadingState = LoadingState.Loading) }
|
||||
val result = block.invoke()
|
||||
|
|
@ -336,17 +354,6 @@ private class MusicPlayerListener(
|
|||
}
|
||||
}
|
||||
|
||||
data class PlayerMediaItemList(
|
||||
private val player: Player,
|
||||
) {
|
||||
val size: Int get() = player.mediaItemCount
|
||||
|
||||
operator fun get(index: Int): AudioItem {
|
||||
// Timber.v("get %s", index)
|
||||
return player.getMediaItemAt(index).localConfiguration?.tag as AudioItem
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberQueue(
|
||||
player: Player,
|
||||
|
|
|
|||
|
|
@ -282,6 +282,7 @@ fun AlbumDetailsPage(
|
|||
playlistViewModel.loadPlaylists(MediaType.AUDIO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onClickRemoveFromQueue = {},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -362,6 +363,7 @@ fun AlbumDetailsPage(
|
|||
actions = moreDialogActions,
|
||||
item = album,
|
||||
index = 0,
|
||||
canRemove = false,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
@ -404,6 +406,7 @@ fun AlbumDetailsPage(
|
|||
actions = moreDialogActions,
|
||||
item = song,
|
||||
index = index,
|
||||
canRemove = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -300,6 +300,7 @@ fun ArtistDetailsPage(
|
|||
playlistViewModel.loadPlaylists(MediaType.AUDIO)
|
||||
showPlaylistDialog.makePresent(itemId)
|
||||
},
|
||||
onClickRemoveFromQueue = {},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -369,6 +370,7 @@ fun ArtistDetailsPage(
|
|||
actions = moreDialogActions,
|
||||
item = artist,
|
||||
index = 0,
|
||||
canRemove = false,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
@ -408,6 +410,7 @@ fun ArtistDetailsPage(
|
|||
actions = moreDialogActions,
|
||||
item = song,
|
||||
index = index,
|
||||
canRemove = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ fun NowPlayingOverlay(
|
|||
current: AudioItem?,
|
||||
queue: List<AudioItem>,
|
||||
controllerViewState: ControllerViewState,
|
||||
onClickSong: (Int, AudioItem) -> Unit,
|
||||
onLongClickSong: (Int, AudioItem) -> Unit,
|
||||
onClickMore: () -> Unit,
|
||||
onMoveQueue: (Int, MoveDirection) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -159,10 +161,8 @@ fun NowPlayingOverlay(
|
|||
runtime = song.runtime?.roundSeconds,
|
||||
showArtist = true,
|
||||
isPlaying = current?.id == song.id,
|
||||
onClick = {
|
||||
player.seekTo(index, 0L)
|
||||
},
|
||||
onLongClick = {},
|
||||
onClick = { onClickSong.invoke(index, song) },
|
||||
onLongClick = { onLongClickSong.invoke(index, song) },
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.input.key.onPreviewKeyEvent
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -42,6 +43,8 @@ import com.github.damontecres.wholphin.preferences.AppPreferences
|
|||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.rememberQueue
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.playback.BottomDialog
|
||||
import com.github.damontecres.wholphin.ui.playback.BottomDialogItem
|
||||
|
|
@ -59,6 +62,7 @@ fun NowPlayingPage(
|
|||
creationCallback = { it.create() },
|
||||
),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val state by viewModel.state.collectAsState()
|
||||
val player = viewModel.player
|
||||
val queue = rememberQueue(player, state.musicServiceState.queueSize)
|
||||
|
|
@ -97,9 +101,20 @@ fun NowPlayingPage(
|
|||
)
|
||||
}
|
||||
|
||||
val actions =
|
||||
remember {
|
||||
MusicQueueDialogActions(
|
||||
onNavigate = { viewModel.navigationManager.navigateTo(it) },
|
||||
onClickPlay = { index, _ -> viewModel.play(index) },
|
||||
onClickPlayNext = { index, _ -> viewModel.playNext(index) },
|
||||
onClickRemoveFromQueue = { index, _ -> viewModel.removeFromQueue(index) },
|
||||
)
|
||||
}
|
||||
|
||||
var showMoreDialog by remember { mutableStateOf(false) }
|
||||
var lyricsActive by remember { mutableStateOf(true) }
|
||||
var showDebugInfo by remember { mutableStateOf(true) }
|
||||
var itemMoreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||
|
|
@ -189,6 +204,22 @@ fun NowPlayingPage(
|
|||
controllerViewState = controllerViewState,
|
||||
onMoveQueue = { index, direction -> viewModel.moveQueue(index, direction) },
|
||||
onClickMore = { showMoreDialog = true },
|
||||
onClickSong = { index, _ -> viewModel.play(index) },
|
||||
onLongClickSong = { index, song ->
|
||||
itemMoreDialog =
|
||||
DialogParams(
|
||||
title = song.title ?: "",
|
||||
fromLongClick = true,
|
||||
items =
|
||||
buildMoreDialogForMusicQueue(
|
||||
context = context,
|
||||
actions = actions,
|
||||
item = song,
|
||||
index = index,
|
||||
canRemove = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.background(AppColors.TransparentBlack50)
|
||||
|
|
@ -199,6 +230,16 @@ fun NowPlayingPage(
|
|||
LoadingPage(focusEnabled = false)
|
||||
}
|
||||
}
|
||||
itemMoreDialog?.let { params ->
|
||||
DialogPopup(
|
||||
showDialog = true,
|
||||
title = params.title,
|
||||
dialogItems = params.items,
|
||||
onDismissRequest = { itemMoreDialog = null },
|
||||
dismissOnClick = true,
|
||||
waitToLoad = params.fromLongClick,
|
||||
)
|
||||
}
|
||||
if (showMoreDialog) {
|
||||
NowPlayingBottomDialog(
|
||||
showDebugInfo = showDebugInfo,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class NowPlayingViewModel
|
|||
constructor(
|
||||
private val api: ApiClient,
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val navigationManager: NavigationManager,
|
||||
val navigationManager: NavigationManager,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
private val backdropService: BackdropService,
|
||||
private val imageUrlService: ImageUrlService,
|
||||
|
|
@ -127,7 +127,7 @@ class NowPlayingViewModel
|
|||
} else {
|
||||
null
|
||||
}
|
||||
Timber.v("lyricIndex=$lyricIndex")
|
||||
// Timber.v("lyricIndex=$lyricIndex")
|
||||
state.update {
|
||||
it.copy(
|
||||
lyrics = lyrics,
|
||||
|
|
@ -178,6 +178,12 @@ class NowPlayingViewModel
|
|||
direction: MoveDirection,
|
||||
) = viewModelScope.launchDefault { musicService.moveQueue(index, direction) }
|
||||
|
||||
fun play(index: Int) = viewModelScope.launchDefault { musicService.playIndex(index) }
|
||||
|
||||
fun playNext(index: Int) = viewModelScope.launchDefault { musicService.moveQueue(index, 1) }
|
||||
|
||||
fun removeFromQueue(index: Int) = viewModelScope.launchDefault { musicService.removeFromQueue(index) }
|
||||
|
||||
fun stop() {
|
||||
player.stop()
|
||||
navigationManager.goBack()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
|||
import com.github.damontecres.wholphin.ui.enableMarquee
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||
import com.github.damontecres.wholphin.ui.roundSeconds
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import kotlin.time.Duration
|
||||
|
|
@ -68,11 +69,13 @@ fun SongListItem(
|
|||
artist = if (showArtist) song?.data?.albumArtist else null,
|
||||
indexNumber = song?.data?.indexNumber,
|
||||
runtime =
|
||||
song
|
||||
?.data
|
||||
?.runTimeTicks
|
||||
?.ticks
|
||||
?.roundMinutes,
|
||||
remember(song) {
|
||||
song
|
||||
?.data
|
||||
?.runTimeTicks
|
||||
?.ticks
|
||||
?.roundSeconds
|
||||
},
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
modifier = modifier,
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import android.content.Context
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.AudioItem
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
|
|
@ -34,6 +36,7 @@ data class MusicMoreDialogActions(
|
|||
val onClickGoToArtist: (UUID) -> Unit = {
|
||||
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ARTIST))
|
||||
},
|
||||
val onClickRemoveFromQueue: (Int) -> Unit,
|
||||
)
|
||||
|
||||
fun buildMoreDialogForMusic(
|
||||
|
|
@ -41,6 +44,7 @@ fun buildMoreDialogForMusic(
|
|||
actions: MusicMoreDialogActions,
|
||||
item: BaseItem,
|
||||
index: Int,
|
||||
canRemove: Boolean,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
add(
|
||||
|
|
@ -61,6 +65,16 @@ fun buildMoreDialogForMusic(
|
|||
actions.onClickPlayNext(index, item)
|
||||
},
|
||||
)
|
||||
if (canRemove) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.remove_from_queue),
|
||||
Icons.Default.Delete,
|
||||
) {
|
||||
actions.onClickRemoveFromQueue(index)
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.add_to_queue),
|
||||
|
|
@ -112,6 +126,77 @@ fun buildMoreDialogForMusic(
|
|||
}
|
||||
}
|
||||
|
||||
data class MusicQueueDialogActions(
|
||||
val onNavigate: (Destination) -> Unit,
|
||||
val onClickPlay: (Int, AudioItem) -> Unit,
|
||||
val onClickPlayNext: (Int, AudioItem) -> Unit,
|
||||
val onClickGoToAlbum: (UUID) -> Unit = {
|
||||
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ALBUM))
|
||||
},
|
||||
val onClickGoToArtist: (UUID) -> Unit = {
|
||||
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ARTIST))
|
||||
},
|
||||
val onClickRemoveFromQueue: (Int, AudioItem) -> Unit,
|
||||
)
|
||||
|
||||
fun buildMoreDialogForMusicQueue(
|
||||
context: Context,
|
||||
actions: MusicQueueDialogActions,
|
||||
item: AudioItem,
|
||||
index: Int,
|
||||
canRemove: Boolean,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
actions.onClickPlay(index, item)
|
||||
},
|
||||
)
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play_next),
|
||||
Icons.Default.PlayArrow,
|
||||
iconColor = Color.Green.copy(alpha = .8f),
|
||||
) {
|
||||
actions.onClickPlayNext(index, item)
|
||||
},
|
||||
)
|
||||
if (canRemove) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.remove_from_queue),
|
||||
Icons.Default.Delete,
|
||||
) {
|
||||
actions.onClickRemoveFromQueue(index, item)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (item.albumId != null) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_album),
|
||||
Icons.Default.ArrowForward,
|
||||
) {
|
||||
actions.onClickGoToAlbum.invoke(item.albumId)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (item.artistId != null) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.go_to_artist),
|
||||
Icons.Default.ArrowForward,
|
||||
) {
|
||||
actions.onClickGoToArtist.invoke(item.artistId)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun ViewModel.getPagerForAlbum(
|
||||
api: ApiClient,
|
||||
albumId: UUID,
|
||||
|
|
|
|||
|
|
@ -737,6 +737,7 @@
|
|||
<string name="hide_lyrics">Hide lyrics</string>
|
||||
<string name="show_lyrics">Show lyrics</string>
|
||||
<string name="song_has_lyrics">Song has lyrics</string>
|
||||
<string name="remove_from_queue">Remove from queue</string>
|
||||
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue