1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-10 07:42:09 -04:00

feat(notifications): add configurable notification rules (#1655)

This commit is contained in:
Bernardo Gomes
2026-02-12 17:04:02 -03:00
committed by GitHub
parent a3baf8ce31
commit 425715e0f0
5 changed files with 524 additions and 18 deletions

View File

@@ -153,12 +153,12 @@ QtObject {
if (!wrapper || !wrapper.notification) {
return false;
}
const incomingUrgency = wrapper.notification.urgency || 0;
const incomingUrgency = wrapper.urgency || 0;
for (const p of activeWindows) {
if (!p.notificationData || !p.notificationData.notification) {
continue;
}
const existingUrgency = p.notificationData.notification.urgency || 0;
const existingUrgency = p.notificationData.urgency || 0;
if (existingUrgency < incomingUrgency) {
return true;
}
@@ -188,10 +188,9 @@ QtObject {
}
function _selectPopupToRemove(activeWindows, incomingWrapper) {
const incomingUrgency = (incomingWrapper && incomingWrapper.notification) ? incomingWrapper.notification.urgency || 0 : 0;
const sortedWindows = activeWindows.slice().sort((a, b) => {
const aUrgency = (a.notificationData && a.notificationData.notification) ? a.notificationData.notification.urgency || 0 : 0;
const bUrgency = (b.notificationData && b.notificationData.notification) ? b.notificationData.notification.urgency || 0 : 0;
const aUrgency = (a.notificationData) ? a.notificationData.urgency || 0 : 0;
const bUrgency = (b.notificationData) ? b.notificationData.urgency || 0 : 0;
if (aUrgency !== bUrgency) {
return aUrgency - bUrgency;
}

View File

@@ -57,6 +57,82 @@ Item {
}
]
readonly property var notificationRuleFieldOptions: [
{
value: "appName",
label: I18n.tr("App Names", "notification rule match field option")
},
{
value: "desktopEntry",
label: I18n.tr("Desktop Entry", "notification rule match field option")
},
{
value: "summary",
label: I18n.tr("Summary", "notification rule match field option")
},
{
value: "body",
label: I18n.tr("Body", "notification rule match field option")
}
]
readonly property var notificationRuleMatchTypeOptions: [
{
value: "contains",
label: I18n.tr("Contains", "notification rule match type option")
},
{
value: "exact",
label: I18n.tr("Exact", "notification rule match type option")
},
{
value: "regex",
label: I18n.tr("Regex", "notification rule match type option")
}
]
readonly property var notificationRuleActionOptions: [
{
value: "default",
label: I18n.tr("Default", "notification rule action option")
},
{
value: "mute",
label: I18n.tr("Mute Popups", "notification rule action option")
},
{
value: "ignore",
label: I18n.tr("Ignore Completely", "notification rule action option")
},
{
value: "popup_only",
label: I18n.tr("Popup Only", "notification rule action option")
},
{
value: "no_history",
label: I18n.tr("No History", "notification rule action option")
}
]
readonly property var notificationRuleUrgencyOptions: [
{
value: "default",
label: I18n.tr("Default", "notification rule urgency option")
},
{
value: "low",
label: I18n.tr("Low Priority", "notification rule urgency option")
},
{
value: "normal",
label: I18n.tr("Normal Priority", "notification rule urgency option")
},
{
value: "critical",
label: I18n.tr("Critical Priority", "notification rule urgency option")
}
]
function getTimeoutText(value) {
if (value === undefined || value === null || isNaN(value))
return I18n.tr("5 seconds");
@@ -73,6 +149,22 @@ Item {
return Math.round(value / 60000) + " " + I18n.tr("minutes");
}
function getRuleOptionLabel(options, value, fallback) {
for (let i = 0; i < options.length; i++) {
if (options[i].value === value)
return options[i].label;
}
return fallback;
}
function getRuleOptionValue(options, label, fallback) {
for (let i = 0; i < options.length; i++) {
if (options[i].label === label)
return options[i].value;
}
return fallback;
}
DankFlickable {
anchors.fill: parent
clip: true
@@ -165,6 +257,228 @@ Item {
}
}
SettingsCard {
width: parent.width
iconName: "rule_settings"
title: I18n.tr("Notification Rules")
settingKey: "notificationRules"
tags: ["notification", "rules", "mute", "ignore", "priority", "regex", "history"]
collapsible: true
expanded: false
headerActions: [
DankActionButton {
buttonSize: 36
iconName: "restart_alt"
iconSize: 20
visible: JSON.stringify(SettingsData.notificationRules) !== JSON.stringify(SettingsData.getDefaultNotificationRules())
backgroundColor: Theme.surfaceContainer
iconColor: Theme.surfaceVariantText
onClicked: SettingsData.resetNotificationRules()
},
DankActionButton {
buttonSize: 36
iconName: "add"
iconSize: 20
backgroundColor: Theme.surfaceContainer
iconColor: Theme.primary
onClicked: SettingsData.addNotificationRule()
}
]
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Create rules to mute, ignore, hide from history, or override notification priority.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
bottomPadding: Theme.spacingS
}
Repeater {
model: SettingsData.notificationRules
delegate: Rectangle {
id: ruleItem
width: parent.width
height: ruleColumn.implicitHeight + Theme.spacingM
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainer, 0.5)
Column {
id: ruleColumn
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
id: ruleLabel
text: I18n.tr("Rule") + " " + (index + 1)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: Math.max(0, parent.width - ruleLabel.implicitWidth - enableToggle.width - deleteBtn.width - Theme.spacingS * 3)
height: 1
}
DankToggle {
id: enableToggle
width: 40
height: 24
hideText: true
checked: modelData.enabled !== false
onToggled: checked => SettingsData.updateNotificationRuleField(index, "enabled", checked)
}
Item {
id: deleteBtn
width: 28
height: 28
anchors.verticalCenter: parent.verticalCenter
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: deleteArea.containsMouse ? Theme.withAlpha(Theme.error, 0.2) : "transparent"
}
DankIcon {
anchors.centerIn: parent
name: "delete"
size: 18
color: deleteArea.containsMouse ? Theme.error : Theme.surfaceVariantText
}
MouseArea {
id: deleteArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: SettingsData.removeNotificationRule(index)
}
}
}
Column {
width: parent.width
spacing: 2
StyledText {
text: I18n.tr("Pattern")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
DankTextField {
width: parent.width
text: modelData.pattern || ""
font.pixelSize: Theme.fontSizeSmall
placeholderText: I18n.tr("Pattern")
onEditingFinished: SettingsData.updateNotificationRuleField(index, "pattern", text)
}
}
Row {
width: parent.width
spacing: Theme.spacingS
Column {
width: (parent.width - Theme.spacingS * 3) / 4
spacing: 2
StyledText {
text: I18n.tr("Field")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
DankDropdown {
width: parent.width
compactMode: true
dropdownWidth: parent.width
currentValue: root.getRuleOptionLabel(root.notificationRuleFieldOptions, modelData.field, root.notificationRuleFieldOptions[0].label)
options: root.notificationRuleFieldOptions.map(o => o.label)
onValueChanged: value => SettingsData.updateNotificationRuleField(index, "field", root.getRuleOptionValue(root.notificationRuleFieldOptions, value, "appName"))
}
}
Column {
width: (parent.width - Theme.spacingS * 3) / 4
spacing: 2
StyledText {
text: I18n.tr("Type")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
DankDropdown {
width: parent.width
compactMode: true
dropdownWidth: parent.width
currentValue: root.getRuleOptionLabel(root.notificationRuleMatchTypeOptions, modelData.matchType, root.notificationRuleMatchTypeOptions[0].label)
options: root.notificationRuleMatchTypeOptions.map(o => o.label)
onValueChanged: value => SettingsData.updateNotificationRuleField(index, "matchType", root.getRuleOptionValue(root.notificationRuleMatchTypeOptions, value, "contains"))
}
}
Column {
width: (parent.width - Theme.spacingS * 3) / 4
spacing: 2
StyledText {
text: I18n.tr("Action")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
DankDropdown {
width: parent.width
compactMode: true
dropdownWidth: parent.width
currentValue: root.getRuleOptionLabel(root.notificationRuleActionOptions, modelData.action, root.notificationRuleActionOptions[0].label)
options: root.notificationRuleActionOptions.map(o => o.label)
onValueChanged: value => SettingsData.updateNotificationRuleField(index, "action", root.getRuleOptionValue(root.notificationRuleActionOptions, value, "default"))
}
}
Column {
width: (parent.width - Theme.spacingS * 3) / 4
spacing: 2
StyledText {
text: I18n.tr("Priority")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
DankDropdown {
width: parent.width
compactMode: true
dropdownWidth: parent.width
currentValue: root.getRuleOptionLabel(root.notificationRuleUrgencyOptions, modelData.urgency, root.notificationRuleUrgencyOptions[0].label)
options: root.notificationRuleUrgencyOptions.map(o => o.label)
onValueChanged: value => SettingsData.updateNotificationRuleField(index, "urgency", root.getRuleOptionValue(root.notificationRuleUrgencyOptions, value, "default"))
}
}
}
}
}
}
}
}
SettingsCard {
width: parent.width
iconName: "lock"