Files
qbz/crates/qbz-qt/qml/settings/PlaybackSettings.qml
T
vicrodh b1dba198c9 feat(qt): kiosk profile release hardening
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
2026-09-07 08:23:11 -06:00

163 lines
7.5 KiB
QML

// Settings > Playback — the QML port of crates/qbz-ui/ui/settings/
// PlaybackSettings.slint. 1:1 group order, labels, descriptions and gating;
// every control rides the settingsJson document and the settings invokables.
import QtQuick
import com.blitzfc.qbz
import "../controls"
import "../theme"
Column {
property bool kioskHost: false
id: root
property var doc: ({})
QbzTheme { id: theme }
spacing: 4
// ============================ PLAYBACK ===============================
GroupHeader { kioskHost: root.kioskHost; text: QbzSession.tr("PLAYBACK", QbzSession.trRev) }
SettingRow { kioskHost: root.kioskHost;
label: QbzSession.tr("Continue playback after track ends", QbzSession.trRev)
description: QbzSession.tr("Keep playing the rest of the album or playlist instead of stopping.", QbzSession.trRev)
QbzToggle { kioskHost: root.kioskHost;
checked: root.doc.continuePlayback === true
onToggled: function (v) { QbzBridge.settingsBool("continue-playback", v) }
}
}
SettingRow { kioskHost: root.kioskHost;
label: QbzSession.tr("Show track playing context", QbzSession.trRev)
description: QbzSession.tr("Display the context-stack icon in the player.", QbzSession.trRev)
QbzToggle { kioskHost: root.kioskHost;
checked: root.doc.showContextIcon === true
onToggled: function (v) { QbzBridge.settingsBool("show-context-icon", v) }
}
}
SettingRow { kioskHost: root.kioskHost;
label: QbzSession.tr("Gapless playback", QbzSession.trRev)
description: QbzSession.tr("Play consecutive same-format tracks without a gap.", QbzSession.trRev)
// Disabled while Streaming only is on — gapless needs the local cache.
rowEnabled: root.doc.streamingOnly !== true
QbzToggle { kioskHost: root.kioskHost;
checked: root.doc.gapless === true
enabled: root.doc.streamingOnly !== true
onToggled: function (v) { QbzBridge.settingsBool("gapless", v) }
}
}
SettingsSpacer { }
SettingsDivider { }
SettingsSpacer { }
// ============================= SESSION ===============================
GroupHeader { kioskHost: root.kioskHost; text: QbzSession.tr("SESSION", QbzSession.trRev) }
SettingRow { kioskHost: root.kioskHost;
label: QbzSession.tr("Restore session on startup", QbzSession.trRev)
description: QbzSession.tr("Restore the queue and current track on the next launch.", QbzSession.trRev)
QbzToggle { kioskHost: root.kioskHost;
checked: root.doc.persistSession === true
onToggled: function (v) { QbzBridge.settingsBool("persist-session", v) }
}
}
SettingRow { kioskHost: root.kioskHost;
label: QbzSession.tr("Resume playback position", QbzSession.trRev)
description: QbzSession.tr("Also seek back to where the saved track left off.", QbzSession.trRev)
rowEnabled: root.doc.persistSession === true
QbzToggle { kioskHost: root.kioskHost;
checked: root.doc.resumePosition === true
enabled: root.doc.persistSession === true
onToggled: function (v) { QbzBridge.settingsBool("resume-position", v) }
}
}
SettingRow { kioskHost: root.kioskHost;
label: QbzSession.tr("Auto-connect Qobuz Connect on startup", QbzSession.trRev)
description: QbzSession.tr("Choose whether Qobuz Connect activates automatically when QBZ launches.", QbzSession.trRev)
QbzSelect { kioskHost: root.kioskHost;
menuWidth: 240
options: root.doc.qconnectStartupModes || []
currentIndex: root.doc.qconnectStartupIndex || 0
onSelected: function (i) { QbzBridge.settingsSelect("qconnect-startup", i) }
}
}
SettingRow { kioskHost: root.kioskHost;
label: QbzSession.tr("When playback conflicts", QbzSession.trRev)
description: QbzSession.tr("Ask each time or automatically choose which queue and device should continue.", QbzSession.trRev)
QbzSelect { kioskHost: root.kioskHost;
menuWidth: 300
popupWidth: 420
options: root.doc.qconnectConflictPolicies || []
currentIndex: root.doc.qconnectConflictPolicyIndex || 0
onSelected: function (i) { QbzBridge.settingsSelect("qconnect-conflict-policy", i) }
}
}
SettingRow { kioskHost: root.kioskHost;
label: QbzSession.tr("Qobuz Connect device name", QbzSession.trRev)
description: QbzSession.tr("The name other Qobuz Connect apps see for this device. Applies on the next connection.", QbzSession.trRev)
QbzLineEdit { kioskHost: root.kioskHost;
width: 240
text: root.doc.qconnectDeviceName || ""
placeholder: root.doc.qconnectDeviceNameDefault || ""
onCommitted: function (s) { QbzBridge.settingsString("qconnect-device-name", s) }
}
}
SettingsSpacer { }
SettingsDivider { }
SettingsSpacer { }
// ============================ STREAMING ==============================
GroupHeader { kioskHost: root.kioskHost; text: QbzSession.tr("STREAMING", QbzSession.trRev) }
SettingRow { kioskHost: root.kioskHost;
label: QbzSession.tr("Stream uncached tracks", QbzSession.trRev)
description: QbzSession.tr("Start uncached tracks via streaming instead of waiting for the full download.", QbzSession.trRev)
QbzToggle { kioskHost: root.kioskHost;
checked: root.doc.streamUncached === true
onToggled: function (v) { QbzBridge.settingsBool("stream-uncached", v) }
}
}
SettingRow { kioskHost: root.kioskHost;
visible: root.doc.streamUncached === true
label: QbzSession.tr("Initial buffer size", QbzSession.trRev)
description: QbzSession.tr("Seconds of audio buffered before streaming playback starts.", QbzSession.trRev)
Row {
spacing: 12
QbzSlider { kioskHost: root.kioskHost;
width: 160
anchors.verticalCenter: parent.verticalCenter
minimum: 1
maximum: 10
value: root.doc.bufferSeconds || 1
onChanged: function (v) { QbzBridge.settingsSlider("buffer-seconds", v) }
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: (root.doc.bufferSeconds || 1) + QbzSession.tr("s", QbzSession.trRev)
color: theme.textSecondary
font.pixelSize: root.kioskHost ? (theme.fontBody) * 1.2 : (theme.fontBody)
font.weight: theme.weightMedium
}
}
}
SettingRow { kioskHost: root.kioskHost;
label: QbzSession.tr("Streaming only", QbzSession.trRev)
description: QbzSession.tr("Skip writing tracks to the local cache while streaming.", QbzSession.trRev)
QbzToggle { kioskHost: root.kioskHost;
checked: root.doc.streamingOnly === true
onToggled: function (v) { QbzBridge.settingsBool("streaming-only", v) }
}
}
SettingRow { kioskHost: root.kioskHost;
label: QbzSession.tr("When quality retries fail", QbzSession.trRev)
description: QbzSession.tr("What to do when every quality tier for a track is unavailable.", QbzSession.trRev)
QbzSelect { kioskHost: root.kioskHost;
menuWidth: 240
options: root.doc.retryBehaviors || []
currentIndex: root.doc.retryBehaviorIndex || 0
onSelected: function (i) { QbzBridge.settingsSelect("retry-behavior", i) }
}
}
}