diff --git a/CMakeLists.txt b/CMakeLists.txt index 20ef5cab..1b6c08b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -345,6 +345,7 @@ set(SRC_FILES src/DeviceVerificationFlow.cpp src/EventAccessors.cpp src/InviteesModel.cpp + src/JdenticonProvider.cpp src/Logging.cpp src/LoginPage.cpp src/MainWindow.cpp @@ -557,6 +558,7 @@ qt5_wrap_cpp(MOC_HEADERS src/DeviceVerificationFlow.h src/ImagePackListModel.h src/InviteesModel.h + src/JdenticonProvider.h src/LoginPage.h src/MainWindow.h src/MemberList.h diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index ab067eee..7bbdeba3 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -35,10 +35,17 @@ Rectangle { font.pixelSize: avatar.height / 2 verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter - visible: img.status != Image.Ready + visible: img.status != Image.Ready && !Settings.useIdenticon color: Nheko.colors.text } + Image { + id: identicon + anchors.fill: parent + visible: img.status != Image.Ready && Settings.useIdenticon + source: "image://jdenticon/" + userid + } + Image { id: img diff --git a/src/JdenticonProvider.cpp b/src/JdenticonProvider.cpp new file mode 100644 index 00000000..4be972dc --- /dev/null +++ b/src/JdenticonProvider.cpp @@ -0,0 +1,68 @@ +#include "JdenticonProvider.h" + +#include +#include +#include +#include +#include + +#include + +#include "Cache.h" +#include "Logging.h" +#include "MatrixClient.h" +#include "Utils.h" +#include "jdenticoninterface.h" + +JdenticonResponse::JdenticonResponse(const QString &key, const QSize &requestedSize) + : m_key(key) + , 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{&m_pixmap}; + QSvgRenderer renderer{ + jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()}; + // m_image = QImage::fromData(jdenticonInterface_->generate(m_key, + // size->width()).toUtf8()); + renderer.render(&painter); + + emit finished(); +} + +namespace Jdenticon { +JdenticonInterface * +getJdenticonInterface() +{ + static JdenticonInterface *interface = nullptr; + + if (interface == nullptr) { + QDir pluginsDir(qApp->applicationDirPath()); + + bool plugins = pluginsDir.cd("plugins"); + if (plugins) { + for (QString fileName : pluginsDir.entryList(QDir::Files)) { + QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName)); + QObject *plugin = pluginLoader.instance(); + if (plugin) { + interface = qobject_cast(plugin); + if (interface) { + nhlog::ui()->info("Loaded jdenticon plugin."); + break; + } + } + } + } else + nhlog::ui()->info("jdenticon plugin not found."); + } + + return interface; +} +} diff --git a/src/JdenticonProvider.h b/src/JdenticonProvider.h new file mode 100644 index 00000000..053842bb --- /dev/null +++ b/src/JdenticonProvider.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include + +#include "jdenticoninterface.h" + +namespace Jdenticon { +JdenticonInterface * +getJdenticonInterface(); +} + +class JdenticonResponse + : public QQuickImageResponse + , public QRunnable +{ +public: + JdenticonResponse(const QString &key, const QSize &requestedSize); + + QQuickTextureFactory *textureFactory() const override + { + return QQuickTextureFactory::textureFactoryForImage(m_pixmap.toImage()); + } + + void run() override; + + QString m_key; + 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 &key, + const QSize &requestedSize) override + { + JdenticonResponse *response = new JdenticonResponse(key, 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(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..18bcfe3d 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -137,6 +137,4 @@ private: //! Overlay modal used to project other widgets. OverlayModal *modal_ = nullptr; LoadingIndicator *spinner_ = nullptr; - - JdenticonInterface *jdenticonInteface_ = nullptr; }; diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index af32344c..bfe5232b 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -86,6 +86,7 @@ UserSettings::load(std::optional 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 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 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,12 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge boxWrap(tr("Circular Avatars"), avatarCircles_, tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle.")); + if (JdenticonProvider::isAvailable()) + 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 +1282,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge settings_->setAvatarCircles(enabled); }); + connect(useIdenticon_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setUseIdenticon(enabled); + }); + 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 #include +#include "JdenticonProvider.h" #include 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/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..254fbadf 100644 --- a/src/ui/Theme.h +++ b/src/ui/Theme.h @@ -11,7 +11,8 @@ namespace ui { enum class AvatarType { Image, - Letter + Letter, + Jdenticon }; // Default font size.