feat(notifications): add notification display rules

- block: skips the notification completely
- mute: does not play sound (played by noctalia), shows popup, adds to
history
- hide: no sound, no popup, still adds to history
This commit is contained in:
Lysec
2026-03-18 21:24:14 +01:00
parent 9f8bf988f0
commit 381444bc2c
7 changed files with 368 additions and 23 deletions
@@ -0,0 +1,120 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services.System
import qs.Widgets
Popup {
id: root
modal: true
closePolicy: Popup.NoAutoClose
dim: true
anchors.centerIn: parent
width: Math.min(500 * Style.uiScaleRatio, parent.width * 0.9)
padding: Style.marginL
property int editIndex: -1
property string patternValue: ""
property string actionValue: "block"
signal saved(string pattern, string action)
property var _savedSlot: null
property string _selectedAction: "block"
background: Rectangle {
color: Color.mSurface
radius: Style.radiusL
border.color: Color.mOutline
border.width: Style.borderS
}
onOpened: {
patternInput.text = patternValue;
actionCombo.currentKey = actionValue;
_selectedAction = actionValue;
patternInput.forceActiveFocus();
}
contentItem: ColumnLayout {
id: contentLayout
spacing: Style.marginL
RowLayout {
Layout.fillWidth: true
NText {
text: editIndex >= 0 ? I18n.tr("panels.notifications.rules-edit") : I18n.tr("panels.notifications.rules-add")
font.weight: Style.fontWeightBold
pointSize: Style.fontSizeL
Layout.fillWidth: true
}
NIconButton {
icon: "close"
onClicked: root.close()
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginM
NTextInput {
id: patternInput
Layout.fillWidth: true
label: I18n.tr("panels.notifications.rules-pattern-label")
placeholderText: I18n.tr("panels.notifications.rules-pattern-placeholder")
fontFamily: Settings.data.ui.fontFixed
}
NComboBox {
id: actionCombo
Layout.fillWidth: true
label: I18n.tr("panels.notifications.rules-action-label")
model: [
{ "key": "block", "name": I18n.tr("panels.notifications.rules-action-block") },
{ "key": "mute", "name": I18n.tr("panels.notifications.rules-action-mute") },
{ "key": "hide", "name": I18n.tr("panels.notifications.rules-action-hide") }
]
currentKey: actionValue
onSelected: key => {
actionValue = key;
_selectedAction = key;
}
}
NLabel {
Layout.fillWidth: true
label: _selectedAction === "block" ? I18n.tr("panels.notifications.rules-action-block-desc") : (_selectedAction === "mute" ? I18n.tr("panels.notifications.rules-action-mute-desc") : I18n.tr("panels.notifications.rules-action-hide-desc"))
labelColor: Color.mOnSurfaceVariant
}
}
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
Item {
Layout.fillWidth: true
}
NButton {
text: I18n.tr("common.cancel")
outlined: true
onClicked: root.close()
}
NButton {
text: I18n.tr("common.save")
icon: "check"
backgroundColor: Color.mPrimary
textColor: Color.mOnPrimary
onClicked: {
root.saved(patternInput.text.trim(), _selectedAction || "block");
root.close();
}
}
}
}
}
@@ -68,6 +68,11 @@ ColumnLayout {
tabIndex: 4
checked: subTabBar.currentIndex === 4
}
NTabButton {
text: I18n.tr("panels.notifications.rules-tab")
tabIndex: 5
checked: subTabBar.currentIndex === 5
}
}
Item {
@@ -92,6 +97,7 @@ ColumnLayout {
onOpenCriticalPicker: root.openCriticalSoundPicker()
}
ToastSubTab {}
RulesSubTab {}
}
// File Pickers for Sound Files
@@ -0,0 +1,108 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services.System
import qs.Widgets
ColumnLayout {
id: root
spacing: Style.marginL
Layout.fillWidth: true
enabled: Settings.data.notifications.enabled
function _saveToService() {
NotificationRulesService.save();
}
function _removeRule(index) {
var arr = (NotificationRulesService.rules || []).slice();
arr.splice(index, 1);
NotificationRulesService.rules = arr;
_saveToService();
}
NotificationRuleEditPopup {
id: editPopup
parent: Overlay.overlay
}
function openEdit(index, patternVal, actionVal) {
editPopup.editIndex = index;
editPopup.patternValue = patternVal || "";
editPopup.actionValue = actionVal || "block";
try {
editPopup.saved.disconnect(editPopup._savedSlot);
} catch (e) {}
editPopup._savedSlot = function (pattern, action) {
var arr = (NotificationRulesService.rules || []).slice();
var rule = { "pattern": pattern, "action": action };
if (index >= 0 && index < arr.length) {
arr[index] = rule;
} else {
arr.push(rule);
}
NotificationRulesService.rules = arr;
_saveToService();
};
editPopup.saved.connect(editPopup._savedSlot);
editPopup.open();
}
NLabel {
label: I18n.tr("panels.notifications.rules-label")
description: I18n.tr("panels.notifications.rules-description")
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginS
Layout.bottomMargin: Style.marginS
}
Repeater {
model: NotificationRulesService.rules || []
delegate: RowLayout {
id: entryDelegate
required property int index
required property var modelData
property string pattern: modelData.pattern || ""
property string action: modelData.action || "block"
property bool isRegex: pattern.length >= 3 && pattern.startsWith("/") && pattern.endsWith("/")
spacing: Style.marginM
Layout.fillWidth: true
NLabel {
Layout.fillWidth: true
label: (entryDelegate.isRegex ? "regex: " : "") + (entryDelegate.pattern || I18n.tr("panels.notifications.rules-empty-pattern"))
description: entryDelegate.action === "block" ? I18n.tr("panels.notifications.rules-action-block") : (entryDelegate.action === "mute" ? I18n.tr("panels.notifications.rules-action-mute") : I18n.tr("panels.notifications.rules-action-hide"))
labelColor: entryDelegate.pattern ? Color.mPrimary : Color.mOnSurface
}
NIconButton {
icon: "settings"
tooltipText: I18n.tr("common.edit")
onClicked: root.openEdit(entryDelegate.index, entryDelegate.pattern, entryDelegate.action)
}
NIconButton {
icon: "trash"
tooltipText: I18n.tr("panels.notifications.rules-delete")
onClicked: root._removeRule(entryDelegate.index)
}
}
}
NButton {
text: I18n.tr("panels.notifications.rules-add")
icon: "add"
enabled: Settings.data.notifications.enabled
onClicked: root.openEdit(-1, "", "block")
}
}