mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-26 13:07:49 -04:00
* fix(notifications): edit rules from the menu instead of deleting them A notification rule can set an action and Bypass DND together, but Unmute and Block in Do Not Disturb spliced the whole rule, dropping the other setting. They now reset only their own field, and a rule is removed once it no longer does anything (action and urgency default, no bypass). Mute and Allow in Do Not Disturb set their field on the app's existing rule instead of appending one that first-match policy would shadow. The menu now only reads and edits enabled, exact rules on the app name or desktop entry. It used to match any rule whose pattern equaled the app name, including summary, body, contains and regex rules, and could delete disabled ones. Also adds a Copy item to the notification menu on popups and center cards, copying the summary and body as plain text. * fix(notifications): let mute apply alongside other rule actions Rule actions are first-match, so a mute rule behind an app's existing rule (e.g. No history) never applied while the menu showed Unmute. Mute is now evaluated like Bypass DND: any matching enabled mute rule disables the popup, and the other actions stay first-match, so No history and Mute both apply. * fix(notifications): skip the sound for dropped and muted notifications The notification sound played before the check that dismisses a notification with neither a popup nor a center entry, so dropped notifications (transient while muted, Popup only plus Mute) still made a sound. Play it only for notifications that are kept, and not when a mute rule applies, like Do Not Disturb.
262 lines
11 KiB
QML
262 lines
11 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
import qs.Modules.Notifications
|
|
import qs.Modules.Notifications as Notifications
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property var notificationGroup
|
|
property bool expanded: NotificationService.expandedGroups[notificationGroup?.key] || false
|
|
property bool animateExpansion: true
|
|
property bool isGroupSelected: false
|
|
property int selectedNotificationIndex: -1
|
|
property bool keyboardNavigationActive: false
|
|
property var transientSurfaceTracker: null
|
|
property bool firstInList: true
|
|
property bool lastInList: true
|
|
property bool nested: false
|
|
property real topRoundness: 0
|
|
property real bottomRoundness: 0
|
|
property real contentOpacity: 1
|
|
property real swipeBleed: 0
|
|
readonly property color cardSurfaceColor: Theme.foregroundColor(nested ? Theme.chipSurface : Theme.cardSurface, Theme.isFloatingWindow(root))
|
|
readonly property color cardChipColor: nested ? Theme.withAlpha(Theme.onSurface, Theme.stateLayerFocus) : Theme.foregroundColor(Theme.chipSurface, Theme.isFloatingWindow(root))
|
|
readonly property real expandedTargetHeight: {
|
|
let total = groupHeader.height;
|
|
for (const child of expandedContent.children) {
|
|
if (child.targetHeight === undefined)
|
|
continue;
|
|
total += Theme.groupedListGap + child.targetHeight;
|
|
}
|
|
return total;
|
|
}
|
|
readonly property real targetHeight: expanded ? expandedTargetHeight : collapsedCard.targetHeight
|
|
readonly property bool isAnimating: heightAnimation.running
|
|
|
|
width: parent ? parent.width : NotificationMetrics.popupWidth
|
|
height: targetHeight
|
|
clip: isAnimating
|
|
|
|
function toggleGroup() {
|
|
NotificationService.toggleGroupExpansion(notificationGroup?.key || "");
|
|
}
|
|
|
|
function invokeAction(action) {
|
|
if (!action?.invoke)
|
|
return;
|
|
action.invoke();
|
|
PopoutService.closeNotificationCenter();
|
|
}
|
|
|
|
function openContextMenu(item, notification) {
|
|
contextActions.notification = notification;
|
|
notificationCardContextMenu.popupAnchorItem = item;
|
|
notificationCardContextMenu.showDropdownMenu();
|
|
}
|
|
|
|
Component.onDestruction: transientSurfaceTracker?.unregister(root)
|
|
|
|
NotificationSwipeGroup {
|
|
id: rowSwipe
|
|
}
|
|
|
|
Connections {
|
|
target: root.transientSurfaceTracker
|
|
ignoreUnknownSignals: true
|
|
function onCloseRequested() {
|
|
notificationCardContextMenu.closeDropdownMenu();
|
|
}
|
|
}
|
|
|
|
Behavior on height {
|
|
enabled: root.animateExpansion && NotificationMetrics.animationsEnabled
|
|
NumberAnimation {
|
|
id: heightAnimation
|
|
duration: root.expanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: NotificationMetrics.expandCurve
|
|
}
|
|
}
|
|
|
|
Notifications.NotificationCard {
|
|
id: collapsedCard
|
|
width: parent.width
|
|
surfaceColor: root.cardSurfaceColor
|
|
chipColor: root.cardChipColor
|
|
visible: !root.expanded
|
|
notificationData: root.notificationGroup?.latestNotification ?? null
|
|
groupCount: root.notificationGroup?.count || 0
|
|
descriptionExpanded: NotificationService.expandedMessages[(notificationData?.notification?.id || "") + "_desc"] || false
|
|
firstInGroup: root.firstInList
|
|
lastInGroup: root.lastInList
|
|
topRoundness: root.topRoundness
|
|
bottomRoundness: root.bottomRoundness
|
|
contentOpacity: root.contentOpacity
|
|
keyboardSelected: root.keyboardNavigationActive && root.isGroupSelected
|
|
keyboardHints: keyboardSelected
|
|
animateHeight: false
|
|
onExpandRequested: NotificationService.toggleMessageExpansion((notificationData?.notification?.id || "") + "_desc")
|
|
onGroupToggleRequested: root.toggleGroup()
|
|
onDismissRequested: NotificationService.dismissGroup(root.notificationGroup?.key || "")
|
|
onActionRequested: action => root.invokeAction(action)
|
|
onBodyClicked: {
|
|
if (groupCount > 1) {
|
|
root.toggleGroup();
|
|
return;
|
|
}
|
|
root.invokeAction(contextActions.defaultAction(notificationData));
|
|
}
|
|
onContextMenuRequested: (x, y) => root.openContextMenu(collapsedCard, collapsedCard.notificationData)
|
|
}
|
|
|
|
Column {
|
|
id: expandedContent
|
|
objectName: "expandedContent"
|
|
width: parent.width
|
|
visible: root.expanded
|
|
opacity: root.contentOpacity
|
|
spacing: Theme.groupedListGap
|
|
|
|
Rectangle {
|
|
id: groupHeader
|
|
width: parent.width
|
|
height: NotificationMetrics.cardPadding * 2 + NotificationMetrics.controlSize
|
|
radius: Theme.groupedListOuterRadius
|
|
color: "transparent"
|
|
border.width: root.keyboardNavigationActive && root.isGroupSelected ? Theme.focusRingWidth : 0
|
|
border.color: Theme.focusRingColor
|
|
StyledText {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: NotificationMetrics.cardPadding
|
|
anchors.right: groupControls.left
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: root.notificationGroup?.appName || ""
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Theme.fontWeightMedium
|
|
color: Theme.primary
|
|
elide: Text.ElideRight
|
|
}
|
|
Row {
|
|
id: groupControls
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: NotificationMetrics.cardPadding
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingXS
|
|
DankButton {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: I18n.tr("Dismiss")
|
|
buttonHeight: NotificationMetrics.controlSize
|
|
horizontalPadding: NotificationMetrics.actionPadding
|
|
backgroundColor: "transparent"
|
|
textColor: Theme.primary
|
|
onClicked: NotificationService.dismissGroup(root.notificationGroup?.key || "")
|
|
}
|
|
DankButton {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: (root.notificationGroup?.count || 0).toString()
|
|
iconName: "expand_less"
|
|
buttonHeight: NotificationMetrics.controlSize
|
|
horizontalPadding: Theme.spacingS
|
|
backgroundColor: root.cardChipColor
|
|
textColor: Theme.onSurfaceVariant
|
|
Accessible.name: I18n.tr("Collapse")
|
|
onClicked: root.toggleGroup()
|
|
}
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
id: notificationRepeater
|
|
objectName: "notificationRepeater"
|
|
model: ScriptModel {
|
|
values: root.expanded ? (root.notificationGroup?.notifications?.slice(0, NotificationMetrics.expandedLimit) || []) : []
|
|
}
|
|
|
|
NotificationSwipeRow {
|
|
id: row
|
|
required property var modelData
|
|
property bool collapsing: false
|
|
property var notificationToDismiss: null
|
|
readonly property real targetHeight: collapsing ? 0 : message.targetHeight
|
|
group: rowSwipe
|
|
bleed: root.swipeBleed
|
|
width: expandedContent.width
|
|
height: message.height
|
|
clip: collapsing
|
|
onDismissed: {
|
|
notificationToDismiss = modelData;
|
|
collapseAnimation.start();
|
|
}
|
|
|
|
Notifications.NotificationCard {
|
|
id: message
|
|
surfaceColor: root.cardSurfaceColor
|
|
chipColor: root.cardChipColor
|
|
x: row.offset
|
|
width: parent.width
|
|
notificationData: row.modelData
|
|
firstInGroup: row.index === 0
|
|
lastInGroup: row.index === notificationRepeater.count - 1
|
|
topRoundness: row.topRoundness
|
|
bottomRoundness: row.bottomRoundness
|
|
contentOpacity: row.contentOpacity
|
|
keyboardSelected: root.keyboardNavigationActive && root.selectedNotificationIndex === row.index
|
|
keyboardHints: keyboardSelected
|
|
descriptionExpanded: NotificationService.expandedMessages[(notificationData?.notification?.id || "") + "_desc"] || false
|
|
onExpandRequested: NotificationService.toggleMessageExpansion((notificationData?.notification?.id || "") + "_desc")
|
|
onDismissRequested: NotificationService.dismissNotification(row.modelData)
|
|
onActionRequested: action => root.invokeAction(action)
|
|
onBodyClicked: root.invokeAction(contextActions.defaultAction(notificationData))
|
|
onContextMenuRequested: (x, y) => root.openContextMenu(message, row.modelData)
|
|
}
|
|
|
|
SequentialAnimation {
|
|
id: collapseAnimation
|
|
|
|
PropertyAction {
|
|
target: row
|
|
property: "collapsing"
|
|
value: true
|
|
}
|
|
NumberAnimation {
|
|
target: row
|
|
property: "height"
|
|
to: 0
|
|
duration: NotificationMetrics.animationsEnabled ? Theme.notificationExpandDuration : 0
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: NotificationMetrics.expandCurve
|
|
}
|
|
ScriptAction {
|
|
script: NotificationService.dismissNotification(row.notificationToDismiss)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NotificationActions {
|
|
id: contextActions
|
|
appName: root.notificationGroup?.appName || ""
|
|
desktopEntry: root.notificationGroup?.latestNotification?.desktopEntry || ""
|
|
onDismissRequested: NotificationService.dismissGroup(root.notificationGroup?.key || "")
|
|
onAppMuted: NotificationService.dismissGroup(root.notificationGroup?.key || "")
|
|
}
|
|
|
|
DankDropdown {
|
|
id: notificationCardContextMenu
|
|
showTrigger: false
|
|
popupWidth: NotificationMetrics.menuWidth
|
|
transientSurfaceTracker: root.transientSurfaceTracker
|
|
options: contextActions.items.map(item => item.label)
|
|
onValueChanged: value => {
|
|
const item = contextActions.items.find(item => item.label === value);
|
|
if (item)
|
|
contextActions.trigger(item.action);
|
|
}
|
|
}
|
|
}
|