feat: album/playlist header and playlist ownership filtering
- Add TrackContextHeader widget: shows album art (fetched via NAM), title, subtitle (artist/description), and metadata (year · tracks · duration) above the track list when an album or playlist is opened - Hide header for favorite tracks and search results - Store user ID in AppSettings on login - Only show "Delete playlist" for playlists the user owns - "Add to playlist" submenu only lists owned playlists - "Remove from this playlist" only appears when viewing an owned playlist Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ namespace List
|
||||
static constexpr int TypeRole = Qt::UserRole + 1;
|
||||
static constexpr int IdRole = Qt::UserRole + 2;
|
||||
static constexpr int NameRole = Qt::UserRole + 3;
|
||||
static constexpr int IsOwnerRole = Qt::UserRole + 4;
|
||||
|
||||
enum NodeType {
|
||||
NodeFavTracks,
|
||||
@@ -86,22 +87,28 @@ void Library::onUserPlaylistsLoaded(const QJsonObject &result)
|
||||
while (m_playlistsNode->childCount() > 0)
|
||||
delete m_playlistsNode->takeChild(0);
|
||||
|
||||
QVector<QPair<qint64, QString>> playlists;
|
||||
QVector<QPair<qint64, QString>> editablePlaylists;
|
||||
const qint64 myUserId = AppSettings::instance().userId();
|
||||
const QJsonArray items = result["items"].toArray();
|
||||
for (const auto &v : items) {
|
||||
const QJsonObject pl = v.toObject();
|
||||
const QString name = pl["name"].toString();
|
||||
const qint64 id = static_cast<qint64>(pl["id"].toDouble());
|
||||
const QJsonObject pl = v.toObject();
|
||||
const QString name = pl["name"].toString();
|
||||
const qint64 id = static_cast<qint64>(pl["id"].toDouble());
|
||||
const qint64 ownId = static_cast<qint64>(pl["owner"].toObject()["id"].toDouble());
|
||||
const bool isOwner = (myUserId > 0 && ownId == myUserId);
|
||||
|
||||
auto *item = new QTreeWidgetItem(m_playlistsNode, QStringList{name});
|
||||
item->setData(0, TypeRole, NodePlaylist);
|
||||
item->setData(0, IdRole, id);
|
||||
item->setData(0, NameRole, name);
|
||||
item->setData(0, TypeRole, NodePlaylist);
|
||||
item->setData(0, IdRole, id);
|
||||
item->setData(0, NameRole, name);
|
||||
item->setData(0, IsOwnerRole, isOwner);
|
||||
|
||||
playlists.append({id, name});
|
||||
// Only include playlists we can edit in the "Add to playlist" submenu
|
||||
if (isOwner)
|
||||
editablePlaylists.append({id, name});
|
||||
}
|
||||
|
||||
emit userPlaylistsChanged(playlists);
|
||||
emit userPlaylistsChanged(editablePlaylists);
|
||||
}
|
||||
|
||||
void Library::onContextMenuRequested(const QPoint &pos)
|
||||
@@ -128,21 +135,24 @@ void Library::onContextMenuRequested(const QPoint &pos)
|
||||
});
|
||||
|
||||
if (isPlaylistItem) {
|
||||
const qint64 plId = item->data(0, IdRole).toLongLong();
|
||||
const QString plName = item->data(0, NameRole).toString();
|
||||
const qint64 plId = item->data(0, IdRole).toLongLong();
|
||||
const QString plName = item->data(0, NameRole).toString();
|
||||
const bool isOwner = item->data(0, IsOwnerRole).toBool();
|
||||
|
||||
menu.addSeparator();
|
||||
auto *delPl = menu.addAction(tr("Delete \"%1\"…").arg(plName));
|
||||
connect(delPl, &QAction::triggered, this, [this, plId, plName] {
|
||||
const auto answer = QMessageBox::question(
|
||||
this,
|
||||
tr("Delete Playlist"),
|
||||
tr("Permanently delete \"%1\"? This cannot be undone.").arg(plName),
|
||||
QMessageBox::Yes | QMessageBox::Cancel,
|
||||
QMessageBox::Cancel);
|
||||
if (answer == QMessageBox::Yes)
|
||||
m_backend->deletePlaylist(plId);
|
||||
});
|
||||
if (isOwner) {
|
||||
menu.addSeparator();
|
||||
auto *delPl = menu.addAction(tr("Delete \"%1\"…").arg(plName));
|
||||
connect(delPl, &QAction::triggered, this, [this, plId, plName] {
|
||||
const auto answer = QMessageBox::question(
|
||||
this,
|
||||
tr("Delete Playlist"),
|
||||
tr("Permanently delete \"%1\"? This cannot be undone.").arg(plName),
|
||||
QMessageBox::Yes | QMessageBox::Cancel,
|
||||
QMessageBox::Cancel);
|
||||
if (answer == QMessageBox::Yes)
|
||||
m_backend->deletePlaylist(plId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
menu.exec(viewport()->mapToGlobal(pos));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../backend/qobuzbackend.hpp"
|
||||
#include "../util/settings.hpp"
|
||||
|
||||
#include <QTreeWidget>
|
||||
#include <QVector>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "tracks.hpp"
|
||||
#include "../util/settings.hpp"
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
@@ -52,8 +53,11 @@ void Tracks::loadAlbum(const QJsonObject &album)
|
||||
|
||||
void Tracks::loadPlaylist(const QJsonObject &playlist)
|
||||
{
|
||||
const qint64 id = static_cast<qint64>(playlist["id"].toDouble());
|
||||
setPlaylistContext(id);
|
||||
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();
|
||||
const bool isOwned = (myId > 0 && ownId == myId);
|
||||
setPlaylistContext(id, isOwned);
|
||||
const QJsonArray items = playlist["tracks"].toObject()["items"].toArray();
|
||||
m_model->setTracks(items, /*usePosition=*/true);
|
||||
}
|
||||
@@ -64,9 +68,10 @@ void Tracks::loadSearchTracks(const QJsonArray &tracks)
|
||||
m_model->setTracks(tracks, false, /*useSequential=*/true);
|
||||
}
|
||||
|
||||
void Tracks::setPlaylistContext(qint64 playlistId)
|
||||
void Tracks::setPlaylistContext(qint64 playlistId, bool isOwned)
|
||||
{
|
||||
m_playlistId = playlistId;
|
||||
m_playlistIsOwned = isOwned;
|
||||
}
|
||||
|
||||
void Tracks::setUserPlaylists(const QVector<QPair<qint64, QString>> &playlists)
|
||||
@@ -164,7 +169,7 @@ void Tracks::onContextMenu(const QPoint &pos)
|
||||
}
|
||||
}
|
||||
|
||||
if (m_playlistId > 0) {
|
||||
if (m_playlistId > 0 && m_playlistIsOwned) {
|
||||
const qint64 playlistTrackId =
|
||||
m_model->data(index, TrackListModel::PlaylistTrackIdRole).toLongLong();
|
||||
if (playlistTrackId > 0) {
|
||||
|
||||
@@ -29,7 +29,8 @@ namespace List
|
||||
void setPlayingTrackId(qint64 id);
|
||||
|
||||
/// Set which playlist is currently displayed (0 = none).
|
||||
void setPlaylistContext(qint64 playlistId);
|
||||
/// isOwned controls whether "Remove from this playlist" is shown.
|
||||
void setPlaylistContext(qint64 playlistId, bool isOwned = false);
|
||||
qint64 playlistId() const { return m_playlistId; }
|
||||
/// Provide the user's playlist list for the "Add to playlist" submenu.
|
||||
void setUserPlaylists(const QVector<QPair<qint64, QString>> &playlists);
|
||||
@@ -44,6 +45,7 @@ namespace List
|
||||
QobuzBackend *m_backend = nullptr;
|
||||
PlayQueue *m_queue = nullptr;
|
||||
qint64 m_playlistId = 0;
|
||||
bool m_playlistIsOwned = false;
|
||||
QVector<QPair<qint64, QString>> m_userPlaylists;
|
||||
|
||||
void onDoubleClicked(const QModelIndex &index);
|
||||
|
||||
Reference in New Issue
Block a user