feat(slint): favorites Playlists actions — play/queue/share/favorite + sub-tab/search
Wire the playlist card media-actions: play / play-next / queue fetch the playlist's tracks (core get_playlist) and route to playback play/enqueue; share copies the Qobuz web-player playlist URL to the clipboard; favorite toggles the LOCAL QBZ favorite (Library un-favorites in place and drops the row, Following adds to Library — per user decision). Wire the sub-tab switch and the name|owner search to derive_playlists. Add share::qobuz_playlist_url and favorites::remove_playlist_row. cc
This commit is contained in:
3 files changed
+116
No files matched your search
@@ -851,6 +851,24 @@ pub fn remove_album_row(window: &AppWindow, id: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a playlist from the Library (favorites) source after a local
|
||||
/// un-favorite, then re-derive the rendered model + update the tab badge.
|
||||
/// Following is untouched (a followed playlist stays followed).
|
||||
pub fn remove_playlist_row(window: &AppWindow, id: &str) {
|
||||
let state = window.global::<FavoritesState>();
|
||||
let model = state.get_playlists_favorites();
|
||||
if let Some(vm) = model.as_any().downcast_ref::<VecModel<SearchPlaylistItem>>() {
|
||||
for i in 0..vm.row_count() {
|
||||
if vm.row_data(i).map(|p| p.id == id).unwrap_or(false) {
|
||||
vm.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.set_playlists_total(model.row_count() as i32);
|
||||
derive_playlists(window);
|
||||
}
|
||||
|
||||
// ---- Artwork propagation -----------------------------------------------
|
||||
// The artwork pipeline writes a decoded cover into the SOURCE model
|
||||
// (`albums` / `tracks`) by index, but the views render the derived
|
||||
|
||||
@@ -4314,6 +4314,99 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
// Switch the Playlists sub-tab (Library / Following) + re-derive.
|
||||
let weak = window.as_weak();
|
||||
window
|
||||
.global::<FavoritesActions>()
|
||||
.on_playlists_set_sub_tab(move |sub| {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
w.global::<FavoritesState>().set_playlists_sub_tab(sub);
|
||||
favorites::derive_playlists(&w);
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
// Local search over the loaded favorite playlists (name | owner).
|
||||
let weak = window.as_weak();
|
||||
window
|
||||
.global::<FavoritesActions>()
|
||||
.on_search_playlists(move |q| {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
w.global::<FavoritesState>().set_playlists_search(q);
|
||||
favorites::derive_playlists(&w);
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
// Playlist card actions: play / play-next / queue / share / favorite.
|
||||
let runtime = app_runtime.clone();
|
||||
let weak = window.as_weak();
|
||||
let handle = tokio_rt.handle().clone();
|
||||
window
|
||||
.global::<FavoritesActions>()
|
||||
.on_playlist_action(move |id, action| {
|
||||
let Some(w) = weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
match action.as_str() {
|
||||
"share" => share::copy_to_clipboard(share::qobuz_playlist_url(&id)),
|
||||
"favorite" => {
|
||||
// Library sub-tab: un-favorite in place (remove the
|
||||
// local favorite + drop the row). Following sub-tab:
|
||||
// add to the local Library (per user decision).
|
||||
let library = w
|
||||
.global::<FavoritesState>()
|
||||
.get_playlists_sub_tab()
|
||||
.to_string()
|
||||
!= "following";
|
||||
if let Ok(pid) = id.parse::<u64>() {
|
||||
let fav = !library;
|
||||
handle.spawn_blocking(move || {
|
||||
crate::library_db::with_db(|db| db.set_playlist_favorite(pid, fav));
|
||||
});
|
||||
}
|
||||
if library {
|
||||
favorites::remove_playlist_row(&w, &id);
|
||||
}
|
||||
}
|
||||
act => {
|
||||
// play / play-next / queue: fetch the playlist's tracks,
|
||||
// then play or enqueue.
|
||||
let Ok(pid) = id.parse::<u64>() else {
|
||||
return;
|
||||
};
|
||||
let runtime = runtime.clone();
|
||||
let weak2 = weak.clone();
|
||||
let handle2 = handle.clone();
|
||||
let act = act.to_string();
|
||||
handle.spawn(async move {
|
||||
let tracks = match runtime.core().get_playlist(pid).await {
|
||||
Ok(p) => p.tracks.map(|t| t.items).unwrap_or_default(),
|
||||
Err(e) => {
|
||||
log::error!("[qbz-slint] playlist {pid} load failed: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
if tracks.is_empty() {
|
||||
return;
|
||||
}
|
||||
match act.as_str() {
|
||||
"play-next" => {
|
||||
playback::enqueue_tracks(runtime, handle2, tracks, true)
|
||||
}
|
||||
"queue" => {
|
||||
playback::enqueue_tracks(runtime, handle2, tracks, false)
|
||||
}
|
||||
_ => {
|
||||
playback::play_tracks(runtime, weak2, handle2, tracks, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let weak = window.as_weak();
|
||||
window
|
||||
|
||||
@@ -6,6 +6,11 @@ pub fn qobuz_track_url(track_id: &str) -> String {
|
||||
format!("https://www.qobuz.com/track/{track_id}")
|
||||
}
|
||||
|
||||
/// Qobuz web-player playlist URL (matches Tauri's share-playlist link).
|
||||
pub fn qobuz_playlist_url(playlist_id: &str) -> String {
|
||||
format!("https://play.qobuz.com/playlist/{playlist_id}")
|
||||
}
|
||||
|
||||
/// Copy `text` to the system clipboard. Runs on a blocking thread —
|
||||
/// clipboard backends (X11/Wayland) can block, and arboard keeps an
|
||||
/// owner thread alive so the contents persist after this returns.
|
||||
|
||||
Reference in new issue
Block a user