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

@@ -24,7 +24,7 @@ add_library(neochat STATIC
models/userfiltermodel.cpp
models/publicroomlistmodel.cpp
models/userdirectorylistmodel.cpp
models/keywordnotificationrulemodel.cpp
models/pushrulemodel.cpp
models/emoticonfiltermodel.cpp
notificationsmanager.cpp
models/sortfilterroomlistmodel.cpp

View File

@@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "controller.h"
#include "models/pushrulemodel.h"
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <qt5keychain/keychain.h>
@@ -126,6 +127,10 @@ Controller::Controller(QObject *parent)
oldAccountCount = Accounts.size();
});
#endif
QTimer::singleShot(0, this, [this] {
m_pushRuleModel = new PushRuleModel;
});
}
Controller &Controller::instance()
@@ -507,6 +512,11 @@ void Controller::setActiveConnection(Connection *connection)
Q_EMIT activeAccountLabelChanged();
}
PushRuleModel *Controller::pushRuleModel() const
{
return m_pushRuleModel;
}
void Controller::saveWindowGeometry()
{
WindowController::instance().saveGeometry();

View File

@@ -3,6 +3,7 @@
#pragma once
#include "models/pushrulemodel.h"
#include <QObject>
#include <QQuickItem>
@@ -50,6 +51,11 @@ class Controller : public QObject
*/
Q_PROPERTY(Quotient::Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
/**
* @brief The PushRuleModel that has the active connection's push rules.
*/
Q_PROPERTY(PushRuleModel *pushRuleModel READ pushRuleModel CONSTANT)
/**
* @brief The row number in the accounts directory of the active connection.
*/
@@ -119,6 +125,8 @@ public:
void setActiveConnection(Quotient::Connection *connection);
[[nodiscard]] Quotient::Connection *activeConnection() const;
[[nodiscard]] PushRuleModel *pushRuleModel() const;
/**
* @brief Add a new connection to the account registry.
*/
@@ -236,6 +244,8 @@ private:
bool hasWindowSystem() const;
QPointer<PushRuleModel> m_pushRuleModel;
private Q_SLOTS:
void invokeLogin();
void showWindow();

View File

@@ -57,13 +57,13 @@
#include "models/emojimodel.h"
#include "models/emoticonfiltermodel.h"
#include "models/imagepacksmodel.h"
#include "models/keywordnotificationrulemodel.h"
#include "models/livelocationsmodel.h"
#include "models/locationsmodel.h"
#include "models/mediamessagefiltermodel.h"
#include "models/messageeventmodel.h"
#include "models/messagefiltermodel.h"
#include "models/publicroomlistmodel.h"
#include "models/pushrulemodel.h"
#include "models/reactionmodel.h"
#include "models/roomlistmodel.h"
#include "models/searchmodel.h"
@@ -257,13 +257,15 @@ int main(int argc, char *argv[])
#ifdef QUOTIENT_07
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
#endif
qmlRegisterType<KeywordNotificationRuleModel>("org.kde.neochat", 1, 0, "KeywordNotificationRuleModel");
qmlRegisterType<PushRuleModel>("org.kde.neochat", 1, 0, "PushRuleModel");
qmlRegisterType<StickerModel>("org.kde.neochat", 1, 0, "StickerModel");
qmlRegisterType<ImagePacksModel>("org.kde.neochat", 1, 0, "ImagePacksModel");
qmlRegisterType<AccountEmoticonModel>("org.kde.neochat", 1, 0, "AccountEmoticonModel");
qmlRegisterType<EmoticonFilterModel>("org.kde.neochat", 1, 0, "EmoticonFilterModel");
qmlRegisterType<DelegateSizeHelper>("org.kde.neochat", 1, 0, "DelegateSizeHelper");
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
qmlRegisterUncreatableType<PushNotificationKind>("org.kde.neochat", 1, 0, "PushNotificationKind", "ENUM");
qmlRegisterUncreatableType<PushNotificationSection>("org.kde.neochat", 1, 0, "PushNotificationSection", "ENUM");
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
qmlRegisterUncreatableType<PushNotificationAction>("org.kde.neochat", 1, 0, "PushNotificationAction", "ENUM");
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");

View File

@@ -1,110 +0,0 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "keywordnotificationrulemodel.h"
#include "controller.h"
#include "notificationsmanager.h"
#include <QDebug>
#include <connection.h>
#include <converters.h>
#include <csapi/definitions/push_ruleset.h>
#include <csapi/pushrules.h>
#include <jobs/basejob.h>
KeywordNotificationRuleModel::KeywordNotificationRuleModel(QObject *parent)
: QAbstractListModel(parent)
{
if (Controller::instance().activeConnection()) {
controllerConnectionChanged();
}
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &KeywordNotificationRuleModel::controllerConnectionChanged);
}
void KeywordNotificationRuleModel::controllerConnectionChanged()
{
connect(Controller::instance().activeConnection(), &Quotient::Connection::accountDataChanged, this, &KeywordNotificationRuleModel::updateNotificationRules);
updateNotificationRules("m.push_rules");
}
void KeywordNotificationRuleModel::updateNotificationRules(const QString &type)
{
if (type != "m.push_rules") {
return;
}
const QJsonObject ruleDataJson = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson["global"].toObject());
const QVector<Quotient::PushRule> contentRules = ruleData.content;
beginResetModel();
m_notificationRules.clear();
for (const auto &i : contentRules) {
if (!m_notificationRules.contains(i.ruleId) && i.ruleId[0] != '.') {
m_notificationRules.append(i.ruleId);
}
}
endResetModel();
}
QVariant KeywordNotificationRuleModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return {};
}
if (index.row() >= m_notificationRules.count()) {
qDebug() << "KeywordNotificationRuleModel, something's wrong: index.row() >= m_notificationRules.count()";
return {};
}
if (role == NameRole) {
return m_notificationRules.at(index.row());
}
return {};
}
int KeywordNotificationRuleModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_notificationRules.count();
}
void KeywordNotificationRuleModel::addKeyword(const QString &keyword)
{
if (m_notificationRules.count() == 0) {
NotificationsManager::instance().initializeKeywordNotificationAction();
}
const QVector<QVariant> actions = NotificationsManager::instance().getKeywordNotificationActions();
auto job = Controller::instance()
.activeConnection()
->callApi<Quotient::SetPushRuleJob>("global", "content", keyword, actions, "", "", QVector<Quotient::PushCondition>(), keyword);
connect(job, &Quotient::BaseJob::success, this, [this, keyword]() {
beginInsertRows(QModelIndex(), m_notificationRules.count(), m_notificationRules.count());
m_notificationRules.append(keyword);
endInsertRows();
});
}
void KeywordNotificationRuleModel::removeKeywordAtIndex(int index)
{
auto job = Controller::instance().activeConnection()->callApi<Quotient::DeletePushRuleJob>("global", "content", m_notificationRules[index]);
connect(job, &Quotient::BaseJob::success, this, [this, index]() {
beginRemoveRows(QModelIndex(), index, index);
m_notificationRules.removeAt(index);
endRemoveRows();
if (m_notificationRules.count() == 0) {
NotificationsManager::instance().deactivateKeywordNotificationAction();
}
});
}
QHash<int, QByteArray> KeywordNotificationRuleModel::roleNames() const
{
return {{NameRole, QByteArrayLiteral("name")}};
}

