fix(cards): carry the Qobuz album version in card titles + tooltip when elided

Every album card producer copied `title` raw although the model carries
`version` ("50th Anniversary", "Deluxe"); the album page, info modal and quick
view already used `format_album_title`. The Artist page listed two identical
"All Things Must Pass" cards for two different editions.

- Route the 8 card producers through `album_qt::format_album_title`
  (artist page buckets, album carousels, label grid, Home rails, Library ALL
  feed, Artist Scene); search_qt drops its private duplicate of the helper.
- AlbumCard.qml: the title is always the full string, elided at the card
  width; when Qt reports `truncated`, hovering shows the whole title in a
  Controls ToolTip (QualityBadge pattern, no character counting, no ticker).

Verified: qmllint clean, qbz-qt suite 782/782, release build.

cc
This commit is contained in:
vicrodh committed 2026-09-11 09:45:47 -06:00
1 parent cab68c9209
commit baed7bb785
7 files changed
+55 -14

No files matched your search

+43
View File
@@ -921,6 +921,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
Text {
id: titleText
width: parent.width
height: 20
text: root.title
@@ -930,6 +931,48 @@ Rectangle {
font.weight: theme.weightMedium
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
// The title is ALWAYS the full one (version included) and
// elides at the card's width; when it is cut, hovering shows
// the whole text in a small bubble above the line. `truncated`
// is Qt's own verdict on the elision, so a title that fits
// never raises a tooltip — no character counting. Qt's own
// ToolTip, like QualityBadge.qml: a recycled delegate deep
// inside a view cannot name the shell's tooltip overlay.
ToolTip {
id: titleTip
visible: titleArea.containsMouse && titleText.truncated
&& root.title !== ""
text: root.title
delay: 350
timeout: -1
padding: 0
x: 0
y: -height - 4
contentWidth: titleTipText.implicitWidth
contentHeight: titleTipText.implicitHeight
contentItem: Text {
id: titleTipText
text: titleTip.text
color: theme.textPrimary
font.pixelSize: 11
font.weight: theme.weightMedium
wrapMode: Text.Wrap
width: Math.min(implicitWidth, 300)
maximumLineCount: 3
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
leftPadding: 9
rightPadding: 9
topPadding: 5
bottomPadding: 5
}
background: Rectangle {
color: theme.surfaceElevated
radius: theme.radiusSm
border.width: 1
border.color: theme.borderSubtle
}
}
MouseArea {
id: titleArea
anchors.fill: parent
+2 -2
View File
@@ -1086,7 +1086,7 @@ fn map_release_card(release: &qbz_models::PageArtistRelease) -> AlbumCardData {
// Heart from the favourite-id cache (see `AlbumCardData::is_favorite`).
is_favorite: crate::fav_cache_qt::is_album_favorite(&release.id),
id: release.id.clone(),
title: release.title.clone(),
title: format_album_title(&release.title, release.version.as_deref()),
artist,
artist_id,
genre: release
@@ -1213,7 +1213,7 @@ pub async fn load_suggestions(
is_pinned: crate::sidebar_qt::is_pinned("album", &a.id),
is_favorite: crate::fav_cache_qt::is_album_favorite(&a.id),
id: a.id.clone(),
title: a.title.clone(),
title: format_album_title(&a.title, a.version.as_deref()),
artist,
artist_id: card_artist_id,
genre: a.genre.as_ref().map(|g| g.name.clone()).unwrap_or_default(),
+4 -1
View File
@@ -442,7 +442,10 @@ pub(crate) fn map_release(release: &PageArtistRelease) -> AlbumCardData {
// mounts the same AlbumCard as Home (see `AlbumCardData::is_favorite`).
is_favorite: crate::fav_cache_qt::is_album_favorite(&release.id),
id: release.id.clone(),
title: release.title.clone(),
// The Qobuz `version` ("50th Anniversary", "Deluxe") rides the title
// everywhere else in the app (`format_album_title`); the card is not
// the exception — a title without it points at the wrong edition.
title: crate::album_qt::format_album_title(&release.title, release.version.as_deref()),
artist,
artist_id,
genre: release
+1 -1
View File
@@ -1115,7 +1115,7 @@ fn map_album(album: &Album) -> SceneAlbum {
.unwrap_or_default();
SceneAlbum {
album_id: album.id.clone(),
title: album.title.clone(),
title: crate::album_qt::format_album_title(&album.title, album.version.as_deref()),
artist: album.artist.name.clone(),
artist_id: album.artist.id.to_string(),
// `image.small || image.large` — Tauri's exact order
+3 -3
View File
@@ -1797,7 +1797,7 @@ pub(crate) fn map_album(album: DiscoverAlbum) -> HomeCard {
is_pinned,
is_favorite,
id: album.id,
title: album.title,
title: crate::album_qt::format_album_title(&album.title, album.version.as_deref()),
artist,
artist_id,
blacklist_artist_ids,
@@ -1893,7 +1893,7 @@ fn map_slim(index: usize, album: DiscoverAlbum) -> HomeCard {
// false. A future SlimCard with a heart inherits a correct row.
is_favorite: crate::fav_cache_qt::is_album_favorite(&album.id),
id: album.id,
title: album.title,
title: crate::album_qt::format_album_title(&album.title, album.version.as_deref()),
artist: subtitle,
artist_id,
blacklist_artist_ids,
@@ -2101,7 +2101,7 @@ pub(crate) fn map_flat_album(album: Album) -> HomeCard {
// DiscoverBrowse, so one stamp covers four surfaces.
is_favorite: crate::fav_cache_qt::is_album_favorite(&album.id),
id: album.id,
title: album.title,
title: crate::album_qt::format_album_title(&album.title, album.version.as_deref()),
artist: album.artist.name,
artist_id: album.artist.id.to_string(),
blacklist_artist_ids,
+1 -1
View File
@@ -651,7 +651,7 @@ fn map_album(album: Album, ready_offline_tracks: usize) -> FeedItem {
group: "favorites".into(),
source: "qobuz".into(),
id: album.id,
title: album.title,
title: crate::album_qt::format_album_title(&album.title, album.version.as_deref()),
subtitle: artist.clone(),
artist,
artist_id: album.artist.id.to_string(),
+1 -6
View File
@@ -45,6 +45,7 @@ use std::sync::{Arc, Mutex};
use cxx_qt_lib::QString;
pub(crate) use qbz_app::settings::search_service::InteractionAction;
use crate::album_qt::format_album_title;
use qbz_app::settings::search_service::SearchService;
use qbz_app::shell::AppRuntime;
use qbz_core::LoggingAdapter;
@@ -320,12 +321,6 @@ fn year_of(date: Option<&str>) -> String {
date.and_then(|d| d.get(0..4)).unwrap_or("").to_string()
}
fn format_album_title(title: &str, version: Option<&str>) -> String {
match version.map(str::trim).filter(|v| !v.is_empty()) {
Some(v) => format!("{title} ({v})"),
None => title.to_string(),
}
}
fn map_album(album: &Album) -> CardRow {
CardRow {