refactor: move nested Notifs.Notif type to NotifData

This commit is contained in:
2 * r + 2 * t 2026-03-20 22:34:08 +11:00
parent 8b64d38e26
commit e88bbe425f
10 changed files with 230 additions and 226 deletions

View file

@ -2,7 +2,6 @@ import "../components"
import qs.components import qs.components
import qs.components.controls import qs.components.controls
import qs.config import qs.config
import qs.services
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts

View file

@ -5,7 +5,6 @@ import "../components"
import qs.components import qs.components
import qs.components.controls import qs.components.controls
import qs.config import qs.config
import qs.services
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts

View file

@ -306,7 +306,7 @@ StyledRect {
component NotifLine: StyledText { component NotifLine: StyledText {
id: notifLine id: notifLine
required property Notifs.Notif modelData required property NotifData modelData
Layout.fillWidth: true Layout.fillWidth: true
textFormat: Text.MarkdownText textFormat: Text.MarkdownText

View file

@ -68,7 +68,7 @@ Item {
delegate: Item { delegate: Item {
id: wrapper id: wrapper
required property Notifs.Notif modelData required property NotifData modelData
required property int index required property int index
readonly property alias nonAnimHeight: notif.nonAnimHeight readonly property alias nonAnimHeight: notif.nonAnimHeight
property int idx property int idx

View file

@ -15,7 +15,7 @@ import QtQuick.Shapes
StyledRect { StyledRect {
id: root id: root
required property Notifs.Notif modelData required property NotifData modelData
readonly property bool hasImage: modelData.image.length > 0 readonly property bool hasImage: modelData.image.length > 0
readonly property bool hasAppIcon: modelData.appIcon.length > 0 readonly property bool hasAppIcon: modelData.appIcon.length > 0
readonly property int bodyTextFormat: /[<*_`#\[\]]/.test(modelData.body) ? Text.MarkdownText : Text.PlainText readonly property int bodyTextFormat: /[<*_`#\[\]]/.test(modelData.body) ? Text.MarkdownText : Text.PlainText

View file

@ -10,7 +10,7 @@ import QtQuick.Layouts
StyledRect { StyledRect {
id: root id: root
required property Notifs.Notif modelData required property NotifData modelData
required property Props props required property Props props
required property bool expanded required property bool expanded
required property var visibilities required property var visibilities

View file

@ -13,7 +13,7 @@ import QtQuick.Layouts
Item { Item {
id: root id: root
required property Notifs.Notif notif required property NotifData notif
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: flickable.contentHeight implicitHeight: flickable.contentHeight

View file

@ -63,7 +63,7 @@ Item {
id: notif id: notif
required property int index required property int index
required property Notifs.Notif modelData required property NotifData modelData
readonly property alias nonAnimHeight: notifInner.nonAnimHeight readonly property alias nonAnimHeight: notifInner.nonAnimHeight
readonly property bool previewHidden: { readonly property bool previewHidden: {

220
services/NotifData.qml Normal file
View file

@ -0,0 +1,220 @@
import qs.config
import qs.utils
import Caelestia
import Quickshell
import Quickshell.Services.Notifications
import QtQuick
QtObject {
id: notif
property bool popup
property bool closed
property var locks: new Set()
property date time: new Date()
property string timeStr: qsTr("now")
readonly property Timer timeStrTimer: Timer {
running: !notif.closed
repeat: true
interval: 5000
onTriggered: notif.updateTimeStr()
}
property Notification notification
property string id
property string summary
property string body
property string appIcon
property string appName
property string image
property var hints // Hints are not persisted across restarts
property real expireTimeout: Config.notifs.defaultExpireTimeout
property int urgency: NotificationUrgency.Normal
property bool resident
property bool hasActionIcons
property list<var> actions
readonly property Timer timer: Timer {
running: true
interval: notif.expireTimeout > 0 ? notif.expireTimeout : Config.notifs.defaultExpireTimeout
onTriggered: {
if (Config.notifs.expire)
notif.popup = false;
}
}
readonly property LazyLoader dummyImageLoader: LazyLoader {
active: false
PanelWindow {
implicitWidth: Config.notifs.sizes.image
implicitHeight: Config.notifs.sizes.image
color: "transparent"
mask: Region {}
Image {
function tryCache(): void {
if (status !== Image.Ready || width != Config.notifs.sizes.image || height != Config.notifs.sizes.image)
return;
const cacheKey = notif.appName + notif.summary + notif.id;
let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch;
for (let i = 0; i < cacheKey.length; i++) {
ch = cacheKey.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
const hash = (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0);
const cache = `${Paths.notifimagecache}/${hash}.png`;
CUtils.saveItem(this, Qt.resolvedUrl(cache), () => {
notif.image = cache;
notif.dummyImageLoader.active = false;
});
}
anchors.fill: parent
source: Qt.resolvedUrl(notif.image)
fillMode: Image.PreserveAspectCrop
cache: false
asynchronous: true
opacity: 0
onStatusChanged: tryCache()
onWidthChanged: tryCache()
onHeightChanged: tryCache()
}
}
}
readonly property Connections conn: Connections {
function onClosed(): void {
notif.close();
}
function onSummaryChanged(): void {
notif.summary = notif.notification.summary;
}
function onBodyChanged(): void {
notif.body = notif.notification.body;
}
function onAppIconChanged(): void {
notif.appIcon = notif.notification.appIcon;
}
function onAppNameChanged(): void {
notif.appName = notif.notification.appName;
}
function onImageChanged(): void {
notif.image = notif.notification.image;
if (notif.notification?.image)
notif.dummyImageLoader.active = true;
}
function onExpireTimeoutChanged(): void {
notif.expireTimeout = notif.notification.expireTimeout;
}
function onUrgencyChanged(): void {
notif.urgency = notif.notification.urgency;
}
function onResidentChanged(): void {
notif.resident = notif.notification.resident;
}
function onHasActionIconsChanged(): void {
notif.hasActionIcons = notif.notification.hasActionIcons;
}
function onActionsChanged(): void {
notif.actions = notif.notification.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
}));
}
function onHintsChanged(): void {
notif.hints = notif.notification.hints;
}
target: notif.notification
}
function updateTimeStr(): void {
const diff = Date.now() - time.getTime();
const m = Math.floor(diff / 60000);
if (m < 1) {
timeStr = qsTr("now");
timeStrTimer.interval = 5000;
} else {
const h = Math.floor(m / 60);
const d = Math.floor(h / 24);
if (d > 0) {
timeStr = `${d}d`;
timeStrTimer.interval = 3600000;
} else if (h > 0) {
timeStr = `${h}h`;
timeStrTimer.interval = 300000;
} else {
timeStr = `${m}m`;
timeStrTimer.interval = m < 10 ? 30000 : 60000;
}
}
}
function lock(item: Item): void {
locks.add(item);
}
function unlock(item: Item): void {
locks.delete(item);
if (closed)
close();
}
function close(): void {
closed = true;
if (locks.size === 0 && Notifs.list.includes(this)) {
Notifs.list = Notifs.list.filter(n => n !== this);
notification?.dismiss();
destroy();
}
}
Component.onCompleted: {
if (!notification)
return;
id = notification.id;
summary = notification.summary;
body = notification.body;
appIcon = notification.appIcon;
appName = notification.appName;
image = notification.image;
if (notification?.image)
dummyImageLoader.active = true;
expireTimeout = notification.expireTimeout;
hints = notification.hints;
urgency = notification.urgency;
resident = notification.resident;
hasActionIcons = notification.hasActionIcons;
actions = notification.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
}));
}
}

View file

@ -14,9 +14,9 @@ import QtQuick
Singleton { Singleton {
id: root id: root
property list<Notif> list: [] property list<NotifData> list: []
readonly property list<Notif> notClosed: list.filter(n => !n.closed) readonly property list<NotifData> notClosed: list.filter(n => !n.closed)
readonly property list<Notif> popups: list.filter(n => n.popup) readonly property list<NotifData> popups: list.filter(n => n.popup)
property alias dnd: props.dnd property alias dnd: props.dnd
property bool loaded property bool loaded
@ -139,223 +139,9 @@ Singleton {
target: "notifs" target: "notifs"
} }
component Notif: QtObject {
id: notif
property bool popup
property bool closed
property var locks: new Set()
property date time: new Date()
property string timeStr: qsTr("now")
readonly property Timer timeStrTimer: Timer {
running: !notif.closed
repeat: true
interval: 5000
onTriggered: notif.updateTimeStr()
}
property Notification notification
property string id
property string summary
property string body
property string appIcon
property string appName
property string image
property var hints // Hints are not persisted across restarts
property real expireTimeout: Config.notifs.defaultExpireTimeout
property int urgency: NotificationUrgency.Normal
property bool resident
property bool hasActionIcons
property list<var> actions
readonly property Timer timer: Timer {
running: true
interval: notif.expireTimeout > 0 ? notif.expireTimeout : Config.notifs.defaultExpireTimeout
onTriggered: {
if (Config.notifs.expire)
notif.popup = false;
}
}
readonly property LazyLoader dummyImageLoader: LazyLoader {
active: false
PanelWindow {
implicitWidth: Config.notifs.sizes.image
implicitHeight: Config.notifs.sizes.image
color: "transparent"
mask: Region {}
Image {
function tryCache(): void {
if (status !== Image.Ready || width != Config.notifs.sizes.image || height != Config.notifs.sizes.image)
return;
const cacheKey = notif.appName + notif.summary + notif.id;
let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch;
for (let i = 0; i < cacheKey.length; i++) {
ch = cacheKey.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
const hash = (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0);
const cache = `${Paths.notifimagecache}/${hash}.png`;
CUtils.saveItem(this, Qt.resolvedUrl(cache), () => {
notif.image = cache;
notif.dummyImageLoader.active = false;
});
}
anchors.fill: parent
source: Qt.resolvedUrl(notif.image)
fillMode: Image.PreserveAspectCrop
cache: false
asynchronous: true
opacity: 0
onStatusChanged: tryCache()
onWidthChanged: tryCache()
onHeightChanged: tryCache()
}
}
}
readonly property Connections conn: Connections {
function onClosed(): void {
notif.close();
}
function onSummaryChanged(): void {
notif.summary = notif.notification.summary;
}
function onBodyChanged(): void {
notif.body = notif.notification.body;
}
function onAppIconChanged(): void {
notif.appIcon = notif.notification.appIcon;
}
function onAppNameChanged(): void {
notif.appName = notif.notification.appName;
}
function onImageChanged(): void {
notif.image = notif.notification.image;
if (notif.notification?.image)
notif.dummyImageLoader.active = true;
}
function onExpireTimeoutChanged(): void {
notif.expireTimeout = notif.notification.expireTimeout;
}
function onUrgencyChanged(): void {
notif.urgency = notif.notification.urgency;
}
function onResidentChanged(): void {
notif.resident = notif.notification.resident;
}
function onHasActionIconsChanged(): void {
notif.hasActionIcons = notif.notification.hasActionIcons;
}
function onActionsChanged(): void {
notif.actions = notif.notification.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
}));
}
function onHintsChanged(): void {
notif.hints = notif.notification.hints;
}
target: notif.notification
}
function updateTimeStr(): void {
const diff = Date.now() - time.getTime();
const m = Math.floor(diff / 60000);
if (m < 1) {
timeStr = qsTr("now");
timeStrTimer.interval = 5000;
} else {
const h = Math.floor(m / 60);
const d = Math.floor(h / 24);
if (d > 0) {
timeStr = `${d}d`;
timeStrTimer.interval = 3600000;
} else if (h > 0) {
timeStr = `${h}h`;
timeStrTimer.interval = 300000;
} else {
timeStr = `${m}m`;
timeStrTimer.interval = m < 10 ? 30000 : 60000;
}
}
}
function lock(item: Item): void {
locks.add(item);
}
function unlock(item: Item): void {
locks.delete(item);
if (closed)
close();
}
function close(): void {
closed = true;
if (locks.size === 0 && root.list.includes(this)) {
root.list = root.list.filter(n => n !== this);
notification?.dismiss();
destroy();
}
}
Component.onCompleted: {
if (!notification)
return;
id = notification.id;
summary = notification.summary;
body = notification.body;
appIcon = notification.appIcon;
appName = notification.appName;
image = notification.image;
if (notification?.image)
dummyImageLoader.active = true;
expireTimeout = notification.expireTimeout;
hints = notification.hints;
urgency = notification.urgency;
resident = notification.resident;
hasActionIcons = notification.hasActionIcons;
actions = notification.actions.map(a => ({
identifier: a.identifier,
text: a.text,
invoke: () => a.invoke()
}));
}
}
Component { Component {
id: notifComp id: notifComp
Notif {} NotifData {}
} }
} }