#pragma once #include #include #include #include #include #include #include #include #include #include #include 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 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 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 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 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 m_notification; QSet 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 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 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& row, const QString& digest, const QByteArray& png); void tick(); void trimRows(); void updateDbRow(NotificationRow* row); static QList 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 m_rows; QHash m_liveRows; NotifImageProvider* m_provider = nullptr; QTimer* m_tick = nullptr; }; } // namespace ZShell::Services