1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 14:05:38 -05:00

notifications: try to prevent zombies better, markdown, re-org

This commit is contained in:
bbedward
2025-07-26 16:08:58 -04:00
parent 484a947127
commit 01a94a17de
22 changed files with 684 additions and 350 deletions

View File

@@ -0,0 +1,656 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: root
property var notificationGroup
property bool expanded: NotificationService.expandedGroups[notificationGroup?.key] || false
property bool descriptionExpanded: NotificationService.expandedMessages[notificationGroup?.latestNotification?.notification?.id + "_desc"] || false
property bool userInitiatedExpansion: false
width: parent ? parent.width : 400
height: {
if (expanded) {
return expandedContent.height + 28;
}
const baseHeight = 116;
if (descriptionExpanded) {
return baseHeight + descriptionText.contentHeight - (descriptionText.font.pixelSize * 1.2 * 2);
}
return baseHeight;
}
radius: Theme.cornerRadiusLarge
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: notificationGroup?.latestNotification?.urgency === 2 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
border.width: notificationGroup?.latestNotification?.urgency === 2 ? 2 : 1
clip: true
Rectangle {
anchors.fill: parent
radius: parent.radius
visible: notificationGroup?.latestNotification?.urgency === 2
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0.0
color: Theme.primary
}
GradientStop {
position: 0.02
color: Theme.primary
}
GradientStop {
position: 0.021
color: "transparent"
}
}
opacity: 1.0
}
Item {
id: collapsedContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 12
anchors.leftMargin: 16
anchors.rightMargin: 56
height: 92
visible: !expanded
Rectangle {
id: iconContainer
readonly property bool hasNotificationImage: notificationGroup?.latestNotification?.image && notificationGroup.latestNotification.image !== ""
width: 55
height: 55
radius: 27.5
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
border.color: "transparent"
border.width: 0
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: 18
IconImage {
anchors.fill: parent
anchors.margins: 2
source: {
if (parent.hasNotificationImage)
return notificationGroup.latestNotification.cleanImage;
if (notificationGroup?.latestNotification?.appIcon) {
const appIcon = notificationGroup.latestNotification.appIcon;
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
return appIcon;
return Quickshell.iconPath(appIcon, "");
}
return "";
}
visible: status === Image.Ready
}
Text {
anchors.centerIn: parent
visible: !parent.hasNotificationImage && (!notificationGroup?.latestNotification?.appIcon || notificationGroup.latestNotification.appIcon === "")
text: {
const appName = notificationGroup?.appName || "?";
return appName.charAt(0).toUpperCase();
}
font.pixelSize: 20
font.weight: Font.Bold
color: Theme.primaryText
}
Rectangle {
width: 18
height: 18
radius: 9
color: Theme.primary
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: -2
anchors.rightMargin: -2
visible: (notificationGroup?.count || 0) > 1
Text {
anchors.centerIn: parent
text: (notificationGroup?.count || 0) > 99 ? "99+" : (notificationGroup?.count || 0).toString()
color: Theme.primaryText
font.pixelSize: 9
font.weight: Font.Bold
}
}
}
Rectangle {
id: textContainer
anchors.left: iconContainer.right
anchors.leftMargin: 12
anchors.right: parent.right
anchors.rightMargin: 0
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
color: "transparent"
Item {
width: parent.width
height: parent.height
anchors.top: parent.top
anchors.topMargin: -4
Column {
width: parent.width
spacing: 2
Text {
width: parent.width
text: {
const timeStr = notificationGroup?.latestNotification?.timeStr || "";
if (timeStr.length > 0)
return (notificationGroup?.appName || "") + " • " + timeStr;
else
return notificationGroup?.appName || "";
}
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
}
Text {
text: notificationGroup?.latestNotification?.summary || ""
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
width: parent.width
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
Text {
id: descriptionText
property string fullText: notificationGroup?.latestNotification?.htmlBody || ""
property bool hasMoreText: truncated
text: fullText
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
width: parent.width
elide: Text.ElideRight
maximumLineCount: descriptionExpanded ? -1 : 2
wrapMode: Text.WordWrap
visible: text.length > 0
linkColor: Theme.primary
onLinkActivated: Qt.openUrlExternally(link)
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor :
(parent.hasMoreText || descriptionExpanded) ? Qt.PointingHandCursor :
Qt.ArrowCursor
onClicked: mouse => {
if (!parent.hoveredLink && (parent.hasMoreText || descriptionExpanded)) {
const messageId = notificationGroup?.latestNotification?.notification?.id + "_desc";
NotificationService.toggleMessageExpansion(messageId);
}
}
propagateComposedEvents: true
onPressed: mouse => {
if (parent.hoveredLink) {
mouse.accepted = false;
}
}
onReleased: mouse => {
if (parent.hoveredLink) {
mouse.accepted = false;
}
}
}
}
}
}
}
}
Column {
id: expandedContent
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 14
anchors.bottomMargin: 14
anchors.leftMargin: Theme.spacingL
anchors.rightMargin: Theme.spacingL
spacing: -1
visible: expanded
Item {
width: parent.width
height: 40
Row {
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: 56
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Text {
text: notificationGroup?.appName || ""
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
maximumLineCount: 1
}
Rectangle {
width: 18
height: 18
radius: 9
color: Theme.primary
visible: (notificationGroup?.count || 0) > 1
anchors.verticalCenter: parent.verticalCenter
Text {
anchors.centerIn: parent
text: (notificationGroup?.count || 0) > 99 ? "99+" : (notificationGroup?.count || 0).toString()
color: Theme.primaryText
font.pixelSize: 9
font.weight: Font.Bold
}
}
}
}
Column {
width: parent.width
spacing: 16
Repeater {
model: notificationGroup?.notifications?.slice(0, 10) || []
delegate: Rectangle {
required property var modelData
readonly property bool messageExpanded: NotificationService.expandedMessages[modelData?.notification?.id] || false
width: parent.width
height: {
const baseHeight = 120;
if (messageExpanded) {
const twoLineHeight = bodyText.font.pixelSize * 1.2 * 2;
if (bodyText.implicitHeight > twoLineHeight + 2) {
const extraHeight = bodyText.implicitHeight - twoLineHeight;
return baseHeight + extraHeight;
}
}
return baseHeight;
}
radius: Theme.cornerRadiusLarge
color: "transparent"
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
border.width: 1
Behavior on height {
enabled: false
}
Item {
anchors.fill: parent
anchors.margins: 12
anchors.bottomMargin: 8
Rectangle {
id: messageIcon
readonly property bool hasNotificationImage: modelData?.image && modelData.image !== ""
width: 32
height: 32
radius: 16
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: 32
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
border.width: 1
IconImage {
anchors.fill: parent
anchors.margins: 1
source: {
if (parent.hasNotificationImage)
return modelData.cleanImage;
if (modelData?.appIcon) {
const appIcon = modelData.appIcon;
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
return appIcon;
return Quickshell.iconPath(appIcon, "");
}
return "";
}
visible: status === Image.Ready
}
Text {
anchors.centerIn: parent
visible: !parent.hasNotificationImage && (!modelData?.appIcon || modelData.appIcon === "")
text: {
const appName = modelData?.appName || "?";
return appName.charAt(0).toUpperCase();
}
font.pixelSize: 12
font.weight: Font.Bold
color: Theme.primaryText
}
}
Item {
anchors.left: messageIcon.right
anchors.leftMargin: 12
anchors.right: parent.right
anchors.rightMargin: 12
anchors.top: parent.top
anchors.bottom: parent.bottom
Column {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: buttonArea.top
anchors.bottomMargin: 4
spacing: 2
Text {
width: parent.width
text: modelData?.timeStr || ""
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
Text {
width: parent.width
text: modelData?.summary || ""
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
Text {
id: bodyText
property bool hasMoreText: truncated
text: modelData?.htmlBody || ""
color: Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
width: parent.width
elide: messageExpanded ? Text.ElideNone : Text.ElideRight
maximumLineCount: messageExpanded ? -1 : 2
wrapMode: Text.WordWrap
visible: text.length > 0
linkColor: Theme.primary
onLinkActivated: Qt.openUrlExternally(link)
MouseArea {
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor :
(bodyText.hasMoreText || messageExpanded) ? Qt.PointingHandCursor :
Qt.ArrowCursor
onClicked: mouse => {
if (!parent.hoveredLink && (bodyText.hasMoreText || messageExpanded)) {
NotificationService.toggleMessageExpansion(modelData?.notification?.id || "");
}
}
propagateComposedEvents: true
onPressed: mouse => {
if (parent.hoveredLink) {
mouse.accepted = false;
}
}
onReleased: mouse => {
if (parent.hoveredLink) {
mouse.accepted = false;
}
}
}
}
}
Item {
id: buttonArea
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 30
Row {
anchors.right: parent.right
anchors.bottom: parent.bottom
spacing: 8
Repeater {
model: modelData?.actions || []
Rectangle {
property bool isHovered: false
width: Math.max(actionText.implicitWidth + 12, 50)
height: 24
radius: 4
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
Text {
id: actionText
text: modelData.text || ""
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
anchors.centerIn: parent
elide: Text.ElideRight
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: parent.isHovered = true
onExited: parent.isHovered = false
onClicked: {
if (modelData && modelData.invoke) {
modelData.invoke();
}
}
}
}
}
Rectangle {
property bool isHovered: false
width: Math.max(dismissText.implicitWidth + 12, 50)
height: 24
radius: 4
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
Text {
id: dismissText
text: "Dismiss"
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: parent.isHovered = true
onExited: parent.isHovered = false
onClicked: NotificationService.dismissNotification(modelData)
}
}
}
}
}
}
}
}
}
}
Row {
visible: !expanded
anchors.right: dismissButton.left
anchors.rightMargin: 8
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
spacing: 8
Repeater {
model: notificationGroup?.latestNotification?.actions || []
Rectangle {
property bool isHovered: false
width: Math.max(actionText.implicitWidth + 12, 50)
height: 24
radius: 4
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
Text {
id: actionText
text: modelData.text || ""
color: parent.isHovered ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
anchors.centerIn: parent
elide: Text.ElideRight
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: parent.isHovered = true
onExited: parent.isHovered = false
onClicked: {
if (modelData && modelData.invoke) {
modelData.invoke();
}
}
}
}
}
}
Rectangle {
id: dismissButton
property bool isHovered: false
visible: !expanded
anchors.right: parent.right
anchors.rightMargin: 16
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
width: dismissText.width + 16
height: dismissText.height + 8
radius: 6
color: isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1) : "transparent"
Text {
id: dismissText
text: "Dismiss"
color: dismissButton.isHovered ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: dismissButton.isHovered = true
onExited: dismissButton.isHovered = false
onClicked: NotificationService.dismissGroup(notificationGroup?.key || "")
}
}
MouseArea {
anchors.fill: parent
visible: !expanded && (notificationGroup?.count || 0) > 1 && !descriptionExpanded
onClicked: {
root.userInitiatedExpansion = true
NotificationService.toggleGroupExpansion(notificationGroup?.key || "")
}
z: -1
}
Item {
id: fixedControls
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: 12
anchors.rightMargin: 16
width: 60
height: 28
DankActionButton {
anchors.left: parent.left
anchors.top: parent.top
visible: (notificationGroup?.count || 0) > 1
iconName: expanded ? "expand_less" : "expand_more"
iconSize: 18
buttonSize: 28
onClicked: {
root.userInitiatedExpansion = true
NotificationService.toggleGroupExpansion(notificationGroup?.key || "")
}
}
DankActionButton {
anchors.right: parent.right
anchors.top: parent.top
iconName: "close"
iconSize: 18
buttonSize: 28
onClicked: NotificationService.dismissGroup(notificationGroup?.key || "")
}
}
Behavior on height {
enabled: root.userInitiatedExpansion
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
onFinished: root.userInitiatedExpansion = false
}
}
}

