mirror of
https://github.com/end-4/dots-hyprland.git
synced 2026-06-05 23:09:26 -05:00
257 lines
10 KiB
QML
257 lines
10 KiB
QML
import qs
|
|
import qs.services
|
|
import qs.modules.common
|
|
import qs.modules.common.functions
|
|
import "./notification_utils.js" as NotificationUtils
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import Quickshell.Services.Notifications
|
|
|
|
/**
|
|
* A group of notifications from the same app.
|
|
* Similar to Android's notifications
|
|
*/
|
|
MouseArea { // Notification group area
|
|
id: root
|
|
property var notificationGroup
|
|
property var notifications: notificationGroup?.notifications ?? []
|
|
property int notificationCount: notifications.length
|
|
property bool multipleNotifications: notificationCount > 1
|
|
property bool expanded: false
|
|
property bool popup: false
|
|
property real padding: 10
|
|
implicitHeight: background.implicitHeight
|
|
|
|
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
|
|
property var parentDragDistance: qmlParent.dragDistance
|
|
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
|
|
|
|
function destroyWithAnimation() {
|
|
root.qmlParent.resetDrag()
|
|
background.anchors.leftMargin = background.anchors.leftMargin; // Break binding
|
|
destroyAnimation.running = true;
|
|
}
|
|
|
|
hoverEnabled: true
|
|
onContainsMouseChanged: {
|
|
if (!root.popup) return;
|
|
if (root.containsMouse) root.notifications.forEach(notif => {
|
|
Notifications.cancelTimeout(notif.notificationId);
|
|
});
|
|
else root.notifications.forEach(notif => {
|
|
Notifications.timeoutNotification(notif.notificationId);
|
|
});
|
|
}
|
|
|
|
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: () => {
|
|
root.notifications.forEach((notif) => {
|
|
Qt.callLater(() => {
|
|
Notifications.discardNotification(notif.notificationId);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
function toggleExpanded() {
|
|
if (expanded) implicitHeightAnim.enabled = true;
|
|
else implicitHeightAnim.enabled = false;
|
|
root.expanded = !root.expanded;
|
|
}
|
|
|
|
DragManager { // Drag manager
|
|
id: dragManager
|
|
anchors.fill: parent
|
|
interactive: !expanded
|
|
automaticallyReset: false
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
|
|
|
onClicked: (mouse) => {
|
|
if (mouse.button === Qt.RightButton)
|
|
root.toggleExpanded();
|
|
else if (mouse.button === Qt.MiddleButton)
|
|
root.destroyWithAnimation();
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
StyledRectangularShadow {
|
|
target: background
|
|
visible: popup
|
|
}
|
|
Rectangle { // Background of the notification
|
|
id: background
|
|
anchors.left: parent.left
|
|
width: parent.width
|
|
color: popup ? ColorUtils.applyAlpha(Appearance.colors.colLayer2, 1 - Appearance.backgroundTransparency) : Appearance.colors.colLayer2
|
|
radius: Appearance.rounding.normal
|
|
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
|
|
}
|
|
}
|
|
|
|
clip: true
|
|
implicitHeight: expanded ?
|
|
row.implicitHeight + padding * 2 :
|
|
Math.min(80, row.implicitHeight + padding * 2)
|
|
|
|
Behavior on implicitHeight {
|
|
id: implicitHeightAnim
|
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
|
}
|
|
|
|
RowLayout { // Left column for icon, right column for content
|
|
id: row
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.margins: root.padding
|
|
spacing: 10
|
|
|
|
NotificationAppIcon { // Icons
|
|
Layout.alignment: Qt.AlignTop
|
|
Layout.fillWidth: false
|
|
image: root?.multipleNotifications ? "" : notificationGroup?.notifications[0]?.image ?? ""
|
|
appIcon: notificationGroup?.appIcon
|
|
summary: notificationGroup?.notifications[root.notificationCount - 1]?.summary
|
|
urgency: root.notifications.some(n => n.urgency === NotificationUrgency.Critical.toString()) ?
|
|
NotificationUrgency.Critical : NotificationUrgency.Normal
|
|
}
|
|
|
|
ColumnLayout { // Content
|
|
Layout.fillWidth: true
|
|
spacing: expanded ? (root.multipleNotifications ?
|
|
(notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 :
|
|
5 : 0) : 0
|
|
// spacing: 00
|
|
Behavior on spacing {
|
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
|
}
|
|
|
|
Item { // App name (or summary when there's only 1 notif) and time
|
|
id: topRow
|
|
// spacing: 0
|
|
Layout.fillWidth: true
|
|
property real fontSize: Appearance.font.pixelSize.smaller
|
|
property bool showAppName: root.multipleNotifications
|
|
implicitHeight: Math.max(topTextRow.implicitHeight, expandButton.implicitHeight)
|
|
|
|
RowLayout {
|
|
id: topTextRow
|
|
anchors.left: parent.left
|
|
anchors.right: expandButton.left
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 5
|
|
StyledText {
|
|
id: appName
|
|
elide: Text.ElideRight
|
|
Layout.fillWidth: true
|
|
text: (topRow.showAppName ?
|
|
notificationGroup?.appName :
|
|
notificationGroup?.notifications[0]?.summary) || ""
|
|
font.pixelSize: topRow.showAppName ?
|
|
topRow.fontSize :
|
|
Appearance.font.pixelSize.small
|
|
color: topRow.showAppName ?
|
|
Appearance.colors.colSubtext :
|
|
Appearance.colors.colOnLayer2
|
|
}
|
|
StyledText {
|
|
id: timeText
|
|
// Layout.fillWidth: true
|
|
Layout.rightMargin: 10
|
|
horizontalAlignment: Text.AlignLeft
|
|
text: NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time)
|
|
font.pixelSize: topRow.fontSize
|
|
color: Appearance.colors.colSubtext
|
|
}
|
|
}
|
|
NotificationGroupExpandButton {
|
|
id: expandButton
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
count: root.notificationCount
|
|
expanded: root.expanded
|
|
fontSize: topRow.fontSize
|
|
onClicked: { root.toggleExpanded() }
|
|
altAction: () => { root.toggleExpanded() }
|
|
|
|
StyledToolTip {
|
|
text: Translation.tr("Tip: right-clicking a group\nalso expands it")
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledListView { // Notification body (expanded)
|
|
id: notificationsColumn
|
|
implicitHeight: contentHeight
|
|
Layout.fillWidth: true
|
|
spacing: expanded ? 5 : 3
|
|
// clip: true
|
|
interactive: false
|
|
Behavior on spacing {
|
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
|
}
|
|
model: ScriptModel {
|
|
values: root.expanded ? root.notifications.slice().reverse() :
|
|
root.notifications.slice().reverse().slice(0, 2)
|
|
}
|
|
delegate: NotificationItem {
|
|
required property int index
|
|
required property var modelData
|
|
notificationObject: modelData
|
|
expanded: root.expanded
|
|
onlyNotification: (root.notificationCount === 1)
|
|
opacity: (!root.expanded && index == 1 && root.notificationCount > 2) ? 0.5 : 1
|
|
visible: root.expanded || (index < 2)
|
|
anchors.left: parent?.left
|
|
anchors.right: parent?.right
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|