fix: lazy-load genre/playlist views and cap playlist search scrolling
Some checks failed
Build for Windows / build-windows (push) Has been cancelled

- Defer eager-load scrollbar checks to next event loop iteration via
  QTimer::singleShot(0), fixing initial load not filling the viewport
- Playlist search: eagerly fill viewport, then show "Load more playlists…"
  button instead of infinite scroll to avoid loading thousands of results
- Increase search page size from 8 to 25
- Featured/discover playlists keep auto-scroll behavior unchanged

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-31 11:17:23 +02:00
parent e9a9077ece
commit 28771e12d5
2 changed files with 63 additions and 14 deletions

View File

@@ -9,7 +9,9 @@
#include <QListWidget> #include <QListWidget>
#include <QMenu> #include <QMenu>
#include <QPushButton> #include <QPushButton>
#include <QScrollBar>
#include <QSignalBlocker> #include <QSignalBlocker>
#include <QTimer>
#include <QTreeWidgetItem> #include <QTreeWidgetItem>
#include <QVBoxLayout> #include <QVBoxLayout>
@@ -120,8 +122,18 @@ GenreBrowserView::GenreBrowserView(QobuzBackend *backend, PlayQueue *queue, QWid
m_playlistList->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); m_playlistList->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
m_playlistList->header()->setStretchLastSection(false); m_playlistList->header()->setStretchLastSection(false);
auto *playlistPage = new QWidget(this);
auto *playlistPageLayout = new QVBoxLayout(playlistPage);
playlistPageLayout->setContentsMargins(0, 0, 0, 0);
playlistPageLayout->setSpacing(0);
playlistPageLayout->addWidget(m_playlistList, 1);
m_loadMorePlaylistsBtn = new QPushButton(tr("Load more playlists…"), this);
m_loadMorePlaylistsBtn->hide();
playlistPageLayout->addWidget(m_loadMorePlaylistsBtn);
m_resultsStack->addWidget(m_albumList); m_resultsStack->addWidget(m_albumList);
m_resultsStack->addWidget(m_playlistList); m_resultsStack->addWidget(playlistPage);
layout->addWidget(m_resultsStack, 1); layout->addWidget(m_resultsStack, 1);
connect(m_backend, &QobuzBackend::genresLoaded, connect(m_backend, &QobuzBackend::genresLoaded,
@@ -180,6 +192,12 @@ GenreBrowserView::GenreBrowserView(QobuzBackend *backend, PlayQueue *queue, QWid
this, &GenreBrowserView::onAlbumScroll); this, &GenreBrowserView::onAlbumScroll);
connect(m_playlistList->verticalScrollBar(), &QScrollBar::valueChanged, connect(m_playlistList->verticalScrollBar(), &QScrollBar::valueChanged,
this, &GenreBrowserView::onPlaylistScroll); this, &GenreBrowserView::onPlaylistScroll);
connect(m_loadMorePlaylistsBtn, &QPushButton::clicked, this, [this] {
m_loadMorePlaylistsBtn->hide();
requestPlaylistsPage(m_lastPlaylistGenreIds, m_lastPlaylistType,
m_lastPlaylistTags, m_lastPlaylistQuery,
m_playlistOffset, true);
});
m_kindCombo->setCurrentIndex(0); m_kindCombo->setCurrentIndex(0);
refreshModeUi(); refreshModeUi();
@@ -391,9 +409,12 @@ void GenreBrowserView::onFeaturedAlbumsLoaded(const QJsonObject &result)
} }
// If the viewport is not scrollable yet, eagerly fetch more pages. // If the viewport is not scrollable yet, eagerly fetch more pages.
// Deferred: the scrollbar maximum isn't updated until after layout runs.
QTimer::singleShot(0, this, [this] {
QScrollBar *bar = m_albumList->verticalScrollBar(); QScrollBar *bar = m_albumList->verticalScrollBar();
if (bar && bar->maximum() == 0 && m_albumOffset < m_albumTotal) if (bar && bar->maximum() == 0 && m_albumOffset < m_albumTotal)
requestAlbumsPage(m_lastAlbumGenreIds, m_lastAlbumType, m_albumOffset, true); requestAlbumsPage(m_lastAlbumGenreIds, m_lastAlbumType, m_albumOffset, true);
});
} }
void GenreBrowserView::onFeaturedPlaylistsLoaded(const QJsonObject &result) void GenreBrowserView::onFeaturedPlaylistsLoaded(const QJsonObject &result)
@@ -413,9 +434,11 @@ void GenreBrowserView::onFeaturedPlaylistsLoaded(const QJsonObject &result)
m_playlistTotal = m_playlistOffset; m_playlistTotal = m_playlistOffset;
m_loadingPlaylists = false; m_loadingPlaylists = false;
QTimer::singleShot(0, this, [this] {
QScrollBar *bar = m_playlistList->verticalScrollBar(); QScrollBar *bar = m_playlistList->verticalScrollBar();
if (bar && bar->maximum() == 0 && m_playlistOffset < m_playlistTotal) if (bar && bar->maximum() == 0 && m_playlistOffset < m_playlistTotal)
requestPlaylistsPage(m_lastPlaylistGenreIds, m_lastPlaylistType, m_lastPlaylistTags, m_lastPlaylistQuery, m_playlistOffset, true); requestPlaylistsPage(m_lastPlaylistGenreIds, m_lastPlaylistType, m_lastPlaylistTags, m_lastPlaylistQuery, m_playlistOffset, true);
});
} }
void GenreBrowserView::onDiscoverPlaylistsLoaded(const QJsonObject &result) void GenreBrowserView::onDiscoverPlaylistsLoaded(const QJsonObject &result)
@@ -435,9 +458,11 @@ void GenreBrowserView::onDiscoverPlaylistsLoaded(const QJsonObject &result)
m_playlistTotal = m_playlistOffset; m_playlistTotal = m_playlistOffset;
m_loadingPlaylists = false; m_loadingPlaylists = false;
QTimer::singleShot(0, this, [this] {
QScrollBar *bar = m_playlistList->verticalScrollBar(); QScrollBar *bar = m_playlistList->verticalScrollBar();
if (bar && bar->maximum() == 0 && m_playlistOffset < m_playlistTotal) if (bar && bar->maximum() == 0 && m_playlistOffset < m_playlistTotal)
requestPlaylistsPage(m_lastPlaylistGenreIds, m_lastPlaylistType, m_lastPlaylistTags, m_lastPlaylistQuery, m_playlistOffset, true); requestPlaylistsPage(m_lastPlaylistGenreIds, m_lastPlaylistType, m_lastPlaylistTags, m_lastPlaylistQuery, m_playlistOffset, true);
});
} }
void GenreBrowserView::onPlaylistSearchLoaded(const QJsonObject &result) void GenreBrowserView::onPlaylistSearchLoaded(const QJsonObject &result)
@@ -456,9 +481,25 @@ void GenreBrowserView::onPlaylistSearchLoaded(const QJsonObject &result)
m_playlistTotal = m_playlistOffset; m_playlistTotal = m_playlistOffset;
m_loadingPlaylists = false; m_loadingPlaylists = false;
// Eagerly fill the viewport, then switch to a manual "Load more" button.
if (m_playlistOffset >= m_playlistTotal) {
m_loadMorePlaylistsBtn->hide();
m_searchViewportFilled = true;
} else if (!m_searchViewportFilled) {
QTimer::singleShot(0, this, [this] {
QScrollBar *bar = m_playlistList->verticalScrollBar(); QScrollBar *bar = m_playlistList->verticalScrollBar();
if (bar && bar->maximum() == 0 && m_playlistOffset < m_playlistTotal) if (bar && bar->maximum() == 0 && m_playlistOffset < m_playlistTotal) {
requestPlaylistsPage(m_lastPlaylistGenreIds, m_lastPlaylistType, m_lastPlaylistTags, m_lastPlaylistQuery, m_playlistOffset, true); requestPlaylistsPage(m_lastPlaylistGenreIds, m_lastPlaylistType,
m_lastPlaylistTags, m_lastPlaylistQuery,
m_playlistOffset, true);
} else {
m_searchViewportFilled = true;
m_loadMorePlaylistsBtn->setVisible(m_playlistOffset < m_playlistTotal);
}
});
} else {
m_loadMorePlaylistsBtn->setVisible(true);
}
} }
void GenreBrowserView::onSelectionChanged() void GenreBrowserView::onSelectionChanged()
@@ -571,6 +612,9 @@ void GenreBrowserView::requestPlaylistsPage(const QString &genreIds, const QStri
m_loadingPlaylists = false; m_loadingPlaylists = false;
m_playlistOffset = 0; m_playlistOffset = 0;
m_playlistTotal = 0; m_playlistTotal = 0;
m_loadMorePlaylistsBtn->hide();
if (type == QStringLiteral("search"))
m_searchViewportFilled = false;
} }
m_lastPlaylistGenreIds = genreIds; m_lastPlaylistGenreIds = genreIds;
@@ -580,7 +624,7 @@ void GenreBrowserView::requestPlaylistsPage(const QString &genreIds, const QStri
m_loadingPlaylists = true; m_loadingPlaylists = true;
if (type == QStringLiteral("search")) { if (type == QStringLiteral("search")) {
m_backend->searchPlaylists(query, 8, static_cast<quint32>(offset)); m_backend->searchPlaylists(query, 25, static_cast<quint32>(offset));
} else if (type.startsWith(QStringLiteral("discover-"))) { } else if (type.startsWith(QStringLiteral("discover-"))) {
m_backend->discoverPlaylists(genreIds, tags, 25, static_cast<quint32>(offset)); m_backend->discoverPlaylists(genreIds, tags, 25, static_cast<quint32>(offset));
} else { } else {
@@ -608,6 +652,9 @@ void GenreBrowserView::onAlbumScroll(int value)
void GenreBrowserView::onPlaylistScroll(int value) void GenreBrowserView::onPlaylistScroll(int value)
{ {
// Search results use a manual "Load more" button instead of infinite scroll.
if (m_lastPlaylistType == QStringLiteral("search"))
return;
if (m_loadingPlaylists) if (m_loadingPlaylists)
return; return;
if (m_playlistOffset >= m_playlistTotal) if (m_playlistOffset >= m_playlistTotal)

View File

@@ -70,6 +70,8 @@ private:
QStackedWidget *m_resultsStack = nullptr; QStackedWidget *m_resultsStack = nullptr;
AlbumListView *m_albumList = nullptr; AlbumListView *m_albumList = nullptr;
QTreeWidget *m_playlistList = nullptr; QTreeWidget *m_playlistList = nullptr;
QPushButton *m_loadMorePlaylistsBtn = nullptr;
bool m_searchViewportFilled = false;
BrowseMode m_mode = BrowseMode::Genres; BrowseMode m_mode = BrowseMode::Genres;
bool m_genresLoaded = false; bool m_genresLoaded = false;
int m_lastGenreComboIndex = 0; int m_lastGenreComboIndex = 0;