Push Rule Model Rework

This is a significant rework of the handling of push rules. Rather than using a lot of boilerplate code for the default models `KeywordNotificationModel` has been converted to `PushRuleModel` and now handles all push rules.

The new model has the following features:
- Handles all push rules
- Has special handling for the names of default keywords (i.e. it still gives the same text as previously for showing in the settings menus)
- Push rules for blocking individuals or room overrides are still there but hidden so will be available for developer tools (to follow)
- Room specific keywords are now supported.

The notification settings pages have also been refactored to take advantage of the new models. Each section is now just a repeater with a filter for the rules that it should contain. The push rule delegate has now been cleaned up and uses required properties.

Implements network/neochat#574
This commit is contained in:
James Graham
2023-07-10 16:17:17 +00:00
parent a6ce44eb24
commit 7bd84bf51e
15 changed files with 899 additions and 867 deletions

View File

@@ -7,10 +7,12 @@ import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
import org.kde.kitemmodels 1.0
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
id: root
property NeoChatRoom room
@@ -62,5 +64,86 @@ Kirigami.ScrollablePage {
}
}
}
MobileForm.FormCard {
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0
MobileForm.FormCardHeader {
title: i18n("Keywords")
}
Repeater {
model: KSortFilterProxyModel {
sourceModel: Controller.pushRuleModel
filterRowCallback: function(source_row, source_parent) {
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
let roomIdRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.RoomIdRole)
return sectionRole == PushNotificationSection.RoomKeywords && roomIdRole == root.room.id;
}
}
delegate: ruleDelegate
}
MobileForm.AbstractFormDelegate {
Layout.fillWidth: true
contentItem : RowLayout {
Kirigami.ActionTextField {
id: keywordAddField
Layout.fillWidth: true
placeholderText: i18n("Keyword…")
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown
rightActions: Kirigami.Action {
icon.name: "edit-clear"
visible: keywordAddField.text.length > 0
onTriggered: {
keywordAddField.text = ""
}
}
onAccepted: {
Controller.pushRuleModel.addKeyword(keywordAddField.text, root.room.id)
keywordAddField.text = ""
}
}
QQC2.Button {
id: addButton
text: i18n("Add keyword")
Accessible.name: text
icon.name: "list-add"
display: QQC2.AbstractButton.IconOnly
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown
onClicked: {
Controller.pushRuleModel.addKeyword(keywordAddField.text, root.room.id)
keywordAddField.text = ""
}
QQC2.ToolTip {
text: addButton.text
delay: Kirigami.Units.toolTipDelay
}
}
}
}
}
}
}
Component {
id: ruleDelegate
NotificationRuleItem {
onDeleteRule: {
Controller.pushRuleModel.removeKeyword(id)
}
onActionChanged: (action) => Controller.pushRuleModel.setPushRuleAction(id, action)
}
}
}

View File