View File

@@ -1,66 +0,0 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <csapi/definitions/push_rule.h>
#include <QAbstractListModel>
/**
* @class KeywordNotificationRuleModel
*
* This class defines the model for managing notification push rule keywords.
*/
class KeywordNotificationRuleModel : public QAbstractListModel
{
Q_OBJECT
public:
/**
* @brief Defines the model roles.
*/
enum EventRoles {
NameRole = Qt::DisplayRole, /**< The push rule keyword. */
};
KeywordNotificationRuleModel(QObject *parent = nullptr);
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns a mapping from Role enum values to role names.
*
* @sa EventRoles, QAbstractItemModel::roleNames()
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
/**
* @brief Add a new keyword to the model.
*/
Q_INVOKABLE void addKeyword(const QString &keyword);
/**
* @brief Remove a keyword from the model.
*/
Q_INVOKABLE void removeKeywordAtIndex(int index);
private Q_SLOTS:
void controllerConnectionChanged();
void updateNotificationRules(const QString &type);
private:
QList<QString> m_notificationRules;
};

View File

@@ -4,7 +4,7 @@
#include "livelocationsmodel.h"
#include <Quotient/events/roommessageevent.h>
#include <events/roommessageevent.h>
#include <QDebug>

View File

@@ -0,0 +1,445 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "pushrulemodel.h"
#include <QDebug>
#include <connection.h>
#include <converters.h>
#include <csapi/definitions/push_ruleset.h>
#include <csapi/pushrules.h>
#include <jobs/basejob.h>
#include <qobjectdefs.h>
#include "controller.h"
#include "neochatconfig.h"
#include "notificationsmanager.h"
// Alternate name text for default rules.
static const QHash<QString, QString> defaultRuleNames = {
{QStringLiteral(".m.rule.master"), QStringLiteral("Enable notifications for this account")},
{QStringLiteral(".m.rule.room_one_to_one"), QStringLiteral("Messages in one-to-one chats")},
{QStringLiteral(".m.rule.encrypted_room_one_to_one"), QStringLiteral("Encrypted messages in one-to-one chats")},
{QStringLiteral(".m.rule.message"), QStringLiteral("Messages in group chats")},
{QStringLiteral(".m.rule.encrypted"), QStringLiteral("Messages in encrypted group chats")},
{QStringLiteral(".m.rule.tombstone"), QStringLiteral("Room upgrade messages")},
{QStringLiteral(".m.rule.contains_display_name"), QStringLiteral("Messages containing my display name")},
{QStringLiteral(".m.rule.roomnotif"), QStringLiteral("Whole room (@room) notifications")},
{QStringLiteral(".m.rule.invite_for_me"), QStringLiteral("Invites to a room")},
{QStringLiteral(".m.rule.call"), QStringLiteral("Call invitation")},
};
// Sections for default rules.
static const QHash<QString, PushNotificationSection::Section> defaultSections = {
{QStringLiteral(".m.rule.master"), PushNotificationSection::Master},
{QStringLiteral(".m.rule.room_one_to_one"), PushNotificationSection::Room},
{QStringLiteral(".m.rule.encrypted_room_one_to_one"), PushNotificationSection::Room},
{QStringLiteral(".m.rule.message"), PushNotificationSection::Room},
{QStringLiteral(".m.rule.encrypted"), PushNotificationSection::Room},
{QStringLiteral(".m.rule.tombstone"), PushNotificationSection::Room},
{QStringLiteral(".m.rule.contains_display_name"), PushNotificationSection::Mentions},
{QStringLiteral(".m.rule.roomnotif"), PushNotificationSection::Mentions},
{QStringLiteral(".m.rule.invite_for_me"), PushNotificationSection::Invites},
{QStringLiteral(".m.rule.call"), PushNotificationSection::Undefined}, // TODO: make invites when VOIP added.
{QStringLiteral(".m.rule.suppress_notices"), PushNotificationSection::Undefined},
{QStringLiteral(".m.rule.member_event"), PushNotificationSection::Undefined},
{QStringLiteral(".m.rule.reaction"), PushNotificationSection::Undefined},
{QStringLiteral(".m.rule.room.server_acl"), PushNotificationSection::Undefined},
{QStringLiteral(".im.vector.jitsi"), PushNotificationSection::Undefined},
};
// Default rules that don't have a highlight option as it would lead to all messages
// in a room being highlighted.
static const QStringList noHighlight = {
QStringLiteral(".m.rule.room_one_to_one"),
QStringLiteral(".m.rule.encrypted_room_one_to_one"),
QStringLiteral(".m.rule.message"),
QStringLiteral(".m.rule.encrypted"),
};
PushRuleModel::PushRuleModel(QObject *parent)
: QAbstractListModel(parent)
{
m_defaultKeywordAction = static_cast<PushNotificationAction::Action>(NeoChatConfig::self()->keywordPushRuleDefault());
if (Controller::instance().activeConnection()) {
controllerConnectionChanged();
}
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &PushRuleModel::controllerConnectionChanged);
}
void PushRuleModel::controllerConnectionChanged()
{
connect(Controller::instance().activeConnection(), &Quotient::Connection::accountDataChanged, this, &PushRuleModel::updateNotificationRules);
updateNotificationRules("m.push_rules");
}
void PushRuleModel::updateNotificationRules(const QString &type)
{
if (type != "m.push_rules") {
return;
}
const QJsonObject ruleDataJson = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson["global"].toObject());
beginResetModel();
m_rules.clear();
// Doing this 5 times because PushRuleset is a struct.
setRules(ruleData.override, PushNotificationKind::Override);
setRules(ruleData.content, PushNotificationKind::Content);
setRules(ruleData.room, PushNotificationKind::Room);
setRules(ruleData.sender, PushNotificationKind::Sender);
setRules(ruleData.underride, PushNotificationKind::Underride);
Q_EMIT globalNotificationsEnabledChanged();
Q_EMIT globalNotificationsSetChanged();
endResetModel();
}
void PushRuleModel::setRules(QVector<Quotient::PushRule> rules, PushNotificationKind::Kind kind)
{
for (const auto &rule : rules) {
QString roomId;
if (rule.conditions.size() > 0) {
for (const auto &condition : rule.conditions) {
if (condition.key == QStringLiteral("room_id")) {
roomId = condition.pattern;
}
}
}
m_rules.append(Rule{
rule.ruleId,
kind,
variantToAction(rule.actions, rule.enabled),
getSection(rule),
rule.enabled,
roomId,
});
}
}
int PushRuleModel::getRuleIndex(const QString &ruleId) const
{
for (auto i = 0; i < m_rules.count(); i++) {
if (m_rules[i].id == ruleId) {
return i;
}
}
return -1;
}
PushNotificationSection::Section PushRuleModel::getSection(Quotient::PushRule rule)
{
auto ruleId = rule.ruleId;
if (defaultSections.contains(ruleId)) {
return defaultSections.value(ruleId);
} else {
/**
* If the rule name resolves to a matrix id for a room that the user is part
* of it shouldn't appear in the global list as it's overriding the global
* state for that room.
*
* Rooms that the user hasn't joined shouldn't have a rule.
*/
auto connection = Controller::instance().activeConnection();
if (connection->room(ruleId) != nullptr) {
return PushNotificationSection::Undefined;
}
/**
* If the rule name resolves to a matrix id for a user it shouldn't appear
* in the global list as it's a rule to block notifications from a user and
* is handled elsewhere.
*/
auto testUserId = ruleId;
// Rules for user matrix IDs often don't have the @ on the beginning so add
// if not there to avoid malformed ID.
if (!testUserId.startsWith(u'@')) {
testUserId.prepend(u'@');
}
if (connection->user(testUserId) != nullptr) {
return PushNotificationSection::Undefined;
}
// If the rule has push conditions and one is a room ID it is a room only keyword.
if (!rule.conditions.isEmpty()) {
for (auto condition : rule.conditions) {
if (condition.key == QStringLiteral("room_id")) {
return PushNotificationSection::RoomKeywords;
}
}
}
return PushNotificationSection::Keywords;
}
}
PushNotificationAction::Action PushRuleModel::defaultState() const
{
return m_defaultKeywordAction;
}
void PushRuleModel::setDefaultState(PushNotificationAction::Action defaultState)
{
if (defaultState == m_defaultKeywordAction) {
return;
}
m_defaultKeywordAction = defaultState;
NeoChatConfig::setKeywordPushRuleDefault(m_defaultKeywordAction);
Q_EMIT defaultStateChanged();
}
bool PushRuleModel::globalNotificationsEnabled() const
{
auto masterIndex = getRuleIndex(QStringLiteral(".m.rule.master"));
if (masterIndex > -1) {
return !m_rules[masterIndex].enabled;
}
return false;
}
void PushRuleModel::setGlobalNotificationsEnabled(bool enabled)
{
setNotificationRuleEnabled("override", ".m.rule.master", !enabled);
}
bool PushRuleModel::globalNotificationsSet() const
{
return getRuleIndex(QStringLiteral(".m.rule.master")) > -1;
}
QVariant PushRuleModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return {};
}
if (index.row() >= rowCount()) {
qDebug() << "PushRuleModel, something's wrong: index.row() >= m_rules.count()";
return {};
}
if (role == NameRole) {
auto ruleId = m_rules.at(index.row()).id;
if (defaultRuleNames.contains(ruleId)) {
return defaultRuleNames.value(ruleId);
} else {
return ruleId;
}
}
if (role == IdRole) {
return m_rules.at(index.row()).id;
}
if (role == KindRole) {
return m_rules.at(index.row()).kind;
}
if (role == ActionRole) {
return m_rules.at(index.row()).action;
}
if (role == HighlightableRole) {
return !noHighlight.contains(m_rules.at(index.row()).id);
}
if (role == DeletableRole) {
return !m_rules.at(index.row()).id.startsWith(QStringLiteral("."));
}
if (role == SectionRole) {
return m_rules.at(index.row()).section;
}
if (role == RoomIdRole) {
return m_rules.at(index.row()).roomId;
}
return {};
}
int PushRuleModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_rules.count();
}
QHash<int, QByteArray> PushRuleModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles[NameRole] = "name";
roles[IdRole] = "id";
roles[KindRole] = "kind";
roles[ActionRole] = "ruleAction";
roles[HighlightableRole] = "highlightable";
roles[DeletableRole] = "deletable";
roles[SectionRole] = "section";
roles[RoomIdRole] = "roomId";
return roles;
}
void PushRuleModel::setPushRuleAction(const QString &id, PushNotificationAction::Action action)
{
int index = getRuleIndex(id);
if (index == -1) {
return;
}
auto rule = m_rules[index];
// Override rules need to be disabled when off so that other rules can match the message if they apply.
if (action == PushNotificationAction::Off && rule.kind == PushNotificationKind::Override) {
setNotificationRuleEnabled(PushNotificationKind::kindString(rule.kind), rule.id, false);
} else if (rule.kind == PushNotificationKind::Override) {
setNotificationRuleEnabled(PushNotificationKind::kindString(rule.kind), rule.id, true);
}
setNotificationRuleActions(PushNotificationKind::kindString(rule.kind), rule.id, action);
}
void PushRuleModel::addKeyword(const QString &keyword, const QString &roomId)
{
PushNotificationKind::Kind kind = PushNotificationKind::Content;
const QVector<QVariant> actions = actionToVariant(m_defaultKeywordAction);
QVector<Quotient::PushCondition> pushConditions;
if (!roomId.isEmpty()) {
kind = PushNotificationKind::Override;
Quotient::PushCondition roomCondition;
roomCondition.kind = "event_match";
roomCondition.key = "room_id";
roomCondition.pattern = roomId;
pushConditions.append(roomCondition);
Quotient::PushCondition keywordCondition;
keywordCondition.kind = "event_match";
keywordCondition.key = "content.body";
keywordCondition.pattern = keyword;
pushConditions.append(keywordCondition);
}
auto job = Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleJob>("global",
PushNotificationKind::kindString(kind),
keyword,
actions,
QLatin1String(""),
QLatin1String(""),
pushConditions,
roomId.isEmpty() ? keyword : QLatin1String(""));
connect(job, &Quotient::BaseJob::failure, this, [job, keyword]() {
qWarning() << QLatin1String("Unable to set push rule for keyword %1: ").arg(keyword) << job->errorString();
});
}
/**
* The rule never being removed from the list by this function is intentional. When
* the server is updated the new push rule account data will be synced and it will
* be removed when the model is updated then.
*/
void PushRuleModel::removeKeyword(const QString &keyword)
{
int index = getRuleIndex(keyword);
if (index == -1) {
return;
}
auto kind = PushNotificationKind::kindString(m_rules[index].kind);
auto job = Controller::instance().activeConnection()->callApi<Quotient::DeletePushRuleJob>("global", kind, m_rules[index].id);
connect(job, &Quotient::BaseJob::failure, this, [this, job, index]() {
qWarning() << QLatin1String("Unable to remove push rule for keyword %1: ").arg(m_rules[index].id) << job->errorString();
});
}
void PushRuleModel::setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled)
{
auto job = Controller::instance().activeConnection()->callApi<Quotient::IsPushRuleEnabledJob>("global", kind, ruleId);
connect(job, &Quotient::BaseJob::success, this, [job, kind, ruleId, enabled]() {
if (job->enabled() != enabled) {
Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleEnabledJob>("global", kind, ruleId, enabled);
}
});
}
void PushRuleModel::setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action)
{
QVector<QVariant> actions;
if (ruleId == ".m.rule.call") {
actions = actionToVariant(action, "ring");
} else {
actions = actionToVariant(action);
}
Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleActionsJob>("global", kind, ruleId, actions);
}
PushNotificationAction::Action PushRuleModel::variantToAction(const QVector<QVariant> &actions, bool enabled)
{
bool notify = false;
bool isNoisy = false;
bool highlightEnabled = false;
for (const auto &i : actions) {
auto actionString = i.toString();
if (!actionString.isEmpty()) {
if (actionString == QLatin1String("notify")) {
notify = true;
}
continue;
}
QJsonObject action = i.toJsonObject();
if (action["set_tweak"].toString() == "sound") {
isNoisy = true;
} else if (action["set_tweak"].toString() == "highlight") {
if (action["value"].toString() != "false") {
highlightEnabled = true;
}
}
}
if (!enabled) {
return PushNotificationAction::Off;
}
if (notify) {
if (isNoisy && highlightEnabled) {
return PushNotificationAction::NoisyHighlight;
} else if (isNoisy) {
return PushNotificationAction::Noisy;
} else if (highlightEnabled) {
return PushNotificationAction::Highlight;
} else {
return PushNotificationAction::On;
}
} else {
return PushNotificationAction::Off;
}
}
QVector<QVariant> PushRuleModel::actionToVariant(PushNotificationAction::Action action, const QString &sound)
{
// The caller should never try to set the state to unknown.
// It exists only as a default state to diable the settings options until the actual state is retrieved from the server.
if (action == PushNotificationAction::Unknown) {
Q_ASSERT(false);
return QVector<QVariant>();
}
QVector<QVariant> actions;
if (action != PushNotificationAction::Off) {
actions.append("notify");
} else {
actions.append("dont_notify");
}
if (action == PushNotificationAction::Noisy || action == PushNotificationAction::NoisyHighlight) {
QJsonObject soundTweak;
soundTweak.insert("set_tweak", "sound");
soundTweak.insert("value", sound);
actions.append(soundTweak);
}
if (action == PushNotificationAction::Highlight || action == PushNotificationAction::NoisyHighlight) {
QJsonObject highlightTweak;
highlightTweak.insert("set_tweak", "highlight");
actions.append(highlightTweak);
}
return actions;
}

