feat(slint): favorites albums list view + grid/list toggle

Replace the bare favorites albums grid with the shared
AlbumCollectionView plus a grid/list segmented toggle and a count line —
adding the list view that was missing (the AlbumListRow extras now fill
via the shared album_map). Album card actions (play / queue / favorite /
go-to) route through a new album-action callback to the album
media-action arms; the artist link still opens the artist.

cc
This commit is contained in:
vicrodh committed 2026-05-26 22:46:52 -06:00
1 parent e9836184e7
commit b4b11a59e5
3 files changed
+72 -19

No files matched your search

+12
View File
@@ -4264,6 +4264,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
}); });
} }
{
// Favorite album card actions (play / queue / favorite / go-to)
// route through the album media-action arms.
let weak = window.as_weak();
window
.global::<FavoritesActions>()
.on_album_action(move |id, action| {
if let Some(w) = weak.upgrade() {
w.invoke_media_action("album".into(), id, action);
}
});
}
{ {
// Local search over the loaded favorite tracks (title / artist / // Local search over the loaded favorite tracks (title / artist /
// album), re-deriving the rendered list. // album), re-deriving the rendered list.
@@ -18,11 +18,12 @@ import {
FavoriteLabelItem, FavoriteLabelItem,
DragState, DragState,
} from "../state.slint"; } from "../state.slint";
import { AlbumCard } from "../discover/AlbumCard.slint"; import { AlbumCollectionView } from "../discover/AlbumCollectionView.slint";
import { TrackRow } from "../primitives/TrackRow.slint"; import { TrackRow } from "../primitives/TrackRow.slint";
import { ExpandableSearch } from "../primitives/ExpandableSearch.slint"; import { ExpandableSearch } from "../primitives/ExpandableSearch.slint";
import { CircleAction } from "../primitives/CircleAction.slint"; import { CircleAction } from "../primitives/CircleAction.slint";
import { MultiSelectBar } from "../primitives/MultiSelectBar.slint"; import { MultiSelectBar } from "../primitives/MultiSelectBar.slint";
import { ToggleButton } from "../primitives/ToggleButton.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint"; import { QbzIcon } from "../primitives/QbzIcon.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint"; import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
import { ListScrollbar } from "../primitives/ListScrollbar.slint"; import { ListScrollbar } from "../primitives/ListScrollbar.slint";
@@ -458,30 +459,68 @@ export component FavoritesView inherits Rectangle {
// --- Albums tab -------------------------------------------- // --- Albums tab --------------------------------------------
if !FavoritesState.loading if !FavoritesState.loading
&& FavoritesState.active-tab == "albums": albums-grid := Rectangle { && FavoritesState.active-tab == "albums": VerticalLayout {
property <int> columns: Math.max( spacing: 0px;
1, // Toolbar — count (left) + grid/list segmented toggle (right).
Math.floor((self.width + root.gap) / (root.album-w + root.gap))); if FavoritesState.albums.length > 0: HorizontalLayout {
property <int> rows: Math.ceil(FavoritesState.albums.length / self.columns); spacing: 8px;
height: self.rows > 0 VerticalLayout {
? self.rows * root.album-h + Math.max(0, self.rows - 1) * root.gap alignment: center;
: 0px; horizontal-stretch: 1;
Text {
text: FavoritesState.albums.length + " " + @tr("albums");
color: Theme.text-muted;
font-size: Typography.legal;
}
}
VerticalLayout {
alignment: center;
Rectangle {
width: 68px;
height: 34px;
border-radius: 6px;
background: Theme.surface-elevated;
clip: true;
HorizontalLayout {
ToggleButton {
icon: @image-url("../assets/icons/layout-grid.svg");
active: FavoritesState.albums-view-mode == "grid";
clicked => {
FavoritesState.albums-view-mode = "grid";
}
}
ToggleButton {
icon: @image-url("../assets/icons/list.svg");
active: FavoritesState.albums-view-mode == "list";
clicked => {
FavoritesState.albums-view-mode = "list";
}
}
}
}
}
}
if FavoritesState.albums.length > 0: Rectangle { height: 12px; }
if FavoritesState.albums.length == 0: Text { if FavoritesState.albums.length == 0: Text {
text: @tr("No favorite albums yet."); text: @tr("No favorite albums yet.");
color: Theme.text-muted; color: Theme.text-muted;
font-size: Typography.body; font-size: Typography.body;
} }
for album[i] in FavoritesState.albums: AlbumCard {
x: Math.mod(i, albums-grid.columns) * (root.album-w + root.gap); AlbumCollectionView {
y: Math.floor(i / albums-grid.columns) * (root.album-h + root.gap); albums: FavoritesState.albums;
width: root.album-w; view-mode: FavoritesState.albums-view-mode;
height: root.album-h; is-grouped: false;
album: album; show-source: false;
clicked(id) => { root.open-album(id); } open-album(id) => {
root.open-album(id);
}
open-artist(id) => {
root.open-artist(id);
}
media-action(kind, id, action) => { media-action(kind, id, action) => {
if (kind == "artist") { FavoritesActions.album-action(id, action);
root.open-artist(id);
}
} }
} }
} }
+2
View File
@@ -1457,6 +1457,7 @@ export global FavoritesState {
in-out property <bool> tracks-multi-select: false; in-out property <bool> tracks-multi-select: false;
in property <int> tracks-selected-count: 0; in property <int> tracks-selected-count: 0;
in property <[AlbumCardItem]> albums: []; in property <[AlbumCardItem]> albums: [];
in-out property <string> albums-view-mode: "grid"; // "grid" | "list"
in property <[FavoriteArtistItem]> artists: []; in property <[FavoriteArtistItem]> artists: [];
in property <[FavoritePlaylistItem]> playlists: []; in property <[FavoritePlaylistItem]> playlists: [];
in property <[FavoriteLabelItem]> labels: []; in property <[FavoriteLabelItem]> labels: [];
@@ -1483,6 +1484,7 @@ export global FavoritesActions {
callback shuffle-tracks(); callback shuffle-tracks();
callback toggle-multi-select(); callback toggle-multi-select();
callback bulk-action(string /* select-all | remove-selected | clear */); callback bulk-action(string /* select-all | remove-selected | clear */);
callback album-action(string /* album id */, string /* action */);
} }
// A Qobuz mix detail page (DailyQ / WeeklyQ / FavQ / TopQ), opened // A Qobuz mix detail page (DailyQ / WeeklyQ / FavQ / TopQ), opened