fix(slint): search refinements — artist card, history, spinner, hover

- ArtistCard rebuilt 1:1 with Tauri SearchView.artist-card: 160x220
  inside a surface-card container, 12px corners, 120px circular avatar
  with a gradient placeholder, hover swaps to surface-elevated. The
  Follow chip (rectangular per ADR-008) sits below the name when the
  user does not follow the artist.
- Search loading state uses a rotating spinner (Lucide LoaderCircle on
  Slint transform-rotation) matching the Tauri spinner — 1s linear
  infinite. The search header (title + tabs + filter radios) collapses
  while the initial load runs so the chrome does not show through.
- Search history: live debounce + submit now use
  nav::push_or_replace_search, so the latest query is the single Search
  entry in history. Back from an opened album lands on that search.
- Track row backgrounds use the OR-d row-hovered (cell / fav / dl /
  more), so hovering the more button no longer drops the row tint.
- replace_category artists arm re-applies the top-result dedupe, so
  filtering does not bring the duplicate back.

cc
This commit is contained in:
vicrodh committed 2026-05-19 20:37:52 -06:00
1 parent b874661bbf
commit e2c0d812fe
11 files changed
+129 -43

No files matched your search

@@ -21,7 +21,7 @@ import { PlaylistCard } from "../discover/PlaylistCard.slint";
import { SearchTrackRow } from "SearchTrackRow.slint";
import { NavButtons } from "../shell/NavButtons.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { LoadingDots } from "../primitives/LoadingDots.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
// One tab in the results tab strip.
component SearchTab inherits Rectangle {
@@ -211,8 +211,8 @@ component ArtistGrid inherits Rectangle {
callback open(string);
callback media-action(string, string, string);
property <length> card-w: 170px;
property <length> card-h: 248px;
property <length> card-w: 160px;
property <length> card-h: 220px;
property <length> gap: 16px;
property <int> columns:
Math.max(1, Math.floor((self.width + root.gap) / (root.card-w + root.gap)));
@@ -296,7 +296,9 @@ export component SearchResultsView inherits Rectangle {
}
// --- Fixed header: title, tabs + filter radios ------------------
VerticalLayout {
// Collapsed during the initial load so the empty title + tabs do
// not show through behind the loading state.
if !(SearchState.loading && !root.has-results): VerticalLayout {
padding-left: 32px;
padding-right: 32px;
padding-bottom: 12px;
@@ -584,9 +586,24 @@ export component SearchResultsView inherits Rectangle {
}
// ---- Loading + empty states -------------------------------
if SearchState.loading && !root.has-results: LoadingDots {
label: @tr("Searching…");
height: 140px;
if SearchState.loading && !root.has-results: Rectangle {
height: 320px;
VerticalLayout {
alignment: center;
spacing: 18px;
HorizontalLayout {
alignment: center;
LoadingSpinner {
size: 36px;
}
}
Text {
text: @tr("Searching…");
color: Theme.text-muted;
font-size: 13px;
horizontal-alignment: center;
}
}
}
if !SearchState.loading && !root.has-results: Text {
text: @tr("No results.");