diff --git a/qml/components/PlayToolbar.qml b/qml/components/PlayToolbar.qml index ae44a3a..8ab7622 100644 --- a/qml/components/PlayToolbar.qml +++ b/qml/components/PlayToolbar.qml @@ -34,9 +34,12 @@ Column { id: playImage anchors.fill: parent fillMode: Image.PreserveAspectCrop - color: Theme.overlayBackgroundColor clip: true } + Rectangle { + anchors.fill: parent + color: Theme.rgba(Theme.overlayBackgroundColor, Theme.opacityLow) + } Icon { id: playButton source: "image://theme/icon-l-play" diff --git a/qml/components/Shim.qml b/qml/components/Shim.qml index 9830279..7398f81 100644 --- a/qml/components/Shim.qml +++ b/qml/components/Shim.qml @@ -23,9 +23,10 @@ import Sailfish.Silica 1.0 Rectangle { property real shimOpacity: 1.0 property color shimColor: Theme.overlayBackgroundColor + property bool upsideDown: false gradient: Gradient { - GradientStop { position: 0.0; color: Theme.rgba(shimColor, 0.0); } - GradientStop { position: 1.0; color: Theme.rgba(shimColor, shimOpacity); } + GradientStop { position: upsideDown ? 1.0 : 0.0; color: Theme.rgba(shimColor, 0.0); } + GradientStop { position: upsideDown ? 0.0 : 1.0; color: Theme.rgba(shimColor, shimOpacity); } } } diff --git a/qml/cover/PosterCover.qml b/qml/cover/PosterCover.qml index 91415c1..d8a032d 100644 --- a/qml/cover/PosterCover.qml +++ b/qml/cover/PosterCover.qml @@ -23,6 +23,7 @@ import Sailfish.Silica 1.0 import nl.netsoj.chris.Jellyfin 1.0 import "../components" +import ".." CoverBackground { property var mData: appWindow.itemData @@ -34,4 +35,52 @@ CoverBackground { fillMode: Image.PreserveAspectCrop } + Shim { + // Movies usually show their name on the poster, + // so showing it here as well is a bit double + visible: itemData.Type !== "Movie" + anchors { + left: parent.left + right: parent.right + top: parent.top + } + upsideDown: true + height: parent.height / 2 + + Rectangle { + anchors { + top: parent.top + left: parent.left + } + width: itemData.UserData.PlayedPercentage / 100 * parent.width + height: Theme.paddingSmall + color: Theme.highlightColor + } + + Column { + id: infoColumn + anchors { + top: parent.top + left: parent.left + right: parent.right + } + anchors.margins: Theme.paddingMedium + Label { + id: itemName + anchors { + left: parent.left + right: parent.right + } + color: Theme.primaryColor + text: itemData.Name + truncationMode: TruncationMode.Fade + } + Label { + visible: typeof itemData.RunTimeTicks !== "undefined" + color: Theme.secondaryColor + text: Utils.ticksToText(itemData.RunTimeTicks) + } + } + } + } diff --git a/qml/cover/VideoCover.qml b/qml/cover/VideoCover.qml index a385422..898861f 100644 --- a/qml/cover/VideoCover.qml +++ b/qml/cover/VideoCover.qml @@ -50,6 +50,15 @@ CoverBackground { fillMode: Image.PreserveAspectCrop } + Shim { + anchors { + left: parent.left + bottom: parent.bottom + right: parent.right + } + height: Theme.iconSizeLarge + } + CoverActionList { CoverAction { id: playPause diff --git a/qml/pages/MainPage.qml b/qml/pages/MainPage.qml index 6f4d872..1e08747 100644 --- a/qml/pages/MainPage.qml +++ b/qml/pages/MainPage.qml @@ -43,6 +43,10 @@ Page { text: qsTr("Settings") onClicked: pageStack.push(Qt.resolvedUrl("SettingsPage.qml")) } + MenuItem { + text: qsTr("Refresh") + onClicked: loadModels(true) + } busy: mediaLibraryModel.status == ApiModel.Loading } @@ -198,7 +202,7 @@ Page { /*model.imageTags["Primary"] ? ApiClient.baseUrl + "/Items/" + model.id + "/Images/Primary?maxHeight=" + height + "&tag=" + model.imageTags["Primary"] : ""*/ - landscape: !Utils.usePortraitCover(model.type) + landscape: !Utils.usePortraitCover(collectionType) progress: (typeof model.userData !== "undefined") ? model.userData.PlayedPercentage / 100 : 0.0 onClicked: { diff --git a/qml/pages/itemdetails/BaseDetailPage.qml b/qml/pages/itemdetails/BaseDetailPage.qml index 7ce36df..2091181 100644 --- a/qml/pages/itemdetails/BaseDetailPage.qml +++ b/qml/pages/itemdetails/BaseDetailPage.qml @@ -44,6 +44,7 @@ Page { on_ParentBackdropImagesChanged: updateBackdrop() function updateBackdrop() { + return; if (_backdropImages && _backdropImages.length > 0) { var rand = Math.floor(Math.random() * (_backdropImages.length - 0.001)) console.log("Random: ", rand) diff --git a/qml/pages/itemdetails/EpisodePage.qml b/qml/pages/itemdetails/EpisodePage.qml index 4b68575..f05cbfc 100644 --- a/qml/pages/itemdetails/EpisodePage.qml +++ b/qml/pages/itemdetails/EpisodePage.qml @@ -24,56 +24,25 @@ import nl.netsoj.chris.Jellyfin 1.0 import "../../components" import "../../" -BaseDetailPage { - SilicaFlickable { - anchors.fill: parent - contentHeight: content.height - Column { - id: content - width: parent.width - - PageHeader { - title: itemData.Name - description: { - if (typeof itemData.IndexNumberEnd !== "undefined") { - qsTr("Episode %1–%2 | %3").arg(itemData.IndexNumber) - .arg(itemData.IndexNumberEnd) - .arg(itemData.SeasonName) - } else { - qsTr("Episode %1 | %2").arg(itemData.IndexNumber).arg(itemData.SeasonName) - } - } - } - - PlayToolbar { - imageSource: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width}) - imageAspectRatio: itemData.PrimaryImageAspectRatio || 1.0 - playProgress: itemData.UserData.PlayedPercentage / 100 - onPlayPressed: pageStack.push(Qt.resolvedUrl("../VideoPage.qml"), - {"itemId": itemId, "itemData": itemData, - "audioTrack": trackSelector.audioTrack, - "subtitleTrack": trackSelector.subtitleTrack, - "startTicks": startFromBeginning ? 0.0 - : itemData.UserData.PlaybackPositionTicks }) - width: parent.width - } - - VideoTrackSelector { - id: trackSelector - width: parent.width - tracks: itemData.MediaStreams - } - - SectionHeader { - text: qsTr("Overview") - } - - PlainLabel { - id: overviewText - text: itemData.Overview || qsTr("No overview available") - font.pixelSize: Theme.fontSizeSmall - color: Theme.secondaryHighlightColor - } +VideoPage { + subtitle: { + if (typeof itemData.IndexNumberEnd !== "undefined") { + qsTr("Episode %1–%2 | %3").arg(itemData.IndexNumber) + .arg(itemData.IndexNumberEnd) + .arg(itemData.SeasonName) + } else { + qsTr("Episode %1 | %2").arg(itemData.IndexNumber).arg(itemData.SeasonName) } } + + SectionHeader { + text: qsTr("Overview") + } + + PlainLabel { + id: overviewText + text: itemData.Overview || qsTr("No overview available") + font.pixelSize: Theme.fontSizeSmall + color: Theme.secondaryHighlightColor + } } diff --git a/qml/pages/itemdetails/FilmPage.qml b/qml/pages/itemdetails/FilmPage.qml index e02160a..ed7a3d1 100644 --- a/qml/pages/itemdetails/FilmPage.qml +++ b/qml/pages/itemdetails/FilmPage.qml @@ -25,46 +25,17 @@ import nl.netsoj.chris.Jellyfin 1.0 import "../../components" import "../.." -BaseDetailPage { - SilicaFlickable { - anchors.fill: parent - contentHeight: content.height +VideoPage { + subtitle: qsTr("Released: %1 — Run time: %2").arg(itemData.ProductionYear).arg(Utils.ticksToText(itemData.RunTimeTicks)) - Column { - id: content - width: parent.width - spacing: Theme.paddingMedium + SectionHeader { + text: qsTr("Overview") + } - PageHeader { - title: itemData.Name - description: qsTr("Released: %1 — Run time: %2").arg(itemData.ProductionYear).arg(Utils.ticksToText(itemData.RunTimeTicks)) - } - - PlayToolbar { - width: parent.width - imageSource: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width}) - imageAspectRatio: 1.66666 //itemData.PrimaryImageAspectRatio - onPlayPressed: pageStack.push(Qt.resolvedUrl("../../pages/VideoPage.qml"), - {"itemId": itemId, "itemData": itemData, "audioTrack": trackSelector.audioTrack, - "subtitleTrack": trackSelector.subtitleTrack }) - } - - VideoTrackSelector { - id: trackSelector - width: parent.width - tracks: itemData.MediaStreams - } - - SectionHeader { - text: qsTr("Overview") - } - - PlainLabel { - id: overviewText - text: itemData.Overview - font.pixelSize: Theme.fontSizeSmall - color: Theme.secondaryHighlightColor - } - } + PlainLabel { + id: overviewText + text: itemData.Overview + font.pixelSize: Theme.fontSizeSmall + color: Theme.secondaryHighlightColor } } diff --git a/qml/pages/itemdetails/SeasonPage.qml b/qml/pages/itemdetails/SeasonPage.qml index 64f2214..99b852e 100644 --- a/qml/pages/itemdetails/SeasonPage.qml +++ b/qml/pages/itemdetails/SeasonPage.qml @@ -26,107 +26,98 @@ import "../../components" import ".." BaseDetailPage { - SilicaFlickable { + ShowEpisodesModel { + id: episodeModel + apiClient: ApiClient + show: itemData.SeriesId + seasonId: itemData.Id + fields: ["Overview"] + } + + SilicaListView { anchors.fill: parent contentHeight: content.height + header: PageHeader { + title: itemData.Name + description: itemData.SeriesName + } + model: episodeModel + delegate: BackgroundItem { + height: Constants.libraryDelegateHeight + RemoteImage { + id: episodeImage + anchors { + top: parent.top + left: parent.left + bottom: parent.bottom + } + width: Constants.libraryDelegateWidth + height: Constants.libraryDelegateHeight + source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": height}) + fillMode: Image.PreserveAspectCrop + clip: true - Column { - id: content - width: parent.width - - PageHeader { - title: itemData.Name - description: itemData.SeriesName - } - - ShowEpisodesModel { - id: episodeModel - apiClient: ApiClient - show: itemData.SeriesId - seasonId: itemData.Id - fields: ["Overview"] - } - - ColumnView { - model: episodeModel - itemHeight: Constants.libraryDelegateHeight - delegate: BackgroundItem { - height: Constants.libraryDelegateHeight - RemoteImage { - id: episodeImage - anchors { - top: parent.top - left: parent.left - bottom: parent.bottom - } - width: Constants.libraryDelegateWidth - height: Constants.libraryDelegateHeight - source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["Primary"], "Primary", {"maxHeight": height}) - fillMode: Image.PreserveAspectCrop - clip: true - - // Makes the progress bar stand out more - Shim { - anchors { - left: parent.left - bottom: parent.bottom - right: parent.right - } - height: parent.height / 3 - shimColor: Theme.overlayBackgroundColor - shimOpacity: Theme.opacityOverlay - //width: model.userData.PlayedPercentage * parent.width / 100 - visible: episodeProgress.width > 0 // It doesn't look nice when it's visible on every image - } - - Rectangle { - id: episodeProgress - anchors { - left: parent.left - bottom: parent.bottom - } - height: Theme.paddingMedium - width: model.userData.PlayedPercentage * parent.width / 100 - color: Theme.highlightColor - } + // Makes the progress bar stand out more + Shim { + anchors { + left: parent.left + bottom: parent.bottom + right: parent.right } + height: parent.height / 3 + shimColor: Theme.overlayBackgroundColor + shimOpacity: Theme.opacityOverlay + //width: model.userData.PlayedPercentage * parent.width / 100 + visible: episodeProgress.width > 0 // It doesn't look nice when it's visible on every image + } - Label { - id: episodeTitle - anchors { - left: episodeImage.right - leftMargin: Theme.paddingLarge - top: parent.top - right: parent.right - rightMargin: Theme.horizontalPageMargin - } - text: model.name - truncationMode: TruncationMode.Fade - horizontalAlignment: Text.AlignLeft + Rectangle { + id: episodeProgress + anchors { + left: parent.left + bottom: parent.bottom } - - Label { - id: episodeOverview - anchors { - left: episodeImage.right - leftMargin: Theme.paddingLarge - right: parent.right - rightMargin: Theme.horizontalPageMargin - top: episodeTitle.bottom - bottom: parent.bottom - } - color: highlighted ? Theme.secondaryHighlightColor: Theme.secondaryColor - font.pixelSize: Theme.fontSizeExtraSmall - //: No overview/summary text of an episode available - text: model.overview || qsTr("No overview available") - wrapMode: Text.WordWrap - elide: Text.ElideRight - } - onClicked: pageStack.push(Utils.getPageUrl(model.mediaType, model.type), {"itemId": model.id}) + height: Theme.paddingMedium + width: model.userData.PlayedPercentage * parent.width / 100 + color: Theme.highlightColor } } + Label { + id: episodeTitle + anchors { + left: episodeImage.right + leftMargin: Theme.paddingLarge + top: parent.top + right: parent.right + rightMargin: Theme.horizontalPageMargin + } + text: model.name + truncationMode: TruncationMode.Fade + horizontalAlignment: Text.AlignLeft + } + + Label { + id: episodeOverview + anchors { + left: episodeImage.right + leftMargin: Theme.paddingLarge + right: parent.right + rightMargin: Theme.horizontalPageMargin + top: episodeTitle.bottom + bottom: parent.bottom + } + color: highlighted ? Theme.secondaryHighlightColor: Theme.secondaryColor + font.pixelSize: Theme.fontSizeExtraSmall + //: No overview/summary text of an episode available + text: model.overview || qsTr("No overview available") + wrapMode: Text.WordWrap + elide: Text.ElideRight + } + onClicked: pageStack.push(Utils.getPageUrl(model.mediaType, model.type), {"itemId": model.id}) } + + VerticalScrollDecorator {} } onItemDataChanged: { console.log(JSON.stringify(itemData)) diff --git a/qml/pages/itemdetails/VideoPage.qml b/qml/pages/itemdetails/VideoPage.qml index 59b38cd..f962aa6 100644 --- a/qml/pages/itemdetails/VideoPage.qml +++ b/qml/pages/itemdetails/VideoPage.qml @@ -30,9 +30,13 @@ import "../.." * the FilmPage or EpisodePage. */ BaseDetailPage { + property alias subtitle: pageHeader.description + default property alias _data: content.data SilicaFlickable { anchors.fill: parent - contentHeight: content.height + contentHeight: content.height + Theme.paddingLarge + + VerticalScrollDecorator {} Column { id: content @@ -40,17 +44,23 @@ BaseDetailPage { spacing: Theme.paddingMedium PageHeader { + id: pageHeader title: itemData.Name description: qsTr("Run time: %2").arg(Utils.ticksToText(itemData.RunTimeTicks)) } PlayToolbar { + id: toolbar width: parent.width imageSource: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width}) imageAspectRatio: Constants.horizontalVideoAspectRatio - onPlayPressed: pageStack.push(Qt.resolvedUrl("../../pages/VideoPage.qml"), - {"itemId": itemId, "itemData": itemData, "audioTrack": trackSelector.audioTrack, - "subtitleTrack": trackSelector.subtitleTrack }) + playProgress: itemData.UserData.PlayedPercentage / 100 + onPlayPressed: pageStack.push(Qt.resolvedUrl("../VideoPage.qml"), + {"itemId": itemId, "itemData": itemData, + "audioTrack": trackSelector.audioTrack, + "subtitleTrack": trackSelector.subtitleTrack, + "startTicks": startFromBeginning ? 0.0 + : itemData.UserData.PlaybackPositionTicks }) } VideoTrackSelector {