feat: show top tracks on artist profile with play/shuffle

- Adds extra=topTracks to artist/page API request
- Embeds a List::Tracks widget at the top of ArtistView showing
  the artist's most popular tracks, with Play and Shuffle buttons
- Bubbles playTrackRequested through MainContent up to MainWindow
- Also adds the viz PCM ring buffer FFI infrastructure (for future
  spectrum widget) to the Rust backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-25 13:45:19 +01:00
parent 4ba6d00748
commit 6f11b364aa
13 changed files with 151 additions and 9 deletions

View File

@@ -75,7 +75,7 @@ void ArtistSection::updateToggleText(int count)
// ArtistView
// ---------------------------------------------------------------------------
ArtistView::ArtistView(QWidget *parent)
ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
: QWidget(parent)
{
auto *outerLayout = new QVBoxLayout(this);
@@ -105,6 +105,43 @@ ArtistView::ArtistView(QWidget *parent)
sectLayout->setContentsMargins(0, 0, 0, 0);
sectLayout->setSpacing(8);
// --- Top Tracks section ---
m_topTracksSection = new QWidget(content);
auto *ttLayout = new QVBoxLayout(m_topTracksSection);
ttLayout->setContentsMargins(0, 0, 0, 0);
ttLayout->setSpacing(0);
// Header row: label + play + shuffle
auto *ttHeader = new QWidget(m_topTracksSection);
auto *ttHeaderLayout = new QHBoxLayout(ttHeader);
ttHeaderLayout->setContentsMargins(6, 4, 6, 4);
ttHeaderLayout->setSpacing(6);
auto *ttLabel = new QLabel(tr("Popular Tracks"), ttHeader);
QFont ttFont = ttLabel->font();
ttFont.setBold(true);
ttLabel->setFont(ttFont);
ttHeaderLayout->addWidget(ttLabel, 1);
auto *playBtn = new QPushButton(tr("▶ Play"), ttHeader);
auto *shuffleBtn = new QPushButton(tr("⇀ Shuffle"), ttHeader);
ttHeaderLayout->addWidget(playBtn);
ttHeaderLayout->addWidget(shuffleBtn);
ttLayout->addWidget(ttHeader);
m_topTracks = new List::Tracks(backend, queue, m_topTracksSection);
// Limit visible height so it doesn't swamp the page
m_topTracks->setMaximumHeight(320);
ttLayout->addWidget(m_topTracks);
connect(playBtn, &QPushButton::clicked, m_topTracks, [this] { m_topTracks->playAll(false); });
connect(shuffleBtn, &QPushButton::clicked, m_topTracks, [this] { m_topTracks->playAll(true); });
connect(m_topTracks, &List::Tracks::playTrackRequested, this, &ArtistView::playTrackRequested);
sectLayout->addWidget(m_topTracksSection);
// --- Album sections ---
m_secAlbums = new ArtistSection(tr("Albums"), content);
m_secEps = new ArtistSection(tr("Singles & EPs"), content);
m_secLive = new ArtistSection(tr("Live"), content);
@@ -153,8 +190,12 @@ void ArtistView::setArtist(const QJsonObject &artist)
m_bioLabel->setVisible(false);
}
// Top tracks: artist/page?extra=topTracks returns {"topTracks": {"items": [...]}}
const QJsonArray topTracks = artist["topTracks"].toObject()["items"].toArray();
m_topTracks->loadTracks(topTracks);
m_topTracksSection->setVisible(!topTracks.isEmpty());
// releases is an array of {type, has_more, items[]}
// types we care about: "album", "epSingle", "live"
const QJsonArray releases = artist["releases"].toArray();
QJsonArray albums, eps, live, compilations;
for (const QJsonValue &rv : releases) {