1
0
Fork 0
mirror of https://github.com/HenkKalkwater/harbour-sailfin.git synced 2024-11-25 02:15:17 +00:00

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.
This commit is contained in:
Chris Josten 2021-01-14 20:35:24 +01:00
parent bb2f6f3a3e
commit 79d378c9ed
13 changed files with 120 additions and 25 deletions

View file

@ -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> userData);
@ -505,6 +509,8 @@ protected:
QStringList m_artists;
QJsonObject m_imageTags;
QJsonObject m_imageBlurHashes;
int m_width;
int m_height;
template<typename T>
QQmlListProperty<T> toReadOnlyQmlListProperty(QList<T *> &list) {

View file

@ -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();
}

View file

@ -29,9 +29,14 @@ void convertToCamelCase(QJsonValueRef val) {
for(const QString &key: obj.keys()) {
QJsonValueRef ref = obj[key];
convertToCamelCase(ref);
obj[convertToCamelCaseHelper(key)] = ref;
if (key[0].isLower() || !key[0].isLetter()) {
obj[key] = ref;
} else {
obj[convertToCamelCaseHelper(key)] = ref;
obj.remove(key);
}
}
val = obj;
break;
}

View file

@ -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

View file

@ -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

View file

@ -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
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
}
}
}

View file

@ -34,6 +34,7 @@ Item {
property real duration
property int songCount
property string name
property alias blurhash : albumArt.blurhash
property string stateIfArt: "largeArt"

View file

@ -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 }

View file

@ -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

View file

@ -60,7 +60,7 @@ Page {
id: loggedInUser
apiClient: ApiClient
}
Image {
RemoteImage {
id: userIcon
width: height
anchors {

View file

@ -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

View file

@ -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"]]; })
}
}

View file

@ -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 {}}
}
BusyIndicator {
running: image.status == Image.Loading
size: BusyIndicatorSize.Large
anchors.centerIn: parent
blurhash: itemData.imageBlurHashes["Primary"][itemData.imageTags["Primary"]]
aspectRatio: itemData.width / itemData.height
}
}