Files
z-bar-qt/Components/Notifs/NotifAvatar.qml
T
zach 11e7aac195
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 11s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Failing after 27s
Rust / build (pull_request) Successful in 54s
Python / verify (pull_request) Failing after 1m26s
Rust / fmt (pull_request) Successful in 46s
Rust / clippy (pull_request) Successful in 1m6s
feat: better notification storage and image caching + blobs
2026-11-02 19:00:43 +01:00

202 lines
4.6 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Shapes
import Quickshell
import ZShell
import M3Shapes
import ZShell.Config
import qs.Components
import qs.Helpers
import qs.Services
import Quickshell.Services.Notifications
Item {
id: root
required property var owner
required property string image
required property string icon
required property int urgency
required property string summary
required property color lowFillColour
property real progress: 0
property string shownImage
property string shownIcon
readonly property color fillColour: urgency === NotificationUrgency.Critical ? Colors.palette.m3primary : urgency === NotificationUrgency.Low ? lowFillColour : Colors.palette.m3secondaryContainer
readonly property color iconColour: urgency === NotificationUrgency.Critical ? Colors.palette.m3onPrimary : urgency === NotificationUrgency.Low ? Colors.palette.m3onSurfaceVariant : Colors.palette.m3onSecondaryContainer
implicitWidth: Config.notifs.sizes.image
implicitHeight: Config.notifs.sizes.image
// Keeping the previously shown url avoids replaying the fade when a row
// swaps between its raw source and the cached blob.
onImageChanged: {
if (image && !owner.isSameImage(shownImage))
shownImage = image;
}
onIconChanged: {
if (icon && !owner.isSameAppIcon(shownIcon))
shownIcon = icon;
}
Component.onCompleted: {
shownImage = image;
shownIcon = icon;
}
Component {
id: imageComp
FadeImage {
source: Qt.resolvedUrl(root.shownImage)
width: Config.notifs.sizes.image
height: Config.notifs.sizes.image
fadeInLargeAnim: Anim.DefaultEffects
}
}
Component {
id: appIconComp
FadeIcon {
size: Math.round(Config.notifs.sizes.image * 0.6)
}
}
Component {
id: materialIconComp
MaterialIcon {
animate: true
text: Icons.getNotifIcon(root.summary, root.urgency)
color: root.iconColour
fontStyle: Tokens.font.icon.medium
}
}
MaterialShape {
id: bgShape
anchors.fill: parent
shape: contentLoader.sourceComponent === materialIconComp && root.urgency === NotificationUrgency.Critical ? MaterialShape.SoftBurst : MaterialShape.Circle
color: Qt.alpha(root.fillColour, 1)
opacity: root.fillColour.a
Behavior on color {
CAnim {}
}
Behavior on opacity {
Anim {
type: Anim.SlowEffects
}
}
}
Item {
anchors.fill: parent
Loader {
id: contentLoader
anchors.centerIn: parent
anchors.verticalCenterOffset: sourceComponent === materialIconComp ? 1 : 0
asynchronous: true
sourceComponent: root.image ? imageComp : root.icon ? appIconComp : materialIconComp
}
}
Loader {
asynchronous: true
anchors.right: parent.right
anchors.bottom: parent.bottom
active: opacity > 0
opacity: root.icon && root.image ? 1 : 0
sourceComponent: CustomRect {
implicitWidth: Config.notifs.sizes.badge
implicitHeight: Config.notifs.sizes.badge
color: root.fillColour
radius: Tokens.rounding.full
FadeIcon {
anchors.centerIn: parent
size: Math.round(Config.notifs.sizes.badge * 0.6)
}
}
Behavior on opacity {
Anim {
type: root.icon && root.image ? Anim.DefaultEffects : Anim.FastEffects
}
}
}
Loader {
asynchronous: true
anchors.fill: parent
active: root.progress > 0
sourceComponent: Shape {
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: progressShape
property real sweep: root.progress * 360
property real spacing: root.urgency === NotificationUrgency.Critical ? Tokens.spacing.extraSmall : 0
capStyle: ShapePath.RoundCap
fillColor: "transparent"
strokeWidth: 2
strokeColor: Colors.palette.m3primary
PathPolyline {
function pointAt(angle: real): point {
const dist = bgShape.distanceAtAngle(angle) + progressShape.strokeWidth / 2 + progressShape.spacing;
const rad = angle * Math.PI / 180;
return Qt.point(root.width / 2 + dist * Math.sin(rad), root.height / 2 - dist * Math.cos(rad));
}
path: {
bgShape.morphProgress; // mapToItem-free dependency: re-run when the shape morphs
const points = [];
for (let angle = 0; angle < progressShape.sweep; angle += 3)
points.push(pointAt(angle));
points.push(pointAt(progressShape.sweep));
return points;
}
}
Behavior on sweep {
Anim {
easing: Tokens.anim.emphasizedDecel
}
}
Behavior on spacing {
Anim {}
}
Behavior on strokeColor {
CAnim {}
}
}
}
}
component FadeIcon: FadeImage {
id: fadeIcon
required property int size
width: size
height: size
source: Quickshell.iconPath(root.shownIcon)
fillMode: Image.PreserveAspectFit
fadeInLargeAnim: Anim.DefaultEffects
}
}