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 playPos(pos: Int) = run(MpdCommands.playPos(pos))
|
||||||
suspend fun playId(songId: Int) = run(MpdCommands.playId(songId))
|
suspend fun playId(songId: Int) = run(MpdCommands.playId(songId))
|
||||||
suspend fun stop() = run(MpdCommands.stop())
|
suspend fun stop() = run(MpdCommands.stop())
|
||||||
|
suspend fun clearQueue() = run(MpdCommands.clear())
|
||||||
suspend fun next() = run(MpdCommands.next())
|
suspend fun next() = run(MpdCommands.next())
|
||||||
suspend fun previous() = run(MpdCommands.previous())
|
suspend fun previous() = run(MpdCommands.previous())
|
||||||
suspend fun pause(paused: Boolean) = run(MpdCommands.pause(paused))
|
suspend fun pause(paused: Boolean) = run(MpdCommands.pause(paused))
|
||||||
@@ -195,6 +196,21 @@ class MpdClient(
|
|||||||
conn.execute(MpdCommands.play())
|
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. */
|
/** 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) }
|
suspend fun songArt(uri: String): ByteArray? = withArtConnection { conn -> readArt(conn, uri) }
|
||||||
|
|
||||||
|
|||||||
@@ -72,13 +72,22 @@ object MpdCommands {
|
|||||||
/** Every album, grouped by album artist (`Album`/`AlbumArtist` lines). */
|
/** Every album, grouped by album artist (`Album`/`AlbumArtist` lines). */
|
||||||
fun listAlbums() = MpdProtocol.command("list", "album", "group", "albumartist")
|
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?) =
|
* Append every track of an album to the queue (optionally scoped to an artist).
|
||||||
if (albumArtist.isNullOrEmpty()) {
|
*
|
||||||
MpdProtocol.command("findadd", "album", album)
|
* When [position] is given it is passed through as `findadd`'s `position`
|
||||||
} else {
|
* argument, controlling where the tracks land. MPD accepts an absolute index
|
||||||
MpdProtocol.command("findadd", "album", album, "albumartist", albumArtist)
|
* 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. */
|
/** First track of an album — used to resolve a URI for album-art lookup. */
|
||||||
fun findFirstTrack(album: String, albumArtist: String?) =
|
fun findFirstTrack(album: String, albumArtist: String?) =
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ class MpdConnectionManager(context: Context) {
|
|||||||
fun resume() = fire { pause(false) }
|
fun resume() = fire { pause(false) }
|
||||||
fun pause() = fire { pause(true) }
|
fun pause() = fire { pause(true) }
|
||||||
fun stop() = fire { stop() }
|
fun stop() = fire { stop() }
|
||||||
|
fun clearQueue() = fire { clearQueue() }
|
||||||
fun next() = fire { next() }
|
fun next() = fire { next() }
|
||||||
fun previous() = fire { previous() }
|
fun previous() = fire { previous() }
|
||||||
fun togglePlayPause() = fire { togglePause() }
|
fun togglePlayPause() = fire { togglePause() }
|
||||||
@@ -135,6 +136,12 @@ class MpdConnectionManager(context: Context) {
|
|||||||
|
|
||||||
fun playAlbum(album: String, albumArtist: String?) = fire { playAlbum(album, albumArtist) }
|
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. */
|
/** Jump to a queue entry by its stable song id. */
|
||||||
fun playQueueItem(songId: Int) = fire { playId(songId) }
|
fun playQueueItem(songId: Int) = fire { playId(songId) }
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -159,7 +159,7 @@ fun NowPlayingScreen(
|
|||||||
|
|
||||||
Spacer(Modifier.height(24.dp))
|
Spacer(Modifier.height(24.dp))
|
||||||
|
|
||||||
CastIndicator(host = serverHost)
|
// CastIndicator(host = serverHost)
|
||||||
VolumeControl(volume = status?.volume, onSetVolume = { vm.setVolume(it) })
|
VolumeControl(volume = status?.volume, onSetVolume = { vm.setVolume(it) })
|
||||||
|
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|||||||
@@ -70,8 +70,11 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
|
|||||||
fun setConsume(on: Boolean) = manager.setConsume(on)
|
fun setConsume(on: Boolean) = manager.setConsume(on)
|
||||||
|
|
||||||
fun playAlbum(album: String, albumArtist: String?) = manager.playAlbum(album, albumArtist)
|
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()
|
suspend fun loadAlbums() = manager.loadAlbums()
|
||||||
|
|
||||||
fun playQueueItem(songId: Int) = manager.playQueueItem(songId)
|
fun playQueueItem(songId: Int) = manager.playQueueItem(songId)
|
||||||
|
fun clearQueue() = manager.clearQueue()
|
||||||
suspend fun loadQueue() = manager.loadQueue()
|
suspend fun loadQueue() = manager.loadQueue()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
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.material.icons.filled.VolumeUp
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -21,10 +23,14 @@ import androidx.compose.material3.ListItemDefaults
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
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)
|
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(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
@@ -72,6 +82,13 @@ fun QueueScreen(vm: PlayerViewModel, onBack: () -> Unit) {
|
|||||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
actions = {
|
||||||
|
if (queueNotEmpty) {
|
||||||
|
IconButton(onClick = { confirmClear = true }) {
|
||||||
|
Icon(Icons.Filled.DeleteSweep, contentDescription = "Clear queue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) { 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. */
|
/** Explains what will actually happen at the end of the list, given the modes. */
|
||||||
|
|||||||
Reference in New Issue
Block a user