Under the ambient field some controls still painted the opaque theme token while their neighbours used the half-alpha one: the album track filter and its select toggle, the Library tab strip's host, the Local Library icon selects, the header's active nav items and open playlists button, the queue sidebar's now-playing card and artwork placeholders, the Listen list's floating actions, and the shared SettingsButton, IconTextButton, multi-select bar, text area, load-more pill, tree row selection, sidebar count chips, Purchases tab strip, play-history filter box and Orbit banner. Each now takes the ambient-aware fill the header search and QbzTabBar already use (theme.ambientOn ? …A50 : …). Tooltips, popups and modals stay opaque on purpose: they are read over content, not chrome. cc
234 lines
9.6 KiB
QML
234 lines
9.6 KiB
QML
// Recently Played Albums + Most Played Albums — ONE view for the two
|
|
// local-history "View all" pages, because discover/RecentAlbumsView.slint
|
|
// and discover/MostPlayedAlbumsView.slint are the same page with five
|
|
// differences (the .slint keeps them apart only because Slint has no
|
|
// runtime-armed component). `mode` on the published document arms them:
|
|
//
|
|
// | recent | mostPlayed
|
|
// title | "Recently Played Albums" | "Most Played Albums"
|
|
// card height | 266 | 286 (the "{} plays" line)
|
|
// search box | none | 240x34, right-anchored
|
|
// root background | ambient-aware (:31) | surface-main (:26)
|
|
// header backgrnd | surface-main (:43) | surface-main (:34)
|
|
//
|
|
// Everything else is byte-identical, INCLUDING the loading block (spinner 36
|
|
// + @tr("Loading…")) and the empty msgid.
|
|
//
|
|
// NO OFFLINE GATE, deliberately: AppShell.slint mounts both routes while
|
|
// offline (:456-470) — neither is in `qobuz-view-blocked` (:112-136) — because
|
|
// both read a LOCAL store. Rendering QbzOfflinePlaceholder here would hide
|
|
// data that is perfectly available.
|
|
//
|
|
// NAV BUTTONS: back/forward live in the shell header, so the title starts at
|
|
// x: 48 (see DiscoverBrowseView.qml's note).
|
|
|
|
import QtQuick
|
|
import QtQuick.Window
|
|
import com.blitzfc.qbz
|
|
import "../controls"
|
|
import "../theme"
|
|
|
|
Rectangle {
|
|
id: root
|
|
property bool kioskHost: false
|
|
|
|
QbzTheme { id: theme }
|
|
|
|
readonly property var doc: {
|
|
try {
|
|
return JSON.parse(QbzHome.playHistoryJson)
|
|
} catch (e) {
|
|
return {}
|
|
}
|
|
}
|
|
// The document is the source of truth; `currentView` only covers the
|
|
// frame before the first publish lands.
|
|
readonly property string mode: doc.mode
|
|
|| (QbzShell.currentView === "mostplayedalbums" ? "mostPlayed" : "recent")
|
|
readonly property bool mostPlayed: mode === "mostPlayed"
|
|
readonly property var items: doc.items || []
|
|
|
|
readonly property bool ambientOn: theme.ambientOn
|
|
// RecentAlbumsView.slint:31 is ambient-aware; MostPlayedAlbumsView
|
|
// .slint:26 is plain surface-main.
|
|
color: (!mostPlayed && ambientOn) ? "transparent" : theme.surfaceMain
|
|
radius: 12
|
|
|
|
Column {
|
|
anchors.fill: parent
|
|
spacing: 0
|
|
|
|
// --- Fixed 56px header --------------------------------------------
|
|
Item {
|
|
width: parent.width
|
|
height: root.kioskHost ? 64 : 56
|
|
|
|
// Unconditional surface-main on BOTH pages (:43 / :34) — these
|
|
// two never take the ambient bar alpha the browse pages do.
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
// Top-rounded: full-bleed at y=0 of the content pane. The fill
|
|
// stays UNCONDITIONAL per the note above (that is reference
|
|
// parity), but the corner is not a colour question — in Slint
|
|
// the pane's `clip` follows its radius and cuts this band;
|
|
// Qt's clip is a rectangular scissor, so the band squares the
|
|
// pane unless it rounds itself. Invisible with the background
|
|
// off, where the view root paints the same colour beneath.
|
|
topLeftRadius: theme.radiusMd
|
|
topRightRadius: theme.radiusMd
|
|
color: theme.surfaceMain
|
|
}
|
|
|
|
Text {
|
|
x: 48
|
|
y: (root.kioskHost ? 32 : 25) - height / 2
|
|
width: Math.max(0, parent.width - 48 - 32 - (root.mostPlayed ? 240 + 16 : 0))
|
|
text: root.mostPlayed
|
|
? QbzSession.tr("Most Played Albums", QbzSession.trRev)
|
|
: QbzSession.tr("Recently Played Albums", QbzSession.trRev)
|
|
color: theme.textPrimary
|
|
font.pixelSize: theme.fontSection
|
|
font.weight: theme.weightBold
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
// Most Played only: a 240x34 title/artist filter. No clear-X in
|
|
// the .slint (:59-104), so the shared search arm is not used here.
|
|
Rectangle {
|
|
visible: root.mostPlayed
|
|
x: parent.width - width - 32
|
|
y: (root.kioskHost ? 32 : 25) - height / 2
|
|
width: 240
|
|
height: root.kioskHost ? 44 : 34
|
|
radius: 6
|
|
border.width: 1
|
|
border.color: theme.borderSubtle
|
|
color: (theme.ambientOn ? theme.surfaceElevatedA50 : theme.surfaceElevated)
|
|
|
|
Row {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 10
|
|
anchors.rightMargin: 10
|
|
spacing: 8
|
|
QbzIcon {
|
|
name: "search"
|
|
width: 14
|
|
height: 14
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
tintName: "muted"
|
|
}
|
|
Item {
|
|
width: parent.width - 14 - 8
|
|
height: parent.height
|
|
clip: true
|
|
TextInput {
|
|
QbzTextEditMenu { }
|
|
id: mpSearch
|
|
anchors.fill: parent
|
|
color: theme.textPrimary
|
|
font.pixelSize: theme.fontBody
|
|
verticalAlignment: Text.AlignVCenter
|
|
selectByMouse: true
|
|
onTextEdited: QbzHome.mostPlayedFilter(text)
|
|
// The opener resets the box in the same hop that
|
|
// raises `loading`; re-seed while not editing.
|
|
Binding {
|
|
target: mpSearch
|
|
property: "text"
|
|
value: root.doc.search || ""
|
|
when: !mpSearch.activeFocus
|
|
}
|
|
}
|
|
Text {
|
|
visible: mpSearch.text === ""
|
|
anchors.fill: parent
|
|
text: QbzSession.tr("Search…", QbzSession.trRev)
|
|
color: theme.textMuted
|
|
font.pixelSize: theme.fontBody
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Scrolling grid ------------------------------------------------
|
|
Item {
|
|
width: parent.width
|
|
height: parent.height - (root.kioskHost ? 64 : 56)
|
|
|
|
Flickable {
|
|
id: flick
|
|
anchors.fill: parent
|
|
clip: true
|
|
contentWidth: width
|
|
contentHeight: page.implicitHeight
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
|
|
Column {
|
|
id: page
|
|
width: parent.width
|
|
leftPadding: 32
|
|
rightPadding: 32
|
|
topPadding: 8
|
|
bottomPadding: 100
|
|
spacing: 0
|
|
|
|
// Spinner + "Loading…" — identical on both pages.
|
|
Column {
|
|
visible: QbzHome.playHistoryLoading
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
spacing: 12
|
|
QbzSpinner {
|
|
size: 36
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
}
|
|
Text {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: QbzSession.tr("Loading…", QbzSession.trRev)
|
|
color: theme.textMuted
|
|
font.pixelSize: 14
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
}
|
|
|
|
Text {
|
|
visible: !QbzHome.playHistoryLoading && root.items.length === 0
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: QbzSession.tr("Albums you play will appear here.", QbzSession.trRev)
|
|
color: theme.textMuted
|
|
font.pixelSize: 14
|
|
}
|
|
|
|
AlbumCollection {
|
|
kioskHost: root.kioskHost
|
|
visible: !QbzHome.playHistoryLoading
|
|
width: parent.width - 64
|
|
albums: root.items
|
|
viewMode: "grid"
|
|
cardWidth: 200
|
|
// The 20px difference IS the "{} plays" line.
|
|
cardHeight: root.mostPlayed ? 286 : 266
|
|
cardGap: 24
|
|
showPlays: root.mostPlayed
|
|
flick: flick
|
|
contentOffset: root.kioskHost ? y : (8)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Back/forward scroll memory (controls/ScrollMemory.qml): reports
|
|
// this container's offset while it is the live page, and restores it
|
|
// when a back/forward step arms this route.
|
|
ScrollMemory { target: flick; scope: QbzShell.currentView }
|
|
QbzScrollBar {
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 4
|
|
anchors.top: parent.top
|
|
anchors.bottom: parent.bottom
|
|
target: flick
|
|
}
|
|
}
|
|
}
|
|
}
|