feat: clear the queue
This commit is contained in:
@@ -161,6 +161,7 @@ class MpdClient(
|
||||
suspend fun playPos(pos: Int) = run(MpdCommands.playPos(pos))
|
||||
suspend fun playId(songId: Int) = run(MpdCommands.playId(songId))
|
||||
suspend fun stop() = run(MpdCommands.stop())
|
||||
suspend fun clearQueue() = run(MpdCommands.clear())
|
||||
suspend fun next() = run(MpdCommands.next())
|
||||
suspend fun previous() = run(MpdCommands.previous())
|
||||
suspend fun pause(paused: Boolean) = run(MpdCommands.pause(paused))
|
||||
@@ -195,6 +196,21 @@ class MpdClient(
|
||||
conn.execute(MpdCommands.play())
|
||||
}
|
||||
|
||||
/** Append an album's tracks to the end of the queue; playback is untouched. */
|
||||
suspend fun queueAlbum(album: String, albumArtist: String?) = withCommand { conn ->
|
||||
conn.execute(MpdCommands.findAddAlbum(album, albumArtist))
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an album right after the current track so it plays next. Falls back
|
||||
* to a plain append when nothing is playing — there is no current song for the
|
||||
* relative `"+0"` position to anchor to.
|
||||
*/
|
||||
suspend fun playAlbumNext(album: String, albumArtist: String?) = withCommand { conn ->
|
||||
val playing = MpdStatus.from(conn.execute(MpdCommands.status()).toMap()).song != null
|
||||
conn.execute(MpdCommands.findAddAlbum(album, albumArtist, position = if (playing) "+0" else null))
|
||||
}
|
||||
|
||||
/** Cover art bytes for a specific song URI, or null if the server has none. */
|
||||
suspend fun songArt(uri: String): ByteArray? = withArtConnection { conn -> readArt(conn, uri) }
|
||||
|
||||
|
||||
@@ -72,12 +72,21 @@ object MpdCommands {
|
||||
/** Every album, grouped by album artist (`Album`/`AlbumArtist` lines). */
|
||||
fun listAlbums() = MpdProtocol.command("list", "album", "group", "albumartist")
|
||||
|
||||
/** Append every track of an album to the queue (optionally scoped to an artist). */
|
||||
fun findAddAlbum(album: String, albumArtist: String?) =
|
||||
if (albumArtist.isNullOrEmpty()) {
|
||||
MpdProtocol.command("findadd", "album", album)
|
||||
} else {
|
||||
MpdProtocol.command("findadd", "album", album, "albumartist", albumArtist)
|
||||
/**
|
||||
* Append every track of an album to the queue (optionally scoped to an artist).
|
||||
*
|
||||
* When [position] is given it is passed through as `findadd`'s `position`
|
||||
* argument, controlling where the tracks land. MPD accepts an absolute index
|
||||
* or a relative one — `"+0"` inserts right after the currently playing song
|
||||
* (i.e. "play next"). Omit it to append to the end of the queue.
|
||||
*/
|
||||
fun findAddAlbum(album: String, albumArtist: String?, position: String? = null): String {
|
||||
val args = buildList {
|
||||
add("album"); add(album)
|
||||
if (!albumArtist.isNullOrEmpty()) { add("albumartist"); add(albumArtist) }
|
||||
if (position != null) { add("position"); add(position) }
|
||||
}
|
||||
return MpdProtocol.command("findadd", *args.toTypedArray())
|
||||
}
|
||||
|
||||
/** First track of an album — used to resolve a URI for album-art lookup. */
|
||||
|
||||
@@ -121,6 +121,7 @@ class MpdConnectionManager(context: Context) {
|
||||
fun resume() = fire { pause(false) }
|
||||
fun pause() = fire { pause(true) }
|
||||
fun stop() = fire { stop() }
|
||||
fun clearQueue() = fire { clearQueue() }
|
||||
fun next() = fire { next() }
|
||||
fun previous() = fire { previous() }
|
||||
fun togglePlayPause() = fire { togglePause() }
|
||||
@@ -135,6 +136,12 @@ class MpdConnectionManager(context: Context) {
|
||||
|
||||
fun playAlbum(album: String, albumArtist: String?) = fire { playAlbum(album, albumArtist) }
|
||||
|
||||
/** Append an album to the end of the queue without changing playback. */
|
||||
fun queueAlbum(album: String, albumArtist: String?) = fire { queueAlbum(album, albumArtist) }
|
||||
|
||||
/** Insert an album right after the current track so it plays next. */
|
||||
fun playAlbumNext(album: String, albumArtist: String?) = fire { playAlbumNext(album, albumArtist) }
|
||||
|
||||
/** Jump to a queue entry by its stable song id. */
|
||||
fun playQueueItem(songId: Int) = fire { playId(songId) }
|
||||
|
||||
|
||||
Binary file not shown.
@@ -159,7 +159,7 @@ fun NowPlayingScreen(
|
||||
|
||||
Spacer(Modifier.height(24.dp))
|
||||
|
||||
CastIndicator(host = serverHost)
|
||||
// CastIndicator(host = serverHost)
|
||||
VolumeControl(volume = status?.volume, onSetVolume = { vm.setVolume(it) })
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
@@ -70,8 +70,11 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
||||
fun setConsume(on: Boolean) = manager.setConsume(on)
|
||||
|
||||
fun playAlbum(album: String, albumArtist: String?) = manager.playAlbum(album, albumArtist)
|
||||
fun queueAlbum(album: String, albumArtist: String?) = manager.queueAlbum(album, albumArtist)
|
||||
fun playAlbumNext(album: String, albumArtist: String?) = manager.playAlbumNext(album, albumArtist)
|
||||
suspend fun loadAlbums() = manager.loadAlbums()
|
||||
|
||||
fun playQueueItem(songId: Int) = manager.playQueueItem(songId)
|
||||
fun clearQueue() = manager.clearQueue()
|
||||
suspend fun loadQueue() = manager.loadQueue()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.DeleteSweep
|
||||
import androidx.compose.material.icons.filled.VolumeUp
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -21,10 +23,14 @@ import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
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.clip
|
||||
@@ -63,6 +69,10 @@ fun QueueScreen(vm: PlayerViewModel, onBack: () -> Unit) {
|
||||
}
|
||||
val hasCurrent = currentPos != null && (fullQueue?.indices?.contains(currentPos) == true)
|
||||
|
||||
// Guards the destructive "clear queue" action behind a confirmation dialog.
|
||||
var confirmClear by remember { mutableStateOf(false) }
|
||||
val queueNotEmpty = fullQueue?.isNotEmpty() == true
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@@ -72,6 +82,13 @@ fun QueueScreen(vm: PlayerViewModel, onBack: () -> Unit) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (queueNotEmpty) {
|
||||
IconButton(onClick = { confirmClear = true }) {
|
||||
Icon(Icons.Filled.DeleteSweep, contentDescription = "Clear queue")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
@@ -137,6 +154,23 @@ fun QueueScreen(vm: PlayerViewModel, onBack: () -> Unit) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (confirmClear) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { confirmClear = false },
|
||||
title = { Text("Clear the queue?") },
|
||||
text = { Text("This removes every track from the queue and stops playback.") },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
vm.clearQueue()
|
||||
confirmClear = false
|
||||
}) { Text("Clear") }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { confirmClear = false }) { Text("Cancel") }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Explains what will actually happen at the end of the list, given the modes. */
|
||||
|
||||
Reference in New Issue
Block a user