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
174 lines
6.1 KiB
Plaintext
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();
|
|
}
|
|
}
|
|
}
|
|
}
|