From 79d378c9edaed118f4b1a42f1b74a6f9fa70ac0f Mon Sep 17 00:00:00 2001 From: Chris Josten Date: Thu, 14 Jan 2021 20:35:24 +0100 Subject: [PATCH] Add BlurHash placeholders at several places * [ui] Added: Blurry previews of images before they are loaded Also fixes a bug where ApiModel would remove items that already started with a lowecase letter or didn't start with a letter at all. --- core/include/JellyfinQt/jellyfinitem.h | 6 ++ core/src/jellyfinapimodel.cpp | 1 + core/src/jsonhelper.cpp | 7 +- sailfish/CMakeLists.txt | 1 + .../qml/components/LibraryItemDelegate.qml | 1 + sailfish/qml/components/RemoteImage.qml | 98 ++++++++++++++++++- .../qml/components/music/NarrowAlbumCover.qml | 1 + .../qml/components/music/WideAlbumCover.qml | 1 + sailfish/qml/pages/MainPage.qml | 11 +-- sailfish/qml/pages/SettingsPage.qml | 2 +- .../qml/pages/itemdetails/CollectionPage.qml | 1 + .../qml/pages/itemdetails/MusicAlbumPage.qml | 1 + sailfish/qml/pages/itemdetails/PhotoPage.qml | 14 +-- 13 files changed, 120 insertions(+), 25 deletions(-) diff --git a/core/include/JellyfinQt/jellyfinitem.h b/core/include/JellyfinQt/jellyfinitem.h index e837dff..dd9f3ec 100644 --- a/core/include/JellyfinQt/jellyfinitem.h +++ b/core/include/JellyfinQt/jellyfinitem.h @@ -362,6 +362,8 @@ public: // a QHash at the moment. Q_PROPERTY(QJsonObject imageTags MEMBER m_imageTags NOTIFY imageTagsChanged) Q_PROPERTY(QJsonObject imageBlurHashes MEMBER m_imageBlurHashes NOTIFY imageBlurHashesChanged) + Q_PROPERTY(int width MEMBER m_width NOTIFY widthChanged) + Q_PROPERTY(int height MEMBER m_height NOTIFY heightChanged) QString jellyfinId() const { return m_id; } void setJellyfinId(QString newId); @@ -450,6 +452,8 @@ signals: void artistsChanged(const QStringList &newArtists); void imageTagsChanged(); void imageBlurHashesChanged(); + void widthChanged(int newWidth); + void heightChanged(int newHeight); public slots: void onUserDataChanged(const QString &itemId, QSharedPointer userData); @@ -505,6 +509,8 @@ protected: QStringList m_artists; QJsonObject m_imageTags; QJsonObject m_imageBlurHashes; + int m_width; + int m_height; template QQmlListProperty toReadOnlyQmlListProperty(QList &list) { diff --git a/core/src/jellyfinapimodel.cpp b/core/src/jellyfinapimodel.cpp index 3b8759b..468b860 100644 --- a/core/src/jellyfinapimodel.cpp +++ b/core/src/jellyfinapimodel.cpp @@ -183,6 +183,7 @@ QVariant ApiModel::data(const QModelIndex &index, int role) const { QJsonObject obj = m_array.at(index.row()).toObject(); const QString &key = m_roles[role]; + if (obj.contains(key)) { return obj[key].toVariant(); } diff --git a/core/src/jsonhelper.cpp b/core/src/jsonhelper.cpp index 7a6c7d8..e78a760 100644 --- a/core/src/jsonhelper.cpp +++ b/core/src/jsonhelper.cpp @@ -30,7 +30,12 @@ void convertToCamelCase(QJsonValueRef val) { QJsonValueRef ref = obj[key]; convertToCamelCase(ref); obj[convertToCamelCaseHelper(key)] = ref; - obj.remove(key); + if (key[0].isLower() || !key[0].isLetter()) { + obj[key] = ref; + } else { + obj[convertToCamelCaseHelper(key)] = ref; + obj.remove(key); + } } val = obj; break; diff --git a/sailfish/CMakeLists.txt b/sailfish/CMakeLists.txt index 54f306b..a098c3e 100644 --- a/sailfish/CMakeLists.txt +++ b/sailfish/CMakeLists.txt @@ -50,6 +50,7 @@ set(sailfin_QML_SOURCES qml/pages/itemdetails/EpisodePage.qml qml/pages/itemdetails/FilmPage.qml qml/pages/itemdetails/MusicAlbumPage.qml + qml/pages/itemdetails/PhotoPage.qml qml/pages/itemdetails/SeasonPage.qml qml/pages/itemdetails/SeriesPage.qml qml/pages/itemdetails/UnsupportedPage.qml diff --git a/sailfish/qml/components/LibraryItemDelegate.qml b/sailfish/qml/components/LibraryItemDelegate.qml index ddbb4f7..8ad2a25 100644 --- a/sailfish/qml/components/LibraryItemDelegate.qml +++ b/sailfish/qml/components/LibraryItemDelegate.qml @@ -28,6 +28,7 @@ BackgroundItem { id: root property alias poster: posterImage.source property alias title: titleText.text + property alias blurhash: posterImage.blurhash property bool landscape: false property real progress: 0.0 diff --git a/sailfish/qml/components/RemoteImage.qml b/sailfish/qml/components/RemoteImage.qml index 6eadcbb..8ee9d6e 100644 --- a/sailfish/qml/components/RemoteImage.qml +++ b/sailfish/qml/components/RemoteImage.qml @@ -20,17 +20,34 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA import QtQuick 2.6 import Sailfish.Silica 1.0 +import nl.netsoj.chris.blurhash 1.0 + /** * An image for "remote" images (loaded over e.g. http), with a spinner and a fallback image */ SilicaItem { + id: root property string fallbackImage property bool usingFallbackImage property color fallbackColor: Theme.highlightColor - property alias source: realImage.source + property var __parentPage : null + property bool alreadyLoaded: false + + onSourceChanged: alreadyLoaded = false + + /** + * BlurHash that is used as placeholder + */ + property string blurhash: "" + /** + * + */ + property real aspectRatio: 1.0 + property string source: "" property alias sourceSize: realImage.sourceSize - property alias fillMode: realImage.fillMode + property var fillMode: Image.Stretch + property alias status: realImage.status implicitHeight: realImage.implicitHeight implicitWidth: realImage.implicitWidth @@ -38,6 +55,14 @@ SilicaItem { id: realImage anchors.fill: parent asynchronous: true + fillMode: root.fillMode + opacity: 1 + source: alreadyLoaded || [PageStatus.Active, PageStatus.Deactivating].indexOf(__parentPage.status) >= 0 ? root.source : "" + onStatusChanged: { + if (status == Image.Ready) { + alreadyLoaded = true + } + } } Rectangle { @@ -47,7 +72,17 @@ SilicaItem { GradientStop { position: 0.0; color: fallbackColor; } GradientStop { position: 1.0; color: Theme.highlightDimmerFromColor(fallbackColor, Theme.colorScheme); } } - visible: realImage.status === Image.Error || realImage.status === Image.Null || realImage.status === Image.Loading + opacity: 0 + } + + Image { + id: blurhashImage + anchors.fill: parent + fillMode: root.fillMode + sourceSize.height: 32 + sourceSize.width: 32 * aspectRatio + source: "image://blurhash/" + encodeURIComponent(blurhash) + opacity: 0 } Rectangle { @@ -65,7 +100,60 @@ SilicaItem { HighlightImage { id: fallbackImageItem anchors.centerIn: parent - visible: realImage.status === Image.Error || realImage.status === Image.Null - source: fallbackImage ? fallbackImage : "image://theme/icon-m-question" + visible: realImage.status === Image.Error || (realImage.status === Image.Null && blurhash.length === 0) + source: fallbackImage ? fallbackImage : "image://theme/icon-m-question" } + + Text { + id: name + text: state + color: Qt.red + } + onStateChanged: console.log("New state: " + state + ", blurhash: '" + blurhash + "'") + states: [ + State { + name: "fallback" + when: (blurhash.length === 0) && (realImage.status === Image.Error || realImage.status === Image.Null || realImage.status === Image.Loading) + PropertyChanges { + target: fallbackBackground + opacity: 1 + } + }, + State { + name: "blurhash" + when: blurhash.length > 0 && (realImage.status === Image.Error || realImage.status === Image.Null || realImage.status === Image.Loading) + PropertyChanges { + target: blurhashImage + opacity: 1 + } + }, + State { + name: "loaded" + when: realImage.status === Image.Ready + PropertyChanges { + target: realImage + //opacity: 1 + } + } + ] + + transitions: [ + Transition { + from: "*" + to: "*" + FadeAnimation {} + } + ] + + Component.onCompleted: { + var item = parent; + while (item != null) { + if ("__silica_page" in item) { + __parentPage = item + console.log("Found parent " + item) + break; + } + item = item.parent + } + } } diff --git a/sailfish/qml/components/music/NarrowAlbumCover.qml b/sailfish/qml/components/music/NarrowAlbumCover.qml index 34ce510..17eb8a5 100644 --- a/sailfish/qml/components/music/NarrowAlbumCover.qml +++ b/sailfish/qml/components/music/NarrowAlbumCover.qml @@ -34,6 +34,7 @@ Item { property real duration property int songCount property string name + property alias blurhash : albumArt.blurhash property string stateIfArt: "largeArt" diff --git a/sailfish/qml/components/music/WideAlbumCover.qml b/sailfish/qml/components/music/WideAlbumCover.qml index eaa1f52..a9e7476 100644 --- a/sailfish/qml/components/music/WideAlbumCover.qml +++ b/sailfish/qml/components/music/WideAlbumCover.qml @@ -35,6 +35,7 @@ Column { property real duration property int songCount property string name + property alias blurhash : albumArt.blurhash Item { width:1; height: Theme.paddingLarge } diff --git a/sailfish/qml/pages/MainPage.qml b/sailfish/qml/pages/MainPage.qml index c44a634..bf4fcbb 100644 --- a/sailfish/qml/pages/MainPage.qml +++ b/sailfish/qml/pages/MainPage.qml @@ -29,6 +29,7 @@ import "../Utils.js" as Utils * Main page, which simply shows some content of every library, as well as next items. */ Page { + /// True if the models on this page already have been loaded and don't necessarily need a refresh property bool _modelsLoaded: false id: page @@ -192,12 +193,6 @@ Page { } } - Label { - text: Screen.sizeCategory - x: 100 - y: 100 - } - Component { id: carrouselView SilicaListView { @@ -227,9 +222,7 @@ Page { property string id: model.id title: model.name poster: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags["primary"], "Primary", {"maxHeight": height}) - /*model.imageTags["Primary"] ? ApiClient.baseUrl + "/Items/" + model.id - + "/Images/Primary?maxHeight=" + height + "&tag=" + model.imageTags["Primary"] - : ""*/ + blurhash: model.imageBlurHashes["primary"][model.imageTags["primary"]] landscape: !Utils.usePortraitCover(collectionType) progress: (typeof model.userData !== "undefined") ? model.userData.playedPercentage / 100 : 0.0 diff --git a/sailfish/qml/pages/SettingsPage.qml b/sailfish/qml/pages/SettingsPage.qml index 1c7a676..fcf7860 100644 --- a/sailfish/qml/pages/SettingsPage.qml +++ b/sailfish/qml/pages/SettingsPage.qml @@ -60,7 +60,7 @@ Page { id: loggedInUser apiClient: ApiClient } - Image { + RemoteImage { id: userIcon width: height anchors { diff --git a/sailfish/qml/pages/itemdetails/CollectionPage.qml b/sailfish/qml/pages/itemdetails/CollectionPage.qml index 89707d9..9801d93 100644 --- a/sailfish/qml/pages/itemdetails/CollectionPage.qml +++ b/sailfish/qml/pages/itemdetails/CollectionPage.qml @@ -61,6 +61,7 @@ BaseDetailPage { id: itemImage anchors.fill: parent source: Utils.itemModelImageUrl(ApiClient.baseUrl, model.id, model.imageTags.primary, "Primary", {"maxWidth": width}) + blurhash: model.imageBlurHashes.primary[model.imageTags.primary] fallbackColor: Utils.colorFromString(model.name) fillMode: Image.PreserveAspectCrop clip: true diff --git a/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml b/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml index 2bf005a..3f0d79e 100644 --- a/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml +++ b/sailfish/qml/pages/itemdetails/MusicAlbumPage.qml @@ -106,5 +106,6 @@ BaseDetailPage { item.duration = Qt.binding(function() { return itemData.runTimeTicks}) item.songCount = Qt.binding(function() { return itemData.childCount}) item.listview = Qt.binding(function() { return list}) + item.blurhash = Qt.binding(function() { return itemData.imageBlurHashes["Primary"][itemData.imageTags["Primary"]]; }) } } diff --git a/sailfish/qml/pages/itemdetails/PhotoPage.qml b/sailfish/qml/pages/itemdetails/PhotoPage.qml index 581ce7d..d7e0805 100644 --- a/sailfish/qml/pages/itemdetails/PhotoPage.qml +++ b/sailfish/qml/pages/itemdetails/PhotoPage.qml @@ -3,6 +3,8 @@ import Sailfish.Silica 1.0 import nl.netsoj.chris.Jellyfin 1.0 +import "../../components" + BaseDetailPage { id: pageRoot navigationStyle: PageNavigation.Vertical @@ -17,19 +19,13 @@ BaseDetailPage { titleColor: Theme.primaryColor } - Image { + RemoteImage { id: image source: ApiClient.downloadUrl(itemId) fillMode: Image.PreserveAspectFit anchors.fill: parent - opacity: status == Image.Ready ? 1.0 : 0.0 - Behavior on opacity { FadeAnimator {}} + blurhash: itemData.imageBlurHashes["Primary"][itemData.imageTags["Primary"]] + aspectRatio: itemData.width / itemData.height } - - BusyIndicator { - running: image.status == Image.Loading - size: BusyIndicatorSize.Large - anchors.centerIn: parent - } }