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:
153
src/view/artistview.cpp
Normal file
153
src/view/artistview.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#include "artistview.hpp"
|
||||
#include "albumlistview.hpp"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QScrollArea>
|
||||
#include <QFont>
|
||||
#include <QJsonValue>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ArtistSection
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
ArtistSection::ArtistSection(const QString &title, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_baseTitle(title)
|
||||
{
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
m_toggle = new QToolButton(this);
|
||||
m_toggle->setCheckable(true);
|
||||
m_toggle->setChecked(true);
|
||||
m_toggle->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
m_toggle->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
m_toggle->setStyleSheet(QStringLiteral(
|
||||
"QToolButton { text-align: left; font-weight: bold; padding: 4px 6px;"
|
||||
" border: none; border-bottom: 1px solid #333; }"
|
||||
"QToolButton:hover { background: #1e1e1e; }"
|
||||
));
|
||||
updateToggleText(0);
|
||||
layout->addWidget(m_toggle);
|
||||
|
||||
m_list = new AlbumListView(this);
|
||||
layout->addWidget(m_list);
|
||||
|
||||
connect(m_toggle, &QToolButton::toggled, m_list, &AlbumListView::setVisible);
|
||||
connect(m_list, &AlbumListView::albumSelected, this, &ArtistSection::albumSelected);
|
||||
}
|
||||
|
||||
void ArtistSection::setAlbums(const QJsonArray &albums)
|
||||
{
|
||||
m_list->setAlbums(albums);
|
||||
updateToggleText(albums.size());
|
||||
}
|
||||
|
||||
bool ArtistSection::isEmpty() const
|
||||
{
|
||||
return m_list->topLevelItemCount() == 0;
|
||||
}
|
||||
|
||||
void ArtistSection::updateToggleText(int count)
|
||||
{
|
||||
const QString arrow = m_toggle->isChecked() ? QStringLiteral("▼ ") : QStringLiteral("▶ ");
|
||||
const QString text = count > 0
|
||||
? QStringLiteral("%1%2 (%3)").arg(arrow, m_baseTitle).arg(count)
|
||||
: arrow + m_baseTitle;
|
||||
m_toggle->setText(text);
|
||||
|
||||
// Keep arrow in sync when toggled
|
||||
disconnect(m_toggle, &QToolButton::toggled, nullptr, nullptr);
|
||||
connect(m_toggle, &QToolButton::toggled, m_list, &AlbumListView::setVisible);
|
||||
connect(m_toggle, &QToolButton::toggled, this, [this, count](bool open) {
|
||||
const QString a = open ? QStringLiteral("▼ ") : QStringLiteral("▶ ");
|
||||
const QString t = count > 0
|
||||
? QStringLiteral("%1%2 (%3)").arg(a, m_baseTitle).arg(count)
|
||||
: a + m_baseTitle;
|
||||
m_toggle->setText(t);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ArtistView
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
ArtistView::ArtistView(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
auto *outerLayout = new QVBoxLayout(this);
|
||||
outerLayout->setContentsMargins(8, 8, 8, 8);
|
||||
outerLayout->setSpacing(6);
|
||||
|
||||
m_nameLabel = new QLabel(this);
|
||||
QFont f = m_nameLabel->font();
|
||||
f.setPointSize(f.pointSize() + 4);
|
||||
f.setBold(true);
|
||||
m_nameLabel->setFont(f);
|
||||
outerLayout->addWidget(m_nameLabel);
|
||||
|
||||
m_bioLabel = new QLabel(this);
|
||||
m_bioLabel->setWordWrap(true);
|
||||
m_bioLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||||
m_bioLabel->setMaximumHeight(80);
|
||||
outerLayout->addWidget(m_bioLabel);
|
||||
|
||||
// Scrollable sections area
|
||||
auto *scroll = new QScrollArea(this);
|
||||
scroll->setWidgetResizable(true);
|
||||
scroll->setFrameShape(QFrame::NoFrame);
|
||||
|
||||
auto *content = new QWidget(scroll);
|
||||
auto *sectLayout = new QVBoxLayout(content);
|
||||
sectLayout->setContentsMargins(0, 0, 0, 0);
|
||||
sectLayout->setSpacing(8);
|
||||
|
||||
m_secAlbums = new ArtistSection(tr("Albums"), content);
|
||||
m_secEps = new ArtistSection(tr("EPs & Singles"), content);
|
||||
m_secOther = new ArtistSection(tr("Other"), content);
|
||||
|
||||
sectLayout->addWidget(m_secAlbums);
|
||||
sectLayout->addWidget(m_secEps);
|
||||
sectLayout->addWidget(m_secOther);
|
||||
sectLayout->addStretch();
|
||||
|
||||
scroll->setWidget(content);
|
||||
outerLayout->addWidget(scroll, 1);
|
||||
|
||||
connect(m_secAlbums, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
||||
connect(m_secEps, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
||||
connect(m_secOther, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
||||
}
|
||||
|
||||
void ArtistView::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 allAlbums = artist["albums"].toObject()["items"].toArray();
|
||||
|
||||
QJsonArray albums, eps, other;
|
||||
for (const QJsonValue &v : allAlbums) {
|
||||
const QJsonObject a = v.toObject();
|
||||
const QString rt = a["release_type"].toString();
|
||||
if (rt == QStringLiteral("album"))
|
||||
albums.append(a);
|
||||
else if (rt == QStringLiteral("epSingle"))
|
||||
eps.append(a);
|
||||
else
|
||||
other.append(a);
|
||||
}
|
||||
|
||||
m_secAlbums->setAlbums(albums);
|
||||
m_secEps->setAlbums(eps);
|
||||
m_secOther->setAlbums(other);
|
||||
|
||||
m_secAlbums->setVisible(!m_secAlbums->isEmpty());
|
||||
m_secEps->setVisible(!m_secEps->isEmpty());
|
||||
m_secOther->setVisible(!m_secOther->isEmpty());
|
||||
}
|
||||
Reference in New Issue
Block a user