diff --git a/core/include/JellyfinQt/apimodel.h b/core/include/JellyfinQt/apimodel.h index 48314c5..045c9bf 100644 --- a/core/include/JellyfinQt/apimodel.h +++ b/core/include/JellyfinQt/apimodel.h @@ -47,6 +47,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include "support/loader.h" #include "viewmodel/modelstatus.h" +Q_DECLARE_LOGGING_CATEGORY(jellyfinApiModel) + namespace Jellyfin { /* @@ -121,6 +123,7 @@ protected: int m_limit = -1; int m_startIndex = 0; int m_totalRecordCount = 0; + bool m_explicitLimitSet = false; const int DEFAULT_LIMIT = 100; void emitModelShouldClear() { emit modelShouldClear(); } void emitItemsLoaded() { emit itemsLoaded(); } @@ -165,14 +168,14 @@ public: m_startIndex = 0; m_totalRecordCount = -1; emitModelShouldClear(); - loadMore(0, m_limit, ViewModel::ModelStatus::Loading); + loadMore(ViewModel::ModelStatus::Loading); } void loadMore() { if (!canReload()) { return; } - loadMore(m_startIndex, m_limit, ViewModel::ModelStatus::LoadingMore); + loadMore(ViewModel::ModelStatus::LoadingMore); } virtual bool canLoadMore() const { @@ -192,12 +195,10 @@ protected: * The itemsLoaded() signal is emitted when new data is ready. Call * getLoadedItems to retrieve the loaded items. * - * @param offset The offset to start loading items from - * @param limit The maximum amount of items to load. * @param suggestedStatus The suggested status this model should take on if it is able to load (more). * Either LOADING or LOAD_MORE. */ - virtual void loadMore(int offset, int limit, ViewModel::ModelStatus suggestedStatus) = 0; + virtual void loadMore(ViewModel::ModelStatus suggestedStatus) = 0; std::pair, int> m_result; }; @@ -270,7 +271,7 @@ public: this->connect(m_loader.data(), &Support::Loader::error, this, &LoaderModelLoader::loaderError); } protected: - void loadMore(int offset, int limit, ViewModel::ModelStatus suggestedModelStatus) override { + void loadMore(ViewModel::ModelStatus suggestedModelStatus) override { // This method should only be callable on one thread. // If futureWatcher's future is running, this method should not be called again. if (m_loader->isRunning()) { @@ -279,20 +280,22 @@ protected: // Set an invalid result. this->m_result = { QList(), -1 }; - if (!setRequestStartIndex