241
src/models/pushrulemodel.h Normal file
View File

@@ -0,0 +1,241 @@
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QAbstractListModel>
#include <csapi/definitions/push_rule.h>
#include "notificationsmanager.h"
/**
* @class PushNotificationKind
*
* A class with the Kind enum for push notifications and helper functions.
*
* The kind relates to the kinds of push rule definied in the matrix spec, see
* https://spec.matrix.org/v1.7/client-server-api/#push-rules for full details.
*/
class PushNotificationKind : public QObject
{
Q_OBJECT
public:
/**
* @brief Defines the different kinds of push rule.
*/
enum Kind {
Override = 0, /**< The highest priority rules. */
Content, /**< These configure behaviour for messages that match certain patterns. */
Room, /**< These rules change the behaviour of all messages for a given room. */
Sender, /**< These rules configure notification behaviour for messages from a specific Matrix user ID. */
Underride, /**< These are identical to override rules, but have a lower priority than content, room and sender rules. */
};
Q_ENUM(Kind);
/**
* @brief Translate the Kind enum value to a human readable string.
*
* @sa Kind
*/
static QString kindString(Kind kind)
{
switch (kind) {
case Kind::Override:
return QLatin1String("override");
case Kind::Content:
return QLatin1String("content");
case Kind::Room:
return QLatin1String("room");
case Kind::Sender:
return QLatin1String("sender");
case Kind::Underride:
return QLatin1String("underride");
default:
return {};
}
};
};
/**
* @class PushNotificationSection
*
* A class with the Section enum for push notifications and helper functions.
*
* @note This is different from the PushNotificationKind and instead is used for sorting
* in the settings page which is not necessarily by Kind.
*
* @sa PushNotificationKind
*/
class PushNotificationSection : public QObject
{
Q_OBJECT
public:
/**
* @brief Defines the sections to sort push rules into.
*/
enum Section {
Master = 0, /**< The master push rule */
Room, /**< Push rules relating to all rooms. */
Mentions, /**< Push rules relating to user mentions. */
Keywords, /**< Global Keyword push rules. */
RoomKeywords, /**< Keyword push rules that only apply to a specific room. */
Invites, /**< Push rules relating to invites. */
/**
* @brief Push rules that should never be shown.
*
* There are numerous rules that get set that shouldn't be shown in the general
* list e.g. The array of rules used to override global settings in individual
* rooms.
*/
Undefined,
};
Q_ENUM(Section);
/**
* @brief Translate the Section enum value to a human readable string.
*
* @sa Section
*/
static QString sectionString(Section section)
{
switch (section) {
case Section::Master:
return QLatin1String("Master");
case Section::Room:
return QLatin1String("Room Notifications");
case Section::Mentions:
return QLatin1String("@Mentions");
case Section::Keywords:
return QLatin1String("Keywords");
case Section::Invites:
return QLatin1String("Invites");
default:
return {};
}
};
};
/**
* @class PushRuleModel
*
* This class defines the model for managing notification push rule keywords.
*/
class PushRuleModel : public QAbstractListModel
{
Q_OBJECT
/**
* @brief The default state for any newly created keyword rule.
*/
Q_PROPERTY(PushNotificationAction::Action defaultState READ defaultState WRITE setDefaultState NOTIFY defaultStateChanged)
/**
* @brief The global notification state.
*
* If this rule is set to off all push notifications are disabled regardless
* of other settings.
*/
Q_PROPERTY(bool globalNotificationsEnabled READ globalNotificationsEnabled WRITE setGlobalNotificationsEnabled NOTIFY globalNotificationsEnabledChanged)
/**
* @brief Whether the global notification state has been retrieved from the server.
*
* @sa globalNotificationsEnabled, PushNotificationAction::Action
*/
Q_PROPERTY(bool globalNotificationsSet READ globalNotificationsSet NOTIFY globalNotificationsSetChanged)
public:
struct Rule {
QString id;
PushNotificationKind::Kind kind;
PushNotificationAction::Action action;
PushNotificationSection::Section section;
bool enabled;
QString roomId;
};
/**
* @brief Defines the model roles.
*/
enum EventRoles {
NameRole = Qt::DisplayRole, /**< The push rule name. */
IdRole, /**< The push rule ID. */
KindRole, /**< The kind of notification rule; override, content, etc. */
ActionRole, /**< The PushNotificationAction for the rule. */
HighlightableRole, /**< Whether the rule can have a highlight action. */
DeletableRole, /**< Whether the rule can be deleted the rule. */
SectionRole, /**< The section to sort into in the settings page. */
RoomIdRole, /**< The room the rule applies to (blank if global). */
};
Q_ENUM(EventRoles)
PushRuleModel(QObject *parent = nullptr);
[[nodiscard]] PushNotificationAction::Action defaultState() const;
void setDefaultState(PushNotificationAction::Action defaultState);
[[nodiscard]] bool globalNotificationsEnabled() const;
void setGlobalNotificationsEnabled(bool enabled);
[[nodiscard]] bool globalNotificationsSet() const;
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns a mapping from Role enum values to role names.
*
* @sa EventRoles, QAbstractItemModel::roleNames()
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void setPushRuleAction(const QString &id, PushNotificationAction::Action action);
/**
* @brief Add a new keyword to the model.
*/
Q_INVOKABLE void addKeyword(const QString &keyword, const QString &roomId = {});
/**
* @brief Remove a keyword from the model.
*/
Q_INVOKABLE void removeKeyword(const QString &keyword);
Q_SIGNALS:
void defaultStateChanged();
void globalNotificationsEnabledChanged();
void globalNotificationsSetChanged();
private Q_SLOTS:
void controllerConnectionChanged();
void updateNotificationRules(const QString &type);
private:
PushNotificationAction::Action m_defaultKeywordAction;
QList<Rule> m_rules;
void setRules(QVector<Quotient::PushRule> rules, PushNotificationKind::Kind kind);
int getRuleIndex(const QString &ruleId) const;
PushNotificationSection::Section getSection(Quotient::PushRule rule);
void setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled);
void setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action);
PushNotificationAction::Action variantToAction(const QVector<QVariant> &actions, bool enabled);
QVector<QVariant> actionToVariant(PushNotificationAction::Action action, const QString &sound = "default");
};
Q_DECLARE_METATYPE(PushRuleModel *)

