Integrate the Kiosk profile hardening round: 12 dedicated Kiosk views plus 22 shared opt-in hosts, shared navigation history with Back/Forward, Albums as the default library tab, native paged Local Library (artists/tracks), lightweight MyQBZ grid and detail, Ephemeral lifecycle, bounded lists and artwork sized to the physical frame, static skeletons, 64px transport bar, Settings touch reflow and a persistent Immersive exit. Desktop keeps its prior behavior: every shared component defaults to the desktop path and only opts into Kiosk behavior through explicit properties (origin-relative ScrollMemory, viewport-bounded artwork requests). Regression coverage: kiosk navigation and artwork Node tests added to the runtime gate, scroll-origin fixtures for fixed and grouped rows, i18n strings for all eight locales. Verified by 750 release tests, six QML audits, Kiosk smokes at 800x480 and 1280x800 and a Desktop A/B with seven pixel-identical captures. cc
65 lines
2.3 KiB
QML
65 lines
2.3 KiB
QML
// One horizontal viewport. Recycles cards; offscreen shelves never mount it.
|
|
import QtQuick
|
|
import com.blitzfc.qbz
|
|
|
|
ListView {
|
|
id: root
|
|
property var rows: []
|
|
property string kind: "album"
|
|
property real artSize: 128
|
|
property bool navFocused: false
|
|
property int cursor: 0
|
|
property real restoreX: 0
|
|
signal scrollSettled(real position)
|
|
function restorePosition() { if (!moving && restoreX > 0) contentX = restoreX }
|
|
Component.onCompleted: Qt.callLater(restorePosition)
|
|
onRestoreXChanged: Qt.callLater(restorePosition)
|
|
onMovementEnded: scrollSettled(contentX)
|
|
signal open(string id)
|
|
orientation: ListView.Horizontal
|
|
model: rows
|
|
spacing: 14
|
|
height: artSize + 58
|
|
clip: true
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
cacheBuffer: 0
|
|
reuseItems: true
|
|
onCursorChanged: if (navFocused) positionViewAtIndex(cursor, ListView.Contain)
|
|
onNavFocusedChanged: if (navFocused) positionViewAtIndex(cursor, ListView.Contain)
|
|
delegate: Loader {
|
|
id: slot
|
|
required property var modelData
|
|
required property int index
|
|
width: root.artSize
|
|
height: root.height
|
|
active: true
|
|
sourceComponent: root.kind === "artist" ? artistCard : albumCard
|
|
KioskCoverSource {
|
|
id: cover
|
|
remote: slot.modelData.artUrl || ""
|
|
local: slot.modelData.artPath || slot.modelData.artwork || ""
|
|
edge: root.artSize
|
|
}
|
|
Component {
|
|
id: albumCard
|
|
KioskCard {
|
|
artSize: root.artSize
|
|
album: ({ id: slot.modelData.id || "", title: slot.modelData.title || "",
|
|
artist: slot.modelData.artist || "", artwork: cover.source,
|
|
qualityTier: slot.modelData.qualityTier || "" })
|
|
navFocused: root.navFocused && slot.index === root.cursor
|
|
onClicked: function(id) { root.open(id) }
|
|
}
|
|
}
|
|
Component {
|
|
id: artistCard
|
|
KioskArtistCard {
|
|
artSize: root.artSize
|
|
artist: ({ id: slot.modelData.id || "", title: slot.modelData.title || slot.modelData.name || "", artwork: cover.source })
|
|
navFocused: root.navFocused && slot.index === root.cursor
|
|
onClicked: function(id) { root.open(id) }
|
|
}
|
|
}
|
|
}
|
|
}
|