(this->m_parameters, offset) + if (!setRequestStartIndex

(this->m_parameters, this->m_startIndex) && suggestedModelStatus == ViewModel::ModelStatus::LoadingMore) { // This loader's parameters does not setting a starting index, // meaning loadMore is not supported. return; } + setRequestStartIndex

(this->m_parameters, this->m_startIndex); - if (limit > 0) { - if (suggestedModelStatus == ViewModel::ModelStatus::Loading) { - setRequestLimit

(this->m_parameters, limit); - } else { - // If an explicit limit is set, we should load no more - return; - } + if (suggestedModelStatus == ViewModel::ModelStatus::LoadingMore && this->m_explicitLimitSet) { + // If an explicit limit is set, we should load no more + return; + } + + qCDebug(jellyfinApiModel) << "Explicit limit set: " << this->m_explicitLimitSet << ", " << this->m_limit; + if (this->m_explicitLimitSet) { + setRequestLimit

(this->m_parameters, this->m_limit); } else { setRequestLimit

(this->m_parameters, this->DEFAULT_LIMIT); } diff --git a/core/include/JellyfinQt/model/playlist.h b/core/include/JellyfinQt/model/playlist.h index 32523db..73cf4a8 100644 --- a/core/include/JellyfinQt/model/playlist.h +++ b/core/include/JellyfinQt/model/playlist.h @@ -100,6 +100,12 @@ public: */ void appendToList(ViewModel::ItemModel &model); + /** + * @brief appendToList Appends a single item to the current list + * @param item The item to append + */ + void appendToList(QSharedPointer item); + /** * @brief Start playing this playlist * @param index The index to start from. diff --git a/core/include/JellyfinQt/support/jsonconvimpl.h b/core/include/JellyfinQt/support/jsonconvimpl.h index d1714a8..e6dd422 100644 --- a/core/include/JellyfinQt/support/jsonconvimpl.h +++ b/core/include/JellyfinQt/support/jsonconvimpl.h @@ -139,6 +139,12 @@ QJsonValue toJsonValue(const QSharedPointer &source, convertType +QString toString(const T &source) { + return toString(source, convertType{}); +} + template QString toString(const T &source, convertType) { return toJsonValue(source).toString(); @@ -154,11 +160,15 @@ QString toString(const std::optional &source, convertType>) } template -QString toString(const T &source) { - return toString(source, convertType{}); +QString toString(const QList &source, convertType>) { + QStringList tmp; + tmp.reserve(source.size()); + for (auto it = source.cbegin(); it != source.cend(); it++) { + tmp.append(toString(*it, convertType{})); + } + return tmp.join(','); } - } // NS Support } // NS Jellyfin diff --git a/core/include/JellyfinQt/viewmodel/itemmodel.h b/core/include/JellyfinQt/viewmodel/itemmodel.h index f94411e..c584fd1 100644 --- a/core/include/JellyfinQt/viewmodel/itemmodel.h +++ b/core/include/JellyfinQt/viewmodel/itemmodel.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "../dto/baseitemdto.h" #include "../dto/baseitemdtoqueryresult.h" @@ -45,6 +46,31 @@ Q_SIGNALS: \ void propName##Changed(); +#define FWDLISTPROP(type, propName, propSetName) \ + public: \ + Q_PROPERTY(QVariantList propName READ propName WRITE set##propSetName NOTIFY propName##Changed) \ + QVariantList propName() const { \ + QVariantList result; \ + QList list; \ + result.reserve(list.size()); \ + for (auto it = list.cbegin(); it != list.cend(); it++) { \ + result.append(QVariant::fromValue(*it)); \ + } \ + return result; \ + } \ + void set##propSetName(const QVariantList &newValue) { \ + QList list;\ + list.reserve(newValue.size()); \ + for(auto it = newValue.cbegin(); it != newValue.cend(); it++) { \ + list.append(it->value()); \ + } \ + this->m_parameters.set##propSetName(list); \ + emit propName##Changed(); \ + autoReloadIfNeeded(); \ + } \ + Q_SIGNALS: \ + void propName##Changed(); + namespace Jellyfin { namespace ViewModel { @@ -112,7 +138,7 @@ public: FWDPROP(QList, enableImageTypes, EnableImageTypes) FWDPROP(bool, enableImages, EnableImages) FWDPROP(bool, enableUserData, EnableUserData) - FWDPROP(QList, fields, Fields) + FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) FWDPROP(bool, groupItems, GroupItems) FWDPROP(qint32, imageTypeLimit, ImageTypeLimit) FWDPROP(QStringList, includeItemTypes, IncludeItemTypes) @@ -134,7 +160,7 @@ public: FWDPROP(QStringList, artists, Artists) FWDPROP(bool, collapseBoxSetItems, CollapseBoxSetItems) FWDPROP(QStringList, contributingArtistIds, ContributingArtistIds) - FWDPROP(QList, enableImageTypes, EnableImageTypes); + FWDLISTPROP(Jellyfin::DTO::ImageTypeClass::Value, enableImageTypes, EnableImageTypes); FWDPROP(bool, enableImages, EnableImages) FWDPROP(bool, enableTotalRecordCount, EnableTotalRecordCount) FWDPROP(bool, enableUserData, EnableUserData) @@ -142,8 +168,8 @@ public: FWDPROP(QStringList, excludeItemIds, ExcludeItemIds) FWDPROP(QStringList, excludeItemTypes, ExcludeItemTypes) FWDPROP(QList, excludeLocationTypes, ExcludeLocationTypes) - FWDPROP(QList, fields, Fields) - FWDPROP(QList, filters, Filters) + FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) + FWDLISTPROP(Jellyfin::DTO::ItemFilterClass::Value, filters, Filters) FWDPROP(QStringList, genreIds, GenreIds) FWDPROP(QStringList, genres, Genres) FWDPROP(bool, hasImdbId, HasImdbId) @@ -159,7 +185,7 @@ public: FWDPROP(bool, hasTvdbId, HasTvdbId) FWDPROP(QStringList, ids, Ids) FWDPROP(qint32, imageTypeLimit, ImageTypeLimit) - FWDPROP(QList, imageTypes, ImageTypes) + FWDLISTPROP(Jellyfin::DTO::ImageTypeClass::Value, imageTypes, ImageTypes) FWDPROP(QStringList, includeItemTypes, IncludeItemTypes) FWDPROP(bool, is3D, Is3D) FWDPROP(bool, is4K, Is4K) @@ -170,8 +196,7 @@ public: FWDPROP(bool, isPlaceHolder, IsPlaceHolder) FWDPROP(bool, isPlayed, IsPlayed) FWDPROP(bool, isUnaired, IsUnaired) - FWDPROP(int, limit, Limit) - FWDPROP(QList, locationTypes, LocationTypes) + FWDLISTPROP(Jellyfin::DTO::LocationTypeClass::Value, locationTypes, LocationTypes) FWDPROP(qint32, maxHeight, MaxHeight) FWDPROP(QString, maxOfficialRating, MaxOfficialRating) FWDPROP(QDateTime, maxPremiereDate, MaxPremiereDate) @@ -198,12 +223,12 @@ class ResumeItemsLoader : public ResumeItemsLoaderBase { public: explicit ResumeItemsLoader(QObject *parent = nullptr); - FWDPROP(QList, enableImageTypes, EnableImageTypes); + FWDLISTPROP(Jellyfin::DTO::ImageTypeClass::Value, enableImageTypes, EnableImageTypes); FWDPROP(bool, enableImages, EnableImages) FWDPROP(bool, enableTotalRecordCount, EnableTotalRecordCount) FWDPROP(bool, enableUserData, EnableUserData) FWDPROP(QStringList, excludeItemTypes, ExcludeItemTypes) - FWDPROP(QList, fields, Fields) + FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) FWDPROP(qint32, imageTypeLimit, ImageTypeLimit) FWDPROP(QStringList, includeItemTypes, IncludeItemTypes) FWDPROP(QStringList, mediaTypes, MediaTypes) @@ -219,10 +244,10 @@ public: FWDPROP(QString, seriesId, SeriesId) FWDPROP(QString, adjacentTo, AdjacentTo) - FWDPROP(QList, enableImageTypes, EnableImageTypes) + FWDLISTPROP(Jellyfin::DTO::ImageTypeClass::Value, enableImageTypes, EnableImageTypes) FWDPROP(bool, enableImages, EnableImages) FWDPROP(bool, enableUserData, EnableUserData) - FWDPROP(QList, fields, Fields) + FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) FWDPROP(qint32, imageTypeLimit, ImageTypeLimit) FWDPROP(bool, isMissing, IsMissing) FWDPROP(bool, isSpecialSeason, IsSpecialSeason) @@ -239,7 +264,7 @@ public: FWDPROP(QString, adjacentTo, AdjacentTo) FWDPROP(bool, enableImages, EnableImages) FWDPROP(bool, enableUserData, EnableUserData) - FWDPROP(QList, fields, Fields) + FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) FWDPROP(qint32, imageTypeLimit, ImageTypeLimit) FWDPROP(bool, isMissing, IsMissing) FWDPROP(qint32, season, Season) @@ -248,6 +273,23 @@ public: FWDPROP(QString, startItemId, StartItemId) }; +using NextUpLoaderBase = AbstractUserParameterLoader; +class NextUpLoader : public NextUpLoaderBase { + Q_OBJECT +public: + explicit NextUpLoader(QObject *parent = nullptr); + + FWDPROP(bool, disableFirstEpisode, DisableFirstEpisode) + FWDLISTPROP(Jellyfin::DTO::ImageTypeClass::Value, enableImageTypes, EnableImageTypes); + FWDPROP(bool, enableImges, EnableImges) + FWDPROP(bool, enableTotalRecordCount, EnableTotalRecordCount) + FWDPROP(bool, enableUserData, EnableUserData) + FWDLISTPROP(Jellyfin::DTO::ItemFieldsClass::Value, fields, Fields) + FWDPROP(qint32, imageTypeLimit, ImageTypeLimit) + FWDPROP(QString, parentId, ParentId) + FWDPROP(QString, seriesId, SeriesId) +}; + /** * @brief Base class for each model that works with items. @@ -278,6 +320,7 @@ public: runTimeTicks, artists, isFolder, + overview, parentIndexNumber, userDataRating, userDataPlayedPercentage, @@ -317,6 +360,7 @@ public: JFRN(runTimeTicks), JFRN(artists), JFRN(isFolder), + JFRN(overview), JFRN(parentIndexNumber), JFRN(userDataRating), JFRN(userDataPlayedPercentage), diff --git a/core/include/JellyfinQt/viewmodel/loader.h b/core/include/JellyfinQt/viewmodel/loader.h index 13dc5d2..373fcd6 100644 --- a/core/include/JellyfinQt/viewmodel/loader.h +++ b/core/include/JellyfinQt/viewmodel/loader.h @@ -60,7 +60,7 @@ public: : QObject(parent), m_apiClient(apiClient) {} Q_PROPERTY(ApiClient *apiClient MEMBER m_apiClient WRITE setApiClient NOTIFY apiClientChanged STORED false) - Q_PROPERTY(Status status READ status NOTIFY statusChanged STORED false) + Q_PROPERTY(Jellyfin::ViewModel::LoaderBase::Status status READ status NOTIFY statusChanged STORED false) Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged STORED false) Q_PROPERTY(bool autoReload MEMBER m_autoReload NOTIFY autoReloadChanged) Q_PROPERTY(QObject *data READ data NOTIFY dataChanged STORED false) @@ -73,7 +73,7 @@ public: void setApiClient(ApiClient *newApiClient); void setExtraFields(const QStringList &extraFields); signals: - void statusChanged(Status newStatus); + void statusChanged(Jellyfin::ViewModel::LoaderBase::Status newStatus); void apiClientChanged(ApiClient *newApiClient); void errorStringChanged(QString newErrorString); void autoReloadChanged(bool newAutoReload); diff --git a/core/src/apiclient.cpp b/core/src/apiclient.cpp index 47ac242..ff54107 100644 --- a/core/src/apiclient.cpp +++ b/core/src/apiclient.cpp @@ -438,7 +438,7 @@ void ApiClient::generateDeviceProfile() { clientCapabilities->setIconUrl("https://chris.netsoj.nl/static/img/logo.png"); clientCapabilities->setSupportsPersistentIdentifier(true); clientCapabilities->setSupportsSync(false); - clientCapabilities->setSupportsMediaControl(false); + clientCapabilities->setSupportsMediaControl(true); clientCapabilities->setSupportsContentUploading(false); d->clientCapabilities = clientCapabilities; diff --git a/core/src/apimodel.cpp b/core/src/apimodel.cpp index 7107d8d..350b495 100644 --- a/core/src/apimodel.cpp +++ b/core/src/apimodel.cpp @@ -23,6 +23,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include "JellyfinQt/dto/useritemdatadto.h" #include "JellyfinQt/dto/userdto.h" +Q_LOGGING_CATEGORY(jellyfinApiModel, "jellyfin.apimodel") + namespace Jellyfin { // BaseApiModel @@ -46,7 +48,7 @@ void BaseModelLoader::componentComplete() { void BaseModelLoader::autoReloadIfNeeded() { if (m_autoReload && canReload()) { - qDebug() << "reloading due to 'autoReloadIfNeeded()'"; + qCDebug(jellyfinApiModel) << "reloading due to 'autoReloadIfNeeded()'"; emit reloadWanted(); } } @@ -57,14 +59,14 @@ void BaseModelLoader::setApiClient(ApiClient *newApiClient) { if (changed) { emit apiClientChanged(newApiClient); } + autoReloadIfNeeded(); } void BaseModelLoader::setLimit(int newLimit) { - int oldLimit = this->m_limit; - m_limit = newLimit; - if (oldLimit != newLimit) { - emit limitChanged(this->m_limit); - } + m_explicitLimitSet = newLimit >= 0; + qCDebug(jellyfinApiModel) << "Limit explicitly set to " << newLimit; + this->m_limit = newLimit; + emit limitChanged(newLimit); } void BaseModelLoader::setAutoReload(bool newAutoReload) { @@ -90,7 +92,7 @@ bool BaseModelLoader::canReload() const { } void BaseApiModel::reload() { - qWarning() << " BaseApiModel slot called instead of overloaded method"; + qCWarning(jellyfinApiModel) << " BaseApiModel slot called instead of overloaded method"; } // Parameters injectors and result extractors diff --git a/core/src/jellyfin.cpp b/core/src/jellyfin.cpp index 4fcb3f3..3d95be2 100644 --- a/core/src/jellyfin.cpp +++ b/core/src/jellyfin.cpp @@ -75,6 +75,7 @@ void JellyfinPlugin::registerTypes(const char *uri) { qmlRegisterType(uri, 1, 0, "ResumeItemsLoader"); qmlRegisterType(uri, 1, 0, "ShowSeasonsLoader"); qmlRegisterType(uri, 1, 0, "ShowEpisodesLoader"); + qmlRegisterType(uri, 1, 0, "NextUpLoader"); qmlRegisterType(uri, 1, 0, "PublicUsersLoader"); // Enumerations diff --git a/core/src/model/playlist.cpp b/core/src/model/playlist.cpp index 561222b..a08f284 100644 --- a/core/src/model/playlist.cpp +++ b/core/src/model/playlist.cpp @@ -144,6 +144,14 @@ void Playlist::appendToList(ViewModel::ItemModel &model) { reshuffle(); } +void Playlist::appendToList(QSharedPointer item) { + int start = m_list.size(); + emit beforeItemsAddedToList(start, 1); + m_list.append(item); + emit itemsAddedToList(); + reshuffle(); +} + void Playlist::reshuffle() { if (m_shuffler->canShuffleInAdvance()) { m_shuffler->shuffleInAdvance(); diff --git a/core/src/viewmodel/itemmodel.cpp b/core/src/viewmodel/itemmodel.cpp index c778666..cd7cbb0 100644 --- a/core/src/viewmodel/itemmodel.cpp +++ b/core/src/viewmodel/itemmodel.cpp @@ -54,6 +54,9 @@ ShowSeasonsLoader::ShowSeasonsLoader(QObject *parent) ShowEpisodesLoader::ShowEpisodesLoader(QObject *parent) : ShowEpisodesLoaderBase(new Jellyfin::Loader::HTTP::GetEpisodesLoader(), parent) {} +NextUpLoader::NextUpLoader(QObject *parent) + : NextUpLoaderBase(new Jellyfin::Loader::HTTP::GetNextUpLoader(), parent) {} + ItemModel::ItemModel(QObject *parent) : ApiModel(parent) { } @@ -86,6 +89,7 @@ QVariant ItemModel::data(const QModelIndex &index, int role) const { JF_CASE(artists) case RoleNames::isFolder: return QVariant(item->isFolder().value_or(false)); + JF_CASE(overview) case RoleNames::parentIndexNumber: return QVariant(item->parentIndexNumber().value_or(1)); // UserData diff --git a/core/src/viewmodel/playbackmanager.cpp b/core/src/viewmodel/playbackmanager.cpp index f1cdb32..50c48be 100644 --- a/core/src/viewmodel/playbackmanager.cpp +++ b/core/src/viewmodel/playbackmanager.cpp @@ -156,7 +156,9 @@ void PlaybackManager::mediaPlayerMediaStatusChanged(QMediaPlayer::MediaStatus ne m_mediaPlayer->setPosition(m_resumePosition / MS_TICK_FACTOR); } } else if (newStatus == QMediaPlayer::EndOfMedia) { - next(); + if (m_queue->hasNext() && m_queue->totalSize() > 1) { + next(); + } } } @@ -178,6 +180,8 @@ void PlaybackManager::updatePlaybackInfo() { } void PlaybackManager::playItem(Item *item) { + m_queue->clearList(); + m_queue->appendToList(item->data()); setItem(item->data()); emit hasNextChanged(m_queue->hasNext()); emit hasPreviousChanged(m_queue->hasPrevious()); diff --git a/sailfish/CMakeLists.txt b/sailfish/CMakeLists.txt index c130d7e..1ad5f8d 100644 --- a/sailfish/CMakeLists.txt +++ b/sailfish/CMakeLists.txt @@ -38,9 +38,9 @@ set(sailfin_QML_SOURCES qml/components/UserGridDelegate.qml qml/components/VideoPlayer.qml qml/components/VideoTrackSelector.qml - qml/cover/CoverPage.qml + qml/cover/CollectionPage.qml qml/cover/PosterCover.qml - qml/cover/VideoCover.qml + qml/cover/NowPlayingCover.qml qml/pages/LegalPage.qml qml/pages/MainPage.qml qml/pages/AboutPage.qml diff --git a/sailfish/qml/components/PlaybackBar.qml b/sailfish/qml/components/PlaybackBar.qml index 60d63ad..05b7847 100644 --- a/sailfish/qml/components/PlaybackBar.qml +++ b/sailfish/qml/components/PlaybackBar.qml @@ -87,7 +87,7 @@ PanelBackground { Rectangle { id: playQueueShim anchors.fill: albumArt - color: Theme.rgba(Theme.overlayBackgroundColor, Theme.opacityLow) + color: Theme.rgba(Theme.overlayBackgroundColor, Theme.opacityOverlay) opacity: 0 } Loader { @@ -159,6 +159,7 @@ PanelBackground { } icon.source: "image://theme/icon-m-shuffle" opacity: 0 + enabled: false onClicked: Notices.show(qsTr("Shuffle not yet implemented")) } @@ -330,7 +331,7 @@ PanelBackground { target: nextButton; opacity: 1; enabled: true; } PropertyChanges { - target: playModeButton; opacity: 1; //enabled: true; + target: playModeButton; opacity: 1; enabled: true; } PropertyChanges { target: queueButton; opacity: 1; enabled: true; diff --git a/sailfish/qml/cover/CoverPage.qml b/sailfish/qml/cover/CollectionPage.qml similarity index 86% rename from sailfish/qml/cover/CoverPage.qml rename to sailfish/qml/cover/CollectionPage.qml index 5119e50..c215ac9 100644 --- a/sailfish/qml/cover/CoverPage.qml +++ b/sailfish/qml/cover/CollectionPage.qml @@ -31,7 +31,7 @@ CoverBackground { readonly property real rowHeight: height / 2 readonly property real bottomOffset: width - rowHeight readonly property bool onMainScreen: appWindow.itemData === null - readonly property bool itemId: appWindow.itemData.jellyfinId || appWindow.pageStack.currentPage.itemId + readonly property string itemId: appWindow.pageStack.currentPage.itemId readonly property bool hasParent: !appWindow.itemData !== null && appWindow.itemData.jellyfinId.length !== 0 J.ItemModel { @@ -39,14 +39,14 @@ CoverBackground { loader: J.UserItemsLoader { id: randomItemsLoader apiClient: appWindow.apiClient - limit: cover.rowCount * 2 + limit: cover.rowCount * 2 - 2 imageTypes: [J.ImageType.Primary] sortBy: "IsFavoriteOrLiked,Random" recursive: false - parentId: hasParent ? itemId : "" + parentId: itemId autoReload: false onParentIdChanged: { - if (parentId.length > 0) reload() + reload() } } } @@ -82,12 +82,15 @@ CoverBackground { x: -rowHeight * rowOffset y: rowHeight * 0.5 } + PathPercent { + value: 1 + } } delegate: RemoteImage { height: rowHeight width: height source: model.jellyfinId - ? Utils.itemModelImageUrl(appWindow.apiClient.baseUrl, model.jellyfinId, model.imageTags["Primary"], "Primary", {"maxHeight": row1.height}) + ? Utils.itemModelImageUrl(appWindow.apiClient.baseUrl, model.jellyfinId, model.imageTags["Primary"], "Primary", {"maxHeight": rowHeight}) : "" blurhash: model.jellyfinId ? model.imageBlurHashes["Primary"][model.imageTags["Primary"]] @@ -96,14 +99,18 @@ CoverBackground { } } - Rectangle { + /*Rectangle { anchors.fill: parent color: Theme.rgba(Theme.overlayBackgroundColor, Theme.opacityHigh) - } + }*/ Column { anchors.centerIn: parent width: parent.width + opacity: randomItemsLoader.status === J.ModelStatus.Ready ? 0.0 : 1.0 + Behavior on opacity { + NumberAnimation {} + } Image { anchors.horizontalCenter: parent.horizontalCenter source: Qt.resolvedUrl("../icon.png") @@ -122,13 +129,12 @@ CoverBackground { target: appWindow.pageStack onCurrentPageChanged: { console.log("Reloading cover collection") - /*randomItems1Loader.parentId = Qt.binding(function() { return onMainScreen ? "" : appWindow.itemData.jellyfinId; }) - randomItems2Loader.parentId = Qt.binding(function() { return onMainScreen ? "" : appWindow.itemData.jellyfinId; })*/ - //randomItems1.reload() - //randomItems2.reload() } } + + Component.onCompleted: randomItems.reload() + Timer { running: true interval: 5000 diff --git a/sailfish/qml/cover/VideoCover.qml b/sailfish/qml/cover/NowPlayingCover.qml similarity index 70% rename from sailfish/qml/cover/VideoCover.qml rename to sailfish/qml/cover/NowPlayingCover.qml index 7dc3fe1..40ab3f5 100644 --- a/sailfish/qml/cover/VideoCover.qml +++ b/sailfish/qml/cover/NowPlayingCover.qml @@ -28,19 +28,6 @@ import "../components" PosterCover { readonly property var player: appWindow.playbackManager - // Wanted to display the currently running move on here, but it's hard :/ - /*Rectangle { - anchors.fill: parent - color: "black" - - VideoOutput { - id: coverOutput - anchors.fill: parent - source: player - } - - }*/ - Shim { anchors { left: parent.left @@ -51,8 +38,27 @@ PosterCover { } CoverActionList { + enabled: player.hasNext + CoverAction { + iconSource: player.playbackState === MediaPlayer.PlayingState ? "image://theme/icon-cover-pause" + : "image://theme/icon-cover-play" + onTriggered: { + if (player.playbackState === MediaPlayer.PlayingState) { + player.pause() + } else { + player.play() + } + } + } + CoverAction { + iconSource: "image://theme/icon-cover-next-song" + onTriggered: player.next(); + } + } + + CoverActionList { + enabled: !player.hasNext CoverAction { - id: playPause iconSource: player.playbackState === MediaPlayer.PlayingState ? "image://theme/icon-cover-pause" : "image://theme/icon-cover-play" onTriggered: { diff --git a/sailfish/qml/cover/PosterCover.qml b/sailfish/qml/cover/PosterCover.qml index 72e3fa4..2bd769e 100644 --- a/sailfish/qml/cover/PosterCover.qml +++ b/sailfish/qml/cover/PosterCover.qml @@ -27,7 +27,7 @@ import ".." CoverBackground { // Due QTBUG-10822, declarartions such as `property J.Item foo` are not possible. - property var mData: appWindow.itemData + property var mData: appWindow.playbackManager.item RemoteImage { anchors.fill: parent source: mData === null ? "" : Utils.itemImageUrl(appWindow.apiClient.baseUrl, mData, "Primary", {"maxWidth": parent.width}) @@ -78,7 +78,7 @@ CoverBackground { Label { visible: typeof mData.runTimeTicks !== "undefined" color: Theme.secondaryColor - text: Utils.ticksToText(mData.runTimeTicks) + text: qsTr("%1/%2").arg(Utils.timeToText(appWindow.playbackManager.position)).arg(Utils.ticksToText(mData.runTimeTicks)) } } } diff --git a/sailfish/qml/harbour-sailfin.qml b/sailfish/qml/harbour-sailfin.qml index 1b2e34c..9ef119d 100644 --- a/sailfish/qml/harbour-sailfin.qml +++ b/sailfish/qml/harbour-sailfin.qml @@ -81,11 +81,9 @@ ApplicationWindow { cover: { // Disabled due to buggy Loader behaviour if ([MediaPlayer.NoMedia, MediaPlayer.InvalidMedia, MediaPlayer.UnknownStatus].indexOf(_playbackManager.mediaStatus) >= 0) { - return Qt.resolvedUrl("cover/CoverPage.qml") - } else if (playbackManager.hasVideo){ - return Qt.resolvedUrl("cover/VideoCover.qml") + return Qt.resolvedUrl("cover/CollectionPage.qml") } else { - return Qt.resolvedUrl("cover/CoverPage.qml") + return Qt.resolvedUrl("cover/NowPlayingCover.qml") } } diff --git a/sailfish/qml/pages/MainPage.qml b/sailfish/qml/pages/MainPage.qml index 333a74a..5139b7e 100644 --- a/sailfish/qml/pages/MainPage.qml +++ b/sailfish/qml/pages/MainPage.qml @@ -30,6 +30,8 @@ import "../" Page { /// True if the models on this page already have been loaded and don't necessarily need a refresh property bool _modelsLoaded: false + // Only for cover page + readonly property string itemId: "" id: mainPage allowedOrientations: Orientation.All @@ -48,7 +50,7 @@ Page { text: qsTr("Reload") onClicked: loadModels(true) } - busy: mediaLibraryLoader.status === J.UsersViewsLoader.Loading + busy: mediaLibraryLoader.status === J.ModelStatus.Loading } } @@ -80,7 +82,7 @@ Page { //- Section header for films and TV shows that an user hasn't completed yet. text: qsTr("Resume watching") clickable: false - busy: userResumeLoader.status === J.UsersViewsLoader.Loading + busy: userResumeLoader.status === J.ModelStatus.Loading Loader { width: parent.width sourceComponent: carrouselView @@ -102,7 +104,7 @@ Page { //- Section header for next episodes in a TV show that an user was watching. text: qsTr("Next up") clickable: false - //busy: showNextUpModel.status === .Loading + busy: showNextUpLoader.status === J.ModelStatus.Loading Loader { width: parent.width @@ -112,8 +114,11 @@ Page { J.ItemModel { id: showNextUpModel - /*apiClient: appWindow.apiClient - limit: 12*/ + loader: J.NextUpLoader { + id: showNextUpLoader + apiClient: appWindow.apiClient + enableUserData: true + } } } } diff --git a/sailfish/qml/pages/itemdetails/BaseDetailPage.qml b/sailfish/qml/pages/itemdetails/BaseDetailPage.qml index 0d3c3c5..ec85461 100644 --- a/sailfish/qml/pages/itemdetails/BaseDetailPage.qml +++ b/sailfish/qml/pages/itemdetails/BaseDetailPage.qml @@ -103,7 +103,7 @@ Page { if (status === PageStatus.Active) { console.log("Page ready, ItemID: ", itemId, ", UserID: ", apiClient.userId) jItemLoader.autoReload = true - //appWindow.itemData = jItemLoader.data + appWindow.itemData = Qt.binding(function() { return jItemLoader.data; }) } } }