View File

@@ -54,6 +54,10 @@
<entry name="LastSaveDirectory" type="String">
<label>Directory last used for saving a file</label>
</entry>
<entry name="KeywordPushRuleDefault" type="int">
<label>The default setting for a new keyword push rule.</label>
<default>4</default>
</entry>
</group>
<group name="Timeline">
<entry name="ShowAvatarInTimeline" type="bool">

View File

@@ -41,12 +41,6 @@ NotificationsManager &NotificationsManager::instance()
NotificationsManager::NotificationsManager(QObject *parent)
: QObject(parent)
{
// Can't connect the signal up until the active connection has been established by the controller
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this]() {
connect(Controller::instance().activeConnection(), &Connection::accountDataChanged, this, &NotificationsManager::updateNotificationRules);
// Ensure that the push rule states are retrieved after the connection is changed
updateNotificationRules("m.push_rules");
});
}
#ifdef QUOTIENT_07
@@ -300,309 +294,3 @@ void NotificationsManager::clearInvitationNotification(const QString &roomId)
m_invitations[roomId]->close();
}
}
/**
* The master push rule sets all notifications to off when enabled
* see https://spec.matrix.org/v1.3/client-server-api/#default-override-rules
* therefore to enable push rules the master rule needs to be disabled and vice versa
*/
void NotificationsManager::setGlobalNotificationsEnabled(bool enabled)
{
setNotificationRuleEnabled("override", ".m.rule.master", !enabled);
}
void NotificationsManager::setOneToOneNotificationAction(PushNotificationAction::Action action)
{
setNotificationRuleActions("underride", ".m.rule.room_one_to_one", action);
}
void NotificationsManager::setEncryptedOneToOneNotificationAction(PushNotificationAction::Action action)
{
setNotificationRuleActions("underride", ".m.rule.encrypted_room_one_to_one", action);
}
void NotificationsManager::setGroupChatNotificationAction(PushNotificationAction::Action action)
{
setNotificationRuleActions("underride", ".m.rule.message", action);
}
void NotificationsManager::setEncryptedGroupChatNotificationAction(PushNotificationAction::Action action)
{
setNotificationRuleActions("underride", ".m.rule.encrypted", action);
}
/*
* .m.rule.contains_display_name is an override rule so it needs to be disabled when off
* so that other rules can match the message if they apply.
*/
void NotificationsManager::setDisplayNameNotificationAction(PushNotificationAction::Action action)
{
if (action == PushNotificationAction::Off) {
setNotificationRuleEnabled("override", ".m.rule.contains_display_name", false);
} else {
setNotificationRuleActions("override", ".m.rule.contains_display_name", action);
setNotificationRuleEnabled("override", ".m.rule.contains_display_name", true);
}
}
/*
* .m.rule.roomnotif is an override rule so it needs to be disabled when off
* so that other rules can match the message if they apply.
*/
void NotificationsManager::setRoomNotificationAction(PushNotificationAction::Action action)
{
if (action == PushNotificationAction::Off) {
setNotificationRuleEnabled("override", ".m.rule.roomnotif", false);
} else {
setNotificationRuleActions("override", ".m.rule.roomnotif", action);
setNotificationRuleEnabled("override", ".m.rule.roomnotif", true);
}
}
void NotificationsManager::initializeKeywordNotificationAction()
{
m_keywordNotificationAction = PushNotificationAction::Highlight;
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
}
void NotificationsManager::deactivateKeywordNotificationAction()
{
m_keywordNotificationAction = PushNotificationAction::Off;
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
}
QVector<QVariant> NotificationsManager::getKeywordNotificationActions()
{
return toActions(m_keywordNotificationAction);
}
void NotificationsManager::setKeywordNotificationAction(PushNotificationAction::Action action)
{
// Unlike the other rules this needs to be set here for the case where there are no keyords.
m_keywordNotificationAction = action;
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
const QJsonObject accountData = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
const QJsonArray contentRuleArray = accountData["global"].toObject()["content"].toArray();
for (const auto &i : contentRuleArray) {
const QJsonObject contentRule = i.toObject();
if (contentRule["rule_id"].toString()[0] != '.') {
setNotificationRuleActions("content", contentRule["rule_id"].toString(), action);
}
}
}
/*
* .m.rule.invite_for_me is an override rule so it needs to be disabled when off
* so that other rules can match the message if they apply.
*/
void NotificationsManager::setInviteNotificationAction(PushNotificationAction::Action action)
{
if (action == PushNotificationAction::Off) {
setNotificationRuleEnabled("override", ".m.rule.invite_for_me", false);
} else {
setNotificationRuleActions("override", ".m.rule.invite_for_me", action);
setNotificationRuleEnabled("override", ".m.rule.invite_for_me", true);
}
}
void NotificationsManager::setCallInviteNotificationAction(PushNotificationAction::Action action)
{
setNotificationRuleActions("underride", ".m.rule.call", action);
}
/*
* .m.rule.tombstone is an override rule so it needs to be disabled when off
* so that other rules can match the message if they apply.
*/
void NotificationsManager::setTombstoneNotificationAction(PushNotificationAction::Action action)
{
if (action == PushNotificationAction::Off) {
setNotificationRuleEnabled("override", ".m.rule.tombstone", false);
} else {
setNotificationRuleActions("override", ".m.rule.tombstone", action);
setNotificationRuleEnabled("override", ".m.rule.tombstone", true);
}
}
void NotificationsManager::updateNotificationRules(const QString &type)
{
if (type != "m.push_rules") {
return;
}
if (!Controller::instance().activeConnection()) {
return;
}
const QJsonObject accountData = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
// Update override rules
const QJsonArray overrideRuleArray = accountData["global"].toObject()["override"].toArray();
for (const auto &i : overrideRuleArray) {
const QJsonObject overrideRule = i.toObject();
if (overrideRule["rule_id"] == ".m.rule.master") {
bool ruleEnabled = overrideRule["enabled"].toBool();
m_globalNotificationsEnabled = !ruleEnabled;
if (!m_globalNotificationsSet) {
m_globalNotificationsSet = true;
}
Q_EMIT globalNotificationsEnabledChanged(m_globalNotificationsEnabled);
}
const PushNotificationAction::Action action = toAction(overrideRule);
if (overrideRule["rule_id"] == ".m.rule.contains_display_name") {
m_displayNameNotificationAction = action;
Q_EMIT displayNameNotificationActionChanged(m_displayNameNotificationAction);
} else if (overrideRule["rule_id"] == ".m.rule.roomnotif") {
m_roomNotificationAction = action;
Q_EMIT roomNotificationActionChanged(m_roomNotificationAction);
} else if (overrideRule["rule_id"] == ".m.rule.invite_for_me") {
m_inviteNotificationAction = action;
Q_EMIT inviteNotificationActionChanged(m_inviteNotificationAction);
} else if (overrideRule["rule_id"] == ".m.rule.tombstone") {
m_tombstoneNotificationAction = action;
Q_EMIT tombstoneNotificationActionChanged(m_tombstoneNotificationAction);
}
}
// Update content rules
const QJsonArray contentRuleArray = accountData["global"].toObject()["content"].toArray();
PushNotificationAction::Action keywordAction = PushNotificationAction::Unknown;
for (const auto &i : contentRuleArray) {
const QJsonObject contentRule = i.toObject();
const PushNotificationAction::Action action = toAction(contentRule);
bool actionMismatch = false;
if (contentRule["rule_id"].toString()[0] != '.' && !actionMismatch) {
if (keywordAction == PushNotificationAction::Unknown) {
keywordAction = action;
m_keywordNotificationAction = action;
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
} else if (action != keywordAction) {
actionMismatch = true;
m_keywordNotificationAction = PushNotificationAction::On;
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
}
}
}
// If there are no keywords set the state to off, this is the only time it'll be in the off state
if (keywordAction == PushNotificationAction::Unknown) {
m_keywordNotificationAction = PushNotificationAction::Off;
Q_EMIT keywordNotificationActionChanged(m_keywordNotificationAction);
}
// Update underride rules
const QJsonArray underrideRuleArray = accountData["global"].toObject()["underride"].toArray();
for (const auto &i : underrideRuleArray) {
const QJsonObject underrideRule = i.toObject();
const PushNotificationAction::Action action = toAction(underrideRule);
if (underrideRule["rule_id"] == ".m.rule.room_one_to_one") {
m_oneToOneNotificationAction = action;
Q_EMIT oneToOneNotificationActionChanged(m_oneToOneNotificationAction);
} else if (underrideRule["rule_id"] == ".m.rule.encrypted_room_one_to_one") {
m_encryptedOneToOneNotificationAction = action;
Q_EMIT encryptedOneToOneNotificationActionChanged(m_encryptedOneToOneNotificationAction);
} else if (underrideRule["rule_id"] == ".m.rule.message") {
m_groupChatNotificationAction = action;
Q_EMIT groupChatNotificationActionChanged(m_groupChatNotificationAction);
} else if (underrideRule["rule_id"] == ".m.rule.encrypted") {
m_encryptedGroupChatNotificationAction = action;
Q_EMIT encryptedGroupChatNotificationActionChanged(m_encryptedGroupChatNotificationAction);
} else if (underrideRule["rule_id"] == ".m.rule.call") {
m_callInviteNotificationAction = action;
Q_EMIT callInviteNotificationActionChanged(m_callInviteNotificationAction);
}
}
}
void NotificationsManager::setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled)
{
auto job = Controller::instance().activeConnection()->callApi<IsPushRuleEnabledJob>("global", kind, ruleId);
connect(job, &BaseJob::success, this, [job, kind, ruleId, enabled]() {
if (job->enabled() != enabled) {
Controller::instance().activeConnection()->callApi<SetPushRuleEnabledJob>("global", kind, ruleId, enabled);
}
});
}
void NotificationsManager::setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action)
{
QVector<QVariant> actions;
if (ruleId == ".m.rule.call") {
actions = toActions(action, "ring");
} else {
actions = toActions(action);
}
Controller::instance().activeConnection()->callApi<SetPushRuleActionsJob>("global", kind, ruleId, actions);
}
PushNotificationAction::Action NotificationsManager::toAction(const QJsonObject &rule)
{
const QJsonArray actions = rule["actions"].toArray();
bool isNoisy = false;
bool highlightEnabled = false;
const bool enabled = rule["enabled"].toBool();
for (const auto &i : actions) {
QJsonObject action = i.toObject();
if (action["set_tweak"].toString() == "sound") {
isNoisy = true;
} else if (action["set_tweak"].toString() == "highlight") {
if (action["value"].toString() != "false") {
highlightEnabled = true;
}
}
}
if (!enabled) {
return PushNotificationAction::Off;
}
if (actions[0] == "notify") {
if (isNoisy && highlightEnabled) {
return PushNotificationAction::NoisyHighlight;
} else if (isNoisy) {
return PushNotificationAction::Noisy;
} else if (highlightEnabled) {
return PushNotificationAction::Highlight;
} else {
return PushNotificationAction::On;
}
} else {
return PushNotificationAction::Off;
}
}
QVector<QVariant> NotificationsManager::toActions(PushNotificationAction::Action action, const QString &sound)
{
// The caller should never try to set the state to unknown.
// It exists only as a default state to diable the settings options until the actual state is retrieved from the server.
if (action == PushNotificationAction::Unknown) {
Q_ASSERT(false);
return QVector<QVariant>();
}
QVector<QVariant> actions;
if (action != PushNotificationAction::Off) {
actions.append("notify");
} else {
actions.append("dont_notify");
}
if (action == PushNotificationAction::Noisy || action == PushNotificationAction::NoisyHighlight) {
QJsonObject soundTweak;
soundTweak.insert("set_tweak", "sound");
soundTweak.insert("value", sound);
actions.append(soundTweak);
}
if (action == PushNotificationAction::Highlight || action == PushNotificationAction::NoisyHighlight) {
QJsonObject highlightTweak;
highlightTweak.insert("set_tweak", "highlight");
actions.append(highlightTweak);
}
return actions;
}

