feat(slint): favorites load error + retry state
navigate_favorites stores the load error in FavoritesState.load-error (cleared on each load attempt); the view shows a "Couldn't load favorites" + detail + Retry block when set, and the tracks/albums tabs are gated so the error replaces them instead of showing alongside an empty state. Retry re-navigates the current tab. cc
This commit is contained in:
4 files changed
+80
-5
No files matched your search
@@ -717,7 +717,9 @@ pub fn selected_ids(window: &AppWindow) -> Vec<String> {
|
||||
}
|
||||
|
||||
pub fn reset_loading(window: &AppWindow) {
|
||||
window.global::<FavoritesState>().set_loading(true);
|
||||
let state = window.global::<FavoritesState>();
|
||||
state.set_loading(true);
|
||||
state.set_load_error("".into());
|
||||
}
|
||||
|
||||
/// Artwork jobs for the freshly loaded tab.
|
||||
|
||||
@@ -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::<FavoritesState>().set_loading(false);
|
||||
let msg = e.to_string();
|
||||
let _ = weak.upgrade_in_event_loop(move |w| {
|
||||
let st = w.global::<FavoritesState>();
|
||||
st.set_loading(false);
|
||||
st.set_load_error(msg.into());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4385,6 +4388,30 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
});
|
||||
});
|
||||
}
|
||||
{
|
||||
// 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::<FavoritesActions>()
|
||||
.on_retry_load(move || {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
let tab_id = w.global::<FavoritesState>().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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -1478,6 +1478,7 @@ export global FavoritesState {
|
||||
in property <int> playlists-total: 0;
|
||||
in property <int> labels-total: 0;
|
||||
in property <bool> loading: false;
|
||||
in property <string> 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
|
||||
|
||||
Reference in new issue
Block a user