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 txn = ro_txn(env_); auto db = getPresenceDb(txn); auto res = db.get(txn, user_id, presenceVal); - mtx::presence::PresenceState state = mtx::presence::offline; + mtx::events::presence::Presence presence_{}; + presence_.presence = mtx::presence::PresenceState::offline; if (res) { - mtx::events::presence::Presence presence = - json::parse(std::string_view(presenceVal.data(), presenceVal.size())); - state = presence.presence; + presence_ = json::parse(std::string_view(presenceVal.data(), presenceVal.size())); } - 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 db = getPresenceDb(txn); - auto res = db.get(txn, user_id, presenceVal); - - std::string status_msg; - - if (res) { - mtx::events::presence::Presence presence = json::parse(presenceVal); - status_msg = presence.status_msg; - } - - 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 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> 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 +#include + +#include "Cache.h" + +namespace { +struct CacheEntry +{ + QString status; + mtx::presence::PresenceState state; +}; +} + +static QCache 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> &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 + +#include + +#include +#include + +class PresenceEmitter : public QObject +{ + Q_OBJECT + +public: + PresenceEmitter(QObject *p = nullptr) + : QObject(p) + {} + + void sync(const std::vector> &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(); qRegisterMetaType(); @@ -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( "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, QColor> userColors; };