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:
3 files changed
+72
-19
No files matched your search
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user