From 287b5aa4c0d52e1ac80a0785ab136aa0f98b3e9f Mon Sep 17 00:00:00 2001 From: Thomas Herzog 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 416f7870..849d60e7 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> typingUsers_; + QTimer *typingRefresher_; QSharedPointer quickSwitcher_; QSharedPointer quickSwitcherModal_; diff --git a/include/MatrixClient.h b/include/MatrixClient.h index ef9e82e6..d6dd7162 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 08b62f45..e32ce2ff 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 5fefd767..d81b64fb 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -122,6 +122,9 @@ ChatPage::ChatPage(QSharedPointer 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 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 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 &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 e1085e82..c6ef501f 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 0d5e1102..7ebef6b1 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(); +}