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:
@@ -186,6 +186,21 @@ quint64 QobuzBackend::duration() const { return qobuz_backend_get_duration(m_bac
|
||||
int QobuzBackend::volume() const { return qobuz_backend_get_volume(m_backend); }
|
||||
int QobuzBackend::state() const { return qobuz_backend_get_state(m_backend); }
|
||||
|
||||
quint32 QobuzBackend::vizRead(float *buf, quint32 maxSamples)
|
||||
{
|
||||
return qobuz_backend_viz_read(m_backend, buf, maxSamples);
|
||||
}
|
||||
|
||||
quint32 QobuzBackend::vizSampleRate() const
|
||||
{
|
||||
return qobuz_backend_viz_sample_rate(m_backend);
|
||||
}
|
||||
|
||||
quint32 QobuzBackend::vizChannels() const
|
||||
{
|
||||
return qobuz_backend_viz_channels(m_backend);
|
||||
}
|
||||
|
||||
// ---- private slots ----
|
||||
|
||||
void QobuzBackend::onPositionTick()
|
||||
|
||||
@@ -68,6 +68,11 @@ public:
|
||||
/// 1 = playing, 2 = paused, 0 = idle
|
||||
int state() const;
|
||||
|
||||
// --- visualizer PCM ---
|
||||
quint32 vizRead(float *buf, quint32 maxSamples);
|
||||
quint32 vizSampleRate() const;
|
||||
quint32 vizChannels() const;
|
||||
|
||||
signals:
|
||||
// auth
|
||||
void loginSuccess(const QString &token, const QJsonObject &user);
|
||||
|
||||
@@ -155,6 +155,8 @@ MainWindow::MainWindow(QobuzBackend *backend, QWidget *parent)
|
||||
this, &MainWindow::onSearchAlbumSelected);
|
||||
connect(m_content, &MainContent::artistRequested,
|
||||
this, &MainWindow::onSearchArtistSelected);
|
||||
connect(m_content, &MainContent::playTrackRequested,
|
||||
this, &MainWindow::onPlayTrackRequested);
|
||||
|
||||
// ---- Queue panel ----
|
||||
connect(m_queuePanel, &QueuePanel::skipToTrackRequested,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "albumlistview.hpp"
|
||||
#include "../list/tracks.hpp"
|
||||
#include "../backend/qobuzbackend.hpp"
|
||||
#include "../playqueue.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QLabel>
|
||||
#include <QToolButton>
|
||||
#include <QPushButton>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
@@ -31,23 +35,29 @@ private:
|
||||
void updateToggleText(int count);
|
||||
};
|
||||
|
||||
/// Artist detail page: name, biography, and albums split into collapsible sections
|
||||
/// (Albums / EPs & Singles / Other) keyed on the release_type field.
|
||||
/// Artist detail page: name, biography, top tracks, and albums split into
|
||||
/// collapsible sections (Albums / EPs & Singles / Live / Compilations).
|
||||
class ArtistView : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ArtistView(QWidget *parent = nullptr);
|
||||
explicit ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent = nullptr);
|
||||
|
||||
void setArtist(const QJsonObject &artist);
|
||||
|
||||
signals:
|
||||
void albumSelected(const QString &albumId);
|
||||
void playTrackRequested(qint64 trackId);
|
||||
|
||||
private:
|
||||
QLabel *m_nameLabel = nullptr;
|
||||
QLabel *m_bioLabel = nullptr;
|
||||
|
||||
// Top tracks section
|
||||
QWidget *m_topTracksSection = nullptr;
|
||||
List::Tracks *m_topTracks = nullptr;
|
||||
|
||||
ArtistSection *m_secAlbums = nullptr;
|
||||
ArtistSection *m_secEps = nullptr;
|
||||
ArtistSection *m_secLive = nullptr;
|
||||
|
||||
@@ -44,7 +44,7 @@ MainContent::MainContent(QobuzBackend *backend, PlayQueue *queue, QWidget *paren
|
||||
|
||||
m_albumList = new AlbumListView(this);
|
||||
m_artistList = new ArtistListView(this);
|
||||
m_artistView = new ArtistView(this);
|
||||
m_artistView = new ArtistView(backend, queue, this);
|
||||
|
||||
m_stack->addWidget(m_welcome); // 0
|
||||
m_stack->addWidget(tracksPage); // 1
|
||||
@@ -57,6 +57,7 @@ MainContent::MainContent(QobuzBackend *backend, PlayQueue *queue, QWidget *paren
|
||||
connect(m_albumList, &AlbumListView::albumSelected, this, &MainContent::albumRequested);
|
||||
connect(m_artistList, &ArtistListView::artistSelected, this, &MainContent::artistRequested);
|
||||
connect(m_artistView, &ArtistView::albumSelected, this, &MainContent::albumRequested);
|
||||
connect(m_artistView, &ArtistView::playTrackRequested, this, &MainContent::playTrackRequested);
|
||||
}
|
||||
|
||||
void MainContent::showWelcome() { m_stack->setCurrentIndex(0); }
|
||||
|
||||
@@ -35,6 +35,7 @@ public:
|
||||
signals:
|
||||
void albumRequested(const QString &albumId);
|
||||
void artistRequested(qint64 artistId);
|
||||
void playTrackRequested(qint64 trackId);
|
||||
|
||||
private:
|
||||
QobuzBackend *m_backend = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user