feat(reco): wire the external-recommendations 4th Discover tab (Slint)
Adds "Recommendations" as a 4th Discover tab (alongside Home / Editor's Picks / For You), rendering the qbz-external-reco engine output. NOT part of For You. - ui: new ExternalRecoState global + ExternalRecoView (4 personalized carousels C1-C4 with length>0 self-hide gates + the editorial cold-start rows), 4th tab pill button in HomeView, mount on active-tab == "recommendations", skeleton gate. Export the global from app.slint. - bin: external_reco controller — RecoCatalog over QbzCore, per-user resolution cache lifecycle, LocalHistory from reco_store (known artists + recent plays), scrobbler-username gate (is_authed), build + apply into ExternalRecoState (models built on the UI thread; slint::Image is !Send) + artwork jobs. - bin: 6 ArtworkTarget variants + resolution arms; reco::recent_track_ids wrapper; home::select_tab + discover_prefs::push_descriptors treat the new tab like For You (own view, Home/Editor pushed empty); lazy-load hooks at the tab click + both Discover nav-restore sites. cargo check -p qbz green. Last.fm reads still need the proxy allowedMethods deploy (separate qbz-api-proxy repo) before they return data. cc
This commit is contained in:
12 files changed
+588
-7
No files matched your search
@@ -15,6 +15,7 @@ import { PlaylistCarousel } from "PlaylistCarousel.slint";
|
||||
import { PlaylistTagFilter } from "PlaylistTagFilter.slint";
|
||||
import { HomeSkeleton } from "HomeSkeleton.slint";
|
||||
import { ForYouView } from "ForYouView.slint";
|
||||
import { ExternalRecoView } from "ExternalRecoView.slint";
|
||||
import { GenreFilterPopup } from "GenreFilterPopup.slint";
|
||||
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
||||
import { ListScrollbar } from "../primitives/ListScrollbar.slint";
|
||||
@@ -210,6 +211,11 @@ export component HomeView inherits Rectangle {
|
||||
active: HomeState.active-tab == "forYou";
|
||||
clicked => { HomeActions.select-tab("forYou"); }
|
||||
}
|
||||
Tab {
|
||||
label: @tr("Recommendations");
|
||||
active: HomeState.active-tab == "recommendations";
|
||||
clicked => { HomeActions.select-tab("recommendations"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -287,7 +293,7 @@ export component HomeView inherits Rectangle {
|
||||
// would suppress it — gate on `loading` alone. The empty-data
|
||||
// placeholders below are gated off while loading, so ONLY the
|
||||
// skeleton shows during the fetch (1:1 Tauri).
|
||||
if DiscoverState.active-tab != "forYou" && HomeState.loading: HomeSkeleton { }
|
||||
if DiscoverState.active-tab != "forYou" && DiscoverState.active-tab != "recommendations" && HomeState.loading: HomeSkeleton { }
|
||||
|
||||
// Prefs-driven render loop (Slice 5). Rust pushes the ordered
|
||||
// ENABLED descriptor list for the active tab (home-sections /
|
||||
@@ -445,6 +451,23 @@ export component HomeView inherits Rectangle {
|
||||
root.media-action("mix", which, "open");
|
||||
}
|
||||
}
|
||||
|
||||
// Recommendations (4th tab) — external-reco carousels from
|
||||
// ExternalRecoState (lazy-loaded on first open).
|
||||
if DiscoverState.active-tab == "recommendations": ExternalRecoView {
|
||||
open-album(id) => {
|
||||
root.album-clicked(id);
|
||||
}
|
||||
open-artist(id) => {
|
||||
root.open-artist(id);
|
||||
}
|
||||
media-action(kind, id, action) => {
|
||||
root.media-action(kind, id, action);
|
||||
}
|
||||
play-track(id) => {
|
||||
root.media-action("track", id, "play");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Shared scrollbar in the right gutter (homologated cross-view).
|
||||
|
||||
Reference in new issue
Block a user