feat: playlist art fix + Play/Shuffle buttons in context header

- Fix playlist art: API returns images/images150/images300 arrays, not
  image_rectangle; update Rust model and Qt header to use images300 →
  images150 → images with fallback
- Add Play (▶) and Shuffle (⇄) buttons to the album/playlist header;
  Play starts from track 1, Shuffle enables shuffle mode and plays from
  a randomised position

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-24 17:20:00 +01:00
parent 9327147021
commit 75429faffe
5 changed files with 77 additions and 13 deletions

View File

@@ -6,6 +6,7 @@
#include <QLabel>
#include <QFont>
#include <QPixmap>
#include <QPushButton>
#include <QStringList>
#include <QJsonObject>
#include <QJsonArray>
@@ -15,14 +16,14 @@
#include <QUrl>
/// Header strip shown above the track list when an album or playlist is open.
/// Displays album art, title, subtitle, and metadata.
/// Displays album art, title, subtitle, metadata, and Play/Shuffle buttons.
class TrackContextHeader : public QWidget
{
public:
explicit TrackContextHeader(QWidget *parent = nullptr)
: QWidget(parent)
{
setFixedHeight(140);
setFixedHeight(148);
auto *hlay = new QHBoxLayout(this);
hlay->setContentsMargins(12, 8, 12, 8);
@@ -61,7 +62,35 @@ public:
m_meta->setPalette(mp);
vlay->addWidget(m_meta);
vlay->addStretch();
// Play / Shuffle buttons
auto *btnRow = new QHBoxLayout;
btnRow->setSpacing(8);
btnRow->setContentsMargins(0, 4, 0, 0);
static const QString btnBase = QStringLiteral(
"QPushButton {"
" padding: 5px 16px;"
" border-radius: 4px;"
" font-weight: bold;"
"}"
"QPushButton:hover { opacity: 0.85; }"
);
m_playBtn = new QPushButton(tr("▶ Play"), info);
m_playBtn->setStyleSheet(btnBase +
QStringLiteral("QPushButton { background: #FFB232; color: #000; }"
"QPushButton:pressed { background: #e09e28; }"));
btnRow->addWidget(m_playBtn);
m_shuffleBtn = new QPushButton(tr("⇄ Shuffle"), info);
m_shuffleBtn->setStyleSheet(btnBase +
QStringLiteral("QPushButton { background: #2a2a2a; color: #FFB232; border: 1px solid #FFB232; }"
"QPushButton:pressed { background: #333; }"));
btnRow->addWidget(m_shuffleBtn);
btnRow->addStretch();
vlay->addLayout(btnRow);
hlay->addWidget(info, 1);
m_nam = new QNetworkAccessManager(this);
@@ -76,6 +105,9 @@ public:
});
}
QPushButton *playButton() { return m_playBtn; }
QPushButton *shuffleButton() { return m_shuffleBtn; }
void setAlbum(const QJsonObject &album)
{
m_title->setText(album["title"].toString());
@@ -93,9 +125,14 @@ public:
m_subtitle->setText(desc.isEmpty() ? owner : desc);
m_meta->setText(buildPlaylistMeta(playlist));
const QJsonArray imgs = playlist["image_rectangle"].toArray();
if (!imgs.isEmpty())
fetchUrl(imgs.first().toString());
// Try images300 → images150 → images (API returns mosaic arrays, not image_rectangle)
const QJsonArray imgs300 = playlist["images300"].toArray();
const QJsonArray imgs150 = playlist["images150"].toArray();
const QJsonArray imgs = playlist["images"].toArray();
const QJsonArray &best = !imgs300.isEmpty() ? imgs300
: !imgs150.isEmpty() ? imgs150 : imgs;
if (!best.isEmpty())
fetchUrl(best.first().toString());
else
m_art->setPixmap(QPixmap());
@@ -140,7 +177,6 @@ private:
if (tracks > 0) parts << QStringLiteral("%1 tracks").arg(tracks);
const int dur = static_cast<int>(album["duration"].toDouble());
if (dur > 0) parts << formatDuration(dur);
// Resolution: e.g. "24-bit / 96 kHz"
const int bits = album["maximum_bit_depth"].toInt();
const double rate = album["maximum_sampling_rate"].toDouble();
if (bits > 0 && rate > 0) {
@@ -162,10 +198,12 @@ private:
return parts.join(QStringLiteral(" · "));
}
QLabel *m_art = nullptr;
QLabel *m_title = nullptr;
QLabel *m_subtitle = nullptr;
QLabel *m_meta = nullptr;
QNetworkAccessManager *m_nam = nullptr;
QLabel *m_art = nullptr;
QLabel *m_title = nullptr;
QLabel *m_subtitle = nullptr;
QLabel *m_meta = nullptr;
QPushButton *m_playBtn = nullptr;
QPushButton *m_shuffleBtn = nullptr;
QNetworkAccessManager *m_nam = nullptr;
QString m_currentArtUrl;
};