feat: artist portrait, race condition fix, and uniform button styling
- Load artist portrait from images.portrait.hash via QNetworkAccessManager
- Fix race condition: fire getArtistReleases after setArtist() clears sections,
not before (from onArtistLoaded instead of onSearchArtistSelected)
- Apply uniform gold (#FFB232) play/shuffle button style matching album view
- Make biography scrollable (QTextEdit with max height + scroll on overflow)
- Extend track artist name parsing to handle top_tracks {display:...} format
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -335,6 +335,12 @@ void MainWindow::onAlbumLoaded(const QJsonObject &album)
|
||||
void MainWindow::onArtistLoaded(const QJsonObject &artist)
|
||||
{
|
||||
m_content->showArtist(artist);
|
||||
// Fire release requests only after the artist page is shown — avoids the
|
||||
// race where a fast-responding release request arrives before setArtist()
|
||||
// clears the sections, causing setArtist() to wipe out the data.
|
||||
const qint64 artistId = static_cast<qint64>(artist["id"].toDouble());
|
||||
for (const char *type : {"album", "epSingle", "live", "compilation"})
|
||||
m_backend->getArtistReleases(artistId, QString::fromLatin1(type));
|
||||
statusBar()->showMessage(
|
||||
tr("Artist: %1").arg(artist["name"].toObject()["display"].toString()), 4000);
|
||||
}
|
||||
@@ -360,9 +366,6 @@ void MainWindow::onSearchAlbumSelected(const QString &albumId)
|
||||
void MainWindow::onSearchArtistSelected(qint64 artistId)
|
||||
{
|
||||
m_backend->getArtist(artistId);
|
||||
// Fire release-type requests in parallel — each updates its section when it arrives
|
||||
for (const char *type : {"album", "epSingle", "live", "compilation"})
|
||||
m_backend->getArtistReleases(artistId, QString::fromLatin1(type));
|
||||
statusBar()->showMessage(tr("Loading artist…"));
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
#include <QHBoxLayout>
|
||||
#include <QScrollArea>
|
||||
#include <QTextEdit>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QPixmap>
|
||||
#include <QUrl>
|
||||
#include <QFont>
|
||||
#include <QJsonValue>
|
||||
#include <QRegularExpression>
|
||||
@@ -76,6 +81,10 @@ void ArtistSection::updateToggleText(int count)
|
||||
// ArtistView
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static const QString kBtnBase = QStringLiteral(
|
||||
"QPushButton { padding: 5px 16px; border-radius: 4px; font-weight: bold; }"
|
||||
);
|
||||
|
||||
ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
@@ -83,20 +92,49 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
||||
outerLayout->setContentsMargins(8, 8, 8, 8);
|
||||
outerLayout->setSpacing(6);
|
||||
|
||||
m_nameLabel = new QLabel(this);
|
||||
// --- Artist header: portrait + name + bio ---
|
||||
auto *headerRow = new QHBoxLayout;
|
||||
headerRow->setSpacing(12);
|
||||
|
||||
m_artLabel = new QLabel(this);
|
||||
m_artLabel->setFixedSize(100, 100);
|
||||
m_artLabel->setScaledContents(true);
|
||||
m_artLabel->setAlignment(Qt::AlignCenter);
|
||||
m_artLabel->setStyleSheet(QStringLiteral("background: #1a1a1a; border-radius: 50px;"));
|
||||
headerRow->addWidget(m_artLabel, 0, Qt::AlignTop);
|
||||
|
||||
auto *headerInfo = new QWidget(this);
|
||||
auto *headerInfoLayout = new QVBoxLayout(headerInfo);
|
||||
headerInfoLayout->setContentsMargins(0, 0, 0, 0);
|
||||
headerInfoLayout->setSpacing(4);
|
||||
|
||||
m_nameLabel = new QLabel(headerInfo);
|
||||
QFont f = m_nameLabel->font();
|
||||
f.setPointSize(f.pointSize() + 4);
|
||||
f.setBold(true);
|
||||
m_nameLabel->setFont(f);
|
||||
outerLayout->addWidget(m_nameLabel);
|
||||
headerInfoLayout->addWidget(m_nameLabel);
|
||||
|
||||
m_bioEdit = new QTextEdit(this);
|
||||
m_bioEdit = new QTextEdit(headerInfo);
|
||||
m_bioEdit->setReadOnly(true);
|
||||
m_bioEdit->setFrameShape(QFrame::NoFrame);
|
||||
m_bioEdit->setMaximumHeight(110);
|
||||
m_bioEdit->setMaximumHeight(80);
|
||||
m_bioEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
m_bioEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
outerLayout->addWidget(m_bioEdit);
|
||||
headerInfoLayout->addWidget(m_bioEdit);
|
||||
|
||||
headerRow->addWidget(headerInfo, 1);
|
||||
outerLayout->addLayout(headerRow);
|
||||
|
||||
m_nam = new QNetworkAccessManager(this);
|
||||
QObject::connect(m_nam, &QNetworkAccessManager::finished,
|
||||
this, [this](QNetworkReply *reply) {
|
||||
reply->deleteLater();
|
||||
if (reply->error() != QNetworkReply::NoError) return;
|
||||
QPixmap pix;
|
||||
if (pix.loadFromData(reply->readAll()))
|
||||
m_artLabel->setPixmap(pix);
|
||||
});
|
||||
|
||||
// Scrollable sections area
|
||||
auto *scroll = new QScrollArea(this);
|
||||
@@ -127,7 +165,15 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
||||
ttHeaderLayout->addWidget(ttLabel, 1);
|
||||
|
||||
auto *playBtn = new QPushButton(tr("▶ Play"), ttHeader);
|
||||
auto *shuffleBtn = new QPushButton(tr("⇀ Shuffle"), ttHeader);
|
||||
playBtn->setStyleSheet(kBtnBase +
|
||||
QStringLiteral("QPushButton { background: #FFB232; color: #000; }"
|
||||
"QPushButton:pressed { background: #e09e28; }"));
|
||||
|
||||
auto *shuffleBtn = new QPushButton(tr("⇄ Shuffle"), ttHeader);
|
||||
shuffleBtn->setStyleSheet(kBtnBase +
|
||||
QStringLiteral("QPushButton { background: #2a2a2a; color: #FFB232; border: 1px solid #FFB232; }"
|
||||
"QPushButton:pressed { background: #333; }"));
|
||||
|
||||
ttHeaderLayout->addWidget(playBtn);
|
||||
ttHeaderLayout->addWidget(shuffleBtn);
|
||||
|
||||
@@ -191,6 +237,21 @@ void ArtistView::setArtist(const QJsonObject &artist)
|
||||
m_bioEdit->setVisible(false);
|
||||
}
|
||||
|
||||
// Artist portrait: images.portrait.hash → CDN URL
|
||||
const QString hash = artist["images"].toObject()["portrait"].toObject()["hash"].toString();
|
||||
if (!hash.isEmpty() && hash.length() >= 4) {
|
||||
const QString p1 = hash.right(2);
|
||||
const QString p2 = hash.mid(hash.length() - 4, 2);
|
||||
const QString url = QStringLiteral("https://static.qobuz.com/images/artists/%1/%2/%3_600.jpg")
|
||||
.arg(p1, p2, hash);
|
||||
if (url != m_currentArtUrl) {
|
||||
m_currentArtUrl = url;
|
||||
m_nam->get(QNetworkRequest(QUrl(url)));
|
||||
}
|
||||
} else {
|
||||
m_artLabel->setPixmap(QPixmap());
|
||||
}
|
||||
|
||||
// top_tracks is a flat array in the artist/page response
|
||||
const QJsonArray topTracks = artist["top_tracks"].toArray();
|
||||
m_topTracks->loadTracks(topTracks);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <QTextEdit>
|
||||
#include <QToolButton>
|
||||
#include <QPushButton>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
@@ -54,8 +55,11 @@ signals:
|
||||
void playTrackRequested(qint64 trackId);
|
||||
|
||||
private:
|
||||
QLabel *m_artLabel = nullptr;
|
||||
QLabel *m_nameLabel = nullptr;
|
||||
QTextEdit *m_bioEdit = nullptr;
|
||||
QNetworkAccessManager *m_nam = nullptr;
|
||||
QString m_currentArtUrl;
|
||||
|
||||
// Top tracks section
|
||||
QWidget *m_topTracksSection = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user