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/QuickSwitcher.cc
+ src/Register.cc
+ src/RegisterPage.cc
src/RoomInfoListItem.cc
- src/RoomMessages.cc
src/RoomList.cc
+ src/RoomMessages.cc
src/RoomState.cc
- src/Register.cc
- src/RegisterPage.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<QString> &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<QString, RoomState> state_manager_;
QMap<QString, QSharedPointer<RoomSettings>> settingsManager_;
+ // Keeps track of the users currently typing on each room.
+ QMap<QString, QList<QString>> 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<QString> typingUserIDs() const;
void deserialize(const QJsonValue &data) override;
private:
State state_;
Timeline timeline_;
- /* Ephemeral ephemeral_; */
+ QList<QString> typingUserIDs_;
/* AccountData account_data_; */
/* UnreadNotifications unread_notifications_; */
};
+inline QList<QString>
+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 <QPaintEvent>
+#include <QWidget>
+
+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<MatrixClient> 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<MatrixClient> 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<QString> &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 <QDebug>
+#include <QPainter>
+#include <QPoint>
+
+#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_);
+}
|