Tauri let the user rename how QBZ announces itself to Qobuz Connect;
the Slint port lost the UI entirely while keeping the persistence and
the announce path (same device_name key in qconnect_settings.db, read
by connect() -> bootstrap_remote_presence). Restored 1:1: a text row
in Settings > Playback next to the QConnect startup select (LineEdit
mirroring the ListenBrainz token row, placeholder = the resolved
default 'Qbz - {hostname}'), a new settings-string callback channel
through the settings chain, and a live-service setter so a rename
takes effect on the next connection without an app restart — exactly
Tauri's semantics (no live re-announce; the row description says so).
Trim-only validation like Tauri; empty clears the override. Two new
msgids across the 8 locales.
cc
224 lines
8.0 KiB
Plaintext
224 lines
8.0 KiB
Plaintext
// Playback settings panel — end-of-source behavior, session persistence,
|
|
// and streaming/caching options. 1:1 with the Tauri app. Backed by the
|
|
// SettingsState global and persisted through the Rust settings
|
|
// controller.
|
|
|
|
import { LineEdit } from "std-widgets.slint";
|
|
import { Theme } from "../foundation/semantic-colors.slint";
|
|
import { Typography } from "../foundation/typography.slint";
|
|
import { SettingsState, UiFocusState } from "../state.slint";
|
|
import { QbzToggle } from "../primitives/QbzToggle.slint";
|
|
import { QbzSelect } from "../primitives/QbzSelect.slint";
|
|
import { QbzSlider } from "../primitives/QbzSlider.slint";
|
|
import { SettingRow } from "SettingRow.slint";
|
|
|
|
component GroupHeader inherits Text {
|
|
color: Theme.text-muted;
|
|
font-size: 11px;
|
|
letter-spacing: 1.5px;
|
|
font-weight: Typography.semibold;
|
|
}
|
|
|
|
component Divider inherits Rectangle {
|
|
height: 1px;
|
|
background: Theme.border-subtle;
|
|
}
|
|
|
|
export component PlaybackSettings inherits VerticalLayout {
|
|
callback settings-bool(string, bool);
|
|
callback settings-select(string, int);
|
|
callback settings-slider(string, int);
|
|
callback settings-string(string, string);
|
|
|
|
spacing: 4px;
|
|
|
|
GroupHeader { text: @tr("PLAYBACK"); }
|
|
|
|
SettingRow {
|
|
label: @tr("Continue playback after track ends");
|
|
description: @tr("Keep playing the rest of the album or playlist instead of stopping.");
|
|
QbzToggle {
|
|
checked: SettingsState.continue-playback;
|
|
toggled(v) => {
|
|
SettingsState.continue-playback = v;
|
|
root.settings-bool("continue-playback", v);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("Show track playing context");
|
|
description: @tr("Display the context-stack icon in the player.");
|
|
QbzToggle {
|
|
checked: SettingsState.show-context-icon;
|
|
toggled(v) => {
|
|
SettingsState.show-context-icon = v;
|
|
root.settings-bool("show-context-icon", v);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("Gapless playback");
|
|
description: @tr("Play consecutive same-format tracks without a gap.");
|
|
// Disabled while Streaming only is on — gapless needs local cache.
|
|
enabled: !SettingsState.streaming-only;
|
|
QbzToggle {
|
|
checked: SettingsState.gapless;
|
|
enabled: !SettingsState.streaming-only;
|
|
toggled(v) => {
|
|
SettingsState.gapless = v;
|
|
root.settings-bool("gapless", v);
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
Divider { }
|
|
Rectangle { height: 12px; }
|
|
|
|
GroupHeader { text: @tr("SESSION"); }
|
|
|
|
SettingRow {
|
|
label: @tr("Restore session on startup");
|
|
description: @tr("Restore the queue and current track on the next launch.");
|
|
QbzToggle {
|
|
checked: SettingsState.persist-session;
|
|
toggled(v) => {
|
|
SettingsState.persist-session = v;
|
|
root.settings-bool("persist-session", v);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("Resume playback position");
|
|
description: @tr("Also seek back to where the saved track left off.");
|
|
enabled: SettingsState.persist-session;
|
|
QbzToggle {
|
|
checked: SettingsState.resume-position;
|
|
enabled: SettingsState.persist-session;
|
|
toggled(v) => {
|
|
SettingsState.resume-position = v;
|
|
root.settings-bool("resume-position", v);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("Auto-connect Qobuz Connect on startup");
|
|
description: @tr("Choose whether Qobuz Connect activates automatically when QBZ launches.");
|
|
QbzSelect {
|
|
menu-width: 240px;
|
|
options: SettingsState.qconnect-startup-modes;
|
|
current-index: SettingsState.qconnect-startup-index;
|
|
selected(i) => {
|
|
SettingsState.qconnect-startup-index = i;
|
|
root.settings-select("qconnect-startup", i);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("Qobuz Connect device name");
|
|
description: @tr("The name other Qobuz Connect apps see for this device. Applies on the next connection.");
|
|
// Same fixed width as the select rows so the input right-aligns
|
|
// flush with the rest of the panel (alignment standard).
|
|
HorizontalLayout {
|
|
width: 240px;
|
|
VerticalLayout {
|
|
alignment: center;
|
|
horizontal-stretch: 1;
|
|
LineEdit {
|
|
text: SettingsState.qconnect-device-name;
|
|
placeholder-text: SettingsState.qconnect-device-name-default;
|
|
// Commit on focus loss too (mirrors the Tauri onchange
|
|
// semantics: blur or Enter), not only on Enter.
|
|
changed has-focus => {
|
|
UiFocusState.text-input-focused = self.has-focus;
|
|
if (!self.has-focus) {
|
|
root.settings-string("qconnect-device-name", self.text);
|
|
}
|
|
}
|
|
edited(s) => {
|
|
SettingsState.qconnect-device-name = s;
|
|
}
|
|
accepted(s) => {
|
|
root.settings-string("qconnect-device-name", s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
Divider { }
|
|
Rectangle { height: 12px; }
|
|
|
|
GroupHeader { text: @tr("STREAMING"); }
|
|
|
|
SettingRow {
|
|
label: @tr("Stream uncached tracks");
|
|
description: @tr("Start uncached tracks via streaming instead of waiting for the full download.");
|
|
QbzToggle {
|
|
checked: SettingsState.stream-uncached;
|
|
toggled(v) => {
|
|
SettingsState.stream-uncached = v;
|
|
root.settings-bool("stream-uncached", v);
|
|
}
|
|
}
|
|
}
|
|
if SettingsState.stream-uncached: SettingRow {
|
|
label: @tr("Initial buffer size");
|
|
description: @tr("Seconds of audio buffered before streaming playback starts.");
|
|
// Fixed width so the slider row right-aligns flush with the toggle
|
|
// and select rows instead of stretching across the row.
|
|
HorizontalLayout {
|
|
width: 200px;
|
|
spacing: 12px;
|
|
VerticalLayout {
|
|
alignment: center;
|
|
horizontal-stretch: 1;
|
|
QbzSlider {
|
|
minimum: 1;
|
|
maximum: 10;
|
|
value: SettingsState.buffer-seconds;
|
|
changed(v) => {
|
|
SettingsState.buffer-seconds = v;
|
|
root.settings-slider("buffer-seconds", v);
|
|
}
|
|
}
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
horizontal-stretch: 0;
|
|
Text {
|
|
text: SettingsState.buffer-seconds + @tr("s");
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.body;
|
|
font-weight: Typography.medium;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("Streaming only");
|
|
description: @tr("Skip writing tracks to the local cache while streaming.");
|
|
QbzToggle {
|
|
checked: SettingsState.streaming-only;
|
|
toggled(v) => {
|
|
SettingsState.streaming-only = v;
|
|
root.settings-bool("streaming-only", v);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("When quality retries fail");
|
|
description: @tr("What to do when every quality tier for a track is unavailable.");
|
|
QbzSelect {
|
|
menu-width: 240px;
|
|
options: SettingsState.retry-behaviors;
|
|
current-index: SettingsState.retry-behavior-index;
|
|
selected(i) => {
|
|
SettingsState.retry-behavior-index = i;
|
|
root.settings-select("retry-behavior", i);
|
|
}
|
|
}
|
|
}
|
|
}
|