View File

@@ -0,0 +1,141 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common
import qs.Services
import qs.Widgets
PanelWindow {
id: root
property bool notificationHistoryVisible: false
visible: notificationHistoryVisible
onNotificationHistoryVisibleChanged: {
NotificationService.disablePopups(notificationHistoryVisible);
}
implicitWidth: 400
implicitHeight: Math.min(Screen.height * 0.8, 400)
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent"
anchors {
top: true
left: true
right: true
bottom: true
}
MouseArea {
anchors.fill: parent
onClicked: {
notificationHistoryVisible = false;
}
}
Rectangle {
id: mainRect
function calculateHeight() {
let baseHeight = Theme.spacingL * 2;
baseHeight += notificationHeader.height;
baseHeight += Theme.spacingM;
let listHeight = notificationList.listContentHeight;
if (NotificationService.groupedNotifications.length === 0)
listHeight = 200;
baseHeight += Math.min(listHeight, 600);
return Math.max(300, baseHeight);
}
width: 400
height: calculateHeight()
x: Screen.width - width - Theme.spacingL
y: Theme.barHeight + Theme.spacingXS
color: Theme.popupBackground()
radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
opacity: notificationHistoryVisible ? 1 : 0
scale: notificationHistoryVisible ? 1 : 0.9
MouseArea {
anchors.fill: parent
onClicked: {
}
}
Column {
id: contentColumn
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
NotificationHeader {
id: notificationHeader
}
NotificationList {
id: notificationList
width: parent.width
height: parent.height - notificationHeader.height - contentColumn.spacing
}
}
Connections {
function onNotificationsChanged() {
mainRect.height = mainRect.calculateHeight();
}
function onGroupedNotificationsChanged() {
mainRect.height = mainRect.calculateHeight();
}
function onExpandedGroupsChanged() {
mainRect.height = mainRect.calculateHeight();
}
function onExpandedMessagesChanged() {
mainRect.height = mainRect.calculateHeight();
}
target: NotificationService
}
Behavior on height {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
Behavior on opacity {
NumberAnimation {
duration: Anims.durMed
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
}
}
Behavior on scale {
NumberAnimation {
duration: Anims.durMed
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
}
}
}
}

View File

@@ -0,0 +1,36 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
width: parent.width
height: 200
visible: NotificationService.notifications.length === 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingXS
width: parent.width * 0.8
DankIcon {
anchors.horizontalCenter: parent.horizontalCenter
name: "notifications_none"
size: Theme.iconSizeLarge + 16
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "Nothing to see here"
font.pixelSize: Theme.fontSizeLarge
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
}
}
}

