diff --git a/core/include/JellyfinQt/jellyfin.h b/core/include/JellyfinQt/jellyfin.h index 5bc28d0..eb12149 100644 --- a/core/include/JellyfinQt/jellyfin.h +++ b/core/include/JellyfinQt/jellyfin.h @@ -36,6 +36,7 @@ #include "viewmodel/loader.h" #include "viewmodel/modelstatus.h" #include "viewmodel/playbackmanager.h" +#include "viewmodel/userdata.h" namespace Jellyfin { diff --git a/core/include/JellyfinQt/model/item.h b/core/include/JellyfinQt/model/item.h index 5f3ba50..9206877 100644 --- a/core/include/JellyfinQt/model/item.h +++ b/core/include/JellyfinQt/model/item.h @@ -37,6 +37,7 @@ class Item : public QObject, public DTO::BaseItemDto { Q_OBJECT public: using UserDataChangedCallback = std::function; + using BaseItemDto::userData; /** * @brief Constructor that creates an empty item. */ diff --git a/core/include/JellyfinQt/viewmodel/item.h b/core/include/JellyfinQt/viewmodel/item.h index 9fabf39..09ca93a 100644 --- a/core/include/JellyfinQt/viewmodel/item.h +++ b/core/include/JellyfinQt/viewmodel/item.h @@ -100,7 +100,7 @@ public: Q_PROPERTY(QString type READ type NOTIFY typeChanged) Q_PROPERTY(QString parentBackdropItemId READ parentBackdropItemId NOTIFY parentBackdropItemIdChanged) Q_PROPERTY(QStringList parentBackdropImageTags READ parentBackdropImageTags NOTIFY parentBackdropImageTagsChanged) - Q_PROPERTY(UserData *userData READ userData NOTIFY userDataChanged) + Q_PROPERTY(Jellyfin::ViewModel::UserData *userData READ userData NOTIFY userDataChanged) Q_PROPERTY(int recursiveItemCount READ recursiveItemCount NOTIFY recursiveItemCountChanged) Q_PROPERTY(int childCount READ childCount NOTIFY childCountChanged) Q_PROPERTY(QString albumArtist READ albumArtist NOTIFY albumArtistChanged) diff --git a/core/include/JellyfinQt/viewmodel/itemmodel.h b/core/include/JellyfinQt/viewmodel/itemmodel.h index 2d8ba68..65fc79a 100644 --- a/core/include/JellyfinQt/viewmodel/itemmodel.h +++ b/core/include/JellyfinQt/viewmodel/itemmodel.h @@ -28,6 +28,7 @@ #include "../loader/http/getuserviews.h" #include "../loader/requesttypes.h" #include "../model/item.h" +#include "../viewmodel/item.h" #include "../apimodel.h" #include "modelstatus.h" #include "propertyhelper.h" @@ -268,6 +269,16 @@ public: artists, isFolder, parentIndexNumber, + userDataRating, + userDataPlayedPercentage, + userDataUnplayedItemCount, + userDataPlaybackPositionTicks, + userDataPlayCount, + userDataFavorite, + userDataLikes, + userDataLastPlayedDate, + userDataPlayed, + userDataKey, jellyfinExtendModelAfterHere = Qt::UserRole + 300 // Should be enough for now }; @@ -297,41 +308,22 @@ public: JFRN(artists), JFRN(isFolder), JFRN(parentIndexNumber), + JFRN(userDataRating), + JFRN(userDataPlayedPercentage), + JFRN(userDataUnplayedItemCount), + JFRN(userDataPlaybackPositionTicks), + JFRN(userDataPlayCount), + JFRN(userDataFavorite), + JFRN(userDataLikes), + JFRN(userDataLastPlayedDate), + JFRN(userDataPlayed), + JFRN(userDataKey), }; } QVariant data(const QModelIndex &index, int role) const override; QSharedPointer itemAt(int index); }; -/*class UserItemModel : public ItemModel { -public: - explicit UserItemModel (QObject *parent = nullptr); -}; - -class UserItemResumeModel : public ItemModel { -public: - explicit UserItemResumeModel (QObject *parent = nullptr); -}; - -class UserItemLatestModel : public ItemModel { -public: - explicit UserItemLatestModel (QObject *parent = nullptr); -}; - -class ShowNextUpModel : public ItemModel { -public: - explicit ShowNextUpModel (QObject *parent = nullptr); -}; - -class ShowSeasonsModel : public ItemModel { -public: - explicit ShowSeasonsModel (QObject *parent = nullptr); -}; - -class ShowEpisodesModel : public ItemModel { -public: - explicit ShowEpisodesModel (QObject *parent = nullptr); -};*/ #undef JFRN } // NS Jellyfin diff --git a/core/src/jellyfin.cpp b/core/src/jellyfin.cpp index ab222cc..95852b4 100644 --- a/core/src/jellyfin.cpp +++ b/core/src/jellyfin.cpp @@ -26,6 +26,7 @@ void registerTypes(const char *uri) { qmlRegisterUncreatableType(uri, 1, 0, "Item", "Acquire one via ItemLoader or exposed properties"); qmlRegisterUncreatableType(uri, 1, 0, "EventBus", "Obtain one via your ApiClient"); qmlRegisterUncreatableType(uri, 1, 0, "WebSocket", "Obtain one via your ApiClient"); + qmlRegisterUncreatableType(uri, 1, 0, "UserData", "Obtain one via an Item"); // AbstractItemModels qmlRegisterUncreatableType(uri, 1, 0, "BaseApiModel", "Please use one of its subclasses"); diff --git a/core/src/viewmodel/item.cpp b/core/src/viewmodel/item.cpp index c82cc46..cd8b140 100644 --- a/core/src/viewmodel/item.cpp +++ b/core/src/viewmodel/item.cpp @@ -27,16 +27,21 @@ Item::Item(QObject *parent, QSharedPointer data) m_data(data), m_userData(new UserData(this)){ connect(m_data.data(), &Model::Item::userDataChanged, this, &Item::onUserDataChanged); + m_userData->setData(data->userData()); } void Item::setData(QSharedPointer newData) { if (!m_data.isNull()) { disconnect(m_data.data(), &Model::Item::userDataChanged, this, &Item::onUserDataChanged); } + m_data = newData; + if (!m_data.isNull()) { connect(m_data.data(), &Model::Item::userDataChanged, this, &Item::onUserDataChanged); + setUserData(m_data->userData()); } + emit userDataChanged(m_userData); } void Item::setUserData(DTO::UserItemDataDto &newData) { @@ -44,7 +49,11 @@ void Item::setUserData(DTO::UserItemDataDto &newData) { } void Item::setUserData(QSharedPointer newData) { - m_userData->setData(newData); + if (m_data.isNull() || m_data->userData().isNull()) { + m_userData->setData(QSharedPointer::create()); + } else { + m_userData->setData(newData); + } emit userDataChanged(m_userData); } diff --git a/core/src/viewmodel/itemmodel.cpp b/core/src/viewmodel/itemmodel.cpp index fba60ba..ba0b7d1 100644 --- a/core/src/viewmodel/itemmodel.cpp +++ b/core/src/viewmodel/itemmodel.cpp @@ -24,6 +24,8 @@ #include "JellyfinQt/loader/http/getresumeitems.h" #include "JellyfinQt/loader/http/getseasons.h" +#include "JellyfinQt/viewmodel/userdata.h" + #define JF_CASE(roleName) case roleName: \ try { \ return QVariant(item->roleName()); \ @@ -87,6 +89,27 @@ QVariant ItemModel::data(const QModelIndex &index, int role) const { return QVariant(item->isFolder().value_or(false)); case RoleNames::parentIndexNumber: return QVariant(item->parentIndexNumber().value_or(1)); + // UserData + case RoleNames::userDataRating: + return QVariant(item->userData()->rating().value_or(0.0)); + case RoleNames::userDataPlayedPercentage: + return QVariant(item->userData()->playedPercentage().value_or(0.0)); + case RoleNames::userDataUnplayedItemCount: + return QVariant(item->userData()->unplayedItemCount().value_or(0)); + case RoleNames::userDataPlaybackPositionTicks: + return QVariant(item->userData()->playbackPositionTicks()); + case RoleNames::userDataPlayCount: + return QVariant(item->userData()->playCount()); + case RoleNames::userDataFavorite: + return QVariant(item->userData()->isFavorite()); + case RoleNames::userDataLikes: + return QVariant(item->userData()->likes().value_or(false)); + case RoleNames::userDataLastPlayedDate: + return QVariant(item->userData()->lastPlayedDate()); + case RoleNames::userDataPlayed: + return QVariant(item->userData()->played()); + case RoleNames::userDataKey: + return QVariant(item->userData()->key()); default: return QVariant(); } diff --git a/core/src/viewmodel/userdata.cpp b/core/src/viewmodel/userdata.cpp index 6c5f5a7..d2b26e1 100644 --- a/core/src/viewmodel/userdata.cpp +++ b/core/src/viewmodel/userdata.cpp @@ -22,17 +22,24 @@ namespace Jellyfin { namespace ViewModel { UserData::UserData(QObject *parent) - : QObject(parent), - m_data(QSharedPointer::create()) { + : UserData(QSharedPointer::create(), parent) { + } UserData::UserData(QSharedPointer data, QObject *parent) : QObject(parent), m_data(data) { + if (m_data.isNull()) { + m_data = QSharedPointer::create(); + } } void UserData::setData(QSharedPointer data) { - m_data = data; + if (data.isNull()) { + m_data = QSharedPointer::create(); + } else { + m_data = data; + } } } // NS ViewModel diff --git a/qtquick/qml/pages/DetailPage.qml b/qtquick/qml/pages/DetailPage.qml index a44685a..35bf1a4 100644 --- a/qtquick/qml/pages/DetailPage.qml +++ b/qtquick/qml/pages/DetailPage.qml @@ -51,6 +51,9 @@ Page { height: parent.height / 3 * 2 anchors.bottom: parent.bottom model: tracks + header: Label { + text: "Play count: %1".arg(jellyfinItem.userData.playCount) + } delegate: ItemDelegate { icon.source: ApiClient.baseUrl + "/Items/" + model.jellyfinId + "/Images/Primary?tag=" + model.tag text: model.name diff --git a/qtquick/test.rcc b/qtquick/test.rcc new file mode 100644 index 0000000..01996ed Binary files /dev/null and b/qtquick/test.rcc differ diff --git a/sailfish/qml/pages/itemdetails/BaseDetailPage.qml b/sailfish/qml/pages/itemdetails/BaseDetailPage.qml index 36b2f01..0d3c3c5 100644 --- a/sailfish/qml/pages/itemdetails/BaseDetailPage.qml +++ b/sailfish/qml/pages/itemdetails/BaseDetailPage.qml @@ -34,6 +34,7 @@ Page { id: pageRoot property string itemId: "" property alias itemData: jItemLoader.data + property alias itemLoader: jItemLoader property bool _loading: jItemLoader.status === J.ItemLoader.Loading readonly property bool hasLogo: (typeof itemData.imageTags !== "undefined") && (typeof itemData.imageTags["Logo"] !== "undefined") property string _chosenBackdropImage: "" diff --git a/sailfish/qml/pages/itemdetails/SeasonPage.qml b/sailfish/qml/pages/itemdetails/SeasonPage.qml index 4b04b61..b9b7baf 100644 --- a/sailfish/qml/pages/itemdetails/SeasonPage.qml +++ b/sailfish/qml/pages/itemdetails/SeasonPage.qml @@ -79,7 +79,7 @@ BaseDetailPage { shimColor: Theme.overlayBackgroundColor shimOpacity: Theme.opacityOverlay //width: model.userData.PlayedPercentage * parent.width / 100 - visible: episodeProgress.width > 0 || model.userData.played || model.userData.isFavorite // It doesn't look nice when it's visible on every image + visible: episodeProgress.width > 0 || model.userDataPlayed || model.userDataFavorite // It doesn't look nice when it's visible on every image } Rectangle { @@ -89,7 +89,7 @@ BaseDetailPage { bottom: parent.bottom } height: Theme.paddingMedium - width: model.userData.playedPercentage * parent.width / 100 + width: model.userDataPlayedPercentage * parent.width / 100 color: Theme.highlightColor } Row { @@ -103,12 +103,12 @@ BaseDetailPage { Icon { source: "image://theme/icon-s-checkmark" - visible: model.userData.played + visible: model.userDataPlayed } Icon { source: "image://theme/icon-s-favorite" - visible: model.userData.isFavorite + visible: model.userDataFavorite } } } diff --git a/sailfish/qml/pages/itemdetails/VideoPage.qml b/sailfish/qml/pages/itemdetails/VideoPage.qml index 1da8c9d..a45faab 100644 --- a/sailfish/qml/pages/itemdetails/VideoPage.qml +++ b/sailfish/qml/pages/itemdetails/VideoPage.qml @@ -33,7 +33,7 @@ BaseDetailPage { property alias subtitle: pageHeader.description default property alias _data: content.data property real _playbackProsition: itemData.userData.playbackPositionTicks - readonly property bool _userdataReady: itemData.status === J.ItemLoader.Ready && itemData.userData !== null + readonly property bool _userdataReady: itemLoader.status === J.ItemLoader.Ready && itemData.userData !== null SilicaFlickable { anchors.fill: parent contentHeight: content.height + Theme.paddingLarge @@ -60,7 +60,7 @@ BaseDetailPage { imageBlurhash: itemData.imageBlurHashes["Primary"][itemData.imageTags["Primary"]] Binding on favourited { when: _userdataReady - value: itemData.userData.isFavorite + value: itemData.userData.favorite } Binding on playProgress { when: _userdataReady @@ -82,7 +82,7 @@ BaseDetailPage { } Connections { - target: itemData + target: itemLoader onStatusChanged: { if (status === J.ItemLoader.Ready) { console.log(itemData.mediaStreams)