feat(slint): carry the dropped favorites-label image through the data path
The favorites-labels wire item (LegacyLabelDto) carries an `image`
field that FavLabel silently dropped, so label cards had no artwork.
Parse it (bare string or the object form, via label::extract_label_image),
plumb it onto LabelCard + FavoriteLabelItem, and issue a FavoriteLabel
artwork job + apply arm mirroring favorite artists. Align the count line
to "{n} albums" (matches Tauri and the sibling artist card) and seed
labels-visible for the upcoming search filter.
cc
This commit is contained in:
4 files changed
+45
-9
No files matched your search
@@ -82,6 +82,8 @@ pub enum ArtworkTarget {
|
||||
FavoriteArtist { index: usize },
|
||||
/// A card in FavoritesState.playlists[index].
|
||||
FavoritePlaylist { index: usize },
|
||||
/// A card in FavoritesState.labels[index].
|
||||
FavoriteLabel { index: usize },
|
||||
/// A card in ForYouState.release-watch.albums[index].
|
||||
ForYouReleaseWatch { index: usize },
|
||||
/// A card in ForYouState.recent-albums.albums[index].
|
||||
@@ -541,6 +543,13 @@ fn apply_artwork(
|
||||
model.set_row_data(index, item);
|
||||
}
|
||||
}
|
||||
ArtworkTarget::FavoriteLabel { index } => {
|
||||
let model = window.global::<crate::FavoritesState>().get_labels();
|
||||
if let Some(mut item) = model.row_data(index) {
|
||||
item.image = image;
|
||||
model.set_row_data(index, item);
|
||||
}
|
||||
}
|
||||
ArtworkTarget::ForYouReleaseWatch { index } => {
|
||||
let model = window.global::<crate::ForYouState>().get_release_watch().albums;
|
||||
if let Some(mut item) = model.row_data(index) {
|
||||
|
||||
@@ -85,7 +85,10 @@ impl FavTab {
|
||||
|
||||
/// Favorites-labels response item — the qbz-models `Label` is just
|
||||
/// {id, name}, but the favorites payload carries an image + count,
|
||||
/// so parse into this richer local shape.
|
||||
/// so parse into this richer local shape. `image` is a bare string on
|
||||
/// the wire (LegacyLabelDto), but typed as Value to also tolerate the
|
||||
/// `{mega|extralarge|large|thumbnail|small}` object form other label
|
||||
/// surfaces return (resolved via `label::extract_label_image`).
|
||||
#[derive(Deserialize)]
|
||||
struct FavLabel {
|
||||
#[serde(default)]
|
||||
@@ -94,6 +97,8 @@ struct FavLabel {
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
albums_count: Option<u32>,
|
||||
#[serde(default)]
|
||||
image: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
pub enum FavData {
|
||||
@@ -141,6 +146,7 @@ pub struct LabelCard {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub albums_line: String,
|
||||
pub image_url: String,
|
||||
}
|
||||
|
||||
/// Fetch + parse one favorites tab.
|
||||
@@ -378,14 +384,17 @@ fn map_playlist(playlist: Playlist) -> PlaylistCard {
|
||||
}
|
||||
|
||||
fn map_label(label: FavLabel) -> LabelCard {
|
||||
// Tauri's favorites label card says "{n} albums" (library.albumCount),
|
||||
// matching the sibling FavArtistCard's "{n} albums".
|
||||
let albums_line = match label.albums_count {
|
||||
Some(n) if n > 0 => format!("{} releases", n),
|
||||
Some(n) if n > 0 => format!("{} albums", n),
|
||||
_ => String::new(),
|
||||
};
|
||||
LabelCard {
|
||||
id: label.id.to_string(),
|
||||
name: label.name,
|
||||
albums_line,
|
||||
image_url: crate::label::extract_label_image(label.image.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,9 +500,16 @@ pub fn apply_favorites(window: &AppWindow, data: FavData) {
|
||||
id: l.id.into(),
|
||||
name: l.name.into(),
|
||||
albums_line: l.albums_line.into(),
|
||||
image_url: l.image_url.into(),
|
||||
image: slint::Image::default(),
|
||||
})
|
||||
.collect();
|
||||
state.set_labels(ModelRc::new(VecModel::from(rows)));
|
||||
// `labels` is the full set the artwork pipeline targets;
|
||||
// `labels-visible` (what the grid renders) shares it until a
|
||||
// search filter forks it, so artwork stays live.
|
||||
let model = ModelRc::new(VecModel::from(rows));
|
||||
state.set_labels(model.clone());
|
||||
state.set_labels_visible(model);
|
||||
state.set_labels_total(total as i32);
|
||||
}
|
||||
}
|
||||
@@ -967,7 +983,14 @@ pub fn artwork_jobs(data: &FavData) -> Vec<ArtworkJob> {
|
||||
target: ArtworkTarget::FavoritePlaylist { index: i },
|
||||
})
|
||||
.collect(),
|
||||
// Labels render an icon, no remote artwork.
|
||||
FavData::Labels { .. } => Vec::new(),
|
||||
FavData::Labels { items, .. } => items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, l)| !l.image_url.is_empty())
|
||||
.map(|(i, l)| ArtworkJob {
|
||||
url: l.image_url.clone(),
|
||||
target: ArtworkTarget::FavoriteLabel { index: i },
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
@@ -112,8 +112,9 @@ where
|
||||
|
||||
/// Extract the best URL from /label/page's flexible image value. It
|
||||
/// can be a bare string or an object with mega/extralarge/large/...
|
||||
/// keys (mirrors the Svelte extraction order).
|
||||
fn extract_label_image(image: Option<&serde_json::Value>) -> String {
|
||||
/// keys (mirrors the Svelte extraction order). Reused by the favorites
|
||||
/// Labels tab, whose wire `image` is a bare string per the Android DTO.
|
||||
pub(crate) fn extract_label_image(image: Option<&serde_json::Value>) -> String {
|
||||
let Some(image) = image else {
|
||||
return String::new();
|
||||
};
|
||||
|
||||
@@ -1448,11 +1448,13 @@ export struct FavoritePlaylistItem {
|
||||
cover: image,
|
||||
}
|
||||
|
||||
// One label row in the Favorites > Labels list.
|
||||
// One label card in the Favorites > Labels grid.
|
||||
export struct FavoriteLabelItem {
|
||||
id: string,
|
||||
name: string,
|
||||
albums-line: string, // "N releases"
|
||||
albums-line: string, // "N albums"
|
||||
image-url: string, // resolved URL ("" -> gradient placeholder)
|
||||
image: image, // decoded cover, filled by the artwork pipeline
|
||||
}
|
||||
|
||||
// Library > Favorites state. The active tab decides which model is
|
||||
@@ -1486,6 +1488,7 @@ export global FavoritesState {
|
||||
in property <[FavoriteArtistItem]> artists: [];
|
||||
in property <[FavoritePlaylistItem]> playlists: [];
|
||||
in property <[FavoriteLabelItem]> labels: [];
|
||||
in property <[FavoriteLabelItem]> labels-visible: []; // after search filter (rendered)
|
||||
in property <int> tracks-total: 0;
|
||||
in property <int> albums-total: 0;
|
||||
in property <int> artists-total: 0;
|
||||
|
||||
Reference in new issue
Block a user