feat(slint): wire playlist queue/play-next + toast confirm on enqueue

Playlist cards (carousels, search, favorites) emitted ("playlist","queue")
and ("playlist","play-next") but no handler existed, so the menu items
were dead. Add enqueue_playlist (fetch the playlist's tracks by id and
append, or insert-next in reverse to keep order) and the two match arms.
The album/track enqueue paths already worked but gave no visible feedback
when the queue panel was closed, reading as "unwired" — add a success
toast (Added to queue / Playing next) to every enqueue path.

cc
This commit is contained in:
vicrodh committed 2026-05-30 21:02:53 -06:00
1 parent ac192a0c4c
commit c6c113d3f9
2 files changed
+86 -4

No files matched your search

+14
View File
@@ -2694,6 +2694,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
playback::play_tracks(runtime, weak, handle, tracks, 0);
});
}
("playlist", "queue") => playback::enqueue_playlist(
runtime.clone(),
weak.clone(),
handle.clone(),
id,
false,
),
("playlist", "play-next") => playback::enqueue_playlist(
runtime.clone(),
weak.clone(),
handle.clone(),
id,
true,
),
("playlist", "favorite") => {
// Favorite/unfavorite the open playlist.
if let Some(w) = weak.upgrade() {
+72 -4
View File
@@ -1015,7 +1015,7 @@ pub fn play_album_radio(
}
/// Enqueue an album's tracks at the end of the current queue.
pub fn enqueue_album(runtime: Runtime, _weak: slint::Weak<AppWindow>, handle: tokio::runtime::Handle, album_id: String) {
pub fn enqueue_album(runtime: Runtime, weak: slint::Weak<AppWindow>, handle: tokio::runtime::Handle, album_id: String) {
handle.spawn(async move {
let album = match runtime.core().get_album(&album_id).await {
Ok(album) => album,
@@ -1043,6 +1043,7 @@ pub fn enqueue_album(runtime: Runtime, _weak: slint::Weak<AppWindow>, handle: to
}
runtime.core().add_tracks(tracks).await;
refresh_sidebar(false);
crate::toast::success_weak(&weak, "Added to queue");
});
}
@@ -1053,7 +1054,7 @@ pub fn enqueue_album(runtime: Runtime, _weak: slint::Weak<AppWindow>, handle: to
/// sequence — mirroring Tauri's `v2_add_tracks_to_queue_next`.
pub fn enqueue_album_next(
runtime: Runtime,
_weak: slint::Weak<AppWindow>,
weak: slint::Weak<AppWindow>,
handle: tokio::runtime::Handle,
album_id: String,
) {
@@ -1087,11 +1088,12 @@ pub fn enqueue_album_next(
runtime.core().add_track_next(track).await;
}
refresh_sidebar(false);
crate::toast::success_weak(&weak, "Playing next");
});
}
/// Enqueue a single track at the end of the current queue.
pub fn enqueue_track(runtime: Runtime, _weak: slint::Weak<AppWindow>, handle: tokio::runtime::Handle, track_id: u64) {
pub fn enqueue_track(runtime: Runtime, weak: slint::Weak<AppWindow>, handle: tokio::runtime::Handle, track_id: u64) {
handle.spawn(async move {
let track = match runtime.core().get_track(track_id).await {
Ok(track) => track,
@@ -1117,13 +1119,14 @@ pub fn enqueue_track(runtime: Runtime, _weak: slint::Weak<AppWindow>, handle: to
make_queue_track(&track, &album_id, &album_title, &album_artist, &album_artwork);
runtime.core().add_track(queue_track).await;
refresh_sidebar(false);
crate::toast::success_weak(&weak, "Added to queue");
});
}
/// Insert a single track immediately after the current track ("Play next").
pub fn play_track_next(
runtime: Runtime,
_weak: slint::Weak<AppWindow>,
weak: slint::Weak<AppWindow>,
handle: tokio::runtime::Handle,
track_id: u64,
) {
@@ -1152,6 +1155,71 @@ pub fn play_track_next(
make_queue_track(&track, &album_id, &album_title, &album_artist, &album_artwork);
runtime.core().add_track_next(queue_track).await;
refresh_sidebar(false);
crate::toast::success_weak(&weak, "Playing next");
});
}
/// Enqueue a whole playlist (by id) at the end of the queue, or immediately
/// after the current track when `next`. Fetches the playlist's tracks fresh,
/// so it works from any playlist CARD (carousels, search, favorites) — not just
/// the currently-open PlaylistView. Mirrors the album enqueue paths.
pub fn enqueue_playlist(
runtime: Runtime,
weak: slint::Weak<AppWindow>,
handle: tokio::runtime::Handle,
playlist_id: String,
next: bool,
) {
let Ok(pid) = playlist_id.parse::<u64>() else {
return;
};
handle.spawn(async move {
let playlist = match runtime.core().get_playlist(pid).await {
Ok(playlist) => playlist,
Err(e) => {
log::error!("[qbz-slint] playback: enqueue get_playlist {pid} failed: {e}");
return;
}
};
let tracks: Vec<QueueTrack> = playlist
.tracks
.as_ref()
.map(|container| container.items.as_slice())
.unwrap_or_default()
.iter()
.map(|track| {
let (album_id, album_title, album_artwork) = track
.album
.as_ref()
.map(|a| {
(
a.id.clone(),
a.title.clone(),
a.image.best().cloned().unwrap_or_default(),
)
})
.unwrap_or_default();
let album_artist = track
.performer
.as_ref()
.map(|p| p.name.clone())
.unwrap_or_default();
make_queue_track(track, &album_id, &album_title, &album_artist, &album_artwork)
})
.collect();
if tracks.is_empty() {
return;
}
if next {
// Reverse so the inserted block keeps the playlist's order.
for track in tracks.into_iter().rev() {
runtime.core().add_track_next(track).await;
}
} else {
runtime.core().add_tracks(tracks).await;
}
refresh_sidebar(false);
crate::toast::success_weak(&weak, if next { "Playing next" } else { "Added to queue" });
});
}