From d60c2b76e30dcbdb1eae2a69b2d3ddff128d00c5 Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Wed, 4 Oct 2017 11:33:34 +0300 Subject: [PATCH] Receive typing notifications (#88) --- CMakeLists.txt | 34 +++++++++++++------------ include/ChatPage.h | 6 +++++ include/Config.h | 7 +++--- include/Sync.h | 11 ++++++-- include/TypingDisplay.h | 21 ++++++++++++++++ src/ChatPage.cc | 31 ++++++++++++++++++++++- src/MatrixClient.cc | 5 ++-- src/Sync.cc | 16 +++++++++++- src/TextInputWidget.cc | 16 ++++++------ src/TypingDisplay.cc | 56 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 171 insertions(+), 32 deletions(-) create mode 100644 include/TypingDisplay.h create mode 100644 src/TypingDisplay.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index a40daebc..99ae1d62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,8 +125,8 @@ endif() # set(SRC_FILES src/AvatarProvider.cc - src/ChatPage.cc src/Cache.cc + src/ChatPage.cc src/Deserializable.cc src/EmojiCategory.cc src/EmojiItemDelegate.cc @@ -135,9 +135,6 @@ set(SRC_FILES src/EmojiProvider.cc src/ImageItem.cc src/ImageOverlayDialog.cc - src/TimelineItem.cc - src/TimelineView.cc - src/TimelineViewManager.cc src/InputValidator.cc src/JoinRoomDialog.cc src/LeaveRoomDialog.cc @@ -147,21 +144,25 @@ set(SRC_FILES src/MainWindow.cc src/MatrixClient.cc src/Profile.cc - src/RoomInfoListItem.cc - src/RoomMessages.cc - src/RoomList.cc - src/RoomState.cc + src/QuickSwitcher.cc src/Register.cc src/RegisterPage.cc + src/RoomInfoListItem.cc + src/RoomList.cc + src/RoomMessages.cc + src/RoomState.cc src/Splitter.cc src/Sync.cc src/TextInputWidget.cc - src/TrayIcon.cc + src/TimelineItem.cc + src/TimelineView.cc + src/TimelineViewManager.cc src/TopRoomBar.cc + src/TrayIcon.cc + src/TypingDisplay.cc src/UserInfoWidget.cc src/Versions.cc src/WelcomePage.cc - src/QuickSwitcher.cc src/main.cc src/ui/Avatar.cc @@ -222,23 +223,24 @@ qt5_wrap_cpp(MOC_HEADERS include/ImageItem.h include/ImageOverlayDialog.h include/JoinRoomDialog.h - include/TimelineItem.h - include/TimelineView.h - include/TimelineViewManager.h include/LeaveRoomDialog.h include/LoginPage.h include/LogoutDialog.h include/MainWindow.h include/MatrixClient.h + include/QuickSwitcher.h include/RegisterPage.h include/RoomInfoListItem.h include/RoomList.h include/Splitter.h - include/UserInfoWidget.h + include/TextInputWidget.h + include/TimelineItem.h + include/TimelineView.h + include/TimelineViewManager.h include/TopRoomBar.h include/TrayIcon.h - include/TextInputWidget.h - include/QuickSwitcher.h + include/TypingDisplay.h + include/UserInfoWidget.h include/WelcomePage.h include/ui/Avatar.h diff --git a/include/ChatPage.h b/include/ChatPage.h index 8becc17f..8332225b 100644 --- a/include/ChatPage.h +++ b/include/ChatPage.h @@ -31,6 +31,7 @@ #include "TextInputWidget.h" #include "TimelineViewManager.h" #include "TopRoomBar.h" +#include "TypingDisplay.h" #include "UserInfoWidget.h" class ChatPage : public QWidget @@ -68,6 +69,7 @@ protected: void keyPressEvent(QKeyEvent *event) override; private: + void updateTypingUsers(const QString &roomid, const QList &user_ids); void updateDisplayNames(const RoomState &state); void loadStateFromCache(); void showQuickSwitcher(); @@ -92,6 +94,7 @@ private: TopRoomBar *top_bar_; TextInputWidget *text_input_; + TypingDisplay *typingDisplay_; QTimer *sync_timer_; int sync_interval_; @@ -104,6 +107,9 @@ private: QMap state_manager_; QMap> settingsManager_; + // Keeps track of the users currently typing on each room. + QMap> typingUsers_; + QuickSwitcher *quickSwitcher_ = nullptr; OverlayModal *quickSwitcherModal_ = nullptr; diff --git a/include/Config.h b/include/Config.h index 654eadb6..50f9eb85 100644 --- a/include/Config.h +++ b/include/Config.h @@ -7,9 +7,10 @@ namespace conf { // Global settings. -static const int fontSize = 12; -static const int emojiSize = 14; -static const int headerFontSize = 21; +static const int fontSize = 12; +static const int emojiSize = 14; +static const int headerFontSize = 21; +static const int typingNotificationFontSize = 11; // Window geometry. namespace window diff --git a/include/Sync.h b/include/Sync.h index a9caf473..c49d7101 100644 --- a/include/Sync.h +++ b/include/Sync.h @@ -142,23 +142,30 @@ Timeline::limited() const return limited_; } -// TODO: Add support for ehpmeral, account_data, undread_notifications +// TODO: Add support for account_data, undread_notifications class JoinedRoom : public Deserializable { public: inline State state() const; inline Timeline timeline() const; + inline QList typingUserIDs() const; void deserialize(const QJsonValue &data) override; private: State state_; Timeline timeline_; - /* Ephemeral ephemeral_; */ + QList typingUserIDs_; /* AccountData account_data_; */ /* UnreadNotifications unread_notifications_; */ }; +inline QList +JoinedRoom::typingUserIDs() const +{ + return typingUserIDs_; +} + inline State JoinedRoom::state() const { diff --git a/include/TypingDisplay.h b/include/TypingDisplay.h new file mode 100644 index 00000000..db8a9519 --- /dev/null +++ b/include/TypingDisplay.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class TypingDisplay : public QWidget +{ + Q_OBJECT + +public: + TypingDisplay(QWidget *parent = nullptr); + + void setUsers(const QStringList &user_ids); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QString text_; + int leftPadding_; +}; diff --git a/src/ChatPage.cc b/src/ChatPage.cc index 9f983b9f..52468f64 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -101,8 +101,10 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) view_manager_ = new TimelineViewManager(client, this); mainContentLayout_->addWidget(view_manager_); - text_input_ = new TextInputWidget(this); + text_input_ = new TextInputWidget(this); + typingDisplay_ = new TypingDisplay(this); contentLayout_->addWidget(text_input_); + contentLayout_->addWidget(typingDisplay_); user_info_widget_ = new UserInfoWidget(sideBarTopWidget_); sideBarTopWidgetLayout_->addWidget(user_info_widget_); @@ -117,6 +119,15 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) connect( top_bar_, &TopRoomBar::leaveRoom, this, [=]() { client_->leaveRoom(current_room_); }); + connect(room_list_, &RoomList::roomChanged, this, [=](const QString &roomid) { + QStringList users; + + if (typingUsers_.contains(roomid)) + users = typingUsers_[roomid]; + + typingDisplay_->setUsers(users); + }); + connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo); connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit); connect( @@ -308,6 +319,8 @@ ChatPage::syncCompleted(const SyncResponse &response) auto joined = response.rooms().join(); for (auto it = joined.constBegin(); it != joined.constEnd(); it++) { + updateTypingUsers(it.key(), it.value().typingUserIDs()); + RoomState room_state; // Merge the new updates for rooms that we are tracking. @@ -620,6 +633,22 @@ ChatPage::removeRoom(const QString &room_id) room_list_->removeRoom(room_id, room_id == current_room_); } +void +ChatPage::updateTypingUsers(const QString &roomid, const QList &user_ids) +{ + QStringList users; + + for (const auto uid : user_ids) + users.append(TimelineViewManager::displayName(uid)); + + users.sort(); + + if (current_room_ == roomid) + typingDisplay_->setUsers(users); + + typingUsers_.insert(roomid, users); +} + ChatPage::~ChatPage() { sync_timer_->stop(); diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index bd43efd8..265b51ce 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -611,8 +611,9 @@ void MatrixClient::sync() noexcept { QJsonObject filter{ { "room", - QJsonObject{ { "include_leave", true }, - { "ephemeral", QJsonObject{ { "limit", 0 } } } } }, + QJsonObject{ + { "include_leave", true }, + } }, { "presence", QJsonObject{ { "limit", 0 } } } }; QUrlQuery query; diff --git a/src/Sync.cc b/src/Sync.cc index 90314352..39d84acb 100644 --- a/src/Sync.cc +++ b/src/Sync.cc @@ -168,7 +168,21 @@ JoinedRoom::deserialize(const QJsonValue &data) if (!ephemeral.value("events").isArray()) qWarning() << "join/ephemeral/events should be an array"; - // TODO: Implement ephemeral handling + auto ephemeralEvents = ephemeral.value("events").toArray(); + + for (const auto e : ephemeralEvents) { + auto obj = e.toObject(); + + if (obj.contains("type") && obj.value("type") == "m.typing") { + auto ids = obj.value("content") + .toObject() + .value("user_ids") + .toArray(); + + for (const auto uid : ids) + typingUserIDs_.push_back(uid.toString()); + } + } } } diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index 5f06d992..4d5f4d5f 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -45,13 +45,14 @@ TextInputWidget::TextInputWidget(QWidget *parent) { setFont(QFont("Emoji One")); + setFixedHeight(45); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); setCursor(Qt::ArrowCursor); - setStyleSheet("background-color: #fff; height: 45px;"); + setStyleSheet("background-color: #fff;"); topLayout_ = new QHBoxLayout(); - topLayout_->setSpacing(2); - topLayout_->setMargin(4); + topLayout_->setSpacing(0); + topLayout_->setContentsMargins(5, 15, 0, 5); QIcon send_file_icon; send_file_icon.addFile(":/icons/icons/clip-dark.png", QSize(), QIcon::Normal, QIcon::Off); @@ -63,18 +64,19 @@ TextInputWidget::TextInputWidget(QWidget *parent) spinner_ = new LoadingIndicator(this); spinner_->setColor("#acc7dc"); - spinner_->setFixedHeight(40); - spinner_->setFixedWidth(40); + spinner_->setFixedHeight(32); + spinner_->setFixedWidth(32); spinner_->hide(); QFont font; font.setPixelSize(conf::fontSize); input_ = new FilteredTextEdit(this); - input_->setFixedHeight(45); + input_->setFixedHeight(32); input_->setFont(font); + input_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); input_->setPlaceholderText(tr("Write a message...")); - input_->setStyleSheet("color: #333333; border-radius: 0; padding-top: 10px;"); + input_->setStyleSheet("color: #333333; border: none; margin: 0 5px"); sendMessageBtn_ = new FlatButton(this); sendMessageBtn_->setForegroundColor(QColor("#acc7dc")); diff --git a/src/TypingDisplay.cc b/src/TypingDisplay.cc new file mode 100644 index 00000000..619b70cb --- /dev/null +++ b/src/TypingDisplay.cc @@ -0,0 +1,56 @@ +#include +#include +#include + +#include "Config.h" +#include "TypingDisplay.h" + +TypingDisplay::TypingDisplay(QWidget *parent) + : QWidget(parent) + , leftPadding_{ 57 } +{ + QFont font; + font.setPixelSize(conf::typingNotificationFontSize); + + setFixedHeight(QFontMetrics(font).height() + 2); +} + +void +TypingDisplay::setUsers(const QStringList &uid) +{ + if (uid.isEmpty()) + text_.clear(); + else + text_ = uid.join(", "); + + if (uid.size() == 1) + text_ += tr(" is typing ..."); + else if (uid.size() > 1) + text_ += tr(" are typing ..."); + + update(); +} + +void +TypingDisplay::paintEvent(QPaintEvent *) +{ + QPen pen(QColor("#333")); + + QFont font; + font.setPixelSize(conf::typingNotificationFontSize); + font.setWeight(40); + font.setItalic(true); + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + p.setFont(font); + p.setPen(pen); + + QRect region = rect(); + region.translate(leftPadding_, 0); + + QFontMetrics fm(font); + text_ = fm.elidedText(text_, Qt::ElideRight, width() - 3 * leftPadding_); + + p.drawText(region, Qt::AlignTop, text_); +}