feat(qt): release-aware album links, an unavailable-release page, Copy submenus and Buy on Qobuz

Unavailable tracks (8): playlist rows now say when the WHOLE release is
gone (`releaseUnavailable`, one probe per distinct album of the pulled rows
through the library's helper); TrackRow's album link and "Go to album" go
dark on it instead of opening an empty page. Every row that opens an album
passes the names it knows (`openAlbumFrom`), so when the catalog answers
404 the page names the release: AlbumView renders an "unavailable" arm —
heading, "This release is no longer available on Qobuz." — with the
best-ranked alternatives Qobuz still sells (the replacement flow's ranking,
exposed as `ranked_alternatives`) in a rail; a network failure gets the
same page with "Couldn't load this release." and no rail.

Copy (9): the player bar's "+" and the track row menus (shared TrackRow
and the artist's Popular Tracks rows) gain a Copy entry with a submenu —
"Track name" or "Track - Album - Artist" — through the shared clipboard
carrier; CardMenu marks `submenu: true` entries with a chevron and the
host reopens the second menu on the same anchor. Row menus keep both lazy.

Buy on Qobuz (10): the album header shows a store action when the catalog
says the release is purchasable and it is not already owned; the track
menus and the player bar carry the same entry for catalog rows and resolve
it at click time — the store page opens in the browser, or a toast says
the release is not sold. The link is `https://www.qobuz.com/album/-/{id}`,
which the store answers with a 301 to the localised page (verified).

cc
This commit is contained in:
vicrodh committed 2026-09-13 20:26:11 -06:00
1 parent 81c8d3908e
commit 1817de0743
11 files changed
+502 -18

No files matched your search

+43 -2
View File
@@ -657,7 +657,7 @@ Rectangle {
favorite: root.npFavorite
ephemeral: root.npEphemeral
tooltip: root.tooltip
onAddRequested: function (anchorItem) { addMenu.openBelowRight(anchorItem) }
onAddRequested: function (anchorItem) { root.addAnchor = anchorItem; addMenu.openBelowRight(anchorItem) }
onTrackInfoRequested: root.openTrackInfo()
}
}
@@ -680,7 +680,7 @@ Rectangle {
playCircle: true
favorite: root.npFavorite
tooltip: root.tooltip
onAddRequested: function (anchorItem) { addMenu.openBelowRight(anchorItem) }
onAddRequested: function (anchorItem) { root.addAnchor = anchorItem; addMenu.openBelowRight(anchorItem) }
onTrackInfoRequested: root.openTrackInfo()
}
}
@@ -908,6 +908,26 @@ Rectangle {
// "Add to…" flyout behind the transport "+" (TransportControls.slint's
// add-menu), on the shared CardMenu surface. Same seven entries, same
// order, same icons.
// The "+" button that opened the add-menu: the Copy submenu reopens on it.
property Item addAnchor: null
QbzClipboard { id: npClipboard }
CardMenu {
id: npCopyMenu
menuWidth: 232
entries: [
{ "label": QbzSession.tr("Track name", QbzSession.trRev), "icon": "copy", "action": "copy-title" },
{ "label": QbzSession.tr("Track - Album - Artist", QbzSession.trRev), "icon": "clipboard", "action": "copy-full" }
]
onPicked: function (a) {
var parts = [QbzPlayer.npTitle || ""]
if (a === "copy-full") {
if ((QbzPlayer.npAlbum || "") !== "") parts.push(QbzPlayer.npAlbum)
if ((QbzPlayer.npArtist || "") !== "") parts.push(QbzPlayer.npArtist)
}
npClipboard.copy(parts.join(" - "))
}
}
CardMenu {
id: addMenu
menuWidth: 232
@@ -963,6 +983,23 @@ Rectangle {
"action": "album-favorite"
})
}
// "Buy on Qobuz" (2026-09-13): catalog tracks only; store_qt
// resolves whether the release is sold and toasts when not.
if (root.npSource === "qobuz") {
m.push({
"label": QbzSession.tr("Buy on Qobuz", QbzSession.trRev),
"icon": "shopping-bag",
"action": "buy"
})
}
// Copy (2026-09-13): a submenu — the track name, or
// "Track - Album - Artist".
m.push({
"label": QbzSession.tr("Copy", QbzSession.trRev),
"icon": "copy",
"action": "copy",
"submenu": true
})
return m.concat(root.abEntries())
}
onPicked: function (a) {
@@ -978,6 +1015,10 @@ Rectangle {
} else if (a === "album-favorite") {
if (QbzPlayer.npAlbumId !== "")
QbzLibrary.libraryToggleFavorite("album", QbzPlayer.npAlbumId)
} else if (a === "buy") {
if (id !== "") QbzAlbum.buyTrack(id)
} else if (a === "copy") {
if (root.addAnchor) npCopyMenu.openBelowRight(root.addAnchor)
} else if (a === "ab-mark") {
QbzPlayer.abMark()
} else if (a === "ab-clear") {