feat: add LabelView to browse albums by record label

- Add get_label API endpoint and Tauri command
- Create LabelView component with album grid, search, and infinite scroll
- Add clickable label links in AlbumDetailView, AlbumCreditsModal, and TrackInfoModal
- Add label_id to AlbumInfo and AlbumDetail types
- Add label to QobuzTrack album summary for track info display
This commit is contained in:
vicrodh committed 2026-01-25 11:28:51 -06:00
1 parent 04197b98f0
commit 6467ca038e
13 files changed
+775 -8

No files matched your search

+24
View File
@@ -478,6 +478,30 @@ impl QobuzClient {
Ok(serde_json::from_value(response)?)
}
/// Get label by ID with albums
pub async fn get_label(&self, label_id: u64, limit: u32, offset: u32) -> Result<LabelDetail> {
let url = endpoints::build_url(paths::LABEL_GET);
let locale = self.locale().await;
let response: Value = self
.http
.get(&url)
.header("X-App-Id", self.app_id().await?)
.query(&[
("label_id", label_id.to_string()),
("extra", "albums".to_string()),
("limit", limit.to_string()),
("offset", offset.to_string()),
("lang", locale),
])
.send()
.await?
.json()
.await?;
Ok(serde_json::from_value(response)?)
}
// === Authenticated endpoints ===
/// Get stream URL for a track (requires auth + signature)
+14 -1
View File
@@ -158,6 +158,8 @@ pub struct AlbumSummary {
pub title: String,
#[serde(default)]
pub image: ImageSet,
/// Label (if returned in track response)
pub label: Option<Label>,
}
/// Artist model
@@ -254,13 +256,24 @@ impl ImageSet {
}
}
/// Label model
/// Label model (basic reference)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Label {
pub id: u64,
pub name: String,
}
/// Label detail with albums
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LabelDetail {
pub id: u64,
pub name: String,
pub description: Option<String>,
pub image: Option<ImageSet>,
pub albums: Option<SearchResultsPage<Album>>,
pub albums_count: Option<u32>,
}
/// Genre model
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Genre {
+2
View File
@@ -40,6 +40,7 @@ pub struct AlbumInfo {
pub year: String,
pub release_date: Option<String>,
pub label: String,
pub label_id: Option<u64>,
pub genre: String,
pub quality: String,
pub track_count: u32,
@@ -100,6 +101,7 @@ fn album_to_info(album: &Album) -> AlbumInfo {
year,
release_date: album.release_date_original.clone(),
label: album.label.as_ref().map(|l| l.name.clone()).unwrap_or_default(),
label_id: album.label.as_ref().map(|l| l.id),
genre: album.genre.as_ref().map(|g| g.name.clone()).unwrap_or_default(),
quality: format_quality(album.maximum_bit_depth, album.maximum_sampling_rate),
track_count: album.tracks_count.unwrap_or(0),
+23 -1
View File
@@ -2,7 +2,7 @@
use tauri::State;
use crate::api::{Album, Artist, ArtistAlbums, SearchResultsPage, Track};
use crate::api::{Album, Artist, ArtistAlbums, LabelDetail, SearchResultsPage, Track};
use crate::api_cache::ApiCacheState;
use crate::AppState;
use serde::{Deserialize, Serialize};
@@ -264,3 +264,25 @@ pub async fn get_similar_artists(
.await
.map_err(|e| e.to_string())
}
/// Get label detail with albums
#[tauri::command]
pub async fn get_label(
label_id: u64,
limit: Option<u32>,
offset: Option<u32>,
state: State<'_, AppState>,
) -> Result<LabelDetail, String> {
log::info!(
"Command: get_label {} limit={:?} offset={:?}",
label_id,
limit,
offset
);
let client = state.client.lock().await;
client
.get_label(label_id, limit.unwrap_or(100), offset.unwrap_or(0))
.await
.map_err(|e| e.to_string())
}
+1
View File
@@ -436,6 +436,7 @@ pub fn run() {
commands::get_artist_detail,
commands::get_artist_albums,
commands::get_similar_artists,
commands::get_label,
// Playback commands
commands::play_track,
commands::prefetch_track,