feat: artist sections, fav indicator, art scaling fix, volume popup fix

- Artist profile: collapsible Albums / EPs & Singles / Other sections
  keyed on release_type; fetches up to 200 albums per artist
- Favorites: starred icon on favorited tracks, context menu shows
  Add or Remove (not both); IDs cached when fav tracks are loaded
- Shuffle button: one-time shuffle via shuffleNow() without touching
  global shuffle flag, so double-click still plays in order
- Now-playing art: replaced setFixedHeight hack with ArtWidget that
  overrides hasHeightForWidth() — scales smoothly up and down, no min-size
- Volume popup: replaced QMenu (laggy, broken drag) with Qt::Popup QFrame;
  appears below button; fixed size locked at 100% label width

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-24 17:56:47 +01:00
parent 75429faffe
commit 56473cae6f
15 changed files with 370 additions and 109 deletions

View File

@@ -3,62 +3,52 @@
#include "albumlistview.hpp"
#include <QWidget>
#include <QVBoxLayout>
#include <QLabel>
#include <QFont>
#include <QToolButton>
#include <QJsonObject>
#include <QJsonArray>
/// Artist detail page: name, biography summary, and their album list.
class ArtistView : public QWidget
class AlbumListView;
/// One collapsible section (e.g. "Albums", "EPs & Singles") inside ArtistView.
class ArtistSection : public QWidget
{
Q_OBJECT
public:
explicit ArtistView(QWidget *parent = nullptr) : QWidget(parent)
{
auto *layout = new QVBoxLayout(this);
layout->setContentsMargins(8, 8, 8, 8);
layout->setSpacing(6);
explicit ArtistSection(const QString &title, QWidget *parent = nullptr);
m_nameLabel = new QLabel(this);
QFont f = m_nameLabel->font();
f.setPointSize(f.pointSize() + 4);
f.setBold(true);
m_nameLabel->setFont(f);
m_bioLabel = new QLabel(this);
m_bioLabel->setWordWrap(true);
m_bioLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
m_bioLabel->setMaximumHeight(80);
m_albums = new AlbumListView(this);
layout->addWidget(m_nameLabel);
layout->addWidget(m_bioLabel);
layout->addWidget(m_albums, 1);
connect(m_albums, &AlbumListView::albumSelected,
this, &ArtistView::albumSelected);
}
void setArtist(const QJsonObject &artist)
{
m_nameLabel->setText(artist["name"].toString());
const QString summary = artist["biography"].toObject()["summary"].toString();
m_bioLabel->setText(summary);
m_bioLabel->setVisible(!summary.isEmpty());
const QJsonArray albums = artist["albums"].toObject()["items"].toArray();
m_albums->setAlbums(albums);
}
void setAlbums(const QJsonArray &albums);
bool isEmpty() const;
signals:
void albumSelected(const QString &albumId);
private:
QLabel *m_nameLabel = nullptr;
QLabel *m_bioLabel = nullptr;
AlbumListView *m_albums = nullptr;
QString m_baseTitle;
QToolButton *m_toggle = nullptr;
AlbumListView *m_list = nullptr;
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.
class ArtistView : public QWidget
{
Q_OBJECT
public:
explicit ArtistView(QWidget *parent = nullptr);
void setArtist(const QJsonObject &artist);
signals:
void albumSelected(const QString &albumId);
private:
QLabel *m_nameLabel = nullptr;
QLabel *m_bioLabel = nullptr;
ArtistSection *m_secAlbums = nullptr;
ArtistSection *m_secEps = nullptr;
ArtistSection *m_secOther = nullptr;
};