fix: artist page top tracks and scrollable biography

- top_tracks is a flat array in the API response, not {items:[...]}
- Replace bio QLabel with scrollable QTextEdit (max 110px, scrolls if longer)
- Track model: handle artist.name as {display:...} object for top_tracks format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-25 14:02:26 +01:00
parent e37de6d897
commit fb58c0ac8c
3 changed files with 25 additions and 15 deletions

View File

@@ -51,8 +51,16 @@ void TrackListModel::setTracks(const QJsonArray &tracks,
const QJsonObject performer = t["performer"].toObject(); const QJsonObject performer = t["performer"].toObject();
item.artist = performer["name"].toString(); item.artist = performer["name"].toString();
if (item.artist.isEmpty()) if (item.artist.isEmpty()) {
item.artist = t["album"].toObject()["artist"].toObject()["name"].toString(); // album.artist.name may be a plain string or {display:"..."} object
const QJsonValue n = t["album"].toObject()["artist"].toObject()["name"];
item.artist = n.isObject() ? n.toObject()["display"].toString() : n.toString();
}
if (item.artist.isEmpty()) {
// top_tracks format: artist.name.display
const QJsonValue n = t["artist"].toObject()["name"];
item.artist = n.isObject() ? n.toObject()["display"].toString() : n.toString();
}
const QJsonObject album = t["album"].toObject(); const QJsonObject album = t["album"].toObject();
item.album = album["title"].toString(); item.album = album["title"].toString();

View File

@@ -4,6 +4,7 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QScrollArea> #include <QScrollArea>
#include <QTextEdit>
#include <QFont> #include <QFont>
#include <QJsonValue> #include <QJsonValue>
#include <QRegularExpression> #include <QRegularExpression>
@@ -89,11 +90,13 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
m_nameLabel->setFont(f); m_nameLabel->setFont(f);
outerLayout->addWidget(m_nameLabel); outerLayout->addWidget(m_nameLabel);
m_bioLabel = new QLabel(this); m_bioEdit = new QTextEdit(this);
m_bioLabel->setWordWrap(true); m_bioEdit->setReadOnly(true);
m_bioLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); m_bioEdit->setFrameShape(QFrame::NoFrame);
m_bioLabel->setMaximumHeight(80); m_bioEdit->setMaximumHeight(110);
outerLayout->addWidget(m_bioLabel); m_bioEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_bioEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
outerLayout->addWidget(m_bioEdit);
// Scrollable sections area // Scrollable sections area
auto *scroll = new QScrollArea(this); auto *scroll = new QScrollArea(this);
@@ -174,7 +177,6 @@ void ArtistView::setArtist(const QJsonObject &artist)
const QString bioHtml = artist["biography"].toObject()["content"].toString(); const QString bioHtml = artist["biography"].toObject()["content"].toString();
if (!bioHtml.isEmpty()) { if (!bioHtml.isEmpty()) {
QString plain = bioHtml; QString plain = bioHtml;
// Strip HTML entities and tags to prevent rendering injected content
plain.remove(QRegularExpression(QStringLiteral("<[^>]*>"))); plain.remove(QRegularExpression(QStringLiteral("<[^>]*>")));
plain.replace(QStringLiteral("&amp;"), QStringLiteral("&")); plain.replace(QStringLiteral("&amp;"), QStringLiteral("&"));
plain.replace(QStringLiteral("&lt;"), QStringLiteral("<")); plain.replace(QStringLiteral("&lt;"), QStringLiteral("<"));
@@ -183,15 +185,14 @@ void ArtistView::setArtist(const QJsonObject &artist)
plain.replace(QStringLiteral("&#39;"), QStringLiteral("'")); plain.replace(QStringLiteral("&#39;"), QStringLiteral("'"));
plain.replace(QStringLiteral("&nbsp;"), QStringLiteral(" ")); plain.replace(QStringLiteral("&nbsp;"), QStringLiteral(" "));
plain = plain.trimmed(); plain = plain.trimmed();
m_bioLabel->setTextFormat(Qt::PlainText); m_bioEdit->setPlainText(plain);
m_bioLabel->setText(plain); m_bioEdit->setVisible(!plain.isEmpty());
m_bioLabel->setVisible(!plain.isEmpty());
} else { } else {
m_bioLabel->setVisible(false); m_bioEdit->setVisible(false);
} }
// Top tracks are in the default artist/page response under "top_tracks" // top_tracks is a flat array in the artist/page response
const QJsonArray topTracks = artist["top_tracks"].toObject()["items"].toArray(); const QJsonArray topTracks = artist["top_tracks"].toArray();
m_topTracks->loadTracks(topTracks); m_topTracks->loadTracks(topTracks);
m_topTracksSection->setVisible(!topTracks.isEmpty()); m_topTracksSection->setVisible(!topTracks.isEmpty());

View File

@@ -7,6 +7,7 @@
#include <QWidget> #include <QWidget>
#include <QLabel> #include <QLabel>
#include <QTextEdit>
#include <QToolButton> #include <QToolButton>
#include <QPushButton> #include <QPushButton>
#include <QJsonObject> #include <QJsonObject>
@@ -54,7 +55,7 @@ signals:
private: private:
QLabel *m_nameLabel = nullptr; QLabel *m_nameLabel = nullptr;
QLabel *m_bioLabel = nullptr; QTextEdit *m_bioEdit = nullptr;
// Top tracks section // Top tracks section
QWidget *m_topTracksSection = nullptr; QWidget *m_topTracksSection = nullptr;