summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--resources/qml/Avatar.qml18
-rw-r--r--resources/qml/MessageView.qml11
-rw-r--r--src/Cache.cpp52
-rw-r--r--src/Cache.h6
-rw-r--r--src/Cache_p.h3
-rw-r--r--src/ChatPage.cpp2
-rw-r--r--src/timeline/PresenceEmitter.cpp73
-rw-r--r--src/timeline/PresenceEmitter.h26
-rw-r--r--src/timeline/TimelineViewManager.cpp26
-rw-r--r--src/timeline/TimelineViewManager.h5
11 files changed, 145 insertions, 79 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a97851b9..6a98bc1c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -328,6 +328,7 @@ set(SRC_FILES
 	src/timeline/TimelineModel.cpp
 	src/timeline/DelegateChooser.cpp
 	src/timeline/Permissions.cpp
+	src/timeline/PresenceEmitter.cpp
 	src/timeline/RoomlistModel.cpp
 
 	# UI components
@@ -535,6 +536,7 @@ qt5_wrap_cpp(MOC_HEADERS
 	src/timeline/TimelineModel.h
 	src/timeline/DelegateChooser.h
 	src/timeline/Permissions.h
+	src/timeline/PresenceEmitter.h
 	src/timeline/RoomlistModel.h
 
 	# UI components
diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml
index 27d139d4..b055d452 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -87,14 +87,18 @@ Rectangle {
     }
 
     Rectangle {
+        id: onlineIndicator
+
         anchors.bottom: avatar.bottom
         anchors.right: avatar.right
         visible: !!userid
         height: avatar.height / 6
         width: height
         radius: Settings.avatarCircles ? height / 2 : height / 8
-        color: {
-            switch (TimelineManager.userPresence(userid)) {
+        color: updatePresence()
+
+        function updatePresence() {
+            switch (Presence.userPresence(userid)) {
             case "online":
                 return "#00cc66";
             case "unavailable":
@@ -102,7 +106,15 @@ Rectangle {
             case "offline":
             default:
                 // return "#a82353" don't show anything if offline, since it is confusing, if presence is disabled
-                "transparent";
+                return "transparent";
+            }
+        }
+
+        Connections {
+            target: Presence
+
+            function onPresenceChanged(id) {
+                if (id == userid) onlineIndicator.color = onlineIndicator.updatePresence();
             }
         }
     }
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index 62f7735b..adbc9d70 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -329,12 +329,21 @@ ScrollView {
                     }
 
                     Label {
+                        id: statusMsg
                         color: Nheko.colors.buttonText
-                        text: TimelineManager.userStatus(userId)
+                        text: Presence.userStatus(userId)
                         textFormat: Text.PlainText
                         elide: Text.ElideRight
                         width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - Nheko.avatarSize
                         font.italic: true
+
+                        Connections {
+                            target: Presence
+
+                            function onPresenceChanged(id) {
+                                if (id == userId) statusMsg.text = Presence.userStatus(userId);
+                            }
+                        }
                     }
 
                 }
diff --git a/src/Cache.cpp b/src/Cache.cpp
index b197353e..eec5b79f 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -3897,53 +3897,26 @@ Cache::avatarUrl(const QString &room_id, const QString &user_id)
     return QString();
 }
 
-mtx::presence::PresenceState
-Cache::presenceState(const std::string &user_id)
+mtx::events::presence::Presence
+Cache::presence(const std::string &user_id)
 {
     if (user_id.empty())
         return {};
 
     std::string_view presenceVal;
 
-    auto txn = lmdb::txn::begin(env_);
-    auto db  = getPresenceDb(txn);
-    auto res = db.get(txn, user_id, presenceVal);
-
-    mtx::presence::PresenceState state = mtx::presence::offline;
-
-    if (res) {
-        mtx::events::presence::Presence presence =
-          json::parse(std::string_view(presenceVal.data(), presenceVal.size()));
-        state = presence.presence;
-    }
-
-    txn.commit();
-
-    return state;
-}
-
-std::string
-Cache::statusMessage(const std::string &user_id)
-{
-    if (user_id.empty())
-        return {};
-
-    std::string_view presenceVal;
-
-    auto txn = lmdb::txn::begin(env_);
+    auto txn = ro_txn(env_);
     auto db  = getPresenceDb(txn);
     auto res = db.get(txn, user_id, presenceVal);
 
-    std::string status_msg;
+    mtx::events::presence::Presence presence_{};
+    presence_.presence = mtx::presence::PresenceState::offline;
 
     if (res) {
-        mtx::events::presence::Presence presence = json::parse(presenceVal);
-        status_msg                               = presence.status_msg;
+        presence_ = json::parse(std::string_view(presenceVal.data(), presenceVal.size()));
     }
 
-    txn.commit();
-
-    return status_msg;
+    return presence_;
 }
 
 void
@@ -4747,17 +4720,12 @@ avatarUrl(const QString &room_id, const QString &user_id)
     return instance_->avatarUrl(room_id, user_id);
 }
 
-mtx::presence::PresenceState
-presenceState(const std::string &user_id)
+mtx::events::presence::Presence
+presence(const std::string &user_id)
 {
     if (!instance_)
         return {};
-    return instance_->presenceState(user_id);
-}
-std::string
-statusMessage(const std::string &user_id)
-{
-    return instance_->statusMessage(user_id);
+    return instance_->presence(user_id);
 }
 
 // user cache stores user keys
diff --git a/src/Cache.h b/src/Cache.h
index f8626430..4a5175f3 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -38,10 +38,8 @@ QString
 avatarUrl(const QString &room_id, const QString &user_id);
 
 // presence
-mtx::presence::PresenceState
-presenceState(const std::string &user_id);
-std::string
-statusMessage(const std::string &user_id);
+mtx::events::presence::Presence
+presence(const std::string &user_id);
 
 // user cache stores user keys
 std::optional<UserKeyCache>
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 6a6b4e0c..a9f1a4e0 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -43,8 +43,7 @@ public:
     QString avatarUrl(const QString &room_id, const QString &user_id);
 
     // presence
-    mtx::presence::PresenceState presenceState(const std::string &user_id);
-    std::string statusMessage(const std::string &user_id);
+    mtx::events::presence::Presence presence(const std::string &user_id);
 
     // user cache stores user keys
     std::map<std::string, std::optional<UserKeyCache>>
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 46c8a9f9..139f935c 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -877,7 +877,7 @@ ChatPage::receivedSessionKey(const std::string &room_id, const std::string &sess
 QString
 ChatPage::status() const
 {
-    return QString::fromStdString(cache::statusMessage(utils::localUser().toStdString()));
+    return QString::fromStdString(cache::presence(utils::localUser().toStdString()).status_msg);
 }
 
 void
diff --git a/src/timeline/PresenceEmitter.cpp b/src/timeline/PresenceEmitter.cpp
new file mode 100644
index 00000000..c052bf43
--- /dev/null
+++ b/src/timeline/PresenceEmitter.cpp
@@ -0,0 +1,73 @@
+#include "PresenceEmitter.h"
+
+#include <QCache>
+#include <Utils.h>
+
+#include "Cache.h"
+
+namespace {
+struct CacheEntry
+{
+    QString status;
+    mtx::presence::PresenceState state;
+};
+}
+
+static QCache<QString, CacheEntry> presences;
+
+static QString
+presenceToStr(mtx::presence::PresenceState state)
+{
+    switch (state) {
+    case mtx::presence::PresenceState::offline:
+        return QStringLiteral("offline");
+    case mtx::presence::PresenceState::unavailable:
+        return QStringLiteral("unavailable");
+    case mtx::presence::PresenceState::online:
+    default:
+        return QStringLiteral("online");
+    }
+}
+
+static CacheEntry *
+pullPresence(const QString &id)
+{
+    auto p = cache::presence(id.toStdString());
+    auto c = new CacheEntry{
+      utils::replaceEmoji(QString::fromStdString(p.status_msg).toHtmlEscaped()), p.presence};
+    presences.insert(id, c);
+    return c;
+}
+
+void
+PresenceEmitter::sync(
+  const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presences_)
+{
+    for (const auto &p : presences_) {
+        auto id = QString::fromStdString(p.sender);
+        presences.remove(id);
+        emit presenceChanged(std::move(id));
+    }
+}
+
+QString
+PresenceEmitter::userPresence(QString id) const
+{
+    if (id.isEmpty())
+        return {};
+    else if (auto p = presences[id])
+        return presenceToStr(p->state);
+    else
+        return presenceToStr(pullPresence(id)->state);
+}
+
+QString
+PresenceEmitter::userStatus(QString id) const
+{
+    if (id.isEmpty())
+        return {};
+    else if (auto p = presences[id])
+        return p->status;
+    else
+        return pullPresence(id)->status;
+}
diff --git a/src/timeline/PresenceEmitter.h b/src/timeline/PresenceEmitter.h
new file mode 100644
index 00000000..bf1a9458
--- /dev/null
+++ b/src/timeline/PresenceEmitter.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <QObject>
+
+#include <vector>
+
+#include <mtx/events.hpp>
+#include <mtx/events/presence.hpp>
+
+class PresenceEmitter : public QObject
+{
+    Q_OBJECT
+
+public:
+    PresenceEmitter(QObject *p = nullptr)
+      : QObject(p)
+    {}
+
+    void sync(const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presences);
+
+    Q_INVOKABLE QString userPresence(QString id) const;
+    Q_INVOKABLE QString userStatus(QString id) const;
+
+signals:
+    void presenceChanged(QString userid);
+};
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 70a1510a..bab093b0 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -124,29 +124,6 @@ TimelineViewManager::userColor(QString id, QColor background)
     return userColors.value(idx);
 }
 
-QString
-TimelineViewManager::userPresence(QString id) const
-{
-    if (id.isEmpty())
-        return {};
-    else
-        switch (cache::presenceState(id.toStdString())) {
-        case mtx::presence::PresenceState::offline:
-            return QStringLiteral("offline");
-        case mtx::presence::PresenceState::unavailable:
-            return QStringLiteral("unavailable");
-        case mtx::presence::PresenceState::online:
-        default:
-            return QStringLiteral("online");
-        }
-}
-
-QString
-TimelineViewManager::userStatus(QString id) const
-{
-    return QString::fromStdString(cache::statusMessage(id.toStdString()));
-}
-
 TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *parent)
   : QObject(parent)
   , imgProvider(new MxcImageProvider())
@@ -157,6 +134,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
   , communities_(new CommunitiesModel(this))
   , callManager_(callManager)
   , verificationManager_(new VerificationManager(this))
+  , presenceEmitter(new PresenceEmitter(this))
 {
     qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>();
     qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>();
@@ -280,6 +258,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
           return new Nheko();
       });
     qmlRegisterSingletonInstance("im.nheko", 1, 0, "VerificationManager", verificationManager_);
