feat: restore most-popular search and top results badges
Some checks failed
Build for Windows / build-windows (push) Has been cancelled

This commit is contained in:
joren
2026-03-30 22:53:41 +02:00
parent 3346b424b3
commit 2da934f3f6
7 changed files with 134 additions and 1 deletions

View File

@@ -37,6 +37,7 @@ enum QobuzEvent {
EV_USER_OK = 23, EV_USER_OK = 23,
EV_ARTIST_RELEASES_OK = 24, EV_ARTIST_RELEASES_OK = 24,
EV_DEEP_SHUFFLE_OK = 25, EV_DEEP_SHUFFLE_OK = 25,
EV_MOST_POPULAR_OK = 26,
EV_GENRES_OK = 27, EV_GENRES_OK = 27,
EV_FEATURED_ALBUMS_OK = 28, EV_FEATURED_ALBUMS_OK = 28,
}; };
@@ -55,6 +56,7 @@ void qobuz_backend_get_user(QobuzBackendOpaque *backend);
// Catalog // Catalog
void qobuz_backend_search(QobuzBackendOpaque *backend, const char *query, uint32_t offset, uint32_t limit); void qobuz_backend_search(QobuzBackendOpaque *backend, const char *query, uint32_t offset, uint32_t limit);
void qobuz_backend_most_popular_search(QobuzBackendOpaque *backend, const char *query, uint32_t limit);
void qobuz_backend_get_album(QobuzBackendOpaque *backend, const char *album_id); void qobuz_backend_get_album(QobuzBackendOpaque *backend, const char *album_id);
void qobuz_backend_get_artist(QobuzBackendOpaque *backend, int64_t artist_id); void qobuz_backend_get_artist(QobuzBackendOpaque *backend, int64_t artist_id);
void qobuz_backend_get_playlist(QobuzBackendOpaque *backend, int64_t playlist_id, uint32_t offset, uint32_t limit); void qobuz_backend_get_playlist(QobuzBackendOpaque *backend, int64_t playlist_id, uint32_t offset, uint32_t limit);

View File

@@ -453,6 +453,24 @@ impl QobuzClient {
}) })
} }
pub async fn get_most_popular(
&self,
query: &str,
offset: u32,
limit: u32,
) -> Result<Value> {
let resp = self
.get_request("most-popular/get")
.query(&[
("query", query.to_string()),
("offset", offset.to_string()),
("limit", limit.to_string()),
])
.send()
.await?;
Self::check_response(resp).await
}
async fn search_tracks( async fn search_tracks(
&self, &self,
query: &str, query: &str,

View File

@@ -45,6 +45,7 @@ pub const EV_TRACK_URL_ERR: c_int = 18;
pub const EV_GENERIC_ERR: c_int = 19; pub const EV_GENERIC_ERR: c_int = 19;
pub const EV_ARTIST_RELEASES_OK: c_int = 24; pub const EV_ARTIST_RELEASES_OK: c_int = 24;
pub const EV_DEEP_SHUFFLE_OK: c_int = 25; pub const EV_DEEP_SHUFFLE_OK: c_int = 25;
pub const EV_MOST_POPULAR_OK: c_int = 26;
pub const EV_GENRES_OK: c_int = 27; pub const EV_GENRES_OK: c_int = 27;
pub const EV_FEATURED_ALBUMS_OK: c_int = 28; pub const EV_FEATURED_ALBUMS_OK: c_int = 28;
@@ -203,6 +204,36 @@ pub unsafe extern "C" fn qobuz_backend_search(
}); });
} }
#[no_mangle]
pub unsafe extern "C" fn qobuz_backend_most_popular_search(
ptr: *mut Backend,
query: *const c_char,
limit: u32,
) {
let inner = &(*ptr).0;
let query = CStr::from_ptr(query).to_string_lossy().into_owned();
let client = inner.client.clone();
let cb = inner.cb;
let ud = inner.ud;
spawn(inner, async move {
let result = client
.lock()
.await
.get_most_popular(&query, 0, limit)
.await;
match result {
Ok(r) => call_cb(
cb,
ud,
EV_MOST_POPULAR_OK,
&serde_json::to_string(&r).unwrap_or_default(),
),
Err(e) => call_cb(cb, ud, EV_SEARCH_ERR, &err_json(&e.to_string())),
}
});
}
// ---------- Album ---------- // ---------- Album ----------
#[no_mangle] #[no_mangle]

View File

