From 306693fa041995e776726721b784e0af116d0536 Mon Sep 17 00:00:00 2001 From: Chris Josten Date: Mon, 15 Feb 2021 00:27:36 +0100 Subject: [PATCH] Add playbackBar -> playbackPage transition --- sailfish/qml/components/PlaybackBar.qml | 400 ++++++++++++++++++++++-- sailfish/qml/components/RemoteImage.qml | 4 +- sailfish/qml/harbour-sailfin.qml | 11 +- sailfish/qml/pages/VideoPage.qml | 3 +- 4 files changed, 385 insertions(+), 33 deletions(-) diff --git a/sailfish/qml/components/PlaybackBar.qml b/sailfish/qml/components/PlaybackBar.qml index d96071b..6e4a29c 100644 --- a/sailfish/qml/components/PlaybackBar.qml +++ b/sailfish/qml/components/PlaybackBar.qml @@ -18,7 +18,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ import QtQuick 2.6 -import QtQuick.Layouts 1.1 import QtMultimedia 5.6 import Sailfish.Silica 1.0 @@ -36,34 +35,69 @@ import "../" * |/ \| +---+ | * +-----+------------------------------------+ */ -DockedPanel { - height: content.height +PanelBackground { + id: playbackBar + height: Theme.itemSizeLarge width: parent.width + y: parent.height property PlaybackManager manager + property bool open + property real visibleSize: height - RowLayout { - id: content + property bool _pageWasShowingNavigationIndicator + + + transform: Translate {id: playbackBarTranslate; y: 0} + + BackgroundItem { + id: backgroundItem width: parent.width - height: Theme.itemSizeLarge + height: parent.height + onClicked: playbackBar.state = (playbackBar.state == "large" ? "open" : "large") + RemoteImage { - Layout.fillHeight: true - Layout.preferredWidth: content.height + id: albumArt + anchors { + left: parent.left + bottom: parent.bottom + top: parent.top + } + width: height blurhash: manager.item.imageBlurHashes["Primary"][manager.item.imageTags["Primary"]] - source: Utils.itemImageUrl(ApiClient.baseUrl, manager.item, "Primary", {"maxWidth": parent.width}) + source: largeAlbumArt.source fillMode: Image.PreserveAspectCrop + + Image { + id: largeAlbumArt + source: Utils.itemImageUrl(ApiClient.baseUrl, manager.item, "Primary") + fillMode: Image.PreserveAspectFit + anchors.fill: parent + opacity: 0 + Behavior on opacity { FadeAnimation {} } + } } - Item { height: 1; Layout.preferredWidth: Theme.paddingMedium; } // Padding + Column { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - Layout.minimumWidth: 10 * Theme.pixelRatio + id: artistInfo + height: Theme.fontSizeMedium + Theme.fontSizeLarge + + anchors { + left: albumArt.right + leftMargin: Theme.paddingMedium + right: playButton.left + verticalCenter: parent.verticalCenter + } + Label { + id: name text: manager.item == null ? qsTr("No media selected") : manager.item.name - width: parent.width + width: Math.min(contentWidth, parent.width) font.pixelSize: Theme.fontSizeMedium + maximumLineCount: 1 truncationMode: TruncationMode.Fade } Label { + id: artists text: { if (manager.item == null) return qsTr("Play some media!") console.log(manager.item.type) @@ -72,35 +106,345 @@ DockedPanel { return manager.item.artists.join(", ") } } - width: parent.width + width: Math.min(contentWidth, parent.width) font.pixelSize: Theme.fontSizeSmall + maximumLineCount: 1 truncationMode: TruncationMode.Fade color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor } } - Item { height: 1; Layout.preferredWidth: Theme.paddingMedium; } // Padding + IconButton { + id: playModeButton + anchors { + right: previousButton.left + rightMargin: Theme.paddingLarge + verticalCenter: playButton.verticalCenter + } + icon.source: "image://theme/icon-m-shuffle" + enabled: false + opacity: 0 + } + + IconButton { + id: previousButton + anchors { + right: playButton.left + rightMargin: Theme.paddingLarge + verticalCenter: playButton.verticalCenter + } + icon.source: "image://theme/icon-m-previous" + enabled: false + opacity: 0 + } + IconButton { id: playButton - Layout.preferredHeight: Theme.iconSizeMedium - Layout.preferredWidth: Theme.iconSizeMedium + anchors { + right: parent.right + rightMargin: Theme.paddingMedium + verticalCenter: parent.verticalCenter + } icon.source: appWindow.mediaPlayer.playbackState === MediaPlayer.PlayingState ? "image://theme/icon-m-pause" : "image://theme/icon-m-play" onClicked: appWindow.mediaPlayer.playbackState === MediaPlayer.PlayingState ? appWindow.mediaPlayer.pause() : appWindow.mediaPlayer.play() } - Item { height: 1; Layout.preferredWidth: Theme.paddingMedium; } // Padding + IconButton { + id: nextButton + anchors { + left: playButton.right + leftMargin: Theme.paddingLarge + verticalCenter: playButton.verticalCenter + } + icon.source: "image://theme/icon-m-next" + enabled: false + opacity: 0 + } + IconButton { + id: queueButton + anchors { + left: nextButton.right + leftMargin: Theme.paddingLarge + verticalCenter: playButton.verticalCenter + } + icon.source: "image://theme/icon-m-menu" + enabled: false + opacity: 0 + } + + ProgressBar { + id: progressBar + anchors.verticalCenter: parent.top + width: parent.width + leftMargin: Theme.itemSizeLarge + rightMargin: 0 + minimumValue: 0 + value: appWindow.mediaPlayer.position + maximumValue: appWindow.mediaPlayer.duration + indeterminate: [MediaPlayer.Loading, MediaPlayer.Buffering].indexOf(appWindow.mediaPlayer.status) >= 0 + } + + Slider { + id: seekBar + animateValue: false + anchors.verticalCenter: progressBar.top + minimumValue: 0 + value: appWindow.mediaPlayer.position + maximumValue: appWindow.mediaPlayer.duration + width: parent.width + stepSize: 1000 + valueText: Utils.timeToText(value) + enabled: false + visible: false + onDownChanged: { if (!down) { + appWindow.mediaPlayer.seek(value); + // For some reason, the binding breaks when dragging the slider. + value = Qt.binding(function() { return appWindow.mediaPlayer.position}) + } + } + } + } - ProgressBar { - anchors.verticalCenter: parent.top - width: parent.width - leftMargin: Theme.itemSizeLarge - rightMargin: 0 - minimumValue: 0 - value: appWindow.mediaPlayer.position - maximumValue: appWindow.mediaPlayer.duration - indeterminate: [MediaPlayer.Loading, MediaPlayer.Buffering].indexOf(appWindow.mediaPlayer.status) >= 0 + states: [ + State { + name: "" + when: appWindow.mediaPlayer.playbackState !== MediaPlayer.StoppedState && state != "page" && !("__hidePlaybackBar" in pageStack.currentPage) + }, + State { + name: "large" + PropertyChanges { + target: playbackBar + height: Screen.height + } + PropertyChanges { + target: albumArt + state: "blurhash" + width: parent.width + anchors.bottomMargin: Theme.paddingLarge + } + AnchorChanges { + target: albumArt + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: artistInfo.top + } + + PropertyChanges { + target: artistInfo + anchors.leftMargin: Theme.horizontalPageMargin + anchors.rightMargin: Theme.horizontalPageMargin + anchors.bottomMargin: Theme.paddingLarge + seekBar.height - progressBar.height + height: Theme.fontSizeLarge + Theme.fontSizeMedium + } + + AnchorChanges { + target: artistInfo + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: progressBar.top + anchors.verticalCenter: undefined + } + + PropertyChanges { + target: progressBar + leftMargin: Screen.width / 8 + rightMargin: Screen.width / 8 + anchors.bottomMargin: Theme.paddingLarge + opacity: 0 + visible: false + } + + AnchorChanges { + target: progressBar + anchors.verticalCenter: undefined + anchors.bottom: playButton.top + } + + PropertyChanges { + target: playButton + anchors.bottomMargin: Theme.paddingLarge * 2 + /*icon.source: appWindow.mediaPlayer.playbackState === MediaPlayer.PlayingState + ? "image://theme/icon-l-pause" : "image://theme/icon-l-play"*/ + width: Theme.itemSizeMedium + height: Theme.itemSizeMedium + icon.width: icon.implicitWidth * 1.5 + icon.height: icon.implicitWidth * 1.5 + } + + AnchorChanges { + target: playButton + anchors.right: undefined + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: undefined + anchors.bottom: parent.bottom + } + + PropertyChanges { + target: previousButton; opacity: 1; enabled: true; + } + PropertyChanges { + target: nextButton; opacity: 1; enabled: true; + } + PropertyChanges { + target: playModeButton; opacity: 1; enabled: true; + } + PropertyChanges { + target: queueButton; opacity: 1; enabled: true; + } + PropertyChanges { + target: seekBar + enabled: true + visible: true + animateValue: true + } + PropertyChanges { + target: largeAlbumArt + opacity: status == Image.Ready ? 1.0 : 0.0 + } + PropertyChanges { + target: artists + font.pixelSize: Theme.fontSizeMedium + } + AnchorChanges { + target: artists + anchors.horizontalCenter: parent.horizontalCenter + } + PropertyChanges { + target: name + font.pixelSize: Theme.fontSizeLarge + } + AnchorChanges { + target: name + anchors.horizontalCenter: parent.horizontalCenter + } + PropertyChanges { + target: backgroundItem + enabled: false + } + + }, + State { + name: "hidden" + when: (appWindow.mediaPlayer.playbackState === MediaPlayer.StoppedState || "__hidePlaybackBar" in pageStack.currentPage) && state != "page" + PropertyChanges { + target: playbackBarTranslate + y: playbackBar.height + } + PropertyChanges { + target: playbackBar + visibleSize: 0 + } + PropertyChanges { + target: albumArt + source: "" + } + }, + State { + name: "page" + extend: "large" + } + ] + + Component { + id: fullPage + Page { + property bool __hidePlaybackBar: true + showNavigationIndicator: true + Loader { + Component.onCompleted: setSource(Qt.resolvedUrl("PlaybackBar.qml"), + {"state": "page", "manager": manager, "y": 0}) + anchors.fill: parent + } + } } + + transitions: [ + Transition { + from: "*" + to: "large" + // We animate this object to a large size and then quickly swap out this component + // with a page containing this component. + SequentialAnimation { + ScriptAction { + script: { + _pageWasShowingNavigationIndicator = pageStack.currentPage.showNavigationIndicator + appWindow.pageStack.currentPage.showNavigationIndicator = false + seekBar.enabled = true + seekBar.visible = true + } + } + ParallelAnimation { + NumberAnimation { + properties: "width,height,targetX,targetY,leftMargin,rightMargin,font.pixelSize" + easing.type: Easing.OutCubic + duration: 300 // Long, but avoids stutters + } + AnchorAnimation { + easing.type: Easing.OutCubic + duration: 300 + } + FadeAnimation { + duration: 300 + } + } + ScriptAction { + script: { + pageStack.currentPage.showNavigationIndicator = _pageWasShowingNavigationIndicator + pageStack.push(fullPage, {"background": pageStack.currentPage.background}, PageStackAction.Immediate) + } + } + } + }, + Transition { + from: "*" + to: "page" + }, + Transition { + from: "hidden" + SequentialAnimation { + ParallelAnimation { + NumberAnimation { + targets: [playbackBarTranslate, playbackBar] + properties: "y,visibileSize" + duration: 250 + easing.type: Easing.OutQuad + } + + NumberAnimation { + target: appWindow + property: "bottomMargin" + duration: 250 + to: Theme.itemSizeLarge + easing.type: Easing.OutQuad + } + } + } + }, + Transition { + from: "" + to: "hidden" + SequentialAnimation { + ParallelAnimation { + NumberAnimation { + targets: [playbackBarTranslate, playbackBar] + properties: "y,visibileSize" + duration: 250 + easing.type: Easing.OutQuad + } + + NumberAnimation { + target: appWindow + property: "bottomMargin" + duration: 250 + to: 0 + easing.type: Easing.OutQuad + } + } + } + } + ] } diff --git a/sailfish/qml/components/RemoteImage.qml b/sailfish/qml/components/RemoteImage.qml index ade3c7a..f0ffe81 100644 --- a/sailfish/qml/components/RemoteImage.qml +++ b/sailfish/qml/components/RemoteImage.qml @@ -143,8 +143,8 @@ SilicaItem { transitions: [ Transition { - from: "*" - to: "*" + from: "blurhash,fallback" + to: "loaded" FadeAnimation {} } ] diff --git a/sailfish/qml/harbour-sailfin.qml b/sailfish/qml/harbour-sailfin.qml index 52d1022..f1c630d 100644 --- a/sailfish/qml/harbour-sailfin.qml +++ b/sailfish/qml/harbour-sailfin.qml @@ -48,7 +48,13 @@ ApplicationWindow { Connections { target: pageStack onCurrentPageChanged: { - _hidePlaybackBar = "__videoPlaybackPage" in pageStack.currentPage + /*_hidePlaybackBar = "__hidePlaybackBar" in pageStack.currentPage + if (_hidePlaybackBar) { + playbackBar.state = "hidden" + } else { + //playbackBar.state = "" + //appWindow.bottomMargin = playbackBar.height + }*/ console.log("Current page changed: " + _hidePlaybackBar) } } @@ -133,7 +139,8 @@ ApplicationWindow { PlaybackBar { id: playbackBar - open: !_hidePlaybackBar//_mediaPlayer.playbackState != MediaPlayer.StoppedState + //open: !_hidePlaybackBar//_mediaPlayer.playbackState != MediaPlayer.StoppedState manager: _playbackManager + //state: "hidden" } } diff --git a/sailfish/qml/pages/VideoPage.qml b/sailfish/qml/pages/VideoPage.qml index 6053950..6ebb7d2 100644 --- a/sailfish/qml/pages/VideoPage.qml +++ b/sailfish/qml/pages/VideoPage.qml @@ -31,7 +31,8 @@ import nl.netsoj.chris.Jellyfin 1.0 Page { id: videoPage - property bool __videoPlaybackPage: true + // PlaybackBar will hide itself when it encounters a page with such a property + property bool __hidePlaybackBar: true property JellyfinItem itemData property int audioTrack property int subtitleTrack