@@ -7,13 +7,17 @@ import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
import org.kde.kitemmodels 1.0
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
id: root
title: i18nc("@title:window", "Notifications")
leftPadding: 0
rightPadding: 0
ColumnLayout {
id: notificationLayout
@@ -22,10 +26,10 @@ Kirigami.ScrollablePage {
contentItem: MobileForm.FormCheckDelegate {
text: i18n("Enable notifications for this account")
description: i18n("Whether push notifications are generated by your Matrix server")
checked: NotificationsManager.globalNotificationsEnabled
enabled: NotificationsManager.globalNotificationsSet
checked: Controller.pushRuleModel.globalNotificationsEnabled
enabled: Controller.pushRuleModel.globalNotificationsSet
onToggled: {
NotificationsManager.globalNotificationsEnabled = checked
Controller.pushRuleModel.globalNotificationsEnabled = checked
}
}
}
@@ -39,81 +43,17 @@ Kirigami.ScrollablePage {
MobileForm.FormCardHeader {
title: i18n("Room Notifications")
}
NotificationRuleItem {
text: i18n("Messages in one-to-one chats")
Repeater {
model: KSortFilterProxyModel {
sourceModel: Controller.pushRuleModel
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.oneToOneNotificationAction)
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.oneToOneNotificationAction)
enabled: NotificationsManager.oneToOneNotificationAction !== PushNotificationAction.Unknown
notificationAction: NotificationsManager.oneToOneNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.oneToOneNotificationAction != notificationAction) {
NotificationsManager.oneToOneNotificationAction = notificationAction
filterRowCallback: function(source_row, source_parent) {
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
return sectionRole == PushNotificationSection.Room;
}
}
}
NotificationRuleItem {
text: i18n("Encrypted messages in one-to-one chats")
visible: Controller.encryptionSupported
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.encryptedOneToOneNotificationAction)
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.encryptedOneToOneNotificationAction)
enabled: NotificationsManager.encryptedOneToOneNotificationAction !== PushNotificationAction.Unknown
notificationAction: NotificationsManager.encryptedOneToOneNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.encryptedOneToOneNotificationAction != notificationAction) {
NotificationsManager.encryptedOneToOneNotificationAction = notificationAction
}
}
}
NotificationRuleItem {
text: i18n("Messages in group chats")
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.groupChatNotificationAction)
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.groupChatNotificationAction)
enabled: NotificationsManager.groupChatNotificationAction !== PushNotificationAction.Unknown
notificationAction: NotificationsManager.groupChatNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.groupChatNotificationAction != notificationAction) {
NotificationsManager.groupChatNotificationAction = notificationAction
}
}
}
NotificationRuleItem {
text: i18n("Messages in encrypted group chats")
visible: Controller.encryptionSupported
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.encryptedGroupChatNotificationAction)
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.encryptedGroupChatNotificationAction)
enabled: NotificationsManager.encryptedGroupChatNotificationAction !== PushNotificationAction.Unknown
notificationAction: NotificationsManager.encryptedGroupChatNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.encryptedGroupChatNotificationAction != notificationAction) {
NotificationsManager.encryptedGroupChatNotificationAction = notificationAction
}
}
}
NotificationRuleItem {
text: i18n("Room upgrade messages")
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.tombstoneNotificationAction)
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.tombstoneNotificationAction)
highlightable: true
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.tombstoneNotificationAction)
enabled: NotificationsManager.tombstoneNotificationAction !== PushNotificationAction.Unknown
notificationAction: NotificationsManager.tombstoneNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.tombstoneNotificationAction != notificationAction) {
NotificationsManager.tombstoneNotificationAction = notificationAction
}
}
delegate: ruleDelegate
}
}
}
@@ -127,37 +67,17 @@ Kirigami.ScrollablePage {
MobileForm.FormCardHeader {
title: i18n("@Mentions")
}
NotificationRuleItem {
text: i18n("Messages containing my display name")
Repeater {
model: KSortFilterProxyModel {
sourceModel: Controller.pushRuleModel
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.displayNameNotificationAction)
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.displayNameNotificationAction)
highlightable: true
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.displayNameNotificationAction)
enabled: NotificationsManager.displayNameNotificationAction !== PushNotificationAction.Unknown
notificationAction: NotificationsManager.displayNameNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.displayNameNotificationAction != notificationAction) {
NotificationsManager.displayNameNotificationAction = notificationAction
filterRowCallback: function(source_row, source_parent) {
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
return sectionRole == PushNotificationSection.Mentions;
}
}
}
NotificationRuleItem {
text: i18n("Whole room (@room) notifications")
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.roomNotificationAction)
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.roomNotificationAction)
highlightable: true
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.roomNotificationAction)
enabled: NotificationsManager.roomNotificationAction !== PushNotificationAction.Unknown
notificationAction: NotificationsManager.roomNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.roomNotificationAction != notificationAction) {
NotificationsManager.roomNotificationAction = notificationAction
}
}
delegate: ruleDelegate
}
}
}
@@ -171,47 +91,17 @@ Kirigami.ScrollablePage {
MobileForm.FormCardHeader {
title: i18n("Keywords")
}
NotificationRuleItem {
id: keywordNotificationAction
text: i18n("Messages containing my keywords")
notificationsOn: true
notificationsOnModifiable: false
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.keywordNotificationAction)
highlightable: true
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.keywordNotificationAction)
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown &&
NotificationsManager.keywordNotificationAction !== PushNotificationAction.Off
notificationAction: NotificationsManager.keywordNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.keywordNotificationAction != notificationAction) {
NotificationsManager.keywordNotificationAction = notificationAction
}
}
}
MobileForm.FormDelegateSeparator {}
Repeater {
model: KeywordNotificationRuleModel {
id: keywordNotificationRuleModel
}
model: KSortFilterProxyModel {
sourceModel: Controller.pushRuleModel
delegate: NotificationRuleItem {
text: name
notificationAction: keywordNotificationAction.notificationAction
notificationsOn: keywordNotificationAction.notificationsOn
notificationsOnModifiable: false
noisyOn: keywordNotificationAction.noisyOn
noisyModifiable: false
highlightOn: keywordNotificationAction.highlightOn
deletable: true
onDeleteItemChanged: {
if (deleteItem && deletable) {
keywordNotificationRuleModel.removeKeywordAtIndex(index)
}
filterRowCallback: function(source_row, source_parent) {
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
return sectionRole == PushNotificationSection.Keywords;
}
}
delegate: ruleDelegate
}
MobileForm.AbstractFormDelegate {
Layout.fillWidth: true
@@ -234,7 +124,7 @@ Kirigami.ScrollablePage {
}
onAccepted: {
keywordNotificationRuleModel.addKeyword(keywordAddField.text, PushNotificationAction.On)
Controller.pushRuleModel.addKeyword(keywordAddField.text)
keywordAddField.text = ""
}
}
@@ -248,7 +138,7 @@ Kirigami.ScrollablePage {
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown
onClicked: {
keywordNotificationRuleModel.addKeyword(keywordAddField.text, PushNotificationAction.On)
Controller.pushRuleModel.addKeyword(keywordAddField.text)
keywordAddField.text = ""
}
@@ -271,59 +161,29 @@ Kirigami.ScrollablePage {
MobileForm.FormCardHeader {
title: i18n("Invites")
}
NotificationRuleItem {
text: i18n("Invites to a room")
Repeater {
model: KSortFilterProxyModel {
sourceModel: Controller.pushRuleModel
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.inviteNotificationAction)
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.inviteNotificationAction)
highlightable: true
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.inviteNotificationAction)
enabled: NotificationsManager.inviteNotificationAction !== PushNotificationAction.Unknown
notificationAction: NotificationsManager.inviteNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.inviteNotificationAction != notificationAction) {
NotificationsManager.inviteNotificationAction = notificationAction
filterRowCallback: function(source_row, source_parent) {
let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
return sectionRole == PushNotificationSection.Invites;
}
}
}
NotificationRuleItem {
text: i18n("Call invitation")
// TODO enable this option when calls are supported
visible: false
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.callInviteNotificationAction)
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.callInviteNotificationAction)
highlightable: true
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.callInviteNotificationAction)
enabled: NotificationsManager.callInviteNotificationAction !== PushNotificationAction.Unknown
notificationAction: NotificationsManager.callInviteNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.callInviteNotificationAction != notificationAction) {
NotificationsManager.callInviteNotificationAction = notificationAction
}
}
delegate: ruleDelegate
}
}
}
}
function isNotificationRuleOn(action) {
return action == PushNotificationAction.On ||
action == PushNotificationAction.Noisy ||
action == PushNotificationAction.Highlight ||
action == PushNotificationAction.NoisyHighlight
}
function isNotificationRuleNoisy(action) {
return action == PushNotificationAction.Noisy ||
action == PushNotificationAction.NoisyHighlight
}
function isNotificationRuleHighlight(action) {
return action == PushNotificationAction.Highlight ||
action == PushNotificationAction.NoisyHighlight
Component {
id: ruleDelegate
NotificationRuleItem {
onDeleteRule: {
Controller.pushRuleModel.removeKeyword(id)
}
onActionChanged: (action) => Controller.pushRuleModel.setPushRuleAction(id, action)
}
}
}

