diff --git a/qml/form/RoomForm.qml b/qml/form/RoomForm.qml index 1efbc16..b73142d 100644 --- a/qml/form/RoomForm.qml +++ b/qml/form/RoomForm.qml @@ -319,7 +319,10 @@ Item { selectByMouse: true text: currentRoom ? currentRoom.cachedInput : "" - onTextChanged: currentRoom.cachedInput = text + onTextChanged: { + currentRoom.isTyping = true + currentRoom.cachedInput = text + } Keys.onReturnPressed: { if (inputField.text) { @@ -332,6 +335,9 @@ Item { color: Material.theme == Material.Light ? "#eaeaea" : "#242424" } + ToolTip.visible: currentRoom && currentRoom.hasUsersTyping + ToolTip.text: currentRoom ? currentRoom.usersTyping : "" + function postMessage(text) { if (text.trim().length === 0) { return } if(!currentRoom) { return } diff --git a/src/matriqueroom.cpp b/src/matriqueroom.cpp index cd0ecd6..76f4a80 100644 --- a/src/matriqueroom.cpp +++ b/src/matriqueroom.cpp @@ -1,14 +1,36 @@ #include "matriqueroom.h" #include "connection.h" +#include "user.h" + #include "csapi/leaving.h" +#include "csapi/typing.h" +#include "events/typingevent.h" #include #include MatriqueRoom::MatriqueRoom(Connection* connection, QString roomId, JoinState joinState) - : Room(connection, std::move(roomId), joinState) {} + : Room(connection, std::move(roomId), joinState) { + m_timeoutTimer->setSingleShot(true); + m_timeoutTimer->setInterval(2000); + m_repeatTimer->setInterval(5000); + connect(m_timeoutTimer, &QTimer::timeout, [=] { setIsTyping(false); }); + connect(m_repeatTimer, &QTimer::timeout, + [=] { sendTypingNotification(true); }); + connect(this, &MatriqueRoom::isTypingChanged, [=] { + if (m_isTyping) { + m_timeoutTimer->start(); + m_repeatTimer->start(); + sendTypingNotification(true); + } else { + m_timeoutTimer->stop(); + m_repeatTimer->stop(); + sendTypingNotification(false); + } + }); +} void MatriqueRoom::chooseAndUploadFile() { auto localFile = QFileDialog::getOpenFileUrl(Q_NULLPTR, tr("Save File as")); @@ -55,3 +77,28 @@ void MatriqueRoom::rejectInvitation() { } void MatriqueRoom::forget() { connection()->forgetRoom(id()); } + +bool MatriqueRoom::hasUsersTyping() { + QList users = usersTyping(); + if (users.isEmpty()) return false; + int count = users.length(); + if (users.contains(localUser())) count--; + return count != 0; +} + +QString MatriqueRoom::getUsersTyping() { + QString usersTypingStr; + QList users = usersTyping(); + users.removeOne(localUser()); + for (User* user : users) { + usersTypingStr += user->displayname() + " "; + } + usersTypingStr += users.count() == 1 ? "is" : "are"; + usersTypingStr += " typing."; + return usersTypingStr; +} + +void MatriqueRoom::sendTypingNotification(bool isTyping) { + connection()->callApi(BackgroundRequest, localUser()->id(), + id(), isTyping, 10000); +} diff --git a/src/matriqueroom.h b/src/matriqueroom.h index 50ac03c..21f85e8 100644 --- a/src/matriqueroom.h +++ b/src/matriqueroom.h @@ -4,17 +4,31 @@ #include "room.h" #include +#include using namespace QMatrixClient; class MatriqueRoom : public Room { Q_OBJECT + Q_PROPERTY( + bool isTyping READ isTyping WRITE setIsTyping NOTIFY isTypingChanged) + Q_PROPERTY(bool hasUsersTyping READ hasUsersTyping NOTIFY typingChanged) + Q_PROPERTY(QString usersTyping READ getUsersTyping NOTIFY typingChanged) Q_PROPERTY(QString cachedInput READ cachedInput WRITE setCachedInput NOTIFY cachedInputChanged) public: explicit MatriqueRoom(Connection* connection, QString roomId, JoinState joinState = {}); + bool isTyping() { return m_isTyping; } + void setIsTyping(bool isTyping) { + if (isTyping) m_timeoutTimer->start(); + if (isTyping != m_isTyping) { + m_isTyping = isTyping; + emit isTypingChanged(); + } + } + const QString& cachedInput() const { return m_cachedInput; } void setCachedInput(const QString& input) { if (input != m_cachedInput) { @@ -23,13 +37,20 @@ class MatriqueRoom : public Room { } } + bool hasUsersTyping(); + QString getUsersTyping(); + private: QString m_cachedInput; + bool m_isTyping; + QTimer* m_timeoutTimer = new QTimer(); + QTimer* m_repeatTimer = new QTimer(); QString getMIME(const QUrl& fileUrl) const; void postFile(const QUrl& localFile, const QUrl& mxcUrl); signals: + void isTypingChanged(); void cachedInputChanged(); public slots: @@ -38,6 +59,7 @@ class MatriqueRoom : public Room { void acceptInvitation(); void rejectInvitation(); void forget(); + void sendTypingNotification(bool isTyping); }; #endif // MATRIQUEROOM_H