diff --git a/harbour-fernschreiber.pro b/harbour-fernschreiber.pro
index 3731efa..63b3d5c 100644
--- a/harbour-fernschreiber.pro
+++ b/harbour-fernschreiber.pro
@@ -29,6 +29,7 @@ SOURCES += src/harbour-fernschreiber.cpp \
src/dbusinterface.cpp \
src/emojisearchworker.cpp \
src/fernschreiberutils.cpp \
+ src/knownusersmodel.cpp \
src/mceinterface.cpp \
src/notificationmanager.cpp \
src/processlauncher.cpp \
@@ -154,6 +155,7 @@ HEADERS += \
src/debuglogjs.h \
src/emojisearchworker.h \
src/fernschreiberutils.h \
+ src/knownusersmodel.h \
src/mceinterface.h \
src/notificationmanager.h \
src/processlauncher.h \
diff --git a/qml/pages/ChatPage.qml b/qml/pages/ChatPage.qml
index 20a1bdc..bffe528 100644
--- a/qml/pages/ChatPage.qml
+++ b/qml/pages/ChatPage.qml
@@ -285,6 +285,12 @@ Page {
} else {
chatPage.emojiProposals = null;
}
+ if (currentWord.length > 1 && currentWord.charAt(0) === '@') {
+ knownUsersRepeater.model = knownUsersProxyModel;
+ knownUsersProxyModel.setFilterWildcard("*" + currentWord.substring(1) + "*");
+ } else {
+ knownUsersRepeater.model = undefined;
+ }
}
function replaceMessageText(text, cursorPosition, newText) {
@@ -1271,6 +1277,83 @@ Page {
}
}
+ Column {
+ id: atMentionColumn
+ width: parent.width
+ anchors.horizontalCenter: parent.horizontalCenter
+ visible: knownUsersRepeater.count > 0 ? true : false
+ opacity: knownUsersRepeater.count > 0 ? 1 : 0
+ Behavior on opacity { NumberAnimation {} }
+ spacing: Theme.paddingMedium
+
+ Flickable {
+ width: parent.width
+ height: atMentionResultRow.height + Theme.paddingSmall
+ anchors.horizontalCenter: parent.horizontalCenter
+ contentWidth: atMentionResultRow.width
+ clip: true
+ Row {
+ id: atMentionResultRow
+ spacing: Theme.paddingMedium
+ Repeater {
+ id: knownUsersRepeater
+
+ Item {
+ id: knownUserItem
+ height: singleAtMentionRow.height
+ width: singleAtMentionRow.width
+
+ property string atMentionText: "@" + (user_name ? user_name : user_id + "(" + title + ")");
+
+ Row {
+ id: singleAtMentionRow
+ spacing: Theme.paddingSmall
+
+ Item {
+ width: Theme.fontSizeHuge
+ height: Theme.fontSizeHuge
+ anchors.verticalCenter: parent.verticalCenter
+ ProfileThumbnail {
+ id: atMentionThumbnail
+ replacementStringHint: title
+ width: parent.width
+ height: parent.width
+ photoData: photo_small
+ }
+ }
+
+ Column {
+ Text {
+ text: Emoji.emojify(title, Theme.fontSizeExtraSmall)
+ textFormat: Text.StyledText
+ color: Theme.primaryColor
+ font.pixelSize: Theme.fontSizeExtraSmall
+ font.bold: true
+ }
+ Text {
+ id: userHandleText
+ text: user_handle
+ textFormat: Text.StyledText
+ color: Theme.primaryColor
+ font.pixelSize: Theme.fontSizeExtraSmall
+ }
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ replaceMessageText(newMessageTextField.text, newMessageTextField.cursorPosition, knownUserItem.atMentionText);
+ knownUsersRepeater.model = undefined;
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+
Row {
width: parent.width
spacing: Theme.paddingSmall
diff --git a/src/harbour-fernschreiber.cpp b/src/harbour-fernschreiber.cpp
index e35b8fd..611fd15 100644
--- a/src/harbour-fernschreiber.cpp
+++ b/src/harbour-fernschreiber.cpp
@@ -42,6 +42,7 @@
#include "stickermanager.h"
#include "tgsplugin.h"
#include "fernschreiberutils.h"
+#include "knownusersmodel.h"
#include "contactsmodel.h"
// The default filter can be overridden by QT_LOGGING_RULES envinronment variable, e.g.
@@ -97,6 +98,14 @@ int main(int argc, char *argv[])
StickerManager stickerManager(tdLibWrapper);
context->setContextProperty("stickerManager", &stickerManager);
+ KnownUsersModel knownUsersModel(tdLibWrapper, view.data());
+ context->setContextProperty("knownUsersModel", &knownUsersModel);
+ QSortFilterProxyModel knownUsersProxyModel(view.data());
+ knownUsersProxyModel.setSourceModel(&knownUsersModel);
+ knownUsersProxyModel.setFilterRole(KnownUsersModel::RoleFilter);
+ knownUsersProxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
+ context->setContextProperty("knownUsersProxyModel", &knownUsersProxyModel);
+
ContactsModel contactsModel(tdLibWrapper, view.data());
context->setContextProperty("contactsModel", &contactsModel);
QSortFilterProxyModel contactsProxyModel(view.data());
diff --git a/src/knownusersmodel.cpp b/src/knownusersmodel.cpp
new file mode 100644
index 0000000..50fdbd2
--- /dev/null
+++ b/src/knownusersmodel.cpp
@@ -0,0 +1,71 @@
+/*
+ Copyright (C) 2020 Sebastian J. Wolf and other contributors
+
+ This file is part of Fernschreiber.
+
+ Fernschreiber is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Fernschreiber is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Fernschreiber. If not, see .
+*/
+
+#include "knownusersmodel.h"
+
+#define DEBUG_MODULE KnwonUsersModel
+#include "debuglog.h"
+
+KnownUsersModel::KnownUsersModel(TDLibWrapper *tdLibWrapper, QObject *parent)
+ : QAbstractListModel(parent)
+{
+ this->tdLibWrapper = tdLibWrapper;
+
+ connect(this->tdLibWrapper, SIGNAL(userUpdated(QString, QVariantMap)), this, SLOT(handleUserUpdated(QString, QVariantMap)));
+}
+
+QHash KnownUsersModel::roleNames() const
+{
+ QHash roles;
+ roles.insert(KnownUserRole::RoleDisplay, "display");
+ roles.insert(KnownUserRole::RoleUserId, "user_id");
+ roles.insert(KnownUserRole::RoleTitle, "title");
+ roles.insert(KnownUserRole::RoleUsername, "user_name");
+ roles.insert(KnownUserRole::RoleUserHandle, "user_handle");
+ roles.insert(KnownUserRole::RolePhotoSmall, "photo_small");
+ roles.insert(KnownUserRole::RoleFilter, "filter");
+ return roles;
+}
+
+int KnownUsersModel::rowCount(const QModelIndex &) const
+{
+ return this->knownUsers.size();
+}
+
+QVariant KnownUsersModel::data(const QModelIndex &index, int role) const
+{
+ if (index.isValid()) {
+ QVariantMap requestedUser = knownUsers.values().value(index.row()).toMap();
+ switch (static_cast(role)) {
+ case KnownUserRole::RoleDisplay: return requestedUser;
+ case KnownUserRole::RoleUserId: return requestedUser.value("id");
+ case KnownUserRole::RoleTitle: return QString(requestedUser.value("first_name").toString() + " " + requestedUser.value("last_name").toString()).trimmed();
+ case KnownUserRole::RoleUsername: return requestedUser.value("username");
+ case KnownUserRole::RoleUserHandle: return QString("@" + (requestedUser.value("username").toString().isEmpty() ? requestedUser.value("id").toString() : requestedUser.value("username").toString()));
+ case KnownUserRole::RolePhotoSmall: return requestedUser.value("profile_photo").toMap().value("small");
+ case KnownUserRole::RoleFilter: return QString(requestedUser.value("first_name").toString() + " " + requestedUser.value("last_name").toString() + " " + requestedUser.value("username").toString()).trimmed();
+ }
+ }
+ return QVariant();
+}
+
+void KnownUsersModel::handleUserUpdated(const QString &userId, const QVariantMap &userInformation)
+{
+ this->knownUsers.insert(userId, userInformation);
+}
diff --git a/src/knownusersmodel.h b/src/knownusersmodel.h
new file mode 100644
index 0000000..fc011f0
--- /dev/null
+++ b/src/knownusersmodel.h
@@ -0,0 +1,57 @@
+/*
+ Copyright (C) 2020 Sebastian J. Wolf and other contributors
+
+ This file is part of Fernschreiber.
+
+ Fernschreiber is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Fernschreiber is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Fernschreiber. If not, see .
+*/
+
+#ifndef KNOWNUSERSMODEL_H
+#define KNOWNUSERSMODEL_H
+
+#include
+#include
+#include "tdlibwrapper.h"
+
+class KnownUsersModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+
+ enum KnownUserRole {
+ RoleDisplay = Qt::DisplayRole,
+ RoleUserId,
+ RoleTitle,
+ RoleUsername,
+ RoleUserHandle,
+ RolePhotoSmall,
+ RoleFilter
+ };
+
+ KnownUsersModel(TDLibWrapper *tdLibWrapper, QObject *parent = nullptr);
+
+ virtual QHash roleNames() const override;
+ virtual int rowCount(const QModelIndex &) const override;
+ virtual QVariant data(const QModelIndex &index, int role) const override;
+
+public slots:
+ void handleUserUpdated(const QString &userId, const QVariantMap &userInformation);
+
+private:
+ TDLibWrapper *tdLibWrapper;
+ QVariantMap knownUsers;
+
+};
+
+#endif // KNOWNUSERSMODEL_H
diff --git a/src/tdlibwrapper.cpp b/src/tdlibwrapper.cpp
index 2ea1c91..64f2edf 100644
--- a/src/tdlibwrapper.cpp
+++ b/src/tdlibwrapper.cpp
@@ -29,6 +29,9 @@
#include
#include
#include
+#include
+#include
+#include
#define DEBUG_MODULE TDLibWrapper
#include "debuglog.h"
@@ -316,6 +319,18 @@ void TDLibWrapper::unpinMessage(const QString &chatId)
this->sendRequest(requestObject);
}
+static bool compareReplacements(const QVariant &replacement1, const QVariant &replacement2)
+{
+ const QVariantMap replacementMap1 = replacement1.toMap();
+ const QVariantMap replacementMap2 = replacement2.toMap();
+
+ if (replacementMap1.value("startIndex").toInt() < replacementMap2.value("startIndex").toInt()) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
void TDLibWrapper::sendTextMessage(const QString &chatId, const QString &message, const QString &replyToMessageId)
{
LOG("Sending text message" << chatId << message << replyToMessageId);
@@ -327,8 +342,50 @@ void TDLibWrapper::sendTextMessage(const QString &chatId, const QString &message
}
QVariantMap inputMessageContent;
inputMessageContent.insert(_TYPE, "inputMessageText");
+
+ // Postprocess message (e.g. for @-mentioning)
+ QString processedMessage = message;
+ QVariantList replacements;
+ QRegularExpression atMentionIdRegex("\\@(\\d+)\\(([^\\)]+)\\)");
+ QRegularExpressionMatchIterator atMentionIdMatchIterator = atMentionIdRegex.globalMatch(processedMessage);
+ while (atMentionIdMatchIterator.hasNext()) {
+ QRegularExpressionMatch nextAtMentionId = atMentionIdMatchIterator.next();
+ LOG("@Mentioning with user ID! Start Index: " << nextAtMentionId.capturedStart(0) << ", length: " << nextAtMentionId.capturedLength(0) << ", user ID: " << nextAtMentionId.captured(1) << ", plain text: " << nextAtMentionId.captured(2));
+ QVariantMap replacement;
+ replacement.insert("startIndex", nextAtMentionId.capturedStart(0));
+ replacement.insert("length", nextAtMentionId.capturedLength(0));
+ replacement.insert("userId", nextAtMentionId.captured(1));
+ replacement.insert("plainText", nextAtMentionId.captured(2));
+ replacements.append(replacement);
+ }
+
QVariantMap formattedText;
- formattedText.insert("text", message);
+
+ if (!replacements.isEmpty()) {
+ QVariantList entities;
+ std::sort(replacements.begin(), replacements.end(), compareReplacements);
+ QListIterator replacementsIterator(replacements);
+ int offsetCorrection = 0;
+ while (replacementsIterator.hasNext()) {
+ QVariantMap nextReplacement = replacementsIterator.next().toMap();
+ int replacementStartOffset = nextReplacement.value("startIndex").toInt();
+ int replacementLength = nextReplacement.value("length").toInt();
+ QString replacementPlainText = nextReplacement.value("plainText").toString();
+ processedMessage = processedMessage.replace(replacementStartOffset - offsetCorrection, replacementLength, replacementPlainText);
+ QVariantMap entity;
+ entity.insert("offset", replacementStartOffset - offsetCorrection);
+ entity.insert("length", replacementPlainText.length());
+ QVariantMap entityType;
+ entityType.insert(_TYPE, "textEntityTypeMentionName");
+ entityType.insert("user_id", nextReplacement.value("userId").toString());
+ entity.insert("type", entityType);
+ entities.append(entity);
+ offsetCorrection += replacementLength - replacementPlainText.length();
+ }
+ formattedText.insert("entities", entities);
+ }
+
+ formattedText.insert("text", processedMessage);
formattedText.insert(_TYPE, "formattedText");
inputMessageContent.insert("text", formattedText);
requestObject.insert("input_message_content", inputMessageContent);