View File

@@ -11,19 +11,25 @@ import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
import org.kde.neochat 1.0
MobileForm.AbstractFormDelegate {
id: notificationRuleItem
id: root
property var notificationAction: PushNotificationAction.Unkown
property bool notificationsOn: false
property bool notificationsOnModifiable: true
property bool noisyOn: false
property bool noisyModifiable: true
property bool highlightOn: false
property bool highlightable: false
property bool deleteItem: false
property bool deletable: false
required property string id
required property string name
required property int ruleAction
required property bool highlightable
required property bool deletable
readonly property bool notificationsOn: isNotificationRuleOn(ruleAction)
readonly property bool notificationsOnModifiable: !deletable
readonly property bool highlightOn: isNotificationRuleHighlight(ruleAction)
signal actionChanged(int action)
signal deleteRule()
Layout.fillWidth: true
enabled: ruleAction !== PushNotificationAction.Unknown
text: name
onClicked: {
notificationAction = nextNotificationRuleAction(notificationAction)
@@ -42,8 +48,8 @@ MobileForm.AbstractFormDelegate {
background: Rectangle {
visible: notificationsOn
Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: highlightOn ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
opacity: highlightOn ? 1 : 0.3
color: highlightOn && highlightable ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
opacity: highlightOn && highlightable ? 1 : 0.3
radius: height / 2
}
}
@@ -51,7 +57,7 @@ MobileForm.AbstractFormDelegate {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
text: notificationRuleItem.text
text: root.text
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
@@ -67,13 +73,13 @@ MobileForm.AbstractFormDelegate {
icon.name: checked ? "notifications" : "notifications-disabled"
display: QQC2.AbstractButton.IconOnly
visible: notificationRuleItem.notificationsOnModifiable
visible: root.notificationsOnModifiable
checkable: true
checked: notificationRuleItem.notificationsOn
enabled: notificationRuleItem.enabled
checked: root.notificationsOn
enabled: root.enabled
down: checked
onToggled: {
notificationRuleItem.notificationAction = notificationRuleItem.notifcationRuleAction()
root.actionChanged(root.notifcationRuleAction())
}
QQC2.ToolTip {
@@ -89,13 +95,12 @@ MobileForm.AbstractFormDelegate {
icon.name: checked ? "audio-volume-high" : "audio-volume-muted"
display: QQC2.AbstractButton.IconOnly
visible: notificationRuleItem.noisyModifiable
checkable: true
checked: notificationRuleItem.noisyOn
enabled: (onButton.checked || !notificationRuleItem.notificationsOnModifiable) && notificationRuleItem.enabled
checked: isNotificationRuleNoisy(root.ruleAction)
enabled: (onButton.checked || !root.notificationsOnModifiable) && root.enabled
down: checked
onToggled: {
notificationRuleItem.notificationAction = notificationRuleItem.notifcationRuleAction()
root.actionChanged(root.notifcationRuleAction())
}
QQC2.ToolTip {
@@ -111,13 +116,13 @@ MobileForm.AbstractFormDelegate {
icon.name: "draw-highlight"
display: QQC2.AbstractButton.IconOnly
visible: notificationRuleItem.highlightable
visible: root.highlightable
checkable: true
checked: notificationRuleItem.highlightOn
enabled: (onButton.checked || !notificationRuleItem.notificationsOnModifiable) && notificationRuleItem.enabled
checked: root.highlightOn
enabled: (onButton.checked || !root.notificationsOnModifiable) && root.enabled
down: checked
onToggled: {
notificationRuleItem.notificationAction = notificationRuleItem.notifcationRuleAction()
root.actionChanged(root.notifcationRuleAction())
}
QQC2.ToolTip {
@@ -131,10 +136,10 @@ MobileForm.AbstractFormDelegate {
Accessible.name: i18n("Delete keyword")
icon.name: "edit-delete-remove"
visible: notificationRuleItem.deletable
visible: root.deletable
onClicked: {
notificationRuleItem.deleteItem = !notificationRuleItem.deleteItem
root.deleteRule()
}
}
}
@@ -142,11 +147,11 @@ MobileForm.AbstractFormDelegate {
function notifcationRuleAction() {
if (onButton.checked) {
if (noisyButton.checked && highlightButton.checked) {
if (noisyButton.checked && highlightButton.checked && root.highlightable) {
return PushNotificationAction.NoisyHighlight
} else if (noisyButton.checked) {
return PushNotificationAction.Noisy
} else if (highlightButton.checked) {
} else if (highlightButton.checked && root.highlightable) {
return PushNotificationAction.Highlight
} else {
return PushNotificationAction.On
@@ -166,17 +171,34 @@ MobileForm.AbstractFormDelegate {
}
while (!finished) {
if (action == PushNotificationAction.Off && !notificationRuleItem.notificationsOnModifiable) {
if (action == PushNotificationAction.Off && !root.notificationsOnModifiable) {
action = PushNotificationAction.On
} else if (action == PushNotificationAction.Noisy && !notificationRuleItem.noisyModifiable) {
} else if (action == PushNotificationAction.Noisy) {
action = PushNotificationAction.Highlight
} else if (action == PushNotificationAction.Highlight && !notificationRuleItem.highlightable) {
} else if (action == PushNotificationAction.Highlight && !root.highlightable) {
action = PushNotificationAction.Off
} else {
finished = true
}
}
return action
actionChanged(action)
}
function isNotificationRuleOn(action) {
return action == PushNotificationAction.On ||
action == PushNotificationAction.Noisy ||
action == PushNotificationAction.Highlight ||
action == PushNotificationAction.NoisyHighlight
}
function isNotificationRuleNoisy(action) {
return action == PushNotificationAction.Noisy ||
action == PushNotificationAction.NoisyHighlight
}
function isNotificationRuleHighlight(action) {
return action == PushNotificationAction.Highlight ||
action == PushNotificationAction.NoisyHighlight
}
}