move NotifService and NotifItem to services/, keep notification UI in modules/
This commit is contained in:
parent
a17a365b81
commit
d20fdf8fa0
8 changed files with 33 additions and 36 deletions
64
shell/services/NotifItem.qml
Normal file
64
shell/services/NotifItem.qml
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import QtQuick
|
||||
import Quickshell.Services.Notifications
|
||||
import "." as S
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property bool popup: false
|
||||
property string state: "visible" // "visible" | "dismissing" | "dismissed"
|
||||
|
||||
property var notification: null
|
||||
property var id
|
||||
property string summary
|
||||
property string body
|
||||
property string appName
|
||||
property string appIcon
|
||||
property string image
|
||||
property var hints
|
||||
property int urgency: NotificationUrgency.Normal
|
||||
property var actions: []
|
||||
property real time: Date.now()
|
||||
|
||||
// Expire timer — owned by this item, not dynamically created
|
||||
readonly property Timer _expireTimer: Timer {
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (root.state === "visible")
|
||||
root.popup = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Relative time string — recomputed whenever NotifService._now ticks (single global 5s timer)
|
||||
readonly property string timeStr: {
|
||||
const diff = S.NotifService._now - time;
|
||||
const m = Math.floor(diff / 60000);
|
||||
if (m < 1)
|
||||
return "now";
|
||||
const h = Math.floor(m / 60);
|
||||
if (h < 1)
|
||||
return m + "m";
|
||||
const d = Math.floor(h / 24);
|
||||
return d > 0 ? d + "d" : h + "h";
|
||||
}
|
||||
|
||||
// App closed the notification from its side — remove from our list while the object is still alive
|
||||
readonly property Connections _notifConn: Connections {
|
||||
target: root.notification
|
||||
function onClosed() {
|
||||
if (root.state !== "dismissed")
|
||||
S.NotifService.dismiss(root.id);
|
||||
}
|
||||
}
|
||||
|
||||
function beginDismiss() {
|
||||
if (state === "visible")
|
||||
state = "dismissing";
|
||||
}
|
||||
|
||||
function finishDismiss() {
|
||||
state = "dismissed";
|
||||
_expireTimer.running = false;
|
||||
notification?.dismiss();
|
||||
}
|
||||
}
|
||||
137
shell/services/NotifService.qml
Normal file
137
shell/services/NotifService.qml
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import "." as S
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property var list: []
|
||||
property bool dnd: false
|
||||
|
||||
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 item = _byId[notifId];
|
||||
if (!item)
|
||||
return;
|
||||
item.finishDismiss();
|
||||
list = list.filter(n => n !== item);
|
||||
delete _byId[notifId];
|
||||
item.destroy();
|
||||
}
|
||||
|
||||
function dismissAll() {
|
||||
for (const item of list.slice()) {
|
||||
item.finishDismiss();
|
||||
delete _byId[item.id];
|
||||
item.destroy();
|
||||
}
|
||||
list = [];
|
||||
}
|
||||
|
||||
function dismissPopup(notifId) {
|
||||
const item = _byId[notifId];
|
||||
if (item) {
|
||||
item.popup = false;
|
||||
_changed();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDnd() {
|
||||
dnd = !dnd;
|
||||
}
|
||||
|
||||
function _changed() {
|
||||
list = list.slice();
|
||||
}
|
||||
|
||||
// Signal popups to animate out before removal
|
||||
signal popupExpiring(var notifId)
|
||||
|
||||
property NotificationServer _server: NotificationServer {
|
||||
actionsSupported: true
|
||||
bodyMarkupSupported: true
|
||||
imageSupported: true
|
||||
persistenceSupported: true
|
||||
keepOnReload: false
|
||||
|
||||
onNotification: notif => {
|
||||
notif.tracked = true;
|
||||
|
||||
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,
|
||||
text: a.text,
|
||||
invoke: () => a.invoke()
|
||||
})) : [],
|
||||
time: Date.now(),
|
||||
popup: isCritical || !root.dnd
|
||||
});
|
||||
|
||||
root._byId[item.id] = item;
|
||||
root.list = [item, ...root.list].sort((a, b) => {
|
||||
const aU = a.urgency === NotificationUrgency.Critical ? 1 : 0;
|
||||
const bU = b.urgency === NotificationUrgency.Critical ? 1 : 0;
|
||||
if (aU !== bU)
|
||||
return bU - aU;
|
||||
return b.time - a.time;
|
||||
});
|
||||
|
||||
// Trim excess popups
|
||||
const max = S.Modules.notifications.maxPopups || 4;
|
||||
const currentPopups = root.list.filter(n => n.popup);
|
||||
if (currentPopups.length > max) {
|
||||
for (let i = max; i < currentPopups.length; i++)
|
||||
currentPopups[i].popup = false;
|
||||
root._changed();
|
||||
}
|
||||
|
||||
// Auto-expire popup (skip for critical)
|
||||
if (item.popup && !isCritical) {
|
||||
const timeout = notif.expireTimeout > 0 ? notif.expireTimeout : (S.Modules.notifications.timeout || 3000);
|
||||
item._expireTimer.interval = timeout;
|
||||
item._expireTimer.running = true;
|
||||
}
|
||||
|
||||
// Trim history (-1 = unlimited)
|
||||
const maxHistory = S.Modules.notifications.maxHistory ?? -1;
|
||||
while (maxHistory > 0 && root.list.length > maxHistory) {
|
||||
const old = root.list.pop();
|
||||
old.finishDismiss();
|
||||
delete root._byId[old.id];
|
||||
old.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Component _itemComp: Component {
|
||||
NotifItem {}
|
||||
}
|
||||
|
||||
// Single global tick for all NotifItem.timeStr bindings — replaces per-item 5s timers
|
||||
property real _now: Date.now()
|
||||
property Timer _nowTimer: Timer {
|
||||
running: root.count > 0
|
||||
repeat: true
|
||||
interval: 5000
|
||||
onTriggered: root._now = Date.now()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,3 +4,5 @@ singleton SystemStats 1.0 SystemStats.qml
|
|||
singleton Modules 1.0 Modules.qml
|
||||
singleton NiriIpc 1.0 NiriIpc.qml
|
||||
singleton PowerProfileService 1.0 PowerProfileService.qml
|
||||
singleton NotifService 1.0 NotifService.qml
|
||||
NotifItem 1.0 NotifItem.qml
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue