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:
parent
bb2f6f3a3e
commit
79d378c9ed
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ Item {
|
|||
property real duration
|
||||
property int songCount
|
||||
property string name
|
||||
property alias blurhash : albumArt.blurhash
|
||||
|
||||
|
||||
property string stateIfArt: "largeArt"
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ Page {
|
|||
id: loggedInUser
|
||||
apiClient: ApiClient
|
||||
}
|
||||
Image {
|
||||
RemoteImage {
|
||||
id: userIcon
|
||||
width: height
|
||||
anchors {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]]; })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue