From 287b5aa4c0d52e1ac80a0785ab136aa0f98b3e9f Mon Sep 17 00:00:00 2001
From: Thomas Herzog <thomas.herzog@mail.com>
Date: Tue, 31 Oct 2017 19:11:49 +0100
Subject: [PATCH] Implemented sending of typing notifications (#105)

---
 include/ChatPage.h        |  6 +++--
 include/MatrixClient.h    |  2 ++
 include/TextInputWidget.h | 13 ++++++++++
 src/ChatPage.cc           | 29 +++++++++++++++++++++--
 src/MatrixClient.cc       | 50 +++++++++++++++++++++++++++++++++++++++
 src/TextInputWidget.cc    | 36 ++++++++++++++++++++++++++--
 6 files changed, 130 insertions(+), 6 deletions(-)

diff --git a/include/ChatPage.h b/include/ChatPage.h
index 416f78708..849d60e7e 100644
--- a/include/ChatPage.h
+++ b/include/ChatPage.h
@@ -45,8 +45,9 @@ class UserInfoWidget;
 class JoinedRoom;
 class LeftRoom;
 
-constexpr int CONSENSUS_TIMEOUT    = 1000;
-constexpr int SHOW_CONTENT_TIMEOUT = 3000;
+constexpr int CONSENSUS_TIMEOUT      = 1000;
+constexpr int SHOW_CONTENT_TIMEOUT   = 3000;
+constexpr int TYPING_REFRESH_TIMEOUT = 10000;
 
 class ChatPage : public QWidget
 {
@@ -141,6 +142,7 @@ private:
 
         // Keeps track of the users currently typing on each room.
         QMap<QString, QList<QString>> typingUsers_;
+        QTimer *typingRefresher_;
 
         QSharedPointer<QuickSwitcher> quickSwitcher_;
         QSharedPointer<OverlayModal> quickSwitcherModal_;
diff --git a/include/MatrixClient.h b/include/MatrixClient.h
index ef9e82e61..d6dd71628 100644
--- a/include/MatrixClient.h
+++ b/include/MatrixClient.h
@@ -56,6 +56,8 @@ public:
         void uploadImage(const QString &roomid, const QString &filename);
         void joinRoom(const QString &roomIdOrAlias);
         void leaveRoom(const QString &roomId);
+        void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000);
+        void removeTypingNotification(const QString &roomid);
 
         QUrl getHomeServer() { return server_; };
         int transactionId() { return txn_id_; };
diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h
index 08b62f45e..e32ce2ff6 100644
--- a/include/TextInputWidget.h
+++ b/include/TextInputWidget.h
@@ -35,12 +35,20 @@ static const QString JOIN_COMMAND("/join ");
 class FilteredTextEdit : public QTextEdit
 {
         Q_OBJECT
+
+private:
+        QTimer *typingTimer_;
+
 public:
         explicit FilteredTextEdit(QWidget *parent = nullptr);
         void keyPressEvent(QKeyEvent *event);
 
+        void stopTyping();
+
 signals:
         void enterPressed();
+        void startedTyping();
+        void stoppedTyping();
 };
 
 class TextInputWidget : public QFrame
@@ -51,6 +59,8 @@ public:
         TextInputWidget(QWidget *parent = 0);
         ~TextInputWidget();
 
+        void stopTyping();
+
 public slots:
         void onSendButtonClicked();
         void openFileSelection();
@@ -66,6 +76,9 @@ signals:
         void uploadImage(QString filename);
         void sendJoinRoomRequest(const QString &room);
 
+        void startedTyping();
+        void stoppedTyping();
+
 private:
         void showUploadSpinner();
         QString parseEmoteCommand(const QString &cmd);
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index 5fefd7672..d81b64fb7 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -122,6 +122,9 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
         contentLayout_->addWidget(typingDisplay_);
         contentLayout_->addWidget(text_input_);
 
+        typingRefresher_ = new QTimer(this);
+        typingRefresher_->setInterval(TYPING_REFRESH_TIMEOUT);
+
         user_info_widget_ = new UserInfoWidget(sideBarTopWidget_);
         sideBarTopWidgetLayout_->addWidget(user_info_widget_);
 
@@ -139,6 +142,7 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
 
                 typingDisplay_->setUsers(users);
         });
+        connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::stopTyping);
 
         connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo);
         connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit);
@@ -159,6 +163,20 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
                                 room_list_->updateUnreadMessageCount(roomid, count);
                 });
 
+        connect(text_input_, &TextInputWidget::startedTyping, this, [=]() {
+                typingRefresher_->start();
+                client_->sendTypingNotification(current_room_);
+        });
+
+        connect(text_input_, &TextInputWidget::stoppedTyping, this, [=]() {
+                typingRefresher_->stop();
+                client_->removeTypingNotification(current_room_);
+        });
+
+        connect(typingRefresher_, &QTimer::timeout, this, [=]() {
+                client_->sendTypingNotification(current_room_);
+        });
+
         connect(view_manager_,
                 &TimelineViewManager::updateRoomsLastMessage,
                 room_list_,
@@ -600,13 +618,20 @@ ChatPage::updateTypingUsers(const QString &roomid, const QList<QString> &user_id
 {
         QStringList users;
 
-        for (const auto uid : user_ids)
+        QSettings settings;
+        QString user_id = settings.value("auth/user_id").toString();
+
+        for (const auto uid : user_ids) {
+                if (uid == user_id)
+                        continue;
                 users.append(TimelineViewManager::displayName(uid));
+        }
 
         users.sort();
 
-        if (current_room_ == roomid)
+        if (current_room_ == roomid) {
                 typingDisplay_->setUsers(users);
+        }
 
         typingUsers_.insert(roomid, users);
 }
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index e1085e820..c6ef501f6 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cc
@@ -794,3 +794,53 @@ MatrixClient::leaveRoom(const QString &roomId)
                 emit leftRoom(roomId);
         });
 }
+
+void
+MatrixClient::sendTypingNotification(const QString &roomid, int timeoutInMillis)
+{
+        QSettings settings;
+        QString user_id = settings.value("auth/user_id").toString();
+
+        QUrlQuery query;
+        query.addQueryItem("access_token", token_);
+
+        QUrl endpoint(server_);
+        endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/typing/%2").arg(roomid).arg(user_id));
+
+        endpoint.setQuery(query);
+
+        QString msgType("");
+        QJsonObject body;
+
+        body = { { "typing", true }, { "timeout", timeoutInMillis } };
+
+        QNetworkRequest request(QString(endpoint.toEncoded()));
+        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+        put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
+}
+
+void
+MatrixClient::removeTypingNotification(const QString &roomid)
+{
+        QSettings settings;
+        QString user_id = settings.value("auth/user_id").toString();
+
+        QUrlQuery query;
+        query.addQueryItem("access_token", token_);
+
+        QUrl endpoint(server_);
+        endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/typing/%2").arg(roomid).arg(user_id));
+
+        endpoint.setQuery(query);
+
+        QString msgType("");
+        QJsonObject body;
+
+        body = { { "typing", false } };
+
+        QNetworkRequest request(QString(endpoint.toEncoded()));
+        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+        put(request, QJsonDocument(body).toJson(QJsonDocument::Compact));
+}
diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc
index 0d5e1102a..7ebef6b11 100644
--- a/src/TextInputWidget.cc
+++ b/src/TextInputWidget.cc
@@ -29,15 +29,37 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent)
   : QTextEdit(parent)
 {
         setAcceptRichText(false);
+
+        typingTimer_ = new QTimer(this);
+        typingTimer_->setInterval(1000);
+        typingTimer_->setSingleShot(true);
+
+        connect(typingTimer_, &QTimer::timeout, this, &FilteredTextEdit::stopTyping);
 }
 
 void
 FilteredTextEdit::keyPressEvent(QKeyEvent *event)
 {
-        if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
+        if (!typingTimer_->isActive()) {
+                emit startedTyping();
+        }
+
+        typingTimer_->start();
+
+        if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
+                stopTyping();
+
                 emit enterPressed();
-        else
+        } else {
                 QTextEdit::keyPressEvent(event);
+        }
+}
+
+void
+FilteredTextEdit::stopTyping()
+{
+        typingTimer_->stop();
+        emit stoppedTyping();
 }
 
 TextInputWidget::TextInputWidget(QWidget *parent)
@@ -104,6 +126,10 @@ TextInputWidget::TextInputWidget(QWidget *parent)
                 SIGNAL(emojiSelected(const QString &)),
                 this,
                 SLOT(addSelectedEmoji(const QString &)));
+
+        connect(input_, &FilteredTextEdit::startedTyping, this, &TextInputWidget::startedTyping);
+
+        connect(input_, &FilteredTextEdit::stoppedTyping, this, &TextInputWidget::stoppedTyping);
 }
 
 void
@@ -227,3 +253,9 @@ TextInputWidget::hideUploadSpinner()
 }
 
 TextInputWidget::~TextInputWidget() {}
+
+void
+TextInputWidget::stopTyping()
+{
+        input_->stopTyping();
+}
-- 
GitLab