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:
@@ -688,6 +688,7 @@ Singleton {
|
|||||||
property int notificationTimeoutNormal: 5000
|
property int notificationTimeoutNormal: 5000
|
||||||
property int notificationTimeoutCritical: 0
|
property int notificationTimeoutCritical: 0
|
||||||
property bool notificationCompactMode: false
|
property bool notificationCompactMode: false
|
||||||
|
property bool notificationDedupeEnabled: true
|
||||||
property int notificationPopupPosition: SettingsData.Position.Top
|
property int notificationPopupPosition: SettingsData.Position.Top
|
||||||
property int notificationAnimationSpeed: SettingsData.AnimationSpeed.Short
|
property int notificationAnimationSpeed: SettingsData.AnimationSpeed.Short
|
||||||
property int notificationCustomAnimationDuration: 400
|
property int notificationCustomAnimationDuration: 400
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ var SPEC = {
|
|||||||
notificationTimeoutNormal: { def: 5000 },
|
notificationTimeoutNormal: { def: 5000 },
|
||||||
notificationTimeoutCritical: { def: 0 },
|
notificationTimeoutCritical: { def: 0 },
|
||||||
notificationCompactMode: { def: false },
|
notificationCompactMode: { def: false },
|
||||||
|
notificationDedupeEnabled: { def: true },
|
||||||
notificationPopupPosition: { def: 0 },
|
notificationPopupPosition: { def: 0 },
|
||||||
notificationAnimationSpeed: { def: 1 },
|
notificationAnimationSpeed: { def: 1 },
|
||||||
notificationCustomAnimationDuration: { def: 400 },
|
notificationCustomAnimationDuration: { def: 400 },
|
||||||
|
|||||||
@@ -182,11 +182,15 @@ Rectangle {
|
|||||||
Row {
|
Row {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingXS
|
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 {
|
StyledText {
|
||||||
id: historyTitleText
|
id: historyTitleText
|
||||||
width: Math.min(implicitWidth, Math.max(0, parent.width - parent.reservedTrailingWidth))
|
anchors.fill: parent
|
||||||
text: {
|
text: {
|
||||||
let title = historyItem.summary || "";
|
let title = historyItem.summary || "";
|
||||||
const appName = historyItem.appName || "";
|
const appName = historyItem.appName || "";
|
||||||
@@ -201,7 +205,7 @@ Rectangle {
|
|||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
visible: text.length > 0
|
}
|
||||||
}
|
}
|
||||||
StyledText {
|
StyledText {
|
||||||
id: historySeparator
|
id: historySeparator
|
||||||
|
|||||||
@@ -273,6 +273,17 @@ Item {
|
|||||||
onToggled: checked => SettingsData.set("notificationCompactMode", checked)
|
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 {
|
SettingsToggleRow {
|
||||||
settingKey: "notificationPopupShadowEnabled"
|
settingKey: "notificationPopupShadowEnabled"
|
||||||
tags: ["notification", "popup", "shadow", "radius", "rounded"]
|
tags: ["notification", "popup", "shadow", "radius", "rounded"]
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ Singleton {
|
|||||||
property int maxIngressPerSecond: 20
|
property int maxIngressPerSecond: 20
|
||||||
property double _lastIngressSec: 0
|
property double _lastIngressSec: 0
|
||||||
property int _ingressCountThisSec: 0
|
property int _ingressCountThisSec: 0
|
||||||
|
readonly property int notificationDedupBurstMs: 5000
|
||||||
|
property var _recentDedupKeys: []
|
||||||
|
|
||||||
property var _dismissQueue: []
|
property var _dismissQueue: []
|
||||||
property int _dismissBatchSize: 8
|
property int _dismissBatchSize: 8
|
||||||
@@ -291,18 +293,58 @@ Singleton {
|
|||||||
return Date.now() / 1000.0;
|
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) {
|
function _notificationDedupKey(source) {
|
||||||
if (!source)
|
if (!source)
|
||||||
return "";
|
return "";
|
||||||
const app = (source.appName || source.desktopEntry || "").toString();
|
const app = _dedupAppId(source);
|
||||||
const summary = (source.summary || "").toString();
|
const summary = _normalizeDedupText(source.summary);
|
||||||
const body = (source.body || "").toString();
|
const body = _normalizeDedupText(source.body);
|
||||||
const urgency = typeof source.urgency === "number" ? source.urgency : NotificationUrgency.Normal;
|
const urgency = typeof source.urgency === "number" ? source.urgency : NotificationUrgency.Normal;
|
||||||
const icon = (source.appIcon || "").toString();
|
|
||||||
if (!app && !summary && !body)
|
if (!app && !summary && !body)
|
||||||
return "";
|
return "";
|
||||||
const sep = "";
|
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) {
|
function _findActiveDuplicate(notif) {
|
||||||
@@ -310,17 +352,14 @@ Singleton {
|
|||||||
if (!key)
|
if (!key)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
for (const w of visibleNotifications) {
|
for (const w of allWrappers) {
|
||||||
if (!w || !w.notification || !w.popup)
|
if (!w || !w.notification || !w.popup)
|
||||||
continue;
|
continue;
|
||||||
if (_notificationDedupKey(w.notification) === key)
|
if (_notificationDedupKey(w.notification) !== key)
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const w of notificationQueue) {
|
|
||||||
if (!w || !w.notification)
|
|
||||||
continue;
|
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;
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,15 +676,18 @@ Singleton {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SettingsData.notificationDedupeEnabled) {
|
||||||
|
const dedupKey = _notificationDedupKey(notif);
|
||||||
const duplicate = _findActiveDuplicate(notif);
|
const duplicate = _findActiveDuplicate(notif);
|
||||||
if (duplicate) {
|
if (duplicate || _hasRecentDuplicate(dedupKey)) {
|
||||||
if (duplicate.timer && duplicate.timer.running)
|
if (duplicate && duplicate.timer && duplicate.timer.running)
|
||||||
duplicate.timer.restart();
|
duplicate.timer.restart();
|
||||||
try {
|
try {
|
||||||
notif.dismiss();
|
notif.dismiss();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!_ingressAllowed(policy.urgency)) {
|
if (!_ingressAllowed(policy.urgency)) {
|
||||||
if (policy.urgency !== NotificationUrgency.Critical) {
|
if (policy.urgency !== NotificationUrgency.Critical) {
|
||||||
@@ -686,6 +728,9 @@ Singleton {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (wrapper) {
|
if (wrapper) {
|
||||||
|
if (SettingsData.notificationDedupeEnabled)
|
||||||
|
_recordDedupKey(_notificationDedupKey(notif));
|
||||||
|
|
||||||
root.allWrappers.push(wrapper);
|
root.allWrappers.push(wrapper);
|
||||||
if (shouldKeepInCenter) {
|
if (shouldKeepInCenter) {
|
||||||
root.notifications.push(wrapper);
|
root.notifications.push(wrapper);
|
||||||
|
|||||||
@@ -5807,6 +5807,28 @@
|
|||||||
],
|
],
|
||||||
"description": "Use smaller notification cards"
|
"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",
|
"section": "notificationHistorySaveCritical",
|
||||||
"label": "Critical Priority",
|
"label": "Critical Priority",
|
||||||
|
|||||||
Reference in New Issue
Block a user