mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 11:00:48 +03:00
Show presence and set custom status messages
This commit is contained in:
parent
e5a55ab1b9
commit
96f4169be9
11 changed files with 191 additions and 1 deletions
|
@ -9,6 +9,7 @@ Rectangle {
|
||||||
radius: settings.avatarCircles ? height/2 : 3
|
radius: settings.avatarCircles ? height/2 : 3
|
||||||
|
|
||||||
property alias url: img.source
|
property alias url: img.source
|
||||||
|
property string userid
|
||||||
property string displayName
|
property string displayName
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -42,6 +43,23 @@ Rectangle {
|
||||||
radius: settings.avatarCircles ? height/2 : 3
|
radius: settings.avatarCircles ? height/2 : 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.bottom: avatar.bottom
|
||||||
|
anchors.right: avatar.right
|
||||||
|
|
||||||
|
height: avatar.height / 6
|
||||||
|
width: height
|
||||||
|
radius: settings.avatarCircles ? height / 2 : height / 4
|
||||||
|
color: switch (timelineManager.userPresence(userid)) {
|
||||||
|
case "online": return "#00cc66"
|
||||||
|
case "unavailable": return "#ff9933"
|
||||||
|
case "offline": return "#a82353"
|
||||||
|
default: "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
color: colors.base
|
color: colors.base
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,6 +152,8 @@ Page {
|
||||||
|
|
||||||
onCountChanged: if (atYEnd) model.currentIndex = 0 // Mark last event as read, since we are at the bottom
|
onCountChanged: if (atYEnd) model.currentIndex = 0 // Mark last event as read, since we are at the bottom
|
||||||
|
|
||||||
|
property int delegateMaxWidth: (settings.timelineMaxWidth > 100 && (parent.width - settings.timelineMaxWidth) > 32) ? settings.timelineMaxWidth : (parent.width - 32)
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
// This would normally be previousSection, but our model's order is inverted.
|
// This would normally be previousSection, but our model's order is inverted.
|
||||||
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
|
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
|
||||||
|
@ -159,7 +161,7 @@ Page {
|
||||||
id: wrapper
|
id: wrapper
|
||||||
property Item section
|
property Item section
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
width: (settings.timelineMaxWidth > 100 && (parent.width - settings.timelineMaxWidth) > 32) ? settings.timelineMaxWidth : (parent.width - 32)
|
width: chat.delegateMaxWidth
|
||||||
height: section ? section.height + timelinerow.height : timelinerow.height
|
height: section ? section.height + timelinerow.height : timelinerow.height
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
|
@ -236,6 +238,7 @@ Page {
|
||||||
height: avatarSize
|
height: avatarSize
|
||||||
url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/")
|
url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/")
|
||||||
displayName: modelData.userName
|
displayName: modelData.userName
|
||||||
|
userid: modelData.userId
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
@ -258,6 +261,15 @@ Page {
|
||||||
propagateComposedEvents: true
|
propagateComposedEvents: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
color: colors.buttonText
|
||||||
|
text: timelineManager.userStatus(modelData.userId)
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: chat.delegateMaxWidth - parent.spacing*2 - userName.implicitWidth - avatarSize
|
||||||
|
font.italic: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -952,6 +952,8 @@ Cache::saveState(const mtx::responses::Sync &res)
|
||||||
|
|
||||||
saveInvites(txn, res.rooms.invite);
|
saveInvites(txn, res.rooms.invite);
|
||||||
|
|
||||||
|
savePresence(txn, res.presence);
|
||||||
|
|
||||||
removeLeftRooms(txn, res.rooms.leave);
|
removeLeftRooms(txn, res.rooms.leave);
|
||||||
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
@ -1037,6 +1039,21 @@ Cache::saveInvite(lmdb::txn &txn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Cache::savePresence(
|
||||||
|
lmdb::txn &txn,
|
||||||
|
const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presenceUpdates)
|
||||||
|
{
|
||||||
|
for (const auto &update : presenceUpdates) {
|
||||||
|
auto presenceDb = getPresenceDb(txn);
|
||||||
|
|
||||||
|
lmdb::dbi_put(txn,
|
||||||
|
presenceDb,
|
||||||
|
lmdb::val(update.sender),
|
||||||
|
lmdb::val(json(update.content).dump()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string>
|
std::vector<std::string>
|
||||||
Cache::roomsWithStateUpdates(const mtx::responses::Sync &res)
|
Cache::roomsWithStateUpdates(const mtx::responses::Sync &res)
|
||||||
{
|
{
|
||||||
|
@ -2254,6 +2271,50 @@ Cache::removeAvatarUrl(const QString &room_id, const QString &user_id)
|
||||||
AvatarUrls.remove(fmt);
|
AvatarUrls.remove(fmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mtx::presence::PresenceState
|
||||||
|
Cache::presenceState(const std::string &user_id)
|
||||||
|
{
|
||||||
|
lmdb::val presenceVal;
|
||||||
|
|
||||||
|
auto txn = lmdb::txn::begin(env_);
|
||||||
|
auto db = getPresenceDb(txn);
|
||||||
|
auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), presenceVal);
|
||||||
|
|
||||||
|
mtx::presence::PresenceState state = mtx::presence::offline;
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
mtx::events::presence::Presence presence =
|
||||||
|
json::parse(std::string(presenceVal.data(), presenceVal.size()));
|
||||||
|
state = presence.presence;
|
||||||
|
}
|
||||||
|
|
||||||
|
txn.commit();
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
Cache::statusMessage(const std::string &user_id)
|
||||||
|
{
|
||||||
|
lmdb::val presenceVal;
|
||||||
|
|
||||||
|
auto txn = lmdb::txn::begin(env_);
|
||||||
|
auto db = getPresenceDb(txn);
|
||||||
|
auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), presenceVal);
|
||||||
|
|
||||||
|
std::string status_msg;
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
mtx::events::presence::Presence presence =
|
||||||
|
json::parse(std::string(presenceVal.data(), presenceVal.size()));
|
||||||
|
status_msg = presence.status_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
txn.commit();
|
||||||
|
|
||||||
|
return status_msg;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
to_json(json &j, const RoomInfo &info)
|
to_json(json &j, const RoomInfo &info)
|
||||||
{
|
{
|
||||||
|
@ -2425,6 +2486,17 @@ insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &a
|
||||||
instance_->insertAvatarUrl(room_id, user_id, avatar_url);
|
instance_->insertAvatarUrl(room_id, user_id, avatar_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mtx::presence::PresenceState
|
||||||
|
presenceState(const std::string &user_id)
|
||||||
|
{
|
||||||
|
return instance_->presenceState(user_id);
|
||||||
|
}
|
||||||
|
std::string
|
||||||
|
statusMessage(const std::string &user_id)
|
||||||
|
{
|
||||||
|
return instance_->statusMessage(user_id);
|
||||||
|
}
|
||||||
|
|
||||||
//! Load saved data for the display names & avatars.
|
//! Load saved data for the display names & avatars.
|
||||||
void
|
void
|
||||||
populateMembers()
|
populateMembers()
|
||||||
|
|
|
@ -54,6 +54,12 @@ insertDisplayName(const QString &room_id, const QString &user_id, const QString
|
||||||
void
|
void
|
||||||
insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url);
|
insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url);
|
||||||
|
|
||||||
|
// presence
|
||||||
|
mtx::presence::PresenceState
|
||||||
|
presenceState(const std::string &user_id);
|
||||||
|
std::string
|
||||||
|
statusMessage(const std::string &user_id);
|
||||||
|
|
||||||
//! Load saved data for the display names & avatars.
|
//! Load saved data for the display names & avatars.
|
||||||
void
|
void
|
||||||
populateMembers();
|
populateMembers();
|
||||||
|
|
|
@ -52,6 +52,10 @@ public:
|
||||||
static QString displayName(const QString &room_id, const QString &user_id);
|
static QString displayName(const QString &room_id, const QString &user_id);
|
||||||
static QString avatarUrl(const QString &room_id, const QString &user_id);
|
static 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);
|
||||||
|
|
||||||
static void removeDisplayName(const QString &room_id, const QString &user_id);
|
static void removeDisplayName(const QString &room_id, const QString &user_id);
|
||||||
static void removeAvatarUrl(const QString &room_id, const QString &user_id);
|
static void removeAvatarUrl(const QString &room_id, const QString &user_id);
|
||||||
|
|
||||||
|
@ -377,6 +381,10 @@ private:
|
||||||
void saveInvites(lmdb::txn &txn,
|
void saveInvites(lmdb::txn &txn,
|
||||||
const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
|
const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
|
||||||
|
|
||||||
|
void savePresence(
|
||||||
|
lmdb::txn &txn,
|
||||||
|
const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presenceUpdates);
|
||||||
|
|
||||||
//! Sends signals for the rooms that are removed.
|
//! Sends signals for the rooms that are removed.
|
||||||
void removeLeftRooms(lmdb::txn &txn,
|
void removeLeftRooms(lmdb::txn &txn,
|
||||||
const std::map<std::string, mtx::responses::LeftRoom> &rooms)
|
const std::map<std::string, mtx::responses::LeftRoom> &rooms)
|
||||||
|
@ -430,6 +438,11 @@ private:
|
||||||
return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE);
|
return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lmdb::dbi getPresenceDb(lmdb::txn &txn)
|
||||||
|
{
|
||||||
|
return lmdb::dbi::open(txn, "presence", MDB_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
//! Retrieves or creates the database that stores the open OLM sessions between our device
|
//! Retrieves or creates the database that stores the open OLM sessions between our device
|
||||||
//! and the given curve25519 key which represents another device.
|
//! and the given curve25519 key which represents another device.
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -61,6 +61,7 @@ constexpr size_t MAX_ONETIME_KEYS = 50;
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(std::optional<mtx::crypto::EncryptedFile>)
|
Q_DECLARE_METATYPE(std::optional<mtx::crypto::EncryptedFile>)
|
||||||
Q_DECLARE_METATYPE(std::optional<RelatedInfo>)
|
Q_DECLARE_METATYPE(std::optional<RelatedInfo>)
|
||||||
|
Q_DECLARE_METATYPE(mtx::presence::PresenceState)
|
||||||
|
|
||||||
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
|
@ -72,6 +73,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||||
|
|
||||||
qRegisterMetaType<std::optional<mtx::crypto::EncryptedFile>>();
|
qRegisterMetaType<std::optional<mtx::crypto::EncryptedFile>>();
|
||||||
qRegisterMetaType<std::optional<RelatedInfo>>();
|
qRegisterMetaType<std::optional<RelatedInfo>>();
|
||||||
|
qRegisterMetaType<mtx::presence::PresenceState>();
|
||||||
|
|
||||||
topLayout_ = new QHBoxLayout(this);
|
topLayout_ = new QHBoxLayout(this);
|
||||||
topLayout_->setSpacing(0);
|
topLayout_->setSpacing(0);
|
||||||
|
@ -1221,6 +1223,24 @@ ChatPage::sendTypingNotifications()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString
|
||||||
|
ChatPage::status() const
|
||||||
|
{
|
||||||
|
return QString::fromStdString(cache::statusMessage(utils::localUser().toStdString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ChatPage::setStatus(const QString &status)
|
||||||
|
{
|
||||||
|
http::client()->put_presence_status(
|
||||||
|
currentPresence(), status.toStdString(), [](mtx::http::RequestErr err) {
|
||||||
|
if (err) {
|
||||||
|
nhlog::net()->warn("failed to set presence status_msg: {}",
|
||||||
|
err->matrix_error.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ChatPage::initialSyncHandler(const mtx::responses::Sync &res, mtx::http::RequestErr err)
|
ChatPage::initialSyncHandler(const mtx::responses::Sync &res, mtx::http::RequestErr err)
|
||||||
{
|
{
|
||||||
|
|
|
@ -89,6 +89,11 @@ public:
|
||||||
void initiateLogout();
|
void initiateLogout();
|
||||||
void focusMessageInput();
|
void focusMessageInput();
|
||||||
|
|
||||||
|
QString status() const;
|
||||||
|
void setStatus(const QString &status);
|
||||||
|
|
||||||
|
mtx::presence::PresenceState currentPresence() const { return mtx::presence::online; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void leaveRoom(const QString &room_id);
|
void leaveRoom(const QString &room_id);
|
||||||
void createRoom(const mtx::requests::CreateRoom &req);
|
void createRoom(const mtx::requests::CreateRoom &req);
|
||||||
|
@ -154,6 +159,7 @@ signals:
|
||||||
const QImage &icon);
|
const QImage &icon);
|
||||||
|
|
||||||
void updateGroupsInfo(const mtx::responses::JoinedGroups &groups);
|
void updateGroupsInfo(const mtx::responses::JoinedGroups &groups);
|
||||||
|
void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
|
||||||
void themeChanged();
|
void themeChanged();
|
||||||
void decryptSidebarChanged();
|
void decryptSidebarChanged();
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <QInputDialog>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QMenu>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QStyle>
|
#include <QStyle>
|
||||||
#include <QStyleOption>
|
#include <QStyleOption>
|
||||||
|
@ -24,6 +26,7 @@
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "ChatPage.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include "Splitter.h"
|
#include "Splitter.h"
|
||||||
|
@ -105,6 +108,27 @@ UserInfoWidget::UserInfoWidget(QWidget *parent)
|
||||||
connect(logoutButton_, &QPushButton::clicked, this, []() {
|
connect(logoutButton_, &QPushButton::clicked, this, []() {
|
||||||
MainWindow::instance()->openLogoutDialog();
|
MainWindow::instance()->openLogoutDialog();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
menu = new QMenu(this);
|
||||||
|
|
||||||
|
auto setStatusAction = menu->addAction(tr("Set custom status message"));
|
||||||
|
connect(setStatusAction, &QAction::triggered, this, [this]() {
|
||||||
|
bool ok = false;
|
||||||
|
QString text = QInputDialog::getText(this,
|
||||||
|
tr("Custom status message"),
|
||||||
|
tr("Status:"),
|
||||||
|
QLineEdit::Normal,
|
||||||
|
ChatPage::instance()->status(),
|
||||||
|
&ok);
|
||||||
|
if (ok && !text.isEmpty())
|
||||||
|
ChatPage::instance()->setStatus(text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UserInfoWidget::contextMenuEvent(QContextMenuEvent *event)
|
||||||
|
{
|
||||||
|
menu->popup(event->globalPos());
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -26,6 +26,7 @@ class OverlayModal;
|
||||||
class QLabel;
|
class QLabel;
|
||||||
class QHBoxLayout;
|
class QHBoxLayout;
|
||||||
class QVBoxLayout;
|
class QVBoxLayout;
|
||||||
|
class QMenu;
|
||||||
|
|
||||||
class UserInfoWidget : public QWidget
|
class UserInfoWidget : public QWidget
|
||||||
{
|
{
|
||||||
|
@ -48,6 +49,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
void resizeEvent(QResizeEvent *event) override;
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
void contextMenuEvent(QContextMenuEvent *) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Avatar *userAvatar_;
|
Avatar *userAvatar_;
|
||||||
|
@ -70,4 +72,6 @@ private:
|
||||||
int logoutButtonSize_;
|
int logoutButtonSize_;
|
||||||
|
|
||||||
QColor borderColor_;
|
QColor borderColor_;
|
||||||
|
|
||||||
|
QMenu *menu = nullptr;
|
||||||
};
|
};
|
||||||
|
|
|
@ -57,6 +57,18 @@ TimelineViewManager::userColor(QString id, QColor background)
|
||||||
return userColors.value(id);
|
return userColors.value(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString
|
||||||
|
TimelineViewManager::userPresence(QString id) const
|
||||||
|
{
|
||||||
|
return QString::fromStdString(
|
||||||
|
mtx::presence::to_string(cache::presenceState(id.toStdString())));
|
||||||
|
}
|
||||||
|
QString
|
||||||
|
TimelineViewManager::userStatus(QString id) const
|
||||||
|
{
|
||||||
|
return QString::fromStdString(cache::statusMessage(id.toStdString()));
|
||||||
|
}
|
||||||
|
|
||||||
TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
||||||
: imgProvider(new MxcImageProvider())
|
: imgProvider(new MxcImageProvider())
|
||||||
, colorImgProvider(new ColorImageProvider())
|
, colorImgProvider(new ColorImageProvider())
|
||||||
|
|
|
@ -41,6 +41,9 @@ public:
|
||||||
Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const;
|
Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const;
|
||||||
Q_INVOKABLE QColor userColor(QString id, QColor background);
|
Q_INVOKABLE QColor userColor(QString id, QColor background);
|
||||||
|
|
||||||
|
Q_INVOKABLE QString userPresence(QString id) const;
|
||||||
|
Q_INVOKABLE QString userStatus(QString id) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void clearRoomMessageCount(QString roomid);
|
void clearRoomMessageCount(QString roomid);
|
||||||
void updateRoomsLastMessage(QString roomid, const DescInfo &info);
|
void updateRoomsLastMessage(QString roomid, const DescInfo &info);
|
||||||
|
|
Loading…
Reference in a new issue