Commit 723b6d13 authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇

Completely wire up old dataengine and further touches

- Old dataengine is fully functional now
  Except the inhibition stuff but I'm not sure if this is worth keeping the way it is
- "More" menu opens on press now and highlights
- Invoking any action closes the notification now
  KNotification explicitly does that for us but e.g. GTK does not
- Add clear button for history
- Restore kbroadcastnotification support
- Allow forgetting seen application (for KCM)
- Let users "create" notifications by calling NotificationServer::add
- When no application name is provided look up the sender's process name as last resort
- Add kdebugsettings categories file
parent 1c4de1d4
...@@ -37,7 +37,8 @@ NotificationApplet::NotificationApplet(QObject *parent, const QVariantList &data ...@@ -37,7 +37,8 @@ NotificationApplet::NotificationApplet(QObject *parent, const QVariantList &data
{ {
static bool s_typesRegistered = false; static bool s_typesRegistered = false;
if (!s_typesRegistered) { if (!s_typesRegistered) {
const char uri[] = "org.kde.plasma.private.notifications"; // FIXME register into org.kde.plasma.private.notifications once old applet is gone
const char uri[] = "org.kde.plasma.private.notificationsng";
qmlRegisterType<FileMenu>(uri, 2, 0, "FileMenu"); qmlRegisterType<FileMenu>(uri, 2, 0, "FileMenu");
qmlRegisterType<Thumbnailer>(uri, 2, 0, "Thumbnailer"); qmlRegisterType<Thumbnailer>(uri, 2, 0, "Thumbnailer");
qmlProtectModule(uri, 2); qmlProtectModule(uri, 2);
......
...@@ -105,20 +105,30 @@ ColumnLayout { ...@@ -105,20 +105,30 @@ ColumnLayout {
} }
} }
Item { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredWidth: units.gridUnit * 18
Layout.preferredHeight: units.gridUnit * 20
PlasmaExtras.Heading { PlasmaExtras.Heading {
width: parent.width Layout.fillWidth: true
level: 3 level: 3
opacity: 0.6 opacity: 0.6
visible: list.count === 0 text: list.count === 0 ? i18n("No unread notifications.") : i18n("Notifications")
text: i18n("No unread notifications.")
} }
PlasmaComponents.ToolButton {
iconName: "edit-clear-history"
tooltip: i18n("Clear History")
visible: historyModel.expiredNotificationsCount > 0
onClicked: historyModel.clear(NotificationManager.Notifications.ClearExpired)
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredWidth: units.gridUnit * 18
Layout.preferredHeight: units.gridUnit * 20
PlasmaExtras.ScrollArea { PlasmaExtras.ScrollArea {
anchors.fill: parent anchors.fill: parent
...@@ -207,7 +217,10 @@ ColumnLayout { ...@@ -207,7 +217,10 @@ ColumnLayout {
onDismissClicked: model.dismissed = false onDismissClicked: model.dismissed = false
onConfigureClicked: historyModel.configure(historyModel.index(index, 0)) onConfigureClicked: historyModel.configure(historyModel.index(index, 0))
onActionInvoked: historyModel.invokeAction(historyModel.index(index, 0), actionName) onActionInvoked: {
historyModel.invokeAction(historyModel.index(index, 0), actionName);
//historyModel.close(historyModel.index(index, 0));
}
onOpenUrl: { onOpenUrl: {
Qt.openUrlExternally(url); Qt.openUrlExternally(url);
//historyModel.close(historyModel.index(index, 0)) //historyModel.close(historyModel.index(index, 0))
......
...@@ -26,7 +26,8 @@ import org.kde.plasma.core 2.0 as PlasmaCore ...@@ -26,7 +26,8 @@ import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.notificationmanager 1.0 as NotificationManager import org.kde.notificationmanager 1.0 as NotificationManager
import org.kde.plasma.private.notifications 2.0 as Notifications
import org.kde.plasma.private.notificationsng 2.0 as Notifications // FIXME
ColumnLayout { ColumnLayout {
id: jobItem id: jobItem
...@@ -149,7 +150,15 @@ ColumnLayout { ...@@ -149,7 +150,15 @@ ColumnLayout {
id: otherFileActionsButton id: otherFileActionsButton
iconName: "application-menu" iconName: "application-menu"
tooltip: i18n("More Options...") tooltip: i18n("More Options...")
onClicked: otherFileActionsMenu.open(-1, -1) checkable: true
onPressedChanged: {
if (pressed) {
checked = Qt.binding(function() {
return otherFileActionsMenu.visible;
});
otherFileActionsMenu.open(-1, -1);
}
}
Notifications.FileMenu { Notifications.FileMenu {
id: otherFileActionsMenu id: otherFileActionsMenu
......
...@@ -27,7 +27,7 @@ import org.kde.plasma.extras 2.0 as PlasmaExtras ...@@ -27,7 +27,7 @@ import org.kde.plasma.extras 2.0 as PlasmaExtras
import org.kde.kquickcontrolsaddons 2.0 as KQCAddons import org.kde.kquickcontrolsaddons 2.0 as KQCAddons
import org.kde.plasma.private.notifications 2.0 as Notifications import org.kde.plasma.private.notificationsng 2.0 as Notifications // FIXME
MouseArea { MouseArea {
id: thumbnailArea id: thumbnailArea
...@@ -167,17 +167,20 @@ MouseArea { ...@@ -167,17 +167,20 @@ MouseArea {
} }
tooltip: i18n("More Options...") tooltip: i18n("More Options...")
Accessible.name: tooltip Accessible.name: tooltip
checkable: true
iconName: "application-menu" iconName: "application-menu"
checkable: true
onClicked: { onPressedChanged: {
checked = Qt.binding(function() { if (pressed) {
return thumbnailer.menuVisible; // fake "pressed" while menu is open
}); checked = Qt.binding(function() {
return fileMenu.visible;
fileMenu.visualParent = this; });
// -1 tells it to "align bottom left of visualParent (this)"
fileMenu.open(-1, -1); fileMenu.visualParent = this;
// -1 tells it to "align bottom left of visualParent (this)"
fileMenu.open(-1, -1);
}
} }
} }
} }
......
...@@ -266,8 +266,14 @@ QtObject { ...@@ -266,8 +266,14 @@ QtObject {
onCloseClicked: popupNotificationsModel.close(popupNotificationsModel.index(index, 0)) onCloseClicked: popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
onDismissClicked: model.dismissed = true onDismissClicked: model.dismissed = true
onConfigureClicked: popupNotificationsModel.configure(popupNotificationsModel.index(index, 0)) onConfigureClicked: popupNotificationsModel.configure(popupNotificationsModel.index(index, 0))
onDefaultActionInvoked: popupNotificationsModel.invokeDefaultAction(popupNotificationsModel.index(index, 0)) onDefaultActionInvoked: {
onActionInvoked: popupNotificationsModel.invokeAction(popupNotificationsModel.index(index, 0), actionName) popupNotificationsModel.invokeDefaultAction(popupNotificationsModel.index(index, 0))
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
}
onActionInvoked: {
popupNotificationsModel.invokeAction(popupNotificationsModel.index(index, 0), actionName)
popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
}
onOpenUrl: { onOpenUrl: {
Qt.openUrlExternally(url); Qt.openUrlExternally(url);
popupNotificationsModel.close(popupNotificationsModel.index(index, 0)) popupNotificationsModel.close(popupNotificationsModel.index(index, 0))
...@@ -286,6 +292,7 @@ QtObject { ...@@ -286,6 +292,7 @@ QtObject {
// Apps with notifyrc can already be configured anyway // Apps with notifyrc can already be configured anyway
if (model.desktopEntry && !model.notifyRcName) { if (model.desktopEntry && !model.notifyRcName) {
notificationSettings.registerKnownApplication(model.desktopEntry); notificationSettings.registerKnownApplication(model.desktopEntry);
notificationSettings.save();
} }
} }
} }
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
#include "notificationaction.h" #include "notificationaction.h"
#include "notificationsengine.h" #include "notificationsengine.h"
#include <notificationmanager/notificationserver.h> #include "notificationserver.h"
#include <klocalizedstring.h> #include <klocalizedstring.h>
......
...@@ -22,47 +22,41 @@ ...@@ -22,47 +22,41 @@
#include "notificationsadaptor.h" #include "notificationsadaptor.h"
#include "notificationsanitizer.h" #include "notificationsanitizer.h"
#include <notificationmanager/notificationserver.h> #include "notificationserver.h"
#include <notificationmanager/notification.h> #include "notification.h"
#include <KConfig>
#include <KConfigGroup> #include <KConfigGroup>
#include <klocalizedstring.h> #include <klocalizedstring.h>
#include <KSharedConfig> #include <KSharedConfig>
#include <KNotifyConfigWidget> #include <KNotifyConfigWidget>
#include <KUser>
#include <QGuiApplication> #include <QGuiApplication>
#include <QRegularExpression>
#include <Plasma/DataContainer> #include <Plasma/DataContainer>
#include <Plasma/Service> #include <Plasma/Service>
#include <QImage> #include <QImage>
#include <kiconloader.h>
#include <KConfig>
// for ::kill
#include <signal.h>
#include "debug.h" #include "debug.h"
using namespace NotificationManager; using namespace NotificationManager;
NotificationsEngine::NotificationsEngine( QObject* parent, const QVariantList& args ) NotificationsEngine::NotificationsEngine( QObject* parent, const QVariantList& args )
: Plasma::DataEngine( parent, args ), m_nextId( 1 ), m_alwaysReplaceAppsList({QStringLiteral("Clementine"), QStringLiteral("Spotify"), QStringLiteral("Amarok")}) : Plasma::DataEngine( parent, args )
{ {
connect(&NotificationServer::self(), &NotificationServer::notificationAdded, this, [this](const Notification &notification) { connect(&NotificationServer::self(), &NotificationServer::notificationAdded, this, [this](const Notification &notification) {
// FIXME handle replaced
notificationAdded(notification); notificationAdded(notification);
}); });
connect(&NotificationServer::self(), &NotificationServer::notificationReplaced, this, [this](uint replacedId, const Notification &notification) { connect(&NotificationServer::self(), &NotificationServer::notificationReplaced, this, [this](uint replacedId, const Notification &notification) {
// Notification will already have the correct identical ID
Q_UNUSED(replacedId);
notificationAdded(notification);
}); });
connect(&NotificationServer::self(), &NotificationServer::notificationRemoved, this, [this](uint id, NotificationServer::CloseReason reason) { connect(&NotificationServer::self(), &NotificationServer::notificationRemoved, this, [this](uint id, NotificationServer::CloseReason reason) {
Q_UNUSED(reason);
const QString source = QStringLiteral("notification %1").arg(id); const QString source = QStringLiteral("notification %1").arg(id);
// if we don't have that notification in our local list, // if we don't have that notification in our local list,
// it has already been closed so don't notify a second time // it has already been closed so don't notify a second time
...@@ -70,44 +64,6 @@ NotificationsEngine::NotificationsEngine( QObject* parent, const QVariantList& a ...@@ -70,44 +64,6 @@ NotificationsEngine::NotificationsEngine( QObject* parent, const QVariantList& a
removeSource(source); removeSource(source);
} }
}); });
// FIXME let the new notification plasmoid do the killing
/*
if (!registerDBusService()) {
QDBusConnection dbus = QDBusConnection::sessionBus();
// Retrieve the pid of the current o.f.Notifications service
QDBusReply<uint> pidReply = dbus.interface()->servicePid(QStringLiteral("org.freedesktop.Notifications"));
uint pid = pidReply.value();
// Check if it's not the same app as our own
if (pid != qApp->applicationPid()) {
QDBusReply<uint> plasmaPidReply = dbus.interface()->servicePid(QStringLiteral("org.kde.plasmashell"));
// It's not the same but check if it isn't plasma,
// we don't want to kill Plasma
if (pid != plasmaPidReply.value()) {
qCDebug(NOTIFICATIONS) << "Terminating current Notification service with pid" << pid;
// Now finally terminate the service and register our own
::kill(pid, SIGTERM);
// Wait 3 seconds and then try registering it again
QTimer::singleShot(3000, this, &NotificationsEngine::registerDBusService);
}
}
}*/
// FIXME implement in notification server
/*
KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("Notifications"));
const bool broadcastsEnabled = config.readEntry("ListenForBroadcasts", false);
if (broadcastsEnabled) {
qCDebug(NOTIFICATIONS) << "Notifications engine is configured to listen for broadcasts";
QDBusConnection::systemBus().connect({}, {}, QStringLiteral("org.kde.BroadcastNotifications"),
QStringLiteral("Notify"), this, SLOT(onBroadcastNotification(QMap<QString,QVariant>)));
}*/
// Read additional single-notification-popup-only from a config file
//KConfig singlePopupConfig(QStringLiteral("plasma_single_popup_notificationrc"));
//KConfigGroup singlePopupConfigGroup(&singlePopupConfig, "General");
//m_alwaysReplaceAppsList += QSet<QString>::fromList(singlePopupConfigGroup.readEntry("applications", QStringList()));
} }
NotificationsEngine::~NotificationsEngine() NotificationsEngine::~NotificationsEngine()
...@@ -121,79 +77,29 @@ void NotificationsEngine::init() ...@@ -121,79 +77,29 @@ void NotificationsEngine::init()
void NotificationsEngine::notificationAdded(const Notification &notification) void NotificationsEngine::notificationAdded(const Notification &notification)
{ {
// FIXME const QString app_name = notification.applicationName();
/*foreach(NotificationInhibiton *ni, m_inhibitions) { const QString appRealName = notification.notifyRcName();
if (hints[ni->hint] == ni->value) { const QString eventId = notification.eventId(); // FIXME = hints[QStringLiteral("x-kde-eventId")].toString();
qCDebug(NOTIFICATIONS) << "notification inhibited. Skipping"; const QStringList urls = QUrl::toStringList(notification.urls());
return -1; const QString desktopEntry = notification.desktopEntry();
} const QString summary = notification.summary();
}*/
QString bodyFinal = notification.body(); // is already sanitized by NotificationManager
uint partOf = 0;
const QString appRealName; // FIXME = hints[QStringLiteral("x-kde-appname")].toString();
const QString eventId; // FIXME = hints[QStringLiteral("x-kde-eventId")].toString();
const bool skipGrouping = false;// FIXME hints[QStringLiteral("x-kde-skipGrouping")].toBool();
const QStringList urls; // FIXME there's no QUrl toStringList? = notification.urls();
const QString desktopEntry; // FIXME = hints[QStringLiteral("desktop-entry")].toString();
// group notifications that have the same title coming from the same app
// or if they are on the "blacklist", honor the skipGrouping hint sent
// FIXME
/*if (!replaces_id && m_activeNotifications.values().contains(app_name + summary) && !skipGrouping && urls.isEmpty() && !m_alwaysReplaceAppsList.contains(app_name)) {
// cut off the "notification " from the source name
partOf = m_activeNotifications.key(app_name + summary).midRef(13).toUInt();
}*/
//qCDebug(NOTIFICATIONS) << "Currrent active notifications:" << m_activeNotifications;
//qCDebug(NOTIFICATIONS) << "Guessing partOf as:" << partOf;
//qCDebug(NOTIFICATIONS) << " New Notification: " << summary << body << timeout << "& Part of:" << partOf;
QString bodyFinal = notification.body(); // is already sanitized
QString summaryFinal = notification.summary(); QString summaryFinal = notification.summary();
int timeout = notification.timeout(); int timeout = notification.timeout();
if (partOf > 0) { if (bodyFinal.isEmpty()) {
const QString source = QStringLiteral("notification %1").arg(partOf);
Plasma::DataContainer *container = containerForSource(source);
if (container) {
// append the body text
const QString previousBody = container->data()[QStringLiteral("body")].toString();
if (previousBody != bodyFinal) {
// FIXME: This will just append the entire old XML document to another one, leading to:
// <?xml><html>old</html><br><?xml><html>new</html>
// It works but is not very clean.
bodyFinal = previousBody + QStringLiteral("<br/>") + bodyFinal;
}
//replaces_id = partOf;
// remove the old notification and replace it with the new one
// TODO: maybe just update the current notification?
//CloseNotification(partOf);
}
} else if (bodyFinal.isEmpty()) {
//some ridiculous apps will send just a title (#372112), in that case, treat it as though there's only a body //some ridiculous apps will send just a title (#372112), in that case, treat it as though there's only a body
//bodyFinal = summary; bodyFinal = summary;
//summaryFinal = app_name; summaryFinal = app_name;
} }
uint id = notification.id();// replaces_id ? replaces_id : m_nextId++; uint id = notification.id();// replaces_id ? replaces_id : m_nextId++;
// If the current app is in the "blacklist"... QString appname_str = app_name;
/*if (m_alwaysReplaceAppsList.contains(app_name)) {
// ...check if we already have a notification from that particular
// app and if yes, use its id to replace it
if (m_notificationsFromReplaceableApp.contains(app_name)) {
id = m_notificationsFromReplaceableApp.value(app_name);
} else {
m_notificationsFromReplaceableApp.insert(app_name, id);
}
}*/
/*QString appname_str = app_name;
if (appname_str.isEmpty()) { if (appname_str.isEmpty()) {
appname_str = i18n("Unknown Application"); appname_str = i18n("Unknown Application");
}*/ }
bool isPersistent = (timeout == 0); bool isPersistent = (timeout == 0);
...@@ -222,7 +128,21 @@ void NotificationsEngine::notificationAdded(const Notification &notification) ...@@ -222,7 +128,21 @@ void NotificationsEngine::notificationAdded(const Notification &notification)
notificationData.insert(QStringLiteral("appIcon"), notification.applicationIconName()); notificationData.insert(QStringLiteral("appIcon"), notification.applicationIconName());
notificationData.insert(QStringLiteral("summary"), summaryFinal); notificationData.insert(QStringLiteral("summary"), summaryFinal);
notificationData.insert(QStringLiteral("body"), bodyFinal); notificationData.insert(QStringLiteral("body"), bodyFinal);
notificationData.insert(QStringLiteral("actions"), QStringList()); // FIXME
QStringList actions;
for (int i = 0; i < notification.actionNames().count(); ++i) {
actions << notification.actionNames().at(i) << notification.actionLabels().at(i);
}
// NotificationManager hides the configure and default stuff from us but we need to re-add them
// to the actions list for compatibility
if (!notification.configureActionLabel().isEmpty()) {
actions << QStringLiteral("settings") << notification.configureActionLabel();
}
if (notification.hasDefaultAction()) {
actions << QStringLiteral("default") << QString();
}
notificationData.insert(QStringLiteral("actions"), actions);
notificationData.insert(QStringLiteral("isPersistent"), isPersistent); notificationData.insert(QStringLiteral("isPersistent"), isPersistent);
notificationData.insert(QStringLiteral("expireTimeout"), timeout); notificationData.insert(QStringLiteral("expireTimeout"), timeout);
...@@ -234,32 +154,30 @@ void NotificationsEngine::notificationAdded(const Notification &notification) ...@@ -234,32 +154,30 @@ void NotificationsEngine::notificationAdded(const Notification &notification)
notificationData.insert(QStringLiteral("appServiceIcon"), service->icon()); notificationData.insert(QStringLiteral("appServiceIcon"), service->icon());
} }
bool configurable = false;
if (!appRealName.isEmpty()) {
if (m_configurableApplications.contains(appRealName)) {
configurable = m_configurableApplications.value(appRealName);
} else {
// Check whether the application actually has notifications we can configure
KConfig config(appRealName + QStringLiteral(".notifyrc"), KConfig::NoGlobals);
config.addConfigSources(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
QStringLiteral("knotifications5/") + appRealName + QStringLiteral(".notifyrc")));
const QRegularExpression regexp(QStringLiteral("^Event/([^/]*)$"));
configurable = !config.groupList().filter(regexp).isEmpty();
m_configurableApplications.insert(appRealName, configurable);
}
}
notificationData.insert(QStringLiteral("appRealName"), appRealName); notificationData.insert(QStringLiteral("appRealName"), appRealName);
notificationData.insert(QStringLiteral("configurable"), configurable); // NotificationManager configurable is anything that has a notifyrc or desktop entry
// but the old stuff assumes only stuff with notifyrc to be configurable
notificationData.insert(QStringLiteral("configurable"), !notification.notifyRcName().isEmpty());
QImage image = notification.image(); QImage image = notification.image();
notificationData.insert(QStringLiteral("image"), image.isNull() ? QVariant() : image); notificationData.insert(QStringLiteral("image"), image.isNull() ? QVariant() : image);
// FIXME did we even have this? int urgency = -1;
/*if (hints.contains(QStringLiteral("urgency"))) { switch (notification.urgency()) {
notificationData.insert(QStringLiteral("urgency"), hints[QStringLiteral("urgency")].toInt()); case Notifications::LowUrgency:
}*/ urgency = 0;
break;
case Notifications::NormalUrgency:
urgency = 1;
break;
case Notifications::CriticalUrgency:
urgency = 2;
break;
}
if (urgency > -1) {
notificationData.insert(QStringLiteral("urgency"), urgency);
}
notificationData.insert(QStringLiteral("urls"), urls); notificationData.insert(QStringLiteral("urls"), urls);
...@@ -268,12 +186,15 @@ void NotificationsEngine::notificationAdded(const Notification &notification) ...@@ -268,12 +186,15 @@ void NotificationsEngine::notificationAdded(const Notification &notification)
m_activeNotifications.insert(source, notification.applicationName() + notification.summary()); m_activeNotifications.insert(source, notification.applicationName() + notification.summary());
} }
uint NotificationsEngine::Notify(const QString &app_name, uint replaces_id, void NotificationsEngine::removeNotification(uint id, uint closeReason)
const QString &app_icon, const QString &summary, const QString &body,
const QStringList &actions, const QVariantMap &hints, int timeout)
{ {
// FIXME wire this thing up to the new one, it's used by notification action job or something const QString source = QStringLiteral("notification %1").arg(id);
return 0; // if we don't have that notification in our local list,
// it has already been closed so don't notify a second time
if (m_activeNotifications.remove(source) > 0) {
removeSource(source);
NotificationServer::self().closeNotification(id, static_cast<NotificationServer::CloseReason>(closeReason));
}
} }
Plasma::Service* NotificationsEngine::serviceForSource(const QString& source) Plasma::Service* NotificationsEngine::serviceForSource(const QString& source)
...@@ -284,8 +205,16 @@ Plasma::Service* NotificationsEngine::serviceForSource(const QString& source) ...@@ -284,8 +205,16 @@ Plasma::Service* NotificationsEngine::serviceForSource(const QString& source)
int NotificationsEngine::createNotification(const QString &appName, const QString &appIcon, const QString &summary, int NotificationsEngine::createNotification(const QString &appName, const QString &appIcon, const QString &summary,
const QString &body, int timeout, const QStringList &actions, const QVariantMap &hints) const QString &body, int timeout, const QStringList &actions, const QVariantMap &hints)
{ {
Notify(appName, 0, appIcon, summary, body, actions, hints, timeout); Notification notification;
return m_nextId; notification.setApplicationName(appName);
notification.setApplicationIconName(appIcon);
notification.setSummary(summary);
notification.setBody(body); // sanitizes
notification.setActions(actions);
notification.setTimeout(timeout);
notification.processHints(hints);
NotificationServer::self().add(notification);
return 0;
} }
void NotificationsEngine::configureNotification(const QString &appName, const QString &eventId) void NotificationsEngine::configureNotification(const QString &appName, const QString &eventId)
...@@ -312,45 +241,6 @@ QSharedPointer<NotificationInhibiton> NotificationsEngine::createInhibition(cons ...@@ -312,45 +241,6 @@ QSharedPointer<NotificationInhibiton> NotificationsEngine::createInhibition(cons
return rc; return rc;
} }
void NotificationsEngine::onBroadcastNotification(const QMap<QString, QVariant> &properties)
{
qCDebug(NOTIFICATIONS) << "Received broadcast notification";
const auto currentUserId = KUserId::currentEffectiveUserId().nativeId();
// a QVariantList with ints arrives as QDBusArgument here, using a QStringList for simplicity
const QStringList &userIds = properties.value(QStringLiteral("uids")).toStringList();
if (!userIds.isEmpty()) {
auto it = std::find_if(userIds.constBegin(), userIds.constEnd(), [currentUserId](const QVariant &id) {
bool ok;
auto uid = id.toString().toLongLong(&ok);
return ok && uid == currentUserId;
});
if (it == userIds.constEnd()) {