@@ -51,6 +51,11 @@ void QobuzBackend::search(const QString &query, quint32 offset, quint32 limit)
qobuz_backend_search(m_backend, query.toUtf8().constData(), offset, limit); qobuz_backend_search(m_backend, query.toUtf8().constData(), offset, limit);
} }
void QobuzBackend::mostPopularSearch(const QString &query, quint32 limit)
{
qobuz_backend_most_popular_search(m_backend, query.toUtf8().constData(), limit);
}
void QobuzBackend::getAlbum(const QString &albumId) void QobuzBackend::getAlbum(const QString &albumId)
{ {
qobuz_backend_get_album(m_backend, albumId.toUtf8().constData()); qobuz_backend_get_album(m_backend, albumId.toUtf8().constData());
@@ -251,6 +256,9 @@ void QobuzBackend::onEvent(int eventType, const QString &json)
case EV_SEARCH_OK: case EV_SEARCH_OK:
emit searchResult(obj); emit searchResult(obj);
break; break;
case 26: // EV_MOST_POPULAR_OK
emit mostPopularResult(obj);
break;
case EV_SEARCH_ERR: case EV_SEARCH_ERR:
emit error(obj["error"].toString()); emit error(obj["error"].toString());
break; break;

View File

@@ -28,6 +28,7 @@ public:
// --- catalog --- // --- catalog ---
void search(const QString &query, quint32 offset = 0, quint32 limit = 20); void search(const QString &query, quint32 offset = 0, quint32 limit = 20);
void mostPopularSearch(const QString &query, quint32 limit = 8);
void getAlbum(const QString &albumId); void getAlbum(const QString &albumId);
void getArtist(qint64 artistId); void getArtist(qint64 artistId);
void getArtistReleases(qint64 artistId, const QString &releaseType, quint32 limit = 50, quint32 offset = 0); void getArtistReleases(qint64 artistId, const QString &releaseType, quint32 limit = 50, quint32 offset = 0);
@@ -83,6 +84,7 @@ signals:
// catalog // catalog
void searchResult(const QJsonObject &result); void searchResult(const QJsonObject &result);
void mostPopularResult(const QJsonObject &result);
void albumLoaded(const QJsonObject &album); void albumLoaded(const QJsonObject &album);
void artistLoaded(const QJsonObject &artist); void artistLoaded(const QJsonObject &artist);
void artistReleasesLoaded(const QString &releaseType, const QJsonArray &items, bool hasMore, int offset); void artistReleasesLoaded(const QString &releaseType, const QJsonArray &items, bool hasMore, int offset);

View File

@@ -39,6 +39,14 @@ SearchTab::SearchTab(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
// Result tabs // Result tabs
m_resultTabs = new QTabWidget(this); m_resultTabs = new QTabWidget(this);
m_topResults = new QTreeWidget(this);
m_topResults->setHeaderLabels({tr(""), tr("Top Result"), tr("Info")});
m_topResults->setRootIsDecorated(false);
m_topResults->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
m_topResults->header()->setSectionResizeMode(1, QHeaderView::Stretch);
m_topResults->header()->setSectionResizeMode(2, QHeaderView::Stretch);
m_topResults->header()->setStretchLastSection(false);
m_trackResults = new QTreeWidget(this); m_trackResults = new QTreeWidget(this);
m_trackResults->setHeaderLabels({tr("Title"), tr("Artist"), tr("Album")}); m_trackResults->setHeaderLabels({tr("Title"), tr("Artist"), tr("Album")});
m_trackResults->setRootIsDecorated(false); m_trackResults->setRootIsDecorated(false);
@@ -57,6 +65,7 @@ SearchTab::SearchTab(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
m_artistResults->setHeaderLabels({tr("Artist")}); m_artistResults->setHeaderLabels({tr("Artist")});
m_artistResults->setRootIsDecorated(false); m_artistResults->setRootIsDecorated(false);
m_resultTabs->addTab(m_topResults, tr("Top Results"));
m_resultTabs->addTab(m_trackResults, tr("Tracks")); m_resultTabs->addTab(m_trackResults, tr("Tracks"));
m_resultTabs->addTab(m_albumResults, tr("Albums")); m_resultTabs->addTab(m_albumResults, tr("Albums"));
m_resultTabs->addTab(m_artistResults, tr("Artists")); m_resultTabs->addTab(m_artistResults, tr("Artists"));
@@ -66,7 +75,9 @@ SearchTab::SearchTab(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
connect(m_searchBox, &QLineEdit::returnPressed, this, &SearchTab::onSearchSubmit); connect(m_searchBox, &QLineEdit::returnPressed, this, &SearchTab::onSearchSubmit);
connect(m_backend, &QobuzBackend::searchResult, this, &SearchTab::onSearchResult); connect(m_backend, &QobuzBackend::searchResult, this, &SearchTab::onSearchResult);
connect(m_backend, &QobuzBackend::mostPopularResult, this, &SearchTab::onMostPopularResult);
connect(m_topResults, &QTreeWidget::itemDoubleClicked, this, &SearchTab::onItemDoubleClicked);
connect(m_trackResults, &QTreeWidget::itemDoubleClicked, this, &SearchTab::onItemDoubleClicked); connect(m_trackResults, &QTreeWidget::itemDoubleClicked, this, &SearchTab::onItemDoubleClicked);
connect(m_albumResults, &QTreeWidget::itemDoubleClicked, this, &SearchTab::onItemDoubleClicked); connect(m_albumResults, &QTreeWidget::itemDoubleClicked, this, &SearchTab::onItemDoubleClicked);
connect(m_artistResults, &QTreeWidget::itemDoubleClicked, this, &SearchTab::onItemDoubleClicked); connect(m_artistResults, &QTreeWidget::itemDoubleClicked, this, &SearchTab::onItemDoubleClicked);
@@ -86,8 +97,67 @@ void SearchTab::setUserPlaylists(const QVector<QPair<qint64, QString>> &playlist
void SearchTab::onSearchSubmit() void SearchTab::onSearchSubmit()
{ {
const QString q = m_searchBox->text().trimmed(); const QString q = m_searchBox->text().trimmed();
if (!q.isEmpty()) if (!q.isEmpty()) {
m_backend->mostPopularSearch(q, 8);
m_backend->search(q, 0, 20); m_backend->search(q, 0, 20);
m_resultTabs->setCurrentIndex(0);
}
}
void SearchTab::onMostPopularResult(const QJsonObject &result)
{
m_topResults->clear();
QFont badgeFont;
badgeFont.setBold(true);
const QJsonArray items = result["most_popular"].toObject()["items"].toArray();
for (const auto &value : items) {
const QJsonObject itemObj = value.toObject();
const QString type = itemObj["type"].toString();
const QJsonObject content = itemObj["content"].toObject();
auto *item = new QTreeWidgetItem(m_topResults);
item->setData(0, JsonRole, content);
if (type == QStringLiteral("tracks")) {
const QString title = content["title"].toString();
const QString artist = content["performer"].toObject()["name"].toString();
const QString album = content["album"].toObject()["title"].toString();
item->setText(0, QStringLiteral("T"));
item->setForeground(0, QColor(QStringLiteral("#2FA84F")));
item->setFont(0, badgeFont);
item->setTextAlignment(0, Qt::AlignCenter);
item->setText(1, title);
item->setText(2, artist.isEmpty() ? album : QStringLiteral("%1 - %2").arg(artist, album));
item->setData(0, TypeRole, QStringLiteral("track"));
item->setData(0, IdRole, static_cast<qint64>(content["id"].toDouble()));
} else if (type == QStringLiteral("albums")) {
const QString title = content["title"].toString();
const QString artist = content["artist"].toObject()["name"].toString();
const bool hiRes = content["hires_streamable"].toBool()
|| content["rights"].toObject()["hires_streamable"].toBool();
item->setText(0, hiRes ? QStringLiteral("H") : QStringLiteral("A"));
item->setForeground(0, hiRes
? QColor(QStringLiteral("#FFB232"))
: QColor(QStringLiteral("#8E8E93")));
item->setFont(0, badgeFont);
item->setTextAlignment(0, Qt::AlignCenter);
item->setText(1, title);
item->setText(2, artist);
item->setData(0, TypeRole, QStringLiteral("album"));
item->setData(1, IdRole, content["id"].toString());
} else if (type == QStringLiteral("artists")) {
item->setText(0, QStringLiteral("A"));
item->setForeground(0, QColor(QStringLiteral("#2B7CD3")));
item->setFont(0, badgeFont);
item->setTextAlignment(0, Qt::AlignCenter);
item->setText(1, content["name"].toString());
item->setText(2, tr("Artist"));
item->setData(0, TypeRole, QStringLiteral("artist"));
item->setData(0, IdRole, static_cast<qint64>(content["id"].toDouble()));
}
}
} }
void SearchTab::onSearchResult(const QJsonObject &result) void SearchTab::onSearchResult(const QJsonObject &result)

View File

@@ -30,6 +30,7 @@ namespace SidePanel
private slots: private slots:
void onSearchResult(const QJsonObject &result); void onSearchResult(const QJsonObject &result);
void onMostPopularResult(const QJsonObject &result);
void onSearchSubmit(); void onSearchSubmit();
void onItemDoubleClicked(QTreeWidgetItem *item, int column); void onItemDoubleClicked(QTreeWidgetItem *item, int column);
@@ -38,6 +39,7 @@ namespace SidePanel
PlayQueue *m_queue = nullptr; PlayQueue *m_queue = nullptr;
QLineEdit *m_searchBox = nullptr; QLineEdit *m_searchBox = nullptr;
QTabWidget *m_resultTabs = nullptr; QTabWidget *m_resultTabs = nullptr;
QTreeWidget *m_topResults = nullptr;
QTreeWidget *m_trackResults = nullptr; QTreeWidget *m_trackResults = nullptr;
QTreeWidget *m_albumResults = nullptr; QTreeWidget *m_albumResults = nullptr;
QTreeWidget *m_artistResults = nullptr; QTreeWidget *m_artistResults = nullptr;