From a172b6d91479638d0b7d73e86fa743331ce0301d Mon Sep 17 00:00:00 2001 From: Chris Josten Date: Tue, 27 Oct 2020 02:35:50 +0100 Subject: [PATCH] Album page landscape improvements --- sailfish/CMakeLists.txt | 7 +- sailfish/qml/Constants.qml | 26 +- sailfish/qml/Utils.js | 8 +- .../qml/components/music/NarrowAlbumCover.qml | 166 +++++++++++ .../qml/components/music/SongDelegate.qml | 95 +++++++ .../qml/components/music/WideAlbumCover.qml | 60 ++++ sailfish/qml/pages/MainPage.qml | 6 + .../qml/pages/itemdetails/CollectionPage.qml | 2 +- .../qml/pages/itemdetails/MusicAlbumPage.qml | 262 +++++------------- 9 files changed, 433 insertions(+), 199 deletions(-) create mode 100644 sailfish/qml/components/music/NarrowAlbumCover.qml create mode 100644 sailfish/qml/components/music/SongDelegate.qml create mode 100644 sailfish/qml/components/music/WideAlbumCover.qml diff --git a/sailfish/CMakeLists.txt b/sailfish/CMakeLists.txt index 9954f69..c41f2e0 100644 --- a/sailfish/CMakeLists.txt +++ b/sailfish/CMakeLists.txt @@ -10,6 +10,11 @@ set(harbour-sailfin_SOURCES set(sailfin_QML_SOURCES qml/Constants.qml qml/Utils.js + qml/components/music/NarrowAlbumCover.qml + qml/components/music/WideAlbumCover.qml + qml/components/music/SongDelegate.qml + qml/components/videoplayer/VideoError.qml + qml/components/videoplayer/VideoHud.qml qml/components/GlassyBackground.qml qml/components/IconListItem.qml qml/components/LibraryItemDelegate.qml @@ -21,8 +26,6 @@ set(sailfin_QML_SOURCES qml/components/UserGridDelegate.qml qml/components/VideoPlayer.qml qml/components/VideoTrackSelector.qml - qml/components/videoplayer/VideoError.qml - qml/components/videoplayer/VideoHud.qml qml/cover/CoverPage.qml qml/cover/PosterCover.qml qml/cover/VideoCover.qml diff --git a/sailfish/qml/Constants.qml b/sailfish/qml/Constants.qml index a7d1a8b..62086e1 100644 --- a/sailfish/qml/Constants.qml +++ b/sailfish/qml/Constants.qml @@ -23,10 +23,30 @@ import QtQuick 2.6 import Sailfish.Silica 1.0 QtObject { - readonly property real libraryDelegateWidth: Screen.width / 3 - readonly property real libraryDelegateHeight: Screen.width / 3 + readonly property real libraryDelegateWidth: { + switch(Screen.sizeCategory) { + case Screen.Small: + case Screen.Medium: + return Screen.width / 3 + case Screen.Large: + return Screen.width / 5 + case Screen.ExtraLarge: + return Screen.width / 7 + } + } + readonly property real libraryDelegateHeight: { + switch(Screen.sizeCategory) { + case Screen.Small: + case Screen.Medium: + return Screen.width / 3 + case Screen.Large: + return Screen.width / 5 + case Screen.ExtraLarge: + return Screen.width / 7 + } + } - readonly property real libraryDelegatePosterHeight: Screen.width / 2 + readonly property real libraryDelegatePosterHeight: libraryDelegateHeight * 1.6667 readonly property real libraryProgressHeight: Theme.paddingMedium diff --git a/sailfish/qml/Utils.js b/sailfish/qml/Utils.js index 9c050e1..f31312d 100644 --- a/sailfish/qml/Utils.js +++ b/sailfish/qml/Utils.js @@ -53,11 +53,15 @@ function itemImageUrl(baseUrl, item, type, options) { } function itemModelImageUrl(baseUrl, itemId, tag, type, options) { - if (tag == undefined) return "" + if (tag === undefined) return "" var extraQuery = ""; for (var prop in options) { if (options.hasOwnProperty(prop)) { - extraQuery += "&" + prop + "=" + options[prop]; + var value = options[prop]; + if (prop === "maxWidth" || prop === "maxHeight") { + value = Math.floor(options[prop]); + } + extraQuery += "&" + prop + "=" + value; } } return baseUrl + "/Items/" + itemId + "/Images/" + type + "?tag=" + tag + extraQuery diff --git a/sailfish/qml/components/music/NarrowAlbumCover.qml b/sailfish/qml/components/music/NarrowAlbumCover.qml new file mode 100644 index 0000000..34ce510 --- /dev/null +++ b/sailfish/qml/components/music/NarrowAlbumCover.qml @@ -0,0 +1,166 @@ +/* +Sailfin: a Jellyfin client written using Qt +Copyright (C) 2020 Chris Josten + +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 +*/ + +import QtQuick 2.6 +import Sailfish.Silica 1.0 + +import ".." +import "../.." + +/** + * Album details for "narrow" devices such as phones in portrait mode. + */ +Item { + property ListView listview + property real releaseYear + property alias albumArt: albumArt.source + property string albumArtist + property real duration + property int songCount + property string name + + + property string stateIfArt: "largeArt" + property alias _albumArt: albumArt + id: listHeader + width: parent.width + //spacing: Theme.paddingLarge + state: _albumArt.source != "" ? stateIfArt : "noArt" + MouseArea { + anchors.fill: parent + onClicked: { + if (listHeader.stateIfArt == "largeArt") { + listHeader.stateIfArt = "details" + } else { + listHeader.stateIfArt = "largeArt" + } + } + } + RemoteImage { + id: albumArt + anchors { + top: parent.top + right: parent.right + } + sourceSize.width: listHeader.width + sourceSize.height: listHeader.width + fillMode: Image.PreserveAspectFit + opacity: 1 + clip: true + } + PageHeader { + id: albumHeader + width: parent.width - Theme.horizontalPageMargin - height + title: name + //: Short description of the album: %1 -> album artist, %2 -> amount of songs, %3 -> duration, %4 -> release year + description: qsTr("%1\n%2 songs | %3 | %4") + .arg(albumArtist) + .arg(songCount) + .arg(Utils.ticksToText(duration)) + //: Unknown album release year + .arg(releaseYear >= 0 ? releaseYear : qsTr("Unknown year")) + } + + states: [ + State { + name: "largeArt" + PropertyChanges { + target: _albumArt + width: parent.width + height: width + } + PropertyChanges { + target: listHeader + height: width + } + PropertyChanges { + target: albumHeader + opacity: 0 + } + PropertyChanges { + target: listview + contentY: -listview.width + } + AnchorChanges { + target: albumHeader + anchors.left: undefined + anchors.right: _albumArt.left + } + }, + State { + name: "details" + PropertyChanges { + target: _albumArt + width: height + height: albumHeader.height + } + PropertyChanges { + target: listHeader + height: albumHeader.height + } + PropertyChanges { + target: albumHeader + opacity: 1 + } + PropertyChanges { + target: listview + contentY: -albumHeader.height + } + AnchorChanges { + target: albumHeader + anchors.left: undefined + anchors.right: _albumArt.left + } + }, + State { + name: "noArt" + extend: "details" + PropertyChanges { + target: _albumArt + opacity: 0 + } + PropertyChanges { + target: albumHeader + width: parent.width - Theme.horizontalPageMargin * 2 + } + AnchorChanges { + target: albumHeader + anchors.left: parent.left + anchors.right: parent.right + } + } + ] + transitions: [ + Transition { + from: "noArt" + // No transitions from "noArt", otherwise the layout animates every load + }, + Transition { + OpacityAnimator { target: albumHeader} + OpacityAnimator { target: _albumArt} + NumberAnimation { + properties: "width,height,contentY" + //velocity: 1600 + duration: 300 + easing.type: Easing.OutQuad + } + AnchorAnimation {} + } + ] +} diff --git a/sailfish/qml/components/music/SongDelegate.qml b/sailfish/qml/components/music/SongDelegate.qml new file mode 100644 index 0000000..d0604a5 --- /dev/null +++ b/sailfish/qml/components/music/SongDelegate.qml @@ -0,0 +1,95 @@ +/* +Sailfin: a Jellyfin client written using Qt +Copyright (C) 2020 Chris Josten + +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 +*/ +import QtQuick 2.6 +import Sailfish.Silica 1.0 + +import "../.." + +ListItem { + id: songDelegateRoot + property var artists: [] + property real duration + property string name + property int indexNumber + + contentHeight: songName.height + songArtists.height + 2 * Theme.paddingMedium + width: parent.width + + TextMetrics { + id: indexMetrics + text: "99" + font.pixelSize: Theme.fontSizeLarge + } + + Label { + id: songIndex + anchors { + top: parent.top + topMargin: Theme.paddingMedium + left: parent.left + leftMargin: Theme.horizontalPageMargin + } + text: indexNumber + horizontalAlignment: Text.AlignRight + font.pixelSize: Theme.fontSizeExtraLarge + width: indexMetrics.width + } + + Label { + id: songName + anchors { + left: songIndex.right + leftMargin: Theme.paddingLarge + top: parent.top + topMargin: Theme.paddingMedium + right: duration.left + rightMargin: Theme.paddingLarge + } + text: name + font.pixelSize: Theme.fontSizeMedium + truncationMode: TruncationMode.Fade + } + Label { + id: songArtists + anchors { + top: songName.bottom + left: songIndex.right + leftMargin: Theme.paddingLarge + right: parent.right + rightMargin: Theme.horizontalPageMargin + } + text: artists.join(", ") + font.pixelSize: Theme.fontSizeSmall + truncationMode: TruncationMode.Fade + color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor + } + + Label { + id: duration + anchors { + right: parent.right + rightMargin: Theme.horizontalPageMargin + baseline: songName.baseline + } + width: contentWidth + text: Utils.ticksToText(songDelegateRoot.duration) + font.pixelSize: Theme.fontSizeSmall + color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor + } +} diff --git a/sailfish/qml/components/music/WideAlbumCover.qml b/sailfish/qml/components/music/WideAlbumCover.qml new file mode 100644 index 0000000..eaa1f52 --- /dev/null +++ b/sailfish/qml/components/music/WideAlbumCover.qml @@ -0,0 +1,60 @@ +/* +Sailfin: a Jellyfin client written using Qt +Copyright (C) 2020 Chris Josten + +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 +*/ + +import QtQuick 2.6 +import Sailfish.Silica 1.0 + +import ".." +import "../.." + +/** + * Album details for "wide" devices such as tablets and phones in landscape mode. + */ + +Column { + property ListView listview + property real releaseYear + property alias albumArt: albumArt.source + property string albumArtist + property real duration + property int songCount + property string name + + Item { width:1; height: Theme.paddingLarge } + + RemoteImage { + id: albumArt + width: parent.width + height: width + fillMode: Image.PreserveAspectFit + } + PageHeader { + id: albumHeader + leftMargin: 0 + rightMargin: 0 + title: name + //: Short description of the album: %1 -> album artist, %2 -> amount of songs, %3 -> duration, %4 -> release year + description: qsTr("%1\n%2 songs | %3 | %4") + .arg(albumArtist) + .arg(songCount) + .arg(Utils.ticksToText(duration)) + //: Unknown album release year + .arg(releaseYear >= 0 ? releaseYear : qsTr("Unknown year")) + } +} diff --git a/sailfish/qml/pages/MainPage.qml b/sailfish/qml/pages/MainPage.qml index c99ddcc..c44a634 100644 --- a/sailfish/qml/pages/MainPage.qml +++ b/sailfish/qml/pages/MainPage.qml @@ -192,6 +192,12 @@ Page { } } + Label { + text: Screen.sizeCategory + x: 100 + y: 100 + } + Component { id: carrouselView SilicaListView { diff --git a/sailfish/qml/pages/itemdetails/CollectionPage.qml b/sailfish/qml/pages/itemdetails/CollectionPage.qml index 685ce41..89707d9 100644 --- a/sailfish/qml/pages/itemdetails/CollectionPage.qml +++ b/sailfish/qml/pages/itemdetails/CollectionPage.qml @@ -40,7 +40,7 @@ BaseDetailPage { anchors.fill: parent model: collectionModel cellWidth: Constants.libraryDelegateWidth - cellHeight: Utils.usePortraitCover(itemData.CollectionType) ? Constants.libraryDelegatePosterHeight + cellHeight: Utils.usePortraitCover(itemData.collectionType) ? Constants.libraryDelegatePosterHeight : Constants.libraryDelegateHeight visible: itemData.status !== JellyfinItem.Error diff --git a/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml b/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml index 04675ef..2bf005a 100644 --- a/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml +++ b/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml @@ -1,14 +1,36 @@ +/* +Sailfin: a Jellyfin client written using Qt +Copyright (C) 2020 Chris Josten + +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 +*/ import QtQuick 2.6 import Sailfish.Silica 1.0 +import QtQuick.Layouts 1.1 import nl.netsoj.chris.Jellyfin 1.0 import "../../components" +import "../../components/music" import "../.." BaseDetailPage { + id: albumPageRoot readonly property int _songIndexWidth: 100 property string _albumArtistText: itemData.albumArtist + width: 800 * Theme.pixelRatio UserItemModel { id: collectionModel @@ -18,203 +40,51 @@ BaseDetailPage { parentId: itemData.jellyfinId onParentIdChanged: reload() } - - SilicaListView { - id: list + RowLayout { anchors.fill: parent - model: collectionModel - header: Item { - property string stateIfArt: "largeArt" - property alias albumArt: albumArt - id: listHeader - width: parent.width - //spacing: Theme.paddingLarge - state: albumArt.source != "" ? stateIfArt : "noArt" - MouseArea { - anchors.fill: parent - onClicked: { - if (listHeader.stateIfArt == "largeArt") { - listHeader.stateIfArt = "details" - } else { - listHeader.stateIfArt = "largeArt" - } - } - } - RemoteImage { - id: albumArt - anchors { - top: parent.top - right: parent.right - } - source: Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width}) - sourceSize.width: listHeader.width - sourceSize.height: listHeader.width - fillMode: Image.PreserveAspectFit - opacity: 1 - clip: true - } - PageHeader { - id: albumHeader - width: parent.width - Theme.horizontalPageMargin - height - title: itemData.name - description: qsTr("%1\n%2 songs | %3 | %4") - .arg(_albumArtistText) - .arg(itemData.childCount) - .arg(Utils.ticksToText(itemData.runTimeTicks)) - .arg(itemData.productionYear > 0 ? itemData.productionYear : qsTr("Unknown year")) - } - states: [ - State { - name: "largeArt" - PropertyChanges { - target: albumArt - width: parent.width - height: width - } - PropertyChanges { - target: listHeader - height: width - } - PropertyChanges { - target: albumHeader - opacity: 0 - } - PropertyChanges { - target: list - contentY: -list.width - } - AnchorChanges { - target: albumHeader - anchors.left: undefined - anchors.right: albumArt.left - } - }, - State { - name: "details" - PropertyChanges { - target: albumArt - width: height - height: albumHeader.height - } - PropertyChanges { - target: listHeader - height: albumHeader.height - } - PropertyChanges { - target: albumHeader - opacity: 1 - } - PropertyChanges { - target: list - contentY: -albumHeader.height - } - AnchorChanges { - target: albumHeader - anchors.left: undefined - anchors.right: albumArt.left - } - }, - State { - name: "noArt" - extend: "details" - PropertyChanges { - target: albumArt - opacity: 0 - } - PropertyChanges { - target: albumHeader - width: parent.width - Theme.horizontalPageMargin * 2 - } - AnchorChanges { - target: albumHeader - anchors.left: parent.left - anchors.right: parent.right - } - } - ] - transitions: Transition { - OpacityAnimator { target: albumHeader} - OpacityAnimator { target: albumArt} - NumberAnimation { - properties: "width,height,contentY" - //velocity: 1600 - duration: 300 - easing.type: Easing.OutQuad - } - AnchorAnimation {} - } + Item {height: 1; width: Theme.horizontalPageMargin; visible: wideAlbumCover.visible; } + Loader { + id: wideAlbumCover + visible: albumPageRoot.width / Theme.pixelRatio >= 800 + Layout.minimumWidth: 1000 / Theme.pixelRatio + Layout.fillHeight: true + source: visible + ? "../../components/music/WideAlbumCover.qml" : "" + onLoaded: bindAlbum(item) } - section { - property: "parentIndexNumber" - delegate: SectionHeader { - text: qsTr("Disc %1").arg(section) + Item {height: 1; width: Theme.horizontalPageMargin; visible: wideAlbumCover.visible; } + SilicaListView { + id: list + Layout.fillHeight: true + Layout.fillWidth: true + model: collectionModel + header: Loader { + width: parent.width + height: item ? item.height : 0 + source: albumPageRoot.width / Theme.pixelRatio < 800 + ? "../../components/music/NarrowAlbumCover.qml" : "" + onLoaded: bindAlbum(item) } + section { + property: "parentIndexNumber" + delegate: SectionHeader { + text: qsTr("Disc %1").arg(section) + } + } + delegate: SongDelegate { + name: model.name + artists: model.artists + duration: model.runTimeTicks + indexNumber: model.indexNumber + } + + VerticalScrollDecorator {} } - delegate: ListItem { - contentHeight: songName.height + songArtists.height + 2 * Theme.paddingMedium - width: parent.width + } - Label { - id: songIndex - anchors { - top: parent.top - topMargin: Theme.paddingMedium - left: parent.left - leftMargin: Theme.horizontalPageMargin - } - text: model.indexNumber - horizontalAlignment: Text.AlignRight - font.pixelSize: Theme.fontSizeExtraLarge - width: _songIndexWidth - } - - Label { - id: songName - anchors { - left: songIndex.right - leftMargin: Theme.paddingLarge - top: parent.top - topMargin: Theme.paddingMedium - right: duration.left - rightMargin: Theme.paddingLarge - } - text: model.name - font.pixelSize: Theme.fontSizeMedium - truncationMode: TruncationMode.Fade - } - Label { - id: songArtists - anchors { - top: songName.bottom - left: songIndex.right - leftMargin: Theme.paddingLarge - right: parent.right - rightMargin: Theme.horizontalPageMargin - } - text: model.artists.join(", ") - font.pixelSize: Theme.fontSizeSmall - truncationMode: TruncationMode.Fade - color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor - } - - Label { - id: duration - anchors { - right: parent.right - rightMargin: Theme.horizontalPageMargin - baseline: songName.baseline - } - width: contentWidth - text: Utils.ticksToText(model.runTimeTicks) - font.pixelSize: Theme.fontSizeSmall - color: highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor - } - } - - - - VerticalScrollDecorator {} + Label { + text: "%1; %2".arg(Theme.pixelRatio).arg(stateIfArt) } Connections { @@ -227,4 +97,14 @@ BaseDetailPage { } } } + + function bindAlbum(item) { + item.albumArt = Qt.binding(function(){ return Utils.itemImageUrl(ApiClient.baseUrl, itemData, "Primary", {"maxWidth": parent.width})}) + item.name = Qt.binding(function(){ return itemData.name}) + item.releaseYear = Qt.binding(function() { return itemData.productionYear}) + item.albumArtist = Qt.binding(function() { return _albumArtistText}) + item.duration = Qt.binding(function() { return itemData.runTimeTicks}) + item.songCount = Qt.binding(function() { return itemData.childCount}) + item.listview = Qt.binding(function() { return list}) + } }