feat: add album favorites in header and unify browse controls
Some checks failed
Build for Windows / build-windows (push) Has been cancelled
Some checks failed
Build for Windows / build-windows (push) Has been cancelled
Improve album workflow with in-header favorite toggle, keep favorite state synced from backend, and normalize browse top-bar sizing while increasing audio output buffer headroom to reduce glitches.
This commit is contained in:
@@ -205,6 +205,7 @@ MainWindow::MainWindow(QobuzBackend *backend, QWidget *parent)
|
||||
#endif
|
||||
|
||||
connect(m_library, &List::Library::favAlbumsRequested, this, [this] {
|
||||
m_showFavAlbumsOnLoad = true;
|
||||
m_backend->getFavAlbums();
|
||||
statusBar()->showMessage(tr("Loading favorite albums…"));
|
||||
});
|
||||
@@ -259,6 +260,19 @@ MainWindow::MainWindow(QobuzBackend *backend, QWidget *parent)
|
||||
this, &MainWindow::onSearchAlbumSelected);
|
||||
connect(m_content, &MainContent::artistRequested,
|
||||
this, &MainWindow::onSearchArtistSelected);
|
||||
connect(m_content, &MainContent::albumFavoriteToggled,
|
||||
this, [this](const QString &albumId, bool favorite) {
|
||||
if (favorite) {
|
||||
m_backend->addFavAlbum(albumId);
|
||||
m_favAlbumIds.insert(albumId);
|
||||
statusBar()->showMessage(tr("Added album to favorites"), 3000);
|
||||
} else {
|
||||
m_backend->removeFavAlbum(albumId);
|
||||
m_favAlbumIds.remove(albumId);
|
||||
statusBar()->showMessage(tr("Removed album from favorites"), 3000);
|
||||
}
|
||||
m_content->setFavAlbumIds(m_favAlbumIds);
|
||||
});
|
||||
connect(m_content, &MainContent::playlistRequested,
|
||||
this, [this](qint64 playlistId) {
|
||||
m_backend->getPlaylist(playlistId);
|
||||
@@ -337,6 +351,8 @@ void MainWindow::tryRestoreSession()
|
||||
m_backend->getUser(); // userLoaded will call m_library->refresh()
|
||||
else
|
||||
m_library->refresh();
|
||||
// Preload fav albums so the album page fav button is accurate immediately.
|
||||
m_backend->getFavAlbums();
|
||||
// Preload fav artists so the artist page fav button works immediately
|
||||
m_backend->getFavArtists();
|
||||
const QString name = AppSettings::instance().displayName();
|
||||
@@ -388,6 +404,8 @@ void MainWindow::onLoginSuccess(const QString &token, const QJsonObject &user)
|
||||
statusBar()->showMessage(tr("Signed in as %1").arg(
|
||||
displayName.isEmpty() ? email : displayName));
|
||||
m_library->refresh();
|
||||
m_backend->getFavAlbums();
|
||||
m_backend->getFavArtists();
|
||||
}
|
||||
|
||||
void MainWindow::onLoginError(const QString &error)
|
||||
@@ -449,9 +467,26 @@ void MainWindow::onFavTracksLoaded(const QJsonObject &result)
|
||||
|
||||
void MainWindow::onFavAlbumsLoaded(const QJsonObject &result)
|
||||
{
|
||||
m_content->showFavAlbums(result);
|
||||
statusBar()->showMessage(
|
||||
tr("%1 favorite albums").arg(result["total"].toInt()), 4000);
|
||||
// Always cache fav album IDs (needed by the album page fav button)
|
||||
m_favAlbumIds.clear();
|
||||
const QJsonArray items = result["items"].toArray();
|
||||
for (const QJsonValue &v : items) {
|
||||
const QJsonObject album = v.toObject();
|
||||
QString id = album["id"].toString();
|
||||
if (id.isEmpty() && album["id"].isDouble())
|
||||
id = QString::number(static_cast<qint64>(album["id"].toDouble()));
|
||||
if (!id.isEmpty())
|
||||
m_favAlbumIds.insert(id);
|
||||
}
|
||||
m_content->setFavAlbumIds(m_favAlbumIds);
|
||||
|
||||
// Only navigate to the fav albums page if the user explicitly requested it
|
||||
if (m_showFavAlbumsOnLoad) {
|
||||
m_showFavAlbumsOnLoad = false;
|
||||
m_content->showFavAlbums(result);
|
||||
statusBar()->showMessage(
|
||||
tr("%1 favorite albums").arg(result["total"].toInt()), 4000);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onFavArtistsLoaded(const QJsonObject &result)
|
||||
|
||||
@@ -55,7 +55,9 @@ private:
|
||||
QobuzBackend *m_backend = nullptr;
|
||||
PlayQueue *m_queue = nullptr;
|
||||
QVector<QPair<qint64, QString>> m_userPlaylists;
|
||||
QSet<QString> m_favAlbumIds;
|
||||
QSet<qint64> m_favArtistIds;
|
||||
bool m_showFavAlbumsOnLoad = false;
|
||||
bool m_showFavArtistsOnLoad = false;
|
||||
MainToolBar *m_toolBar = nullptr;
|
||||
MainContent *m_content = nullptr;
|
||||
|
||||
@@ -29,12 +29,20 @@ GenreBrowserView::GenreBrowserView(QobuzBackend *backend, PlayQueue *queue, QWid
|
||||
topLayout->setContentsMargins(8, 6, 8, 6);
|
||||
topLayout->setSpacing(6);
|
||||
|
||||
QFont topFont = topBar->font();
|
||||
if (topFont.pointSize() > 0)
|
||||
topFont.setPointSize(topFont.pointSize() + 1);
|
||||
static constexpr int controlHeight = 30;
|
||||
|
||||
m_browseLabel = new QLabel(tr("Show:"), this);
|
||||
m_browseLabel->setFont(topFont);
|
||||
topLayout->addWidget(m_browseLabel);
|
||||
m_kindCombo = new QComboBox(this);
|
||||
m_kindCombo->addItem(tr("Albums"), QStringLiteral("albums"));
|
||||
m_kindCombo->addItem(tr("Playlists"), QStringLiteral("playlists"));
|
||||
m_kindCombo->setFont(topFont);
|
||||
m_kindCombo->setMinimumWidth(110);
|
||||
m_kindCombo->setFixedHeight(controlHeight);
|
||||
topLayout->addWidget(m_kindCombo);
|
||||
|
||||
m_gapAfterKind = new QWidget(this);
|
||||
@@ -42,9 +50,12 @@ GenreBrowserView::GenreBrowserView(QobuzBackend *backend, PlayQueue *queue, QWid
|
||||
topLayout->addWidget(m_gapAfterKind);
|
||||
|
||||
m_genreLabel = new QLabel(tr("Genre:"), this);
|
||||
m_genreLabel->setFont(topFont);
|
||||
topLayout->addWidget(m_genreLabel);
|
||||
m_genreCombo = new QComboBox(this);
|
||||
m_genreCombo->setFont(topFont);
|
||||
m_genreCombo->setMinimumWidth(180);
|
||||
m_genreCombo->setFixedHeight(controlHeight);
|
||||
topLayout->addWidget(m_genreCombo);
|
||||
|
||||
m_gapAfterGenre = new QWidget(this);
|
||||
@@ -52,29 +63,39 @@ GenreBrowserView::GenreBrowserView(QobuzBackend *backend, PlayQueue *queue, QWid
|
||||
topLayout->addWidget(m_gapAfterGenre);
|
||||
|
||||
m_typeLabel = new QLabel(tr("Type:"), this);
|
||||
m_typeLabel->setFont(topFont);
|
||||
topLayout->addWidget(m_typeLabel);
|
||||
m_typeCombo = new QComboBox(this);
|
||||
m_typeCombo->setFont(topFont);
|
||||
m_typeCombo->setMinimumWidth(180);
|
||||
m_typeCombo->setFixedHeight(controlHeight);
|
||||
topLayout->addWidget(m_typeCombo);
|
||||
|
||||
m_playlistSearchLabel = new QLabel(tr("Search:"), this);
|
||||
m_playlistSearchLabel->setFont(topFont);
|
||||
m_playlistSearchLabel->setVisible(false);
|
||||
topLayout->addWidget(m_playlistSearchLabel);
|
||||
|
||||
m_playlistSearchBox = new QLineEdit(this);
|
||||
m_playlistSearchBox->setFont(topFont);
|
||||
m_playlistSearchBox->setPlaceholderText(tr("Search playlists..."));
|
||||
m_playlistSearchBox->setClearButtonEnabled(true);
|
||||
m_playlistSearchBox->setVisible(false);
|
||||
m_playlistSearchBox->setMinimumWidth(220);
|
||||
m_playlistSearchBox->setMaximumWidth(320);
|
||||
m_playlistSearchBox->setFixedHeight(controlHeight);
|
||||
topLayout->addWidget(m_playlistSearchBox);
|
||||
|
||||
m_playlistSearchBtn = new QPushButton(tr("Go"), this);
|
||||
m_playlistSearchBtn->setFont(topFont);
|
||||
m_playlistSearchBtn->setVisible(false);
|
||||
m_playlistSearchBtn->setFixedHeight(controlHeight);
|
||||
topLayout->addWidget(m_playlistSearchBtn);
|
||||
|
||||
m_deepShuffleBtn = new QPushButton(tr("⇄ Deep Shuffle"), this);
|
||||
m_deepShuffleBtn->setFont(topFont);
|
||||
m_deepShuffleBtn->setVisible(false);
|
||||
m_deepShuffleBtn->setFixedHeight(controlHeight);
|
||||
topLayout->addWidget(m_deepShuffleBtn);
|
||||
|
||||
topLayout->addStretch();
|
||||
|
||||
@@ -48,6 +48,13 @@ MainContent::MainContent(QobuzBackend *backend, PlayQueue *queue, QWidget *paren
|
||||
return;
|
||||
emit playlistFollowToggled(id, !m_header->playlistFollowed());
|
||||
});
|
||||
QObject::connect(m_header->favButton(), &QPushButton::clicked,
|
||||
[this] {
|
||||
const QString albumId = m_header->albumId();
|
||||
if (albumId.isEmpty())
|
||||
return;
|
||||
emit albumFavoriteToggled(albumId, !m_header->albumFaved());
|
||||
});
|
||||
|
||||
m_albumList = new AlbumListView(this);
|
||||
m_artistList = new ArtistListView(this);
|
||||
@@ -77,7 +84,10 @@ void MainContent::showWelcome() { m_stack->setCurrentIndex(0); }
|
||||
|
||||
void MainContent::showAlbum(const QJsonObject &album)
|
||||
{
|
||||
m_header->setAlbum(album);
|
||||
QString albumId = album["id"].toString();
|
||||
if (albumId.isEmpty() && album["id"].isDouble())
|
||||
albumId = QString::number(static_cast<qint64>(album["id"].toDouble()));
|
||||
m_header->setAlbum(album, m_favAlbumIds.contains(albumId));
|
||||
m_tracks->loadAlbum(album);
|
||||
m_stack->setCurrentIndex(1);
|
||||
}
|
||||
@@ -131,6 +141,14 @@ void MainContent::setFavArtistIds(const QSet<qint64> &ids)
|
||||
m_artistView->setFavArtistIds(ids);
|
||||
}
|
||||
|
||||
void MainContent::setFavAlbumIds(const QSet<QString> &ids)
|
||||
{
|
||||
m_favAlbumIds = ids;
|
||||
const QString shownAlbumId = m_header->albumId();
|
||||
if (!shownAlbumId.isEmpty())
|
||||
m_header->setAlbumFaved(m_favAlbumIds.contains(shownAlbumId));
|
||||
}
|
||||
|
||||
void MainContent::onDeepShuffleTracks(const QJsonArray &tracks)
|
||||
{
|
||||
if (m_genreBrowser->tryHandleDeepShuffleTracks(tracks))
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <QStackedWidget>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QSet>
|
||||
|
||||
class MainContent : public QWidget
|
||||
{
|
||||
@@ -33,6 +34,7 @@ public:
|
||||
void showFavArtists(const QJsonObject &result);
|
||||
void showArtist(const QJsonObject &artist);
|
||||
void updateArtistReleases(const QString &releaseType, const QJsonArray &items, bool hasMore, int offset);
|
||||
void setFavAlbumIds(const QSet<QString> &ids);
|
||||
void setFavArtistIds(const QSet<qint64> &ids);
|
||||
void onDeepShuffleTracks(const QJsonArray &tracks);
|
||||
void showGenreBrowser();
|
||||
@@ -44,6 +46,7 @@ public:
|
||||
signals:
|
||||
void albumRequested(const QString &albumId);
|
||||
void artistRequested(qint64 artistId);
|
||||
void albumFavoriteToggled(const QString &albumId, bool favorite);
|
||||
void playlistRequested(qint64 playlistId);
|
||||
void playlistFollowToggled(qint64 playlistId, bool follow);
|
||||
void playTrackRequested(qint64 trackId);
|
||||
@@ -58,4 +61,5 @@ private:
|
||||
ArtistListView *m_artistList = nullptr;
|
||||
ArtistView *m_artistView = nullptr;
|
||||
GenreBrowserView *m_genreBrowser = nullptr;
|
||||
QSet<QString> m_favAlbumIds;
|
||||
};
|
||||
|
||||
@@ -93,6 +93,13 @@ public:
|
||||
"QPushButton:pressed { background: #333; }"));
|
||||
btnRow->addWidget(m_shuffleBtn);
|
||||
|
||||
m_favBtn = new QPushButton(tr("♡ Favourite"), info);
|
||||
m_favBtn->setStyleSheet(btnBase +
|
||||
QStringLiteral("QPushButton { background: #2a2a2a; color: #ccc; border: 1px solid #555; }"
|
||||
"QPushButton:pressed { background: #333; }"));
|
||||
m_favBtn->hide();
|
||||
btnRow->addWidget(m_favBtn);
|
||||
|
||||
m_followBtn = new QPushButton(tr("Follow"), info);
|
||||
m_followBtn->setStyleSheet(btnBase +
|
||||
QStringLiteral("QPushButton { background: #2a2a2a; color: #ddd; border: 1px solid #666; }"
|
||||
@@ -120,24 +127,35 @@ public:
|
||||
|
||||
QPushButton *playButton() { return m_playBtn; }
|
||||
QPushButton *shuffleButton() { return m_shuffleBtn; }
|
||||
QPushButton *favButton() { return m_favBtn; }
|
||||
QPushButton *followButton() { return m_followBtn; }
|
||||
|
||||
QPushButton *subtitleButton() { return m_subtitle; }
|
||||
QString albumId() const { return m_albumId; }
|
||||
bool albumFaved() const { return m_albumFaved; }
|
||||
qint64 artistId() const { return m_artistId; }
|
||||
qint64 playlistId() const { return m_playlistId; }
|
||||
bool playlistFollowed() const { return m_playlistFollowed; }
|
||||
bool playlistOwned() const { return m_playlistOwned; }
|
||||
|
||||
void setAlbum(const QJsonObject &album)
|
||||
void setAlbum(const QJsonObject &album, bool isFaved)
|
||||
{
|
||||
const QString base = album["title"].toString();
|
||||
const QString ver = album["version"].toString().trimmed();
|
||||
m_title->setText(ver.isEmpty() ? base : base + QStringLiteral(" (") + ver + QLatin1Char(')'));
|
||||
m_albumId = album["id"].toString();
|
||||
if (m_albumId.isEmpty() && album["id"].isDouble())
|
||||
m_albumId = QString::number(static_cast<qint64>(album["id"].toDouble()));
|
||||
m_artistId = static_cast<qint64>(album["artist"].toObject()["id"].toDouble());
|
||||
m_subtitle->setText(album["artist"].toObject()["name"].toString());
|
||||
m_subtitle->setEnabled(m_artistId > 0);
|
||||
m_subtitle->setCursor(m_artistId > 0 ? Qt::PointingHandCursor : Qt::ArrowCursor);
|
||||
m_meta->setText(buildAlbumMeta(album));
|
||||
|
||||
setAlbumFaved(isFaved);
|
||||
m_favBtn->setEnabled(!m_albumId.isEmpty());
|
||||
m_favBtn->show();
|
||||
|
||||
m_followBtn->hide();
|
||||
m_playlistId = 0;
|
||||
m_playlistFollowed = false;
|
||||
@@ -160,6 +178,10 @@ public:
|
||||
m_subtitle->setCursor(Qt::ArrowCursor);
|
||||
m_meta->setText(buildPlaylistMeta(playlist));
|
||||
|
||||
m_albumId.clear();
|
||||
m_albumFaved = false;
|
||||
m_favBtn->hide();
|
||||
|
||||
if (m_playlistOwned) {
|
||||
m_followBtn->setText(tr("Owned"));
|
||||
m_followBtn->setEnabled(false);
|
||||
@@ -191,6 +213,24 @@ public:
|
||||
m_followBtn->setText(m_playlistFollowed ? tr("Unfollow") : tr("Follow"));
|
||||
}
|
||||
|
||||
void setAlbumFaved(bool faved)
|
||||
{
|
||||
m_albumFaved = faved;
|
||||
if (faved) {
|
||||
m_favBtn->setText(tr("♥ Favourited"));
|
||||
m_favBtn->setStyleSheet(QStringLiteral(
|
||||
"QPushButton { padding: 5px 16px; border-radius: 4px; font-weight: bold;"
|
||||
" background: #2a2a2a; color: #FFB232; border: 1px solid #FFB232; }"
|
||||
"QPushButton:pressed { background: #333; }"));
|
||||
} else {
|
||||
m_favBtn->setText(tr("♡ Favourite"));
|
||||
m_favBtn->setStyleSheet(QStringLiteral(
|
||||
"QPushButton { padding: 5px 16px; border-radius: 4px; font-weight: bold;"
|
||||
" background: #2a2a2a; color: #ccc; border: 1px solid #555; }"
|
||||
"QPushButton:pressed { background: #333; }"));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void fetchArt(const QJsonObject &img)
|
||||
{
|
||||
@@ -256,9 +296,12 @@ private:
|
||||
QLabel *m_meta = nullptr;
|
||||
QPushButton *m_playBtn = nullptr;
|
||||
QPushButton *m_shuffleBtn = nullptr;
|
||||
QPushButton *m_favBtn = nullptr;
|
||||
QPushButton *m_followBtn = nullptr;
|
||||
QNetworkAccessManager *m_nam = nullptr;
|
||||
QString m_currentArtUrl;
|
||||
QString m_albumId;
|
||||
bool m_albumFaved = false;
|
||||
qint64 m_artistId = 0;
|
||||
qint64 m_playlistId = 0;
|
||||
bool m_playlistFollowed = false;
|
||||
|
||||
Reference in New Issue
Block a user