feat: artist/page endpoint, multi-disc fix, playlist ownership, UX improvements

- Switch artist view to artist/page API (proper sections: Albums, Singles & EPs,
  Live, Compilations; version in titles like "Deluxe")
- Fix artist sections categorization using releases[].type from artist/page
- Add getUser() backend call; fetch on session restore when userId=0 to fix
  playlist ownership (Remove from playlist / Delete playlist were missing)
- Fix multi-disc double-click / Play Now queue start index (disc headers were
  counted in row index but excluded from currentTracksJson)
- Hide redundant Album column when viewing an album
- Make artist name in context header clickable (navigates to artist page)
- Fix gap between title and artist name in context header (addStretch)
- Fix queue panel track titles to include version field
- Fix album list to show version in title column

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-24 23:09:04 +01:00
parent 69fb818c38
commit 872fdecdce
16 changed files with 272 additions and 58 deletions

View File

@@ -35,24 +35,32 @@ Tracks::Tracks(QobuzBackend *backend, PlayQueue *queue, QWidget *parent)
this, &Tracks::onDoubleClicked);
connect(this, &QTreeView::customContextMenuRequested,
this, &Tracks::onContextMenu);
connect(m_model, &QAbstractItemModel::modelReset, this, [this] {
for (int row : m_model->discHeaderRows())
setFirstColumnSpanned(row, {}, true);
setSortingEnabled(!m_model->hasMultipleDiscs());
});
}
void Tracks::loadTracks(const QJsonArray &tracks)
{
setPlaylistContext(0);
setColumnHidden(TrackListModel::ColAlbum, false);
m_model->setTracks(tracks, false, /*useSequential=*/true);
}
void Tracks::loadAlbum(const QJsonObject &album)
{
setPlaylistContext(0);
setColumnHidden(TrackListModel::ColAlbum, true);
const QJsonArray items = album["tracks"].toObject()["items"].toArray();
m_model->setTracks(items); // album: use track_number
}
void Tracks::loadPlaylist(const QJsonObject &playlist)
{
setColumnHidden(TrackListModel::ColAlbum, false);
const qint64 id = static_cast<qint64>(playlist["id"].toDouble());
const qint64 ownId = static_cast<qint64>(playlist["owner"].toObject()["id"].toDouble());
const qint64 myId = AppSettings::instance().userId();
@@ -65,6 +73,7 @@ void Tracks::loadPlaylist(const QJsonObject &playlist)
void Tracks::loadSearchTracks(const QJsonArray &tracks)
{
setPlaylistContext(0);
setColumnHidden(TrackListModel::ColAlbum, false);
m_model->setTracks(tracks, false, /*useSequential=*/true);
}
@@ -118,7 +127,11 @@ void Tracks::onDoubleClicked(const QModelIndex &index)
{
const qint64 id = m_model->data(index, TrackListModel::TrackIdRole).toLongLong();
if (id > 0) {
m_queue->setContext(m_model->currentTracksJson(), index.row());
// Compute filtered row (disc headers excluded from currentTracksJson)
int filteredRow = 0;
for (int r = 0; r < index.row(); ++r)
if (!m_model->trackAt(r).isDiscHeader) ++filteredRow;
m_queue->setContext(m_model->currentTracksJson(), filteredRow);
emit playTrackRequested(id);
}
}
@@ -129,6 +142,7 @@ void Tracks::onContextMenu(const QPoint &pos)
if (!index.isValid()) return;
const qint64 id = m_model->data(index, TrackListModel::TrackIdRole).toLongLong();
if (id <= 0) return; // disc header row
const QJsonObject trackJson = m_model->data(index, TrackListModel::TrackJsonRole).toJsonObject();
QMenu menu(this);
@@ -153,9 +167,12 @@ void Tracks::onContextMenu(const QPoint &pos)
});
}
const int row = index.row();
connect(playNow, &QAction::triggered, this, [this, id, row] {
m_queue->setContext(m_model->currentTracksJson(), row);
// Compute filtered row for multi-disc albums (disc headers excluded from currentTracksJson)
int filteredRow = 0;
for (int r = 0; r < index.row(); ++r)
if (!m_model->trackAt(r).isDiscHeader) ++filteredRow;
connect(playNow, &QAction::triggered, this, [this, id, filteredRow] {
m_queue->setContext(m_model->currentTracksJson(), filteredRow);
emit playTrackRequested(id);
});
connect(playNext, &QAction::triggered, this, [this, trackJson] {