diff --git a/core/include/JellyfinQt/apimodel.h b/core/include/JellyfinQt/apimodel.h
index 5fef3cd..352ed31 100644
--- a/core/include/JellyfinQt/apimodel.h
+++ b/core/include/JellyfinQt/apimodel.h
@@ -168,7 +168,7 @@ public:
m_startIndex = 0;
m_totalRecordCount = -1;
emitModelShouldClear();
- loadMore(0, -1, ViewModel::ModelStatus::Loading);
+ loadMore(0, m_limit, ViewModel::ModelStatus::Loading);
}
void loadMore() {
@@ -272,7 +272,9 @@ protected:
// meaning loadMore is not supported.
return;
}
- setRequestLimit
(this->m_parameters, limit);
+ if (limit > 0) {
+ setRequestLimit
(this->m_parameters, limit);
+ }
this->setStatus(suggestedModelStatus);
// We never want to set this while the loader is running, hence the Mutex and setting it here
diff --git a/core/include/JellyfinQt/jellyfin.h b/core/include/JellyfinQt/jellyfin.h
index 2a2ddfa..5bc28d0 100644
--- a/core/include/JellyfinQt/jellyfin.h
+++ b/core/include/JellyfinQt/jellyfin.h
@@ -30,6 +30,7 @@
#include "apiclient.h"
#include "apimodel.h"
#include "serverdiscoverymodel.h"
+#include "websocket.h"
#include "viewmodel/item.h"
#include "viewmodel/itemmodel.h"
#include "viewmodel/loader.h"
diff --git a/core/include/JellyfinQt/model/item.h b/core/include/JellyfinQt/model/item.h
index e313f74..9675a58 100644
--- a/core/include/JellyfinQt/model/item.h
+++ b/core/include/JellyfinQt/model/item.h
@@ -33,6 +33,16 @@ namespace Model {
class Item : public DTO::BaseItemDto {
public:
+ /**
+ * @brief Constructor that creates an empty item.
+ */
+ Item();
+
+ /**
+ * @brief Copies the data from the DTO into this model and attaches an ApiClient
+ * @param data The DTO to copy information from
+ * @param apiClient The ApiClient to attach to, to listen for updates and so on.
+ */
Item(const DTO::BaseItemDto &data, ApiClient *apiClient = nullptr);
virtual ~Item();
diff --git a/core/include/JellyfinQt/support/jsonconv.h b/core/include/JellyfinQt/support/jsonconv.h
index 1d78231..c8cc6c9 100644
--- a/core/include/JellyfinQt/support/jsonconv.h
+++ b/core/include/JellyfinQt/support/jsonconv.h
@@ -152,9 +152,24 @@ QJsonValue toJsonValue(const QSharedPointer &source, convertType
+QString toString(const T &source, convertType) {
+ return toJsonValue(source).toString();
+}
+
+template
+QString toString(const std::optional &source, convertType>) {
+ if (source.has_value()) {
+ return toString(source.value(), convertType{});
+ } else {
+ return QString();
+ }
+}
+
template
QString toString(const T &source) {
- return toJsonValue(source).toString();
+ return toString(source, convertType{});
}
diff --git a/core/include/JellyfinQt/viewmodel/item.h b/core/include/JellyfinQt/viewmodel/item.h
index f3ef7b8..74d5eae 100644
--- a/core/include/JellyfinQt/viewmodel/item.h
+++ b/core/include/JellyfinQt/viewmodel/item.h
@@ -51,9 +51,7 @@ namespace ViewModel {
class Item : public QObject {
Q_OBJECT
public:
- explicit Item(QObject *parent = nullptr);
- explicit Item(QSharedPointer data = QSharedPointer(),
- QObject *parent = nullptr);
+ explicit Item(QObject *parent = nullptr, QSharedPointer data = QSharedPointer::create());
// Please keep the order of the properties the same as in the file linked above.
Q_PROPERTY(QUuid jellyfinId READ jellyfinId NOTIFY jellyfinIdChanged)
@@ -194,7 +192,7 @@ public:
Q_PROPERTY(QString itemId READ itemId WRITE setItemId NOTIFY itemIdChanged)
QString itemId() const { return m_parameters.itemId(); }
- void setItemId(QString newItemId) { m_parameters.setItemId(newItemId); }
+ void setItemId(QString newItemId) { m_parameters.setItemId(newItemId); emit itemIdChanged(newItemId); }
virtual bool canReload() const override;
signals:
diff --git a/core/include/JellyfinQt/viewmodel/itemmodel.h b/core/include/JellyfinQt/viewmodel/itemmodel.h
index 8516ef2..03c8557 100644
--- a/core/include/JellyfinQt/viewmodel/itemmodel.h
+++ b/core/include/JellyfinQt/viewmodel/itemmodel.h
@@ -44,7 +44,44 @@ namespace ViewModel {
// This file contains all models that expose a Model::Item
-using UserViewsLoaderBase = LoaderModelLoader;
+/**
+ * @brief Class intended for models which have a mandatory userId property, which can be extracted from the
+ * ApiClient.
+ */
+template
+class AbstractUserParameterLoader : public LoaderModelLoader {
+public:
+ explicit AbstractUserParameterLoader(Support::Loader *loader, QObject *parent = nullptr)
+ : LoaderModelLoader(loader, parent) {
+ this->connect(this, &BaseModelLoader::apiClientChanged, this, &AbstractUserParameterLoader::apiClientChanged);
+ }
+protected:
+ virtual bool canReload() const override {
+ return BaseModelLoader::canReload() && !this->m_parameters.userId().isNull();
+ }
+private:
+ void apiClientChanged(ApiClient *newApiClient) {
+ if (this->m_apiClient != nullptr) {
+ this->disconnect(this->m_apiClient, &ApiClient::userIdChanged, this, &AbstractUserParameterLoader::userIdChanged);
+ }
+ if (newApiClient != nullptr) {
+ this->connect(newApiClient, &ApiClient::userIdChanged, this, &AbstractUserParameterLoader::userIdChanged);
+ if (!newApiClient->userId().isNull()) {
+ this->m_parameters.setUserId(newApiClient->userId());
+ }
+ }
+ }
+
+ void userIdChanged(const QString &newUserId) {
+ this->m_parameters.setUserId(newUserId);
+ this->autoReloadIfNeeded();
+ }
+};
+
+/**
+ * Loads the views of an user, such as "Videos", "Music" and so on.
+ */
+using UserViewsLoaderBase = AbstractUserParameterLoader;
class UserViewsLoader : public UserViewsLoaderBase {
Q_OBJECT
public:
@@ -53,12 +90,27 @@ public:
FWDPROP(bool, includeExternalContent, IncludeExternalContent)
FWDPROP(bool, includeHidden, IncludeHidden)
FWDPROP(QStringList, presetViews, PresetViews)
-private slots:
- void apiClientChanged(ApiClient *newApiClient);
- void userIdChanged(const QString &newUserId);
};
-using UserItemsLoaderBase = LoaderModelLoader;
+using LatestMediaBase = AbstractUserParameterLoader, Jellyfin::Loader::GetLatestMediaParams>;
+class LatestMediaLoader : public LatestMediaBase {
+ Q_OBJECT
+public:
+ explicit LatestMediaLoader(QObject *parent = nullptr);
+
+ // Optional
+ FWDPROP(QList, enableImageTypes, EnableImageTypes)
+ FWDPROP(bool, enableImages, EnableImages)
+ FWDPROP(bool, enableUserData, EnableUserData)
+ FWDPROP(QList, fields, Fields)
+ FWDPROP(bool, groupItems, GroupItems)
+ FWDPROP(qint32, imageTypeLimit, ImageTypeLimit)
+ FWDPROP(QStringList, includeItemTypes, IncludeItemTypes)
+ FWDPROP(bool, isPlayed, IsPlayed)
+ FWDPROP(QString, parentId, ParentId)
+};
+
+using UserItemsLoaderBase = AbstractUserParameterLoader;
class UserItemsLoader : public UserItemsLoaderBase {
Q_OBJECT
public:
@@ -70,14 +122,26 @@ public:
FWDPROP(QStringList, albums, Albums)
FWDPROP(QStringList, artistIds, ArtistIds)
FWDPROP(QStringList, artists, Artists)
+ FWDPROP(bool, collapseBoxSetItems, CollapseBoxSetItems)
+ FWDPROP(QStringList, contributingArtistIds, ContributingArtistIds)
+ FWDPROP(QList, enableImageTypes, EnableImageTypes);
+ FWDPROP(bool, enableImages, EnableImages)
+ FWDPROP(bool, enableTotalRecordCount, EnableTotalRecordCount)
+ FWDPROP(bool, enableUserData, EnableUserData)
+ FWDPROP(QStringList, excludeArtistIds, ExcludeArtistIds)
+ FWDPROP(QStringList, excludeItemIds, ExcludeItemIds)
+ FWDPROP(QStringList, excludeItemTypes, ExcludeItemTypes)
+ FWDPROP(QList, excludeLocationTypes, ExcludeLocationTypes)
+ FWDPROP(QList, fields, Fields)
+ FWDPROP(QList, filters, Filters)
+
+ FWDPROP(QString, parentId, ParentId)
FWDPROP(bool, recursive, Recursive)
//FWDPROP(bool, collapseBoxSetItems)
-protected:
- virtual bool canReload() const override;
-private slots:
- void apiClientChanged(ApiClient *newApiClient);
- void userIdChanged(const QString &newUserId);
};
+
+
+
/**
* @brief Base class for each model that works with items.
*/
@@ -95,7 +159,10 @@ public:
playlistItemId,
dateCreated,
dateLastMediaAdded,
- extraType
+ extraType,
+
+ // Hand-picked, important ones
+ imageTags
};
explicit ItemModel (QObject *parent = nullptr);
@@ -111,7 +178,9 @@ public:
JFRN(playlistItemId),
JFRN(dateCreated),
JFRN(dateLastMediaAdded),
- JFRN(extraType)
+ JFRN(extraType),
+ // Handpicked, important ones
+ JFRN(imageTags),
};
}
QVariant data(const QModelIndex &index, int role) const override;
diff --git a/core/include/JellyfinQt/viewmodel/loader.h b/core/include/JellyfinQt/viewmodel/loader.h
index f065afa..3ed7f9d 100644
--- a/core/include/JellyfinQt/viewmodel/loader.h
+++ b/core/include/JellyfinQt/viewmodel/loader.h
@@ -144,13 +144,14 @@ template
class Loader : public LoaderBase {
using RFutureWatcher = QFutureWatcher>;
public:
- Loader(Support::Loader loaderImpl, QObject *parent = nullptr)
+ Loader(Support::Loader *loaderImpl, QObject *parent = nullptr)
: Loader(nullptr, loaderImpl, parent) {}
- Loader(ApiClient *apiClient, Support::Loader loaderImpl, QObject *parent = nullptr)
+ Loader(ApiClient *apiClient, Support::Loader *loaderImpl, QObject *parent = nullptr)
: LoaderBase(apiClient, parent),
m_loader(loaderImpl),
- m_futureWatcher(new QFutureWatcher>) {
+ m_futureWatcher(new QFutureWatcher>(this)) {
+
m_dataViewModel = new T(this);
connect(m_futureWatcher, &RFutureWatcher::finished, this, &Loader::updateData);
}
@@ -161,8 +162,9 @@ public:
void reload() override {
if (m_futureWatcher->isRunning()) return;
setStatus(Loading);
- m_loader.setParameters(m_parameters);
- m_loader.prepareLoad();
+ this->m_loader->setApiClient(m_apiClient);
+ m_loader->setParameters(m_parameters);
+ m_loader->prepareLoad();
QFuture> future = QtConcurrent::run(this, &Loader::invokeLoader);
m_futureWatcher->setFuture(future);
}
@@ -172,7 +174,7 @@ protected:
/**
* @brief Subclasses should initialize this to a loader that actually loads stuff.
*/
- Support::Loader m_loader;
+ QScopedPointer> m_loader = nullptr;
private:
QFutureWatcher> *m_futureWatcher;
@@ -184,9 +186,8 @@ private:
*/
std::optional invokeLoader() {
QMutexLocker(&this->m_mutex);
- this->m_loader.setApiClient(m_apiClient);
try {
- return this->m_loader.load();
+ return this->m_loader->load();
} catch (Support::LoadException &e) {
qWarning() << "Exception while loading an item: " << e.what();
this->setErrorString(QString(e.what()));
@@ -206,7 +207,7 @@ private:
} else {
// Replace the model
using PointerType = typename decltype(m_dataViewModel->data())::Type;
- m_dataViewModel = new T(QSharedPointer::create(newData), this);
+ m_dataViewModel = new T(this, QSharedPointer::create(newData, m_apiClient));
}
setStatus(Ready);
emitDataChanged();
diff --git a/core/include/JellyfinQt/viewmodel/playbackmanager.h b/core/include/JellyfinQt/viewmodel/playbackmanager.h
index 8743d20..0859fab 100644
--- a/core/include/JellyfinQt/viewmodel/playbackmanager.h
+++ b/core/include/JellyfinQt/viewmodel/playbackmanager.h
@@ -80,7 +80,7 @@ public:
Q_PROPERTY(PlayMethod playMethod READ playMethod NOTIFY playMethodChanged)
// Current Item and queue informatoion
- Q_PROPERTY(Model::Item *item READ item NOTIFY itemChanged)
+ Q_PROPERTY(ViewModel::Item *item READ item NOTIFY itemChanged)
Q_PROPERTY(QAbstractItemModel *queue READ queue NOTIFY queueChanged)
Q_PROPERTY(int queueIndex READ queueIndex NOTIFY queueIndexChanged)
@@ -94,7 +94,7 @@ public:
Q_PROPERTY(QMediaPlayer::State playbackState READ playbackState NOTIFY playbackStateChanged)
Q_PROPERTY(qint64 position READ position NOTIFY positionChanged)
- Model::Item *item() const { return m_item.data(); }
+ ViewModel::Item *item() const { return m_displayItem.get(); }
void setApiClient(ApiClient *apiClient);
QString streamUrl() const { return m_streamUrl; }
@@ -169,6 +169,8 @@ private:
QTimer m_updateTimer;
ApiClient *m_apiClient = nullptr;
QSharedPointer m_item;
+ QScopedPointer m_displayItem = QScopedPointer(new ViewModel::Item());
+
QString m_streamUrl;
QString m_playSessionId;
int m_audioIndex = 0;
diff --git a/core/src/apimodel.cpp b/core/src/apimodel.cpp
index 90d8186..90e0839 100644
--- a/core/src/apimodel.cpp
+++ b/core/src/apimodel.cpp
@@ -62,7 +62,7 @@ void BaseModelLoader::setApiClient(ApiClient *newApiClient) {
void BaseModelLoader::setLimit(int newLimit) {
int oldLimit = this->m_limit;
m_limit = newLimit;
- if (oldLimit != this->m_limit) {
+ if (oldLimit != newLimit) {
emit limitChanged(this->m_limit);
}
}
@@ -76,6 +76,7 @@ void BaseModelLoader::setAutoReload(bool newAutoReload) {
bool BaseModelLoader::canReload() const {
return m_apiClient != nullptr
+ && !m_isBeingParsed
// If the loader for this model needs authentication (almost every one does)
// block if the ApiClient is not authenticated yet.
&& (!m_needsAuthentication || m_apiClient->authenticated())
@@ -88,6 +89,8 @@ void BaseApiModel::reload() {
qWarning() << " BaseApiModel slot called instead of overloaded method";
}
+// Parameters injectors and result extractors
+
template <>
bool setRequestStartIndex(Loader::GetUserViewsParams ¶ms, int startIndex) {
// Not supported
@@ -112,6 +115,28 @@ int extractTotalRecordCount(const DTO::BaseItemDtoQueryResult &result) {
return result.totalRecordCount();
}
+template <>
+QList extractRecords(const QList &result) {
+ return result;
+}
+
+template <>
+int extractTotalRecordCount(const QList &result) {
+ return result.size();
+}
+
+template<>
+void setRequestLimit(Loader::GetLatestMediaParams ¶ms, int limit) {
+ params.setLimit(limit);
+}
+
+template<>
+bool setRequestStartIndex(Loader::GetLatestMediaParams ¶ms, int offset) {
+ Q_UNUSED(params)
+ Q_UNUSED(offset)
+ return false;
+}
+
void registerModels(const char *URI) {
Q_UNUSED(URI)
diff --git a/core/src/jellyfin.cpp b/core/src/jellyfin.cpp
index d6ec414..d4a2630 100644
--- a/core/src/jellyfin.cpp
+++ b/core/src/jellyfin.cpp
@@ -22,20 +22,25 @@ namespace Jellyfin {
void registerTypes(const char *uri) {
qmlRegisterType(uri, 1, 0, "ApiClient");
qmlRegisterType(uri, 1, 0, "ServerDiscoveryModel");
+ qmlRegisterType(uri, 1, 0, "PlaybackManager");
+ qmlRegisterUncreatableType(uri, 1, 0, "Item", "Acquire one via ItemLoader or exposed properties");
+ qmlRegisterUncreatableType(uri, 1, 0, "WebSocket", "Obtain one via your ApiClient");
-
+ // AbstractItemModels
qmlRegisterUncreatableType(uri, 1, 0, "BaseApiModel", "Please use one of its subclasses");
qmlRegisterUncreatableType(uri, 1, 0, "BaseModelLoader", "Please use one of its subclasses");
- qmlRegisterUncreatableType(uri, 1, 0, "LoaderBase", "Use on eof its subclasses");
-
- qmlRegisterUncreatableType(uri, 1, 0, "Item", "Acquire one via ItemLoader or exposed properties");
- qmlRegisterType(uri, 1, 0, "ItemLoader");
qmlRegisterType(uri, 1, 0, "ItemModel");
- qmlRegisterType(uri, 1, 0, "UsersViewLoader");
- qmlRegisterType(uri, 1, 0, "PlaybackManager");
+ // Loaders
+ qmlRegisterUncreatableType(uri, 1, 0, "LoaderBase", "Use one of its subclasses");
+ qmlRegisterType(uri, 1, 0, "ItemLoader");
+ qmlRegisterType(uri, 1, 0, "LatestMediaLoader");
+ qmlRegisterType(uri, 1, 0, "UserItemsLoader");
+ qmlRegisterType(uri, 1, 0, "UsersViewsLoader");
+ // Enumerations
qmlRegisterUncreatableType(uri, 1, 0, "GeneralCommandType", "Is an enum");
qmlRegisterUncreatableType(uri, 1, 0, "ModelStatus", "Is an enum");
+
}
}
diff --git a/core/src/model/item.cpp b/core/src/model/item.cpp
index f0e6583..f2de2e5 100644
--- a/core/src/model/item.cpp
+++ b/core/src/model/item.cpp
@@ -22,6 +22,10 @@
namespace Jellyfin {
namespace Model {
+
+Item::Item()
+ : Item(DTO::BaseItemDto(), nullptr) { }
+
Item::Item(const DTO::BaseItemDto &data, ApiClient *apiClient)
: DTO::BaseItemDto(data), m_apiClient(apiClient) {
if (m_apiClient != nullptr) {
diff --git a/core/src/support/jsonconv.cpp b/core/src/support/jsonconv.cpp
index 5889c8d..f6a959e 100644
--- a/core/src/support/jsonconv.cpp
+++ b/core/src/support/jsonconv.cpp
@@ -149,8 +149,14 @@ QJsonValue toJsonValue(const QStringList &source, convertType
QJsonObject fromJsonValue(const QJsonValue &source, convertType) {
- if (!source.isObject()) throw ParseException("Error parsing JSON value as object: not a double");
- return source.toObject();
+ switch(source.type()) {
+ case QJsonValue::Null:
+ return QJsonObject();
+ case QJsonValue::Object:
+ return source.toObject();
+ default:
+ throw ParseException("Error parsing JSON value as object: not an object");
+ }
}
template <>
@@ -161,7 +167,7 @@ QJsonValue toJsonValue(const QJsonObject &source, convertType
double fromJsonValue(const QJsonValue &source, convertType) {
- if (!source.isDouble()) throw ParseException("Error parsing JSON value as integer: not a double");
+ if (!source.isDouble()) throw ParseException("Error parsing JSON value as double: not a double");
return source.toDouble();
}
@@ -170,6 +176,18 @@ QJsonValue toJsonValue(const double &source, convertType) {
return QJsonValue(source);
}
+// Float
+template <>
+float fromJsonValue(const QJsonValue &source, convertType) {
+ if (!source.isDouble()) throw ParseException("Error parsing JSON value as float: not a double");
+ return static_cast(source.toDouble());
+}
+
+template <>
+QJsonValue toJsonValue(const float &source, convertType) {
+ return QJsonValue(static_cast(source));
+}
+
// QDateTime
template <>
QDateTime fromJsonValue(const QJsonValue &source, convertType) {
@@ -205,27 +223,37 @@ QJsonValue toJsonValue(const QUuid &source, convertType) {
// String types
template <>
-QString toString(const QUuid &source) {
+QString toString(const QUuid &source, convertType) {
return uuidToString(source);
}
template <>
-QString toString(const qint32 &source) {
+QString toString(const qint32 &source, convertType) {
return QString::number(source);
}
template <>
-QString toString(const qint64 &source) {
+QString toString(const qint64 &source, convertType) {
return QString::number(source);
}
template <>
-QString toString(const bool &source) {
+QString toString(const float &source, convertType) {
+ return QString::number(source);
+}
+
+template <>
+QString toString(const double &source, convertType) {
+ return QString::number(source);
+}
+
+template <>
+QString toString(const bool &source, convertType) {
return source ? QStringLiteral("true") : QStringLiteral("false");
}
template <>
-QString toString(const QString &source) {
+QString toString(const QString &source, convertType) {
return source;
}
diff --git a/core/src/viewmodel/item.cpp b/core/src/viewmodel/item.cpp
index 8da4aa4..b7a673c 100644
--- a/core/src/viewmodel/item.cpp
+++ b/core/src/viewmodel/item.cpp
@@ -21,11 +21,10 @@
namespace Jellyfin {
namespace ViewModel {
-Item::Item(QObject *parent)
- : Item(nullptr, parent){}
+Item::Item(QObject *parent, QSharedPointer data)
+ : QObject(parent), m_data(data){
-Item::Item(QSharedPointer data, QObject *parent)
- : QObject(parent), m_data(data){}
+}
void Item::setData(QSharedPointer newData) {
Model::Item oldData = *m_data.data();
@@ -36,7 +35,8 @@ void Item::setData(QSharedPointer newData) {
// ItemLoader
ItemLoader::ItemLoader(QObject *parent)
- : BaseClass(Jellyfin::Loader::HTTP::GetItemLoader(), parent) {
+ : BaseClass(new Jellyfin::Loader::HTTP::GetItemLoader(), parent) {
+ connect(this, &LoaderBase::apiClientChanged, this, &ItemLoader::onApiClientChanged);
}
void ItemLoader::onApiClientChanged(ApiClient *newApiClient) {
@@ -54,7 +54,9 @@ void ItemLoader::setUserId(const QString &newUserId) {
}
bool ItemLoader::canReload() const {
- return BaseClass::canReload() && !m_parameters.itemId().isEmpty();
+ return BaseClass::canReload()
+ && !m_parameters.itemId().isEmpty()
+ && !m_parameters.userId().isEmpty();
}
}
diff --git a/core/src/viewmodel/itemmodel.cpp b/core/src/viewmodel/itemmodel.cpp
index 7a5c84c..36d0ba1 100644
--- a/core/src/viewmodel/itemmodel.cpp
+++ b/core/src/viewmodel/itemmodel.cpp
@@ -18,6 +18,9 @@
*/
#include "JellyfinQt/viewmodel/itemmodel.h"
+#include "JellyfinQt/loader/http/getlatestmedia.h"
+#include "JellyfinQt/loader/http/getitemsbyuserid.h"
+
#define JF_CASE(roleName) case roleName: \
try { \
return QVariant(item.roleName()); \
@@ -30,37 +33,13 @@ namespace Jellyfin {
namespace ViewModel {
UserViewsLoader::UserViewsLoader(QObject *parent)
- : UserViewsLoaderBase(new Jellyfin::Loader::HTTP::GetUserViewsLoader(), parent) {
- connect(this, &BaseModelLoader::apiClientChanged, this, &UserViewsLoader::apiClientChanged);
-}
+ : UserViewsLoaderBase(new Jellyfin::Loader::HTTP::GetUserViewsLoader(), parent) { }
-void UserViewsLoader::apiClientChanged(ApiClient *newApiClient) {
- if (m_apiClient != nullptr) disconnect(m_apiClient, &ApiClient::userIdChanged, this, &UserViewsLoader::userIdChanged);
- if (newApiClient != nullptr) {
- connect(newApiClient, &ApiClient::userIdChanged, this, &UserViewsLoader::userIdChanged);
- if (!newApiClient->userId().isNull()) {
- m_parameters.setUserId(newApiClient->userId());
- }
- }
-}
+LatestMediaLoader::LatestMediaLoader(QObject *parent)
+ : LatestMediaBase(new Jellyfin::Loader::HTTP::GetLatestMediaLoader(), parent){ }
-void UserViewsLoader::userIdChanged(const QString &newUserId) {
- m_parameters.setUserId(newUserId);
- autoReloadIfNeeded();
-}
-void UserItemsLoader::apiClientChanged(ApiClient *newApiClient) {
- if (m_apiClient != nullptr) disconnect(m_apiClient, &ApiClient::userIdChanged, this, &UserItemsLoader::userIdChanged);
- if (newApiClient != nullptr) connect(newApiClient, &ApiClient::userIdChanged, this, &UserItemsLoader::userIdChanged);
-}
-
-void UserItemsLoader::userIdChanged(const QString &newUserId) {
- m_parameters.setUserId(newUserId);
- autoReloadIfNeeded();
-}
-
-bool UserItemsLoader::canReload() const {
- return BaseModelLoader::canReload() && !m_parameters.userId().isNull();
-}
+UserItemsLoader::UserItemsLoader(QObject *parent)
+ : UserItemsLoaderBase(new Jellyfin::Loader::HTTP::GetItemsByUserIdLoader(), parent) {}
ItemModel::ItemModel(QObject *parent)
: ApiModel(parent) { }
@@ -81,6 +60,8 @@ QVariant ItemModel::data(const QModelIndex &index, int role) const {
JF_CASE(dateCreated)
JF_CASE(dateLastMediaAdded)
JF_CASE(extraType)
+ // Handpicked, important ones
+ JF_CASE(imageTags)
default:
return QVariant();
}
diff --git a/qtquick/qml/main.qml b/qtquick/qml/main.qml
index 65d9116..88ee587 100644
--- a/qtquick/qml/main.qml
+++ b/qtquick/qml/main.qml
@@ -14,6 +14,11 @@ ApplicationWindow {
height: 600
visible: true
property int _oldDepth: 0
+ property alias playbackManager: playbackManager
+
+ J.PlaybackManager {
+ id: playbackManager
+ }
background: Background {
id: background
@@ -43,4 +48,20 @@ ApplicationWindow {
Component.onCompleted: {
ApiClient.restoreSavedSession()
}
+
+ footer: Column {
+ id: footer
+ Text {
+ text: qsTr("Now playing")
+ color: "white"
+ }
+ Text {
+ text: playbackManager.item.name ? playbackManager.item.name : "Nothing"
+ color: "white"
+ }
+ }
+ Rectangle {
+ color: "darkblue"
+ anchors.fill: footer
+ }
}
diff --git a/qtquick/qml/pages/DetailPage.qml b/qtquick/qml/pages/DetailPage.qml
index 91d2994..b7dc2ba 100644
--- a/qtquick/qml/pages/DetailPage.qml
+++ b/qtquick/qml/pages/DetailPage.qml
@@ -6,12 +6,14 @@ import nl.netsoj.chris.Jellyfin 1.0 as J
import "../components"
import "../.."
+import ".."
Page {
+ id: detailPage
property bool _modelsLoaded: false
property StackView stackView: StackView.view
property string itemId
- property alias jellyfinItem: jellyfinItem.data
+ property alias jellyfinItem: jellyfinItemLoader.data
header: ToolBar {
Label {
anchors.horizontalCenter: parent.horizontalCenter
@@ -25,13 +27,33 @@ Page {
onClicked: stackView.pop()
}
J.ItemLoader {
- id: jellyfinItem
- jellyfinId: itemId
+ id: jellyfinItemLoader
+ itemId: detailPage.itemId
apiClient: ApiClient
}
Image {
- anchors.centerIn: parent
+ anchors.top: parent.top
+ width: parent.width
+ height: parent.height / 3
source: ApiClient.baseUrl + "/Items/" + itemId + "/Images/Primary?tag=" + jellyfinItem.tag
}
+
+ ListView {
+ width: parent.width
+ height: parent.height / 3 * 2
+ anchors.bottom: parent.bottom
+ model: J.ItemModel {
+ loader: J.UserItemsLoader {
+ apiClient: ApiClient
+ parentId: detailPage.itemId
+ }
+ }
+ delegate: ItemDelegate{
+ icon.source: ApiClient.baseUrl + "/Items/" + model.jellyfinId + "/Images/Primary?tag=" + model.tag
+ text: model.name
+ width: parent.width
+ onClicked: playbackManager.play(model.jellyfinId)
+ }
+ }
}
diff --git a/qtquick/qml/pages/MainPage.qml b/qtquick/qml/pages/MainPage.qml
index c5b1ba8..2bff4eb 100644
--- a/qtquick/qml/pages/MainPage.qml
+++ b/qtquick/qml/pages/MainPage.qml
@@ -21,7 +21,7 @@ Page {
J.ItemModel {
id: mediaLibraryModel
- loader: J.UsersViewLoader {
+ loader: J.UsersViewsLoader {
id: mediaLibraryModelLoader
apiClient: ApiClient
}
@@ -30,6 +30,7 @@ Page {
ScrollView {
anchors.fill: parent
contentHeight: content.height
+ contentWidth: availableWidth
Column {
id: content
width: parent.width
@@ -37,12 +38,15 @@ Page {
model: mediaLibraryModel
Column {
width: parent.width
- /*J.UserItemLatestModel {
+ J.ItemModel{
id: userItemModel
- apiClient: ApiClient
- parentId: model.id
- limit: 16
- }*/
+ loader: J.LatestMediaLoader {
+ id: latestMediaLoader
+ apiClient: ApiClient
+ parentId: model.jellyfinId
+ //limit: 16
+ }
+ }
Label {
text: model.name ? model.name : ""
}
@@ -51,13 +55,14 @@ Page {
width: parent.width
height: SailfinStyle.unit * 20
orientation: ListView.Horizontal
- model: 10 // userItemModel
+ model: userItemModel
delegate: ItemDelegate {
width: 12 * SailfinStyle.unit
height: 20 * SailfinStyle.unit
Image {
anchors.fill: parent
- source: ApiClient.baseUrl + "/Items/" + model.id + "/Images/Primary?tag=" + model.tag
+ source: ApiClient.baseUrl + "/Items/" + model.jellyfinId
+ + "/Images/Primary?tag=" + model.imageTags["Primary"] //model.tag
}
Label {
anchors.left: parent.left
@@ -65,14 +70,18 @@ Page {
anchors.right: parent.right
text: model.name
}
- onClicked: stackView.push(Qt.resolvedUrl("DetailPage.qml"), {"itemId": model.id})
+ onClicked: stackView.push(Qt.resolvedUrl(
+ "DetailPage.qml"), {
+ "itemId": model.jellyfinId
+ })
}
}
Connections {
target: mediaLibraryModelLoader
onReady: {
if (mediaLibraryModelLoader.status === ModelStatus.Ready) {
- //userItemModel.reload()
+
+ latestMediaLoader.reload()
}
}
}
@@ -81,13 +90,14 @@ Page {
}
}
+
/**
* Loads models if not laoded. Set force to true to reload models
* even if loaded.
*/
function loadModels(force) {
if (force || (ApiClient.authenticated && !_modelsLoaded)) {
- _modelsLoaded = true;
+ _modelsLoaded = true
mediaLibraryModel.reload()
}
}