diff --git a/crates/qbz-slint/src/favorites.rs b/crates/qbz-slint/src/favorites.rs index d04ec8e2c..303cfa0ab 100644 --- a/crates/qbz-slint/src/favorites.rs +++ b/crates/qbz-slint/src/favorites.rs @@ -717,7 +717,9 @@ pub fn selected_ids(window: &AppWindow) -> Vec { } pub fn reset_loading(window: &AppWindow) { - window.global::().set_loading(true); + let state = window.global::(); + state.set_loading(true); + state.set_load_error("".into()); } /// Artwork jobs for the freshly loaded tab. diff --git a/crates/qbz-slint/src/main.rs b/crates/qbz-slint/src/main.rs index dbb6e9840..63ebb4ea4 100644 --- a/crates/qbz-slint/src/main.rs +++ b/crates/qbz-slint/src/main.rs @@ -791,8 +791,11 @@ fn navigate_favorites( } Err(e) => { log::error!("[qbz-slint] favorites load failed: {e}"); - let _ = weak.upgrade_in_event_loop(|w| { - w.global::().set_loading(false); + let msg = e.to_string(); + let _ = weak.upgrade_in_event_loop(move |w| { + let st = w.global::(); + st.set_loading(false); + st.set_load_error(msg.into()); }); } } @@ -4385,6 +4388,30 @@ fn main() -> Result<(), Box> { }); }); } + { + // Retry loading the current favorites tab after a load error. + let runtime = app_runtime.clone(); + let weak = window.as_weak(); + let handle = tokio_rt.handle().clone(); + let image_cache = image_cache.clone(); + window + .global::() + .on_retry_load(move || { + if let Some(w) = weak.upgrade() { + let tab_id = w.global::().get_active_tab().to_string(); + if let Some(tab) = favorites::FavTab::from_tab_id(&tab_id) { + navigate_favorites( + runtime.clone(), + weak.clone(), + &handle, + image_cache.clone(), + tab, + &tab_id, + ); + } + } + }); + } { // Local search over the loaded favorite tracks (title / artist / // album), re-deriving the rendered list. diff --git a/crates/qbz-slint/ui/favorites/FavoritesView.slint b/crates/qbz-slint/ui/favorites/FavoritesView.slint index 80c6eb651..cafc906c0 100644 --- a/crates/qbz-slint/ui/favorites/FavoritesView.slint +++ b/crates/qbz-slint/ui/favorites/FavoritesView.slint @@ -390,8 +390,52 @@ export component FavoritesView inherits Rectangle { LoadingSpinner { size: 36px; } } + // Load error + retry. + if !FavoritesState.loading && FavoritesState.load-error != "": VerticalLayout { + alignment: center; + spacing: 10px; + Rectangle { height: 24px; } + Text { + text: @tr("Couldn't load favorites."); + color: Theme.text-primary; + font-size: Typography.body; + font-weight: Typography.semibold; + horizontal-alignment: center; + } + Text { + text: FavoritesState.load-error; + color: Theme.text-muted; + font-size: Typography.legal; + horizontal-alignment: center; + } + HorizontalLayout { + alignment: center; + Rectangle { + width: retry-label.preferred-width + 28px; + height: 32px; + border-radius: 6px; + background: retry-ta.has-hover ? Theme.surface-hover : Theme.surface-elevated; + border-width: 1px; + border-color: Theme.border-subtle; + retry-label := Text { + text: @tr("Retry"); + color: Theme.text-primary; + font-size: Typography.legal; + x: (parent.width - self.width) / 2; + y: (parent.height - self.height) / 2; + } + retry-ta := TouchArea { + mouse-cursor: pointer; + clicked => { + FavoritesActions.retry-load(); + } + } + } + } + } + // --- Tracks tab -------------------------------------------- - if !FavoritesState.loading + if !FavoritesState.loading && FavoritesState.load-error == "" && FavoritesState.active-tab == "tracks": VerticalLayout { spacing: 0px; // Toolbar — results count (left) + expandable search (right). @@ -499,7 +543,7 @@ export component FavoritesView inherits Rectangle { } // --- Albums tab -------------------------------------------- - if !FavoritesState.loading + if !FavoritesState.loading && FavoritesState.load-error == "" && FavoritesState.active-tab == "albums": VerticalLayout { spacing: 0px; // Toolbar — count (left) + grid/list segmented toggle (right). diff --git a/crates/qbz-slint/ui/state.slint b/crates/qbz-slint/ui/state.slint index 67fb7ef4b..711e44eff 100644 --- a/crates/qbz-slint/ui/state.slint +++ b/crates/qbz-slint/ui/state.slint @@ -1478,6 +1478,7 @@ export global FavoritesState { in property playlists-total: 0; in property labels-total: 0; in property loading: false; + in property load-error: ""; // non-empty -> show error + retry } export global FavoritesActions { @@ -1503,6 +1504,7 @@ export global FavoritesActions { callback albums-shuffle(); callback unfavorite-track(string /* track id */); callback unfavorite-album(string /* album id */); + callback retry-load(); } // A Qobuz mix detail page (DailyQ / WeeklyQ / FavQ / TopQ), opened