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/userfiltermodel.cpp
models/publicroomlistmodel.cpp models/publicroomlistmodel.cpp
models/userdirectorylistmodel.cpp models/userdirectorylistmodel.cpp
models/keywordnotificationrulemodel.cpp models/pushrulemodel.cpp
models/emoticonfiltermodel.cpp models/emoticonfiltermodel.cpp
notificationsmanager.cpp notificationsmanager.cpp
models/sortfilterroomlistmodel.cpp models/sortfilterroomlistmodel.cpp

View File

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

View File

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

View File

@@ -57,13 +57,13 @@
#include "models/emojimodel.h" #include "models/emojimodel.h"
#include "models/emoticonfiltermodel.h" #include "models/emoticonfiltermodel.h"
#include "models/imagepacksmodel.h" #include "models/imagepacksmodel.h"
#include "models/keywordnotificationrulemodel.h"
#include "models/livelocationsmodel.h" #include "models/livelocationsmodel.h"
#include "models/locationsmodel.h" #include "models/locationsmodel.h"
#include "models/mediamessagefiltermodel.h" #include "models/mediamessagefiltermodel.h"
#include "models/messageeventmodel.h" #include "models/messageeventmodel.h"
#include "models/messagefiltermodel.h" #include "models/messagefiltermodel.h"
#include "models/publicroomlistmodel.h" #include "models/publicroomlistmodel.h"
#include "models/pushrulemodel.h"
#include "models/reactionmodel.h" #include "models/reactionmodel.h"
#include "models/roomlistmodel.h" #include "models/roomlistmodel.h"
#include "models/searchmodel.h" #include "models/searchmodel.h"
@@ -257,13 +257,15 @@ int main(int argc, char *argv[])
#ifdef QUOTIENT_07 #ifdef QUOTIENT_07
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler"); qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
#endif #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<StickerModel>("org.kde.neochat", 1, 0, "StickerModel");
qmlRegisterType<ImagePacksModel>("org.kde.neochat", 1, 0, "ImagePacksModel"); qmlRegisterType<ImagePacksModel>("org.kde.neochat", 1, 0, "ImagePacksModel");
qmlRegisterType<AccountEmoticonModel>("org.kde.neochat", 1, 0, "AccountEmoticonModel"); qmlRegisterType<AccountEmoticonModel>("org.kde.neochat", 1, 0, "AccountEmoticonModel");
qmlRegisterType<EmoticonFilterModel>("org.kde.neochat", 1, 0, "EmoticonFilterModel"); qmlRegisterType<EmoticonFilterModel>("org.kde.neochat", 1, 0, "EmoticonFilterModel");
qmlRegisterType<DelegateSizeHelper>("org.kde.neochat", 1, 0, "DelegateSizeHelper"); qmlRegisterType<DelegateSizeHelper>("org.kde.neochat", 1, 0, "DelegateSizeHelper");
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM"); 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<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
qmlRegisterUncreatableType<PushNotificationAction>("org.kde.neochat", 1, 0, "PushNotificationAction", "ENUM"); qmlRegisterUncreatableType<PushNotificationAction>("org.kde.neochat", 1, 0, "PushNotificationAction", "ENUM");
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "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 "livelocationsmodel.h"
#include <Quotient/events/roommessageevent.h> #include <events/roommessageevent.h>
#include <QDebug> #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"> <entry name="LastSaveDirectory" type="String">
<label>Directory last used for saving a file</label> <label>Directory last used for saving a file</label>
</entry> </entry>
<entry name="KeywordPushRuleDefault" type="int">
<label>The default setting for a new keyword push rule.</label>
<default>4</default>
</entry>
</group> </group>
<group name="Timeline"> <group name="Timeline">
<entry name="ShowAvatarInTimeline" type="bool"> <entry name="ShowAvatarInTimeline" type="bool">

View File

