[network/neochat] src: Overhaul notification controls, expose KDE settings where possible
Joshua Goins <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 50735786638760b3dd45760a5ff65b118ec7ee72 by Joshua Goins.
Committed on 26/07/2026 at 16:04.
Pushed by redstrate into branch 'master'.
Overhaul notification controls, expose KDE settings where possible
There are a few problems with our notification settings, setting aside
the complex push rule system and it's UX.
First of all, we lacked a switch to disable notifications for your
current device and *not* the entire account. This is something that's
expected from other Matrix clients and makes sense here too. To
distinguish between the account and device switches, I re-arranged them
so the global switch is in the header. The notification controls now
disappear when you toggle either option since it adds nothing but
clutter.
Another problem we had is that the system notification controls are
undiscoverable. For example, I incorrectly informed a user there was no
setting to turn the task manager badge off for NeoChat. I was wrong,
such a setting is exposed in the Notifications KCM! I added a button to
do this in KDE sessions until the KNotifications API is merged.
The same problem is going on with push notifications too, now that
KUnifiedPush is being deployed in more distributions. It looks like the
UnifiedPush spec lacks a way to open settings universally from the
client side, so I hard-coded our Push Notifications KCM for now.
BUG: 512523
M +4 -0 src/app/notificationsmanager.cpp
M +18 -0 src/libneochat/neochatconnection.cpp
M +11 -0 src/libneochat/neochatconnection.h
M +122 -25 src/settings/GlobalNotificationsPage.qml
https://invent.kde.org/network/neochat/-/commit/50735786638760b3dd45760a5ff65b118ec7ee72
diff --git a/src/app/notificationsmanager.cpp b/src/app/notificationsmanager.cpp
index 84a28cbc4..8847a8a66 100644
--- a/src/app/notificationsmanager.cpp
+++ b/src/app/notificationsmanager.cpp
@@ -41,6 +41,10 @@ NotificationsManager::NotificationsManager(QObject *parent)
void NotificationsManager::handleNotifications(const QPointer<NeoChatConnection> &connection)
{
+ if (!connection->enableDeviceNotifications()) {
+ return;
+ }
+
if (KNotificationPermission::checkPermission() == Qt::PermissionStatus::Granted) {
startNotificationJob(connection);
} else if (!permissionAsked) {
diff --git a/src/libneochat/neochatconnection.cpp b/src/libneochat/neochatconnection.cpp
index dc32a66fc..e69ae4b2d 100644
--- a/src/libneochat/neochatconnection.cpp
+++ b/src/libneochat/neochatconnection.cpp
@@ -730,6 +730,24 @@ void NeoChatConnection::setProfileField(const QString &key, const QString &value
m_profileFields[key] = value;
}
+bool NeoChatConnection::enableDeviceNotifications() const
+{
+ const Quotient::AccountSettings account{userId()};
+ return account.get(QStringLiteral("enable_notifications"), true);
+}
+
+void NeoChatConnection::setEnableDeviceNotifications(bool enable)
+{
+ Quotient::AccountSettings account{userId()};
+ account.setValue(QStringLiteral("enable_notifications"), enable);
+ Q_EMIT enableDeviceNotificationsChanged();
+}
+
+bool NeoChatConnection::inKDESession() const
+{
+ return qgetenv("XDG_SESSION_DESKTOP") == "KDE";
+}
+
bool NeoChatConnection::supportsProfileFields() const
{
return m_supportsProfileFields;
diff --git a/src/libneochat/neochatconnection.h b/src/libneochat/neochatconnection.h
index f1a284eea..09f785ac8 100644
--- a/src/libneochat/neochatconnection.h
+++ b/src/libneochat/neochatconnection.h
@@ -103,6 +103,11 @@ class NeoChatConnection : public Quotient::Connection
Q_PROPERTY(bool initialSyncDone MEMBER m_syncDone NOTIFY initialSyncDoneChanged)
+ /**
+ * @brief Whether this device should process and display notifications.
+ */
+ Q_PROPERTY(bool enableDeviceNotifications READ enableDeviceNotifications WRITE setEnableDeviceNotifications NOTIFY enableDeviceNotificationsChanged)
+
public:
/**
* @brief Defines the status after an attempt to change the password on an account.
@@ -266,6 +271,11 @@ public:
*/
Q_INVOKABLE void setProfileField(const QString &key, const QString &value);
+ [[nodiscard]] bool enableDeviceNotifications() const;
+ void setEnableDeviceNotifications(bool enable);
+
+ [[nodiscard]] Q_INVOKABLE bool inKDESession() const;
+
Q_SIGNALS:
void globalUrlPreviewEnabledChanged();
void identityServerChanged();
@@ -310,6 +320,7 @@ Q_SIGNALS:
void keyBackupError();
void blockAllInvitesChanged();
void initialSyncDoneChanged();
+ void enableDeviceNotificationsChanged();
private:
static bool m_globalUrlPreviewDefault;
diff --git a/src/settings/GlobalNotificationsPage.qml b/src/settings/GlobalNotificationsPage.qml
index a505aef2a..a2049456d 100644
--- a/src/settings/GlobalNotificationsPage.qml
+++ b/src/settings/GlobalNotificationsPage.qml
@@ -20,35 +20,122 @@ FormCard.FormCardPage {
title: i18nc("@title:window", "Notifications")
- property PushRuleModel pushRuleModel: PushRuleModel {
+ readonly property PushRuleModel pushRuleModel: PushRuleModel {
connection: root.connection
}
+
+ actions: [
+ Kirigami.Action {
+ displayComponent: QQC2.Switch {
+ text: i18nc("@option:check Whether notifications are enabled for this account", "Enabled for this account")
+ checkable: true
+ checked: root.pushRuleModel.globalNotificationsEnabled
+ enabled: root.pushRuleModel.globalNotificationsSet
+ onToggled: root.pushRuleModel.globalNotificationsEnabled = checked
+ }
+ }
+ ]
+
+ background: Item {
+ Kirigami.PlaceholderMessage {
+ icon.name: "notifications"
+ text: i18nc("@info:placeholder", "Notifications Disabled")
+ visible: !root.pushRuleModel.globalNotificationsEnabled
+
+ anchors.centerIn: parent
+ }
+ }
FormCard.FormCard {
+ visible: root.pushRuleModel.globalNotificationsEnabled
+
Layout.topMargin: Kirigami.Units.largeSpacing * 4
- FormCard.FormCheckDelegate {
- text: i18n("Enable notifications for this account")
- description: {
- if (root.connection.pushNotificationsAvailable) {
- if (root.connection.enablePushNotifications) {
- return i18n("Notifications can appear even when NeoChat isn't running.");
- } else {
- return i18n("Push notifications are available, but the push distributor does not have a Matrix gateway.");
+
+ FormCard.FormSwitchDelegate {
+ id: enableDeviceNotificationsDelegate
+
+ text: i18nc("@option:check", "Enable notifications for this device")
+ checked: root.connection.enableDeviceNotifications
+ onToggled: root.connection.enableDeviceNotifications = checked
+ }
+ FormCard.FormDelegateSeparator {
+ visible: root.connection.enableDeviceNotifications
+ above: enableDeviceNotificationsDelegate
+ below: configureSystemNotificationsDelegate
+ }
+ FormCard.FormButtonDelegate {
+ id: configureSystemNotificationsDelegate
+
+ icon.name: "configure-symbolic"
+ text: i18nc("@action:button", "Configure System Notifications…")
+ visible: root.connection.enableDeviceNotifications && root.connection.inKDESession()
+
+ // TODO: replace with proper KNotifications API in the future and when we can depend on it!
+ onClicked: Qt.openUrlExternally("systemsettings:/kcm_notifications/--notifyrc neochat")
+ }
+ }
+
+ FormCard.FormHeader {
+ title: i18nc("@title:group Background/push notifications", "Background Notifications")
+ visible: root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications && root.connection.pushNotificationsAvailable
+ }
+ FormCard.FormCard {
+ visible: root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications && root.connection.pushNotificationsAvailable
+
+ FormCard.AbstractFormDelegate {
+ id: pushNotificationsStatusDelegate
+
+ contentItem: RowLayout {
+ spacing: Kirigami.Units.largeSpacing
+ Kirigami.Icon {
+ source: {
+ if (root.connection.pushNotificationsAvailable && !root.connection.enablePushNotifications) {
+ "data-warning"
+ } else {
+ "data-information"
+ }
}
- } else {
- return i18n("Notifications will only appear when NeoChat is running.");
}
- }
- checked: root.pushRuleModel.globalNotificationsEnabled
- enabled: root.pushRuleModel.globalNotificationsSet
- onToggled: {
- root.pushRuleModel.globalNotificationsEnabled = checked;
+ QQC2.Label {
+ text: {
+ if (root.connection.pushNotificationsAvailable) {
+ if (root.connection.enablePushNotifications) {
+ return i18nc("@info:label", "Notifications can appear even when NeoChat isn't running.");
+ } else {
+ return i18nc("@info:label", "The configured push distributor does not have a Matrix gateway.");
+ }
+ }
+ return ""; // this section would be hidden anyway!
+ }
+ wrapMode: Text.WordWrap
+
+ Layout.fillWidth: true
+ }
}
}
+ FormCard.FormDelegateSeparator {
+ above: pushNotificationsStatusDelegate
+ below: backgroundNotificationsDelegate
+ visible: root.connection.inKDESession()
+ }
+ FormCard.FormButtonDelegate {
+ id: backgroundNotificationsDelegate
+
+ icon.name: "configure-symbolic"
+ text: i18nc("@action:button", "Configure Background Notifications…")
+ visible: root.connection.inKDESession()
+
+ onClicked: Qt.openUrlExternally("systemsettings:/kcm_push_notifications")
+ }
}
+ FormCard.FormHeader {
+ title: i18nc("@title:group", "Room Notifications")
+ visible: root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications
+ }
FormCard.FormCard {
- Layout.topMargin: Kirigami.Units.largeSpacing
+ visible: root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications
+
FormCard.AbstractFormDelegate {
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
@@ -64,11 +151,11 @@ FormCard.FormCardPage {
}
}
}
-
- FormCard.FormHeader {
- title: i18nc("@title:group", "Room Notifications")
- }
FormCard.FormCard {
+ visible: root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications
+
+ Layout.topMargin: Kirigami.Units.largeSpacing
+
Repeater {
model: KSortFilterProxyModel {
sourceModel: root.pushRuleModel
@@ -83,9 +170,12 @@ FormCard.FormCardPage {
}
FormCard.FormHeader {
- title: i18nc("@title:group", "@Mentions")
+ title: i18nc("@title:group Mention notifications", "Mentions")
+ visible: root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications
}
FormCard.FormCard {
+ visible: root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications
+
Repeater {
model: KSortFilterProxyModel {
sourceModel: root.pushRuleModel
@@ -101,8 +191,11 @@ FormCard.FormCardPage {
FormCard.FormHeader {
title: i18nc("@title:group", "Keywords")
+ visible: root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications
}
FormCard.FormCard {
+ visible: root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications
+
Repeater {
model: KSortFilterProxyModel {
sourceModel: root.pushRuleModel
@@ -115,6 +208,7 @@ FormCard.FormCardPage {
delegate: root.ruleDelegate
}
+
FormCard.AbstractFormDelegate {
Layout.fillWidth: true
@@ -165,8 +259,11 @@ FormCard.FormCardPage {
FormCard.FormHeader {
title: i18nc("@title:group", "Invites")
+ visible: root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications
}
FormCard.FormCard {
+ visible: root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications
+
Repeater {
model: KSortFilterProxyModel {
sourceModel: root.pushRuleModel
@@ -182,10 +279,10 @@ FormCard.FormCardPage {
FormCard.FormHeader {
title: i18nc("@title:group", "Unknown")
- visible: unknownModel.rowCount() > 0
+ visible: unknownModel.rowCount() > 0 && root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications
}
FormCard.FormCard {
- visible: unknownModel.rowCount() > 0
+ visible: unknownModel.rowCount() > 0 && root.pushRuleModel.globalNotificationsEnabled && root.connection.enableDeviceNotifications
Repeater {
model: KSortFilterProxyModel {
@@ -201,7 +298,7 @@ FormCard.FormCardPage {
}
}
- property Component ruleDelegate: Component {
+ readonly property Component ruleDelegate: Component {
NotificationRuleItem {
onDeleteRule: {
root.pushRuleModel.removeKeyword(id);