Files
qbz/crates/qbz-slint/ui/settings/OfflineSettings.slint
T
vicrodh b4caa5a309 feat(slint): Settings Offline Mode group (tri-state status, induced toggle, network folders)
New OFFLINE MODE group above the cache group on the Offline settings page:
read-only tri-state status row (online / no connection / offline mode
enabled, with captive-portal hint) plus an honest Check now that forces an
actor re-probe; Enable Offline Mode toggle driving the shared engine (issue
#279 stream_first_track snapshot/restore included, Playback panel re-synced
after); Show Network Folder Content toggle mounted only while induced
offline is on. Deliberate deviations from Tauri per the approved spec: no
probe gate on disable, no dead cast/scrobbling toggles.

cc
2026-06-09 20:46:05 -06:00

174 lines
6.1 KiB
Plaintext

// Settings > Offline — two explicitly-titled groups (the Tauri tab hid
// this boundary; the owner endorsed the split):
//
// - OFFLINE MODE — the app operating without Qobuz: tri-state status row
// (online / no connection / induced) with an honest "Check now"
// re-probe, the persisted Enable Offline Mode toggle (#279 stream-first
// snapshot handled by the shared engine), and the network-folders
// policy toggle (mounted only while induced is on, Tauri parity).
// - OFFLINE CACHE — downloads: manager launch + open-folder / clear-all.
// The Local-Library rows (show-in-library, repair) arrive with the
// Local Library port.
//
// Same structure as the other settings panels (GroupHeader + SettingRow +
// SecondaryButton). Toggle changes bubble through settings-bool; the
// status row reads the live OfflineState global; the panel's init
// re-seeds the toggle states from the engine store (OfflineModeActions).
import { Theme } from "../foundation/semantic-colors.slint";
import { Palette } from "../foundation/palette.slint";
import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint";
import { OfflineManagerActions, OfflineModeActions, OfflineState, SettingsState } from "../state.slint";
import { QbzToggle } from "../primitives/QbzToggle.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;
}
// Matches AudioSettings/AppearanceSettings' SecondaryButton.
component SecondaryButton inherits Rectangle {
in property <string> label;
in property <bool> danger: false;
callback clicked();
width: 160px;
height: 34px;
border-radius: Radius.sm;
border-width: 1px;
border-color: root.danger ? #e0564f : Theme.border-subtle;
background: ta.has-hover ? Theme.surface-hover : Theme.surface-elevated;
Text {
width: 100%;
height: 100%;
text: root.label;
color: root.danger ? #e0564f : Theme.text-secondary;
font-size: Typography.body;
font-weight: Typography.medium;
horizontal-alignment: center;
vertical-alignment: center;
}
ta := TouchArea {
mouse-cursor: pointer;
clicked => {
root.clicked();
}
}
}
export component OfflineSettings inherits VerticalLayout {
callback settings-bool(string, bool);
spacing: 4px;
// Re-seed the toggle states from the engine store on every mount of
// this section (same lazy-load hook LocalLibrarySettings uses).
init => {
OfflineModeActions.load();
}
GroupHeader { text: @tr("OFFLINE MODE"); }
SettingRow {
label: @tr("Status");
description: OfflineState.captive-portal
? @tr("Captive portal detected — sign in to the network to get online.")
: "";
HorizontalLayout {
spacing: 16px;
VerticalLayout {
alignment: center;
// Tri-state: online green / real-offline amber / induced accent.
Text {
text: OfflineState.mode == 2 ? @tr("Offline mode enabled")
: OfflineState.mode == 1 ? @tr("No connection")
: @tr("Online");
color: OfflineState.mode == 2 ? Theme.accent
: OfflineState.mode == 1 ? Palette.warning
: #22c55e;
font-size: Typography.body;
font-weight: Typography.medium;
}
}
SecondaryButton {
label: SettingsState.offline-checking ? @tr("Checking...") : @tr("Check now");
clicked => {
if (!SettingsState.offline-checking) {
OfflineModeActions.check-now();
}
}
}
}
}
SettingRow {
label: @tr("Enable Offline Mode");
description: @tr("Manually switch to offline mode even with internet.");
QbzToggle {
checked: SettingsState.offline-mode-enabled;
toggled(v) => {
SettingsState.offline-mode-enabled = v;
root.settings-bool("offline-mode-enabled", v);
}
}
}
// The network-folders policy only applies while induced offline is on,
// so the row only mounts then (Tauri parity; ADR-010 conditional mount).
if SettingsState.offline-mode-enabled: SettingRow {
label: @tr("Show Network Folder Content");
description: @tr("Display content from network-mounted folders (NAS, Samba, etc.) while in offline mode.");
QbzToggle {
checked: SettingsState.offline-show-network-folders;
toggled(v) => {
SettingsState.offline-show-network-folders = v;
root.settings-bool("offline-show-network-folders", v);
}
}
}
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
GroupHeader { text: @tr("OFFLINE CACHE"); }
SettingRow {
label: @tr("Manage offline cache");
description: @tr("Browse and manage your downloaded tracks and albums.");
SecondaryButton {
label: @tr("Open manager");
clicked => {
OfflineManagerActions.open();
}
}
}
SettingRow {
label: @tr("Cache folder");
description: @tr("Open the folder where offline tracks are stored on disk.");
SecondaryButton {
label: @tr("Open folder");
clicked => {
OfflineManagerActions.open-folder();
}
}
}
SettingRow {
label: @tr("Clear offline cache");
description: @tr("Remove every downloaded track. This cannot be undone.");
SecondaryButton {
label: @tr("Clear all");
danger: true;
clicked => {
OfflineManagerActions.clear-all();
}
}
}
}