View File

@@ -56,93 +56,6 @@ class NotificationsManager : public QObject
{
Q_OBJECT
/**
* @brief The global notification state.
*
* If this rule is set to off all push notifications are disabled regardless
* of other settings.
*/
Q_PROPERTY(bool globalNotificationsEnabled MEMBER m_globalNotificationsEnabled WRITE setGlobalNotificationsEnabled NOTIFY globalNotificationsEnabledChanged)
/**
* @brief Whether the global notification state has been retrieved from the server.
*
* This is different to the others below as globalNotificationsEnabled is only
* a bool rather than a PushNotificationAction::Action so a separate property
* is required to track if the state has been retrieved from the server.
*
* @sa globalNotificationsEnabled, PushNotificationAction::Action
*/
Q_PROPERTY(bool globalNotificationsSet MEMBER m_globalNotificationsSet NOTIFY globalNotificationsSetChanged)
/**
* @brief The notification action for direct chats.
*
* @note Encrypted direct chats have a separate setting.
*
* @sa encryptedOneToOneNotificationAction
*/
Q_PROPERTY(PushNotificationAction::Action oneToOneNotificationAction MEMBER m_oneToOneNotificationAction WRITE setOneToOneNotificationAction NOTIFY
oneToOneNotificationActionChanged)
/**
* @brief The notification action for encrypted direct chats.
*/
Q_PROPERTY(PushNotificationAction::Action encryptedOneToOneNotificationAction MEMBER m_encryptedOneToOneNotificationAction WRITE
setEncryptedOneToOneNotificationAction NOTIFY encryptedOneToOneNotificationActionChanged)
/**
* @brief The notification action for group chats.
*
* @note Encrypted group chats have a separate setting.
*
* @sa encryptedGroupChatNotificationAction
*/
Q_PROPERTY(PushNotificationAction::Action groupChatNotificationAction MEMBER m_groupChatNotificationAction WRITE setGroupChatNotificationAction NOTIFY
groupChatNotificationActionChanged)
/**
* @brief The notification action for encrypted group chats.
*/
Q_PROPERTY(PushNotificationAction::Action encryptedGroupChatNotificationAction MEMBER m_encryptedGroupChatNotificationAction WRITE
setEncryptedGroupChatNotificationAction NOTIFY encryptedGroupChatNotificationActionChanged)
/**
* @brief The notification action for events containing the local user's display name.
*/
Q_PROPERTY(PushNotificationAction::Action displayNameNotificationAction MEMBER m_displayNameNotificationAction WRITE setDisplayNameNotificationAction NOTIFY
displayNameNotificationActionChanged)
/**
* @brief The notification action for room events.
*/
Q_PROPERTY(PushNotificationAction::Action roomNotificationAction MEMBER m_roomNotificationAction WRITE setRoomNotificationAction NOTIFY
roomNotificationActionChanged)
/**
* @brief The notification action for keyword push rules.
*/
Q_PROPERTY(PushNotificationAction::Action keywordNotificationAction MEMBER m_keywordNotificationAction WRITE setKeywordNotificationAction NOTIFY
keywordNotificationActionChanged)
/**
* @brief The notification action for invites to chats.
*/
Q_PROPERTY(PushNotificationAction::Action inviteNotificationAction MEMBER m_inviteNotificationAction WRITE setInviteNotificationAction NOTIFY
inviteNotificationActionChanged)
/**
* @brief The notification action for voice calls.
*/
Q_PROPERTY(PushNotificationAction::Action callInviteNotificationAction MEMBER m_callInviteNotificationAction WRITE setCallInviteNotificationAction NOTIFY
callInviteNotificationActionChanged)
/**
* @brief The notification action for room upgrade events.
*/
Q_PROPERTY(PushNotificationAction::Action tombstoneNotificationAction MEMBER m_tombstoneNotificationAction WRITE setTombstoneNotificationAction NOTIFY
tombstoneNotificationActionChanged)
public:
static NotificationsManager &instance();
@@ -164,30 +77,6 @@ public:
*/
void clearInvitationNotification(const QString &roomId);
/**
* @brief Set the initial keyword notification action.
*
* This is only required if no keyword rules exist and one is created. The default
* action is PushNotificationAction::Action::Highlight.
*
* @sa PushNotificationAction::Action
*/
void initializeKeywordNotificationAction();
/**
* @brief Set the keyword notification action to PushNotificationAction::Action::Off.
*
* This is only required the last keyword rule is deleted.
*
* @sa PushNotificationAction::Action
*/
void deactivateKeywordNotificationAction();
/**
* @brief Return the keyword notification action in a form required for the server push rule.
*/
QVector<QVariant> getKeywordNotificationActions();
#ifdef QUOTIENT_07
/**
* @brief Handle the notifications for the given connection.
@@ -208,52 +97,6 @@ private:
QHash<QString, KNotification *> m_notifications;
QHash<QString, QPointer<KNotification>> m_invitations;
bool m_globalNotificationsEnabled = false;
bool m_globalNotificationsSet = false;
PushNotificationAction::Action m_oneToOneNotificationAction = PushNotificationAction::Unknown;
PushNotificationAction::Action m_encryptedOneToOneNotificationAction = PushNotificationAction::Unknown;
PushNotificationAction::Action m_groupChatNotificationAction = PushNotificationAction::Unknown;
PushNotificationAction::Action m_encryptedGroupChatNotificationAction = PushNotificationAction::Unknown;
PushNotificationAction::Action m_displayNameNotificationAction = PushNotificationAction::Unknown;
PushNotificationAction::Action m_roomNotificationAction = PushNotificationAction::Unknown;
PushNotificationAction::Action m_keywordNotificationAction = PushNotificationAction::Unknown;
PushNotificationAction::Action m_inviteNotificationAction = PushNotificationAction::Unknown;
PushNotificationAction::Action m_callInviteNotificationAction = PushNotificationAction::Unknown;
PushNotificationAction::Action m_tombstoneNotificationAction = PushNotificationAction::Unknown;
void setGlobalNotificationsEnabled(bool enabled);
void setOneToOneNotificationAction(PushNotificationAction::Action action);
void setEncryptedOneToOneNotificationAction(PushNotificationAction::Action action);
void setGroupChatNotificationAction(PushNotificationAction::Action action);
void setEncryptedGroupChatNotificationAction(PushNotificationAction::Action action);
void setDisplayNameNotificationAction(PushNotificationAction::Action action);
void setRoomNotificationAction(PushNotificationAction::Action action);
void setKeywordNotificationAction(PushNotificationAction::Action action);
void setInviteNotificationAction(PushNotificationAction::Action action);
void setCallInviteNotificationAction(PushNotificationAction::Action action);
void setTombstoneNotificationAction(PushNotificationAction::Action action);
void setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled);
void setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action);
PushNotificationAction::Action toAction(const QJsonObject &rule);
QVector<QVariant> toActions(PushNotificationAction::Action action, const QString &sound = "default");
private Q_SLOTS:
void processNotificationJob(QPointer<Quotient::Connection> connection, Quotient::GetNotificationsJob *job, bool initialization);
void updateNotificationRules(const QString &type);
Q_SIGNALS:
void globalNotificationsEnabledChanged(bool newState);
void globalNotificationsSetChanged(bool newState);
void oneToOneNotificationActionChanged(PushNotificationAction::Action action);
void encryptedOneToOneNotificationActionChanged(PushNotificationAction::Action action);
void groupChatNotificationActionChanged(PushNotificationAction::Action action);
void encryptedGroupChatNotificationActionChanged(PushNotificationAction::Action action);
void displayNameNotificationActionChanged(PushNotificationAction::Action action);
void roomNotificationActionChanged(PushNotificationAction::Action action);
void keywordNotificationActionChanged(PushNotificationAction::Action action);
void inviteNotificationActionChanged(PushNotificationAction::Action action);
void callInviteNotificationActionChanged(PushNotificationAction::Action action);
void tombstoneNotificationActionChanged(PushNotificationAction::Action action);
};

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
}
}