feat(slint): favorites tracks search + results count

Add an expandable search over the loaded favorite tracks (title /
artist / album) plus a results-count line. tracks-visible (what the list
renders) derives from the full tracks set: an empty query shares the
model so artwork stays live, a query forks a filtered clone carrying the
already-decoded artwork (the LabelState albums/visible pattern). Adds a
no-match empty state.

cc
This commit is contained in:
vicrodh committed 2026-05-26 22:18:02 -06:00
1 parent fd5246a7ce
commit 708fd50c87
4 files changed
+91 -4

No files matched your search

@@ -20,6 +20,7 @@ import {
} from "../state.slint";
import { AlbumCard } from "../discover/AlbumCard.slint";
import { TrackRow } from "../primitives/TrackRow.slint";
import { ExpandableSearch } from "../primitives/ExpandableSearch.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
import { ListScrollbar } from "../primitives/ListScrollbar.slint";
@@ -333,12 +334,48 @@ export component FavoritesView inherits Rectangle {
if !FavoritesState.loading
&& FavoritesState.active-tab == "tracks": VerticalLayout {
spacing: 0px;
// Toolbar — results count (left) + expandable search (right).
if FavoritesState.tracks.length > 0: HorizontalLayout {
spacing: 8px;
VerticalLayout {
alignment: center;
horizontal-stretch: 1;
Text {
text: FavoritesState.tracks-visible.length
+ (FavoritesState.tracks-search != ""
? " / " + FavoritesState.tracks.length
: "")
+ " " + @tr("tracks");
color: Theme.text-muted;
font-size: Typography.legal;
}
}
VerticalLayout {
alignment: center;
ExpandableSearch {
placeholder: @tr("Search");
query <=> FavoritesState.tracks-search;
edited(q) => {
FavoritesActions.search-tracks(q);
}
}
}
}
if FavoritesState.tracks.length > 0: Rectangle { height: 12px; }
if FavoritesState.tracks.length == 0: Text {
text: @tr("No favorite tracks yet.");
color: Theme.text-muted;
font-size: Typography.body;
}
for track[i] in FavoritesState.tracks: TrackRow {
// Search filtered everything out.
if FavoritesState.tracks.length > 0
&& FavoritesState.tracks-visible.length == 0: Text {
text: @tr("No tracks match your search.");
color: Theme.text-muted;
font-size: Typography.body;
}
for track[i] in FavoritesState.tracks-visible: TrackRow {
track: track;
index: i;
show-artwork: true;
+7 -1
View File
@@ -1447,8 +1447,13 @@ export global FavoritesState {
// "tracks" | "albums" | "artists" | "playlists" | "labels"
in-out property <string> active-tab: "tracks";
// Tracks reuse TrackItem so the shared SearchTrackRow can
// render them (artwork-on, per-row cover thumbnail).
// render them (artwork-on, per-row cover thumbnail). `tracks` is the
// full fetched set (the artwork pipeline targets it); `tracks-visible`
// is what the list renders after the search filter (mirrors
// LabelState.albums vs .visible).
in property <[TrackItem]> tracks: [];
in property <[TrackItem]> tracks-visible: [];
in-out property <string> tracks-search: "";
in property <[AlbumCardItem]> albums: [];
in property <[FavoriteArtistItem]> artists: [];
in property <[FavoritePlaylistItem]> playlists: [];
@@ -1471,6 +1476,7 @@ export global FavoritesActions {
callback open-playlist(string /* playlist id */);
callback play-track(string /* track id */);
callback track-action(string /* track id */, string /* action */);
callback search-tracks(string /* query */);
}
// A Qobuz mix detail page (DailyQ / WeeklyQ / FavQ / TopQ), opened