1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-07 19:59:14 -04:00

refactor(Notifications): further support for duplicate notification logic

- New setting to stack or suppress identical alerts (on by default)
Closes #2334
This commit is contained in:
purian23
2026-05-24 22:22:34 -04:00
parent 6093c37b41
commit d9525908f1
6 changed files with 122 additions and 38 deletions
+1
View File
@@ -688,6 +688,7 @@ Singleton {
property int notificationTimeoutNormal: 5000
property int notificationTimeoutCritical: 0
property bool notificationCompactMode: false
property bool notificationDedupeEnabled: true
property int notificationPopupPosition: SettingsData.Position.Top
property int notificationAnimationSpeed: SettingsData.AnimationSpeed.Short
property int notificationCustomAnimationDuration: 400
@@ -399,6 +399,7 @@ var SPEC = {
notificationTimeoutNormal: { def: 5000 },
notificationTimeoutCritical: { def: 0 },
notificationCompactMode: { def: false },
notificationDedupeEnabled: { def: true },
notificationPopupPosition: { def: 0 },
notificationAnimationSpeed: { def: 1 },
notificationCustomAnimationDuration: { def: 400 },
@@ -182,11 +182,15 @@ Rectangle {
Row {
width: parent.width
spacing: Theme.spacingXS
readonly property real reservedTrailingWidth: historySeparator.implicitWidth + Math.max(historyTimeText.implicitWidth, 72) + spacing
Item {
width: Math.max(0, parent.width - historySeparator.implicitWidth - Math.max(historyTimeText.implicitWidth, 72) - parent.spacing * 2)
height: historyTitleText.implicitHeight
visible: historyTitleText.text.length > 0
StyledText {
id: historyTitleText
width: Math.min(implicitWidth, Math.max(0, parent.width - parent.reservedTrailingWidth))
anchors.fill: parent
text: {
let title = historyItem.summary || "";
const appName = historyItem.appName || "";
@@ -201,7 +205,7 @@ Rectangle {
font.weight: Font.Medium
elide: Text.ElideRight
maximumLineCount: 1
visible: text.length > 0
}
}
StyledText {
id: historySeparator
@@ -273,6 +273,17 @@ Item {
onToggled: checked => SettingsData.set("notificationCompactMode", checked)
}
SettingsToggleRow {
settingKey: "notificationDedupeEnabled"
tags: ["notification", "duplicate", "dedupe", "stack", "coalesce", "repeat"]
text: I18n.tr("Suppress Duplicate Notifications")
description: SettingsData.notificationDedupeEnabled
? I18n.tr("Identical alerts show as one popup instead of stacking")
: I18n.tr("Identical alerts stack as separate notification cards")
checked: SettingsData.notificationDedupeEnabled
onToggled: checked => SettingsData.set("notificationDedupeEnabled", checked)
}
SettingsToggleRow {
settingKey: "notificationPopupShadowEnabled"
tags: ["notification", "popup", "shadow", "radius", "rounded"]
+60 -15
View File
@@ -35,6 +35,8 @@ Singleton {
property int maxIngressPerSecond: 20
property double _lastIngressSec: 0
property int _ingressCountThisSec: 0
readonly property int notificationDedupBurstMs: 5000
property var _recentDedupKeys: []
property var _dismissQueue: []
property int _dismissBatchSize: 8
@@ -291,18 +293,58 @@ Singleton {
return Date.now() / 1000.0;
}
function _normalizeDedupText(text) {
if (!text)
return "";
let normalized = text.toString();
normalized = normalized.replace(/<img\b[^>]*>/gi, "");
normalized = normalized.replace(/<[^>]+>/g, "");
normalized = normalized.replace(/\s+/g, " ").trim();
return normalized.toLowerCase();
}
function _dedupAppId(source) {
if (!source)
return "";
const desktopEntry = (source.desktopEntry || "").toString().trim().toLowerCase();
if (desktopEntry)
return desktopEntry;
return (source.appName || "").toString().trim().toLowerCase();
}
function _notificationDedupKey(source) {
if (!source)
return "";
const app = (source.appName || source.desktopEntry || "").toString();
const summary = (source.summary || "").toString();
const body = (source.body || "").toString();
const app = _dedupAppId(source);
const summary = _normalizeDedupText(source.summary);
const body = _normalizeDedupText(source.body);
const urgency = typeof source.urgency === "number" ? source.urgency : NotificationUrgency.Normal;
const icon = (source.appIcon || "").toString();
if (!app && !summary && !body)
return "";
const sep = "";
return app + sep + summary + sep + body + sep + urgency + sep + icon;
return app + sep + summary + sep + body + sep + urgency;
}
function _pruneRecentDedupKeys() {
const cutoff = Date.now() - notificationDedupBurstMs;
_recentDedupKeys = _recentDedupKeys.filter(entry => entry && entry.atMs >= cutoff);
}
function _hasRecentDuplicate(key) {
if (!key)
return false;
_pruneRecentDedupKeys();
return _recentDedupKeys.some(entry => entry && entry.key === key);
}
function _recordDedupKey(key) {
if (!key)
return;
_pruneRecentDedupKeys();
_recentDedupKeys.push({
"key": key,
"atMs": Date.now()
});
}
function _findActiveDuplicate(notif) {
@@ -310,17 +352,14 @@ Singleton {
if (!key)
return null;
for (const w of visibleNotifications) {
for (const w of allWrappers) {
if (!w || !w.notification || !w.popup)
continue;
if (_notificationDedupKey(w.notification) === key)
return w;
}
for (const w of notificationQueue) {
if (!w || !w.notification)
if (_notificationDedupKey(w.notification) !== key)
continue;
if (_notificationDedupKey(w.notification) === key)
if (visibleNotifications.indexOf(w) !== -1 || notificationQueue.indexOf(w) !== -1)
return w;
if (w.timer && w.timer.running)
return w;
}
@@ -637,15 +676,18 @@ Singleton {
return;
}
if (SettingsData.notificationDedupeEnabled) {
const dedupKey = _notificationDedupKey(notif);
const duplicate = _findActiveDuplicate(notif);
if (duplicate) {
if (duplicate.timer && duplicate.timer.running)
if (duplicate || _hasRecentDuplicate(dedupKey)) {
if (duplicate && duplicate.timer && duplicate.timer.running)
duplicate.timer.restart();
try {
notif.dismiss();
} catch (e) {}
return;
}
}
if (!_ingressAllowed(policy.urgency)) {
if (policy.urgency !== NotificationUrgency.Critical) {
@@ -686,6 +728,9 @@ Singleton {
});
if (wrapper) {
if (SettingsData.notificationDedupeEnabled)
_recordDedupKey(_notificationDedupKey(notif));
root.allWrappers.push(wrapper);
if (shouldKeepInCenter) {
root.notifications.push(wrapper);
@@ -5807,6 +5807,28 @@
],
"description": "Use smaller notification cards"
},
{
"section": "notificationDedupeEnabled",
"label": "Suppress Duplicate Notifications",
"tabIndex": 17,
"category": "Notifications",
"keywords": [
"alert",
"alerts",
"coalesce",
"dedupe",
"duplicate",
"duplicates",
"messages",
"notif",
"notification",
"notifications",
"repeat",
"stack",
"toast"
],
"description": "Control whether identical alerts stack or show as a single popup"
},
{
"section": "notificationHistorySaveCritical",
"label": "Critical Priority",