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
This commit is contained in:
7 files changed
+282
-12
No files matched your search
@@ -3990,6 +3990,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Settings > Offline MODE — re-seed the toggle states on panel mount
|
||||||
|
// (the panel's init fires load), and the status row's "Check now"
|
||||||
|
// connectivity re-probe. The toggles themselves persist through the
|
||||||
|
// generic settings-bool path above.
|
||||||
|
{
|
||||||
|
let weak = window.as_weak();
|
||||||
|
let handle = tokio_rt.handle().clone();
|
||||||
|
window.global::<OfflineModeActions>().on_load(move || {
|
||||||
|
offline_mode::seed_settings(weak.clone(), handle.clone());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let weak = window.as_weak();
|
||||||
|
let handle = tokio_rt.handle().clone();
|
||||||
|
window.global::<OfflineModeActions>().on_check_now(move || {
|
||||||
|
offline_mode::check_now(weak.clone(), handle.clone());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Appearance settings persistence. The toggles/selects set their
|
// Appearance settings persistence. The toggles/selects set their
|
||||||
// AppearanceState property locally, then fire these generic callbacks so
|
// AppearanceState property locally, then fire these generic callbacks so
|
||||||
// the choice survives restart. Tray keys persist to the shared per-user
|
// the choice survives restart. Tray keys persist to the shared per-user
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use qbz_app::offline_mode::{Connectivity, ConnectivityActor, OfflineMode, Offlin
|
|||||||
use qbz_app::settings::subscription::SubscriptionStateStore;
|
use qbz_app::settings::subscription::SubscriptionStateStore;
|
||||||
use qbz_app::user_data::UserDataPaths;
|
use qbz_app::user_data::UserDataPaths;
|
||||||
|
|
||||||
use crate::{AppWindow, OfflineState};
|
use crate::{AppWindow, OfflineState, SettingsState};
|
||||||
|
|
||||||
/// Process-global engine. Exists from first use; per-user state binds via
|
/// Process-global engine. Exists from first use; per-user state binds via
|
||||||
/// [`init_for_user`], connectivity via [`start`].
|
/// [`init_for_user`], connectivity via [`start`].
|
||||||
@@ -57,15 +57,58 @@ pub fn start() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force an immediate connectivity re-probe (Settings "Check now",
|
/// Force an immediate connectivity re-probe (Settings "Check now").
|
||||||
/// consumed by the Settings slice).
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn request_recheck() {
|
pub fn request_recheck() {
|
||||||
if let Some(actor) = CONNECTIVITY.get() {
|
if let Some(actor) = CONNECTIVITY.get() {
|
||||||
actor.request_recheck();
|
actor.request_recheck();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Settings > Offline "Check now": flag the in-flight state (the status
|
||||||
|
/// row's button flips to "Checking..."), then force an actor re-probe.
|
||||||
|
/// The flag clears on the next engine broadcast ([`apply_status`]) — or
|
||||||
|
/// after a short timeout when the verdict comes back unchanged, since the
|
||||||
|
/// actor only broadcasts state flips.
|
||||||
|
pub fn check_now(weak: slint::Weak<AppWindow>, handle: tokio::runtime::Handle) {
|
||||||
|
let set_weak = weak.clone();
|
||||||
|
let _ = set_weak.upgrade_in_event_loop(|w| {
|
||||||
|
w.global::<SettingsState>().set_offline_checking(true);
|
||||||
|
});
|
||||||
|
request_recheck();
|
||||||
|
handle.spawn(async move {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(4)).await;
|
||||||
|
let _ = weak.upgrade_in_event_loop(|w| {
|
||||||
|
w.global::<SettingsState>().set_offline_checking(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seed the Settings > Offline MODE toggle states from the persisted
|
||||||
|
/// engine store. Fired by the panel's `init` (`OfflineModeActions.load`),
|
||||||
|
/// so every mount of Settings > Offline re-reads them — the same lazy-load
|
||||||
|
/// hook LocalLibrarySettings uses. Best-effort: pre-session reads (no
|
||||||
|
/// store bound) keep the defaults.
|
||||||
|
pub fn seed_settings(weak: slint::Weak<AppWindow>, handle: tokio::runtime::Handle) {
|
||||||
|
handle.spawn(async move {
|
||||||
|
let settings = match tokio::task::spawn_blocking(|| engine().settings()).await {
|
||||||
|
Ok(Ok(s)) => s,
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::warn!("[qbz-slint] offline mode settings read failed: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("[qbz-slint] offline mode settings seed task failed: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let _ = weak.upgrade_in_event_loop(move |w| {
|
||||||
|
let st = w.global::<SettingsState>();
|
||||||
|
st.set_offline_mode_enabled(settings.manual_offline_mode);
|
||||||
|
st.set_offline_show_network_folders(settings.show_network_folders_in_manual_offline);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Mirror every engine status change into the `OfflineState` Slint global
|
/// Mirror every engine status change into the `OfflineState` Slint global
|
||||||
/// (login affordances + the D2 recovery banner read it). Also seeds
|
/// (login affordances + the D2 recovery banner read it). Also seeds
|
||||||
/// `has-previous-session` once; `enter_shell` refreshes it after a
|
/// `has-previous-session` once; `enter_shell` refreshes it after a
|
||||||
@@ -107,6 +150,8 @@ fn apply_status(w: &AppWindow, status: OfflineStatus) {
|
|||||||
});
|
});
|
||||||
state.set_captive_portal(status.captive_portal);
|
state.set_captive_portal(status.captive_portal);
|
||||||
state.set_show_recovery_banner(status.show_recovery_banner());
|
state.set_show_recovery_banner(status.show_recovery_banner());
|
||||||
|
// A status broadcast resolves any in-flight Settings "Check now".
|
||||||
|
w.global::<SettingsState>().set_offline_checking(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `<data_dir>/qbz/users/<user_id>/` — the per-user directory both the
|
/// `<data_dir>/qbz/users/<user_id>/` — the per-user directory both the
|
||||||
|
|||||||
@@ -754,6 +754,20 @@ pub async fn handle_bool(
|
|||||||
value: bool,
|
value: bool,
|
||||||
) {
|
) {
|
||||||
let key = key.as_str();
|
let key = key.as_str();
|
||||||
|
// Offline-MODE toggles persist through the shared engine's per-user
|
||||||
|
// store, not the audio/playback stores — routed apart from the Apply
|
||||||
|
// machinery below.
|
||||||
|
match key {
|
||||||
|
"offline-mode-enabled" => {
|
||||||
|
set_offline_mode(ctx, runtime, weak, value).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
"offline-show-network-folders" => {
|
||||||
|
set_offline_network_folders(weak, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
// Cross-setting cascades — force dependent settings off and persist
|
// Cross-setting cascades — force dependent settings off and persist
|
||||||
// those forced changes. `cascaded` flags whether a full snapshot
|
// those forced changes. `cascaded` flags whether a full snapshot
|
||||||
// re-push is needed afterwards.
|
// re-push is needed afterwards.
|
||||||
@@ -873,6 +887,82 @@ pub async fn handle_bool(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Settings > Offline — the Enable Offline Mode toggle (induced offline).
|
||||||
|
///
|
||||||
|
/// The shared engine persists the flag, handles the #279 stream-first
|
||||||
|
/// snapshot/restore against the real audio store, and broadcasts the
|
||||||
|
/// status change (the offline_mode UI forwarder updates `OfflineState`).
|
||||||
|
/// Exit is ALWAYS allowed — no confirm dialog, no network-probe gate
|
||||||
|
/// (deliberate improvement over Tauri's exit trap, spec §3.1).
|
||||||
|
async fn set_offline_mode(
|
||||||
|
ctx: Arc<SettingsCtx>,
|
||||||
|
runtime: Arc<AppRuntime<SlintAdapter>>,
|
||||||
|
weak: slint::Weak<AppWindow>,
|
||||||
|
value: bool,
|
||||||
|
) {
|
||||||
|
let engine = crate::offline_mode::engine();
|
||||||
|
// Best-effort #279: pass the real audio store when it's open (the
|
||||||
|
// outer Ok-wrap keeps store-unavailability distinct from engine
|
||||||
|
// failures); flip the mode without the snapshot when it isn't.
|
||||||
|
let result = match with_audio(&ctx.audio, |store| {
|
||||||
|
Ok(engine.set_induced(value, Some(store)))
|
||||||
|
}) {
|
||||||
|
Ok(inner) => inner,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"[qbz-slint] offline toggle: audio store unavailable for the #279 snapshot ({e}); flipping without it"
|
||||||
|
);
|
||||||
|
engine.set_induced(value, None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match result {
|
||||||
|
Ok(status) => {
|
||||||
|
log::info!(
|
||||||
|
"[qbz-slint] offline mode toggled: induced={value} (mode={:?})",
|
||||||
|
status.mode
|
||||||
|
);
|
||||||
|
// The #279 snapshot may have mutated stream_first_track behind
|
||||||
|
// the Playback panel's back: reload the live player settings and
|
||||||
|
// re-push the full snapshot so the "Stream uncached tracks"
|
||||||
|
// toggle stays honest (Tauri's audio-settings-changed re-sync).
|
||||||
|
apply_audio(&ctx, &runtime, Apply::Reload);
|
||||||
|
rebuild_and_push(ctx, weak).await;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("[qbz-slint] offline mode toggle failed: {e}");
|
||||||
|
// Revert the optimistic toggle to the engine's actual state.
|
||||||
|
let actual = engine
|
||||||
|
.settings()
|
||||||
|
.map(|s| s.manual_offline_mode)
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
engine.status().mode == qbz_app::offline_mode::OfflineMode::InducedOffline
|
||||||
|
});
|
||||||
|
let _ = weak.upgrade_in_event_loop(move |w| {
|
||||||
|
w.global::<SettingsState>().set_offline_mode_enabled(actual);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Settings > Offline — the Show Network Folder Content policy toggle
|
||||||
|
/// (visible only while induced offline is on). Pure persistence; the
|
||||||
|
/// gating slice consumes the flag.
|
||||||
|
fn set_offline_network_folders(weak: slint::Weak<AppWindow>, value: bool) {
|
||||||
|
let engine = crate::offline_mode::engine();
|
||||||
|
if let Err(e) = engine.set_show_network_folders(value) {
|
||||||
|
log::error!("[qbz-slint] show-network-folders toggle failed: {e}");
|
||||||
|
// Revert the optimistic toggle to the persisted state.
|
||||||
|
let actual = engine
|
||||||
|
.settings()
|
||||||
|
.map(|s| s.show_network_folders_in_manual_offline)
|
||||||
|
.unwrap_or(!value);
|
||||||
|
let _ = weak.upgrade_in_event_loop(move |w| {
|
||||||
|
w.global::<SettingsState>()
|
||||||
|
.set_offline_show_network_folders(actual);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle a slider change: persist it and reload the player settings.
|
/// Handle a slider change: persist it and reload the player settings.
|
||||||
/// Currently only the Initial Buffer Size slider exists.
|
/// Currently only the Initial Buffer Size slider exists.
|
||||||
pub fn handle_slider(
|
pub fn handle_slider(
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { AppShell } from "shell/AppShell.slint";
|
|||||||
import { Toast } from "primitives/Toast.slint";
|
import { Toast } from "primitives/Toast.slint";
|
||||||
|
|
||||||
// Re-export the state globals so the Rust layer can populate them.
|
// Re-export the state globals so the Rust layer can populate them.
|
||||||
export { HomeState, HomeActions, DiscoverBrowseState, DiscoverBrowseActions, ForYouState, MixState, GenreFilterState, GenreFilterActions, AlbumState, ArtistState, NavState, ShellState, SessionState, SettingsState, AlbumActions, ArtistActions, ArtworkActions, NowPlayingState, QueueState, SearchState, SearchActions, NetworkSidebarState, NetworkSidebarActions, MusicianState, MusicianActions, LabelState, LabelActions, LocationViewState, LocationViewActions, FavoritesState, FavoritesActions, PlaylistPickerState, PlaylistPickerActions, PlaylistState, PlaylistActions, SidebarState, SidebarActions, SidebarFolderPopupState, CreatePlaylistState, CreatePlaylistActions, EditPlaylistState, EditPlaylistActions, CreateFolderState, CreateFolderActions, MyQbzCreateState, MyQbzCreateActions, DragState, DragActions, PlaylistManagerState, PlaylistManagerActions, OfflineManagerState, OfflineManagerActions, MyQbzState, MyQbzActions, MixtapeCardItem, MyQbzAddState, MyQbzAddActions, MyQbzAddRow, MyQbzDetailState, MyQbzDetailActions, MixtapeDetailItem, MyQbzEditState, MyQbzEditActions, MyQbzMixState, MyQbzMixActions, DiscoBuilderState, DiscoBuilderActions, DiscoGroup, DiscoCandidate, LocalLibraryState, LocalLibraryActions, LibraryFoldersState, LibFolderEditState, LibraryManageActions, LibraryScanState, LibAlbumFilterState, LocalAlbumState, LocalAlbumActions, TagEditorState, TagEditorActions, FolderEditState, FolderEditActions, ToastState, TextUtil, QconnectDevState, QconnectDevice, AppearanceState, MyQbzBrandingState, EphemeralPlayChoiceState, EphemeralPlayChoiceActions, PlexSettingsState, PlexAuthActions, PlexSectionItem, OfflineState } from "state.slint";
|
export { HomeState, HomeActions, DiscoverBrowseState, DiscoverBrowseActions, ForYouState, MixState, GenreFilterState, GenreFilterActions, AlbumState, ArtistState, NavState, ShellState, SessionState, SettingsState, AlbumActions, ArtistActions, ArtworkActions, NowPlayingState, QueueState, SearchState, SearchActions, NetworkSidebarState, NetworkSidebarActions, MusicianState, MusicianActions, LabelState, LabelActions, LocationViewState, LocationViewActions, FavoritesState, FavoritesActions, PlaylistPickerState, PlaylistPickerActions, PlaylistState, PlaylistActions, SidebarState, SidebarActions, SidebarFolderPopupState, CreatePlaylistState, CreatePlaylistActions, EditPlaylistState, EditPlaylistActions, CreateFolderState, CreateFolderActions, MyQbzCreateState, MyQbzCreateActions, DragState, DragActions, PlaylistManagerState, PlaylistManagerActions, OfflineManagerState, OfflineManagerActions, MyQbzState, MyQbzActions, MixtapeCardItem, MyQbzAddState, MyQbzAddActions, MyQbzAddRow, MyQbzDetailState, MyQbzDetailActions, MixtapeDetailItem, MyQbzEditState, MyQbzEditActions, MyQbzMixState, MyQbzMixActions, DiscoBuilderState, DiscoBuilderActions, DiscoGroup, DiscoCandidate, LocalLibraryState, LocalLibraryActions, LibraryFoldersState, LibFolderEditState, LibraryManageActions, LibraryScanState, LibAlbumFilterState, LocalAlbumState, LocalAlbumActions, TagEditorState, TagEditorActions, FolderEditState, FolderEditActions, ToastState, TextUtil, QconnectDevState, QconnectDevice, AppearanceState, MyQbzBrandingState, EphemeralPlayChoiceState, EphemeralPlayChoiceActions, PlexSettingsState, PlexAuthActions, PlexSectionItem, OfflineState, OfflineModeActions } from "state.slint";
|
||||||
|
|
||||||
// Which top-level screen is shown. The app starts on `splash` while it
|
// Which top-level screen is shown. The app starts on `splash` while it
|
||||||
// restores a saved session, then resolves to `shell` or `login`.
|
// restores a saved session, then resolves to `shell` or `login`.
|
||||||
|
|||||||
@@ -1,13 +1,26 @@
|
|||||||
// Settings > Offline Playback (cache half). Same structure as the other
|
// Settings > Offline — two explicitly-titled groups (the Tauri tab hid
|
||||||
// settings panels (GroupHeader + SettingRow + a secondary button control) so
|
// this boundary; the owner endorsed the split):
|
||||||
// the section stays consistent as more offline settings land. Launches the
|
//
|
||||||
// Offline Cache Manager + offers open-folder / clear-all. The Local-Library
|
// - OFFLINE MODE — the app operating without Qobuz: tri-state status row
|
||||||
// rows (show-in-library, repair) arrive with the Local Library port.
|
// (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 { Theme } from "../foundation/semantic-colors.slint";
|
||||||
|
import { Palette } from "../foundation/palette.slint";
|
||||||
import { Typography } from "../foundation/typography.slint";
|
import { Typography } from "../foundation/typography.slint";
|
||||||
import { Radius } from "../foundation/radius.slint";
|
import { Radius } from "../foundation/radius.slint";
|
||||||
import { OfflineManagerActions } from "../state.slint";
|
import { OfflineManagerActions, OfflineModeActions, OfflineState, SettingsState } from "../state.slint";
|
||||||
|
import { QbzToggle } from "../primitives/QbzToggle.slint";
|
||||||
import { SettingRow } from "SettingRow.slint";
|
import { SettingRow } from "SettingRow.slint";
|
||||||
|
|
||||||
component GroupHeader inherits Text {
|
component GroupHeader inherits Text {
|
||||||
@@ -17,6 +30,11 @@ component GroupHeader inherits Text {
|
|||||||
font-weight: Typography.semibold;
|
font-weight: Typography.semibold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
component Divider inherits Rectangle {
|
||||||
|
height: 1px;
|
||||||
|
background: Theme.border-subtle;
|
||||||
|
}
|
||||||
|
|
||||||
// Matches AudioSettings/AppearanceSettings' SecondaryButton.
|
// Matches AudioSettings/AppearanceSettings' SecondaryButton.
|
||||||
component SecondaryButton inherits Rectangle {
|
component SecondaryButton inherits Rectangle {
|
||||||
in property <string> label;
|
in property <string> label;
|
||||||
@@ -47,8 +65,78 @@ component SecondaryButton inherits Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export component OfflineSettings inherits VerticalLayout {
|
export component OfflineSettings inherits VerticalLayout {
|
||||||
|
callback settings-bool(string, bool);
|
||||||
|
|
||||||
spacing: 4px;
|
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"); }
|
GroupHeader { text: @tr("OFFLINE CACHE"); }
|
||||||
|
|
||||||
SettingRow {
|
SettingRow {
|
||||||
|
|||||||
@@ -219,7 +219,11 @@ export component SettingsView inherits Rectangle {
|
|||||||
// AppearanceState and emit its own callbacks, so no
|
// AppearanceState and emit its own callbacks, so no
|
||||||
// SettingsView-level callback forwarding is needed here.
|
// SettingsView-level callback forwarding is needed here.
|
||||||
if !SettingsState.loading && SettingsState.section == 2: AppearanceSettings { }
|
if !SettingsState.loading && SettingsState.section == 2: AppearanceSettings { }
|
||||||
if !SettingsState.loading && SettingsState.section == 3: OfflineSettings { }
|
if !SettingsState.loading && SettingsState.section == 3: OfflineSettings {
|
||||||
|
settings-bool(k, v) => {
|
||||||
|
root.settings-bool(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
if !SettingsState.loading && SettingsState.section == 4: LocalLibrarySettings { }
|
if !SettingsState.loading && SettingsState.section == 4: LocalLibrarySettings { }
|
||||||
if !SettingsState.loading && SettingsState.section == 5: DeveloperSettings { }
|
if !SettingsState.loading && SettingsState.section == 5: DeveloperSettings { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2040,6 +2040,20 @@ export global SettingsState {
|
|||||||
// Playback — "When quality retries fail" dropdown.
|
// Playback — "When quality retries fail" dropdown.
|
||||||
in-out property <[string]> retry-behaviors: [];
|
in-out property <[string]> retry-behaviors: [];
|
||||||
in-out property <int> retry-behavior-index: 0;
|
in-out property <int> retry-behavior-index: 0;
|
||||||
|
|
||||||
|
// --- OFFLINE MODE --------------------------------------------------------
|
||||||
|
|
||||||
|
// Settings > Offline, MODE group (the app operating without Qobuz — NOT
|
||||||
|
// the offline cache). Backed by the shared engine's per-user store, not
|
||||||
|
// the audio/playback stores: seeded by OfflineModeActions.load() on panel
|
||||||
|
// mount; persisted through settings-bool keys "offline-mode-enabled" /
|
||||||
|
// "offline-show-network-folders".
|
||||||
|
in-out property <bool> offline-mode-enabled: false;
|
||||||
|
in-out property <bool> offline-show-network-folders: false;
|
||||||
|
// "Check now" re-probe in flight — flips the status row's button label.
|
||||||
|
// Cleared by Rust on the next status broadcast, or after a short timeout
|
||||||
|
// when the verdict comes back unchanged (the actor only broadcasts flips).
|
||||||
|
in-out property <bool> offline-checking: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Appearance settings — backs the Settings > Appearance panel. 1:1 with
|
// Appearance settings — backs the Settings > Appearance panel. 1:1 with
|
||||||
@@ -3121,3 +3135,13 @@ export global OfflineState {
|
|||||||
// successful shell entry.
|
// successful shell entry.
|
||||||
in property <string> login-error: "";
|
in property <string> login-error: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Settings > Offline, MODE group — Rust-wired actions (crate::offline_mode).
|
||||||
|
export global OfflineModeActions {
|
||||||
|
// (Re)seed the MODE-group toggle states from the engine store. Fired by
|
||||||
|
// the panel's init, so every mount of Settings > Offline re-reads them.
|
||||||
|
callback load();
|
||||||
|
// Force an immediate connectivity re-probe (the status row's "Check
|
||||||
|
// now"). The status text updates by itself via the engine forwarder.
|
||||||
|
callback check-now();
|
||||||
|
}
|
||||||
Reference in new issue
Block a user