forked from Shinonome/dots-hyprland
notifications: groups
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
import "root:/modules/common"
|
||||
import "root:/services"
|
||||
import "root:/modules/common/functions/string_utils.js" as StringUtils
|
||||
import "root:/modules/common/functions/color_utils.js" as ColorUtils
|
||||
import "./notification_utils.js" as NotificationUtils
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.Notifications
|
||||
|
||||
Item { // Notification item area
|
||||
id: root
|
||||
property var notificationObject
|
||||
property bool expanded: false
|
||||
property bool onlyNotification: false
|
||||
property real fontSize: Appearance.font.pixelSize.small
|
||||
property real padding: 8
|
||||
|
||||
property real dragConfirmThreshold: 70 // Drag further to discard notification
|
||||
property real dismissOvershoot: 20 // Account for gaps and bouncy animations
|
||||
property var qmlParent: root?.parent?.parent // There's something between this and the parent ListView
|
||||
property var parentDragIndex: qmlParent?.dragIndex ?? -1
|
||||
property var parentDragDistance: qmlParent?.dragDistance ?? 0
|
||||
property var dragIndexDiff: Math.abs(parentDragIndex - index)
|
||||
property real xOffset: dragIndexDiff == 0 ? Math.max(0, parentDragDistance) :
|
||||
parentDragDistance > dragConfirmThreshold ? 0 :
|
||||
dragIndexDiff == 1 ? Math.max(0, parentDragDistance * 0.3) :
|
||||
dragIndexDiff == 2 ? Math.max(0, parentDragDistance * 0.1) : 0
|
||||
|
||||
implicitHeight: background.implicitHeight
|
||||
|
||||
function destroyWithAnimation() {
|
||||
root.qmlParent.resetDrag()
|
||||
background.anchors.leftMargin = background.anchors.leftMargin; // Break binding
|
||||
destroyAnimation.running = true;
|
||||
}
|
||||
|
||||
SequentialAnimation { // Drag finish animation
|
||||
id: destroyAnimation
|
||||
running: false
|
||||
|
||||
NumberAnimation {
|
||||
target: background.anchors
|
||||
property: "leftMargin"
|
||||
to: root.width + root.dismissOvershoot
|
||||
duration: Appearance.animation.elementMove.duration
|
||||
easing.type: Appearance.animation.elementMove.type
|
||||
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
|
||||
}
|
||||
onFinished: () => {
|
||||
Notifications.discardNotification(notificationObject.id);
|
||||
}
|
||||
}
|
||||
|
||||
DragManager { // Drag manager
|
||||
id: dragManager
|
||||
anchors.fill: parent
|
||||
interactive: expanded
|
||||
automaticallyReset: false
|
||||
acceptedButtons: Qt.LeftButton
|
||||
|
||||
onPressAndHold: (mouse) => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`)
|
||||
notificationSummaryText.text = String.format(qsTr("{0} (copied)"), notificationObject.summary)
|
||||
}
|
||||
}
|
||||
onDraggingChanged: () => {
|
||||
if (dragging) {
|
||||
root.qmlParent.dragIndex = root.index ?? root.parent.children.indexOf(root);
|
||||
}
|
||||
}
|
||||
|
||||
onDragDiffXChanged: () => {
|
||||
root.qmlParent.dragDistance = dragDiffX;
|
||||
}
|
||||
|
||||
onDragReleased: (diffX, diffY) => {
|
||||
if (diffX > root.dragConfirmThreshold)
|
||||
root.destroyWithAnimation();
|
||||
else
|
||||
dragManager.resetDrag();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // Background of notification item
|
||||
id: background
|
||||
width: parent.width
|
||||
anchors.left: parent.left
|
||||
radius: Appearance.rounding.small
|
||||
anchors.leftMargin: root.xOffset
|
||||
|
||||
Behavior on anchors.leftMargin {
|
||||
enabled: !dragManager.dragging
|
||||
NumberAnimation {
|
||||
duration: Appearance.animation.elementMove.duration
|
||||
easing.type: Appearance.animation.elementMove.type
|
||||
easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial
|
||||
}
|
||||
}
|
||||
|
||||
color: expanded ?
|
||||
(notificationObject.urgency == NotificationUrgency.Critical) ?
|
||||
ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) :
|
||||
(Appearance.m3colors.m3surfaceContainerHigh) :
|
||||
ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHighest)
|
||||
|
||||
implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight
|
||||
Behavior on implicitHeight {
|
||||
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
ColumnLayout { // Content column
|
||||
id: contentColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: expanded ? root.padding : 0
|
||||
spacing: 3
|
||||
|
||||
Behavior on anchors.margins {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
RowLayout { // Summary row
|
||||
id: summaryRow
|
||||
visible: !root.onlyNotification || !root.expanded
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: summaryText.implicitHeight
|
||||
// Layout.fillWidth: true
|
||||
StyledText {
|
||||
id: summaryText
|
||||
visible: !root.onlyNotification
|
||||
font.pixelSize: root.fontSize
|
||||
color: Appearance.colors.colOnLayer2
|
||||
elide: Text.ElideRight
|
||||
text: root.notificationObject.summary || ""
|
||||
}
|
||||
StyledText {
|
||||
opacity: !root.expanded ? 1 : 0
|
||||
visible: opacity > 0
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: root.fontSize
|
||||
color: Appearance.colors.colSubtext
|
||||
elide: Text.ElideRight
|
||||
textFormat: Text.StyledText
|
||||
text: notificationObject.body.replace(/<img/g, "\n <img").split("\n")[0]
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout { // Expanded content
|
||||
Layout.fillWidth: true
|
||||
opacity: root.expanded ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
StyledText { // Notification body (expanded)
|
||||
id: notificationBodyText
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: root.fontSize
|
||||
color: Appearance.colors.colSubtext
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
textFormat: Text.RichText
|
||||
text: `<style>img{max-width:${notificationBodyText.width}px;}</style>` +
|
||||
`${notificationObject.body.replace(/\n/g, "<br/>")}`
|
||||
}
|
||||
|
||||
Flickable { // Notification actions
|
||||
id: actionsFlickable
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: actionRowLayout.implicitHeight
|
||||
contentWidth: actionRowLayout.implicitWidth
|
||||
clip: true
|
||||
|
||||
Behavior on opacity {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on height {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
Behavior on implicitHeight {
|
||||
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: actionRowLayout
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
Repeater {
|
||||
id: actionRepeater
|
||||
model: notificationObject.actions
|
||||
NotificationActionButton {
|
||||
Layout.fillWidth: true
|
||||
buttonText: modelData.text
|
||||
urgency: notificationObject.urgency
|
||||
onClicked: {
|
||||
Notifications.attemptInvokeAction(notificationObject.id, modelData.identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NotificationActionButton {
|
||||
Layout.fillWidth: true
|
||||
urgency: notificationObject.urgency
|
||||
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
|
||||
(contentItem.implicitWidth + leftPadding + rightPadding)
|
||||
|
||||
onClicked: {
|
||||
Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`)
|
||||
copyIcon.text = "inventory"
|
||||
copyIconTimer.restart()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: copyIconTimer
|
||||
interval: 1500
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
copyIcon.text = "content_copy"
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: MaterialSymbol {
|
||||
id: copyIcon
|
||||
iconSize: Appearance.font.pixelSize.large
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: (notificationObject.urgency == NotificationUrgency.Critical) ?
|
||||
Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
|
||||
text: "content_copy"
|
||||
}
|
||||
}
|
||||
|
||||
NotificationActionButton {
|
||||
Layout.fillWidth: true
|
||||
buttonText: qsTr("Close")
|
||||
urgency: notificationObject.urgency
|
||||
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
|
||||
(contentItem.implicitWidth + leftPadding + rightPadding)
|
||||
|
||||
onClicked: {
|
||||
root.destroyWithAnimation()
|
||||
}
|
||||
|
||||
contentItem: MaterialSymbol {
|
||||
iconSize: Appearance.font.pixelSize.large
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
color: (notificationObject.urgency == NotificationUrgency.Critical) ?
|
||||
Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
|
||||
text: "close"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user