refactor: UI polish — lock sidebar, remove nav buttons, uniform artist tables, deep shuffle
- Lock sidebar width (setFixedWidth) so it doesn't jump between views - Remove back/forward navigation buttons and all NavPage history code - Uniform column layout on artist page: hide Artist column from both Popular Tracks and release sections, set matching fixed column widths so columns align vertically across all sections - Deep shuffle: new Rust FFI endpoint fetches tracks from all albums in parallel, combines them, and returns via EV_DEEP_SHUFFLE_OK - Auto-paginate artist releases in Rust (loop until has_more=false) so all releases load at once sorted newest-first Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
#include "artistview.hpp"
|
||||
#include "albumlistview.hpp"
|
||||
#include "../model/tracklistmodel.hpp"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QScrollArea>
|
||||
#include <QHeaderView>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
@@ -49,50 +51,18 @@ ArtistSection::ArtistSection(const QString &title, const QString &releaseType, Q
|
||||
m_list = new AlbumListView(this);
|
||||
layout->addWidget(m_list);
|
||||
|
||||
// "Load more" button (shown when has_more is true)
|
||||
m_loadMoreBtn = new QPushButton(tr("Load more…"), this);
|
||||
m_loadMoreBtn->setStyleSheet(QStringLiteral(
|
||||
"QPushButton { text-align: left; color: #FFB232; background: transparent;"
|
||||
" border: none; padding: 6px 8px; }"
|
||||
"QPushButton:hover { background: #1e1e1e; }"));
|
||||
m_loadMoreBtn->setCursor(Qt::PointingHandCursor);
|
||||
m_loadMoreBtn->setVisible(false);
|
||||
layout->addWidget(m_loadMoreBtn);
|
||||
|
||||
connect(m_toggle, &QPushButton::toggled, this, [this](bool checked) {
|
||||
m_list->setVisible(checked);
|
||||
m_loadMoreBtn->setVisible(checked && m_hasMore);
|
||||
updateToggleText();
|
||||
});
|
||||
connect(m_list, &AlbumListView::albumSelected, this, &ArtistSection::albumSelected);
|
||||
connect(m_loadMoreBtn, &QPushButton::clicked, this, [this] {
|
||||
m_loadMoreBtn->setEnabled(false);
|
||||
m_loadMoreBtn->setText(tr("Loading…"));
|
||||
emit loadMoreRequested(m_releaseType, m_loadedCount);
|
||||
});
|
||||
|
||||
updateToggleText();
|
||||
}
|
||||
|
||||
void ArtistSection::setAlbums(const QJsonArray &albums, bool hasMore)
|
||||
void ArtistSection::setAlbums(const QJsonArray &albums)
|
||||
{
|
||||
m_list->setAlbums(albums);
|
||||
m_loadedCount = albums.size();
|
||||
m_hasMore = hasMore;
|
||||
m_loadMoreBtn->setVisible(hasMore && m_toggle->isChecked());
|
||||
m_loadMoreBtn->setEnabled(true);
|
||||
m_loadMoreBtn->setText(tr("Load more…"));
|
||||
updateToggleText();
|
||||
}
|
||||
|
||||
void ArtistSection::appendAlbums(const QJsonArray &albums, bool hasMore)
|
||||
{
|
||||
m_list->addAlbums(albums);
|
||||
m_loadedCount += albums.size();
|
||||
m_hasMore = hasMore;
|
||||
m_loadMoreBtn->setVisible(hasMore && m_toggle->isChecked());
|
||||
m_loadMoreBtn->setEnabled(true);
|
||||
m_loadMoreBtn->setText(tr("Load more…"));
|
||||
updateToggleText();
|
||||
}
|
||||
|
||||
@@ -101,11 +71,28 @@ bool ArtistSection::isEmpty() const
|
||||
return m_list->topLevelItemCount() == 0;
|
||||
}
|
||||
|
||||
QStringList ArtistSection::albumIds() const
|
||||
{
|
||||
QStringList ids;
|
||||
for (int i = 0; i < m_list->topLevelItemCount(); ++i) {
|
||||
const QString id = m_list->topLevelItem(i)->data(1, Qt::UserRole).toString();
|
||||
if (!id.isEmpty())
|
||||
ids.append(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void ArtistSection::setArtistPageMode()
|
||||
{
|
||||
m_list->setArtistPageMode();
|
||||
}
|
||||
|
||||
void ArtistSection::updateToggleText()
|
||||
{
|
||||
const int count = m_list->topLevelItemCount();
|
||||
const QString arrow = m_toggle->isChecked() ? QStringLiteral("▼ ") : QStringLiteral("▶ ");
|
||||
const QString text = m_loadedCount > 0
|
||||
? QStringLiteral("%1%2 (%3)").arg(arrow, m_baseTitle).arg(m_loadedCount)
|
||||
const QString text = count > 0
|
||||
? QStringLiteral("%1%2 (%3)").arg(arrow, m_baseTitle).arg(count)
|
||||
: arrow + m_baseTitle;
|
||||
m_toggle->setText(text);
|
||||
}
|
||||
@@ -117,6 +104,7 @@ void ArtistSection::updateToggleText()
|
||||
ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_backend(backend)
|
||||
, m_queue(queue)
|
||||
{
|
||||
auto *outerLayout = new QVBoxLayout(this);
|
||||
outerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
@@ -165,7 +153,7 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
||||
QStringLiteral("QPushButton { background: #FFB232; color: #000; }"
|
||||
"QPushButton:pressed { background: #e09e28; }"));
|
||||
|
||||
m_shuffleBtn = new QPushButton(tr("⇄ Shuffle"), info);
|
||||
m_shuffleBtn = new QPushButton(tr("⇄ Shuffle All"), info);
|
||||
m_shuffleBtn->setStyleSheet(kBtnBase +
|
||||
QStringLiteral("QPushButton { background: #2a2a2a; color: #FFB232; border: 1px solid #FFB232; }"
|
||||
"QPushButton:pressed { background: #333; }"));
|
||||
@@ -222,6 +210,14 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
||||
|
||||
m_topTracks = new List::Tracks(backend, queue, m_topTracksSection);
|
||||
m_topTracks->setMaximumHeight(320);
|
||||
// Artist page column layout: hide Artist & Album, match album-section widths
|
||||
m_topTracks->setColumnHidden(TrackListModel::ColArtist, true);
|
||||
m_topTracks->setColumnHidden(TrackListModel::ColAlbum, true);
|
||||
m_topTracks->header()->setSectionResizeMode(TrackListModel::ColNumber, QHeaderView::Fixed);
|
||||
m_topTracks->header()->setSectionResizeMode(TrackListModel::ColTitle, QHeaderView::Stretch);
|
||||
m_topTracks->header()->setSectionResizeMode(TrackListModel::ColDuration, QHeaderView::Fixed);
|
||||
m_topTracks->header()->resizeSection(TrackListModel::ColNumber, 40);
|
||||
m_topTracks->header()->resizeSection(TrackListModel::ColDuration, 70);
|
||||
ttLayout->addWidget(m_topTracks);
|
||||
|
||||
connect(m_topTracksToggle, &QPushButton::toggled, m_topTracks, &QWidget::setVisible);
|
||||
@@ -236,6 +232,10 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
||||
m_secCompilations = new ArtistSection(tr("Compilations"), QStringLiteral("compilation"), content);
|
||||
m_secOther = new ArtistSection(tr("Other"), QStringLiteral("other"), content);
|
||||
|
||||
// Uniform column layout: hide Artist column, match fixed widths across all sections
|
||||
for (ArtistSection *sec : {m_secAlbums, m_secEps, m_secLive, m_secCompilations, m_secOther})
|
||||
sec->setArtistPageMode();
|
||||
|
||||
sectLayout->addWidget(m_secAlbums);
|
||||
sectLayout->addWidget(m_secEps);
|
||||
sectLayout->addWidget(m_secLive);
|
||||
@@ -246,9 +246,21 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
||||
scroll->setWidget(content);
|
||||
outerLayout->addWidget(scroll, 1);
|
||||
|
||||
// Playback connections
|
||||
connect(m_playBtn, &QPushButton::clicked, m_topTracks, [this] { m_topTracks->playAll(false); });
|
||||
connect(m_shuffleBtn, &QPushButton::clicked, m_topTracks, [this] { m_topTracks->playAll(true); });
|
||||
// Play top tracks
|
||||
connect(m_playBtn, &QPushButton::clicked, m_topTracks, [this] { m_topTracks->playAll(false); });
|
||||
|
||||
// Deep shuffle: fetch all album tracks, combine, shuffle, play
|
||||
connect(m_shuffleBtn, &QPushButton::clicked, this, [this] {
|
||||
const QStringList ids = allAlbumIds();
|
||||
if (ids.isEmpty()) {
|
||||
// Fallback: just shuffle popular tracks
|
||||
m_topTracks->playAll(true);
|
||||
return;
|
||||
}
|
||||
m_shuffleBtn->setEnabled(false);
|
||||
m_shuffleBtn->setText(tr("Loading…"));
|
||||
m_backend->getAlbumsTracks(ids);
|
||||
});
|
||||
|
||||
// Favourite button
|
||||
connect(m_favBtn, &QPushButton::clicked, this, [this] {
|
||||
@@ -270,20 +282,6 @@ ArtistView::ArtistView(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
|
||||
connect(m_secLive, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
||||
connect(m_secCompilations, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
||||
connect(m_secOther, &ArtistSection::albumSelected, this, &ArtistView::albumSelected);
|
||||
|
||||
// Load-more connections
|
||||
auto connectLoadMore = [this](ArtistSection *sec) {
|
||||
connect(sec, &ArtistSection::loadMoreRequested, this,
|
||||
[this](const QString &releaseType, int nextOffset) {
|
||||
if (m_artistId > 0)
|
||||
m_backend->getArtistReleases(m_artistId, releaseType, 50, static_cast<quint32>(nextOffset));
|
||||
});
|
||||
};
|
||||
connectLoadMore(m_secAlbums);
|
||||
connectLoadMore(m_secEps);
|
||||
connectLoadMore(m_secLive);
|
||||
connectLoadMore(m_secCompilations);
|
||||
connectLoadMore(m_secOther);
|
||||
}
|
||||
|
||||
void ArtistView::setArtist(const QJsonObject &artist)
|
||||
@@ -352,6 +350,10 @@ void ArtistView::setArtist(const QJsonObject &artist)
|
||||
: QStringLiteral("▼ Popular Tracks"));
|
||||
m_topTracksSection->setVisible(!topTracks.isEmpty());
|
||||
|
||||
// Reset shuffle button state
|
||||
m_shuffleBtn->setEnabled(true);
|
||||
m_shuffleBtn->setText(tr("⇄ Shuffle All"));
|
||||
|
||||
// Clear release sections
|
||||
for (ArtistSection *sec : {m_secAlbums, m_secEps, m_secLive, m_secCompilations, m_secOther}) {
|
||||
sec->setAlbums({});
|
||||
@@ -360,7 +362,7 @@ void ArtistView::setArtist(const QJsonObject &artist)
|
||||
}
|
||||
|
||||
void ArtistView::setReleases(const QString &releaseType, const QJsonArray &items,
|
||||
bool hasMore, int offset)
|
||||
bool /*hasMore*/, int /*offset*/)
|
||||
{
|
||||
ArtistSection *sec = nullptr;
|
||||
if (releaseType == QStringLiteral("album")) sec = m_secAlbums;
|
||||
@@ -369,22 +371,42 @@ void ArtistView::setReleases(const QString &releaseType, const QJsonArray &items
|
||||
else if (releaseType == QStringLiteral("compilation")) sec = m_secCompilations;
|
||||
else sec = m_secOther;
|
||||
|
||||
if (offset == 0)
|
||||
sec->setAlbums(items, hasMore);
|
||||
else
|
||||
sec->appendAlbums(items, hasMore);
|
||||
|
||||
// Rust auto-paginates, so we always get the full list at once
|
||||
sec->setAlbums(items);
|
||||
sec->setVisible(!sec->isEmpty());
|
||||
}
|
||||
|
||||
void ArtistView::setFavArtistIds(const QSet<qint64> &ids)
|
||||
{
|
||||
m_favArtistIds = ids;
|
||||
// Update current state if we're showing an artist
|
||||
if (m_artistId > 0)
|
||||
setFaved(ids.contains(m_artistId));
|
||||
}
|
||||
|
||||
void ArtistView::onDeepShuffleTracks(const QJsonArray &tracks)
|
||||
{
|
||||
m_shuffleBtn->setEnabled(true);
|
||||
m_shuffleBtn->setText(tr("⇄ Shuffle All"));
|
||||
|
||||
if (tracks.isEmpty()) return;
|
||||
|
||||
m_queue->setContext(tracks, 0);
|
||||
m_queue->shuffleNow();
|
||||
|
||||
const QJsonObject first = m_queue->current();
|
||||
const qint64 id = static_cast<qint64>(first["id"].toDouble());
|
||||
if (id > 0)
|
||||
emit playTrackRequested(id);
|
||||
}
|
||||
|
||||
QStringList ArtistView::allAlbumIds() const
|
||||
{
|
||||
QStringList ids;
|
||||
for (const ArtistSection *sec : {m_secAlbums, m_secEps, m_secLive, m_secCompilations, m_secOther})
|
||||
ids.append(sec->albumIds());
|
||||
return ids;
|
||||
}
|
||||
|
||||
void ArtistView::setFaved(bool faved)
|
||||
{
|
||||
m_isFaved = faved;
|
||||
|
||||
Reference in New Issue
Block a user