diff --git a/src/Cache.cpp b/src/Cache.cpp
index d009c0d3..9ebc61b9 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -1572,7 +1572,6 @@ Cache::saveState(const mtx::responses::Sync &res)
savePresence(txn, res.presence);
markUserKeysOutOfDate(txn, userKeyCacheDb, res.device_lists.changed, currentBatchToken);
- deleteUserKeys(txn, userKeyCacheDb, res.device_lists.left);
removeLeftRooms(txn, res.rooms.leave);
@@ -2777,6 +2776,46 @@ Cache::getMembers(const std::string &room_id, std::size_t startIndex, std::size_
return members;
}
+std::vector<RoomMember>
+Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len)
+{
+ auto txn = ro_txn(env_);
+ auto db = getInviteMembersDb(txn, room_id);
+ auto cursor = lmdb::cursor::open(txn, db);
+
+ std::size_t currentIndex = 0;
+
+ const auto endIndex = std::min(startIndex + len, db.size(txn));
+
+ std::vector<RoomMember> members;
+
+ std::string_view user_id, user_data;
+ while (cursor.get(user_id, user_data, MDB_NEXT)) {
+ if (currentIndex < startIndex) {
+ currentIndex += 1;
+ continue;
+ }
+
+ if (currentIndex >= endIndex)
+ break;
+
+ try {
+ MemberInfo tmp = json::parse(user_data);
+ members.emplace_back(
+ RoomMember{QString::fromStdString(std::string(user_id)),
+ QString::fromStdString(tmp.name)});
+ } catch (const json::exception &e) {
+ nhlog::db()->warn("{}", e.what());
+ }
+
+ currentIndex += 1;
+ }
+
+ cursor.close();
+
+ return members;
+}
+
bool
Cache::isRoomMember(const std::string &user_id, const std::string &room_id)
{
@@ -4125,13 +4164,6 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
}
void
-Cache::deleteUserKeys(lmdb::txn &txn, lmdb::dbi &db, const std::vector<std::string> &user_ids)
-{
- for (const auto &user_id : user_ids)
- db.del(txn, user_id);
-}
-
-void
Cache::markUserKeysOutOfDate(lmdb::txn &txn,
lmdb::dbi &db,
const std::vector<std::string> &user_ids,
@@ -4816,6 +4848,12 @@ getMembers(const std::string &room_id, std::size_t startIndex, std::size_t len)
return instance_->getMembers(room_id, startIndex, len);
}
+std::vector<RoomMember>
+getMembersFromInvite(const std::string &room_id, std::size_t startIndex, std::size_t len)
+{
+ return instance_->getMembersFromInvite(room_id, startIndex, len);
+}
+
void
saveState(const mtx::responses::Sync &res)
{
diff --git a/src/Cache.h b/src/Cache.h
index 57a36d73..f8626430 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -83,6 +83,9 @@ getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
//! Retrieve member info from a room.
std::vector<RoomMember>
getMembers(const std::string &room_id, std::size_t startIndex = 0, std::size_t len = 30);
+//! Retrive member info from an invite.
+std::vector<RoomMember>
+getMembersFromInvite(const std::string &room_id, std::size_t start_index = 0, std::size_t len = 30);
bool
isInitialized();
diff --git a/src/CacheStructs.h b/src/CacheStructs.h
index 4a5c5c76..5f4d392a 100644
--- a/src/CacheStructs.h
+++ b/src/CacheStructs.h
@@ -93,7 +93,7 @@ to_json(nlohmann::json &j, const RoomInfo &info);
void
from_json(const nlohmann::json &j, RoomInfo &info);
-//! Basic information per member;
+//! Basic information per member.
struct MemberInfo
{
std::string name;
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 6190413f..ff2f31e5 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -55,9 +55,6 @@ public:
lmdb::dbi &db,
const std::vector<std::string> &user_ids,
const std::string &sync_token);
- void deleteUserKeys(lmdb::txn &txn,
- lmdb::dbi &db,
- const std::vector<std::string> &user_ids);
void query_keys(const std::string &user_id,
std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb);
@@ -109,6 +106,10 @@ public:
std::vector<RoomMember> getMembers(const std::string &room_id,
std::size_t startIndex = 0,
std::size_t len = 30);
+
+ std::vector<RoomMember> getMembersFromInvite(const std::string &room_id,
+ std::size_t startIndex = 0,
+ std::size_t len = 30);
size_t memberCount(const std::string &room_id);
void saveState(const mtx::responses::Sync &res);
@@ -313,7 +314,6 @@ public:
return get_skey(a).compare(get_skey(b));
}
-
signals:
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void roomReadStatus(const std::map<QString, bool> &status);
diff --git a/src/CallManager.cpp b/src/CallManager.cpp
index 6d41f1c6..601c9d6b 100644
--- a/src/CallManager.cpp
+++ b/src/CallManager.cpp
@@ -206,7 +206,9 @@ CallManager::sendInvite(const QString &roomid, CallType callType, unsigned int w
std::vector<RoomMember> members(cache::getMembers(roomid.toStdString()));
const RoomMember &callee =
members.front().user_id == utils::localUser() ? members.back() : members.front();
- callParty_ = callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
+ callParty_ = callee.user_id;
+ callPartyDisplayName_ =
+ callee.display_name.isEmpty() ? callee.user_id : callee.display_name;
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
emit newInviteState();
playRingtone(QUrl("qrc:/media/media/ringback.ogg"), true);
@@ -308,7 +310,9 @@ CallManager::handleEvent(const RoomEvent<CallInvite> &callInviteEvent)
std::vector<RoomMember> members(cache::getMembers(callInviteEvent.room_id));
const RoomMember &caller =
members.front().user_id == utils::localUser() ? members.back() : members.front();
- callParty_ = caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
+ callParty_ = caller.user_id;
+ callPartyDisplayName_ =
+ caller.display_name.isEmpty() ? caller.user_id : caller.display_name;
callPartyAvatarUrl_ = QString::fromStdString(roomInfo.avatar_url);
haveCallInvite_ = true;
@@ -459,6 +463,7 @@ CallManager::clear()
{
roomid_.clear();
callParty_.clear();
+ callPartyDisplayName_.clear();
callPartyAvatarUrl_.clear();
callid_.clear();
callType_ = CallType::VOICE;
diff --git a/src/CallManager.h b/src/CallManager.h
index 1d973191..407b8366 100644
--- a/src/CallManager.h
+++ b/src/CallManager.h
@@ -32,6 +32,7 @@ class CallManager : public QObject
Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState)
Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState)
Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState)
+ Q_PROPERTY(QString callPartyDisplayName READ callPartyDisplayName NOTIFY newInviteState)
Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState)
Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
Q_PROPERTY(bool haveLocalPiP READ haveLocalPiP NOTIFY newCallState)
@@ -48,6 +49,7 @@ public:
webrtc::CallType callType() const { return callType_; }
webrtc::State callState() const { return session_.state(); }
QString callParty() const { return callParty_; }
+ QString callPartyDisplayName() const { return callPartyDisplayName_; }
QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
bool isMicMuted() const { return session_.isMicMuted(); }
bool haveLocalPiP() const { return session_.haveLocalPiP(); }
@@ -87,6 +89,7 @@ private:
WebRTCSession &session_;
QString roomid_;
QString callParty_;
+ QString callPartyDisplayName_;
QString callPartyAvatarUrl_;
std::string callid_;
const uint32_t timeoutms_ = 120000;
diff --git a/src/JdenticonProvider.cpp b/src/JdenticonProvider.cpp
new file mode 100644
index 00000000..23b601fc
--- /dev/null
+++ b/src/JdenticonProvider.cpp
@@ -0,0 +1,112 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "JdenticonProvider.h"
+
+#include <QApplication>
+#include <QDir>
+#include <QPainter>
+#include <QPainterPath>
+#include <QPluginLoader>
+#include <QSvgRenderer>
+
+#include <mtxclient/crypto/client.hpp>
+
+#include "Cache.h"
+#include "Logging.h"
+#include "MatrixClient.h"
+#include "Utils.h"
+#include "jdenticoninterface.h"
+
+static QPixmap
+clipRadius(QPixmap img, double radius)
+{
+ QPixmap out(img.size());
+ out.fill(Qt::transparent);
+
+ QPainter painter(&out);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
+
+ QPainterPath ppath;
+ ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize);
+
+ painter.setClipPath(ppath);
+ painter.drawPixmap(img.rect(), img);
+
+ return out;
+}
+
+JdenticonResponse::JdenticonResponse(const QString &key,
+ bool crop,
+ double radius,
+ const QSize &requestedSize)
+ : m_key(key)
+ , m_crop{crop}
+ , m_radius{radius}
+ , m_requestedSize(requestedSize.isValid() ? requestedSize : QSize(100, 100))
+ , m_pixmap{m_requestedSize}
+ , jdenticonInterface_{Jdenticon::getJdenticonInterface()}
+{
+ setAutoDelete(false);
+}
+
+void
+JdenticonResponse::run()
+{
+ m_pixmap.fill(Qt::transparent);
+
+ QPainter painter;
+ painter.begin(&m_pixmap);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
+
+ try {
+ QSvgRenderer renderer{
+ jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()};
+ renderer.render(&painter);
+ } catch (std::exception &e) {
+ nhlog::ui()->error(
+ "caught {} in jdenticonprovider, key '{}'", e.what(), m_key.toStdString());
+ }
+
+ painter.end();
+
+ m_pixmap = clipRadius(m_pixmap, m_radius);
+
+ emit finished();
+}
+
+namespace Jdenticon {
+JdenticonInterface *
+getJdenticonInterface()
+{
+ static JdenticonInterface *interface = nullptr;
+ static bool interfaceExists{true};
+
+ if (interface == nullptr && interfaceExists) {
+ QDir pluginsDir(qApp->applicationDirPath());
+
+ bool plugins = pluginsDir.cd("plugins");
+ if (plugins) {
+ for (const QString &fileName : pluginsDir.entryList(QDir::Files)) {
+ QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
+ QObject *plugin = pluginLoader.instance();
+ if (plugin) {
+ interface = qobject_cast<JdenticonInterface *>(plugin);
+ if (interface) {
+ nhlog::ui()->info("Loaded jdenticon plugin.");
+ break;
+ }
+ }
+ }
+ } else {
+ nhlog::ui()->info("jdenticon plugin not found.");
+ interfaceExists = false;
+ }
+ }
+
+ return interface;
+}
+}
diff --git a/src/JdenticonProvider.h b/src/JdenticonProvider.h
new file mode 100644
index 00000000..bcda29c8
--- /dev/null
+++ b/src/JdenticonProvider.h
@@ -0,0 +1,81 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <QImage>
+#include <QQuickAsyncImageProvider>
+#include <QQuickImageResponse>
+#include <QThreadPool>
+
+#include <mtx/common.hpp>
+
+#include "jdenticoninterface.h"
+
+namespace Jdenticon {
+JdenticonInterface *
+getJdenticonInterface();
+}
+
+class JdenticonResponse
+ : public QQuickImageResponse
+ , public QRunnable
+{
+public:
+ JdenticonResponse(const QString &key, bool crop, double radius, const QSize &requestedSize);
+
+ QQuickTextureFactory *textureFactory() const override
+ {
+ return QQuickTextureFactory::textureFactoryForImage(m_pixmap.toImage());
+ }
+
+ void run() override;
+
+ QString m_key;
+ bool m_crop;
+ double m_radius;
+ QSize m_requestedSize;
+ QPixmap m_pixmap;
+ JdenticonInterface *jdenticonInterface_ = nullptr;
+};
+
+class JdenticonProvider
+ : public QObject
+ , public QQuickAsyncImageProvider
+{
+ Q_OBJECT
+
+public:
+ static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; }
+
+public slots:
+ QQuickImageResponse *requestImageResponse(const QString &id,
+ const QSize &requestedSize) override
+ {
+ auto id_ = id;
+ bool crop = true;
+ double radius = 0;
+
+ auto queryStart = id.lastIndexOf('?');
+ if (queryStart != -1) {
+ id_ = id.left(queryStart);
+ auto query = id.midRef(queryStart + 1);
+ auto queryBits = query.split('&');
+
+ for (auto b : queryBits) {
+ if (b.startsWith("radius=")) {
+ radius = b.mid(7).toDouble();
+ }
+ }
+ }
+
+ JdenticonResponse *response =
+ new JdenticonResponse(id_, crop, radius, requestedSize);
+ pool.start(response);
+ return response;
+ }
+
+private:
+ QThreadPool pool;
+};
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 7eadc6df..b423304f 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -16,6 +16,7 @@
#include "Cache_p.h"
#include "ChatPage.h"
#include "Config.h"
+#include "JdenticonProvider.h"
#include "Logging.h"
#include "LoginPage.h"
#include "MainWindow.h"
@@ -152,10 +153,6 @@ MainWindow::MainWindow(QWidget *parent)
showChatPage();
}
});
-
- if (loadJdenticonPlugin()) {
- nhlog::ui()->info("loaded jdenticon.");
- }
}
void
@@ -428,29 +425,6 @@ MainWindow::showDialog(QWidget *dialog)
dialog->show();
}
-bool
-MainWindow::loadJdenticonPlugin()
-{
- QDir pluginsDir(qApp->applicationDirPath());
-
- bool plugins = pluginsDir.cd("plugins");
- if (plugins) {
- foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
- QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
- QObject *plugin = pluginLoader.instance();
- if (plugin) {
- jdenticonInteface_ = qobject_cast<JdenticonInterface *>(plugin);
- if (jdenticonInteface_) {
- nhlog::ui()->info("Found jdenticon plugin.");
- return true;
- }
- }
- }
- }
-
- nhlog::ui()->info("jdenticon plugin not found.");
- return false;
-}
void
MainWindow::showWelcomePage()
{
diff --git a/src/MainWindow.h b/src/MainWindow.h
index d423af9f..d9ffb9b1 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -106,8 +106,6 @@ signals:
void reload();
private:
- bool loadJdenticonPlugin();
-
void showDialog(QWidget *dialog);
bool hasActiveUser();
void restoreWindowSize();
@@ -137,6 +135,4 @@ private:
//! Overlay modal used to project other widgets.
OverlayModal *modal_ = nullptr;
LoadingIndicator *spinner_ = nullptr;
-
- JdenticonInterface *jdenticonInteface_ = nullptr;
};
diff --git a/src/RoomsModel.cpp b/src/RoomsModel.cpp
index 80f13756..656a0deb 100644
--- a/src/RoomsModel.cpp
+++ b/src/RoomsModel.cpp
@@ -77,7 +77,7 @@ RoomsModel::data(const QModelIndex &index, int role) const
return QString::fromStdString(
roomInfos.at(roomids[index.row()]).avatar_url);
case Roles::RoomID:
- return roomids[index.row()];
+ return roomids[index.row()].toHtmlEscaped();
}
}
return {};
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index af32344c..7b01b0b8 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -86,6 +86,7 @@ UserSettings::load(std::optional<QString> profile)
theme_ = settings.value("user/theme", defaultTheme_).toString();
font_ = settings.value("user/font_family", "default").toString();
avatarCircles_ = settings.value("user/avatar_circles", true).toBool();
+ useIdenticon_ = settings.value("user/use_identicon", true).toBool();
decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool();
privacyScreen_ = settings.value("user/privacy_screen", false).toBool();
privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt();
@@ -596,6 +597,15 @@ UserSettings::setDisableCertificateValidation(bool disabled)
disableCertificateValidation_ = disabled;
http::client()->verify_certificates(!disabled);
emit disableCertificateValidationChanged(disabled);
+}
+
+void
+UserSettings::setUseIdenticon(bool state)
+{
+ if (state == useIdenticon_)
+ return;
+ useIdenticon_ = state;
+ emit useIdenticonChanged(useIdenticon_);
save();
}
@@ -674,6 +684,7 @@ UserSettings::save()
settings.setValue("screen_share_hide_cursor", screenShareHideCursor_);
settings.setValue("use_stun_server", useStunServer_);
settings.setValue("currentProfile", profile_);
+ settings.setValue("use_identicon", useIdenticon_);
settings.endGroup(); // user
@@ -746,6 +757,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
trayToggle_ = new Toggle{this};
startInTrayToggle_ = new Toggle{this};
avatarCircles_ = new Toggle{this};
+ useIdenticon_ = new Toggle{this};
decryptSidebar_ = new Toggle(this);
privacyScreen_ = new Toggle{this};
onlyShareKeysWithVerifiedUsers_ = new Toggle(this);
@@ -779,6 +791,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
trayToggle_->setChecked(settings_->tray());
startInTrayToggle_->setChecked(settings_->startInTray());
avatarCircles_->setChecked(settings_->avatarCircles());
+ useIdenticon_->setChecked(settings_->useIdenticon());
decryptSidebar_->setChecked(settings_->decryptSidebar());
privacyScreen_->setChecked(settings_->privacyScreen());
onlyShareKeysWithVerifiedUsers_->setChecked(settings_->onlyShareKeysWithVerifiedUsers());
@@ -941,6 +954,9 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
boxWrap(tr("Circular Avatars"),
avatarCircles_,
tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle."));
+ boxWrap(tr("Use identicons"),
+ useIdenticon_,
+ tr("Display an identicon instead of a letter when a user has not set an avatar."));
boxWrap(tr("Group's sidebar"),
groupViewToggle_,
tr("Show a column containing groups and tags next to the room list."));
@@ -1263,6 +1279,13 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
settings_->setAvatarCircles(enabled);
});
+ if (JdenticonProvider::isAvailable())
+ connect(useIdenticon_, &Toggle::toggled, this, [this](bool enabled) {
+ settings_->setUseIdenticon(enabled);
+ });
+ else
+ useIdenticon_->setDisabled(true);
+
connect(markdown_, &Toggle::toggled, this, [this](bool enabled) {
settings_->setMarkdown(enabled);
});
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index 93b53211..bcd9439b 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -12,6 +12,7 @@
#include <QSharedPointer>
#include <QWidget>
+#include "JdenticonProvider.h"
#include <optional>
class Toggle;
@@ -105,6 +106,8 @@ class UserSettings : public QObject
Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
Q_PROPERTY(bool disableCertificateValidation READ disableCertificateValidation WRITE
setDisableCertificateValidation NOTIFY disableCertificateValidationChanged)
+ Q_PROPERTY(
+ bool useIdenticon READ useIdenticon WRITE setUseIdenticon NOTIFY useIdenticonChanged)
UserSettings();
@@ -172,6 +175,7 @@ public:
void setHomeserver(QString homeserver);
void setDisableCertificateValidation(bool disabled);
void setHiddenTags(QStringList hiddenTags);
+ void setUseIdenticon(bool state);
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
bool messageHoverHighlight() const { return messageHoverHighlight_; }
@@ -230,6 +234,7 @@ public:
QString homeserver() const { return homeserver_; }
bool disableCertificateValidation() const { return disableCertificateValidation_; }
QStringList hiddenTags() const { return hiddenTags_; }
+ bool useIdenticon() const { return useIdenticon_ && JdenticonProvider::isAvailable(); }
signals:
void groupViewStateChanged(bool state);
@@ -277,6 +282,7 @@ signals:
void deviceIdChanged(QString deviceId);
void homeserverChanged(QString homeserver);
void disableCertificateValidationChanged(bool disabled);
+ void useIdenticonChanged(bool state);
private:
// Default to system theme if QT_QPA_PLATFORMTHEME var is set.
@@ -330,6 +336,7 @@ private:
QString deviceId_;
QString homeserver_;
QStringList hiddenTags_;
+ bool useIdenticon_;
QSettings settings;
@@ -391,6 +398,7 @@ private:
Toggle *desktopNotifications_;
Toggle *alertOnNotification_;
Toggle *avatarCircles_;
+ Toggle *useIdenticon_;
Toggle *useStunServer_;
Toggle *decryptSidebar_;
Toggle *privacyScreen_;
diff --git a/src/timeline/Reaction.h b/src/timeline/Reaction.h
index 47dac617..788e9ced 100644
--- a/src/timeline/Reaction.h
+++ b/src/timeline/Reaction.h
@@ -16,8 +16,8 @@ struct Reaction
Q_PROPERTY(int count READ count)
public:
- QString key() const { return key_; }
- QString users() const { return users_; }
+ QString key() const { return key_.toHtmlEscaped(); }
+ QString users() const { return users_.toHtmlEscaped(); }
QString selfReactedEvent() const { return selfReactedEvent_; }
int count() const { return count_; }
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 0f44ec6c..2d1dd49d 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -76,6 +76,8 @@ RoomlistModel::roleNames() const
{IsSpace, "isSpace"},
{Tags, "tags"},
{ParentSpaces, "parentSpaces"},
+ {IsDirect, "isDirect"},
+ {DirectChatOtherUserId, "directChatOtherUserId"},
};
}
@@ -129,6 +131,10 @@ RoomlistModel::data(const QModelIndex &index, int role) const
list.push_back(QString::fromStdString(t));
return list;
}
+ case Roles::IsDirect:
+ return room->isDirect();
+ case Roles::DirectChatOtherUserId:
+ return room->directChatOtherUserId();
default:
return {};
}
@@ -158,6 +164,14 @@ RoomlistModel::data(const QModelIndex &index, int role) const
return false;
case Roles::Tags:
return QStringList();
+ case Roles::IsDirect:
+ // The list of users from the room doesn't contain the invited
+ // users, so we won't factor the invite into the count
+ return room.member_count == 1;
+ case Roles::DirectChatOtherUserId:
+ return cache::getMembersFromInvite(roomid.toStdString(), 0, 1)
+ .front()
+ .user_id;
default:
return {};
}
@@ -190,6 +204,10 @@ RoomlistModel::data(const QModelIndex &index, int role) const
return true;
case Roles::Tags:
return QStringList();
+ case Roles::IsDirect:
+ return false;
+ case Roles::DirectChatOtherUserId:
+ return QString{}; // should never be reached
default:
return {};
}
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index dbdd06c2..27c14bec 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -65,6 +65,8 @@ public:
IsPreviewFetched,
Tags,
ParentSpaces,
+ IsDirect,
+ DirectChatOtherUserId,
};
RoomlistModel(TimelineViewManager *parent = nullptr);
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 78409e1d..ca303040 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -817,6 +817,11 @@ TimelineModel::syncState(const mtx::responses::State &s)
emit roomAvatarUrlChanged();
emit roomNameChanged();
emit roomMemberCountChanged();
+
+ if (roomMemberCount() <= 2) {
+ emit isDirectChanged();
+ emit directChatOtherUserIdChanged();
+ }
} else if (std::holds_alternative<StateEvent<state::Encryption>>(e)) {
this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
emit encryptionChanged();
@@ -2073,3 +2078,16 @@ TimelineModel::roomMemberCount() const
{
return (int)cache::client()->memberCount(room_id_.toStdString());
}
+
+QString
+TimelineModel::directChatOtherUserId() const
+{
+ if (roomMemberCount() < 3) {
+ QString id;
+ for (auto member : cache::getMembers(room_id_.toStdString()))
+ if (member.user_id != UserSettings::instance()->userId())
+ id = member.user_id;
+ return id;
+ } else
+ return "";
+}
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 417fbb7f..66e0622e 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -176,6 +176,9 @@ class TimelineModel : public QAbstractListModel
Q_PROPERTY(bool isEncrypted READ isEncrypted NOTIFY encryptionChanged)
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged)
+ Q_PROPERTY(bool isDirect READ isDirect NOTIFY isDirectChanged)
+ Q_PROPERTY(QString directChatOtherUserId READ directChatOtherUserId NOTIFY
+ directChatOtherUserIdChanged)
Q_PROPERTY(InputBar *input READ input CONSTANT)
Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged)
@@ -292,6 +295,8 @@ public:
bool isEncrypted() const { return isEncrypted_; }
crypto::Trust trustlevel() const;
int roomMemberCount() const;
+ bool isDirect() const { return roomMemberCount() <= 2; }
+ QString directChatOtherUserId() const;
std::optional<mtx::events::collections::TimelineEvents> eventById(const QString &id)
{
@@ -391,6 +396,8 @@ signals:
void roomTopicChanged();
void roomAvatarUrlChanged();
void roomMemberCountChanged();
+ void isDirectChanged();
+ void directChatOtherUserIdChanged();
void permissionsChanged();
void forwardToRoom(mtx::events::collections::TimelineEvents *e, QString roomId);
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 681cbe09..ea231b03 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -141,6 +141,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
, imgProvider(new MxcImageProvider())
, colorImgProvider(new ColorImageProvider())
, blurhashProvider(new BlurhashProvider())
+ , jdenticonProvider(new JdenticonProvider())
, callManager_(callManager)
, rooms_(new RoomlistModel(this))
, communities_(new CommunitiesModel(this))
@@ -310,6 +311,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
view->engine()->addImageProvider("MxcImage", imgProvider);
view->engine()->addImageProvider("colorimage", colorImgProvider);
view->engine()->addImageProvider("blurhash", blurhashProvider);
+ if (JdenticonProvider::isAvailable())
+ view->engine()->addImageProvider("jdenticon", jdenticonProvider);
view->setSource(QUrl("qrc:///qml/Root.qml"));
connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette);
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 4dd5e996..8991de55 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -18,6 +18,7 @@
#include "Cache.h"
#include "CallManager.h"
+#include "JdenticonProvider.h"
#include "Logging.h"
#include "TimelineModel.h"
#include "Utils.h"
@@ -141,6 +142,7 @@ private:
MxcImageProvider *imgProvider;
ColorImageProvider *colorImgProvider;
BlurhashProvider *blurhashProvider;
+ JdenticonProvider *jdenticonProvider;
CallManager *callManager_ = nullptr;
diff --git a/src/ui/Theme.h b/src/ui/Theme.h
index b5bcd4dd..cc39714b 100644
--- a/src/ui/Theme.h
+++ b/src/ui/Theme.h
@@ -8,12 +8,6 @@
#include <QPalette>
namespace ui {
-enum class AvatarType
-{
- Image,
- Letter
-};
-
// Default font size.
const int FontSize = 16;
|