Files
DankMaterialShell/quickshell/Modules/Settings/NotificationRulesTab.qml
T

371 lines
14 KiB
QML

import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
Item {
id: root
property var parentModal: null
property bool editorOpen: false
property bool editorMounted: false
property var editorRequest: null
readonly property bool hasPendingRule: SettingsData.pendingNotificationRule !== null
function indexedRules(predicate) {
return (SettingsData.notificationRules || []).map((rule, index) => ({
rule: rule,
index: index
})).filter(entry => predicate(entry.rule));
}
readonly property var mutedRules: indexedRules(rule => (rule.action || "").toString().toLowerCase() === "mute")
readonly property var notificationRuleFieldOptions: [
{
value: "appName",
label: I18n.tr("App name", "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 getRuleOptionLabel(options, value, fallback) {
for (let i = 0; i < options.length; i++) {
if (options[i].value === value)
return options[i].label;
}
return fallback;
}
function matchSummary(rule) {
return [getRuleOptionLabel(notificationRuleFieldOptions, rule.field, notificationRuleFieldOptions[0].label), getRuleOptionLabel(notificationRuleMatchTypeOptions, rule.matchType, notificationRuleMatchTypeOptions[0].label)].join(" · ");
}
function outcomeBadges(rule) {
const badges = [];
if ((rule.action || "default") !== "default")
badges.push(getRuleOptionLabel(notificationRuleActionOptions, rule.action, rule.action));
if ((rule.urgency || "default") !== "default")
badges.push(getRuleOptionLabel(notificationRuleUrgencyOptions, rule.urgency, rule.urgency));
if (rule.bypassDnd === true)
badges.push(I18n.tr("Allow in Do Not Disturb"));
return badges;
}
onHasPendingRuleChanged: {
// clearing the draft inside this handler is a binding loop on hasPendingRule
if (hasPendingRule)
Qt.callLater(openPendingRule);
}
function openPendingRule() {
const draft = SettingsData.pendingNotificationRule;
if (!draft)
return;
SettingsData.pendingNotificationRule = null;
openEditor(-1, draft);
}
function openEditor(index, rule) {
if (editorOpen)
return;
editorRequest = {
index,
rule
};
editorOpen = true;
editorMounted = true;
if (editorLoader.item)
presentEditor();
}
function presentEditor() {
if (!editorRequest)
return;
editorLoader.item.show(editorRequest.index, editorRequest.rule);
}
function closeEditor() {
if (!editorOpen)
return;
editorOpen = false;
editorRequest = null;
if (editorLoader.item)
editorLoader.item.opened = false;
}
SettingsPage {
id: mainColumn
SettingsCard {
id: notificationRulesCard
width: parent.width
iconName: "rule_settings"
title: I18n.tr("Notification rules")
settingKey: "notificationRules"
tags: ["notification", "rules", "mute", "ignore", "priority", "regex", "history"]
headerActions: DankActionButton {
buttonSize: 36
iconName: "restart_alt"
tooltipText: I18n.tr("Reset to default")
iconSize: 20
visible: JSON.stringify(SettingsData.notificationRules) !== JSON.stringify(SettingsData.getDefaultNotificationRules())
iconColor: Theme.surfaceVariantText
onClicked: SettingsData.resetNotificationRules()
}
SettingsRow {
subtitle: I18n.tr("Mute, ignore and priority rules per app")
}
Repeater {
model: SettingsData.notificationRules
delegate: SettingsRow {
id: ruleRow
required property var modelData
required property int index
readonly property var badges: root.outcomeBadges(modelData)
title: modelData.pattern || I18n.tr("Rule %1", "notification rule heading, %1 is the rule number").arg(index + 1)
titleColor: modelData.enabled !== false ? Theme.surfaceText : Theme.surfaceVariantText
subtitle: root.matchSummary(modelData)
DankToggle {
anchors.verticalCenter: parent.verticalCenter
hideText: true
text: ruleRow.title
checked: ruleRow.modelData.enabled !== false
onToggled: checked => SettingsData.updateNotificationRuleField(ruleRow.index, "enabled", checked)
}
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "edit"
Accessible.name: I18n.tr("Edit rule")
onClicked: root.openEditor(ruleRow.index, ruleRow.modelData)
}
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "delete"
iconColor: Theme.error
Accessible.name: I18n.tr("Delete rule")
onClicked: SettingsData.removeNotificationRule(ruleRow.index)
}
body: Flow {
width: parent.width
spacing: Theme.spacingXS
visible: ruleRow.badges.length > 0
Repeater {
model: ruleRow.badges
delegate: DankBadge {
required property string modelData
text: modelData
color: Theme.primaryContainer
textColor: Theme.onPrimaryContainer
}
}
}
}
}
}
SettingsCard {
iconName: "volume_off"
title: I18n.tr("Muted apps")
settingKey: "mutedApps"
tags: ["notification", "mute", "unmute", "popup"]
SettingsRow {
body: Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: mutedRules.length > 0 ? I18n.tr("Apps with notification popups muted. Unmute or delete to remove.") : I18n.tr("No apps muted. Right-click a notification and choose \"Mute popups\" to add one here.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
bottomPadding: Theme.spacingS
}
Repeater {
model: mutedRules
delegate: Rectangle {
width: parent.width
height: mutedRow.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius
color: Theme.floatingWindowFieldColor
Row {
id: mutedRow
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: Theme.spacingM
StyledText {
id: mutedAppLabel
text: (modelData.rule && modelData.rule.pattern) ? modelData.rule.pattern : I18n.tr("Unknown")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: Math.max(0, parent.width - parent.spacing - mutedAppLabel.width - unmuteBtn.width - mutedDeleteBtn.width - Theme.spacingS * 5)
height: 1
}
DankButton {
id: unmuteBtn
text: I18n.tr("Unmute")
backgroundColor: Theme.chipSurface
textColor: Theme.primary
onClicked: SettingsData.removeNotificationRule(modelData.index)
}
Item {
id: mutedDeleteBtn
Accessible.role: Accessible.Button
Accessible.name: I18n.tr("Remove")
width: 28
height: 28
anchors.verticalCenter: parent.verticalCenter
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: mutedDeleteArea.containsMouse ? Theme.withAlpha(Theme.error, 0.2) : Theme.withAlpha(Theme.error, 0)
}
DankIcon {
anchors.centerIn: parent
name: "delete"
size: 18
color: mutedDeleteArea.containsMouse ? Theme.error : Theme.surfaceVariantText
}
MouseArea {
id: mutedDeleteArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: SettingsData.removeNotificationRule(modelData.index)
}
}
}
}
}
}
}
}
SettingsFabBar {
DankFab {
text: I18n.tr("Add rule")
iconName: "add"
onClicked: root.openEditor(-1, null)
}
}
}
Loader {
id: editorLoader
parent: root.parentModal?.modalFocusScope ?? root
anchors.fill: parent
z: 100
active: root.editorMounted
onLoaded: root.presentEditor()
sourceComponent: NotificationRuleEditorDialog {
fieldOptions: root.notificationRuleFieldOptions
matchTypeOptions: root.notificationRuleMatchTypeOptions
actionOptions: root.notificationRuleActionOptions
urgencyOptions: root.notificationRuleUrgencyOptions
onRejected: root.closeEditor()
onRuleSubmitted: root.closeEditor()
onActiveChanged: {
if (!active && !root.editorOpen)
root.editorMounted = false;
}
}
}
}