@@ -41,12 +41,6 @@ NotificationsManager &NotificationsManager::instance()
NotificationsManager::NotificationsManager(QObject *parent) NotificationsManager::NotificationsManager(QObject *parent)
: 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 #ifdef QUOTIENT_07
@@ -300,309 +294,3 @@ void NotificationsManager::clearInvitationNotification(const QString &roomId)
m_invitations[roomId]->close(); 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 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: public:
static NotificationsManager &instance(); static NotificationsManager &instance();
@@ -164,30 +77,6 @@ public:
*/ */
void clearInvitationNotification(const QString &roomId); 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 #ifdef QUOTIENT_07
/** /**
* @brief Handle the notifications for the given connection. * @brief Handle the notifications for the given connection.
@@ -208,52 +97,6 @@ private:
QHash<QString, KNotification *> m_notifications; QHash<QString, KNotification *> m_notifications;
QHash<QString, QPointer<KNotification>> m_invitations; 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: private Q_SLOTS:
void processNotificationJob(QPointer<Quotient::Connection> connection, Quotient::GetNotificationsJob *job, bool initialization); 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.kirigami 2.15 as Kirigami
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
import org.kde.kitemmodels 1.0
import org.kde.neochat 1.0 import org.kde.neochat 1.0
Kirigami.ScrollablePage { Kirigami.ScrollablePage {
id: root
property NeoChatRoom room 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.kirigami 2.15 as Kirigami
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
import org.kde.kitemmodels 1.0
import org.kde.neochat 1.0 import org.kde.neochat 1.0
Kirigami.ScrollablePage { Kirigami.ScrollablePage {
id: root
title: i18nc("@title:window", "Notifications") title: i18nc("@title:window", "Notifications")
leftPadding: 0 leftPadding: 0
rightPadding: 0 rightPadding: 0
ColumnLayout { ColumnLayout {
id: notificationLayout id: notificationLayout
@@ -22,10 +26,10 @@ Kirigami.ScrollablePage {
contentItem: MobileForm.FormCheckDelegate { contentItem: MobileForm.FormCheckDelegate {
text: i18n("Enable notifications for this account") text: i18n("Enable notifications for this account")
description: i18n("Whether push notifications are generated by your Matrix server") description: i18n("Whether push notifications are generated by your Matrix server")
checked: NotificationsManager.globalNotificationsEnabled checked: Controller.pushRuleModel.globalNotificationsEnabled
enabled: NotificationsManager.globalNotificationsSet enabled: Controller.pushRuleModel.globalNotificationsSet
onToggled: { onToggled: {
NotificationsManager.globalNotificationsEnabled = checked Controller.pushRuleModel.globalNotificationsEnabled = checked
} }
} }
} }
@@ -39,81 +43,17 @@ Kirigami.ScrollablePage {
MobileForm.FormCardHeader { MobileForm.FormCardHeader {
title: i18n("Room Notifications") title: i18n("Room Notifications")
} }
NotificationRuleItem { Repeater {
text: i18n("Messages in one-to-one chats") model: KSortFilterProxyModel {
sourceModel: Controller.pushRuleModel
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.oneToOneNotificationAction) filterRowCallback: function(source_row, source_parent) {
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.oneToOneNotificationAction) let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
enabled: NotificationsManager.oneToOneNotificationAction !== PushNotificationAction.Unknown return sectionRole == PushNotificationSection.Room;
notificationAction: NotificationsManager.oneToOneNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.oneToOneNotificationAction != notificationAction) {
NotificationsManager.oneToOneNotificationAction = notificationAction
} }
} }
}
NotificationRuleItem {
text: i18n("Encrypted messages in one-to-one chats")
visible: Controller.encryptionSupported delegate: ruleDelegate
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
}
}
} }
} }
} }
@@ -127,37 +67,17 @@ Kirigami.ScrollablePage {
MobileForm.FormCardHeader { MobileForm.FormCardHeader {
title: i18n("@Mentions") title: i18n("@Mentions")
} }
NotificationRuleItem { Repeater {
text: i18n("Messages containing my display name") model: KSortFilterProxyModel {
sourceModel: Controller.pushRuleModel
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.displayNameNotificationAction) filterRowCallback: function(source_row, source_parent) {
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.displayNameNotificationAction) let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
highlightable: true return sectionRole == PushNotificationSection.Mentions;
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.displayNameNotificationAction)
enabled: NotificationsManager.displayNameNotificationAction !== PushNotificationAction.Unknown
notificationAction: NotificationsManager.displayNameNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.displayNameNotificationAction != notificationAction) {
NotificationsManager.displayNameNotificationAction = notificationAction
} }
} }
}
NotificationRuleItem {
text: i18n("Whole room (@room) notifications")
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.roomNotificationAction) delegate: ruleDelegate
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
}
}
} }
} }
} }
@@ -171,47 +91,17 @@ Kirigami.ScrollablePage {
MobileForm.FormCardHeader { MobileForm.FormCardHeader {
title: i18n("Keywords") 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 { Repeater {
model: KeywordNotificationRuleModel { model: KSortFilterProxyModel {
id: keywordNotificationRuleModel sourceModel: Controller.pushRuleModel
}
delegate: NotificationRuleItem { filterRowCallback: function(source_row, source_parent) {
text: name let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
notificationAction: keywordNotificationAction.notificationAction return sectionRole == PushNotificationSection.Keywords;
notificationsOn: keywordNotificationAction.notificationsOn
notificationsOnModifiable: false
noisyOn: keywordNotificationAction.noisyOn
noisyModifiable: false
highlightOn: keywordNotificationAction.highlightOn
deletable: true
onDeleteItemChanged: {
if (deleteItem && deletable) {
keywordNotificationRuleModel.removeKeywordAtIndex(index)
}
} }
} }
delegate: ruleDelegate
} }
MobileForm.AbstractFormDelegate { MobileForm.AbstractFormDelegate {
Layout.fillWidth: true Layout.fillWidth: true
@@ -234,7 +124,7 @@ Kirigami.ScrollablePage {
} }
onAccepted: { onAccepted: {
keywordNotificationRuleModel.addKeyword(keywordAddField.text, PushNotificationAction.On) Controller.pushRuleModel.addKeyword(keywordAddField.text)
keywordAddField.text = "" keywordAddField.text = ""
} }
} }
@@ -248,7 +138,7 @@ Kirigami.ScrollablePage {
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown
onClicked: { onClicked: {
keywordNotificationRuleModel.addKeyword(keywordAddField.text, PushNotificationAction.On) Controller.pushRuleModel.addKeyword(keywordAddField.text)
keywordAddField.text = "" keywordAddField.text = ""
} }
@@ -271,59 +161,29 @@ Kirigami.ScrollablePage {
MobileForm.FormCardHeader { MobileForm.FormCardHeader {
title: i18n("Invites") title: i18n("Invites")
} }
NotificationRuleItem { Repeater {
text: i18n("Invites to a room") model: KSortFilterProxyModel {
sourceModel: Controller.pushRuleModel
notificationsOn: notificationLayout.isNotificationRuleOn(NotificationsManager.inviteNotificationAction) filterRowCallback: function(source_row, source_parent) {
noisyOn: notificationLayout.isNotificationRuleNoisy(NotificationsManager.inviteNotificationAction) let sectionRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), PushRuleModel.SectionRole)
highlightable: true return sectionRole == PushNotificationSection.Invites;
highlightOn: notificationLayout.isNotificationRuleHighlight(NotificationsManager.inviteNotificationAction)
enabled: NotificationsManager.inviteNotificationAction !== PushNotificationAction.Unknown
notificationAction: NotificationsManager.inviteNotificationAction
onNotificationActionChanged: {
if (notificationAction && NotificationsManager.inviteNotificationAction != notificationAction) {
NotificationsManager.inviteNotificationAction = notificationAction
} }
} }
}
NotificationRuleItem {
text: i18n("Call invitation")
// TODO enable this option when calls are supported delegate: ruleDelegate
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
}
}
} }
} }
} }
}
function isNotificationRuleOn(action) { Component {
return action == PushNotificationAction.On || id: ruleDelegate
action == PushNotificationAction.Noisy || NotificationRuleItem {
action == PushNotificationAction.Highlight || onDeleteRule: {
action == PushNotificationAction.NoisyHighlight Controller.pushRuleModel.removeKeyword(id)
} }
onActionChanged: (action) => Controller.pushRuleModel.setPushRuleAction(id, action)
function isNotificationRuleNoisy(action) {
return action == PushNotificationAction.Noisy ||
action == PushNotificationAction.NoisyHighlight
}
function isNotificationRuleHighlight(action) {
return action == PushNotificationAction.Highlight ||
action == PushNotificationAction.NoisyHighlight
} }
} }
} }

View File

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