refactor: UI polish — lock sidebar, remove nav buttons, uniform artist tables, deep shuffle
- Lock sidebar width (setFixedWidth) so it doesn't jump between views - Remove back/forward navigation buttons and all NavPage history code - Uniform column layout on artist page: hide Artist column from both Popular Tracks and release sections, set matching fixed column widths so columns align vertically across all sections - Deep shuffle: new Rust FFI endpoint fetches tracks from all albums in parallel, combines them, and returns via EV_DEEP_SHUFFLE_OK - Auto-paginate artist releases in Rust (loop until has_more=false) so all releases load at once sorted newest-first Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,8 @@ enum QobuzEvent {
|
||||
EV_PLAYLIST_DELETED = 21,
|
||||
EV_PLAYLIST_TRACK_ADDED = 22,
|
||||
EV_USER_OK = 23,
|
||||
EV_ARTIST_RELEASES_OK = 24,
|
||||
EV_DEEP_SHUFFLE_OK = 25,
|
||||
};
|
||||
|
||||
// Callback signature
|
||||
@@ -84,9 +86,12 @@ uint32_t qobuz_backend_viz_read(QobuzBackendOpaque *backend, float *buf, uint32_
|
||||
uint32_t qobuz_backend_viz_sample_rate(const QobuzBackendOpaque *backend);
|
||||
uint32_t qobuz_backend_viz_channels(const QobuzBackendOpaque *backend);
|
||||
|
||||
// Artist releases (full paginated list per release type)
|
||||
// Artist releases (auto-paginates to fetch all)
|
||||
void qobuz_backend_get_artist_releases(QobuzBackendOpaque *backend, int64_t artist_id, const char *release_type, uint32_t limit, uint32_t offset);
|
||||
|
||||
// Deep shuffle: fetch tracks from multiple albums (album_ids_json is a JSON array of strings)
|
||||
void qobuz_backend_get_albums_tracks(QobuzBackendOpaque *backend, const char *album_ids_json);
|
||||
|
||||
// Playlist management
|
||||
void qobuz_backend_create_playlist(QobuzBackendOpaque *backend, const char *name);
|
||||
void qobuz_backend_delete_playlist(QobuzBackendOpaque *backend, int64_t playlist_id);
|
||||
|
||||
@@ -69,6 +69,7 @@ pub const EV_TRACK_URL_OK: c_int = 17;
|
||||
pub const EV_TRACK_URL_ERR: c_int = 18;
|
||||
pub const EV_GENERIC_ERR: c_int = 19;
|
||||
pub const EV_ARTIST_RELEASES_OK: c_int = 24;
|
||||
pub const EV_DEEP_SHUFFLE_OK: c_int = 25;
|
||||
|
||||
// ---------- Callback ----------
|
||||
|
||||
@@ -263,7 +264,7 @@ pub unsafe extern "C" fn qobuz_backend_get_artist_releases(
|
||||
artist_id: i64,
|
||||
release_type: *const c_char,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
_offset: u32,
|
||||
) {
|
||||
let inner = &(*ptr).0;
|
||||
let client = inner.client.clone();
|
||||
@@ -271,19 +272,90 @@ pub unsafe extern "C" fn qobuz_backend_get_artist_releases(
|
||||
let rtype = CStr::from_ptr(release_type).to_string_lossy().into_owned();
|
||||
|
||||
spawn(inner, async move {
|
||||
let result = client.lock().await
|
||||
.get_artist_releases_list(artist_id, &rtype, limit, offset)
|
||||
.await;
|
||||
let (ev, json) = match result {
|
||||
Ok(r) => {
|
||||
// Wrap with the release_type so Qt can route to the right section
|
||||
let mut obj = r.as_object().cloned().unwrap_or_default();
|
||||
obj.insert("release_type".to_string(), serde_json::Value::String(rtype));
|
||||
(EV_ARTIST_RELEASES_OK, serde_json::to_string(&obj).unwrap_or_default())
|
||||
// Auto-paginate: fetch all pages until has_more is false.
|
||||
let mut all_items: Vec<serde_json::Value> = Vec::new();
|
||||
let mut offset: u32 = 0;
|
||||
loop {
|
||||
let result = client.lock().await
|
||||
.get_artist_releases_list(artist_id, &rtype, limit, offset)
|
||||
.await;
|
||||
match result {
|
||||
Ok(r) => {
|
||||
let obj = r.as_object().cloned().unwrap_or_default();
|
||||
if let Some(items) = obj.get("items").and_then(|v| v.as_array()) {
|
||||
all_items.extend(items.iter().cloned());
|
||||
}
|
||||
let has_more = obj.get("has_more").and_then(|v| v.as_bool()).unwrap_or(false);
|
||||
if !has_more {
|
||||
break;
|
||||
}
|
||||
offset += limit;
|
||||
}
|
||||
Err(e) => {
|
||||
call_cb(cb, ud, EV_GENERIC_ERR, &err_json(&e.to_string()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(e) => (EV_GENERIC_ERR, err_json(&e.to_string())),
|
||||
};
|
||||
call_cb(cb, ud, ev, &json);
|
||||
}
|
||||
let result = serde_json::json!({
|
||||
"release_type": rtype,
|
||||
"items": all_items,
|
||||
"has_more": false,
|
||||
"offset": 0
|
||||
});
|
||||
call_cb(cb, ud, EV_ARTIST_RELEASES_OK, &serde_json::to_string(&result).unwrap_or_default());
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Deep shuffle (fetch tracks from multiple albums) ----------
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn qobuz_backend_get_albums_tracks(
|
||||
ptr: *mut Backend,
|
||||
album_ids_json: *const c_char,
|
||||
) {
|
||||
let inner = &(*ptr).0;
|
||||
let client = inner.client.clone();
|
||||
let cb = inner.cb; let ud = inner.ud;
|
||||
let ids_str = CStr::from_ptr(album_ids_json).to_string_lossy().into_owned();
|
||||
|
||||
let album_ids: Vec<String> = match serde_json::from_str(&ids_str) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
call_cb(cb, ud, EV_GENERIC_ERR, &err_json(&e.to_string()));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
spawn(inner, async move {
|
||||
let mut all_tracks: Vec<serde_json::Value> = Vec::new();
|
||||
for id in &album_ids {
|
||||
let result = client.lock().await.get_album(id).await;
|
||||
if let Ok(album) = result {
|
||||
if let Some(tracks) = album.tracks.as_ref().and_then(|t| t.items.as_ref()) {
|
||||
for t in tracks {
|
||||
// Serialize track and inject album info for playback context
|
||||
if let Ok(mut tv) = serde_json::to_value(t) {
|
||||
if let Some(obj) = tv.as_object_mut() {
|
||||
// Ensure album context is present on each track
|
||||
if obj.get("album").is_none() || obj["album"].is_null() {
|
||||
obj.insert("album".to_string(), serde_json::json!({
|
||||
"id": album.id,
|
||||
"title": album.title,
|
||||
"artist": album.artist,
|
||||
"image": album.image,
|
||||
}));
|
||||
}
|
||||
}
|
||||
all_tracks.push(tv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Skip albums that fail — don't abort the whole operation
|
||||
}
|
||||
let result = serde_json::json!({ "tracks": all_tracks });
|
||||
call_cb(cb, ud, EV_DEEP_SHUFFLE_OK, &serde_json::to_string(&result).unwrap_or_default());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user