View File

@@ -0,0 +1,95 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
width: parent.width
height: 32
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
Text {
text: "Notifications"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
DankActionButton {
iconName: Prefs.doNotDisturb ? "notifications_off" : "notifications"
iconColor: Prefs.doNotDisturb ? Theme.error : Theme.surfaceText
buttonSize: 28
anchors.verticalCenter: parent.verticalCenter
onClicked: Prefs.setDoNotDisturb(!Prefs.doNotDisturb)
}
}
Rectangle {
id: clearAllButton
width: 120
height: 28
radius: Theme.cornerRadiusLarge
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
visible: NotificationService.notifications.length > 0
color: clearArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: clearArea.containsMouse ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "delete_sweep"
size: Theme.iconSizeSmall
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: "Clear All"
font.pixelSize: Theme.fontSizeSmall
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: clearArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: NotificationService.clearAllNotifications()
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on border.color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}

View File

@@ -0,0 +1,118 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
ListView {
id: root
property alias count: root.count
readonly property real listContentHeight: root.contentHeight
readonly property bool atYBeginning: root.contentY === 0
property real stableY: 0
property bool isUserScrolling: false
width: parent.width
height: parent.height
clip: true
model: NotificationService.groupedNotifications
spacing: Theme.spacingL
interactive: true
boundsBehavior: Flickable.StopAtBounds
flickDeceleration: 1500
maximumFlickVelocity: 2000
cacheBuffer: 1000
onMovementStarted: isUserScrolling = true
onMovementEnded: {
isUserScrolling = false;
if (contentY > 40)
stableY = contentY;
}
onContentYChanged: {
if (!isUserScrolling && visible && parent.visible && stableY > 40 && Math.abs(contentY - stableY) > 10)
contentY = stableY;
}
NotificationEmptyState {
visible: root.count === 0
anchors.centerIn: parent
}
add: Transition {
enabled: !root.isUserScrolling
ParallelAnimation {
NumberAnimation {
properties: "opacity"
from: 0
to: 1
duration: root.isUserScrolling ? 0 : Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
NumberAnimation {
properties: "height"
from: 0
duration: root.isUserScrolling ? 0 : Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
remove: Transition {
SequentialAnimation {
PauseAnimation {
duration: 50
}
ParallelAnimation {
NumberAnimation {
properties: "opacity"
to: 0
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
NumberAnimation {
properties: "height,anchors.topMargin,anchors.bottomMargin"
to: 0
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
}
displaced: Transition {
enabled: false
NumberAnimation {
properties: "y"
duration: 0
}
}
move: Transition {
enabled: false
NumberAnimation {
properties: "y"
duration: 0
easing.type: Theme.emphasizedEasing
}
}
delegate: NotificationCard {
notificationGroup: modelData
}
}