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
79 lines
2.9 KiB
QML
79 lines
2.9 KiB
QML
// KioskEmptyState — the static "there is nothing here" / "this failed" panel
|
|
// of the kiosk shell.
|
|
//
|
|
// Contract §1.7: a kiosk route must let the user tell loading, empty, error and
|
|
// populated apart without looking dead. This is the second and third of those
|
|
// four; KioskSkeleton is the first, and it is what mounts this file.
|
|
//
|
|
// DELIBERATELY INERT. No icon, no artwork, no button, no animation, no timer.
|
|
// - no artwork, because an empty state that mounts an image is a network
|
|
// request for a state that by definition has no content (contract §5.2:
|
|
// "un tab/card no montado no solicita imagen");
|
|
// - no action, because the RETRY lives with whoever owns the document. This
|
|
// panel is presentational; a host that wants a retry puts its own control
|
|
// under it and gets the ≥64 px target from its own chrome.
|
|
//
|
|
// TEXT IS SUPPLIED, NOT INVENTED. `text` and `detail` arrive already
|
|
// translated from the host (KioskSkeleton passes an existing msgid through
|
|
// QbzSession.tr, and an error string arrives from the document that produced
|
|
// it). Nothing here hardcodes English.
|
|
//
|
|
// Sizing is for the 800x480 floor: the label wraps, is capped at four lines and
|
|
// elides, and the whole block stays inside the item, so a long German or
|
|
// Russian string cannot push itself off a Pi panel.
|
|
|
|
import QtQuick
|
|
import "../theme"
|
|
|
|
Item {
|
|
id: root
|
|
|
|
/// The headline. Already translated.
|
|
property string text: ""
|
|
/// An optional second line — a document's own error detail, a hint. Already
|
|
/// translated. Empty hides it.
|
|
property string detail: ""
|
|
/// Error tone: the headline takes the secondary text colour instead of the
|
|
/// muted one, so a failure reads louder than an emptiness without
|
|
/// introducing a red the kiosk theme does not define.
|
|
property bool isError: false
|
|
/// Horizontal breathing room. 24 keeps the block clear of the NavRail at
|
|
/// 800 px wide.
|
|
property real pad: 24
|
|
|
|
QbzTheme { id: theme }
|
|
|
|
implicitHeight: block.implicitHeight
|
|
|
|
Column {
|
|
id: block
|
|
anchors.centerIn: parent
|
|
width: Math.max(0, root.width - 2 * root.pad)
|
|
spacing: 8
|
|
|
|
Text {
|
|
width: block.width
|
|
text: root.text
|
|
color: root.isError ? theme.textSecondary : theme.textMuted
|
|
font.pixelSize: 16
|
|
font.weight: theme.weightMedium
|
|
horizontalAlignment: Text.AlignHCenter
|
|
wrapMode: Text.WordWrap
|
|
maximumLineCount: 3
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
Text {
|
|
width: block.width
|
|
visible: root.detail !== ""
|
|
text: root.detail
|
|
color: theme.textMuted
|
|
font.pixelSize: 13
|
|
horizontalAlignment: Text.AlignHCenter
|
|
wrapMode: Text.WordWrap
|
|
maximumLineCount: 4
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
}
|