C1: NotifItem QtObject with owned timer, refactor service to stable identities

This commit is contained in:
Damocles 2026-04-13 16:56:37 +02:00
parent 45704cb102
commit 88d8842064
3 changed files with 136 additions and 52 deletions

View file

@ -12,30 +12,37 @@ QtObject {
property var list: []
property bool dnd: false
readonly property var popups: list.filter(n => n.popup)
readonly property int count: list.length
readonly property var popups: list.filter(n => n.popup && n.state !== "dismissed")
readonly property int count: list.filter(n => n.state !== "dismissed").length
// O(1) lookup
property var _byId: ({})
function dismiss(notifId) {
const idx = list.findIndex(n => n.id === notifId);
if (idx >= 0) {
const n = list[idx];
n.notification?.dismiss();
list.splice(idx, 1);
_changed();
}
const item = _byId[notifId];
if (!item)
return;
item.finishDismiss();
list = list.filter(n => n !== item);
delete _byId[notifId];
item.destroy();
_saveTimer.restart();
}
function dismissAll() {
for (const n of list.slice())
n.notification?.dismiss();
for (const item of list.slice()) {
item.finishDismiss();
delete _byId[item.id];
item.destroy();
}
list = [];
_changed();
_saveTimer.restart();
}
function dismissPopup(notifId) {
const n = list.find(n => n.id === notifId);
if (n) {
n.popup = false;
const item = _byId[notifId];
if (item) {
item.popup = false;
_changed();
}
}
@ -46,9 +53,11 @@ QtObject {
function _changed() {
list = list.slice();
_saveTimer.restart();
}
// Signal popups to animate out before removal
signal popupExpiring(var notifId)
property NotificationServer _server: NotificationServer {
actionsSupported: true
bodyMarkupSupported: true
@ -59,13 +68,17 @@ QtObject {
onNotification: notif => {
notif.tracked = true;
const data = {
const isCritical = notif.urgency === NotificationUrgency.Critical;
const item = _itemComp.createObject(root, {
notification: notif,
id: notif.id,
summary: notif.summary,
body: notif.body,
appName: notif.appName,
appIcon: notif.appIcon,
image: notif.image,
hints: notif.hints,
urgency: notif.urgency,
actions: notif.actions ? notif.actions.map(a => ({
identifier: a.identifier,
@ -73,14 +86,13 @@ QtObject {
invoke: () => a.invoke()
})) : [],
time: Date.now(),
popup: !root.dnd,
closed: false,
notification: notif
};
popup: isCritical || !root.dnd
});
root.list = [data, ...root.list];
root._byId[item.id] = item;
root.list = [item, ...root.list];
// Dismiss excess popups (keep in history, just hide popup)
// Trim excess popups
const max = M.Modules.notifications.maxPopups || 4;
const currentPopups = root.list.filter(n => n.popup);
if (currentPopups.length > max) {
@ -89,38 +101,33 @@ QtObject {
root._changed();
}
// Auto-expire popup
if (data.popup) {
// Auto-expire popup (skip for critical)
if (item.popup && !isCritical) {
const timeout = notif.expireTimeout > 0 ? notif.expireTimeout : (M.Modules.notifications.timeout || 3000);
Qt.callLater(() => {
_expireTimer.createObject(root, {
_notifId: data.id,
interval: timeout
});
});
item._expireTimer.interval = timeout;
item._expireTimer.running = true;
}
// Trim history
const maxHistory = M.Modules.notifications.maxHistory || 50;
while (root.list.length > maxHistory) {
const old = root.list.pop();
old.finishDismiss();
delete root._byId[old.id];
old.destroy();
}
}
}
// Signal popups to animate out before removal
signal popupExpiring(var notifId)
property Component _expireTimer: Component {
Timer {
property var _notifId
running: true
onTriggered: {
root.popupExpiring(_notifId);
destroy();
}
}
property Component _itemComp: Component {
NotifItem {}
}
// Persistence
property Timer _saveTimer: Timer {
interval: 1000
onTriggered: {
const data = root.list.map(n => ({
const data = root.list.filter(n => n.state !== "dismissed").map(n => ({
id: n.id,
summary: n.summary,
body: n.body,
@ -139,16 +146,25 @@ QtObject {
onLoaded: {
try {
const data = JSON.parse(text());
for (let i = 0; i < data.length; i++) {
const maxHistory = M.Modules.notifications.maxHistory || 50;
for (let i = 0; i < Math.min(data.length, maxHistory); i++) {
const n = data[i];
// Prefix persisted IDs to avoid collision with live D-Bus IDs
n.id = "p_" + (n.id ?? i) + "_" + n.time;
n.popup = false;
n.closed = false;
n.notification = null;
n.actions = [];
const item = _itemComp.createObject(root, {
id: "p_" + (n.id ?? i) + "_" + n.time,
summary: n.summary || "",
body: n.body || "",
appName: n.appName || "",
appIcon: n.appIcon || "",
image: n.image || "",
urgency: n.urgency ?? 1,
time: n.time || Date.now(),
popup: false,
actions: []
});
root._byId[item.id] = item;
root.list.push(item);
}
root.list = data.concat(root.list);
root._changed();
} catch (e) {}
}
}