feat(slint): Library > Favorites view (Tracks / Albums / Artists)
Wires the Library header dropdown to a real Favorites view. Mirrors
Tauri's FavoritesView.svelte core: a tab bar over per-tab content,
each tab lazy-loaded on first open.
Slint:
- ContentView gains `favorites`. NavMenuEntry gains a `route`
field; the Library dropdown rows carry "favorites-tracks" /
"-albums" / "-artists" / "-playlists" / "-labels". The
HeaderMenuOverlay click bubbles the route up through AppShell ->
AppWindow -> Rust (new header-menu-navigate callback).
- FavoritesState / FavoritesActions globals. Tracks reuse
SearchTrackItem (so the shared SearchTrackRow renders them with
per-row cover art), albums reuse AlbumCardItem + the Discover
AlbumCard, artists use a new FavoriteArtistItem round-portrait
grid.
- New ui/favorites/FavoritesView.slint: title, tab bar with live
counts + active underline, and the three populated tabs.
Playlists / Labels show "Coming soon" pending their backing
views. AppShell routes ContentView.favorites.
Rust:
- crate::favorites controller — load_favorites parses the raw
get_favorites JSON (`{ <type>: { items, total } }`) into typed
qbz-models Track / Album / Artist, maps to the Slint structs.
Lazy per-tab: select-tab and the header route both call
navigate_favorites, which fetches only the opened tab.
- artwork::ArtworkTarget gains FavoriteTrack / FavoriteAlbum /
FavoriteArtist; covers stream in via the shared pipeline.
- main.rs: navigate_favorites + on_header_menu_navigate +
FavoritesActions (select-tab / open-album / open-artist /
play-track / track-action). No new qbz-core method —
get_favorites already existed.
Deferred: Playlists + Labels tab content, full pagination (one 500
page is fetched per tab), favorites preferences (tab order).
cc
This commit is contained in:
9 files changed
+831
-10
No files matched your search
@@ -19,6 +19,7 @@ import { ArtistPageView } from "../artist/ArtistPageView.slint";
|
||||
import { MusicianPageView } from "../musician/MusicianPageView.slint";
|
||||
import { LabelReleasesView } from "../label/LabelReleasesView.slint";
|
||||
import { ArtistsByLocationView } from "../location/ArtistsByLocationView.slint";
|
||||
import { FavoritesView } from "../favorites/FavoritesView.slint";
|
||||
import { SettingsView } from "../settings/SettingsView.slint";
|
||||
import { SearchResultsView } from "../search/SearchResultsView.slint";
|
||||
|
||||
@@ -26,6 +27,7 @@ export component AppShell inherits Rectangle {
|
||||
callback open-album(string);
|
||||
callback open-artist(string);
|
||||
callback media-action(string, string, string);
|
||||
callback header-menu-navigate(string);
|
||||
callback settings-bool(string, bool);
|
||||
callback settings-select(string, int);
|
||||
callback settings-slider(string, int);
|
||||
@@ -115,6 +117,14 @@ export component AppShell inherits Rectangle {
|
||||
root.open-artist(id);
|
||||
}
|
||||
}
|
||||
if NavState.view == ContentView.favorites: FavoritesView {
|
||||
open-album(id) => {
|
||||
root.open-album(id);
|
||||
}
|
||||
open-artist(id) => {
|
||||
root.open-artist(id);
|
||||
}
|
||||
}
|
||||
if NavState.view == ContentView.settings: SettingsView {
|
||||
settings-bool(k, v) => {
|
||||
root.settings-bool(k, v);
|
||||
@@ -163,5 +173,9 @@ export component AppShell inherits Rectangle {
|
||||
// the header and the content region. A plain in-window overlay (not
|
||||
// a PopupWindow) — it does not grab the pointer, so the header tabs
|
||||
// stay hoverable and menus can be hover-switched instantly.
|
||||
HeaderMenuOverlay { }
|
||||
HeaderMenuOverlay {
|
||||
navigate(route) => {
|
||||
root.header-menu-navigate(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,7 +266,7 @@ export component HeaderBar inherits Rectangle {
|
||||
show-icon: root.show-tab-icons;
|
||||
// Discover rows are text-only (no glyphs).
|
||||
items: [
|
||||
{ label: @tr("Home"), has-glyph: false },
|
||||
{ label: @tr("Home"), has-glyph: false, route: "home" },
|
||||
{ label: @tr("Editor's Picks"), has-glyph: false },
|
||||
{ label: @tr("For You"), has-glyph: false },
|
||||
];
|
||||
@@ -279,11 +279,11 @@ export component HeaderBar inherits Rectangle {
|
||||
label: @tr("Library");
|
||||
show-icon: root.show-tab-icons;
|
||||
items: [
|
||||
{ label: @tr("Tracks"), icon: @image-url("../assets/icons/music.svg"), has-glyph: true },
|
||||
{ label: @tr("Albums"), icon: @image-url("../assets/icons/disc.svg"), has-glyph: true },
|
||||
{ label: @tr("Artists"), icon: @image-url("../assets/icons/user.svg"), has-glyph: true },
|
||||
{ label: @tr("Playlists"), icon: @image-url("../assets/icons/list-music.svg"), has-glyph: true },
|
||||
{ label: @tr("Labels"), icon: @image-url("../assets/icons/disc-3.svg"), has-glyph: true },
|
||||
{ label: @tr("Tracks"), icon: @image-url("../assets/icons/music.svg"), has-glyph: true, route: "favorites-tracks" },
|
||||
{ label: @tr("Albums"), icon: @image-url("../assets/icons/disc.svg"), has-glyph: true, route: "favorites-albums" },
|
||||
{ label: @tr("Artists"), icon: @image-url("../assets/icons/user.svg"), has-glyph: true, route: "favorites-artists" },
|
||||
{ label: @tr("Playlists"), icon: @image-url("../assets/icons/list-music.svg"), has-glyph: true, route: "favorites-playlists" },
|
||||
{ label: @tr("Labels"), icon: @image-url("../assets/icons/disc-3.svg"), has-glyph: true, route: "favorites-labels" },
|
||||
];
|
||||
}
|
||||
NavTabWithMenu {
|
||||
|
||||
@@ -26,6 +26,10 @@ export component HeaderMenuOverlay inherits Rectangle {
|
||||
// while a menu is open.
|
||||
visible: HeaderMenuState.open-index >= 0;
|
||||
|
||||
// Bubbles a row's destination route (e.g. "favorites-tracks") up
|
||||
// to the shell so Rust can run the navigation + data load.
|
||||
callback navigate(string);
|
||||
|
||||
// (Re)arms the idle countdown — called on any pointer activity over
|
||||
// the panel or its rows.
|
||||
function reset-idle() {
|
||||
@@ -111,7 +115,9 @@ export component HeaderMenuOverlay inherits Rectangle {
|
||||
icon: entry.icon;
|
||||
has-glyph: entry.has-glyph;
|
||||
clicked => {
|
||||
if (idx == HeaderMenuState.home-item) {
|
||||
if (entry.route != "") {
|
||||
root.navigate(entry.route);
|
||||
} else if (idx == HeaderMenuState.home-item) {
|
||||
NavState.view = ContentView.home;
|
||||
}
|
||||
root.close-menu();
|
||||
|
||||
Reference in new issue
Block a user