diff --git a/crates/qbz-slint/src/album.rs b/crates/qbz-slint/src/album.rs index 1a80839bb..87c2155a9 100644 --- a/crates/qbz-slint/src/album.rs +++ b/crates/qbz-slint/src/album.rs @@ -259,6 +259,8 @@ pub fn apply_album(window: &AppWindow, data: AlbumData) { removing: false, cache_status: if crate::offline_cache::is_cached(&track.id) { 3 } else { 0 }, cache_progress: 0.0, + // Qobuz album-detail rows; local albums override via map_local_track. + source: "qobuz".into(), unlocking: false, }) .collect(); diff --git a/crates/qbz-slint/src/artist.rs b/crates/qbz-slint/src/artist.rs index dc9eac5a1..a9f14ea54 100644 --- a/crates/qbz-slint/src/artist.rs +++ b/crates/qbz-slint/src/artist.rs @@ -505,6 +505,7 @@ pub fn apply_artist(window: &AppWindow, data: ArtistData) { removing: false, cache_status: if crate::offline_cache::is_cached(&track.id) { 3 } else { 0 }, cache_progress: 0.0, + source: "qobuz".into(), unlocking: false, }) .collect(); diff --git a/crates/qbz-slint/src/favorites.rs b/crates/qbz-slint/src/favorites.rs index 7490c249e..7cc6f7cc1 100644 --- a/crates/qbz-slint/src/favorites.rs +++ b/crates/qbz-slint/src/favorites.rs @@ -421,6 +421,7 @@ pub fn apply_favorites(window: &AppWindow, data: FavData) { removing: false, cache_status: if crate::offline_cache::is_cached(&t.id) { 3 } else { 0 }, cache_progress: 0.0, + source: "qobuz".into(), unlocking: false, }) .collect(); diff --git a/crates/qbz-slint/src/label.rs b/crates/qbz-slint/src/label.rs index 78d2b9dd6..2a6a6c1f7 100644 --- a/crates/qbz-slint/src/label.rs +++ b/crates/qbz-slint/src/label.rs @@ -765,6 +765,7 @@ fn top_track_to_item(t: &TopTrack) -> TrackItem { removing: false, cache_status: if crate::offline_cache::is_cached(&t.id) { 3 } else { 0 }, cache_progress: 0.0, + source: "qobuz".into(), unlocking: false, } } diff --git a/crates/qbz-slint/src/local_library.rs b/crates/qbz-slint/src/local_library.rs index db083d195..db2ffec94 100644 --- a/crates/qbz-slint/src/local_library.rs +++ b/crates/qbz-slint/src/local_library.rs @@ -454,6 +454,13 @@ fn map_local_track(t: qbz_library::LocalTrack) -> TrackItem { removing: false, cache_status: 0, cache_progress: 0.0, + // Source indicator: offline copies read as Qobuz, user files as local. + source: match t.source.as_deref() { + Some("qobuz_download") => "qobuz", + Some("plex") => "plex", + _ => "local", + } + .into(), unlocking: false, } } diff --git a/crates/qbz-slint/src/mix.rs b/crates/qbz-slint/src/mix.rs index 091ff2bfa..b4bf266f7 100644 --- a/crates/qbz-slint/src/mix.rs +++ b/crates/qbz-slint/src/mix.rs @@ -177,6 +177,7 @@ fn to_item(track: &Track) -> TrackItem { removing: false, cache_status: if crate::offline_cache::is_cached(&track.id.to_string()) { 3 } else { 0 }, cache_progress: 0.0, + source: "qobuz".into(), unlocking: false, } } diff --git a/crates/qbz-slint/src/playlist.rs b/crates/qbz-slint/src/playlist.rs index 1f1568d9e..e2e40b717 100644 --- a/crates/qbz-slint/src/playlist.rs +++ b/crates/qbz-slint/src/playlist.rs @@ -384,6 +384,7 @@ fn to_item(track: &Track) -> TrackItem { removing: false, cache_status: if crate::offline_cache::is_cached(&track.id.to_string()) { 3 } else { 0 }, cache_progress: 0.0, + source: "qobuz".into(), unlocking: false, } } diff --git a/crates/qbz-slint/src/search.rs b/crates/qbz-slint/src/search.rs index 49193e7ea..93dc65029 100644 --- a/crates/qbz-slint/src/search.rs +++ b/crates/qbz-slint/src/search.rs @@ -375,6 +375,7 @@ fn track_item(row: TrackRowData) -> TrackItem { removing: false, cache_status: if is_cached { 3 } else { 0 }, cache_progress: 0.0, + source: "qobuz".into(), unlocking: false, } } diff --git a/crates/qbz-slint/ui/album/AlbumPageView.slint b/crates/qbz-slint/ui/album/AlbumPageView.slint index 7b859ef01..5ff93c931 100644 --- a/crates/qbz-slint/ui/album/AlbumPageView.slint +++ b/crates/qbz-slint/ui/album/AlbumPageView.slint @@ -529,8 +529,10 @@ export component AlbumPageView inherits Rectangle { horizontal-alignment: center; vertical-alignment: center; } - // Favorite / offline-cache column headers. - Rectangle { + // Favorite / offline-cache column headers (Qobuz). For + // a local album the rows show a source indicator + // instead, so one unlabeled column keeps alignment. + if !AlbumState.is-local: Rectangle { width: 28px; QbzIcon { source: @image-url("../assets/icons/heart.svg"); @@ -541,7 +543,7 @@ export component AlbumPageView inherits Rectangle { tint: Theme.text-muted; } } - Rectangle { + if !AlbumState.is-local: Rectangle { width: 28px; QbzIcon { source: @image-url("../assets/icons/cloud-download.svg"); @@ -552,6 +554,7 @@ export component AlbumPageView inherits Rectangle { tint: Theme.text-muted; } } + if AlbumState.is-local: Rectangle { width: 28px; } // Aligns with the per-row more-button slot. Rectangle { width: 32px; } } @@ -559,10 +562,14 @@ export component AlbumPageView inherits Rectangle { for track[index] in AlbumState.tracks: TrackRow { track: track; index: index; - // Album context: number cell (no cover), with the - // offline-cache button. + // Number cell (no cover). Qobuz albums carry the + // favorite heart (matching the column header) + the + // offline-cache button; local albums show the source + // indicator instead. show-artwork: false; - show-download: true; + show-favorite: !AlbumState.is-local; + show-download: !AlbumState.is-local; + show-source: AlbumState.is-local; media-action(kind, id, action) => { root.media-action(kind, id, action); } diff --git a/crates/qbz-slint/ui/locallibrary/LocalLibraryView.slint b/crates/qbz-slint/ui/locallibrary/LocalLibraryView.slint index 0cb4c3eba..0688ed723 100644 --- a/crates/qbz-slint/ui/locallibrary/LocalLibraryView.slint +++ b/crates/qbz-slint/ui/locallibrary/LocalLibraryView.slint @@ -513,6 +513,7 @@ export component LocalLibraryView inherits Rectangle { show-album: true; show-favorite: false; show-download: false; + show-source: true; media-action(kind, id, action) => { if (kind == "album" && action == "open") { LocalLibraryActions.open-album(id); diff --git a/crates/qbz-slint/ui/primitives/TrackRow.slint b/crates/qbz-slint/ui/primitives/TrackRow.slint index 14689d202..dc6459f3d 100644 --- a/crates/qbz-slint/ui/primitives/TrackRow.slint +++ b/crates/qbz-slint/ui/primitives/TrackRow.slint @@ -36,6 +36,9 @@ export component TrackRow inherits Rectangle { // button. A non-Qobuz surface (future Local Library / Plex rows, which are // already local) should set this false. in property show-download: true; + // Local Library: a subtle per-source indicator (local / qobuz / plex) + // shown INSTEAD of the favorite + offline columns. + in property show-source: false; in property multi-select-mode: false; // Custom-order reorder controls (up/down chevrons) at the leading // edge — used by the playlist custom sort. @@ -451,6 +454,31 @@ export component TrackRow inherits Rectangle { } } + // Source indicator (Local Library) — a subtle per-source glyph shown + // instead of the favorite/offline columns. local -> hard-drive + // (tinted), plex / qobuz(offline) -> their mark; small + flat, not the + // bold album-card badge. + if root.show-source && root.track.source != "": VerticalLayout { + alignment: center; + Rectangle { + width: 28px; + height: 28px; + Image { + source: root.track.source == "plex" + ? @image-url("../assets/icons/plex-logo.svg") + : (root.track.source == "qobuz" + ? @image-url("../assets/icons/qobuz-logo-filled.svg") + : @image-url("../assets/icons/hard-drive.svg")); + colorize: root.track.source == "local" ? Theme.text-muted : transparent; + width: root.track.source == "local" ? 15px : 16px; + height: self.width; + x: (parent.width - self.width) / 2; + y: (parent.height - self.height) / 2; + image-fit: contain; + } + } + } + // Context menu. VerticalLayout { alignment: center; diff --git a/crates/qbz-slint/ui/state.slint b/crates/qbz-slint/ui/state.slint index 35e2f62eb..f1564b3d6 100644 --- a/crates/qbz-slint/ui/state.slint +++ b/crates/qbz-slint/ui/state.slint @@ -251,6 +251,9 @@ export struct TrackItem { // album view, both via the media-action bubble channel. artist-id: string, album-id: string, + // "qobuz" | "local" | "plex" | "" — drives the Local Library source + // indicator (shown instead of the favorite/offline columns). + source: string, // Fading out (un-favorited in the favorites view) before removal. removing: bool, // Offline-cache status: 0 none, 1 queued, 2 downloading, 3 ready, 4 failed.