mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2024-11-22 09:15:18 +00:00
Rewire more of Sailfish frontend into new backend
This should encompass most simple things, besides some larger, trickier things, like the video streams and the now-broken userdata
This commit is contained in:
parent
df1e134821
commit
7b6c272aa9
|
@ -19,6 +19,7 @@ set(JellyfinQt_SOURCES
|
||||||
src/viewmodel/modelstatus.cpp
|
src/viewmodel/modelstatus.cpp
|
||||||
src/viewmodel/playbackmanager.cpp
|
src/viewmodel/playbackmanager.cpp
|
||||||
src/viewmodel/playlist.cpp
|
src/viewmodel/playlist.cpp
|
||||||
|
src/viewmodel/userdata.cpp
|
||||||
src/apiclient.cpp
|
src/apiclient.cpp
|
||||||
src/apimodel.cpp
|
src/apimodel.cpp
|
||||||
src/credentialmanager.cpp
|
src/credentialmanager.cpp
|
||||||
|
@ -46,6 +47,7 @@ set(JellyfinQt_HEADERS
|
||||||
include/JellyfinQt/viewmodel/propertyhelper.h
|
include/JellyfinQt/viewmodel/propertyhelper.h
|
||||||
include/JellyfinQt/viewmodel/playbackmanager.h
|
include/JellyfinQt/viewmodel/playbackmanager.h
|
||||||
include/JellyfinQt/viewmodel/playlist.h
|
include/JellyfinQt/viewmodel/playlist.h
|
||||||
|
include/JellyfinQt/viewmodel/userdata.h
|
||||||
include/JellyfinQt/apiclient.h
|
include/JellyfinQt/apiclient.h
|
||||||
include/JellyfinQt/apimodel.h
|
include/JellyfinQt/apimodel.h
|
||||||
include/JellyfinQt/credentialmanager.h
|
include/JellyfinQt/credentialmanager.h
|
||||||
|
|
|
@ -124,7 +124,7 @@ public:
|
||||||
Q_ENUM(ApiError)
|
Q_ENUM(ApiError)
|
||||||
|
|
||||||
const QString &baseUrl() const { return this->m_baseUrl; }
|
const QString &baseUrl() const { return this->m_baseUrl; }
|
||||||
const QString &userId() const { return m_userId; }
|
const QString userId() const { return m_userId; }
|
||||||
const QString &deviceId() const { return m_deviceId; }
|
const QString &deviceId() const { return m_deviceId; }
|
||||||
/**
|
/**
|
||||||
* @brief QML applications can set this type to indicate which commands they support.
|
* @brief QML applications can set this type to indicate which commands they support.
|
||||||
|
@ -275,7 +275,7 @@ private:
|
||||||
QString m_token;
|
QString m_token;
|
||||||
QString m_deviceName;
|
QString m_deviceName;
|
||||||
QString m_deviceId;
|
QString m_deviceId;
|
||||||
QString m_userId = "";
|
QString m_userId;
|
||||||
QJsonObject m_deviceProfile;
|
QJsonObject m_deviceProfile;
|
||||||
QJsonObject m_playbackDeviceProfile;
|
QJsonObject m_playbackDeviceProfile;
|
||||||
bool m_online = true;
|
bool m_online = true;
|
||||||
|
|
|
@ -188,7 +188,7 @@ public:
|
||||||
* @return pair containing the items loaded and the integer containing the starting offset. A starting
|
* @return pair containing the items loaded and the integer containing the starting offset. A starting
|
||||||
* offset of -1 means an error has occurred.
|
* offset of -1 means an error has occurred.
|
||||||
*/
|
*/
|
||||||
std::pair<QList<T>, int> &&result() { return std::move(m_result); }
|
std::pair<QList<T*>, int> &&result() { return std::move(m_result); }
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* @brief Loads data from the given offset with a maximum count of limit.
|
* @brief Loads data from the given offset with a maximum count of limit.
|
||||||
|
@ -205,7 +205,7 @@ protected:
|
||||||
m_startIndex = startIndex;
|
m_startIndex = startIndex;
|
||||||
m_totalRecordCount = totalRecordCount;
|
m_totalRecordCount = totalRecordCount;
|
||||||
}
|
}
|
||||||
std::pair<QList<T>, int> m_result;
|
std::pair<QList<T*>, int> m_result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -275,7 +275,7 @@ protected:
|
||||||
// If futureWatcher's future is running, this method should not be called again.
|
// If futureWatcher's future is running, this method should not be called again.
|
||||||
if (m_futureWatcher.isRunning()) return;
|
if (m_futureWatcher.isRunning()) return;
|
||||||
// Set an invalid result.
|
// Set an invalid result.
|
||||||
this->m_result = { QList<T>(), -1 };
|
this->m_result = { QList<T*>(), -1 };
|
||||||
|
|
||||||
if (!setRequestStartIndex<P>(this->m_parameters, offset)
|
if (!setRequestStartIndex<P>(this->m_parameters, offset)
|
||||||
&& suggestedModelStatus == ViewModel::ModelStatus::LoadingMore) {
|
&& suggestedModelStatus == ViewModel::ModelStatus::LoadingMore) {
|
||||||
|
@ -326,12 +326,12 @@ protected:
|
||||||
if (totalRecordCount < 0) {
|
if (totalRecordCount < 0) {
|
||||||
totalRecordCount = records.size();
|
totalRecordCount = records.size();
|
||||||
}
|
}
|
||||||
QList<T> models;
|
QList<T*> models;
|
||||||
models.reserve(records.size());
|
models.reserve(records.size());
|
||||||
|
|
||||||
// Convert the DTOs into models
|
// Convert the DTOs into models
|
||||||
for (int i = 0; i < records.size(); i++) {
|
for (int i = 0; i < records.size(); i++) {
|
||||||
models.append(T(records[i], m_loader->apiClient()));
|
models.append(new T(records[i], m_loader->apiClient()));
|
||||||
}
|
}
|
||||||
this->setStatus(ViewModel::ModelStatus::Ready);
|
this->setStatus(ViewModel::ModelStatus::Ready);
|
||||||
this->m_result = { models, this->m_startIndex};
|
this->m_result = { models, this->m_startIndex};
|
||||||
|
@ -441,7 +441,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// QList-like API
|
// QList-like API
|
||||||
const T& at(int index) const { return m_array.at(index); }
|
QSharedPointer<T> at(int index) const { return m_array.at(index); }
|
||||||
/**
|
/**
|
||||||
* @return the amount of objects in this model.
|
* @return the amount of objects in this model.
|
||||||
*/
|
*/
|
||||||
|
@ -449,22 +449,23 @@ public:
|
||||||
return m_array.size();
|
return m_array.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void insert(int index, T &object) {
|
void insert(int index, QSharedPointer<T> object) {
|
||||||
Q_ASSERT(index >= 0 && index <= size());
|
Q_ASSERT(index >= 0 && index <= size());
|
||||||
this->beginInsertRows(QModelIndex(), index, index);
|
this->beginInsertRows(QModelIndex(), index, index);
|
||||||
m_array.insert(index, object);
|
m_array.insert(index, object);
|
||||||
this->endInsertRows();
|
this->endInsertRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
void append(T &object) { insert(size(), object); }
|
void append(QSharedPointer<T> object) { insert(size(), object); }
|
||||||
void append(QList<T> &objects) {
|
//void append(T &object) { insert(size(), object); }
|
||||||
|
void append(QList<QSharedPointer<T>> &objects) {
|
||||||
int index = size();
|
int index = size();
|
||||||
this->beginInsertRows(QModelIndex(), index, index + objects.size() - 1);
|
this->beginInsertRows(QModelIndex(), index, index + objects.size() - 1);
|
||||||
m_array.append(objects);
|
m_array.append(objects);
|
||||||
this->endInsertRows();
|
this->endInsertRows();
|
||||||
};
|
};
|
||||||
|
|
||||||
QList<T> mid(int pos, int length = -1) {
|
QList<T*> mid(int pos, int length = -1) {
|
||||||
return m_array.mid(pos, length);
|
return m_array.mid(pos, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -483,7 +484,7 @@ public:
|
||||||
this->endRemoveRows();
|
this->endRemoveRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeOne(T &object) {
|
void removeOne(QSharedPointer<T> object) {
|
||||||
int idx = m_array.indexOf(object);
|
int idx = m_array.indexOf(object);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
removeAt(idx);
|
removeAt(idx);
|
||||||
|
@ -535,20 +536,26 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Model-specific properties.
|
// Model-specific properties.
|
||||||
QList<T> m_array;
|
QList<QSharedPointer<T>> m_array;
|
||||||
ModelLoader<T> *m_loader = nullptr;
|
ModelLoader<T> *m_loader = nullptr;
|
||||||
|
|
||||||
void loadingFinished() override {
|
void loadingFinished() override {
|
||||||
Q_ASSERT(m_loader != nullptr);
|
Q_ASSERT(m_loader != nullptr);
|
||||||
std::pair<QList<T>, int> result = m_loader->result();
|
std::pair<QList<T*>, int> result = m_loader->result();
|
||||||
qDebug() << "Results loaded: index: " << result.second << ", count: " << result.first.size();
|
qDebug() << "Results loaded: index: " << result.second << ", count: " << result.first.size();
|
||||||
if (result.second == -1) {
|
if (result.second == -1) {
|
||||||
clear();
|
clear();
|
||||||
} else if (result.second == m_array.size()) {
|
} else if (result.second == m_array.size()) {
|
||||||
append(result.first);
|
m_array.reserve(m_array.size() + result.second);
|
||||||
|
for (auto it = result.first.begin(); it != result.first.end(); it++) {
|
||||||
|
append(QSharedPointer<T>(*it));
|
||||||
|
}
|
||||||
} else if (result.second < m_array.size()){
|
} else if (result.second < m_array.size()){
|
||||||
removeUntilEnd(result.second);
|
removeUntilEnd(result.second);
|
||||||
append(result.first);
|
m_array.reserve(m_array.size() + result.second);
|
||||||
|
for (auto it = result.first.begin(); it != result.first.end(); it++) {
|
||||||
|
append(QSharedPointer<T>(*it));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// result.second > m_array.size()
|
// result.second > m_array.size()
|
||||||
qWarning() << "Retrieved data from loader at position bigger than size()";
|
qWarning() << "Retrieved data from loader at position bigger than size()";
|
||||||
|
|
|
@ -123,7 +123,6 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString urlToGroupName(const QString &url) const;
|
QString urlToGroupName(const QString &url) const;
|
||||||
QString groupNameToUrl(const QString &group) const;
|
|
||||||
QSettings m_settings;
|
QSettings m_settings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,11 @@
|
||||||
#ifndef JELLYFIN_MODEL_ITEM
|
#ifndef JELLYFIN_MODEL_ITEM
|
||||||
#define JELLYFIN_MODEL_ITEM
|
#define JELLYFIN_MODEL_ITEM
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QThreadPool>
|
|
||||||
#include <QtConcurrent/QtConcurrent>
|
|
||||||
|
|
||||||
#include "../dto/baseitemdto.h"
|
#include "../dto/baseitemdto.h"
|
||||||
#include "../support/loader.h"
|
#include "../support/loader.h"
|
||||||
|
@ -31,20 +33,22 @@
|
||||||
namespace Jellyfin {
|
namespace Jellyfin {
|
||||||
namespace Model {
|
namespace Model {
|
||||||
|
|
||||||
class Item : public DTO::BaseItemDto {
|
class Item : public QObject, public DTO::BaseItemDto {
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
using UserDataChangedCallback = std::function<void(DTO::UserItemDataDto)>;
|
||||||
/**
|
/**
|
||||||
* @brief Constructor that creates an empty item.
|
* @brief Constructor that creates an empty item.
|
||||||
*/
|
*/
|
||||||
Item();
|
Item(QObject *parent = nullptr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Copies the data from the DTO into this model and attaches an ApiClient
|
* @brief Copies the data from the DTO into this model and attaches an ApiClient
|
||||||
* @param data The DTO to copy information from
|
* @param data The DTO to copy information from
|
||||||
* @param apiClient The ApiClient to attach to, to listen for updates and so on.
|
* @param apiClient The ApiClient to attach to, to listen for updates and so on.
|
||||||
*/
|
*/
|
||||||
Item(const DTO::BaseItemDto &data, ApiClient *apiClient = nullptr);
|
Item(const DTO::BaseItemDto &data, ApiClient *apiClient = nullptr, QObject *parent = nullptr);
|
||||||
virtual ~Item();
|
//virtual ~Item();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief sameAs Returns true if this item represents the same item as `other`
|
* @brief sameAs Returns true if this item represents the same item as `other`
|
||||||
|
@ -58,11 +62,13 @@ public:
|
||||||
bool sameAs(const DTO::BaseItemDto &other);
|
bool sameAs(const DTO::BaseItemDto &other);
|
||||||
|
|
||||||
void setApiClient(ApiClient *apiClient);
|
void setApiClient(ApiClient *apiClient);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void userDataChanged(const DTO::UserItemDataDto &newUserData);
|
||||||
private:
|
private:
|
||||||
ApiClient *m_apiClient = nullptr;
|
ApiClient *m_apiClient = nullptr;
|
||||||
QList<QMetaObject::Connection> m_apiClientConnections;
|
void updateUserData(const QString &itemId, const DTO::UserItemDataDto &userData);
|
||||||
|
|
||||||
void onUserDataUpdated(const QString &itemId, const DTO::UserItemDataDto &userData);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief Appends all items from the given itemModel to this list
|
* @brief Appends all items from the given itemModel to this list
|
||||||
*/
|
*/
|
||||||
void appendToList(const ViewModel::ItemModel &model);
|
void appendToList(ViewModel::ItemModel &model);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Start playing this playlist
|
* @brief Start playing this playlist
|
||||||
|
|
|
@ -46,15 +46,22 @@
|
||||||
#include "loader.h"
|
#include "loader.h"
|
||||||
|
|
||||||
namespace Jellyfin {
|
namespace Jellyfin {
|
||||||
|
|
||||||
|
namespace DTO {
|
||||||
|
class UserItemDataDto;
|
||||||
|
} // NS DTO
|
||||||
|
|
||||||
namespace ViewModel {
|
namespace ViewModel {
|
||||||
|
|
||||||
|
class UserData;
|
||||||
|
|
||||||
class Item : public QObject {
|
class Item : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit Item(QObject *parent = nullptr, QSharedPointer<Model::Item> data = QSharedPointer<Model::Item>::create());
|
explicit Item(QObject *parent = nullptr, QSharedPointer<Model::Item> data = QSharedPointer<Model::Item>::create());
|
||||||
|
|
||||||
// Please keep the order of the properties the same as in the file linked above.
|
// Please keep the order of the properties the same as in the file linked above.
|
||||||
Q_PROPERTY(QUuid jellyfinId READ jellyfinId NOTIFY jellyfinIdChanged)
|
Q_PROPERTY(QString jellyfinId READ jellyfinId NOTIFY jellyfinIdChanged)
|
||||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||||
Q_PROPERTY(QString originalTitle READ originalTitle NOTIFY originalTitleChanged)
|
Q_PROPERTY(QString originalTitle READ originalTitle NOTIFY originalTitleChanged)
|
||||||
Q_PROPERTY(QString serverId READ serverId NOTIFY serverIdChanged)
|
Q_PROPERTY(QString serverId READ serverId NOTIFY serverIdChanged)
|
||||||
|
@ -81,27 +88,29 @@ public:
|
||||||
//SKIP: ExternalUrls
|
//SKIP: ExternalUrls
|
||||||
//SKIP: MediaSources
|
//SKIP: MediaSources
|
||||||
Q_PROPERTY(float criticRating READ criticRating NOTIFY criticRatingChanged)
|
Q_PROPERTY(float criticRating READ criticRating NOTIFY criticRatingChanged)
|
||||||
Q_PROPERTY(QStringList productionLocations READ productionLocations NOTIFY productionLocationsChanged)
|
Q_PROPERTY(QStringList productionLocations READ productionLocations NOTIFY productionLocationsChanged)*/
|
||||||
|
|
||||||
// Handpicked, important ones
|
// Handpicked, important ones
|
||||||
Q_PROPERTY(qint64 runTimeTicks READ runTimeTicks NOTIFY runTimeTicksChanged)*/
|
Q_PROPERTY(qint64 runTimeTicks READ runTimeTicks NOTIFY runTimeTicksChanged)
|
||||||
Q_PROPERTY(QString overview READ overview NOTIFY overviewChanged)
|
Q_PROPERTY(QString overview READ overview NOTIFY overviewChanged)
|
||||||
Q_PROPERTY(int productionYear READ productionYear NOTIFY productionYearChanged)
|
Q_PROPERTY(int productionYear READ productionYear NOTIFY productionYearChanged)
|
||||||
Q_PROPERTY(int indexNumber READ indexNumber NOTIFY indexNumberChanged)
|
Q_PROPERTY(int indexNumber READ indexNumber NOTIFY indexNumberChanged)
|
||||||
Q_PROPERTY(int indexNumberEnd READ indexNumberEnd NOTIFY indexNumberEndChanged)
|
Q_PROPERTY(int indexNumberEnd READ indexNumberEnd NOTIFY indexNumberEndChanged)
|
||||||
Q_PROPERTY(bool isFolder READ isFolder NOTIFY isFolderChanged)
|
Q_PROPERTY(bool isFolder READ isFolder NOTIFY isFolderChanged)
|
||||||
Q_PROPERTY(QString type READ type NOTIFY typeChanged)
|
Q_PROPERTY(QString type READ type NOTIFY typeChanged)
|
||||||
/*Q_PROPERTY(QString parentBackdropItemId READ parentBackdropItemId NOTIFY parentBackdropItemIdChanged)
|
Q_PROPERTY(QString parentBackdropItemId READ parentBackdropItemId NOTIFY parentBackdropItemIdChanged)
|
||||||
Q_PROPERTY(QStringList parentBackdropImageTags READ parentBackdropImageTags NOTIFY parentBackdropImageTagsChanged)
|
Q_PROPERTY(QStringList parentBackdropImageTags READ parentBackdropImageTags NOTIFY parentBackdropImageTagsChanged)
|
||||||
Q_PROPERTY(UserData *userData READ userData NOTIFY userDataChanged)
|
Q_PROPERTY(UserData *userData READ userData NOTIFY userDataChanged)
|
||||||
Q_PROPERTY(int recursiveItemCount READ recursiveItemCount NOTIFY recursiveItemCountChanged)
|
Q_PROPERTY(int recursiveItemCount READ recursiveItemCount NOTIFY recursiveItemCountChanged)
|
||||||
Q_PROPERTY(int childCount READ childCount NOTIFY childCountChanged)
|
Q_PROPERTY(int childCount READ childCount NOTIFY childCountChanged)
|
||||||
Q_PROPERTY(QString albumArtist READ albumArtist NOTIFY albumArtistChanged)
|
Q_PROPERTY(QString albumArtist READ albumArtist NOTIFY albumArtistChanged)
|
||||||
Q_PROPERTY(QVariantList albumArtists READ albumArtists NOTIFY albumArtistsChanged)
|
/*Q_PROPERTY(QVariantList albumArtists READ albumArtists NOTIFY albumArtistsChanged)*/
|
||||||
Q_PROPERTY(QString seriesName READ seriesName NOTIFY seriesNameChanged)
|
Q_PROPERTY(QString seriesName READ seriesName NOTIFY seriesNameChanged)
|
||||||
|
Q_PROPERTY(QString seriesId READ seriesId NOTIFY seriesIdChanged)
|
||||||
|
Q_PROPERTY(QString seasonId READ seasonId NOTIFY seasonIdChanged)
|
||||||
Q_PROPERTY(QString seasonName READ seasonName NOTIFY seasonNameChanged)
|
Q_PROPERTY(QString seasonName READ seasonName NOTIFY seasonNameChanged)
|
||||||
Q_PROPERTY(QList<MediaStream *> __list__mediaStreams MEMBER __list__m_mediaStreams NOTIFY mediaStreamsChanged)
|
/*Q_PROPERTY(QList<MediaStream *> __list__mediaStreams MEMBER __list__m_mediaStreams NOTIFY mediaStreamsChanged)
|
||||||
Q_PROPERTY(QVariantList mediaStreams READ mediaStreams NOTIFY mediaStreamsChanged STORED false)
|
Q_PROPERTY(QVariantList mediaStreams READ mediaStreams NOTIFY mediaStreamsChanged STORED false)*/
|
||||||
Q_PROPERTY(QStringList artists READ artists NOTIFY artistsChanged)
|
Q_PROPERTY(QStringList artists READ artists NOTIFY artistsChanged)
|
||||||
// Why is this a QJsonObject? Well, because I couldn't be bothered to implement the deserialisations of
|
// Why is this a QJsonObject? Well, because I couldn't be bothered to implement the deserialisations of
|
||||||
// a QHash at the moment.
|
// a QHash at the moment.
|
||||||
|
@ -110,9 +119,9 @@ public:
|
||||||
Q_PROPERTY(QJsonObject imageBlurHashes READ imageBlurHashes NOTIFY imageBlurHashesChanged)
|
Q_PROPERTY(QJsonObject imageBlurHashes READ imageBlurHashes NOTIFY imageBlurHashesChanged)
|
||||||
Q_PROPERTY(QString mediaType READ mediaType READ mediaType NOTIFY mediaTypeChanged)
|
Q_PROPERTY(QString mediaType READ mediaType READ mediaType NOTIFY mediaTypeChanged)
|
||||||
Q_PROPERTY(int width READ width NOTIFY widthChanged)
|
Q_PROPERTY(int width READ width NOTIFY widthChanged)
|
||||||
Q_PROPERTY(int height READ height NOTIFY heightChanged)*/
|
Q_PROPERTY(int height READ height NOTIFY heightChanged)
|
||||||
|
|
||||||
QUuid jellyfinId() const { return m_data->jellyfinId(); }
|
QString jellyfinId() const { return m_data->jellyfinId(); }
|
||||||
QString name() const { return m_data->name(); }
|
QString name() const { return m_data->name(); }
|
||||||
QString originalTitle() const { return m_data->originalTitle(); }
|
QString originalTitle() const { return m_data->originalTitle(); }
|
||||||
QString serverId() const { return m_data->serverId(); }
|
QString serverId() const { return m_data->serverId(); }
|
||||||
|
@ -125,17 +134,36 @@ public:
|
||||||
int airsBeforeSeasonNumber() const { return m_data->airsBeforeSeasonNumber().value_or(0); }
|
int airsBeforeSeasonNumber() const { return m_data->airsBeforeSeasonNumber().value_or(0); }
|
||||||
int airsAfterSeasonNumber() const { return m_data->airsAfterSeasonNumber().value_or(999); }
|
int airsAfterSeasonNumber() const { return m_data->airsAfterSeasonNumber().value_or(999); }
|
||||||
int airsBeforeEpisodeNumber() const { return m_data->airsBeforeEpisodeNumber().value_or(0); }
|
int airsBeforeEpisodeNumber() const { return m_data->airsBeforeEpisodeNumber().value_or(0); }
|
||||||
|
qint64 runTimeTicks() const { return m_data->runTimeTicks().value_or(0); }
|
||||||
QString overview() const { return m_data->overview(); }
|
QString overview() const { return m_data->overview(); }
|
||||||
int productionYear() const { return m_data->productionYear().value_or(0); }
|
int productionYear() const { return m_data->productionYear().value_or(0); }
|
||||||
int indexNumber() const { return m_data->indexNumber().value_or(-1); }
|
int indexNumber() const { return m_data->indexNumber().value_or(-1); }
|
||||||
int indexNumberEnd() const { return m_data->indexNumberEnd().value_or(-1); }
|
int indexNumberEnd() const { return m_data->indexNumberEnd().value_or(-1); }
|
||||||
bool isFolder() const { return m_data->isFolder().value_or(false); }
|
bool isFolder() const { return m_data->isFolder().value_or(false); }
|
||||||
QString type() const { return m_data->type(); }
|
QString type() const { return m_data->type(); }
|
||||||
|
QString parentBackdropItemId() const { return m_data->parentBackdropItemId(); }
|
||||||
|
QStringList parentBackdropImageTags() const { return m_data->parentBackdropImageTags(); }
|
||||||
|
UserData *userData() const { return m_userData; }
|
||||||
|
int recursiveItemCount() const { return m_data->recursiveItemCount().value_or(0); }
|
||||||
|
int childCount() const { return m_data->childCount().value_or(0); }
|
||||||
|
QString albumArtist() const { return m_data->albumArtist(); }
|
||||||
|
QString seriesName() const { return m_data->seriesName(); }
|
||||||
|
QString seriesId() const { return m_data->seriesId(); }
|
||||||
|
QString seasonId() const { return m_data->seasonId(); }
|
||||||
|
QString seasonName() const { return m_data->seasonName(); }
|
||||||
|
|
||||||
|
QStringList artists() const { return m_data->artists(); }
|
||||||
|
QJsonObject imageTags() const { return m_data->imageTags(); }
|
||||||
|
QStringList backdropImageTags() const { return m_data->backdropImageTags(); }
|
||||||
|
QJsonObject imageBlurHashes() const { return m_data->imageBlurHashes(); }
|
||||||
|
QString mediaType() const { return m_data->mediaType(); }
|
||||||
|
int width() const { return m_data->width().value_or(0); }
|
||||||
|
int height() const { return m_data->height().value_or(0); }
|
||||||
|
|
||||||
QSharedPointer<Model::Item> data() const { return m_data; }
|
QSharedPointer<Model::Item> data() const { return m_data; }
|
||||||
void setData(QSharedPointer<Model::Item> newData);
|
void setData(QSharedPointer<Model::Item> newData);
|
||||||
signals:
|
signals:
|
||||||
void jellyfinIdChanged(const QUuid &newId);
|
void jellyfinIdChanged(const QString &newId);
|
||||||
void nameChanged(const QString &newName);
|
void nameChanged(const QString &newName);
|
||||||
void originalTitleChanged(const QString &newOriginalTitle);
|
void originalTitleChanged(const QString &newOriginalTitle);
|
||||||
void serverIdChanged(const QString &newServerId);
|
void serverIdChanged(const QString &newServerId);
|
||||||
|
@ -171,12 +199,14 @@ signals:
|
||||||
void typeChanged(const QString &newType);
|
void typeChanged(const QString &newType);
|
||||||
void parentBackdropItemIdChanged();
|
void parentBackdropItemIdChanged();
|
||||||
void parentBackdropImageTagsChanged();
|
void parentBackdropImageTagsChanged();
|
||||||
//void userDataChanged(UserData *newUserData);
|
void userDataChanged(UserData *newUserData);
|
||||||
void recursiveItemCountChanged(int newRecursiveItemCount);
|
void recursiveItemCountChanged(int newRecursiveItemCount);
|
||||||
void childCountChanged(int newChildCount);
|
void childCountChanged(int newChildCount);
|
||||||
void albumArtistChanged(const QString &newAlbumArtist);
|
void albumArtistChanged(const QString &newAlbumArtist);
|
||||||
//void albumArtistsChanged(NameGuidPair *newAlbumArtists);
|
//void albumArtistsChanged(NameGuidPair *newAlbumArtists);
|
||||||
void seriesNameChanged(const QString &newSeriesName);
|
void seriesNameChanged(const QString &newSeriesName);
|
||||||
|
void seriesIdChanged(const QString &newSeriesId);
|
||||||
|
void seasonIdChanged(const QString &newSeasonId);
|
||||||
void seasonNameChanged(const QString &newSeasonName);
|
void seasonNameChanged(const QString &newSeasonName);
|
||||||
void mediaStreamsChanged(/*const QList<MediaStream *> &newMediaStreams*/);
|
void mediaStreamsChanged(/*const QList<MediaStream *> &newMediaStreams*/);
|
||||||
void artistsChanged(const QStringList &newArtists);
|
void artistsChanged(const QStringList &newArtists);
|
||||||
|
@ -187,7 +217,13 @@ signals:
|
||||||
void widthChanged(int newWidth);
|
void widthChanged(int newWidth);
|
||||||
void heightChanged(int newHeight);
|
void heightChanged(int newHeight);
|
||||||
protected:
|
protected:
|
||||||
|
void setUserData(DTO::UserItemDataDto &newData);
|
||||||
|
void setUserData(QSharedPointer<DTO::UserItemDataDto> newData);
|
||||||
|
|
||||||
QSharedPointer<Model::Item> m_data;
|
QSharedPointer<Model::Item> m_data;
|
||||||
|
UserData *m_userData = nullptr;
|
||||||
|
private slots:
|
||||||
|
void onUserDataChanged(const DTO::UserItemDataDto &userData);
|
||||||
};
|
};
|
||||||
|
|
||||||
class ItemLoader : public Loader<ViewModel::Item, DTO::BaseItemDto, Jellyfin::Loader::GetItemParams> {
|
class ItemLoader : public Loader<ViewModel::Item, DTO::BaseItemDto, Jellyfin::Loader::GetItemParams> {
|
||||||
|
|
|
@ -134,12 +134,108 @@ public:
|
||||||
FWDPROP(QList<Jellyfin::DTO::LocationTypeClass::Value>, excludeLocationTypes, ExcludeLocationTypes)
|
FWDPROP(QList<Jellyfin::DTO::LocationTypeClass::Value>, excludeLocationTypes, ExcludeLocationTypes)
|
||||||
FWDPROP(QList<Jellyfin::DTO::ItemFieldsClass::Value>, fields, Fields)
|
FWDPROP(QList<Jellyfin::DTO::ItemFieldsClass::Value>, fields, Fields)
|
||||||
FWDPROP(QList<Jellyfin::DTO::ItemFilterClass::Value>, filters, Filters)
|
FWDPROP(QList<Jellyfin::DTO::ItemFilterClass::Value>, filters, Filters)
|
||||||
|
FWDPROP(QStringList, genreIds, GenreIds)
|
||||||
|
FWDPROP(QStringList, genres, Genres)
|
||||||
|
FWDPROP(bool, hasImdbId, HasImdbId)
|
||||||
|
FWDPROP(bool, hasOfficialRating, HasOfficialRating)
|
||||||
|
FWDPROP(bool, hasOverview, HasOverview)
|
||||||
|
FWDPROP(bool, hasParentalRating, HasParentalRating)
|
||||||
|
FWDPROP(bool, hasSpecialFeature, HasSpecialFeature)
|
||||||
|
FWDPROP(bool, hasSubtitles, HasSubtitles)
|
||||||
|
FWDPROP(bool, hasThemeSong, HasThemeSong)
|
||||||
|
FWDPROP(bool, hasThemeVideo, HasThemeVideo)
|
||||||
|
FWDPROP(bool, hasTmdbId, HasTmdbId)
|
||||||
|
FWDPROP(bool, hasTrailer, HasTrailer)
|
||||||
|
FWDPROP(bool, hasTvdbId, HasTvdbId)
|
||||||
|
FWDPROP(QStringList, ids, Ids)
|
||||||
|
FWDPROP(qint32, imageTypeLimit, ImageTypeLimit)
|
||||||
|
FWDPROP(QList<Jellyfin::DTO::ImageTypeClass::Value>, imageTypes, ImageTypes)
|
||||||
|
FWDPROP(QStringList, includeItemTypes, IncludeItemTypes)
|
||||||
|
FWDPROP(bool, is3D, Is3D)
|
||||||
|
FWDPROP(bool, is4K, Is4K)
|
||||||
|
FWDPROP(bool, isFavorite, IsFavorite)
|
||||||
|
FWDPROP(bool, isHd, IsHd)
|
||||||
|
FWDPROP(bool, isLocked, IsLocked)
|
||||||
|
FWDPROP(bool, isMissing, IsMissing)
|
||||||
|
FWDPROP(bool, isPlaceHolder, IsPlaceHolder)
|
||||||
|
FWDPROP(bool, isPlayed, IsPlayed)
|
||||||
|
FWDPROP(bool, isUnaired, IsUnaired)
|
||||||
|
FWDPROP(QList<Jellyfin::DTO::LocationTypeClass::Value>, locationTypes, LocationTypes)
|
||||||
|
FWDPROP(qint32, maxHeight, MaxHeight)
|
||||||
|
FWDPROP(QString, maxOfficialRating, MaxOfficialRating)
|
||||||
|
FWDPROP(QDateTime, maxPremiereDate, MaxPremiereDate)
|
||||||
|
FWDPROP(qint32, maxWidth, MaxWidth)
|
||||||
|
FWDPROP(QStringList, mediaTypes, MediaTypes)
|
||||||
|
FWDPROP(qint32, minHeight, MinHeight)
|
||||||
|
FWDPROP(QString, minOfficialRating, MinOfficialRating)
|
||||||
|
FWDPROP(QDateTime, minPremiereDate, MinPremiereDate)
|
||||||
|
FWDPROP(qint32, minWidth, MinWidth)
|
||||||
|
FWDPROP(QString, sortBy, SortBy)
|
||||||
|
FWDPROP(QString, sortOrder, SortOrder)
|
||||||
|
FWDPROP(QStringList, tags, Tags)
|
||||||
|
FWDPROP(QList<qint32>, years, Years)
|
||||||
|
|
||||||
FWDPROP(QString, parentId, ParentId)
|
FWDPROP(QString, parentId, ParentId)
|
||||||
FWDPROP(bool, recursive, Recursive)
|
FWDPROP(bool, recursive, Recursive)
|
||||||
|
FWDPROP(QString, searchTerm, SearchTerm)
|
||||||
//FWDPROP(bool, collapseBoxSetItems)
|
//FWDPROP(bool, collapseBoxSetItems)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using ResumeItemsLoaderBase = AbstractUserParameterLoader<Model::Item, DTO::BaseItemDto, DTO::BaseItemDtoQueryResult, Jellyfin::Loader::GetResumeItemsParams>;
|
||||||
|
class ResumeItemsLoader : public ResumeItemsLoaderBase {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ResumeItemsLoader(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
FWDPROP(QList<Jellyfin::DTO::ImageTypeClass::Value>, enableImageTypes, EnableImageTypes);
|
||||||
|
FWDPROP(bool, enableImages, EnableImages)
|
||||||
|
FWDPROP(bool, enableTotalRecordCount, EnableTotalRecordCount)
|
||||||
|
FWDPROP(bool, enableUserData, EnableUserData)
|
||||||
|
FWDPROP(QStringList, excludeItemTypes, ExcludeItemTypes)
|
||||||
|
FWDPROP(QList<Jellyfin::DTO::ItemFieldsClass::Value>, fields, Fields)
|
||||||
|
FWDPROP(qint32, imageTypeLimit, ImageTypeLimit)
|
||||||
|
FWDPROP(QStringList, includeItemTypes, IncludeItemTypes)
|
||||||
|
FWDPROP(QStringList, mediaTypes, MediaTypes)
|
||||||
|
FWDPROP(QString, parentId, ParentId)
|
||||||
|
FWDPROP(QString, searchTerm, SearchTerm)
|
||||||
|
};
|
||||||
|
|
||||||
|
using ShowSeasonsLoaderBase = AbstractUserParameterLoader<Model::Item, DTO::BaseItemDto, DTO::BaseItemDtoQueryResult, Jellyfin::Loader::GetSeasonsParams>;
|
||||||
|
class ShowSeasonsLoader : public ShowSeasonsLoaderBase {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ShowSeasonsLoader(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
FWDPROP(QString, seriesId, SeriesId)
|
||||||
|
FWDPROP(QString, adjacentTo, AdjacentTo)
|
||||||
|
FWDPROP(QList<Jellyfin::DTO::ImageTypeClass::Value>, enableImageTypes, EnableImageTypes)
|
||||||
|
FWDPROP(bool, enableImages, EnableImages)
|
||||||
|
FWDPROP(bool, enableUserData, EnableUserData)
|
||||||
|
FWDPROP(QList<Jellyfin::DTO::ItemFieldsClass::Value>, fields, Fields)
|
||||||
|
FWDPROP(qint32, imageTypeLimit, ImageTypeLimit)
|
||||||
|
FWDPROP(bool, isMissing, IsMissing)
|
||||||
|
FWDPROP(bool, isSpecialSeason, IsSpecialSeason)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
using ShowEpisodesLoaderBase = AbstractUserParameterLoader<Model::Item, DTO::BaseItemDto, DTO::BaseItemDtoQueryResult, Jellyfin::Loader::GetEpisodesParams>;
|
||||||
|
class ShowEpisodesLoader : public ShowEpisodesLoaderBase {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ShowEpisodesLoader(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
FWDPROP(QString, seriesId, SeriesId)
|
||||||
|
FWDPROP(QString, adjacentTo, AdjacentTo)
|
||||||
|
FWDPROP(bool, enableImages, EnableImages)
|
||||||
|
FWDPROP(bool, enableUserData, EnableUserData)
|
||||||
|
FWDPROP(QList<Jellyfin::DTO::ItemFieldsClass::Value>, fields, Fields)
|
||||||
|
FWDPROP(qint32, imageTypeLimit, ImageTypeLimit)
|
||||||
|
FWDPROP(bool, isMissing, IsMissing)
|
||||||
|
FWDPROP(qint32, season, Season)
|
||||||
|
FWDPROP(QString, seasonId, SeasonId)
|
||||||
|
FWDPROP(QString, sortBy, SortBy)
|
||||||
|
FWDPROP(QString, startItemId, StartItemId)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -167,6 +263,11 @@ public:
|
||||||
mediaType,
|
mediaType,
|
||||||
type,
|
type,
|
||||||
collectionType,
|
collectionType,
|
||||||
|
indexNumber,
|
||||||
|
runTimeTicks,
|
||||||
|
artists,
|
||||||
|
isFolder,
|
||||||
|
parentIndexNumber,
|
||||||
|
|
||||||
jellyfinExtendModelAfterHere = Qt::UserRole + 300 // Should be enough for now
|
jellyfinExtendModelAfterHere = Qt::UserRole + 300 // Should be enough for now
|
||||||
};
|
};
|
||||||
|
@ -191,6 +292,11 @@ public:
|
||||||
JFRN(mediaType),
|
JFRN(mediaType),
|
||||||
JFRN(type),
|
JFRN(type),
|
||||||
JFRN(collectionType),
|
JFRN(collectionType),
|
||||||
|
JFRN(indexNumber),
|
||||||
|
JFRN(runTimeTicks),
|
||||||
|
JFRN(artists),
|
||||||
|
JFRN(isFolder),
|
||||||
|
JFRN(parentIndexNumber),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
QVariant data(const QModelIndex &index, int role) const override;
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
|
|
@ -91,6 +91,7 @@ public:
|
||||||
Q_PROPERTY(QMediaPlayer::Error error READ error NOTIFY errorChanged)
|
Q_PROPERTY(QMediaPlayer::Error error READ error NOTIFY errorChanged)
|
||||||
Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
|
Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged)
|
||||||
Q_PROPERTY(bool hasVideo READ hasVideo NOTIFY hasVideoChanged)
|
Q_PROPERTY(bool hasVideo READ hasVideo NOTIFY hasVideoChanged)
|
||||||
|
Q_PROPERTY(bool seekable READ seekable NOTIFY seekableChanged)
|
||||||
Q_PROPERTY(QObject* mediaObject READ mediaObject NOTIFY mediaObjectChanged)
|
Q_PROPERTY(QObject* mediaObject READ mediaObject NOTIFY mediaObjectChanged)
|
||||||
Q_PROPERTY(QMediaPlayer::MediaStatus mediaStatus READ mediaStatus NOTIFY mediaStatusChanged)
|
Q_PROPERTY(QMediaPlayer::MediaStatus mediaStatus READ mediaStatus NOTIFY mediaStatusChanged)
|
||||||
Q_PROPERTY(QMediaPlayer::State playbackState READ playbackState NOTIFY playbackStateChanged)
|
Q_PROPERTY(QMediaPlayer::State playbackState READ playbackState NOTIFY playbackStateChanged)
|
||||||
|
@ -108,9 +109,10 @@ public:
|
||||||
int queueIndex() const { return m_queueIndex; }
|
int queueIndex() const { return m_queueIndex; }
|
||||||
|
|
||||||
// Current media player related property getters
|
// Current media player related property getters
|
||||||
QMediaPlayer::State playbackState() const { return m_playbackState; }
|
QMediaPlayer::State playbackState() const { return m_mediaPlayer->state()/*m_playbackState*/; }
|
||||||
QMediaPlayer::MediaStatus mediaStatus() const { return m_mediaPlayer->mediaStatus(); }
|
QMediaPlayer::MediaStatus mediaStatus() const { return m_mediaPlayer->mediaStatus(); }
|
||||||
bool hasVideo() const { return m_mediaPlayer->isVideoAvailable(); }
|
bool hasVideo() const { return m_mediaPlayer->isVideoAvailable(); }
|
||||||
|
bool seekable() const { return m_mediaPlayer->isSeekable(); }
|
||||||
QMediaPlayer::Error error () const { return m_mediaPlayer->error(); }
|
QMediaPlayer::Error error () const { return m_mediaPlayer->error(); }
|
||||||
QString errorString() const { return m_mediaPlayer->errorString(); }
|
QString errorString() const { return m_mediaPlayer->errorString(); }
|
||||||
signals:
|
signals:
|
||||||
|
@ -132,6 +134,7 @@ signals:
|
||||||
void playbackStateChanged(QMediaPlayer::State newState);
|
void playbackStateChanged(QMediaPlayer::State newState);
|
||||||
void mediaStatusChanged(QMediaPlayer::MediaStatus newMediaStatus);
|
void mediaStatusChanged(QMediaPlayer::MediaStatus newMediaStatus);
|
||||||
void hasVideoChanged(bool newHasVideo);
|
void hasVideoChanged(bool newHasVideo);
|
||||||
|
void seekableChanged(bool newSeekable);
|
||||||
void errorChanged(QMediaPlayer::Error newError);
|
void errorChanged(QMediaPlayer::Error newError);
|
||||||
void errorStringChanged(const QString &newErrorString);
|
void errorStringChanged(const QString &newErrorString);
|
||||||
public slots:
|
public slots:
|
||||||
|
|
81
core/include/JellyfinQt/viewmodel/userdata.h
Normal file
81
core/include/JellyfinQt/viewmodel/userdata.h
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Sailfin: a Jellyfin client written using Qt
|
||||||
|
* Copyright (C) 2021 Chris Josten and the Sailfin Contributors.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#ifndef JELLYFIN_VIEWMODEL_USERDATA_H
|
||||||
|
#define JELLYFIN_VIEWMODEL_USERDATA_H
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
|
#include "../dto/useritemdatadto.h"
|
||||||
|
|
||||||
|
namespace Jellyfin {
|
||||||
|
namespace ViewModel{
|
||||||
|
|
||||||
|
class UserData : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit UserData(QObject* parent = nullptr);
|
||||||
|
explicit UserData(QSharedPointer<DTO::UserItemDataDto> data, QObject* parent = nullptr);
|
||||||
|
|
||||||
|
void setData(QSharedPointer<DTO::UserItemDataDto> data);
|
||||||
|
|
||||||
|
Q_PROPERTY(double rating READ rating NOTIFY ratingChanged)
|
||||||
|
Q_PROPERTY(double playedPercentage READ playedPercentage NOTIFY playedPercentageChanged)
|
||||||
|
Q_PROPERTY(int unplayedItemCount READ unplayedItemCount NOTIFY unplayedItemCountChanged)
|
||||||
|
Q_PROPERTY(qint64 playbackPositionTicks READ playbackPositionTicks NOTIFY playbackPositionTicksChanged);
|
||||||
|
Q_PROPERTY(int playCount READ playCount NOTIFY playCountChanged)
|
||||||
|
Q_PROPERTY(bool favorite READ favorite NOTIFY favoriteChanged)
|
||||||
|
Q_PROPERTY(bool m_likes READ likes NOTIFY likesChanged)
|
||||||
|
Q_PROPERTY(QDateTime lastPlayedDate READ lastPlayedDate NOTIFY lastPlayedDateChanged)
|
||||||
|
Q_PROPERTY(bool played READ played NOTIFY playedChanged)
|
||||||
|
Q_PROPERTY(QString key READ key NOTIFY keyChanged)
|
||||||
|
|
||||||
|
double rating() const { return m_data->rating().value_or(0); }
|
||||||
|
double playedPercentage() const { return m_data->playedPercentage().value_or(0); }
|
||||||
|
int unplayedItemCount() const { return m_data->unplayedItemCount().value_or(0); }
|
||||||
|
qint64 playbackPositionTicks() const { return m_data->playbackPositionTicks(); }
|
||||||
|
int playCount() const { return m_data->playCount(); }
|
||||||
|
bool favorite() const { return m_data->isFavorite(); }
|
||||||
|
bool likes() const { return m_data->likes().value_or(false); }
|
||||||
|
QDateTime lastPlayedDate() const { return m_data->lastPlayedDate(); }
|
||||||
|
bool played() const { return m_data->played(); }
|
||||||
|
QString key() const { return m_data->key(); }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void ratingChanged(double newRating);
|
||||||
|
void playedPercentageChanged(double newPlayedPercentage);
|
||||||
|
void unplayedItemCountChanged(int newUnplayedItemCount);
|
||||||
|
void playbackPositionTicksChanged(qint64 newPlaybackPositionTicks);
|
||||||
|
void playCountChanged(int newPlayCount);
|
||||||
|
void favoriteChanged(bool newFavorite);
|
||||||
|
void likesChanged(bool newLikes);
|
||||||
|
void lastPlayedDateChanged(QDateTime newLastPlayedDate);
|
||||||
|
void playedChanged(bool newPLayed);
|
||||||
|
void keyChanged(QString newKey);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSharedPointer<DTO::UserItemDataDto> m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // NS ViewModel
|
||||||
|
} // NS Jellyfin
|
||||||
|
|
||||||
|
#endif // JELLYFIN_VIEWMODEL_USERDATA_H
|
|
@ -35,6 +35,7 @@ ApiClient::ApiClient(QObject *parent)
|
||||||
m_credManager = CredentialsManager::newInstance(this);
|
m_credManager = CredentialsManager::newInstance(this);
|
||||||
connect(m_credManager, &CredentialsManager::serversListed, this, &ApiClient::credManagerServersListed);
|
connect(m_credManager, &CredentialsManager::serversListed, this, &ApiClient::credManagerServersListed);
|
||||||
connect(m_credManager, &CredentialsManager::usersListed, this, &ApiClient::credManagerUsersListed);
|
connect(m_credManager, &CredentialsManager::usersListed, this, &ApiClient::credManagerUsersListed);
|
||||||
|
connect(m_credManager, &CredentialsManager::tokenRetrieved, this, &ApiClient::credManagerTokenRetrieved);
|
||||||
generateDeviceProfile();
|
generateDeviceProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,20 +119,14 @@ void ApiClient::credManagerUsersListed(const QString &server, QStringList users)
|
||||||
QString user = users[0];
|
QString user = users[0];
|
||||||
qDebug() << "Chosen user: " << user;
|
qDebug() << "Chosen user: " << user;
|
||||||
|
|
||||||
QObject *ctx3 = new QObject(this);
|
|
||||||
connect(m_credManager, &CredentialsManager::tokenRetrieved, ctx3, [this, ctx3]
|
|
||||||
(const QString &server, const QString &user, const QString &token) {
|
|
||||||
Q_UNUSED(server)
|
|
||||||
this->m_token = token;
|
|
||||||
this->setUserId(user);
|
|
||||||
this->setAuthenticated(true);
|
|
||||||
this->postCapabilities();
|
|
||||||
disconnect(ctx3);
|
|
||||||
}, Qt::UniqueConnection);
|
|
||||||
m_credManager->get(server, user);
|
m_credManager->get(server, user);
|
||||||
}
|
}
|
||||||
void ApiClient::credManagerTokenRetrieved(const QString &server, const QString &user, const QString &token) {
|
void ApiClient::credManagerTokenRetrieved(const QString &server, const QString &user, const QString &token) {
|
||||||
|
this->m_token = token;
|
||||||
|
qDebug() << "Token retreived, logged in as user " << user;
|
||||||
|
this->setUserId(user);
|
||||||
|
this->setAuthenticated(true);
|
||||||
|
this->postCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiClient::setupConnection() {
|
void ApiClient::setupConnection() {
|
||||||
|
|
|
@ -71,6 +71,10 @@ void BaseModelLoader::setAutoReload(bool newAutoReload) {
|
||||||
if (m_autoReload != newAutoReload) {
|
if (m_autoReload != newAutoReload) {
|
||||||
m_autoReload = newAutoReload;
|
m_autoReload = newAutoReload;
|
||||||
emit autoReloadChanged(newAutoReload);
|
emit autoReloadChanged(newAutoReload);
|
||||||
|
|
||||||
|
if (canReload()) {
|
||||||
|
reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,11 +42,6 @@ QString FallbackCredentialsManager::urlToGroupName(const QString &url) const {
|
||||||
return QString::number(qHash(url), 16);
|
return QString::number(qHash(url), 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString FallbackCredentialsManager::groupNameToUrl(const QString &group) const {
|
|
||||||
QString tmp = QString(group);
|
|
||||||
return tmp.replace('|', "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
void FallbackCredentialsManager::store(const QString &server, const QString &user, const QString &token) {
|
void FallbackCredentialsManager::store(const QString &server, const QString &user, const QString &token) {
|
||||||
m_settings.setValue(urlToGroupName(server) + "/users/" + user + "/accessToken", token);
|
m_settings.setValue(urlToGroupName(server) + "/users/" + user + "/accessToken", token);
|
||||||
m_settings.setValue(urlToGroupName(server) + "/address", server);
|
m_settings.setValue(urlToGroupName(server) + "/address", server);
|
||||||
|
|
|
@ -38,11 +38,15 @@ void registerTypes(const char *uri) {
|
||||||
qmlRegisterType<ViewModel::LatestMediaLoader>(uri, 1, 0, "LatestMediaLoader");
|
qmlRegisterType<ViewModel::LatestMediaLoader>(uri, 1, 0, "LatestMediaLoader");
|
||||||
qmlRegisterType<ViewModel::UserItemsLoader>(uri, 1, 0, "UserItemsLoader");
|
qmlRegisterType<ViewModel::UserItemsLoader>(uri, 1, 0, "UserItemsLoader");
|
||||||
qmlRegisterType<ViewModel::UserViewsLoader>(uri, 1, 0, "UsersViewsLoader");
|
qmlRegisterType<ViewModel::UserViewsLoader>(uri, 1, 0, "UsersViewsLoader");
|
||||||
|
qmlRegisterType<ViewModel::ResumeItemsLoader>(uri, 1, 0, "ResumeItemsLoader");
|
||||||
|
qmlRegisterType<ViewModel::ShowSeasonsLoader>(uri, 1, 0, "ShowSeasonsLoader");
|
||||||
|
qmlRegisterType<ViewModel::ShowEpisodesLoader>(uri, 1, 0, "ShowEpisodesLoader");
|
||||||
|
|
||||||
// Enumerations
|
// Enumerations
|
||||||
qmlRegisterUncreatableType<Jellyfin::DTO::GeneralCommandTypeClass>(uri, 1, 0, "GeneralCommandType", "Is an enum");
|
qmlRegisterUncreatableType<Jellyfin::DTO::GeneralCommandTypeClass>(uri, 1, 0, "GeneralCommandType", "Is an enum");
|
||||||
qmlRegisterUncreatableType<Jellyfin::ViewModel::ModelStatusClass>(uri, 1, 0, "ModelStatus", "Is an enum");
|
qmlRegisterUncreatableType<Jellyfin::ViewModel::ModelStatusClass>(uri, 1, 0, "ModelStatus", "Is an enum");
|
||||||
qmlRegisterUncreatableType<Jellyfin::DTO::PlayMethodClass>(uri, 1, 0, "PlayMethod", "Is an enum");
|
qmlRegisterUncreatableType<Jellyfin::DTO::PlayMethodClass>(uri, 1, 0, "PlayMethod", "Is an enum");
|
||||||
|
qmlRegisterUncreatableType<Jellyfin::DTO::ItemFieldsClass>(uri, 1, 0, "ItemFields", "Is an enum");
|
||||||
|
|
||||||
qRegisterMetaType<Jellyfin::DTO::PlayMethodClass::Value>();
|
qRegisterMetaType<Jellyfin::DTO::PlayMethodClass::Value>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,25 +23,14 @@ namespace Jellyfin {
|
||||||
namespace Model {
|
namespace Model {
|
||||||
|
|
||||||
|
|
||||||
Item::Item()
|
Item::Item(QObject *parent)
|
||||||
: Item(DTO::BaseItemDto(), nullptr) { }
|
: Item(DTO::BaseItemDto(), nullptr, parent) { }
|
||||||
|
|
||||||
Item::Item(const DTO::BaseItemDto &data, ApiClient *apiClient)
|
Item::Item(const DTO::BaseItemDto &data, ApiClient *apiClient, QObject *parent)
|
||||||
: DTO::BaseItemDto(data), m_apiClient(apiClient) {
|
: DTO::BaseItemDto(data),
|
||||||
if (m_apiClient != nullptr) {
|
QObject(parent),
|
||||||
m_apiClientConnections.append(QObject::connect(
|
m_apiClient(nullptr) {
|
||||||
m_apiClient->eventbus(),
|
setApiClient(apiClient);
|
||||||
&EventBus::itemUserDataUpdated,
|
|
||||||
[&](auto itemId, auto userData) {
|
|
||||||
onUserDataUpdated(itemId, userData);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item::~Item() {
|
|
||||||
for(auto it = m_apiClientConnections.begin(); it != m_apiClientConnections.end(); it++) {
|
|
||||||
QObject::disconnect(*it);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Item::sameAs(const DTO::BaseItemDto &other) {
|
bool Item::sameAs(const DTO::BaseItemDto &other) {
|
||||||
|
@ -50,23 +39,20 @@ bool Item::sameAs(const DTO::BaseItemDto &other) {
|
||||||
|
|
||||||
void Item::setApiClient(ApiClient *apiClient) {
|
void Item::setApiClient(ApiClient *apiClient) {
|
||||||
if (m_apiClient != nullptr) {
|
if (m_apiClient != nullptr) {
|
||||||
for(auto it = m_apiClientConnections.begin(); it != m_apiClientConnections.end(); it++) {
|
disconnect(m_apiClient->eventbus(), &EventBus::itemUserDataUpdated,
|
||||||
QObject::disconnect(*it);
|
this, &Item::updateUserData);
|
||||||
}
|
|
||||||
m_apiClientConnections.clear();
|
|
||||||
}
|
}
|
||||||
this->m_apiClient = apiClient;
|
this->m_apiClient = apiClient;
|
||||||
if (apiClient != nullptr) {
|
if (apiClient != nullptr) {
|
||||||
m_apiClientConnections.append(QObject::connect(
|
QObject::connect(m_apiClient->eventbus(), &EventBus::itemUserDataUpdated,
|
||||||
m_apiClient->eventbus(),
|
this, &Item::updateUserData);
|
||||||
&EventBus::itemUserDataUpdated,
|
|
||||||
[&](auto itemId, auto userData) {
|
|
||||||
onUserDataUpdated(itemId, userData);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void Item::onUserDataUpdated(const QString &itemId, const DTO::UserItemDataDto &userData) {
|
|
||||||
|
|
||||||
|
void Item::updateUserData(const QString &itemId, const DTO::UserItemDataDto &userData) {
|
||||||
|
if (itemId == this->jellyfinId()) {
|
||||||
|
emit userDataChanged(userData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,12 +103,12 @@ QSharedPointer<Item> Playlist::nextItem() {
|
||||||
return m_nextItem;
|
return m_nextItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::appendToList(const ViewModel::ItemModel &model) {
|
void Playlist::appendToList(ViewModel::ItemModel &model) {
|
||||||
int start = m_list.size();
|
int start = m_list.size();
|
||||||
int count = model.size();
|
int count = model.size();
|
||||||
m_list.reserve(count);
|
m_list.reserve(count);
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
m_list.append(QSharedPointer<Model::Item>::create(model.at(i)));
|
m_list.append(QSharedPointer<Model::Item>(model.at(i)));
|
||||||
}
|
}
|
||||||
emit itemsAddedToList(start, count);
|
emit itemsAddedToList(start, count);
|
||||||
reshuffle();
|
reshuffle();
|
||||||
|
|
|
@ -17,17 +17,39 @@
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
#include "JellyfinQt/viewmodel/item.h"
|
#include "JellyfinQt/viewmodel/item.h"
|
||||||
|
#include "JellyfinQt/viewmodel/userdata.h"
|
||||||
|
|
||||||
namespace Jellyfin {
|
namespace Jellyfin {
|
||||||
namespace ViewModel {
|
namespace ViewModel {
|
||||||
|
|
||||||
Item::Item(QObject *parent, QSharedPointer<Model::Item> data)
|
Item::Item(QObject *parent, QSharedPointer<Model::Item> data)
|
||||||
: QObject(parent), m_data(data){
|
: QObject(parent),
|
||||||
|
m_data(data),
|
||||||
|
m_userData(new UserData(this)){
|
||||||
|
connect(m_data.data(), &Model::Item::userDataChanged, this, &Item::onUserDataChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Item::setData(QSharedPointer<Model::Item> newData) {
|
void Item::setData(QSharedPointer<Model::Item> newData) {
|
||||||
|
if (!m_data.isNull()) {
|
||||||
|
disconnect(m_data.data(), &Model::Item::userDataChanged, this, &Item::onUserDataChanged);
|
||||||
|
}
|
||||||
m_data = newData;
|
m_data = newData;
|
||||||
|
if (!m_data.isNull()) {
|
||||||
|
connect(m_data.data(), &Model::Item::userDataChanged, this, &Item::onUserDataChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Item::setUserData(DTO::UserItemDataDto &newData) {
|
||||||
|
setUserData(QSharedPointer<DTO::UserItemDataDto>::create(newData));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Item::setUserData(QSharedPointer<DTO::UserItemDataDto> newData) {
|
||||||
|
m_userData->setData(newData);
|
||||||
|
emit userDataChanged(m_userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Item::onUserDataChanged(const DTO::UserItemDataDto &newData) {
|
||||||
|
setUserData(QSharedPointer<DTO::UserItemDataDto>::create(newData));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,15 @@
|
||||||
*/
|
*/
|
||||||
#include "JellyfinQt/viewmodel/itemmodel.h"
|
#include "JellyfinQt/viewmodel/itemmodel.h"
|
||||||
|
|
||||||
|
#include "JellyfinQt/loader/http/getepisodes.h"
|
||||||
#include "JellyfinQt/loader/http/getlatestmedia.h"
|
#include "JellyfinQt/loader/http/getlatestmedia.h"
|
||||||
#include "JellyfinQt/loader/http/getitemsbyuserid.h"
|
#include "JellyfinQt/loader/http/getitemsbyuserid.h"
|
||||||
|
#include "JellyfinQt/loader/http/getresumeitems.h"
|
||||||
|
#include "JellyfinQt/loader/http/getseasons.h"
|
||||||
|
|
||||||
#define JF_CASE(roleName) case roleName: \
|
#define JF_CASE(roleName) case roleName: \
|
||||||
try { \
|
try { \
|
||||||
return QVariant(item.roleName()); \
|
return QVariant(item->roleName()); \
|
||||||
} catch(std::bad_optional_access &e) { \
|
} catch(std::bad_optional_access &e) { \
|
||||||
return QVariant(); \
|
return QVariant(); \
|
||||||
}
|
}
|
||||||
|
@ -41,6 +44,15 @@ LatestMediaLoader::LatestMediaLoader(QObject *parent)
|
||||||
UserItemsLoader::UserItemsLoader(QObject *parent)
|
UserItemsLoader::UserItemsLoader(QObject *parent)
|
||||||
: UserItemsLoaderBase(new Jellyfin::Loader::HTTP::GetItemsByUserIdLoader(), parent) {}
|
: UserItemsLoaderBase(new Jellyfin::Loader::HTTP::GetItemsByUserIdLoader(), parent) {}
|
||||||
|
|
||||||
|
ResumeItemsLoader::ResumeItemsLoader(QObject *parent)
|
||||||
|
: ResumeItemsLoaderBase(new Jellyfin::Loader::HTTP::GetResumeItemsLoader(), parent) {}
|
||||||
|
|
||||||
|
ShowSeasonsLoader::ShowSeasonsLoader(QObject *parent)
|
||||||
|
: ShowSeasonsLoaderBase(new Jellyfin::Loader::HTTP::GetSeasonsLoader(), parent) {}
|
||||||
|
|
||||||
|
ShowEpisodesLoader::ShowEpisodesLoader(QObject *parent)
|
||||||
|
: ShowEpisodesLoaderBase(new Jellyfin::Loader::HTTP::GetEpisodesLoader(), parent) {}
|
||||||
|
|
||||||
ItemModel::ItemModel(QObject *parent)
|
ItemModel::ItemModel(QObject *parent)
|
||||||
: ApiModel<Model::Item>(parent) { }
|
: ApiModel<Model::Item>(parent) { }
|
||||||
|
|
||||||
|
@ -48,7 +60,7 @@ QVariant ItemModel::data(const QModelIndex &index, int role) const {
|
||||||
if (role <= Qt::UserRole || !index.isValid()) return QVariant();
|
if (role <= Qt::UserRole || !index.isValid()) return QVariant();
|
||||||
int row = index.row();
|
int row = index.row();
|
||||||
if (row < 0 || row >= m_array.size()) return QVariant();
|
if (row < 0 || row >= m_array.size()) return QVariant();
|
||||||
Model::Item item = m_array[row];
|
QSharedPointer<Model::Item> item = m_array[row];
|
||||||
switch(role) {
|
switch(role) {
|
||||||
JF_CASE(jellyfinId)
|
JF_CASE(jellyfinId)
|
||||||
JF_CASE(name)
|
JF_CASE(name)
|
||||||
|
@ -66,6 +78,15 @@ QVariant ItemModel::data(const QModelIndex &index, int role) const {
|
||||||
JF_CASE(mediaType)
|
JF_CASE(mediaType)
|
||||||
JF_CASE(type)
|
JF_CASE(type)
|
||||||
JF_CASE(collectionType)
|
JF_CASE(collectionType)
|
||||||
|
case RoleNames::indexNumber:
|
||||||
|
return QVariant(item->indexNumber().value_or(0));
|
||||||
|
case RoleNames::runTimeTicks:
|
||||||
|
return QVariant(item->runTimeTicks().value_or(0));
|
||||||
|
JF_CASE(artists)
|
||||||
|
case RoleNames::isFolder:
|
||||||
|
return QVariant(item->isFolder().value_or(false));
|
||||||
|
case RoleNames::parentIndexNumber:
|
||||||
|
return QVariant(item->parentIndexNumber().value_or(1));
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
@ -73,7 +94,7 @@ QVariant ItemModel::data(const QModelIndex &index, int role) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<Model::Item> ItemModel::itemAt(int index) {
|
QSharedPointer<Model::Item> ItemModel::itemAt(int index) {
|
||||||
return QSharedPointer<Model::Item>::create(m_array[index]);
|
return m_array[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
} // NS ViewModel
|
} // NS ViewModel
|
||||||
|
|
|
@ -56,6 +56,7 @@ PlaybackManager::PlaybackManager(QObject *parent)
|
||||||
connect(m_mediaPlayer, &QMediaPlayer::durationChanged, this, &PlaybackManager::mediaPlayerDurationChanged);
|
connect(m_mediaPlayer, &QMediaPlayer::durationChanged, this, &PlaybackManager::mediaPlayerDurationChanged);
|
||||||
connect(m_mediaPlayer, &QMediaPlayer::mediaStatusChanged, this, &PlaybackManager::mediaPlayerMediaStatusChanged);
|
connect(m_mediaPlayer, &QMediaPlayer::mediaStatusChanged, this, &PlaybackManager::mediaPlayerMediaStatusChanged);
|
||||||
connect(m_mediaPlayer, &QMediaPlayer::videoAvailableChanged, this, &PlaybackManager::hasVideoChanged);
|
connect(m_mediaPlayer, &QMediaPlayer::videoAvailableChanged, this, &PlaybackManager::hasVideoChanged);
|
||||||
|
connect(m_mediaPlayer, &QMediaPlayer::seekableChanged, this, &PlaybackManager::seekableChanged);
|
||||||
// I do not like the complicated overload cast
|
// I do not like the complicated overload cast
|
||||||
connect(m_mediaPlayer, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(mediaPlayerError(QMediaPlayer::Error)));
|
connect(m_mediaPlayer, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(mediaPlayerError(QMediaPlayer::Error)));
|
||||||
|
|
||||||
|
@ -142,6 +143,7 @@ void PlaybackManager::mediaPlayerStateChanged(QMediaPlayer::State newState) {
|
||||||
postPlaybackInfo(Progress);
|
postPlaybackInfo(Progress);
|
||||||
}
|
}
|
||||||
m_oldState = newState;
|
m_oldState = newState;
|
||||||
|
emit playbackStateChanged(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlaybackManager::mediaPlayerMediaStatusChanged(QMediaPlayer::MediaStatus newStatus) {
|
void PlaybackManager::mediaPlayerMediaStatusChanged(QMediaPlayer::MediaStatus newStatus) {
|
||||||
|
@ -352,6 +354,9 @@ void ItemUrlFetcherThread::run() {
|
||||||
playMethod = PlayMethod::DirectPlay;
|
playMethod = PlayMethod::DirectPlay;
|
||||||
} else if (source.supportsDirectStream()) {
|
} else if (source.supportsDirectStream()) {
|
||||||
QString mediaType = item->mediaType();
|
QString mediaType = item->mediaType();
|
||||||
|
if (mediaType == "Video") {
|
||||||
|
mediaType.append('s');
|
||||||
|
}
|
||||||
QUrlQuery query;
|
QUrlQuery query;
|
||||||
query.addQueryItem("mediaSourceId", source.jellyfinId());
|
query.addQueryItem("mediaSourceId", source.jellyfinId());
|
||||||
query.addQueryItem("deviceId", m_parent->m_apiClient->deviceId());
|
query.addQueryItem("deviceId", m_parent->m_apiClient->deviceId());
|
||||||
|
|
39
core/src/viewmodel/userdata.cpp
Normal file
39
core/src/viewmodel/userdata.cpp
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Sailfin: a Jellyfin client written using Qt
|
||||||
|
* Copyright (C) 2021 Chris Josten and the Sailfin Contributors.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#include <JellyfinQt/viewmodel/userdata.h>
|
||||||
|
|
||||||
|
namespace Jellyfin {
|
||||||
|
namespace ViewModel {
|
||||||
|
|
||||||
|
UserData::UserData(QObject *parent)
|
||||||
|
: QObject(parent),
|
||||||
|
m_data(QSharedPointer<DTO::UserItemDataDto>::create()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
UserData::UserData(QSharedPointer<DTO::UserItemDataDto> data, QObject *parent)
|
||||||
|
: QObject(parent),
|
||||||
|
m_data(data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void UserData::setData(QSharedPointer<DTO::UserItemDataDto> data) {
|
||||||
|
m_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // NS ViewModel
|
||||||
|
} // NS Jellyfin
|
|
@ -18,7 +18,6 @@ set(harbour-sailfin_SOURCES
|
||||||
src/harbour-sailfin.cpp)
|
src/harbour-sailfin.cpp)
|
||||||
|
|
||||||
set(sailfin_QML_SOURCES
|
set(sailfin_QML_SOURCES
|
||||||
qml/ApiClient.qml
|
|
||||||
qml/Constants.qml
|
qml/Constants.qml
|
||||||
qml/Utils.js
|
qml/Utils.js
|
||||||
qml/components/music/NarrowAlbumCover.qml
|
qml/components/music/NarrowAlbumCover.qml
|
||||||
|
@ -27,8 +26,8 @@ set(sailfin_QML_SOURCES
|
||||||
qml/components/videoplayer/VideoError.qml
|
qml/components/videoplayer/VideoError.qml
|
||||||
qml/components/videoplayer/VideoHud.qml
|
qml/components/videoplayer/VideoHud.qml
|
||||||
qml/components/IconListItem.qml
|
qml/components/IconListItem.qml
|
||||||
qml/components/JItem.qml
|
qml/components/JItem.qml
|
||||||
qml/components/LibraryItemDelegate.qml
|
qml/components/LibraryItemDelegate.qml
|
||||||
qml/components/MoreSection.qml
|
qml/components/MoreSection.qml
|
||||||
qml/components/PlainLabel.qml
|
qml/components/PlainLabel.qml
|
||||||
qml/components/PlaybackBar.qml
|
qml/components/PlaybackBar.qml
|
||||||
|
@ -60,8 +59,8 @@ set(sailfin_QML_SOURCES
|
||||||
qml/pages/itemdetails/VideoPage.qml
|
qml/pages/itemdetails/VideoPage.qml
|
||||||
qml/pages/settings/DebugPage.qml
|
qml/pages/settings/DebugPage.qml
|
||||||
qml/pages/setup/AddServerConnectingPage.qml
|
qml/pages/setup/AddServerConnectingPage.qml
|
||||||
qml/pages/setup/AddServerPage.qml
|
qml/pages/setup/AddServerPage.qml
|
||||||
qml/pages/setup/LoginDialog.qml
|
qml/pages/setup/LoginDialog.qml
|
||||||
qml/qmldir)
|
qml/qmldir)
|
||||||
|
|
||||||
add_executable(harbour-sailfin ${harbour-sailfin_SOURCES} ${sailfin_QML_SOURCES})
|
add_executable(harbour-sailfin ${harbour-sailfin_SOURCES} ${sailfin_QML_SOURCES})
|
||||||
|
|
|
@ -3,7 +3,7 @@ Type=Application
|
||||||
Version=1.1
|
Version=1.1
|
||||||
X-Nemo-Application-Type=silica-qt5
|
X-Nemo-Application-Type=silica-qt5
|
||||||
Icon=harbour-sailfin
|
Icon=harbour-sailfin
|
||||||
Exec=harbour-sailfin
|
Exec=harbour-sailfin --no-attempt-sandbox
|
||||||
|
|
||||||
|
|
||||||
Name=Sailfin
|
Name=Sailfin
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
pragma Singleton
|
|
||||||
import QtQuick 2.6
|
|
||||||
import nl.netsoj.chris.Jellyfin 1.0 as J
|
|
||||||
|
|
||||||
J.ApiClient {
|
|
||||||
supportedCommands: [J.GeneralCommandType.Play, J.GeneralCommandType.DisplayContent, J.GeneralCommandType.DisplayMessage]
|
|
||||||
}
|
|
|
@ -46,7 +46,7 @@ QtObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property real libraryDelegatePosterHeight: libraryDelegateHeight * 1.6667
|
readonly property real libraryDelegatePosterHeight: libraryDelegateHeight * 1.5 // 1.6667
|
||||||
|
|
||||||
readonly property real libraryProgressHeight: Theme.paddingMedium
|
readonly property real libraryProgressHeight: Theme.paddingMedium
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import QtQuick 2.6
|
||||||
import QtMultimedia 5.6
|
import QtMultimedia 5.6
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
import nl.netsoj.chris.Jellyfin 1.0
|
import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
import "../"
|
import "../"
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ import "../"
|
||||||
* +---+--------------------------------------+
|
* +---+--------------------------------------+
|
||||||
* |\ /| +---+ |
|
* |\ /| +---+ |
|
||||||
* | \ / | Media title | | |
|
* | \ / | Media title | | |
|
||||||
* | X | | ⏸︎| |
|
* | X | | ⏸︎ | |
|
||||||
* | / \ | Artist 1, artist 2 | | |
|
* | / \ | Artist 1, artist 2 | | |
|
||||||
* |/ \| +---+ |
|
* |/ \| +---+ |
|
||||||
* +-----+------------------------------------+
|
* +-----+------------------------------------+
|
||||||
|
@ -40,13 +40,15 @@ PanelBackground {
|
||||||
height: Theme.itemSizeLarge
|
height: Theme.itemSizeLarge
|
||||||
width: parent.width
|
width: parent.width
|
||||||
y: parent.height - height
|
y: parent.height - height
|
||||||
property PlaybackManager manager
|
//FIXME: Once QTBUG-10822 is resolved, change to J.PlaybackManager
|
||||||
|
property var manager
|
||||||
property bool open
|
property bool open
|
||||||
property real visibleSize: height
|
property real visibleSize: height
|
||||||
property bool isFullPage: false
|
property bool isFullPage: false
|
||||||
property bool showQueue: false
|
property bool showQueue: false
|
||||||
|
|
||||||
property bool _pageWasShowingNavigationIndicator
|
property bool _pageWasShowingNavigationIndicator
|
||||||
|
readonly property bool mediaLoading: [MediaPlayer.Loading, MediaPlayer.Buffering].indexOf(manager.mediaStatus) >= 0
|
||||||
|
|
||||||
|
|
||||||
transform: Translate {id: playbackBarTranslate; y: 0}
|
transform: Translate {id: playbackBarTranslate; y: 0}
|
||||||
|
@ -72,10 +74,11 @@ PanelBackground {
|
||||||
}
|
}
|
||||||
source: largeAlbumArt.source
|
source: largeAlbumArt.source
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
opacity: 1
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: largeAlbumArt
|
id: largeAlbumArt
|
||||||
source: Utils.itemImageUrl(ApiClient.baseUrl, manager.item, "Primary")
|
source: Utils.itemImageUrl(apiClient.baseUrl, manager.item, "Primary")
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
opacity: 0
|
opacity: 0
|
||||||
|
@ -119,11 +122,11 @@ PanelBackground {
|
||||||
Label {
|
Label {
|
||||||
id: artists
|
id: artists
|
||||||
text: {
|
text: {
|
||||||
if (manager.item == null) return qsTr("Play some media!")
|
//return manager.item.mediaType;
|
||||||
console.log(manager.item.type)
|
if (manager.item === null) return qsTr("Play some media!")
|
||||||
switch(manager.item.type) {
|
switch(manager.item.mediaType) {
|
||||||
case "Audio":
|
case "Audio":
|
||||||
return manager.item.artists.join(", ")
|
return manager.item.artists //.join(", ")
|
||||||
}
|
}
|
||||||
return qsTr("Not audio")
|
return qsTr("Not audio")
|
||||||
}
|
}
|
||||||
|
@ -157,6 +160,7 @@ PanelBackground {
|
||||||
icon.source: "image://theme/icon-m-previous"
|
icon.source: "image://theme/icon-m-previous"
|
||||||
enabled: false
|
enabled: false
|
||||||
opacity: 0
|
opacity: 0
|
||||||
|
onClicked: manager.previous()
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton {
|
IconButton {
|
||||||
|
@ -182,6 +186,7 @@ PanelBackground {
|
||||||
icon.source: "image://theme/icon-m-next"
|
icon.source: "image://theme/icon-m-next"
|
||||||
enabled: false
|
enabled: false
|
||||||
opacity: 0
|
opacity: 0
|
||||||
|
onClicked: manager.next()
|
||||||
}
|
}
|
||||||
IconButton {
|
IconButton {
|
||||||
id: queueButton
|
id: queueButton
|
||||||
|
@ -206,7 +211,7 @@ PanelBackground {
|
||||||
minimumValue: 0
|
minimumValue: 0
|
||||||
value: manager.position
|
value: manager.position
|
||||||
maximumValue: manager.duration
|
maximumValue: manager.duration
|
||||||
indeterminate: [MediaPlayer.Loading, MediaPlayer.Buffering].indexOf(manager.mediaStatus) >= 0
|
indeterminate: mediaLoading
|
||||||
}
|
}
|
||||||
|
|
||||||
Slider {
|
Slider {
|
||||||
|
@ -352,7 +357,7 @@ PanelBackground {
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "hidden"
|
name: "hidden"
|
||||||
when: (manager.playbackState === MediaPlayer.StoppedState || "__hidePlaybackBar" in pageStack.currentPage) && !isFullPage
|
when: ((manager.playbackState === MediaPlayer.StoppedState && !mediaLoading) || "__hidePlaybackBar" in pageStack.currentPage) && !isFullPage
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: playbackBarTranslate
|
target: playbackBarTranslate
|
||||||
// + small padding since the ProgressBar otherwise would stick out
|
// + small padding since the ProgressBar otherwise would stick out
|
||||||
|
|
|
@ -20,7 +20,7 @@ import QtQuick 2.6
|
||||||
import QtMultimedia 5.6
|
import QtMultimedia 5.6
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
import nl.netsoj.chris.Jellyfin 1.0
|
import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
import "videoplayer"
|
import "videoplayer"
|
||||||
import "../"
|
import "../"
|
||||||
|
@ -31,7 +31,8 @@ import "../"
|
||||||
|
|
||||||
SilicaItem {
|
SilicaItem {
|
||||||
id: playerRoot
|
id: playerRoot
|
||||||
property JellyfinItem item
|
//FIXME: Once QTBUG-10822 is resolved, change to J.Item
|
||||||
|
property var item
|
||||||
property string title: item.name
|
property string title: item.name
|
||||||
property bool resume
|
property bool resume
|
||||||
property int progress
|
property int progress
|
||||||
|
@ -39,7 +40,8 @@ SilicaItem {
|
||||||
readonly property bool hudVisible: !hud.hidden || manager.error !== MediaPlayer.NoError
|
readonly property bool hudVisible: !hud.hidden || manager.error !== MediaPlayer.NoError
|
||||||
property int audioTrack: 0
|
property int audioTrack: 0
|
||||||
property int subtitleTrack: 0
|
property int subtitleTrack: 0
|
||||||
property PlaybackManager manager;
|
//FIXME: Once QTBUG-10822 is resolved, change to J.PlaybackManager
|
||||||
|
property var manager;
|
||||||
|
|
||||||
// Blackground to prevent the ambience from leaking through
|
// Blackground to prevent the ambience from leaking through
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -59,21 +61,6 @@ SilicaItem {
|
||||||
manager: playerRoot.manager
|
manager: playerRoot.manager
|
||||||
title: videoPlayer.title
|
title: videoPlayer.title
|
||||||
|
|
||||||
Label {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.horizontalPageMargin
|
|
||||||
text: item.jellyfinId + "\n" + appWindow.playbackManager.streamUrl + "\n"
|
|
||||||
+ (manager.playMethod === PlaybackManager.DirectPlay ? "Direct Play" : "Transcoding") + "\n"
|
|
||||||
+ manager.position + "\n"
|
|
||||||
+ manager.mediaStatus + "\n"
|
|
||||||
// + player.bufferProgress + "\n"
|
|
||||||
// + player.metaData.videoCodec + "@" + player.metaData.videoFrameRate + "(" + player.metaData.videoBitRate + ")" + "\n"
|
|
||||||
// + player.metaData.audioCodec + "(" + player.metaData.audioBitRate + ")" + "\n"
|
|
||||||
// + player.errorString + "\n"
|
|
||||||
font.pixelSize: Theme.fontSizeExtraSmall
|
|
||||||
wrapMode: "WordWrap"
|
|
||||||
visible: appWindow.showDebugInfo
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoError {
|
VideoError {
|
||||||
|
@ -81,11 +68,35 @@ SilicaItem {
|
||||||
player: manager
|
player: manager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.horizontalPageMargin
|
||||||
|
text: item.jellyfinId + "\n" + appWindow.playbackManager.streamUrl + "\n"
|
||||||
|
+ (manager.playMethod === J.PlaybackManager.DirectPlay ? "Direct Play" : "Transcoding") + "\n"
|
||||||
|
+ manager.position + "\n"
|
||||||
|
+ manager.mediaStatus + "\n"
|
||||||
|
// + player.bufferProgress + "\n"
|
||||||
|
// + player.metaData.videoCodec + "@" + player.metaData.videoFrameRate + "(" + player.metaData.videoBitRate + ")" + "\n"
|
||||||
|
// + player.metaData.audioCodec + "(" + player.metaData.audioBitRate + ")" + "\n"
|
||||||
|
// + player.errorString + "\n"
|
||||||
|
font.pixelSize: Theme.fontSizeExtraSmall
|
||||||
|
wrapMode: "WordWrap"
|
||||||
|
visible: appWindow.showDebugInfo
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
enabled: parent.visible
|
||||||
|
onClicked: Clipboard.text = appWindow.playbackManager.streamUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
manager.audioIndex = audioTrack
|
manager.audioIndex = audioTrack
|
||||||
manager.subtitleIndex = subtitleTrack
|
manager.subtitleIndex = subtitleTrack
|
||||||
manager.resumePlayback = resume
|
manager.resumePlayback = resume
|
||||||
manager.playItem(item.jellyfinId)
|
manager.playItem(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
|
|
|
@ -20,11 +20,12 @@ import QtQuick 2.6
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
import QtMultimedia 5.6
|
import QtMultimedia 5.6
|
||||||
|
|
||||||
import nl.netsoj.chris.Jellyfin 1.0
|
import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: videoError
|
id: videoError
|
||||||
property PlaybackManager player
|
//FIXME: Once QTBUG-10822 is resolved, change to J.PlaybackManager
|
||||||
|
property var player
|
||||||
color: pal.palette.overlayBackgroundColor
|
color: pal.palette.overlayBackgroundColor
|
||||||
opacity: player.error === MediaPlayer.NoError ? 0.0 : 1.0
|
opacity: player.error === MediaPlayer.NoError ? 0.0 : 1.0
|
||||||
Behavior on opacity { FadeAnimator {} }
|
Behavior on opacity { FadeAnimator {} }
|
||||||
|
|
|
@ -20,7 +20,7 @@ import QtQuick 2.6
|
||||||
import QtMultimedia 5.6
|
import QtMultimedia 5.6
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
import nl.netsoj.chris.Jellyfin 1.0
|
import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
import "../../Utils.js" as Utils
|
import "../../Utils.js" as Utils
|
||||||
|
|
||||||
|
@ -30,7 +30,8 @@ import "../../Utils.js" as Utils
|
||||||
*/
|
*/
|
||||||
Item {
|
Item {
|
||||||
id: videoHud
|
id: videoHud
|
||||||
property PlaybackManager manager
|
//FIXME: Once QTBUG-10822 is resolved, change to J.PlaybackManager
|
||||||
|
property var manager
|
||||||
property string title
|
property string title
|
||||||
property bool _manuallyActivated: false
|
property bool _manuallyActivated: false
|
||||||
readonly property bool hidden: opacity == 0.0
|
readonly property bool hidden: opacity == 0.0
|
||||||
|
|
|
@ -35,6 +35,7 @@ ApplicationWindow {
|
||||||
// The global mediaPlayer instance
|
// The global mediaPlayer instance
|
||||||
//readonly property MediaPlayer mediaPlayer: _mediaPlayer
|
//readonly property MediaPlayer mediaPlayer: _mediaPlayer
|
||||||
readonly property PlaybackManager playbackManager: _playbackManager
|
readonly property PlaybackManager playbackManager: _playbackManager
|
||||||
|
readonly property ApiClient apiClient: _apiClient
|
||||||
|
|
||||||
// Due QTBUG-10822, declarartions such as `property J.Item foo` are not possible.
|
// Due QTBUG-10822, declarartions such as `property J.Item foo` are not possible.
|
||||||
property QtObject itemData
|
property QtObject itemData
|
||||||
|
@ -46,20 +47,23 @@ ApplicationWindow {
|
||||||
property bool _hidePlaybackBar: false
|
property bool _hidePlaybackBar: false
|
||||||
|
|
||||||
bottomMargin: playbackBar.visibleSize
|
bottomMargin: playbackBar.visibleSize
|
||||||
|
ApiClient {
|
||||||
|
id: _apiClient
|
||||||
|
objectName: "Test"
|
||||||
|
supportedCommands: [J.GeneralCommandType.Play, J.GeneralCommandType.DisplayContent, J.GeneralCommandType.DisplayMessage]
|
||||||
|
}
|
||||||
|
|
||||||
initialPage: Component {
|
initialPage: Component {
|
||||||
MainPage {
|
MainPage {
|
||||||
Connections {
|
Connections {
|
||||||
target: D.ApiClient
|
target: apiClient
|
||||||
// Replace the MainPage if no server was set up.
|
// Replace the MainPage if no server was set up.
|
||||||
|
|
||||||
}
|
}
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (status == PageStatus.Active && !_hasInitialized) {
|
if (status == PageStatus.Active && !_hasInitialized) {
|
||||||
_hasInitialized = true;
|
_hasInitialized = true;
|
||||||
D.ApiClient.restoreSavedSession();
|
apiClient.restoreSavedSession();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,14 +91,9 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*MediaPlayer {
|
|
||||||
id: _mediaPlayer
|
|
||||||
autoPlay: true
|
|
||||||
}*/
|
|
||||||
|
|
||||||
PlaybackManager {
|
PlaybackManager {
|
||||||
id: _playbackManager
|
id: _playbackManager
|
||||||
apiClient: D.ApiClient
|
apiClient: appWindow.apiClient
|
||||||
audioIndex: 0
|
audioIndex: 0
|
||||||
autoOpen: true
|
autoOpen: true
|
||||||
}
|
}
|
||||||
|
@ -111,14 +110,13 @@ ApplicationWindow {
|
||||||
PlaybackBar {
|
PlaybackBar {
|
||||||
id: playbackBar
|
id: playbackBar
|
||||||
manager: _playbackManager
|
manager: _playbackManager
|
||||||
state: "hidden"
|
|
||||||
// CTMBWSIU: Code That Might Break When Silica Is Updated
|
// CTMBWSIU: Code That Might Break When Silica Is Updated
|
||||||
Component.onCompleted: playbackBar.parent = __silica_applicationwindow_instance._rotatingItem
|
Component.onCompleted: playbackBar.parent = __silica_applicationwindow_instance._rotatingItem
|
||||||
}
|
}
|
||||||
|
|
||||||
//FIXME: proper error handling
|
//FIXME: proper error handling
|
||||||
Connections {
|
Connections {
|
||||||
target: D.ApiClient
|
target: apiClient
|
||||||
onNetworkError: errorNotification.show("Network error: " + error)
|
onNetworkError: errorNotification.show("Network error: " + error)
|
||||||
onConnectionFailed: errorNotification.show("Connect error: " + error)
|
onConnectionFailed: errorNotification.show("Connect error: " + error)
|
||||||
//onConnectionSuccess: errorNotification.show("Success: " + loginMessage)
|
//onConnectionSuccess: errorNotification.show("Success: " + loginMessage)
|
||||||
|
|
|
@ -54,7 +54,7 @@ Page {
|
||||||
"Copyright © Chris Josten 2020</p>" +
|
"Copyright © Chris Josten 2020</p>" +
|
||||||
"<p>Sailfin is Free Software licensed under the <a href='lgpl'>LGPL-v2.1</a> or later, at your choice. " +
|
"<p>Sailfin is Free Software licensed under the <a href='lgpl'>LGPL-v2.1</a> or later, at your choice. " +
|
||||||
"Parts of the code of Sailfin are from other libraries. <a href='3rdparty'>View their licenses here</a>.</p>")
|
"Parts of the code of Sailfin are from other libraries. <a href='3rdparty'>View their licenses here</a>.</p>")
|
||||||
.arg(ApiClient.version)
|
.arg(apiClient.version)
|
||||||
textFormat: Text.StyledText
|
textFormat: Text.StyledText
|
||||||
color: Theme.secondaryHighlightColor
|
color: Theme.secondaryHighlightColor
|
||||||
linkColor: Theme.primaryColor
|
linkColor: Theme.primaryColor
|
||||||
|
|
|
@ -23,7 +23,6 @@ import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
import "../components"
|
import "../components"
|
||||||
import "../"
|
import "../"
|
||||||
import "../Utils.js" as Utils
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main page, which simply shows some content of every library, as well as next items.
|
* Main page, which simply shows some content of every library, as well as next items.
|
||||||
|
@ -49,7 +48,7 @@ Page {
|
||||||
text: qsTr("Reload")
|
text: qsTr("Reload")
|
||||||
onClicked: loadModels(true)
|
onClicked: loadModels(true)
|
||||||
}
|
}
|
||||||
busy: userViewsLoader.status === J.UsersViewsLoader.Loading
|
busy: mediaLibraryLoader.status === J.UsersViewsLoader.Loading
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +72,7 @@ Page {
|
||||||
id: mediaLibraryModel
|
id: mediaLibraryModel
|
||||||
loader: J.UsersViewsLoader {
|
loader: J.UsersViewsLoader {
|
||||||
id: mediaLibraryLoader
|
id: mediaLibraryLoader
|
||||||
apiClient: ApiClient
|
apiClient: appWindow.apiClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +80,7 @@ Page {
|
||||||
//- Section header for films and TV shows that an user hasn't completed yet.
|
//- Section header for films and TV shows that an user hasn't completed yet.
|
||||||
text: qsTr("Resume watching")
|
text: qsTr("Resume watching")
|
||||||
clickable: false
|
clickable: false
|
||||||
//busy: userResumeModel.status === J.ApiModel.Loading
|
busy: userResumeLoader.status === J.ApiModel.Loading
|
||||||
Loader {
|
Loader {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
sourceComponent: carrouselView
|
sourceComponent: carrouselView
|
||||||
|
@ -90,10 +89,12 @@ Page {
|
||||||
|
|
||||||
J.ItemModel {
|
J.ItemModel {
|
||||||
id: userResumeModel
|
id: userResumeModel
|
||||||
// Resume model
|
loader: J.ResumeItemsLoader {
|
||||||
/*apiClient: ApiClient
|
id: userResumeLoader
|
||||||
limit: 12
|
apiClient: appWindow.apiClient
|
||||||
recursive: true*/
|
limit: 12
|
||||||
|
//recursive: true*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,7 +112,7 @@ Page {
|
||||||
|
|
||||||
J.ItemModel {
|
J.ItemModel {
|
||||||
id: showNextUpModel
|
id: showNextUpModel
|
||||||
/*apiClient: ApiClient
|
/*apiClient: appWindow.apiClient
|
||||||
limit: 12*/
|
limit: 12*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +133,7 @@ Page {
|
||||||
J.ItemModel {
|
J.ItemModel {
|
||||||
id: userItemModel
|
id: userItemModel
|
||||||
loader: J.LatestMediaLoader {
|
loader: J.LatestMediaLoader {
|
||||||
apiClient: ApiClient
|
apiClient: appWindow.apiClient
|
||||||
parentId: jellyfinId
|
parentId: jellyfinId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,7 +172,7 @@ Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: ApiClient
|
target: appWindow.apiClient
|
||||||
onAuthenticatedChanged: loadModels(false)
|
onAuthenticatedChanged: loadModels(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +182,7 @@ Page {
|
||||||
* even if loaded.
|
* even if loaded.
|
||||||
*/
|
*/
|
||||||
function loadModels(force) {
|
function loadModels(force) {
|
||||||
if (force || (ApiClient.authenticated && !_modelsLoaded)) {
|
if (force || (appWindow.apiClient.authenticated && !_modelsLoaded)) {
|
||||||
_modelsLoaded = true;
|
_modelsLoaded = true;
|
||||||
mediaLibraryModel.reload()
|
mediaLibraryModel.reload()
|
||||||
//userResumeModel.reload()
|
//userResumeModel.reload()
|
||||||
|
@ -192,14 +193,15 @@ Page {
|
||||||
Component {
|
Component {
|
||||||
id: carrouselView
|
id: carrouselView
|
||||||
SilicaListView {
|
SilicaListView {
|
||||||
|
property bool isPortrait: Utils.usePortraitCover(collectionType)
|
||||||
id: list
|
id: list
|
||||||
clip: true
|
clip: true
|
||||||
height: {
|
height: {
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
if (["tvshows", "movies"].indexOf(collectionType) == -1) {
|
if (isPortrait) {
|
||||||
Constants.libraryDelegateHeight
|
|
||||||
} else {
|
|
||||||
Constants.libraryDelegatePosterHeight
|
Constants.libraryDelegatePosterHeight
|
||||||
|
} else {
|
||||||
|
Constants.libraryDelegateHeight
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
|
@ -217,12 +219,12 @@ Page {
|
||||||
delegate: LibraryItemDelegate {
|
delegate: LibraryItemDelegate {
|
||||||
property string id: model.jellyfinId
|
property string id: model.jellyfinId
|
||||||
title: model.name
|
title: model.name
|
||||||
poster: Utils.itemModelImageUrl(ApiClient.baseUrl, model.jellyfinId, model.imageTags["Primary"], "Primary", {"maxHeight": height})
|
poster: Utils.itemModelImageUrl(appWindow.apiClient.baseUrl, model.jellyfinId, model.imageTags["Primary"], "Primary", {"maxHeight": height})
|
||||||
Binding on blurhash {
|
Binding on blurhash {
|
||||||
when: poster !== ""
|
when: poster !== ""
|
||||||
value: model.imageBlurHashes["Primary"][model.imageTags["Primary"]]
|
value: model.imageBlurHashes["Primary"][model.imageTags["Primary"]]
|
||||||
}
|
}
|
||||||
landscape: !Utils.usePortraitCover(collectionType)
|
landscape: !isPortrait
|
||||||
progress: (typeof model.userData !== "undefined") ? model.userData.playedPercentage / 100 : 0.0
|
progress: (typeof model.userData !== "undefined") ? model.userData.playedPercentage / 100 : 0.0
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import Sailfish.Silica 1.0
|
||||||
import nl.netsoj.chris.Jellyfin 1.0 as J
|
import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
import "../components"
|
import "../components"
|
||||||
|
import ".."
|
||||||
|
|
||||||
Page {
|
Page {
|
||||||
id: settingsPage
|
id: settingsPage
|
||||||
|
@ -69,7 +70,7 @@ Page {
|
||||||
top: parent.top
|
top: parent.top
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
}
|
}
|
||||||
source: ApiClient.baseUrl + "/Users/" + ApiClient.userId + "/Images/Primary?tag=" + loggedInUser.primaryImageTag
|
source: apiClient.baseUrl + "/Users/" + apiClient.userId + "/Images/Primary?tag=" + loggedInUser.primaryImageTag
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -80,7 +81,7 @@ Page {
|
||||||
bottom: parent.verticalCenter
|
bottom: parent.verticalCenter
|
||||||
right: parent.right
|
right: parent.right
|
||||||
}
|
}
|
||||||
text: loggedInUser.status == User.Ready ? loggedInUser.name : ApiClient.userId
|
text: loggedInUser.status == User.Ready ? loggedInUser.name : apiClient.userId
|
||||||
color: Theme.highlightColor
|
color: Theme.highlightColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +93,7 @@ Page {
|
||||||
top: parent.verticalCenter
|
top: parent.verticalCenter
|
||||||
right: parent.right
|
right: parent.right
|
||||||
}
|
}
|
||||||
text: ApiClient.baseUrl
|
text: apiClient.baseUrl
|
||||||
color: Theme.secondaryHighlightColor
|
color: Theme.secondaryHighlightColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,21 +105,23 @@ Page {
|
||||||
ButtonLayout {
|
ButtonLayout {
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("Log out")
|
text: qsTr("Log out")
|
||||||
onClicked: remorse.execute(qsTr("Logging out"), ApiClient.deleteSession)
|
onClicked: remorse.execute(qsTr("Logging out"), apiClient.deleteSession)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SectionHeader {
|
SectionHeader {
|
||||||
//: Other settings
|
//: Other settings menu item
|
||||||
text: qsTr("Other")
|
text: qsTr("Other")
|
||||||
}
|
}
|
||||||
|
|
||||||
IconListItem {
|
IconListItem {
|
||||||
|
//: Debug information settings menu itemy
|
||||||
text: qsTr("Debug information")
|
text: qsTr("Debug information")
|
||||||
iconSource: "image://theme/icon-s-developer"
|
iconSource: "image://theme/icon-s-developer"
|
||||||
onClicked: pageStack.push(Qt.resolvedUrl("settings/DebugPage.qml"))
|
onClicked: pageStack.push(Qt.resolvedUrl("settings/DebugPage.qml"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//: About Sailfin settings menu itemy
|
||||||
IconListItem {
|
IconListItem {
|
||||||
text: qsTr("About Sailfin")
|
text: qsTr("About Sailfin")
|
||||||
iconSource: "image://theme/icon-m-about"
|
iconSource: "image://theme/icon-m-about"
|
||||||
|
|
|
@ -21,7 +21,7 @@ import Sailfish.Silica 1.0
|
||||||
|
|
||||||
import "../components"
|
import "../components"
|
||||||
|
|
||||||
import nl.netsoj.chris.Jellyfin 1.0
|
import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page only containing a video player.
|
* Page only containing a video player.
|
||||||
|
@ -33,7 +33,7 @@ Page {
|
||||||
id: videoPage
|
id: videoPage
|
||||||
// PlaybackBar will hide itself when it encounters a page with such a property
|
// PlaybackBar will hide itself when it encounters a page with such a property
|
||||||
property bool __hidePlaybackBar: true
|
property bool __hidePlaybackBar: true
|
||||||
property JellyfinItem itemData
|
property var itemData
|
||||||
property int audioTrack
|
property int audioTrack
|
||||||
property int subtitleTrack
|
property int subtitleTrack
|
||||||
property bool resume: true
|
property bool resume: true
|
||||||
|
|
|
@ -22,7 +22,7 @@ import Sailfish.Silica 1.0
|
||||||
import nl.netsoj.chris.Jellyfin 1.0 as J
|
import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
import "../../components"
|
import "../../components"
|
||||||
import "../.."
|
import "../../"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This page displays details about a film, show, season, episode, and so on.
|
* This page displays details about a film, show, season, episode, and so on.
|
||||||
|
@ -34,8 +34,6 @@ Page {
|
||||||
id: pageRoot
|
id: pageRoot
|
||||||
property string itemId: ""
|
property string itemId: ""
|
||||||
property alias itemData: jItemLoader.data
|
property alias itemData: jItemLoader.data
|
||||||
//property string itemId: ""
|
|
||||||
//property var itemData: ({})
|
|
||||||
property bool _loading: jItemLoader.status === J.ItemLoader.Loading
|
property bool _loading: jItemLoader.status === J.ItemLoader.Loading
|
||||||
readonly property bool hasLogo: (typeof itemData.imageTags !== "undefined") && (typeof itemData.imageTags["Logo"] !== "undefined")
|
readonly property bool hasLogo: (typeof itemData.imageTags !== "undefined") && (typeof itemData.imageTags["Logo"] !== "undefined")
|
||||||
property string _chosenBackdropImage: ""
|
property string _chosenBackdropImage: ""
|
||||||
|
@ -46,10 +44,10 @@ Page {
|
||||||
if (itemData.backdropImageTags.length > 0) {
|
if (itemData.backdropImageTags.length > 0) {
|
||||||
rand = Math.floor(Math.random() * (itemData.backdropImageTags.length - 0.001))
|
rand = Math.floor(Math.random() * (itemData.backdropImageTags.length - 0.001))
|
||||||
console.log("Random: ", rand)
|
console.log("Random: ", rand)
|
||||||
_chosenBackdropImage = ApiClient.baseUrl + "/Items/" + itemId + "/Images/Backdrop/" + rand + "?tag=" +itemData.backdropImageTags[rand] + "&maxHeight" + height
|
_chosenBackdropImage = apiClient.baseUrl + "/Items/" + itemId + "/Images/Backdrop/" + rand + "?tag=" +itemData.backdropImageTags[rand] + "&maxHeight" + height
|
||||||
} else if (itemData.parentBackdropImageTags.length > 0) {
|
} else if (itemData.parentBackdropImageTags.length > 0) {
|
||||||
rand = Math.floor(Math.random() * (itemData.parentBackdropImageTags.length - 0.001))
|
rand = Math.floor(Math.random() * (itemData.parentBackdropImageTags.length - 0.001))
|
||||||
_chosenBackdropImage = ApiClient.baseUrl + "/Items/" + itemData.parentBackdropItemId + "/Images/Backdrop/" + rand + "?tag=" + itemData.parentBackdropImageTags[0]
|
_chosenBackdropImage = apiClient.baseUrl + "/Items/" + itemData.parentBackdropItemId + "/Images/Backdrop/" + rand + "?tag=" + itemData.parentBackdropImageTags[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,8 +84,9 @@ Page {
|
||||||
|
|
||||||
J.ItemLoader {
|
J.ItemLoader {
|
||||||
id: jItemLoader
|
id: jItemLoader
|
||||||
apiClient: ApiClient
|
apiClient: appWindow.apiClient
|
||||||
itemId: pageRoot.itemId
|
itemId: pageRoot.itemId
|
||||||
|
autoReload: false
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
console.log("Status changed: " + newStatus, JSON.stringify(jItemLoader.data))
|
console.log("Status changed: " + newStatus, JSON.stringify(jItemLoader.data))
|
||||||
if (status === J.ItemLoader.Ready) {
|
if (status === J.ItemLoader.Ready) {
|
||||||
|
@ -96,15 +95,13 @@ Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "ItemLoader status=%1, \nitemId=%2\nitemData=%3".arg(jItemLoader.status).arg(jItemLoader.itemId).arg(jItemLoader.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (status == PageStatus.Deactivating) {
|
if (status === PageStatus.Deactivating) {
|
||||||
//appWindow.itemData = ({})
|
//appWindow.itemData = ({})
|
||||||
}
|
}
|
||||||
if (status == PageStatus.Active) {
|
if (status === PageStatus.Active) {
|
||||||
|
console.log("Page ready, ItemID: ", itemId, ", UserID: ", apiClient.userId)
|
||||||
|
jItemLoader.autoReload = true
|
||||||
//appWindow.itemData = jItemLoader.data
|
//appWindow.itemData = jItemLoader.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,10 +29,13 @@ BaseDetailPage {
|
||||||
|
|
||||||
J.ItemModel {
|
J.ItemModel {
|
||||||
id: collectionModel
|
id: collectionModel
|
||||||
//sortBy: ["SortName"]
|
|
||||||
loader: J.UserItemsLoader {
|
loader: J.UserItemsLoader {
|
||||||
apiClient: ApiClient
|
id: collectionLoader
|
||||||
|
apiClient: appWindow.apiClient
|
||||||
parentId: itemData.jellyfinId
|
parentId: itemData.jellyfinId
|
||||||
|
autoReload: itemData.jellyfinId.length > 0
|
||||||
|
onParentIdChanged: if (parentId.length > 0) reload()
|
||||||
|
sortBy: "SortName"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +64,7 @@ BaseDetailPage {
|
||||||
RemoteImage {
|
RemoteImage {
|
||||||
id: itemImage
|
id: itemImage
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.jellyfinId, model.imageTags.Primary, "Primary", {"maxWidth": width})
|
source: Utils.itemModelImageUrl(apiClient.baseUrl, model.jellyfinId, model.imageTags.Primary, "Primary", {"maxWidth": width})
|
||||||
blurhash: model.imageBlurHashes.Primary[model.imageTags.Primary]
|
blurhash: model.imageBlurHashes.Primary[model.imageTags.Primary]
|
||||||
fallbackColor: Utils.colorFromString(model.name)
|
fallbackColor: Utils.colorFromString(model.name)
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
@ -138,20 +141,20 @@ BaseDetailPage {
|
||||||
MenuItem {
|
MenuItem {
|
||||||
//: Sort order
|
//: Sort order
|
||||||
text: qsTr("Ascending")
|
text: qsTr("Ascending")
|
||||||
onClicked: apply(model.value, ApiModel.Ascending)
|
onClicked: apply(model.value, "Ascending")
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
//: Sort order
|
//: Sort order
|
||||||
text: qsTr("Descending")
|
text: qsTr("Descending")
|
||||||
onClicked: apply(model.value, ApiModel.Descending)
|
onClicked: apply(model.value, "Descending")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onClicked: openMenu()
|
onClicked: openMenu()
|
||||||
|
|
||||||
function apply(field, order) {
|
function apply(field, order) {
|
||||||
collectionModel.sortBy = [field];
|
collectionLoader.sortBy = field;
|
||||||
collectionModel.sortOrder = order;
|
collectionLoader.sortOrder = order;
|
||||||
collectionModel.reload()
|
collectionLoader.reload()
|
||||||
pageStack.pop()
|
pageStack.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,11 +36,12 @@ BaseDetailPage {
|
||||||
J.ItemModel {
|
J.ItemModel {
|
||||||
id: collectionModel
|
id: collectionModel
|
||||||
loader: J.UserItemsLoader {
|
loader: J.UserItemsLoader {
|
||||||
apiClient: ApiClient
|
apiClient: appWindow.apiClient
|
||||||
//sortBy: ["SortName"]
|
sortBy: "SortName"
|
||||||
//fields: ["ItemCounts","PrimaryImageAspectRatio","BasicSyncInfo","CanDelete","MediaSourceCount"]
|
//fields: ["ItemCounts","PrimaryImageAspectRatio","BasicSyncInfo","CanDelete","MediaSourceCount"]
|
||||||
parentId: itemData.jellyfinId
|
parentId: itemData.jellyfinId
|
||||||
onParentIdChanged: reload()
|
autoReload: itemData.jellyfinId.length > 0
|
||||||
|
onParentIdChanged: if (parentId.length > 0) reload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
@ -52,9 +53,9 @@ BaseDetailPage {
|
||||||
visible: _twoColumns
|
visible: _twoColumns
|
||||||
Layout.minimumWidth: 1000 / Theme.pixelRatio
|
Layout.minimumWidth: 1000 / Theme.pixelRatio
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
/*source: visible
|
source: visible
|
||||||
? "../../components/music/WideAlbumCover.qml" : ""
|
? "../../components/music/WideAlbumCover.qml" : ""
|
||||||
onLoaded: bindAlbum(item)*/
|
onLoaded: bindAlbum(item)
|
||||||
}
|
}
|
||||||
Item {height: 1; width: Theme.horizontalPageMargin; visible: wideAlbumCover.visible; }
|
Item {height: 1; width: Theme.horizontalPageMargin; visible: wideAlbumCover.visible; }
|
||||||
SilicaListView {
|
SilicaListView {
|
||||||
|
@ -64,8 +65,8 @@ BaseDetailPage {
|
||||||
model: collectionModel
|
model: collectionModel
|
||||||
header: Loader {
|
header: Loader {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
/*source: "../../components/music/NarrowAlbumCover.qml"
|
source: "../../components/music/NarrowAlbumCover.qml"
|
||||||
onLoaded: bindAlbum(item)*/
|
onLoaded: bindAlbum(item)
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
property: "parentIndexNumber"
|
property: "parentIndexNumber"
|
||||||
|
@ -79,7 +80,7 @@ BaseDetailPage {
|
||||||
artists: model.artists
|
artists: model.artists
|
||||||
duration: model.runTimeTicks
|
duration: model.runTimeTicks
|
||||||
indexNumber: model.indexNumber
|
indexNumber: model.indexNumber
|
||||||
onClicked: window.playbackManager.playItem(model.jellyfinId)
|
onClicked: window.playbackManager.playItemInList(collectionModel, model.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
VerticalScrollDecorator {}
|
VerticalScrollDecorator {}
|
||||||
|
@ -87,7 +88,7 @@ BaseDetailPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindAlbum(item) {
|
function bindAlbum(item) {
|
||||||
//item.albumArt = Qt.binding(function(){ return Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width})})
|
item.albumArt = Qt.binding(function(){ return Utils.itemImageUrl(apiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width})})
|
||||||
item.name = Qt.binding(function(){ return itemData.name})
|
item.name = Qt.binding(function(){ return itemData.name})
|
||||||
item.releaseYear = Qt.binding(function() { return itemData.productionYear})
|
item.releaseYear = Qt.binding(function() { return itemData.productionYear})
|
||||||
item.albumArtist = Qt.binding(function() { return itemData.albumArtist})
|
item.albumArtist = Qt.binding(function() { return itemData.albumArtist})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import QtQuick 2.6
|
import QtQuick 2.6
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
import nl.netsoj.chris.Jellyfin 1.0
|
import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
import "../../components"
|
import "../../components"
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ BaseDetailPage {
|
||||||
|
|
||||||
RemoteImage {
|
RemoteImage {
|
||||||
id: image
|
id: image
|
||||||
source: ApiClient.downloadUrl(itemId)
|
source: apiClient.downloadUrl(itemId)
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
|
|
|
@ -19,19 +19,22 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
import QtQuick 2.6
|
import QtQuick 2.6
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
import nl.netsoj.chris.Jellyfin 1.0
|
import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
import "../.."
|
import "../.."
|
||||||
import "../../components"
|
import "../../components"
|
||||||
import ".."
|
import ".."
|
||||||
|
|
||||||
BaseDetailPage {
|
BaseDetailPage {
|
||||||
ShowEpisodesModel {
|
J.ItemModel {
|
||||||
id: episodeModel
|
id: episodeModel
|
||||||
apiClient: ApiClient
|
loader: J.ShowEpisodesLoader {
|
||||||
show: itemData.seriesId
|
apiClient: appWindow.apiClient
|
||||||
seasonId: itemData.jellyfinId
|
seriesId: itemData.seriesId
|
||||||
fields: ["Overview"]
|
seasonId: itemData.jellyfinId
|
||||||
|
fields: [J.ItemFields.Overview]
|
||||||
|
autoReload: itemData.jellyfinId.length > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
@ -42,7 +45,7 @@ BaseDetailPage {
|
||||||
SilicaListView {
|
SilicaListView {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
contentHeight: content.height
|
contentHeight: content.height
|
||||||
visible: itemData.status !== JellyfinItem.Error
|
//visible: itemData.status !== JellyfinItem.Error
|
||||||
|
|
||||||
header: PageHeader {
|
header: PageHeader {
|
||||||
title: itemData.name
|
title: itemData.name
|
||||||
|
@ -60,7 +63,7 @@ BaseDetailPage {
|
||||||
}
|
}
|
||||||
width: Constants.libraryDelegateWidth
|
width: Constants.libraryDelegateWidth
|
||||||
height: Constants.libraryDelegateHeight
|
height: Constants.libraryDelegateHeight
|
||||||
source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.jellyfinId, model.imageTags.Primary, "Primary", {"maxHeight": height})
|
source: Utils.itemModelImageUrl(apiClient.baseUrl, model.jellyfinId, model.imageTags.Primary, "Primary", {"maxHeight": height})
|
||||||
blurhash: model.imageBlurHashes.Primary[model.imageTags.Primary]
|
blurhash: model.imageBlurHashes.Primary[model.imageTags.Primary]
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
clip: true
|
clip: true
|
||||||
|
@ -157,8 +160,8 @@ BaseDetailPage {
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (status == PageStatus.Active) {
|
if (status == PageStatus.Active) {
|
||||||
//console.log(JSON.stringify(itemData))
|
//console.log(JSON.stringify(itemData))
|
||||||
episodeModel.show = itemData.seriesId
|
//episodeModel.show = itemData.seriesId
|
||||||
episodeModel.seasonId = itemData.jellyfinId
|
//episodeModel.seasonId = itemData.jellyfinId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
import QtQuick 2.6
|
import QtQuick 2.6
|
||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
import nl.netsoj.chris.Jellyfin 1.0
|
import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
import "../../components"
|
import "../../components"
|
||||||
import "../.."
|
import "../.."
|
||||||
|
@ -28,7 +28,7 @@ BaseDetailPage {
|
||||||
SilicaFlickable {
|
SilicaFlickable {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
contentHeight: content.height
|
contentHeight: content.height
|
||||||
visible: itemData.status !== JellyfinItem.Error
|
//visible: itemData.status !== JellyfinItem.Error
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: content
|
id: content
|
||||||
|
@ -47,7 +47,7 @@ BaseDetailPage {
|
||||||
RemoteImage {
|
RemoteImage {
|
||||||
id: logoImage
|
id: logoImage
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
source: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Logo")
|
source: Utils.itemImageUrl(apiClient.baseUrl, itemData, "Logo")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,11 +63,14 @@ BaseDetailPage {
|
||||||
text: qsTr("Seasons")
|
text: qsTr("Seasons")
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowSeasonsModel {
|
J.ItemModel {
|
||||||
id: showSeasonsModel
|
id: showSeasonsModel
|
||||||
apiClient: ApiClient
|
loader: J.ShowSeasonsLoader {
|
||||||
show: itemData.jellyfinId
|
id: showSeasonLoader
|
||||||
onShowChanged: reload()
|
apiClient: appWindow.apiClient
|
||||||
|
seriesId: itemData.jellyfinId
|
||||||
|
autoReload: itemData.jellyfinId.length > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Connections {
|
Connections {
|
||||||
target: itemData
|
target: itemData
|
||||||
|
@ -84,7 +87,7 @@ BaseDetailPage {
|
||||||
leftMargin: Theme.horizontalPageMargin
|
leftMargin: Theme.horizontalPageMargin
|
||||||
rightMargin: Theme.horizontalPageMargin
|
rightMargin: Theme.horizontalPageMargin
|
||||||
delegate: LibraryItemDelegate {
|
delegate: LibraryItemDelegate {
|
||||||
poster: Utils.itemModelImageUrl(ApiClient.baseUrl, model.jellyfinId, model.imageTags.Primary, "Primary", {"maxHeight": height})
|
poster: Utils.itemModelImageUrl(apiClient.baseUrl, model.jellyfinId, model.imageTags.Primary, "Primary", {"maxHeight": height})
|
||||||
blurhash: model.imageBlurHashes["Primary"][model.imageTags.Primary]
|
blurhash: model.imageBlurHashes["Primary"][model.imageTags.Primary]
|
||||||
title: model.name
|
title: model.name
|
||||||
onClicked: pageStack.push(Utils.getPageUrl(model.mediaType, model.type), {"itemId": model.jellyfinId})
|
onClicked: pageStack.push(Utils.getPageUrl(model.mediaType, model.type), {"itemId": model.jellyfinId})
|
||||||
|
|
|
@ -31,7 +31,7 @@ BaseDetailPage {
|
||||||
|
|
||||||
enabled: true
|
enabled: true
|
||||||
text: qsTr("Item type (%1) unsupported").arg(itemData.type)
|
text: qsTr("Item type (%1) unsupported").arg(itemData.type)
|
||||||
hintText: qsTr("This is still an alpha version :)")
|
hintText: qsTr("Fallback page for %2 not found either\nThis is still an alpha version :)").arg(itemData.mediaType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ BaseDetailPage {
|
||||||
PlayToolbar {
|
PlayToolbar {
|
||||||
id: toolbar
|
id: toolbar
|
||||||
width: parent.width
|
width: parent.width
|
||||||
imageSource: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width})
|
imageSource: Utils.itemImageUrl(apiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width})
|
||||||
imageAspectRatio: Constants.horizontalVideoAspectRatio
|
imageAspectRatio: Constants.horizontalVideoAspectRatio
|
||||||
imageBlurhash: itemData.imageBlurHashes["Primary"][itemData.imageTags["Primary"]]
|
imageBlurhash: itemData.imageBlurHashes["Primary"][itemData.imageTags["Primary"]]
|
||||||
Binding on favourited {
|
Binding on favourited {
|
||||||
|
|
|
@ -54,7 +54,7 @@ Page {
|
||||||
label: qsTr("Connection state")
|
label: qsTr("Connection state")
|
||||||
value: {
|
value: {
|
||||||
var stateText
|
var stateText
|
||||||
switch(ApiClient.websocket.state) {
|
switch(apiClient.websocket.state) {
|
||||||
case 0:
|
case 0:
|
||||||
//- Socket state
|
//- Socket state
|
||||||
stateText = qsTr("Unconnected");
|
stateText = qsTr("Unconnected");
|
||||||
|
@ -85,7 +85,7 @@ Page {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//- Socket state: "state no (state description)"
|
//- Socket state: "state no (state description)"
|
||||||
qsTr("%1 (%2)").arg(ApiClient.websocket.state).arg(stateText)
|
qsTr("%1 (%2)").arg(apiClient.websocket.state).arg(stateText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ Page {
|
||||||
Label {
|
Label {
|
||||||
id: deviceProfile
|
id: deviceProfile
|
||||||
color: Theme.secondaryHighlightColor
|
color: Theme.secondaryHighlightColor
|
||||||
text: JSON.stringify(ApiClient.deviceProfile, null, '\t')
|
text: JSON.stringify(apiClient.deviceProfile, null, '\t')
|
||||||
}
|
}
|
||||||
HorizontalScrollDecorator {}
|
HorizontalScrollDecorator {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,27 +26,26 @@ import "../.."
|
||||||
* Page to indicate that the application is connecting to a server.
|
* Page to indicate that the application is connecting to a server.
|
||||||
*/
|
*/
|
||||||
Page {
|
Page {
|
||||||
property string serverName
|
property string serverName
|
||||||
property string serverAddress
|
property string serverAddress
|
||||||
property Page firstPage
|
property Page firstPage
|
||||||
|
|
||||||
allowedOrientations: Orientation.All
|
allowedOrientations: Orientation.All
|
||||||
|
|
||||||
|
|
||||||
BusyLabel {
|
BusyLabel {
|
||||||
text: qsTr("Connecting to %1").arg(serverName)
|
text: qsTr("Connecting to %1").arg(serverName)
|
||||||
running: true
|
running: true
|
||||||
}
|
}
|
||||||
|
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (status == PageStatus.Active) {
|
if (status == PageStatus.Active) {
|
||||||
console.log("Connecting page active");
|
apiClient.setupConnection();
|
||||||
ApiClient.setupConnection();
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: ApiClient
|
target: apiClient
|
||||||
onConnectionSuccess: {
|
onConnectionSuccess: {
|
||||||
console.log("Login success: " + loginMessage);
|
console.log("Login success: " + loginMessage);
|
||||||
pageStack.replace(Qt.resolvedUrl("LoginDialog.qml"), {"loginMessage": loginMessage, "firstPage": firstPage});
|
pageStack.replace(Qt.resolvedUrl("LoginDialog.qml"), {"loginMessage": loginMessage, "firstPage": firstPage});
|
||||||
|
|
|
@ -28,40 +28,40 @@ import "../../"
|
||||||
* This dialog allows manual address entry or use one of the addresses discovered via UDP broadcasts.
|
* This dialog allows manual address entry or use one of the addresses discovered via UDP broadcasts.
|
||||||
*/
|
*/
|
||||||
Dialog {
|
Dialog {
|
||||||
id: dialogRoot
|
id: dialogRoot
|
||||||
allowedOrientations: Orientation.All
|
allowedOrientations: Orientation.All
|
||||||
// Picks the address of the ComboBox if selected, otherwise the manual address entry
|
// Picks the address of the ComboBox if selected, otherwise the manual address entry
|
||||||
readonly property string address: serverSelect.currentItem._address
|
readonly property string address: serverSelect.currentItem._address
|
||||||
readonly property bool addressCorrect: serverSelect.currentIndex > 0 || manualAddress.acceptableInput
|
readonly property bool addressCorrect: serverSelect.currentIndex > 0 || manualAddress.acceptableInput
|
||||||
readonly property string serverName: serverSelect.currentItem._name
|
readonly property string serverName: serverSelect.currentItem._name
|
||||||
readonly property bool _isSetupPage: true
|
readonly property bool _isSetupPage: true
|
||||||
|
|
||||||
|
|
||||||
acceptDestination: AddServerConnectingPage {
|
acceptDestination: AddServerConnectingPage {
|
||||||
id: connectingPage
|
id: connectingPage
|
||||||
serverName: dialogRoot.serverName
|
serverName: dialogRoot.serverName
|
||||||
serverAddress: address
|
serverAddress: address
|
||||||
firstPage: dialogRoot
|
firstPage: dialogRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
DialogHeader {
|
DialogHeader {
|
||||||
acceptText: qsTr("Connect")
|
acceptText: qsTr("Connect")
|
||||||
title: qsTr("Connect to Jellyfin")
|
title: qsTr("Connect to Jellyfin")
|
||||||
}
|
}
|
||||||
|
|
||||||
J.ServerDiscoveryModel {
|
J.ServerDiscoveryModel {
|
||||||
id: serverModel
|
id: serverModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ComboBox {
|
ComboBox {
|
||||||
id: serverSelect
|
id: serverSelect
|
||||||
label: qsTr("Server")
|
label: qsTr("Server")
|
||||||
description: qsTr("Sailfin will try to search for Jellyfin servers on your local network automatically")
|
description: qsTr("Sailfin will try to search for Jellyfin servers on your local network automatically")
|
||||||
|
|
||||||
menu: ContextMenu {
|
menu: ContextMenu {
|
||||||
MenuItem {
|
MenuItem {
|
||||||
// Special values are cool, aren't they?
|
// Special values are cool, aren't they?
|
||||||
readonly property string _address: manualAddress.text
|
readonly property string _address: manualAddress.text
|
||||||
|
@ -106,8 +106,8 @@ Dialog {
|
||||||
|
|
||||||
function tryConnect() {
|
function tryConnect() {
|
||||||
console.log("Hi there!")
|
console.log("Hi there!")
|
||||||
ApiClient.baseUrl = address;
|
apiClient.baseUrl = address;
|
||||||
//ApiClient.setupConnection()
|
//apiClient.setupConnection()
|
||||||
//fakeTimer.start()
|
//fakeTimer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,14 +46,14 @@ Dialog {
|
||||||
}
|
}
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if(status == PageStatus.Active) {
|
if(status == PageStatus.Active) {
|
||||||
ApiClient.authenticate(username.text, password.text, true)
|
apiClient.authenticate(username.text, password.text, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: ApiClient
|
target: apiClient
|
||||||
onAuthenticatedChanged: {
|
onAuthenticatedChanged: {
|
||||||
if (ApiClient.authenticated) {
|
if (apiClient.authenticated) {
|
||||||
console.log("authenticated!")
|
console.log("authenticated!")
|
||||||
pageStack.replaceAbove(null, Qt.resolvedUrl("../MainPage.qml"))
|
pageStack.replaceAbove(null, Qt.resolvedUrl("../MainPage.qml"))
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ Dialog {
|
||||||
|
|
||||||
/*PublicUserModel {
|
/*PublicUserModel {
|
||||||
id: userModel
|
id: userModel
|
||||||
apiClient: ApiClient
|
apiClient: appWindow.apiClient
|
||||||
Component.onCompleted: reload();
|
Component.onCompleted: reload();
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ Dialog {
|
||||||
model: 0 //userModel
|
model: 0 //userModel
|
||||||
delegate: UserGridDelegate {
|
delegate: UserGridDelegate {
|
||||||
name: model.name
|
name: model.name
|
||||||
image: model.primaryImageTag ? "%1/Users/%2/Images/Primary?tag=%3".arg(ApiClient.baseUrl).arg(model.jellyfinId).arg(model.primaryImageTag) : ""
|
image: model.primaryImageTag ? "%1/Users/%2/Images/Primary?tag=%3".arg(apiClient.baseUrl).arg(model.jellyfinId).arg(model.primaryImageTag) : ""
|
||||||
highlighted: model.name === username.text
|
highlighted: model.name === username.text
|
||||||
onHighlightedChanged: {
|
onHighlightedChanged: {
|
||||||
if (highlighted) {
|
if (highlighted) {
|
||||||
|
|
|
@ -16,5 +16,4 @@
|
||||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
singleton Constants 1.0 Constants.qml
|
singleton Constants 1.0 Constants.qml
|
||||||
singleton ApiClient 1.0 ApiClient.qml
|
|
||||||
Utils 1.0 Utils.js
|
Utils 1.0 Utils.js
|
||||||
|
|
|
@ -69,7 +69,7 @@ int main(int argc, char *argv[]) {
|
||||||
if (canSanbox && !cmdParser.isSet(sandboxOption)) {
|
if (canSanbox && !cmdParser.isSet(sandboxOption)) {
|
||||||
qDebug() << "Restarting in sandbox mode";
|
qDebug() << "Restarting in sandbox mode";
|
||||||
QProcess::execute(QString(SANDBOX_PROGRAM),
|
QProcess::execute(QString(SANDBOX_PROGRAM),
|
||||||
QStringList() << "-p" << "harbour-sailfin.desktop" << "/usr/bin/harbour-sailfin");
|
QStringList() << "-p" << "harbour-sailfin.desktop" << "harbour-sailfin");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue