feat(qt): For You gains Qobuz Mixes pages, Artist Spotlight and Radios

Ports the three sections the tab was missing, against the Slint
implementation rather than invented: the section rails, the cards, and the
mix landing page with its navigation seam so back/forward behave.

The mixes reuse the shared crates for their requests -- the Slint app
already makes them, so nothing new talks to Qobuz here.

Also carries the qt-run.sh portability work needed for the Mac mini:
`free`, `nproc`, `pgrep` and `timeout` all differ or are simply absent on
BSD/macOS. Two of those bit during the first Mac build. `pgrep` exits 1
when it matches nothing -- the normal case -- and under `pipefail` that
killed the script before a single line of output, which reads exactly like
a build that did nothing. And `timeout` does not exist on macOS at all, so
the smoke produced a one-line log over which the grep cheerfully reported
"0 QML complaints": a test that never ran, rendering as a pass. The smoke
now fails outright on a log that short.

cc
This commit is contained in:
vicrodh committed 2026-07-29 16:26:11 -06:00
1 parent a3b89107fc
commit 0c159c141c
24 files changed
+2659 -205

No files matched your search

+39 -1
View File
@@ -41,6 +41,14 @@ Rectangle {
property string ribbonKind: ""
// Artwork image source (file://… or "") — the host's cache lookup.
property string artSource: ""
// REMOTE cover url, for the pin payload only (the AlbumCard.slint pin
// TouchArea passes `album.artwork-url`). The pinned store keeps a
// denormalized display snapshot taken at pin time, so a host that
// leaves this empty pins a row the Pinned rail can only draw as a
// placeholder — `artSource` is NOT a substitute, it is a local
// file:// path that means nothing on another machine or after a
// cache wipe.
property string artworkUrl: ""
property bool isFavorite: false
property bool isPinned: false
// Source badge (Library show-local): "local" | "plex" | "" (hidden).
@@ -89,7 +97,37 @@ Rectangle {
}
function togglePin() {
root.isPinned = !root.isPinned
QbzLibrary.togglePin("album", root.albumId, root.title, root.artist, "")
QbzLibrary.togglePin("album", root.albumId, root.title, root.artist,
root.artworkUrl)
}
// Pin fan-out. The pinned store has no change-notify, so `pinChanged`
// (key `{kind}:{id}`, emitted by main.rs::toggle_pin after the write) is
// the ONLY signal that this album's state moved — and it moves from
// anywhere: another rail, another tab, the album page header. This is the
// port's equivalent of the Slint `set_album_row_pinned` walk over every
// live model, and the reason no surface has to republish its document to
// keep a glyph honest. Assigning breaks the host's `isPinned` binding on
// purpose (the optimistic flip above already does).
Connections {
target: QbzLibrary
function onPinChanged(key, value) {
if (root.albumId !== "" && key === "album:" + root.albumId)
root.isPinned = value
}
// Favourite fan-out — the SAME shape, one signal later. `isFavorite`
// was the odd one out: the optimistic flip above breaks the host's
// binding (by design), and nothing settled it afterwards, so a write
// that FAILED stayed visibly wrong until the user navigated away, and
// a heart flipped on another surface (the album page header, a search
// result, the queue) never reached this card. `libraryFavoriteChanged`
// carries the value the write actually produced — the flipped one on
// success, the UNCHANGED one on failure — so this is both the
// cross-surface walk and the rollback.
function onLibraryFavoriteChanged(key, value) {
if (root.albumId !== "" && key === "album:" + root.albumId)
root.isFavorite = value
}
}
// AlbumCard.slint's album-menu, in its order. localMode drops the