diff --git a/sandbox/controls/ListItems.qml b/sandbox/controls/ListItems.qml
index 0cbd609b..215ef0dd 100644
--- a/sandbox/controls/ListItems.qml
+++ b/sandbox/controls/ListItems.qml
@@ -445,4 +445,33 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
isAdmin: true
isUntrustworthy: true
}
+
+ StatusBaseText {
+ Layout.fillWidth: true
+ Layout.topMargin: 16
+ text: "Loading features:"
+ font.pixelSize: 17
+ }
+
+ StatusListItem {
+ title: "Nokia 3310"
+ subTitle: "Incoming device"
+ label: "loading: true"
+ icon.emoji: "😁"
+ icon.color: "hotpink"
+ icon.letterSize: 14
+ icon.isLetterIdenticon: true
+ loading: true
+ }
+
+ StatusListItem {
+ title: "Nokia 3310"
+ subTitle: "Device"
+ label: "loadingFailed: true"
+ icon.emoji: "😁"
+ icon.color: "hotpink"
+ icon.letterSize: 14
+ icon.isLetterIdenticon: true
+ loadingFailed: true
+ }
}
diff --git a/src/StatusQ/Animations/SkeletonAnimation.qml b/src/StatusQ/Animations/SkeletonAnimation.qml
new file mode 100644
index 00000000..f8c2264a
--- /dev/null
+++ b/src/StatusQ/Animations/SkeletonAnimation.qml
@@ -0,0 +1,30 @@
+import QtQuick 2.14
+import QtGraphicalEffects 1.14
+
+Item {
+ id: root
+
+ property Item mask
+ property color color: "#F6F8FA"
+
+ Rectangle {
+ id: gradient
+ anchors.fill: parent
+ visible: false
+
+ gradient: Gradient {
+ orientation: Gradient.Horizontal
+ SkeletonGradientStop { color: "transparent"; from: -3; }
+ SkeletonGradientStop { color: "transparent"; from: -2; }
+ SkeletonGradientStop { color: root.color; from: -1 ; }
+ SkeletonGradientStop { color: "transparent"; from: 0; }
+ SkeletonGradientStop { color: "transparent"; from: 1; }
+ }
+ }
+
+ OpacityMask {
+ anchors.fill: parent
+ source: gradient
+ maskSource: root.mask
+ }
+}
diff --git a/src/StatusQ/Animations/SkeletonGradientStop.qml b/src/StatusQ/Animations/SkeletonGradientStop.qml
new file mode 100644
index 00000000..a9119931
--- /dev/null
+++ b/src/StatusQ/Animations/SkeletonGradientStop.qml
@@ -0,0 +1,23 @@
+import QtQuick 2.14
+import QtGraphicalEffects 1.14
+
+/*
+ TODO: This component should be implemented as inline component of `SkeletonAnimation.qml`
+ when we use Qt > 5.15
+*/
+
+GradientStop {
+ id: root
+
+ property real from: 0
+
+ color: "transparent"
+
+ NumberAnimation on position {
+ easing.type: Easing.Linear
+ loops: Animation.Infinite
+ from: root.from
+ to: from + 4
+ duration: 2000
+ }
+}
diff --git a/src/StatusQ/Animations/qmldir b/src/StatusQ/Animations/qmldir
new file mode 100644
index 00000000..e31b6772
--- /dev/null
+++ b/src/StatusQ/Animations/qmldir
@@ -0,0 +1,4 @@
+module StatusQ.Animations
+
+SkeletonAnimation 0.1 SkeletonAnimation.qml
+SkeletonGradientStop 0.1 SkeletonGradientStop.qml
diff --git a/src/StatusQ/Components/StatusListItem.qml b/src/StatusQ/Components/StatusListItem.qml
index 7cb333a9..99e2ddd0 100644
--- a/src/StatusQ/Components/StatusListItem.qml
+++ b/src/StatusQ/Components/StatusListItem.qml
@@ -5,6 +5,10 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
+import StatusQ.Animations 0.1
+import QtGraphicalEffects 1.14
+
+import "private"
Rectangle {
id: statusListItem
@@ -27,6 +31,8 @@ Rectangle {
property Component bottomDelegate
property var tagsModel: []
property Component tagsDelegate
+ property bool loading: false
+ property bool loadingFailed: false
property StatusIconSettings icon: StatusIconSettings {
height: isLetterIdenticon ? 40 : 20
@@ -134,6 +140,7 @@ Rectangle {
anchors.leftMargin: statusListItem.leftPadding
anchors.top: parent.top
anchors.topMargin: 12
+ visible: !iconOrImageLoadingOverlay.visible
image: statusListItem.image
icon: statusListItem.icon
name: statusListItem.title
@@ -145,6 +152,20 @@ Rectangle {
ringSettings: statusListItem.ringSettings
}
+ Rectangle {
+ id: iconOrImageLoadingOverlay
+ visible: statusListItem.loading || statusListItem.loadingFailed
+ anchors.fill: iconOrImage
+ radius: width / 2
+ color: statusListItem.loadingFailed ? Theme.palette.dangerColor2 : Theme.palette.baseColor1
+
+ SkeletonAnimation {
+ anchors.fill: parent
+ mask: parent
+ visible: statusListItem.loading && !statusListItem.loadingFailed
+ }
+ }
+
Item {
id: statusListItemTitleArea
@@ -164,8 +185,29 @@ Rectangle {
height: childrenRect.height
+ Rectangle {
+ id: titleLoadingOverlay
+ visible: statusListItem.loading || statusListItem.loadingFailed
+ anchors {
+ left: statusListItemTitle.left
+ top: statusListItemTitle.top
+ bottom: statusListItemTitle.bottom
+ }
+
+ width: Math.max(95, statusListItemTitle.width)
+ radius: 4
+ color: statusListItem.loadingFailed ? Theme.palette.dangerColor2 : Theme.palette.baseColor1
+
+ SkeletonAnimation {
+ anchors.fill: parent
+ mask: parent
+ visible: statusListItem.loading && !statusListItem.loadingFailed
+ }
+ }
+
StatusBaseText {
id: statusListItemTitle
+ opacity: titleLoadingOverlay.visible ? 0 : 1
text: statusListItem.title
font.pixelSize: 15
height: visible ? contentHeight : 0
@@ -235,7 +277,10 @@ Rectangle {
width: parent.width
text: statusListItem.subTitle
font.pixelSize: 15
- color: !statusListItem.enabled || !statusListItem.tertiaryTitle ? Theme.palette.baseColor1 : Theme.palette.directColor1
+ color: statusListItem.loadingFailed ? Theme.palette.dangerColor1
+ : !statusListItem.enabled || !statusListItem.tertiaryTitle
+ ? Theme.palette.baseColor1
+ : Theme.palette.directColor1
height: visible ? contentHeight : 0
visible: !!statusListItem.subTitle
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
diff --git a/statusq.qrc b/statusq.qrc
index ce1e48cc..9a76dede 100644
--- a/statusq.qrc
+++ b/statusq.qrc
@@ -10502,5 +10502,8 @@
src/assets/twemoji/LICENSE
src/StatusQ/Controls/StatusTabBar.qml
src/StatusQ/Controls/StatusTagItem.qml
+ src/StatusQ/Animations/SkeletonAnimation.qml
+ src/StatusQ/Animations/SkeletonGradientStop.qml
+ src/StatusQ/Animations/qmldir