Files
alt-illogical-impulse/configs/quickshell/ii/qs/modules/common/widgets/NotificationGroup.qml
T

237 lines
9.4 KiB
QML

import qs.modules.common
import qs.services
import qs.modules.common.functions
import "./notification_utils.js" as NotificationUtils
import QtQuick
import QtQuick.Layouts
import Quickshell
/**
* A group of notifications from the same app.
* Similar to Android's notifications
*/
Item { // 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;
}
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: Appearance.colors.colSurfaceContainer
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
}
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() }
}
}
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
}
}
}
}
}
}