+    qmlRegisterSingletonInstance("im.nheko", 1, 0, "Presence", presenceEmitter);
     qmlRegisterSingletonType<SelfVerificationStatus>(
       "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * {
           auto ptr = new SelfVerificationStatus();
@@ -407,6 +386,7 @@ TimelineViewManager::sync(const mtx::responses::Sync &sync_)
 {
     this->rooms_->sync(sync_);
     this->communities_->sync(sync_);
+    this->presenceEmitter->sync(sync_.presence);
 
     if (isInitialSync_) {
         this->isInitialSync_ = false;
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 30b3564f..d875fd88 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -24,6 +24,7 @@
 #include "emoji/Provider.h"
 #include "encryption/VerificationManager.h"
 #include "timeline/CommunitiesModel.h"
+#include "timeline/PresenceEmitter.h"
 #include "timeline/RoomlistModel.h"
 #include "voip/CallManager.h"
 #include "voip/WebRTCSession.h"
@@ -64,9 +65,6 @@ public:
     Q_INVOKABLE QString escapeEmoji(QString str) const;
     Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); }
 
-    Q_INVOKABLE QString userPresence(QString id) const;
-    Q_INVOKABLE QString userStatus(QString id) const;
-
     Q_INVOKABLE void openRoomMembers(TimelineModel *room);
     Q_INVOKABLE void openRoomSettings(QString room_id);
     Q_INVOKABLE void openInviteUsers(QString roomId);
@@ -146,6 +144,7 @@ private:
     // don't move this above the rooms_
     CallManager *callManager_                 = nullptr;
     VerificationManager *verificationManager_ = nullptr;
+    PresenceEmitter *presenceEmitter          = nullptr;
 
     QHash<QPair<QString, quint64>, QColor> userColors;
 };