diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 39a2676..6209901 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -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(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…")); } diff --git a/src/view/artistview.cpp b/src/view/artistview.cpp index 6c3dae5..bfa77f5 100644 --- a/src/view/artistview.cpp +++ b/src/view/artistview.cpp @@ -5,6 +5,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -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); @@ -126,8 +164,16 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent) ttLabel->setFont(ttFont); ttHeaderLayout->addWidget(ttLabel, 1); - auto *playBtn = new QPushButton(tr("▶ Play"), ttHeader); - auto *shuffleBtn = new QPushButton(tr("⇀ Shuffle"), ttHeader); + auto *playBtn = new QPushButton(tr("▶ Play"), 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); diff --git a/src/view/artistview.hpp b/src/view/artistview.hpp index 46e925b..205c404 100644 --- a/src/view/artistview.hpp +++ b/src/view/artistview.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -54,8 +55,11 @@ signals: void playTrackRequested(qint64 trackId); private: - QLabel *m_nameLabel = nullptr; - QTextEdit *m_bioEdit = nullptr; + 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;