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
117 lines
4.5 KiB
QML
117 lines
4.5 KiB
QML
// The Qobuz-mix gradient square — ONE definition of the four mix identities
|
|
// (colours, badge, display name), used by BOTH surfaces that draw them:
|
|
//
|
|
// views/HomeView.qml MixesRail — 220px tile, badge + 22px name
|
|
// views/MixView.qml header — 224px art, no badge, 34px name
|
|
//
|
|
// In the Slint build those two live in different files and each re-declares
|
|
// the same four gradients (discover/QobuzMixesRow.slint:79-104 and
|
|
// mix/MixView.slint:63-70). Porting that duplication would mean a colour
|
|
// changed on the tile and not on the page it opens, so the port keeps one
|
|
// source (track rule 5).
|
|
//
|
|
// GRADIENT DELTA, inherited from the existing MixesRail: the .slint uses
|
|
// `@linear-gradient(135deg, a 0%, b 50%, c 100%)` and QML's Gradient has no
|
|
// angle — only Vertical / Horizontal. The vertical 3-stop plus a
|
|
// top-left-to-bottom-right darkening overlay is the approximation the POC
|
|
// already shipped for the tiles; the page header now uses the same one, so
|
|
// the two match each other exactly.
|
|
|
|
import QtQuick
|
|
import com.blitzfc.qbz
|
|
import "../theme"
|
|
|
|
Item {
|
|
id: root
|
|
|
|
/// "daily" | "weekly" | "fav" | "top".
|
|
property string kind: "daily"
|
|
property int size: 220
|
|
property int titleSize: 22
|
|
property int cornerRadius: 8
|
|
property bool showBadge: true
|
|
/// Whether the square reacts to the pointer at all (the page header does
|
|
/// not — it is not a link to anywhere).
|
|
property bool interactive: true
|
|
|
|
signal clicked()
|
|
|
|
QbzTheme { id: theme }
|
|
|
|
width: root.size
|
|
height: root.size
|
|
|
|
// QobuzMixesRow.slint:79-104 — the badge is the SERVICE the mix comes
|
|
// from ("qobuz" = Qobuz's own dynamic mixes, "qbz" = built locally from
|
|
// the user's library / playlists), which is why it is not uniform.
|
|
readonly property var _spec: ({
|
|
"daily": { "badge": "qobuz", "name": "DailyQ",
|
|
"c0": "#1e3a8a", "c1": "#6366f1", "c2": "#c084fc" },
|
|
"weekly": { "badge": "qobuz", "name": "WeeklyQ",
|
|
"c0": "#065f46", "c1": "#10b981", "c2": "#fbbf24" },
|
|
"fav": { "badge": "qbz", "name": "FavQ",
|
|
"c0": "#7f1d1d", "c1": "#ef4444", "c2": "#fb923c" },
|
|
"top": { "badge": "qbz", "name": "TopQ",
|
|
"c0": "#1f2937", "c1": "#4b5563", "c2": "#fbbf24" }
|
|
})
|
|
readonly property var spec: root._spec[root.kind] !== undefined
|
|
? root._spec[root.kind] : root._spec["daily"]
|
|
readonly property string mixName: root.spec.name
|
|
readonly property string badge: root.spec.badge
|
|
|
|
/// The four tile descriptions (QobuzMixesRow.slint:82-103). NOTE these
|
|
/// are NOT the mix PAGE's subtitles — `mix.rs::mix_meta` uses a different
|
|
/// string for `weekly` and `top`, and the page reads its own from Rust.
|
|
function tileDescription(k) {
|
|
return k === "daily"
|
|
? QbzSession.tr("Elevate your day with a customized selection of music.", QbzSession.trRev)
|
|
: k === "weekly"
|
|
? QbzSession.tr("Take a weekly journey with a fresh mix every Friday.", QbzSession.trRev)
|
|
: k === "fav"
|
|
? QbzSession.tr("A fresh shuffle from your personal library.", QbzSession.trRev)
|
|
: QbzSession.tr("Discover new music from your most-played playlists.", QbzSession.trRev)
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: root.cornerRadius
|
|
gradient: Gradient {
|
|
GradientStop { position: 0.0; color: root.spec.c0 }
|
|
GradientStop { position: 0.5; color: root.spec.c1 }
|
|
GradientStop { position: 1.0; color: root.spec.c2 }
|
|
}
|
|
// The 135deg sweep, approximated (see the header note).
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: root.cornerRadius
|
|
gradient: Gradient {
|
|
GradientStop { position: 0.0; color: "#00000000" }
|
|
GradientStop { position: 1.0; color: "#33000000" }
|
|
}
|
|
}
|
|
Text {
|
|
visible: root.showBadge
|
|
x: 12
|
|
y: 12
|
|
text: root.badge
|
|
color: "#ccffffff"
|
|
font.pixelSize: 10
|
|
font.weight: theme.weightSemibold
|
|
font.letterSpacing: 1
|
|
}
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: root.mixName
|
|
color: "#ffffff"
|
|
font.pixelSize: root.titleSize
|
|
font.weight: theme.weightBold
|
|
}
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
enabled: root.interactive
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.clicked()
|
|
}
|
|
}
|
|
}
|