mirror of
https://github.com/HenkKalkwater/harbour-sailfin.git
synced 2024-12-22 14:05:18 +00:00
sailfish: add LiveTvChannels page
This ocmmit adds a LiveTvChannels page for displaying the programs that are now playing. The section Live TV Channels on the main page now shows the TV channel list in order of the channel number. Additionally, it fixes an issue in ApiModel, where it would not reload when a new loader was assigned. This is now fixed and some code on pages that worked around this fix has been removed.
This commit is contained in:
parent
edcd3a93af
commit
57b67292fd
1
core/3rdparty/qtpromise
vendored
Submodule
1
core/3rdparty/qtpromise
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f7639e921ee7b0d20de4ac0da67c0f69e0692101
|
|
@ -239,23 +239,33 @@ bool setRequestStartIndex(P ¶meters, int startIndex) {
|
||||||
#ifndef JELLYFIN_APIMODEL_CPP
|
#ifndef JELLYFIN_APIMODEL_CPP
|
||||||
extern template bool setRequestStartIndex(Loader::GetUserViewsParams ¶ms, int startIndex);
|
extern template bool setRequestStartIndex(Loader::GetUserViewsParams ¶ms, int startIndex);
|
||||||
extern template void setRequestLimit(Loader::GetUserViewsParams ¶ms, int limit);
|
extern template void setRequestLimit(Loader::GetUserViewsParams ¶ms, int limit);
|
||||||
|
|
||||||
extern template QList<DTO::BaseItemDto> extractRecords(const DTO::BaseItemDtoQueryResult &result);
|
extern template QList<DTO::BaseItemDto> extractRecords(const DTO::BaseItemDtoQueryResult &result);
|
||||||
extern template int extractTotalRecordCount(const DTO::BaseItemDtoQueryResult &result);
|
extern template int extractTotalRecordCount(const DTO::BaseItemDtoQueryResult &result);
|
||||||
extern template QList<DTO::BaseItemDto> extractRecords(const QList<DTO::BaseItemDto> &result);
|
extern template QList<DTO::BaseItemDto> extractRecords(const QList<DTO::BaseItemDto> &result);
|
||||||
extern template int extractTotalRecordCount(const QList<DTO::BaseItemDto> &result);
|
extern template int extractTotalRecordCount(const QList<DTO::BaseItemDto> &result);
|
||||||
|
|
||||||
extern template void setRequestLimit(Loader::GetLatestMediaParams ¶ms, int limit);
|
extern template void setRequestLimit(Loader::GetLatestMediaParams ¶ms, int limit);
|
||||||
extern template bool setRequestStartIndex(Loader::GetLatestMediaParams ¶ms, int offset);
|
extern template bool setRequestStartIndex(Loader::GetLatestMediaParams ¶ms, int offset);
|
||||||
|
|
||||||
extern template void setRequestLimit(Loader::GetItemsByUserIdParams ¶ms, int limit);
|
extern template void setRequestLimit(Loader::GetItemsByUserIdParams ¶ms, int limit);
|
||||||
extern template bool setRequestStartIndex(Loader::GetItemsByUserIdParams ¶ms, int offset);
|
extern template bool setRequestStartIndex(Loader::GetItemsByUserIdParams ¶ms, int offset);
|
||||||
|
|
||||||
extern template void setRequestLimit(Loader::GetResumeItemsParams ¶ms, int limit);
|
extern template void setRequestLimit(Loader::GetResumeItemsParams ¶ms, int limit);
|
||||||
extern template bool setRequestStartIndex(Loader::GetResumeItemsParams ¶ms, int offset);
|
extern template bool setRequestStartIndex(Loader::GetResumeItemsParams ¶ms, int offset);
|
||||||
|
|
||||||
extern template void setRequestLimit(Loader::GetPublicUsersParams ¶ms, int limit);
|
extern template void setRequestLimit(Loader::GetPublicUsersParams ¶ms, int limit);
|
||||||
extern template bool setRequestStartIndex(Loader::GetPublicUsersParams ¶ms, int offset);
|
extern template bool setRequestStartIndex(Loader::GetPublicUsersParams ¶ms, int offset);
|
||||||
|
|
||||||
extern template void setRequestLimit(Loader::GetNextUpParams ¶ms, int limit);
|
extern template void setRequestLimit(Loader::GetNextUpParams ¶ms, int limit);
|
||||||
extern template bool setRequestStartIndex(Loader::GetNextUpParams ¶ms, int offset);
|
extern template bool setRequestStartIndex(Loader::GetNextUpParams ¶ms, int offset);
|
||||||
|
|
||||||
extern template void setRequestLimit(Loader::GetAlbumArtistsParams ¶ms, int limit);
|
extern template void setRequestLimit(Loader::GetAlbumArtistsParams ¶ms, int limit);
|
||||||
extern template bool setRequestStartIndex(Loader::GetAlbumArtistsParams ¶ms, int offset);
|
extern template bool setRequestStartIndex(Loader::GetAlbumArtistsParams ¶ms, int offset);
|
||||||
|
|
||||||
|
extern template void setRequestLimit(Loader::GetLiveTvChannelsParams ¶ms, int limit);
|
||||||
|
extern template bool setRequestStartIndex(Loader::GetLiveTvChannelsParams ¶ms, int offset);
|
||||||
|
|
||||||
extern template QList<DTO::UserDto> extractRecords(const QList<DTO::UserDto> &result);
|
extern template QList<DTO::UserDto> extractRecords(const QList<DTO::UserDto> &result);
|
||||||
extern template int extractTotalRecordCount(const QList<DTO::UserDto> &result);
|
extern template int extractTotalRecordCount(const QList<DTO::UserDto> &result);
|
||||||
#endif
|
#endif
|
||||||
|
@ -519,6 +529,7 @@ public:
|
||||||
BaseApiModel::setLoader(newLoader);
|
BaseApiModel::setLoader(newLoader);
|
||||||
BaseApiModel::disconnectOldLoader(m_loader);
|
BaseApiModel::disconnectOldLoader(m_loader);
|
||||||
m_loader = castedLoader;
|
m_loader = castedLoader;
|
||||||
|
reload();
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Somehow set a BaseModelLoader on ApiModel instead of a ModelLoader<T>";
|
qWarning() << "Somehow set a BaseModelLoader on ApiModel instead of a ModelLoader<T>";
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,9 +178,12 @@ public:
|
||||||
Q_PROPERTY(int albumCount READ albumCount NOTIFY albumCountChanged)
|
Q_PROPERTY(int albumCount READ albumCount NOTIFY albumCountChanged)
|
||||||
Q_PROPERTY(int artistCount READ artistCount NOTIFY artistCountChanged)
|
Q_PROPERTY(int artistCount READ artistCount NOTIFY artistCountChanged)
|
||||||
Q_PROPERTY(int musicVideoCount READ musicVideoCount NOTIFY musicVideoCountChanged)
|
Q_PROPERTY(int musicVideoCount READ musicVideoCount NOTIFY musicVideoCountChanged)
|
||||||
Q_PROPERTY(QString mediaType READ mediaType READ mediaType NOTIFY mediaTypeChanged)
|
Q_PROPERTY(QString mediaType READ mediaType NOTIFY mediaTypeChanged)
|
||||||
|
Q_PROPERTY(QDateTime endDate READ endDate NOTIFY endDateChanged)
|
||||||
|
Q_PROPERTY(QDateTime startDate READ startDate NOTIFY startDateChanged)
|
||||||
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)
|
||||||
|
Q_PROPERTY(Jellyfin::ViewModel::Item *currentProgram READ currentProgram NOTIFY currentProgramChanged)
|
||||||
|
|
||||||
QString 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(); }
|
||||||
|
@ -225,6 +228,9 @@ public:
|
||||||
QStringList backdropImageTags() const { return m_data->backdropImageTags(); }
|
QStringList backdropImageTags() const { return m_data->backdropImageTags(); }
|
||||||
QJsonObject imageBlurHashes() const { return m_data->imageBlurHashes(); }
|
QJsonObject imageBlurHashes() const { return m_data->imageBlurHashes(); }
|
||||||
QString mediaType() const { return m_data->mediaType(); }
|
QString mediaType() const { return m_data->mediaType(); }
|
||||||
|
QDateTime endDate() const { return m_data->endDate(); }
|
||||||
|
QDateTime startDate() const { return m_data->startDate(); }
|
||||||
|
Item *currentProgram() const { return m_currentProgram; }
|
||||||
|
|
||||||
int trailerCount() const { return m_data->trailerCount().value_or(0); }
|
int trailerCount() const { return m_data->trailerCount().value_or(0); }
|
||||||
int movieCount() const { return m_data->movieCount().value_or(0); }
|
int movieCount() const { return m_data->movieCount().value_or(0); }
|
||||||
|
@ -308,8 +314,11 @@ signals:
|
||||||
void artistCountChanged(int newArtistCount);
|
void artistCountChanged(int newArtistCount);
|
||||||
void musicVideoCountChanged(int newMusicVideoCount);
|
void musicVideoCountChanged(int newMusicVideoCount);
|
||||||
void mediaTypeChanged(const QString &newMediaType);
|
void mediaTypeChanged(const QString &newMediaType);
|
||||||
|
void endDateChanged();
|
||||||
|
void startDateChanged();
|
||||||
void widthChanged(int newWidth);
|
void widthChanged(int newWidth);
|
||||||
void heightChanged(int newHeight);
|
void heightChanged(int newHeight);
|
||||||
|
void currentProgramChanged();
|
||||||
protected:
|
protected:
|
||||||
void setUserData(DTO::UserItemDataDto &newData);
|
void setUserData(DTO::UserItemDataDto &newData);
|
||||||
void setUserData(QSharedPointer<DTO::UserItemDataDto> newData);
|
void setUserData(QSharedPointer<DTO::UserItemDataDto> newData);
|
||||||
|
@ -322,6 +331,7 @@ protected:
|
||||||
QObjectList m_videoStreams;
|
QObjectList m_videoStreams;
|
||||||
QObjectList m_subtitleStreams;
|
QObjectList m_subtitleStreams;
|
||||||
QObjectList m_artistItems;
|
QObjectList m_artistItems;
|
||||||
|
Item *m_currentProgram = nullptr;
|
||||||
private slots:
|
private slots:
|
||||||
void onUserDataChanged(const DTO::UserItemDataDto &userData);
|
void onUserDataChanged(const DTO::UserItemDataDto &userData);
|
||||||
};
|
};
|
||||||
|
|
|
@ -327,6 +327,32 @@ public:
|
||||||
FWDLISTPROP(int, years, Years);
|
FWDLISTPROP(int, years, Years);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using LiveTvChannelsLoaderBase = AbstractUserParameterLoader<Model::Item, DTO::BaseItemDto, DTO::BaseItemDtoQueryResult, Jellyfin::Loader::GetLiveTvChannelsParams>;
|
||||||
|
class LiveTvChannelsLoader : public LiveTvChannelsLoaderBase {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit LiveTvChannelsLoader(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
FWDPROP(Jellyfin::DTO::ChannelTypeClass::Value, type, Type)
|
||||||
|
FWDPROP(bool, isMovie, IsMovie)
|
||||||
|
FWDPROP(bool, isSeries, IsSeries)
|
||||||
|
FWDPROP(bool, isNews, IsNews)
|
||||||
|
FWDPROP(bool, isKids, IsKids)
|
||||||
|
FWDPROP(bool, isSports, IsSports)
|
||||||
|
FWDPROP(bool, isFavorite, IsFavorite)
|
||||||
|
FWDPROP(bool, isLiked, IsLiked)
|
||||||
|
FWDPROP(bool, isDisliked, IsDisliked)
|
||||||
|
FWDPROP(bool, enableImages, EnableImages)
|
||||||
|
FWDPROP(int, imageTypeLimit, ImageTypeLimit)
|
||||||
|
FWDLISTPROP(Jellyfin::DTO::ImageTypeClass::Value, enableImageTypes, EnableImageTypes)
|
||||||
|
FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields)
|
||||||
|
FWDPROP(bool, enableUserData, EnableUserData)
|
||||||
|
FWDPROP(QStringList, sortBy, SortBy)
|
||||||
|
FWDPROP(Jellyfin::DTO::SortOrderClass::Value, sortOrder, SortOrder)
|
||||||
|
FWDPROP(bool, enableFavoriteSorting, EnableFavoriteSorting)
|
||||||
|
FWDPROP(bool, addCurrentProgram, AddCurrentProgram)
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Base class for each model that works with items.
|
* @brief Base class for each model that works with items.
|
||||||
*/
|
*/
|
||||||
|
@ -369,6 +395,10 @@ public:
|
||||||
userDataLastPlayedDate,
|
userDataLastPlayedDate,
|
||||||
userDataPlayed,
|
userDataPlayed,
|
||||||
userDataKey,
|
userDataKey,
|
||||||
|
currentProgramName,
|
||||||
|
currentProgramOverview,
|
||||||
|
currentProgramStartDate,
|
||||||
|
currentProgramEndDate,
|
||||||
|
|
||||||
jellyfinExtendModelAfterHere = Qt::UserRole + 300 // Should be enough for now
|
jellyfinExtendModelAfterHere = Qt::UserRole + 300 // Should be enough for now
|
||||||
};
|
};
|
||||||
|
@ -410,6 +440,10 @@ public:
|
||||||
JFRN(userDataLastPlayedDate),
|
JFRN(userDataLastPlayedDate),
|
||||||
JFRN(userDataPlayed),
|
JFRN(userDataPlayed),
|
||||||
JFRN(userDataKey),
|
JFRN(userDataKey),
|
||||||
|
JFRN(currentProgramName),
|
||||||
|
JFRN(currentProgramOverview),
|
||||||
|
JFRN(currentProgramStartDate),
|
||||||
|
JFRN(currentProgramEndDate),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
QVariant data(const QModelIndex &index, int role) const override;
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
|
|
@ -194,6 +194,18 @@ bool setRequestStartIndex(Loader::GetAlbumArtistsParams ¶ms, int offset) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
void setRequestLimit(Loader::GetLiveTvChannelsParams ¶ms, int limit) {
|
||||||
|
params.setLimit(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
bool setRequestStartIndex(Loader::GetLiveTvChannelsParams ¶ms, int offset) {
|
||||||
|
params.setStartIndex(offset);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
QList<DTO::UserDto> extractRecords(const QList<DTO::UserDto> &result) {
|
QList<DTO::UserDto> extractRecords(const QList<DTO::UserDto> &result) {
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -83,6 +83,7 @@ void JellyfinPlugin::registerTypes(const char *uri) {
|
||||||
qmlRegisterType<ViewModel::NextUpLoader>(uri, 1, 0, "NextUpLoader");
|
qmlRegisterType<ViewModel::NextUpLoader>(uri, 1, 0, "NextUpLoader");
|
||||||
qmlRegisterType<ViewModel::PublicUsersLoader>(uri, 1, 0, "PublicUsersLoader");
|
qmlRegisterType<ViewModel::PublicUsersLoader>(uri, 1, 0, "PublicUsersLoader");
|
||||||
qmlRegisterType<ViewModel::AlbumArtistLoader>(uri, 1, 0, "AlbumArtistLoader");
|
qmlRegisterType<ViewModel::AlbumArtistLoader>(uri, 1, 0, "AlbumArtistLoader");
|
||||||
|
qmlRegisterType<ViewModel::LiveTvChannelsLoader>(uri, 1, 0, "LiveTvChannelsLoader");
|
||||||
|
|
||||||
// Enumerations
|
// Enumerations
|
||||||
qmlRegisterUncreatableType<Jellyfin::DTO::GeneralCommandTypeClass>(uri, 1, 0, "GeneralCommandType", "Is an enum");
|
qmlRegisterUncreatableType<Jellyfin::DTO::GeneralCommandTypeClass>(uri, 1, 0, "GeneralCommandType", "Is an enum");
|
||||||
|
|
|
@ -34,9 +34,7 @@ Item::Item(QObject *parent, QSharedPointer<Model::Item> data)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
m_data(data),
|
m_data(data),
|
||||||
m_userData(new UserData(this)){
|
m_userData(new UserData(this)){
|
||||||
connect(m_data.data(), &Model::Item::userDataChanged, this, &Item::onUserDataChanged);
|
this->setData(data);
|
||||||
m_userData->setData(data->userData());
|
|
||||||
updateMediaStreams();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Item::setData(QSharedPointer<Model::Item> newData) {
|
void Item::setData(QSharedPointer<Model::Item> newData) {
|
||||||
|
@ -50,8 +48,18 @@ void Item::setData(QSharedPointer<Model::Item> newData) {
|
||||||
connect(m_data.data(), &Model::Item::userDataChanged, this, &Item::onUserDataChanged);
|
connect(m_data.data(), &Model::Item::userDataChanged, this, &Item::onUserDataChanged);
|
||||||
updateMediaStreams();
|
updateMediaStreams();
|
||||||
setUserData(m_data->userData());
|
setUserData(m_data->userData());
|
||||||
|
|
||||||
|
if (m_data->currentProgram().isNull()) {
|
||||||
|
m_currentProgram = nullptr;
|
||||||
|
} else {
|
||||||
|
QSharedPointer<DTO::BaseItemDto> dataDto = m_data->currentProgram();
|
||||||
|
QSharedPointer<Model::Item> data = QSharedPointer<Model::Item>::create(*dataDto.data());
|
||||||
|
m_currentProgram = new Item(this, data);
|
||||||
|
}
|
||||||
|
emit currentProgramChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
emit userDataChanged(m_userData);
|
emit userDataChanged(m_userData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,18 +16,19 @@
|
||||||
* License along with this library; if not, write to the Free Software
|
* License along with this library; if not, write to the Free Software
|
||||||
* 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/itemmodel.h"
|
#include <JellyfinQt/viewmodel/itemmodel.h>
|
||||||
|
|
||||||
#include "JellyfinQt/viewmodel/item.h"
|
#include <JellyfinQt/viewmodel/item.h>
|
||||||
|
|
||||||
#include "JellyfinQt/loader/http/artists.h"
|
#include <JellyfinQt/loader/http/artists.h>
|
||||||
#include "JellyfinQt/loader/http/items.h"
|
#include <JellyfinQt/loader/http/items.h>
|
||||||
#include "JellyfinQt/loader/http/userlibrary.h"
|
#include <JellyfinQt/loader/http/livetv.h>
|
||||||
#include "JellyfinQt/loader/http/userviews.h"
|
#include <JellyfinQt/loader/http/userlibrary.h>
|
||||||
#include "JellyfinQt/loader/http/tvshows.h"
|
#include <JellyfinQt/loader/http/userviews.h>
|
||||||
|
#include <JellyfinQt/loader/http/tvshows.h>
|
||||||
|
|
||||||
#include "JellyfinQt/viewmodel/userdata.h"
|
#include <JellyfinQt/viewmodel/userdata.h>
|
||||||
#include "JellyfinQt/viewmodel/utils.h"
|
#include <JellyfinQt/viewmodel/utils.h>
|
||||||
|
|
||||||
#define JF_CASE(roleName) case roleName: \
|
#define JF_CASE(roleName) case roleName: \
|
||||||
try { \
|
try { \
|
||||||
|
@ -64,6 +65,9 @@ NextUpLoader::NextUpLoader(QObject *parent)
|
||||||
AlbumArtistLoader::AlbumArtistLoader(QObject *parent)
|
AlbumArtistLoader::AlbumArtistLoader(QObject *parent)
|
||||||
: AlbumArtistLoaderBase(new Jellyfin::Loader::HTTP::GetAlbumArtistsLoader(), parent) {}
|
: AlbumArtistLoaderBase(new Jellyfin::Loader::HTTP::GetAlbumArtistsLoader(), parent) {}
|
||||||
|
|
||||||
|
LiveTvChannelsLoader::LiveTvChannelsLoader(QObject *parent)
|
||||||
|
: LiveTvChannelsLoaderBase(new Jellyfin::Loader::HTTP::GetLiveTvChannelsLoader(), parent) {}
|
||||||
|
|
||||||
ItemModel::ItemModel(QObject *parent)
|
ItemModel::ItemModel(QObject *parent)
|
||||||
: ApiModel<Model::Item>(parent) {
|
: ApiModel<Model::Item>(parent) {
|
||||||
connect(this, &QAbstractItemModel::rowsInserted, this, &ItemModel::onInsertItems);
|
connect(this, &QAbstractItemModel::rowsInserted, this, &ItemModel::onInsertItems);
|
||||||
|
@ -126,6 +130,30 @@ QVariant ItemModel::data(const QModelIndex &index, int role) const {
|
||||||
return QVariant(item->userData()->played());
|
return QVariant(item->userData()->played());
|
||||||
case RoleNames::userDataKey:
|
case RoleNames::userDataKey:
|
||||||
return QVariant(item->userData()->key());
|
return QVariant(item->userData()->key());
|
||||||
|
case RoleNames::currentProgramName:
|
||||||
|
if (item->currentProgram()) {
|
||||||
|
return QVariant(item->currentProgram()->name());
|
||||||
|
} else {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
case RoleNames::currentProgramOverview:
|
||||||
|
if (item->currentProgram()) {
|
||||||
|
return QVariant(item->currentProgram()->overview());
|
||||||
|
} else {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
case RoleNames::currentProgramStartDate:
|
||||||
|
if (item->currentProgram()) {
|
||||||
|
return QVariant(item->currentProgram()->startDate());
|
||||||
|
} else {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
case RoleNames::currentProgramEndDate:
|
||||||
|
if (item->currentProgram()) {
|
||||||
|
return QVariant(item->currentProgram()->endDate());
|
||||||
|
} else {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,12 @@
|
||||||
# * date Author's Name <author's email> version-release
|
# * date Author's Name <author's email> version-release
|
||||||
# - Summary of changes
|
# - Summary of changes
|
||||||
#
|
#
|
||||||
|
* ??? ??? ? ???? Chris Josten <chris@netsoj.nl> ?.?.?-?
|
||||||
|
- New features:
|
||||||
|
- Added a page that shows a list of Live TV channels with details about currently running
|
||||||
|
programs.
|
||||||
|
- Changes:
|
||||||
|
- TV CHannels on the main page are now sorted by channel number, instead of recently added
|
||||||
* Tue Jan 2 2024 Chris Josten <chris@netsoj.nl> 0.5.0-1
|
* Tue Jan 2 2024 Chris Josten <chris@netsoj.nl> 0.5.0-1
|
||||||
- New features
|
- New features
|
||||||
- Allow remote controlling other Jellyfin clients. See the pulley item on the main screen named
|
- Allow remote controlling other Jellyfin clients. See the pulley item on the main screen named
|
||||||
|
|
|
@ -56,10 +56,12 @@ set(sailfin_QML_SOURCES
|
||||||
qml/pages/itemdetails/CollectionPage.qml
|
qml/pages/itemdetails/CollectionPage.qml
|
||||||
qml/pages/itemdetails/EpisodePage.qml
|
qml/pages/itemdetails/EpisodePage.qml
|
||||||
qml/pages/itemdetails/FilmPage.qml
|
qml/pages/itemdetails/FilmPage.qml
|
||||||
qml/pages/itemdetails/MusicAlbumPage.qml
|
qml/pages/itemdetails/LiveTvChannelPage.qml
|
||||||
|
qml/pages/itemdetails/LiveTvChannelsPage.qml
|
||||||
|
qml/pages/itemdetails/MusicAlbumPage.qml
|
||||||
qml/pages/itemdetails/MusicArtistPage.qml
|
qml/pages/itemdetails/MusicArtistPage.qml
|
||||||
qml/pages/itemdetails/MusicLibraryPage.qml
|
qml/pages/itemdetails/MusicLibraryPage.qml
|
||||||
qml/pages/itemdetails/PhotoPage.qml
|
qml/pages/itemdetails/PhotoPage.qml
|
||||||
qml/pages/itemdetails/SeasonPage.qml
|
qml/pages/itemdetails/SeasonPage.qml
|
||||||
qml/pages/itemdetails/SeriesPage.qml
|
qml/pages/itemdetails/SeriesPage.qml
|
||||||
qml/pages/itemdetails/UnsupportedPage.qml
|
qml/pages/itemdetails/UnsupportedPage.qml
|
||||||
|
|
|
@ -115,6 +115,8 @@ function getPageUrl(mediaType, itemType, isFolder) {
|
||||||
return Qt.resolvedUrl("pages/itemdetails/MusicAlbumPage.qml")
|
return Qt.resolvedUrl("pages/itemdetails/MusicAlbumPage.qml")
|
||||||
case "photo":
|
case "photo":
|
||||||
return Qt.resolvedUrl("pages/itemdetails/PhotoPage.qml")
|
return Qt.resolvedUrl("pages/itemdetails/PhotoPage.qml")
|
||||||
|
case "tvchannel":
|
||||||
|
return Qt.resolvedUrl("pages/itemdetails/LiveTvChannelPage.qml")
|
||||||
case "collectionfolder":
|
case "collectionfolder":
|
||||||
// TODO: support for other collection folders
|
// TODO: support for other collection folders
|
||||||
switch(mediaType.toLowerCase()) {
|
switch(mediaType.toLowerCase()) {
|
||||||
|
@ -124,6 +126,8 @@ function getPageUrl(mediaType, itemType, isFolder) {
|
||||||
// FALLTRHOUGH
|
// FALLTRHOUGH
|
||||||
default:
|
default:
|
||||||
switch (mediaType ? mediaType.toLowerCase() : isFolder ? "folder" : "") {
|
switch (mediaType ? mediaType.toLowerCase() : isFolder ? "folder" : "") {
|
||||||
|
case "livetv":
|
||||||
|
return Qt.resolvedUrl("pages/itemdetails/LiveTvChannelsPage.qml")
|
||||||
case "folder":
|
case "folder":
|
||||||
return Qt.resolvedUrl("pages/itemdetails/CollectionPage.qml")
|
return Qt.resolvedUrl("pages/itemdetails/CollectionPage.qml")
|
||||||
case "video":
|
case "video":
|
||||||
|
|
|
@ -190,7 +190,7 @@ PanelBackground {
|
||||||
left: parent.left
|
left: parent.left
|
||||||
verticalCenter: parent.verticalCenter
|
verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
height: parent
|
height: parent.height
|
||||||
source: "image://theme/icon-s-device-upload"
|
source: "image://theme/icon-s-device-upload"
|
||||||
visible: controllingRemote
|
visible: controllingRemote
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,9 @@ SilicaItem {
|
||||||
BusyIndicator {
|
BusyIndicator {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
running: realImage.status === Image.Loading
|
running: realImage.status === Image.Loading
|
||||||
|
size: root.height <= Theme.fontSizeLarge
|
||||||
|
? BusyIndicatorSize.Small
|
||||||
|
: BusyIndicatorSize.Medium
|
||||||
}
|
}
|
||||||
|
|
||||||
HighlightImage {
|
HighlightImage {
|
||||||
|
|
|
@ -114,6 +114,12 @@ Page {
|
||||||
apiClient: appWindow.apiClient
|
apiClient: appWindow.apiClient
|
||||||
parentId: jellyfinId
|
parentId: jellyfinId
|
||||||
}
|
}
|
||||||
|
Binding on loader {
|
||||||
|
when: model.collectionType == "livetv"
|
||||||
|
value: J.LiveTvChannelsLoader{
|
||||||
|
apiClient: appWindow.apiClient
|
||||||
|
}
|
||||||
|
}
|
||||||
Connections {
|
Connections {
|
||||||
target: mediaLibraryLoader
|
target: mediaLibraryLoader
|
||||||
onReady: loader.reload()
|
onReady: loader.reload()
|
||||||
|
|
23
sailfish/qml/pages/itemdetails/LiveTvChannelPage.qml
Normal file
23
sailfish/qml/pages/itemdetails/LiveTvChannelPage.qml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import QtQuick 2.0
|
||||||
|
import Sailfish.Silica 1.0
|
||||||
|
|
||||||
|
import "../../components"
|
||||||
|
|
||||||
|
VideoPage {
|
||||||
|
title: itemData.currentProgram.name
|
||||||
|
subtitle: qsTr("%1 | %2 - %3")
|
||||||
|
.arg(itemData.name)
|
||||||
|
.arg(Qt.formatTime(itemData.currentProgram.startDate))
|
||||||
|
.arg(Qt.formatTime(itemData.currentProgram.endDate))
|
||||||
|
|
||||||
|
SectionHeader {
|
||||||
|
text: qsTr("Program info")
|
||||||
|
}
|
||||||
|
|
||||||
|
PlainLabel {
|
||||||
|
id: overviewText
|
||||||
|
text: itemData.currentProgram.overview || qsTr("No program info available")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.secondaryHighlightColor
|
||||||
|
}
|
||||||
|
}
|
109
sailfish/qml/pages/itemdetails/LiveTvChannelsPage.qml
Normal file
109
sailfish/qml/pages/itemdetails/LiveTvChannelsPage.qml
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import QtQuick 2.6
|
||||||
|
import Sailfish.Silica 1.0
|
||||||
|
import nl.netsoj.chris.Jellyfin 1.0 as J
|
||||||
|
|
||||||
|
import "../../components"
|
||||||
|
import "../../"
|
||||||
|
|
||||||
|
BaseDetailPage {
|
||||||
|
J.ItemModel {
|
||||||
|
id: episodeModel
|
||||||
|
loader: J.LiveTvChannelsLoader{
|
||||||
|
apiClient: appWindow.apiClient
|
||||||
|
fields: [J.ItemFields.Overview]
|
||||||
|
autoReload: itemData.jellyfinId.length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SilicaListView {
|
||||||
|
anchors.fill: parent
|
||||||
|
model: episodeModel
|
||||||
|
header: PageHeader {
|
||||||
|
title: itemData.name
|
||||||
|
description: itemData.seriesName
|
||||||
|
}
|
||||||
|
delegate: BackgroundItem {
|
||||||
|
height: content.height
|
||||||
|
onClicked: appWindow.navigateToItem(model.jellyfinId, model.mediaType, model.type, model.isFolder);
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: content
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: Theme.horizontalPageMargin
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: Theme.horizontalPageMargin
|
||||||
|
}
|
||||||
|
spacing: Theme.paddingSmall
|
||||||
|
topPadding: Theme.paddingLarge
|
||||||
|
bottomPadding: Theme.paddingLarge
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
spacing: Theme.paddingMedium
|
||||||
|
|
||||||
|
RemoteImage {
|
||||||
|
id: channelLogo
|
||||||
|
width: Theme.fontSizeLarge
|
||||||
|
height: width
|
||||||
|
source: Utils.itemModelImageUrl(apiClient.baseUrl, model.jellyfinId, model.imageTags.Primary, "Primary", {"maxHeight": height})
|
||||||
|
blurhash: model.imageBlurHashes.Primary[model.imageTags.Primary]
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
fallbackColor: "transparent"
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.verticalCenter: channelLogo.verticalCenter
|
||||||
|
id: channelName
|
||||||
|
text: model.name
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
anchors { left: parent.left; right: parent.right }
|
||||||
|
height: programName.height
|
||||||
|
Label {
|
||||||
|
id: programName
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: programTime.left
|
||||||
|
rightMargin: Theme.paddingLarge
|
||||||
|
}
|
||||||
|
text: currentProgramName
|
||||||
|
? currentProgramName
|
||||||
|
//: Shown in the channel list when the name of the current program is unknown
|
||||||
|
: qsTr("No program information available")
|
||||||
|
truncationMode: TruncationMode.Fade
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
id: programTime
|
||||||
|
anchors.right: parent.right
|
||||||
|
text: "%1 - %2"
|
||||||
|
.arg(Qt.formatTime(currentProgramStartDate))
|
||||||
|
.arg(Qt.formatTime(currentProgramEndDate))
|
||||||
|
color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
visible: text
|
||||||
|
|
||||||
|
text: currentProgramOverview
|
||||||
|
color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor
|
||||||
|
maximumLineCount: 2
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
elide: "ElideRight"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VerticalScrollDecorator {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,7 +55,6 @@ BaseDetailPage {
|
||||||
fields: [J.ItemFields.ItemCounts, J.ItemFields.PrimaryImageAspectRatio]
|
fields: [J.ItemFields.ItemCounts, J.ItemFields.PrimaryImageAspectRatio]
|
||||||
parentId: itemData.jellyfinId
|
parentId: itemData.jellyfinId
|
||||||
autoReload: itemData.jellyfinId.length > 0
|
autoReload: itemData.jellyfinId.length > 0
|
||||||
//onParentIdChanged: if (parentId.length > 0) reload()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
|
|
@ -37,11 +37,6 @@ BaseDetailPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: itemData
|
|
||||||
onReady: episodeModel.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
SilicaListView {
|
SilicaListView {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
contentHeight: content.height
|
contentHeight: content.height
|
||||||
|
@ -149,19 +144,4 @@ BaseDetailPage {
|
||||||
|
|
||||||
VerticalScrollDecorator {}
|
VerticalScrollDecorator {}
|
||||||
}
|
}
|
||||||
Connections {
|
|
||||||
target: itemData
|
|
||||||
onStatusChanged: {
|
|
||||||
if (itemData.status == JellyfinItem.Ready) {
|
|
||||||
episodeModel.reload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onStatusChanged: {
|
|
||||||
if (status == PageStatus.Active) {
|
|
||||||
//console.log(JSON.stringify(itemData))
|
|
||||||
//episodeModel.show = itemData.seriesId
|
|
||||||
//episodeModel.seasonId = itemData.jellyfinId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,10 +72,6 @@ BaseDetailPage {
|
||||||
autoReload: itemData.jellyfinId.length > 0
|
autoReload: itemData.jellyfinId.length > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Connections {
|
|
||||||
target: itemData
|
|
||||||
onReady: showSeasonsModel.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
SilicaListView {
|
SilicaListView {
|
||||||
model: showSeasonsModel
|
model: showSeasonsModel
|
||||||
|
@ -96,17 +92,4 @@ BaseDetailPage {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*onStatusChanged: {
|
|
||||||
if (status == PageStatus.Active) {
|
|
||||||
showSeasonsModel.reload()
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
Connections {
|
|
||||||
target: itemData
|
|
||||||
onJellyfinIdChanged: {
|
|
||||||
console.log("Item id changed")
|
|
||||||
//showSeasonsModel.show = itemData.jellyfinId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import "../.."
|
||||||
*/
|
*/
|
||||||
BaseDetailPage {
|
BaseDetailPage {
|
||||||
id: detailPage
|
id: detailPage
|
||||||
|
property alias title: pageHeader.title
|
||||||
property alias subtitle: pageHeader.description
|
property alias subtitle: pageHeader.description
|
||||||
default property alias _data: content.data
|
default property alias _data: content.data
|
||||||
property real _playbackProsition: itemData.userData.playbackPositionTicks
|
property real _playbackProsition: itemData.userData.playbackPositionTicks
|
||||||
|
|
|
@ -265,6 +265,29 @@
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>LiveTvChannelPage</name>
|
||||||
|
<message>
|
||||||
|
<source>%1 | %2 - %3</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Program info</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>No program info available</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>LiveTvChannelsPage</name>
|
||||||
|
<message>
|
||||||
|
<source>No program information available</source>
|
||||||
|
<extracomment>Shown in the channel list when the name of the current program is unknown</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>LoginDialog</name>
|
<name>LoginDialog</name>
|
||||||
<message>
|
<message>
|
||||||
|
|
|
@ -265,6 +265,29 @@
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>LiveTvChannelPage</name>
|
||||||
|
<message>
|
||||||
|
<source>%1 | %2 - %3</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Program info</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>No program info available</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>LiveTvChannelsPage</name>
|
||||||
|
<message>
|
||||||
|
<source>No program information available</source>
|
||||||
|
<extracomment>Shown in the channel list when the name of the current program is unknown</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>LoginDialog</name>
|
<name>LoginDialog</name>
|
||||||
<message>
|
<message>
|
||||||
|
|
|
@ -265,6 +265,29 @@
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>LiveTvChannelPage</name>
|
||||||
|
<message>
|
||||||
|
<source>%1 | %2 - %3</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Program info</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>No program info available</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>LiveTvChannelsPage</name>
|
||||||
|
<message>
|
||||||
|
<source>No program information available</source>
|
||||||
|
<extracomment>Shown in the channel list when the name of the current program is unknown</extracomment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>LoginDialog</name>
|
<name>LoginDialog</name>
|
||||||
<message>
|
<message>
|
||||||
|
|
Loading…
Reference in a new issue