Address the owner's kiosk feedback on top of the release hardening: - Chrome: the back bar is a drag surface and shrank to 44px with one shared button form for Back/Forward/Connect/Cast; Qobuz Connect and Cast are a permanent cluster in the bar; the dead Share-logs button left the kiosk Settings header. - Transport bar: the hidden overflow menu is gone; Volume is always visible with a vertical sheet, and Desktop mode has its own button. - Now Playing: one centred transport row (New-bar order); lyrics are centred (shared LyricsLinesView gains an opt-in centered, desktop default false). - Local Library: the media funnel and an Open (ephemeral) entry point reach the kiosk; Artists is its own multi-column round-card grid (kiosk-only, the desktop LocalArtistsTab is untouched); the Artists drill-down query no longer hangs off a stale derived guard. - Search: the All-tab dashboard shelves render again — they were mounted in a Loader Component that read the delegate id under reuse and bound an empty model; they are direct children now. - Album detail: icon actions the height of the quality badge plus play next / later / queue, an inline floating quality badge, and zebra rows; both album headers show a quality badge. - Immersive: a quiet auto-hiding kiosk exit moved to the bottom, clear of the window controls. Regression coverage in scripts/test_qt_kiosk_feedback.mjs (wired into the runtime gate). Verified on a private authenticated Xvfb; QML audits 6/6, Node gate 5/5, release build 0 warnings. cc
144 lines
5.3 KiB
QML
144 lines
5.3 KiB
QML
// Local Library > Albums, kiosk body.
|
|
//
|
|
// THE FIX THIS FILE CARRIES. The previous kiosk Local Library read
|
|
// `QbzLocal.localAlbumsJson` unconditionally. That document is only published
|
|
// by the LEGACY reader: once the paged catalog surface is live
|
|
// (`localAlbumsNativeActive`, the production default) Rust stops republishing
|
|
// it, so the kiosk rendered whatever stale array it happened to hold — an
|
|
// empty grid on a machine whose library is fine. This body consumes the SAME
|
|
// two readers the desktop tab does, and the native one is authoritative
|
|
// whenever it is active.
|
|
//
|
|
// It owns exactly three things: the native query descriptor, the model's
|
|
// `pageMiss` answer, and the tab's loading/error/empty chrome. Everything
|
|
// visual is KioskLocalAlbumGrid.
|
|
//
|
|
// NO SEARCH / SORT / FILTER UI. The kiosk shell has none, so the descriptor is
|
|
// the neutral one (empty search, the desktop default sort, no grouping, no
|
|
// funnel) and the grid never re-queries on user input — only on a column
|
|
// change, which is a geometry event.
|
|
|
|
import QtQuick
|
|
import com.blitzfc.qbz
|
|
import "../theme"
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property var view: null
|
|
|
|
// The native reader knows no favorites (LocalLibraryView.qml:341-344):
|
|
// under "Favorites only" the host loads the legacy document instead.
|
|
readonly property bool nativeActive: QbzLocal.localAlbumsNativeActive
|
|
&& !(root.view && root.view.favoriteOnly)
|
|
readonly property var nativeModel: QbzLocalAlbums
|
|
readonly property int albumTotal: root.nativeActive
|
|
? (QbzLocal.localAlbumsNativeTotal || 0)
|
|
: (root.view ? root.view.albums.length : 0)
|
|
|
|
function t(s) { return QbzSession.tr(s, QbzSession.trRev) }
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Native query — the descriptor is width-dependent (Rust chunks the rows
|
|
// into `columns` cards), so it is (re)issued whenever the grid settles on
|
|
// a new column count. Coalesced: a mount that resizes twice queries once.
|
|
// ---------------------------------------------------------------------
|
|
Timer {
|
|
id: queryCoalescer
|
|
interval: 0
|
|
repeat: false
|
|
onTriggered: root.resetNativeQuery()
|
|
}
|
|
function resetNativeQuery() {
|
|
if (grid.columns <= 0)
|
|
return
|
|
QbzLocal.albumsNativeReset("", "artist-asc", "off",
|
|
root.view ? root.view.filterJson : "{}", grid.columns)
|
|
}
|
|
Component.onCompleted: {
|
|
queryCoalescer.restart()
|
|
root.publishNav()
|
|
}
|
|
|
|
// The mounted body owns the nav geometry: only it knows its column count
|
|
// and how many items the focus ring can reach.
|
|
function publishNav() {
|
|
if (root.view)
|
|
root.view.publishNav(Math.max(1, grid.columns), root.albumTotal)
|
|
}
|
|
onAlbumTotalChanged: root.publishNav()
|
|
|
|
Connections {
|
|
target: grid
|
|
function onColumnsChanged() {
|
|
queryCoalescer.restart()
|
|
root.publishNav()
|
|
}
|
|
}
|
|
Connections {
|
|
target: QbzLocal
|
|
// Album identity is a query, not a filter: flipping it invalidates the
|
|
// descriptor exactly as it does on the desktop tab.
|
|
function onLocalAlbumModeChanged() { queryCoalescer.restart() }
|
|
}
|
|
// The funnel IS part of the descriptor: a chip toggled in the host's
|
|
// sheet re-issues the native query.
|
|
Connections {
|
|
target: root.view
|
|
function onFilterJsonChanged() { queryCoalescer.restart() }
|
|
}
|
|
|
|
// The model only asks for a page when this signal has a receiver
|
|
// (LocalAlbumsModel::requestPage bails on `!isSignalConnected`), so the
|
|
// subscription belongs to the MOUNTED tab and to nothing else.
|
|
Connections {
|
|
target: root.nativeModel
|
|
function onPageMiss(page, generation) {
|
|
QbzLocal.albumsNativePageMiss(page, generation)
|
|
}
|
|
}
|
|
// Covers resolve id-keyed; the native page holds `artPath` per row, so a
|
|
// resolved key has to reach the model or the resident rows never show it.
|
|
Connections {
|
|
target: QbzLocal
|
|
function onLocalArtworkReady(key, path) {
|
|
root.nativeModel.setArtwork(key, path)
|
|
}
|
|
}
|
|
|
|
KioskLocalAlbumGrid {
|
|
id: grid
|
|
anchors.fill: parent
|
|
visible: !QbzLocal.localAlbumsLoading
|
|
&& QbzLocal.localAlbumsError === ""
|
|
&& root.albumTotal > 0
|
|
view: root.view
|
|
surface: "albums"
|
|
scrollScope: "local:albums"
|
|
pad: root.view ? root.view.pad : 16
|
|
nativeActive: root.nativeActive
|
|
nativeModel: root.nativeModel
|
|
rows: root.nativeActive ? [] : (root.view ? root.view.albums : [])
|
|
onOpen: function (id) {
|
|
if (root.view)
|
|
root.view.openAlbum(id)
|
|
}
|
|
}
|
|
|
|
// Loading / error / empty. One layer, static, mounts no artwork and no
|
|
// model; it unmounts itself entirely once the grid is populated.
|
|
KioskSkeleton {
|
|
anchors.fill: parent
|
|
kind: "grid"
|
|
columns: Math.max(2, grid.columns)
|
|
pad: grid.pad
|
|
gap: grid.gap
|
|
loading: QbzLocal.localAlbumsLoading
|
|
error: QbzLocal.localAlbumsError
|
|
empty: !QbzLocal.localAlbumsLoading
|
|
&& QbzLocal.localAlbumsError === ""
|
|
&& root.albumTotal === 0
|
|
emptyText: root.t("No albums in your local library yet.")
|
|
}
|
|
}
|