diff --git a/crates/qbz-qt/qml/views/LocalLibraryView.qml b/crates/qbz-qt/qml/views/LocalLibraryView.qml index 91c1d5a83..63157edb0 100644 --- a/crates/qbz-qt/qml/views/LocalLibraryView.qml +++ b/crates/qbz-qt/qml/views/LocalLibraryView.qml @@ -190,9 +190,32 @@ Rectangle { selectedArtist = pending QbzLocal.clearPendingArtist() } + + // A TAB route from the cortinilla's local "View more" links. Same shape + // as consumePendingArtist above: the property change is the trigger, so + // it must be released after it is applied or the same link cannot fire + // twice in a row. + function consumePendingRoute() { + var raw = QbzLocal.localPendingRoute + if (raw === "") return + var route + try { route = JSON.parse(raw) } catch (e) { QbzLocal.clearPendingRoute(); return } + if (route.tab) activeTab = route.tab + // The query pre-filters the TRACKS tab only; the albums and artists + // tabs have no search box of their own. + if (route.tab === "tracks" && route.query) { + // Set the view's own search state too, not just the query, or the + // box would render empty while the list is filtered. + root.tracksSearch = route.query + QbzLocal.tracksSearch(route.query) + } + QbzLocal.clearPendingRoute() + } + Connections { target: QbzLocal function onLocalPendingArtistChanged() { root.consumePendingArtist() } + function onLocalPendingRouteChanged() { root.consumePendingRoute() } } // Mount: load the default tab. Tab switches load on demand (each tab is @@ -200,6 +223,7 @@ Rectangle { Component.onCompleted: { QbzLocal.loadTab(root.activeTab) consumePendingArtist() + consumePendingRoute() } onActiveTabChanged: { QbzLocal.loadTab(activeTab) diff --git a/crates/qbz-qt/src/local_album_actions.rs b/crates/qbz-qt/src/local_album_actions.rs index 2c3501b2b..1c19fa037 100644 --- a/crates/qbz-qt/src/local_album_actions.rs +++ b/crates/qbz-qt/src/local_album_actions.rs @@ -348,6 +348,30 @@ pub fn clear_pending_artist() { ui(|mut b| b.as_mut().set_local_pending_artist(QString::from(""))); } +/// Route the Local Library view to a TAB, optionally pre-filtered. +/// +/// Used by the cortinilla's local "View more" links: those leave the search +/// surface entirely and land the user on the matching Local Library tab, +/// rather than on a Qobuz results page that has no local content at all. +/// +/// The tab is QML-owned state (there is no `local_tab` property on this +/// bridge), so this is a pending route the view consumes, exactly like +/// `local_pending_artist` above. `query` is applied only by the tracks tab — +/// the albums and artists tabs have no search box of their own. +pub fn set_pending_route(tab: &str, query: &str) { + let json = serde_json::json!({ "tab": tab, "query": query }).to_string(); + ui(move |mut b| { + b.as_mut() + .set_local_pending_route(QString::from(json.as_str())); + }); +} + +/// The view applied the pending route — release it so the same route can fire +/// twice in a row. +pub fn clear_pending_route() { + ui(|mut b| b.as_mut().set_local_pending_route(QString::from(""))); +} + /// Mirror `AppearanceState.local-library-track-artwork` (ui_prefs.json, /// default OFF — per-row covers on a 16K-track list are the freeze surface) /// onto the bridge so the Local Library view can gate its track artwork. diff --git a/crates/qbz-qt/src/local_bridge.rs b/crates/qbz-qt/src/local_bridge.rs index e74dc9862..84db12eed 100644 --- a/crates/qbz-qt/src/local_bridge.rs +++ b/crates/qbz-qt/src/local_bridge.rs @@ -99,6 +99,13 @@ pub mod qbz_local { /// Artist NAME route from the routed local album page into the Artists /// tab (local/Plex artists carry no catalog id). "" once consumed. #[qproperty(QString, local_pending_artist)] + /// A pending ROUTE into this view: which tab to show, and an optional + /// query to pre-filter it with. Set by the cortinilla's local "View + /// more" links, consumed by LocalLibraryView on mount and on change, + /// then cleared — the property CHANGE is the trigger, so it has to be + /// released or the same route cannot fire twice in a row. JSON: + /// {"tab":"albums|artists|tracks","query":"..."}. + #[qproperty(QString, local_pending_route)] // --- Ephemeral folder (an ad-hoc folder outside the index) --------- /// A session is open: the Folders tab shows the ephemeral pane. #[qproperty(bool, local_ephemeral_active)] @@ -202,6 +209,9 @@ pub mod qbz_local { /// The view consumed the pending artist-name route. #[qinvokable] fn clear_pending_artist(self: Pin<&mut QbzLocal>); + /// The view applied the pending route — release it. + #[qinvokable] + fn clear_pending_route(self: Pin<&mut QbzLocal>); /// "Go to artist" on a local/Plex album — a NAME route, not an id. #[qinvokable] fn open_artist_by_name(self: Pin<&mut QbzLocal>, name: QString); @@ -346,6 +356,7 @@ pub struct QbzLocalRust { local_album_json: QString, local_track_artwork: bool, local_pending_artist: QString, + local_pending_route: QString, local_ephemeral_active: bool, local_ephemeral_loading: bool, local_ephemeral_json: QString, @@ -385,6 +396,7 @@ impl Default for QbzLocalRust { local_album_json: QString::from(""), local_track_artwork: false, local_pending_artist: QString::default(), + local_pending_route: QString::default(), local_ephemeral_active: false, local_ephemeral_loading: false, local_ephemeral_json: QString::from(""), @@ -746,6 +758,10 @@ impl qbz_local::QbzLocal { crate::local_album_actions::clear_pending_artist(); } + pub fn clear_pending_route(self: Pin<&mut Self>) { + crate::local_album_actions::clear_pending_route(); + } + pub fn open_artist_by_name(self: Pin<&mut Self>, name: QString) { crate::local_album_actions::open_artist_by_name(name.to_string()); } diff --git a/crates/qbz-qt/src/search_qt.rs b/crates/qbz-qt/src/search_qt.rs index affa70a6f..5ec972fdf 100644 --- a/crates/qbz-qt/src/search_qt.rs +++ b/crates/qbz-qt/src/search_qt.rs @@ -1039,6 +1039,25 @@ fn play_local_row(row_id: &str) { /// "View more" on a section: full search page on the matching tab /// (album -> 1, track -> 2, artist -> 3, playlist -> 4). pub async fn view_more(runtime: &Arc>, kind: &str) { + // The three LOCAL sections leave the search surface entirely: their + // "View more" opens the matching Local Library tab, because the Qobuz + // results page has no on-device content to show and landing there would + // read as the link having done nothing. + let local_tab = match kind { + "local-album" => Some("albums"), + "local-artist" => Some("artists"), + "local" => Some("tracks"), + _ => None, + }; + if let Some(tab) = local_tab { + let q = crate::search_bridge::cortinilla_query(); + set_cortinilla_open(false); + // The query pre-filters the TRACKS tab only — albums and artists have + // no search box of their own, so passing it there would be a lie. + crate::local_album_actions::set_pending_route(tab, if tab == "tracks" { &q } else { "" }); + crate::navigate_to("local"); + return; + } let tab = match kind { "album" => 1, "track" => 2,