mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +03:00
Receive typing notifications (#88)
This commit is contained in:
parent
88349eae90
commit
d60c2b76e3
10 changed files with 171 additions and 32 deletions
|
@ -125,8 +125,8 @@ endif()
|
||||||
#
|
#
|
||||||
set(SRC_FILES
|
set(SRC_FILES
|
||||||
src/AvatarProvider.cc
|
src/AvatarProvider.cc
|
||||||
src/ChatPage.cc
|
|
||||||
src/Cache.cc
|
src/Cache.cc
|
||||||
|
src/ChatPage.cc
|
||||||
src/Deserializable.cc
|
src/Deserializable.cc
|
||||||
src/EmojiCategory.cc
|
src/EmojiCategory.cc
|
||||||
src/EmojiItemDelegate.cc
|
src/EmojiItemDelegate.cc
|
||||||
|
@ -135,9 +135,6 @@ set(SRC_FILES
|
||||||
src/EmojiProvider.cc
|
src/EmojiProvider.cc
|
||||||
src/ImageItem.cc
|
src/ImageItem.cc
|
||||||
src/ImageOverlayDialog.cc
|
src/ImageOverlayDialog.cc
|
||||||
src/TimelineItem.cc
|
|
||||||
src/TimelineView.cc
|
|
||||||
src/TimelineViewManager.cc
|
|
||||||
src/InputValidator.cc
|
src/InputValidator.cc
|
||||||
src/JoinRoomDialog.cc
|
src/JoinRoomDialog.cc
|
||||||
src/LeaveRoomDialog.cc
|
src/LeaveRoomDialog.cc
|
||||||
|
@ -147,21 +144,25 @@ set(SRC_FILES
|
||||||
src/MainWindow.cc
|
src/MainWindow.cc
|
||||||
src/MatrixClient.cc
|
src/MatrixClient.cc
|
||||||
src/Profile.cc
|
src/Profile.cc
|
||||||
src/RoomInfoListItem.cc
|
src/QuickSwitcher.cc
|
||||||
src/RoomMessages.cc
|
|
||||||
src/RoomList.cc
|
|
||||||
src/RoomState.cc
|
|
||||||
src/Register.cc
|
src/Register.cc
|
||||||
src/RegisterPage.cc
|
src/RegisterPage.cc
|
||||||
|
src/RoomInfoListItem.cc
|
||||||
|
src/RoomList.cc
|
||||||
|
src/RoomMessages.cc
|
||||||
|
src/RoomState.cc
|
||||||
src/Splitter.cc
|
src/Splitter.cc
|
||||||
src/Sync.cc
|
src/Sync.cc
|
||||||
src/TextInputWidget.cc
|
src/TextInputWidget.cc
|
||||||
src/TrayIcon.cc
|
src/TimelineItem.cc
|
||||||
|
src/TimelineView.cc
|
||||||
|
src/TimelineViewManager.cc
|
||||||
src/TopRoomBar.cc
|
src/TopRoomBar.cc
|
||||||
|
src/TrayIcon.cc
|
||||||
|
src/TypingDisplay.cc
|
||||||
src/UserInfoWidget.cc
|
src/UserInfoWidget.cc
|
||||||
src/Versions.cc
|
src/Versions.cc
|
||||||
src/WelcomePage.cc
|
src/WelcomePage.cc
|
||||||
src/QuickSwitcher.cc
|
|
||||||
src/main.cc
|
src/main.cc
|
||||||
|
|
||||||
src/ui/Avatar.cc
|
src/ui/Avatar.cc
|
||||||
|
@ -222,23 +223,24 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||||
include/ImageItem.h
|
include/ImageItem.h
|
||||||
include/ImageOverlayDialog.h
|
include/ImageOverlayDialog.h
|
||||||
include/JoinRoomDialog.h
|
include/JoinRoomDialog.h
|
||||||
include/TimelineItem.h
|
|
||||||
include/TimelineView.h
|
|
||||||
include/TimelineViewManager.h
|
|
||||||
include/LeaveRoomDialog.h
|
include/LeaveRoomDialog.h
|
||||||
include/LoginPage.h
|
include/LoginPage.h
|
||||||
include/LogoutDialog.h
|
include/LogoutDialog.h
|
||||||
include/MainWindow.h
|
include/MainWindow.h
|
||||||
include/MatrixClient.h
|
include/MatrixClient.h
|
||||||
|
include/QuickSwitcher.h
|
||||||
include/RegisterPage.h
|
include/RegisterPage.h
|
||||||
include/RoomInfoListItem.h
|
include/RoomInfoListItem.h
|
||||||
include/RoomList.h
|
include/RoomList.h
|
||||||
include/Splitter.h
|
include/Splitter.h
|
||||||
include/UserInfoWidget.h
|
include/TextInputWidget.h
|
||||||
|
include/TimelineItem.h
|
||||||
|
include/TimelineView.h
|
||||||
|
include/TimelineViewManager.h
|
||||||
include/TopRoomBar.h
|
include/TopRoomBar.h
|
||||||
include/TrayIcon.h
|
include/TrayIcon.h
|
||||||
include/TextInputWidget.h
|
include/TypingDisplay.h
|
||||||
include/QuickSwitcher.h
|
include/UserInfoWidget.h
|
||||||
include/WelcomePage.h
|
include/WelcomePage.h
|
||||||
|
|
||||||
include/ui/Avatar.h
|
include/ui/Avatar.h
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include "TextInputWidget.h"
|
#include "TextInputWidget.h"
|
||||||
#include "TimelineViewManager.h"
|
#include "TimelineViewManager.h"
|
||||||
#include "TopRoomBar.h"
|
#include "TopRoomBar.h"
|
||||||
|
#include "TypingDisplay.h"
|
||||||
#include "UserInfoWidget.h"
|
#include "UserInfoWidget.h"
|
||||||
|
|
||||||
class ChatPage : public QWidget
|
class ChatPage : public QWidget
|
||||||
|
@ -68,6 +69,7 @@ protected:
|
||||||
void keyPressEvent(QKeyEvent *event) override;
|
void keyPressEvent(QKeyEvent *event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void updateTypingUsers(const QString &roomid, const QList<QString> &user_ids);
|
||||||
void updateDisplayNames(const RoomState &state);
|
void updateDisplayNames(const RoomState &state);
|
||||||
void loadStateFromCache();
|
void loadStateFromCache();
|
||||||
void showQuickSwitcher();
|
void showQuickSwitcher();
|
||||||
|
@ -92,6 +94,7 @@ private:
|
||||||
|
|
||||||
TopRoomBar *top_bar_;
|
TopRoomBar *top_bar_;
|
||||||
TextInputWidget *text_input_;
|
TextInputWidget *text_input_;
|
||||||
|
TypingDisplay *typingDisplay_;
|
||||||
|
|
||||||
QTimer *sync_timer_;
|
QTimer *sync_timer_;
|
||||||
int sync_interval_;
|
int sync_interval_;
|
||||||
|
@ -104,6 +107,9 @@ private:
|
||||||
QMap<QString, RoomState> state_manager_;
|
QMap<QString, RoomState> state_manager_;
|
||||||
QMap<QString, QSharedPointer<RoomSettings>> settingsManager_;
|
QMap<QString, QSharedPointer<RoomSettings>> settingsManager_;
|
||||||
|
|
||||||
|
// Keeps track of the users currently typing on each room.
|
||||||
|
QMap<QString, QList<QString>> typingUsers_;
|
||||||
|
|
||||||
QuickSwitcher *quickSwitcher_ = nullptr;
|
QuickSwitcher *quickSwitcher_ = nullptr;
|
||||||
OverlayModal *quickSwitcherModal_ = nullptr;
|
OverlayModal *quickSwitcherModal_ = nullptr;
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
namespace conf
|
namespace conf
|
||||||
{
|
{
|
||||||
// Global settings.
|
// Global settings.
|
||||||
static const int fontSize = 12;
|
static const int fontSize = 12;
|
||||||
static const int emojiSize = 14;
|
static const int emojiSize = 14;
|
||||||
static const int headerFontSize = 21;
|
static const int headerFontSize = 21;
|
||||||
|
static const int typingNotificationFontSize = 11;
|
||||||
|
|
||||||
// Window geometry.
|
// Window geometry.
|
||||||
namespace window
|
namespace window
|
||||||
|
|
|
@ -142,23 +142,30 @@ Timeline::limited() const
|
||||||
return limited_;
|
return limited_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add support for ehpmeral, account_data, undread_notifications
|
// TODO: Add support for account_data, undread_notifications
|
||||||
class JoinedRoom : public Deserializable
|
class JoinedRoom : public Deserializable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
inline State state() const;
|
inline State state() const;
|
||||||
inline Timeline timeline() const;
|
inline Timeline timeline() const;
|
||||||
|
inline QList<QString> typingUserIDs() const;
|
||||||
|
|
||||||
void deserialize(const QJsonValue &data) override;
|
void deserialize(const QJsonValue &data) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
State state_;
|
State state_;
|
||||||
Timeline timeline_;
|
Timeline timeline_;
|
||||||
/* Ephemeral ephemeral_; */
|
QList<QString> typingUserIDs_;
|
||||||
/* AccountData account_data_; */
|
/* AccountData account_data_; */
|
||||||
/* UnreadNotifications unread_notifications_; */
|
/* UnreadNotifications unread_notifications_; */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline QList<QString>
|
||||||
|
JoinedRoom::typingUserIDs() const
|
||||||
|
{
|
||||||
|
return typingUserIDs_;
|
||||||
|
}
|
||||||
|
|
||||||
inline State
|
inline State
|
||||||
JoinedRoom::state() const
|
JoinedRoom::state() const
|
||||||
{
|
{
|
||||||
|
|
21
include/TypingDisplay.h
Normal file
21
include/TypingDisplay.h
Normal file
|
@ -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_;
|
||||||
|
};
|
|
@ -101,8 +101,10 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
|
||||||
view_manager_ = new TimelineViewManager(client, this);
|
view_manager_ = new TimelineViewManager(client, this);
|
||||||
mainContentLayout_->addWidget(view_manager_);
|
mainContentLayout_->addWidget(view_manager_);
|
||||||
|
|
||||||
text_input_ = new TextInputWidget(this);
|
text_input_ = new TextInputWidget(this);
|
||||||
|
typingDisplay_ = new TypingDisplay(this);
|
||||||
contentLayout_->addWidget(text_input_);
|
contentLayout_->addWidget(text_input_);
|
||||||
|
contentLayout_->addWidget(typingDisplay_);
|
||||||
|
|
||||||
user_info_widget_ = new UserInfoWidget(sideBarTopWidget_);
|
user_info_widget_ = new UserInfoWidget(sideBarTopWidget_);
|
||||||
sideBarTopWidgetLayout_->addWidget(user_info_widget_);
|
sideBarTopWidgetLayout_->addWidget(user_info_widget_);
|
||||||
|
@ -117,6 +119,15 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
|
||||||
connect(
|
connect(
|
||||||
top_bar_, &TopRoomBar::leaveRoom, this, [=]() { client_->leaveRoom(current_room_); });
|
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, this, &ChatPage::changeTopRoomInfo);
|
||||||
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit);
|
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit);
|
||||||
connect(
|
connect(
|
||||||
|
@ -308,6 +319,8 @@ ChatPage::syncCompleted(const SyncResponse &response)
|
||||||
auto joined = response.rooms().join();
|
auto joined = response.rooms().join();
|
||||||
|
|
||||||
for (auto it = joined.constBegin(); it != joined.constEnd(); it++) {
|
for (auto it = joined.constBegin(); it != joined.constEnd(); it++) {
|
||||||
|
updateTypingUsers(it.key(), it.value().typingUserIDs());
|
||||||
|
|
||||||
RoomState room_state;
|
RoomState room_state;
|
||||||
|
|
||||||
// Merge the new updates for rooms that we are tracking.
|
// 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_);
|
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()
|
ChatPage::~ChatPage()
|
||||||
{
|
{
|
||||||
sync_timer_->stop();
|
sync_timer_->stop();
|
||||||
|
|
|
@ -611,8 +611,9 @@ void
|
||||||
MatrixClient::sync() noexcept
|
MatrixClient::sync() noexcept
|
||||||
{
|
{
|
||||||
QJsonObject filter{ { "room",
|
QJsonObject filter{ { "room",
|
||||||
QJsonObject{ { "include_leave", true },
|
QJsonObject{
|
||||||
{ "ephemeral", QJsonObject{ { "limit", 0 } } } } },
|
{ "include_leave", true },
|
||||||
|
} },
|
||||||
{ "presence", QJsonObject{ { "limit", 0 } } } };
|
{ "presence", QJsonObject{ { "limit", 0 } } } };
|
||||||
|
|
||||||
QUrlQuery query;
|
QUrlQuery query;
|
||||||
|
|
16
src/Sync.cc
16
src/Sync.cc
|
@ -168,7 +168,21 @@ JoinedRoom::deserialize(const QJsonValue &data)
|
||||||
if (!ephemeral.value("events").isArray())
|
if (!ephemeral.value("events").isArray())
|
||||||
qWarning() << "join/ephemeral/events should be an array";
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,13 +45,14 @@ TextInputWidget::TextInputWidget(QWidget *parent)
|
||||||
{
|
{
|
||||||
setFont(QFont("Emoji One"));
|
setFont(QFont("Emoji One"));
|
||||||
|
|
||||||
|
setFixedHeight(45);
|
||||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
setCursor(Qt::ArrowCursor);
|
setCursor(Qt::ArrowCursor);
|
||||||
setStyleSheet("background-color: #fff; height: 45px;");
|
setStyleSheet("background-color: #fff;");
|
||||||
|
|
||||||
topLayout_ = new QHBoxLayout();
|
topLayout_ = new QHBoxLayout();
|
||||||
topLayout_->setSpacing(2);
|
topLayout_->setSpacing(0);
|
||||||
topLayout_->setMargin(4);
|
topLayout_->setContentsMargins(5, 15, 0, 5);
|
||||||
|
|
||||||
QIcon send_file_icon;
|
QIcon send_file_icon;
|
||||||
send_file_icon.addFile(":/icons/icons/clip-dark.png", QSize(), QIcon::Normal, QIcon::Off);
|
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_ = new LoadingIndicator(this);
|
||||||
spinner_->setColor("#acc7dc");
|
spinner_->setColor("#acc7dc");
|
||||||
spinner_->setFixedHeight(40);
|
spinner_->setFixedHeight(32);
|
||||||
spinner_->setFixedWidth(40);
|
spinner_->setFixedWidth(32);
|
||||||
spinner_->hide();
|
spinner_->hide();
|
||||||
|
|
||||||
QFont font;
|
QFont font;
|
||||||
font.setPixelSize(conf::fontSize);
|
font.setPixelSize(conf::fontSize);
|
||||||
|
|
||||||
input_ = new FilteredTextEdit(this);
|
input_ = new FilteredTextEdit(this);
|
||||||
input_->setFixedHeight(45);
|
input_->setFixedHeight(32);
|
||||||
input_->setFont(font);
|
input_->setFont(font);
|
||||||
|
input_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||||
input_->setPlaceholderText(tr("Write a message..."));
|
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_ = new FlatButton(this);
|
||||||
sendMessageBtn_->setForegroundColor(QColor("#acc7dc"));
|
sendMessageBtn_->setForegroundColor(QColor("#acc7dc"));
|
||||||
|
|
56
src/TypingDisplay.cc
Normal file
56
src/TypingDisplay.cc
Normal file
|
@ -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_);
|
||||||
|
}
|
Loading…
Reference in a new issue