feat: sql database for notifications
This commit is contained in:
+15
-261
@@ -4,14 +4,11 @@ pragma ComponentBehavior: Bound
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
import ZShell
|
||||
import ZShell.Services
|
||||
import qs.Components
|
||||
import qs.Modules
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
import qs.Paths
|
||||
import qs.Services
|
||||
import ZShell.Config
|
||||
|
||||
Singleton {
|
||||
@@ -19,11 +16,9 @@ Singleton {
|
||||
|
||||
readonly property var appCooldownMap: new Map()
|
||||
property alias dnd: props.dnd
|
||||
property list<Notif> list: []
|
||||
property bool loaded
|
||||
readonly property list<Notif> notClosed: list.filter(n => !n.closed)
|
||||
readonly property list<Notif> popups: list.filter(n => n.popup)
|
||||
property alias server: server
|
||||
property alias list: store.rows
|
||||
readonly property list<NotificationRow> notClosed: store.rows.filter(n => !n.closed)
|
||||
readonly property list<NotificationRow> popups: store.rows.filter(n => n.popup)
|
||||
|
||||
function shouldThrottle(appName: string): bool {
|
||||
if (props.dnd || ShellState.anySidebarOpen())
|
||||
@@ -46,33 +41,6 @@ Singleton {
|
||||
return true;
|
||||
}
|
||||
|
||||
onListChanged: {
|
||||
if (loaded) {
|
||||
saveTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: saveTimer
|
||||
|
||||
interval: 1000
|
||||
|
||||
onTriggered: storage.setText(JSON.stringify(root.notClosed.map(n => ({
|
||||
time: n.time,
|
||||
id: n.id,
|
||||
summary: n.summary,
|
||||
body: n.body,
|
||||
appIcon: n.appIcon,
|
||||
appName: n.appName,
|
||||
image: n.image,
|
||||
expireTimeout: n.expireTimeout,
|
||||
urgency: n.urgency,
|
||||
resident: n.resident,
|
||||
hasActionIcons: n.hasActionIcons,
|
||||
actions: n.actions
|
||||
}))))
|
||||
}
|
||||
|
||||
PersistentProperties {
|
||||
id: props
|
||||
|
||||
@@ -81,6 +49,12 @@ Singleton {
|
||||
reloadableId: "notifs"
|
||||
}
|
||||
|
||||
NotificationStore {
|
||||
id: store
|
||||
|
||||
path: `${Paths.state}/notifs.sqlite`
|
||||
}
|
||||
|
||||
NotificationServer {
|
||||
id: server
|
||||
|
||||
@@ -89,40 +63,12 @@ Singleton {
|
||||
bodyImagesSupported: true
|
||||
bodyMarkupSupported: true
|
||||
imageSupported: true
|
||||
keepOnReload: false
|
||||
keepOnReload: true
|
||||
persistenceSupported: true
|
||||
|
||||
onNotification: notif => {
|
||||
notif.tracked = true;
|
||||
|
||||
const is_popup = root.shouldThrottle(notif.appName);
|
||||
|
||||
const comp = notifComp.createObject(root, {
|
||||
popup: is_popup,
|
||||
notification: notif
|
||||
});
|
||||
root.list = [comp, ...root.list];
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: storage
|
||||
|
||||
path: `${Paths.state}/notifs.json`
|
||||
printErrors: false
|
||||
|
||||
onLoadFailed: err => {
|
||||
if (err === FileViewError.FileNotFound) {
|
||||
root.loaded = true;
|
||||
Qt.callLater(() => setText("[]"));
|
||||
}
|
||||
}
|
||||
onLoaded: {
|
||||
const data = JSON.parse(text());
|
||||
for (const notif of data)
|
||||
root.list.push(notifComp.createObject(root, notif));
|
||||
root.list.sort((a, b) => b.time - a.time);
|
||||
root.loaded = true;
|
||||
store.insert(notif, root.shouldThrottle(notif.appName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,16 +76,12 @@ Singleton {
|
||||
description: "Clear all notifications"
|
||||
name: "clearnotifs"
|
||||
|
||||
onPressed: {
|
||||
for (const notif of root.list.slice())
|
||||
notif.close();
|
||||
}
|
||||
onPressed: store.clear()
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function clear(): void {
|
||||
for (const notif of root.list.slice())
|
||||
notif.close();
|
||||
store.clear();
|
||||
}
|
||||
|
||||
function disableDnd(): void {
|
||||
@@ -160,192 +102,4 @@ Singleton {
|
||||
|
||||
target: "notifs"
|
||||
}
|
||||
|
||||
Component {
|
||||
id: notifComp
|
||||
|
||||
Notif {
|
||||
}
|
||||
}
|
||||
|
||||
component Notif: QtObject {
|
||||
id: notif
|
||||
|
||||
property list<var> actions
|
||||
property string appIcon
|
||||
property string appName
|
||||
property string body
|
||||
property string cachedImageSource: ""
|
||||
property bool cachingImage: false
|
||||
property bool closed
|
||||
readonly property Connections conn: Connections {
|
||||
function onActionsChanged(): void {
|
||||
notif.actions = notif.notification.actions.map(a => ({
|
||||
identifier: a.identifier,
|
||||
text: a.text,
|
||||
invoke: () => a.invoke()
|
||||
}));
|
||||
}
|
||||
|
||||
function onAppIconChanged(): void {
|
||||
notif.appIcon = notif.notification.appIcon;
|
||||
}
|
||||
|
||||
function onAppNameChanged(): void {
|
||||
notif.appName = notif.notification.appName;
|
||||
}
|
||||
|
||||
function onBodyChanged(): void {
|
||||
notif.body = notif.notification.body;
|
||||
}
|
||||
|
||||
function onClosed(): void {
|
||||
notif.close();
|
||||
}
|
||||
|
||||
function onExpireTimeoutChanged(): void {
|
||||
notif.expireTimeout = notif.notification.expireTimeout;
|
||||
}
|
||||
|
||||
function onHasActionIconsChanged(): void {
|
||||
notif.hasActionIcons = notif.notification.hasActionIcons;
|
||||
}
|
||||
|
||||
function onHintsChanged(): void {
|
||||
notif.hints = notif.notification.hints;
|
||||
}
|
||||
|
||||
function onImageChanged(): void {
|
||||
notif.imageSource = notif.notification.image || "";
|
||||
notif.image = notif.imageSource;
|
||||
notif.cacheImageIfNeeded();
|
||||
}
|
||||
|
||||
function onResidentChanged(): void {
|
||||
notif.resident = notif.notification.resident;
|
||||
}
|
||||
|
||||
function onSummaryChanged(): void {
|
||||
notif.summary = notif.notification.summary;
|
||||
}
|
||||
|
||||
function onUrgencyChanged(): void {
|
||||
notif.urgency = notif.notification.urgency;
|
||||
}
|
||||
|
||||
target: notif.notification
|
||||
}
|
||||
property real expireTimeout: 5
|
||||
property bool hasActionIcons
|
||||
property var hints
|
||||
property string image
|
||||
property string imageSource
|
||||
property var locks: new Set()
|
||||
property string notifId
|
||||
property Notification notification
|
||||
property bool popup
|
||||
property bool resident
|
||||
property string summary
|
||||
property date time: new Date()
|
||||
readonly property string timeStr: {
|
||||
const diff = Time.date.getTime() - time.getTime();
|
||||
const m = Math.floor(diff / 60000);
|
||||
|
||||
if (m < 1)
|
||||
return qsTr("now");
|
||||
|
||||
const h = Math.floor(m / 60);
|
||||
const d = Math.floor(h / 24);
|
||||
|
||||
if (d > 0)
|
||||
return `${d}d`;
|
||||
if (h > 0)
|
||||
return `${h}h`;
|
||||
return `${m}m`;
|
||||
}
|
||||
readonly property Timer timer: Timer {
|
||||
property bool paused: false
|
||||
property int remainingTime: totalTime
|
||||
property int totalTime: Config.notifs.defaultExpireTimeout
|
||||
|
||||
interval: 50
|
||||
repeat: true
|
||||
running: !paused
|
||||
|
||||
onTriggered: {
|
||||
remainingTime -= interval;
|
||||
|
||||
if (remainingTime <= 0) {
|
||||
remainingTime = 0;
|
||||
notif.popup = false;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
property int urgency: NotificationUrgency.Normal
|
||||
|
||||
function cacheImageIfNeeded(): void {
|
||||
const source = imageSource;
|
||||
|
||||
if (!source || cachingImage)
|
||||
return;
|
||||
|
||||
if (cachedImageSource === source)
|
||||
return;
|
||||
|
||||
cachingImage = true;
|
||||
|
||||
ZShellIo.cacheImage(Qt.resolvedUrl(source), Paths.notifimagecache, (path, url) => {
|
||||
cachedImageSource = source;
|
||||
image = path;
|
||||
cachingImage = false;
|
||||
}, () => {
|
||||
cachingImage = false;
|
||||
});
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
closed = true;
|
||||
if (locks.size === 0 && root.list.includes(this)) {
|
||||
root.list = root.list.filter(n => n !== this);
|
||||
notification?.dismiss();
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
function lock(item: Item): void {
|
||||
locks.add(item);
|
||||
}
|
||||
|
||||
function unlock(item: Item): void {
|
||||
locks.delete(item);
|
||||
if (closed)
|
||||
close();
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!notification)
|
||||
return;
|
||||
|
||||
notifId = notification.id;
|
||||
summary = notification.summary;
|
||||
body = notification.body;
|
||||
appIcon = notification.appIcon;
|
||||
appName = notification.appName;
|
||||
imageSource = notification.image || "";
|
||||
image = imageSource;
|
||||
hints = notification.hints;
|
||||
expireTimeout = notification.expireTimeout;
|
||||
urgency = notification.urgency;
|
||||
resident = notification.resident;
|
||||
hasActionIcons = notification.hasActionIcons;
|
||||
actions = notification.actions.map(a => ({
|
||||
identifier: a.identifier,
|
||||
text: a.text,
|
||||
invoke: () => a.invoke()
|
||||
}));
|
||||
|
||||
cacheImageIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import Quickshell.Widgets
|
||||
import Quickshell.Services.Notifications
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Services
|
||||
import qs.Modules
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
@@ -277,7 +278,7 @@ CustomRect {
|
||||
component NotifLine: CustomText {
|
||||
id: notifLine
|
||||
|
||||
required property NotifServer.Notif modelData
|
||||
required property NotificationRow modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: root.urgency === "critical" ? Colors.palette.m3onSecondaryContainer : Colors.palette.m3onSurface
|
||||
|
||||
@@ -5,6 +5,7 @@ import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import ZShell.Config
|
||||
import ZShell.Components
|
||||
import ZShell.Services
|
||||
import qs.Components
|
||||
import qs.Daemons
|
||||
import qs.Helpers
|
||||
@@ -97,7 +98,7 @@ Item {
|
||||
|
||||
property int idx
|
||||
required property int index
|
||||
required property NotifServer.Notif modelData
|
||||
required property NotificationRow modelData
|
||||
readonly property alias nonAnimHeight: notif.nonAnimHeight
|
||||
|
||||
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : Tokens.spacing.small)
|
||||
|
||||
@@ -5,7 +5,7 @@ import QtQuick.Shapes
|
||||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import ZShell.Components
|
||||
import qs.Daemons
|
||||
import ZShell.Services
|
||||
import qs.Helpers
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
@@ -17,7 +17,7 @@ CustomRect {
|
||||
property bool expanded: Config.notifs.openExpanded
|
||||
readonly property bool hasAppIcon: modelData.appIcon.length > 0
|
||||
readonly property bool hasImage: modelData.image.length > 0
|
||||
required property NotifServer.Notif modelData
|
||||
required property NotificationRow modelData
|
||||
readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? Tokens.spacing.extraSmall * 2 + appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2
|
||||
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? Colors.palette.m3secondaryContainer : Colors.tPalette.m3surfaceContainer
|
||||
@@ -56,10 +56,10 @@ CustomRect {
|
||||
if (actions.length === 1)
|
||||
actions[0].invoke();
|
||||
}
|
||||
onEntered: root.modelData.timer.stop()
|
||||
onEntered: root.modelData.pausePopup()
|
||||
onExited: {
|
||||
if (!pressed)
|
||||
root.modelData.timer.start();
|
||||
root.modelData.resumePopup();
|
||||
}
|
||||
onPositionChanged: event => {
|
||||
if (pressed) {
|
||||
@@ -69,14 +69,14 @@ CustomRect {
|
||||
}
|
||||
}
|
||||
onPressed: event => {
|
||||
root.modelData.timer.stop();
|
||||
root.modelData.pausePopup();
|
||||
startY = event.y;
|
||||
if (event.button === Qt.MiddleButton)
|
||||
root.modelData.close();
|
||||
}
|
||||
onReleased: event => {
|
||||
if (!containsMouse)
|
||||
root.modelData.timer.start();
|
||||
root.modelData.resumePopup();
|
||||
|
||||
if (Math.abs(root.x) < root.implicitWidth * Config.notifs.clearThreshold)
|
||||
root.x = 0;
|
||||
@@ -200,7 +200,7 @@ CustomRect {
|
||||
radiusX: progressIndicator.width / 2 - Tokens.padding.extraSmall / 2
|
||||
radiusY: progressIndicator.height / 2 - Tokens.padding.extraSmall / 2
|
||||
startAngle: -90
|
||||
sweepAngle: ((root.modelData.hints.value ?? 0) / 100) * 360
|
||||
sweepAngle: (root.modelData.progress / 100) * 360
|
||||
|
||||
Behavior on sweepAngle {
|
||||
Anim {
|
||||
@@ -477,7 +477,12 @@ CustomRect {
|
||||
shapeMorph: true
|
||||
text: modelData.text
|
||||
|
||||
onClicked: modelData.invoke()
|
||||
onClicked: {
|
||||
if (modelData.invokable)
|
||||
modelData.invoke();
|
||||
else if (!root.modelData.resident)
|
||||
root.modelData.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,16 +4,17 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import ZShell.Config
|
||||
import ZShell.Components
|
||||
import ZShell.Services
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
import qs.Daemons
|
||||
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
readonly property CustomText body: (expandedContent.item as ExpandedBody)?.body ?? null
|
||||
required property bool expanded
|
||||
required property NotifServer.Notif modelData
|
||||
required property NotificationRow modelData
|
||||
readonly property real nonAnimHeight: expanded ? summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + Tokens.padding.medium * 2 : summaryHeightMetrics.height
|
||||
required property ScreenState screenState
|
||||
|
||||
|
||||
@@ -5,15 +5,15 @@ import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import ZShell.Config
|
||||
import ZShell.Services
|
||||
import qs.Components
|
||||
import qs.Effects
|
||||
import qs.Services
|
||||
import qs.Daemons
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property NotifServer.Notif notif
|
||||
required property NotificationRow notif
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: flickable.contentHeight
|
||||
@@ -36,7 +36,7 @@ Item {
|
||||
{
|
||||
isClose: true
|
||||
},
|
||||
...(root.notif?.actions ?? []),
|
||||
...Array.from(root.notif?.actions ?? []),
|
||||
{
|
||||
isCopy: true
|
||||
}
|
||||
@@ -84,7 +84,7 @@ Item {
|
||||
Quickshell.clipboardText = root.notif.body;
|
||||
actionInner.item.text = "inventory";
|
||||
copyTimer.start();
|
||||
} else if (action.modelData.invoke) {
|
||||
} else if (action.modelData.invokable) {
|
||||
action.modelData.invoke();
|
||||
} else if (!root.notif.resident) {
|
||||
root.notif.close();
|
||||
|
||||
@@ -81,9 +81,8 @@ Item {
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
sourceComponent: SnapLayout {
|
||||
sourceComponent: ColumnLayout {
|
||||
spacing: 20
|
||||
orientation: Qt.Vertical
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
@@ -3,8 +3,8 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import ZShell.Components
|
||||
import ZShell.Services
|
||||
import ZShell.Config
|
||||
import qs.Daemons
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
@@ -37,7 +37,7 @@ LazyListView {
|
||||
id: notif
|
||||
|
||||
required property int index
|
||||
required property NotifServer.Notif modelData
|
||||
required property NotificationRow modelData
|
||||
property int startY
|
||||
|
||||
LazyListView.preferredHeight: modelData?.closed || LazyListView.removing ? 0 : notifInner.nonAnimHeight
|
||||
|
||||
@@ -23,11 +23,14 @@ qml_module(ZShell-services
|
||||
lyriccandidate.hpp lyriccandidate.cpp
|
||||
lyrics.hpp lyrics.cpp
|
||||
dropimporter.hpp dropimporter.cpp
|
||||
notificationstore.hpp notificationstore.cpp
|
||||
LIBRARIES
|
||||
Qt6::Core
|
||||
Qt6::Qml
|
||||
Qt6::Quick
|
||||
Qt6::Concurrent
|
||||
Qt6::Network
|
||||
Qt6::Sql
|
||||
PkgConfig::Pipewire
|
||||
PkgConfig::Aubio
|
||||
PkgConfig::Cava
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,248 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QJSValue>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <qqmlintegration.h>
|
||||
#include <QQmlListProperty>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
namespace ZShell::config {
|
||||
class Notifs;
|
||||
}
|
||||
|
||||
namespace ZShell::Services {
|
||||
|
||||
class NotificationStore;
|
||||
class NotifImageProvider;
|
||||
|
||||
class NotificationActionRow : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE(
|
||||
"NotificationActionRow instances come from a NotificationStore")
|
||||
|
||||
Q_PROPERTY(bool invokable READ invokable NOTIFY invokableChanged)
|
||||
Q_PROPERTY(QString identifier READ identifier CONSTANT)
|
||||
Q_PROPERTY(QString text READ text CONSTANT)
|
||||
|
||||
public:
|
||||
NotificationActionRow(
|
||||
QObject* action,
|
||||
const QString& identifier,
|
||||
const QString& text,
|
||||
QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool invokable() const;
|
||||
[[nodiscard]] QString identifier() const;
|
||||
[[nodiscard]] QString text() const;
|
||||
|
||||
Q_INVOKABLE void invoke();
|
||||
|
||||
signals:
|
||||
void invokableChanged();
|
||||
|
||||
private:
|
||||
QPointer<QObject> m_action;
|
||||
QString m_identifier;
|
||||
QString m_text;
|
||||
};
|
||||
|
||||
class NotificationRow : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("NotificationRow instances come from a NotificationStore")
|
||||
|
||||
Q_PROPERTY(QQmlListProperty<NotificationActionRow> actions READ actions NOTIFY actionsChanged)
|
||||
Q_PROPERTY(QString appIcon READ appIcon NOTIFY appIconChanged)
|
||||
Q_PROPERTY(QString appName READ appName NOTIFY appNameChanged)
|
||||
Q_PROPERTY(QString body READ body NOTIFY bodyChanged)
|
||||
Q_PROPERTY(bool closed READ closed NOTIFY closedChanged)
|
||||
Q_PROPERTY(bool hasActionIcons READ hasActionIcons NOTIFY hasActionIconsChanged)
|
||||
Q_PROPERTY(QString image READ image NOTIFY imageChanged)
|
||||
Q_PROPERTY(QString imageSource READ imageSource NOTIFY imageSourceChanged)
|
||||
Q_PROPERTY(int notificationId READ notificationId CONSTANT)
|
||||
Q_PROPERTY(int progress READ progress NOTIFY progressChanged)
|
||||
Q_PROPERTY(bool popup READ popup WRITE setPopup NOTIFY popupChanged)
|
||||
Q_PROPERTY(bool resident READ resident NOTIFY residentChanged)
|
||||
Q_PROPERTY(QString summary READ summary NOTIFY summaryChanged)
|
||||
Q_PROPERTY(QDateTime time READ time CONSTANT)
|
||||
Q_PROPERTY(QString timeStr READ timeStr NOTIFY timeStrChanged)
|
||||
Q_PROPERTY(int urgency READ urgency NOTIFY urgencyChanged)
|
||||
|
||||
public:
|
||||
explicit NotificationRow(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QQmlListProperty<NotificationActionRow> actions();
|
||||
[[nodiscard]] QString appIcon() const;
|
||||
[[nodiscard]] QString appName() const;
|
||||
[[nodiscard]] QString body() const;
|
||||
[[nodiscard]] bool closed() const;
|
||||
[[nodiscard]] bool hasActionIcons() const;
|
||||
[[nodiscard]] QString image() const;
|
||||
[[nodiscard]] QString imageSource() const;
|
||||
[[nodiscard]] int notificationId() const;
|
||||
[[nodiscard]] int progress() const;
|
||||
[[nodiscard]] bool popup() const;
|
||||
void setPopup(bool popup);
|
||||
[[nodiscard]] bool resident() const;
|
||||
[[nodiscard]] QString summary() const;
|
||||
[[nodiscard]] QDateTime time() const;
|
||||
[[nodiscard]] QString timeStr() const;
|
||||
[[nodiscard]] int urgency() const;
|
||||
|
||||
Q_INVOKABLE void close();
|
||||
Q_INVOKABLE void lock(QObject* item);
|
||||
Q_INVOKABLE void pausePopup();
|
||||
Q_INVOKABLE void resumePopup();
|
||||
Q_INVOKABLE void unlock(QObject* item);
|
||||
|
||||
signals:
|
||||
void actionsChanged();
|
||||
void appIconChanged();
|
||||
void appNameChanged();
|
||||
void bodyChanged();
|
||||
void closedChanged();
|
||||
void hasActionIconsChanged();
|
||||
void imageChanged();
|
||||
void imageSourceChanged();
|
||||
void popupChanged();
|
||||
void progressChanged();
|
||||
void residentChanged();
|
||||
void summaryChanged();
|
||||
void timeStrChanged();
|
||||
void urgencyChanged();
|
||||
|
||||
private:
|
||||
friend NotificationStore;
|
||||
|
||||
[[nodiscard]] bool expired(qint64 now) const;
|
||||
void refreshTimeStr();
|
||||
|
||||
QList<NotificationActionRow*> m_actions;
|
||||
NotificationStore* m_store = nullptr;
|
||||
|
||||
QString m_appIcon;
|
||||
QString m_appName;
|
||||
QString m_body;
|
||||
QString m_digest;
|
||||
QString m_image;
|
||||
QString m_imageSource;
|
||||
QString m_summary;
|
||||
QString m_timeStr;
|
||||
|
||||
qint64 m_deadline = 0;
|
||||
qint64 m_rowId = -1;
|
||||
qint64 m_timeMs = 0;
|
||||
|
||||
QPointer<QObject> m_notification;
|
||||
QSet<QObject*> m_locks;
|
||||
QJSValue m_jsNotification;
|
||||
|
||||
bool m_closed = false;
|
||||
bool m_hasActionIcons = false;
|
||||
bool m_liveConnected = false;
|
||||
bool m_popup = false;
|
||||
bool m_popupPaused = false;
|
||||
bool m_resident = false;
|
||||
|
||||
int m_notificationId = 0;
|
||||
int m_progress = 0;
|
||||
int m_urgency = 1;
|
||||
};
|
||||
|
||||
class NotificationStore : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(bool autoExpire READ autoExpire WRITE setAutoExpire NOTIFY autoExpireChanged)
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
|
||||
Q_PROPERTY(int popupTimeout READ popupTimeout WRITE setPopupTimeout NOTIFY
|
||||
popupTimeoutChanged)
|
||||
Q_PROPERTY(QQmlListProperty<NotificationRow> rows READ rows NOTIFY rowsChanged)
|
||||
|
||||
public:
|
||||
explicit NotificationStore(QObject* parent = nullptr);
|
||||
~NotificationStore() override;
|
||||
|
||||
[[nodiscard]] QString path() const;
|
||||
void setPath(const QString& path);
|
||||
|
||||
[[nodiscard]] bool autoExpire() const;
|
||||
void setAutoExpire(bool autoExpire);
|
||||
|
||||
[[nodiscard]] int popupTimeout() const;
|
||||
void setPopupTimeout(int timeout);
|
||||
|
||||
[[nodiscard]] QQmlListProperty<NotificationRow> rows();
|
||||
|
||||
Q_INVOKABLE NotificationRow* insert(const QJSValue& notification, bool popup);
|
||||
Q_INVOKABLE void clear();
|
||||
|
||||
signals:
|
||||
void autoExpireChanged();
|
||||
void pathChanged();
|
||||
void popupTimeoutChanged();
|
||||
void rowsChanged();
|
||||
|
||||
private slots:
|
||||
void handleLiveUpdate();
|
||||
|
||||
private:
|
||||
friend NotificationRow;
|
||||
|
||||
void attachNotification(
|
||||
NotificationRow* row, QObject* notification, const QJSValue& js);
|
||||
void applyNotifConfig(const config::Notifs* notifs);
|
||||
void closeDb();
|
||||
void copyFields(NotificationRow* row, QObject* notification);
|
||||
void handleClosed(NotificationRow* row);
|
||||
void insertDbRow(NotificationRow* row);
|
||||
void loadRows();
|
||||
void migrateLegacyJson();
|
||||
[[nodiscard]] bool openDb();
|
||||
void orphanCleanup();
|
||||
void persistPopup(const NotificationRow* row);
|
||||
void prune();
|
||||
void registerProvider();
|
||||
void removeRow(NotificationRow* row);
|
||||
// Coalesced rowsChanged: a burst of inserts/removes triggers one QML
|
||||
// rebind on the next event loop turn instead of one per notification.
|
||||
void scheduleRowsChanged();
|
||||
[[nodiscard]] NotificationRow* rowForNotificationId(int id) const;
|
||||
void startImageCache(NotificationRow* row);
|
||||
void storeImageBlob(
|
||||
const QPointer<NotificationRow>& row,
|
||||
const QString& digest,
|
||||
const QByteArray& png);
|
||||
void tick();
|
||||
void trimRows();
|
||||
void updateDbRow(NotificationRow* row);
|
||||
|
||||
static QList<NotificationActionRow*> parseActions(
|
||||
const QJSValue& actions, NotificationRow* row);
|
||||
|
||||
int m_popupTimeout = 5000;
|
||||
|
||||
qint64 m_nextPrune = 0;
|
||||
qint64 m_nextTimeStrRefresh = 0;
|
||||
bool m_autoExpire = true;
|
||||
bool m_clearing = false;
|
||||
bool m_rowsSignalPending = false;
|
||||
|
||||
QString m_uuid;
|
||||
QString m_path;
|
||||
|
||||
QList<NotificationRow*> m_rows;
|
||||
QHash<QObject*, NotificationRow*> m_liveRows;
|
||||
|
||||
NotifImageProvider* m_provider = nullptr;
|
||||
QTimer* m_tick = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ZShell::Services
|
||||
Reference in New Issue
Block a user