diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 08c29927..2fd4b6d4 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -1388,3 +1388,33 @@ TimelineModel::cacheMedia(QString eventId)
emit mediaCached(mxcUrl, filename.filePath());
});
}
+
+QString
+TimelineModel::formatTypingUsers(const std::vector<QString> &users, QColor bg)
+{
+ QString temp =
+ tr("%1 and %2 are typing",
+ "Multiple users are typing. First argument is a comma separated list of potentially "
+ "multiple users. Second argument is the last user of that list. (If only one user is "
+ "typing, %1 is empty. You should still use it in your string though to silence Qt "
+ "warnings.)",
+ users.size());
+
+ if (users.empty()) {
+ return "";
+ }
+
+ QStringList uidWithoutLast;
+
+ auto formatUser = [this, bg](const QString &user_id) -> QString {
+ return QString("<font color=\"%1\">%2</font>")
+ .arg(userColor(user_id, bg).name())
+ .arg(escapeEmoji(displayName(user_id).toHtmlEscaped()));
+ };
+
+ for (size_t i = 0; i + 1 < users.size(); i++) {
+ uidWithoutLast.append(formatUser(users[i]));
+ }
+
+ return temp.arg(uidWithoutLast.join(", ")).arg(formatUser(users.back()));
+}
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index ae505c17..6d351359 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -120,6 +120,8 @@ class TimelineModel : public QAbstractListModel
Q_OBJECT
Q_PROPERTY(
int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
+ Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY
+ typingUsersChanged)
public:
explicit TimelineModel(TimelineViewManager *manager, QString room_id, QObject *parent = 0);
@@ -162,6 +164,7 @@ public:
Q_INVOKABLE QString displayName(QString id) const;
Q_INVOKABLE QString avatarUrl(QString id) const;
Q_INVOKABLE QString formatDateSeparator(QDate date) const;
+ Q_INVOKABLE QString formatTypingUsers(const std::vector<QString> &users, QColor bg);
Q_INVOKABLE QString escapeEmoji(QString str) const;
Q_INVOKABLE void viewRawMessage(QString id) const;
@@ -183,6 +186,14 @@ public slots:
int currentIndex() const { return idToIndex(currentId); }
void markEventsAsRead(const std::vector<QString> &event_ids);
QVariantMap getDump(QString eventId) const;
+ void updateTypingUsers(const std::vector<QString> &users)
+ {
+ if (this->typingUsers_ != users) {
+ this->typingUsers_ = users;
+ emit typingUsersChanged(typingUsers_);
+ }
+ }
+ std::vector<QString> typingUsers() const { return typingUsers_; }
private slots:
// Add old events at the top of the timeline.
@@ -202,6 +213,7 @@ signals:
void mediaCached(QString mxcUrl, QString cacheUrl);
void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo);
void replyFetched(QString requestingEvent, mtx::events::collections::TimelineEvents event);
+ void typingUsersChanged(std::vector<QString> users);
private:
DecryptionResult decryptEvent(
@@ -232,6 +244,7 @@ private:
QHash<QString, QColor> userColors;
QString currentId;
+ std::vector<QString> typingUsers_;
TimelineViewManager *manager_;
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index ddbc6af8..c7a4e50e 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -8,6 +8,7 @@
#include "ColorImageProvider.h"
#include "DelegateChooser.h"
#include "Logging.h"
+#include "MatrixClient.h"
#include "MxcImageProvider.h"
#include "UserSettingsPage.h"
#include "dialogs/ImageOverlay.h"
@@ -92,10 +93,21 @@ TimelineViewManager::TimelineViewManager(QWidget *parent)
void
TimelineViewManager::sync(const mtx::responses::Rooms &rooms)
{
- for (auto it = rooms.join.cbegin(); it != rooms.join.cend(); ++it) {
+ for (const auto &[room_id, room] : rooms.join) {
// addRoom will only add the room, if it doesn't exist
- addRoom(QString::fromStdString(it->first));
- models.value(QString::fromStdString(it->first))->addEvents(it->second.timeline);
+ addRoom(QString::fromStdString(room_id));
+ const auto &room_model = models.value(QString::fromStdString(room_id));
+ room_model->addEvents(room.timeline);
+
+ if (ChatPage::instance()->userSettings()->isTypingNotificationsEnabled()) {
+ std::vector<QString> typing;
+ typing.reserve(room.ephemeral.typing.size());
+ for (const auto &user : room.ephemeral.typing) {
+ if (user != http::client()->user_id().to_string())
+ typing.push_back(QString::fromStdString(user));
+ }
+ room_model->updateTypingUsers(typing);
+ }
}
this->isInitialSync_ = false;
|