diff --git a/CMakeLists.txt b/CMakeLists.txt
index 20ef5cab88bb6cd984e0c81ffd73f93f7a8be584..1b6c08b73d2bdf9bf33845a87a4c249cb67aa4fe 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 ab067eeea64aa18adbc6ac344bf6da3b51197c6e..5d2b583a917c86b22edccc58f61772cbeda9be24 100644
--- a/resources/qml/Avatar.qml
+++ b/resources/qml/Avatar.qml
@@ -12,6 +12,7 @@ Rectangle {
 
     property string url
     property string userid
+    property string roomid
     property string displayName
     property alias textColor: label.color
     property bool crop: true
@@ -35,10 +36,28 @@ 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: Settings.useIdenticon && img.status != Image.Ready
+        source: Settings.useIdenticon ? ("image://jdenticon/" + (userid !== "" ? userid : roomid) + "?radius=" + (Settings.avatarCircles ? 100 : 25)) : ""
+
+        MouseArea {
+            anchors.fill: parent
+
+            Ripple {
+                rippleTarget: parent
+                color: Qt.rgba(Nheko.colors.alternateBase.r, Nheko.colors.alternateBase.g, Nheko.colors.alternateBase.b, 0.5)
+            }
+
+        }
+
+    }
+
     Image {
         id: img
 
diff --git a/resources/qml/CommunitiesList.qml b/resources/qml/CommunitiesList.qml
index 491913bef50969eb2b1c7ee2d787582bf9434722..ff9b7da781ad236a47d5d2b52d7d89b9cfd27ffe 100644
--- a/resources/qml/CommunitiesList.qml
+++ b/resources/qml/CommunitiesList.qml
@@ -130,6 +130,7 @@ Page {
                         else
                             return "image://colorimage/" + model.avatarUrl + "?" + communityItem.unimportantText;
                     }
+                    roomid: model.id
                     displayName: model.displayName
                     color: communityItem.background
                 }
diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml
index 00fc3216131e088bc227f8917112126eb059e649..6bde67fa11af405ed40e243ed91e03a0035c67e9 100644
--- a/resources/qml/Completer.qml
+++ b/resources/qml/Completer.qml
@@ -139,6 +139,7 @@ Popup {
                             height: popup.avatarHeight
                             width: popup.avatarWidth
                             displayName: model.displayName
+                            userid: model.userid
                             url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
                             onClicked: popup.completionClicked(completer.completionAt(model.index))
                         }
@@ -194,6 +195,7 @@ Popup {
                             height: popup.avatarHeight
                             width: popup.avatarWidth
                             displayName: model.roomName
+                            roomid: model.roomid
                             url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
                             onClicked: {
                                 popup.completionClicked(completer.completionAt(model.index));
@@ -225,6 +227,7 @@ Popup {
                             height: popup.avatarHeight
                             width: popup.avatarWidth
                             displayName: model.roomName
+                            roomid: model.roomid
                             url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
                             onClicked: popup.completionClicked(completer.completionAt(model.index))
                         }
diff --git a/resources/qml/RoomDirectory.qml b/resources/qml/RoomDirectory.qml
index 2d7b3a34caf13772779b0b1b1c4e47a2098691b5..b51c7bbc12845eaadee5edd9e889ed24b9cdc433 100644
--- a/resources/qml/RoomDirectory.qml
+++ b/resources/qml/RoomDirectory.qml
@@ -65,6 +65,7 @@ ApplicationWindow {
                     width: avatarSize
                     height: avatarSize
                     url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+                    roomid: model.roomid
                     displayName: model.name
                 }
 
diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index a0009174ff582a0664aad83f1b09c06d0297d1e3..addbf5716d8465816a4cac00393ed703f81ad703 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -143,6 +143,8 @@ Page {
             required property int notificationCount
             required property bool hasLoudNotification
             required property bool hasUnreadMessages
+            required property bool isDirect
+            required property string directChatOtherUserId
 
             color: background
             height: avatarSize + 2 * Nheko.paddingMedium
@@ -237,6 +239,8 @@ Page {
                     width: avatarSize
                     url: avatarUrl.replace("mxc://", "image://MxcImage/")
                     displayName: roomName
+                    userid: isDirect ? directChatOtherUserId : ""
+                    roomid: roomId
 
                     Rectangle {
                         id: collapsedNotificationBubble
diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml
index 62175bf053263d90199ba48c15d14b417841fd89..3376a4b61ffea9f26a1f393155800a0c88999aba 100644
--- a/resources/qml/RoomMembers.qml
+++ b/resources/qml/RoomMembers.qml
@@ -39,6 +39,7 @@ ApplicationWindow {
 
             width: 130
             height: width
+            roomid: members.roomId
             displayName: members.roomName
             Layout.alignment: Qt.AlignHCenter
             url: members.avatarUrl.replace("mxc://", "image://MxcImage/")
diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml
index a70cd71a0c795b97897fe18eef95ce4b16d7dbb7..152567c8193cef09469cca50677d6fa0c34c76bf 100644
--- a/resources/qml/RoomSettings.qml
+++ b/resources/qml/RoomSettings.qml
@@ -38,6 +38,7 @@ ApplicationWindow {
 
         Avatar {
             url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
+            roomid: roomSettings.roomid
             displayName: roomSettings.roomName
             height: 130
             width: 130
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index f12060f2e1a2a6e37299b7e420574546e9264ce9..91bbca5b81dbf9ae28ea6531edbefd59511fa761 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -137,6 +137,7 @@ Item {
     ColumnLayout {
         id: preview
 
+        property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomId : "")
         property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "")
         property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "")
         property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "")
@@ -153,6 +154,7 @@ Item {
 
         Avatar {
             url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
+            roomid: parent.roomId
             displayName: parent.roomName
             height: 130
             width: 130
diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml
index 7f67c028dc4bf94d0361f335acb7c14b6a2a90b5..05c61d99dd01acfc1f1854a454b1aeec73b05079 100644
--- a/resources/qml/TopBar.qml
+++ b/resources/qml/TopBar.qml
@@ -13,10 +13,13 @@ Rectangle {
 
     property bool showBackButton: false
     property string roomName: room ? room.roomName : qsTr("No room selected")
+    property string roomId: room ? room.roomId : ""
     property string avatarUrl: room ? room.roomAvatarUrl : ""
     property string roomTopic: room ? room.roomTopic : ""
     property bool isEncrypted: room ? room.isEncrypted : false
     property int trustlevel: room ? room.trustlevel : Crypto.Unverified
+    property bool isDirect: room ? room.isDirect : false
+    property string directChatOtherUserId: room ? room.directChatOtherUserId : ""
 
     Layout.fillWidth: true
     implicitHeight: topLayout.height + Nheko.paddingMedium * 2
@@ -65,10 +68,12 @@ Rectangle {
             width: Nheko.avatarSize
             height: Nheko.avatarSize
             url: avatarUrl.replace("mxc://", "image://MxcImage/")
+            roomid: roomId
+            userid: isDirect ? directChatOtherUserId : ""
             displayName: roomName
             onClicked: {
                 if (room)
-                    TimelineManager.openRoomSettings(room.roomId);
+                    TimelineManager.openRoomSettings(roomId);
 
             }
         }
@@ -135,7 +140,7 @@ Rectangle {
                 Platform.MenuItem {
                     visible: room ? room.permissions.canInvite() : false
                     text: qsTr("Invite users")
-                    onTriggered: TimelineManager.openInviteUsers(room.roomId)
+                    onTriggered: TimelineManager.openInviteUsers(roomId)
                 }
 
                 Platform.MenuItem {
@@ -145,12 +150,12 @@ Rectangle {
 
                 Platform.MenuItem {
                     text: qsTr("Leave room")
-                    onTriggered: TimelineManager.openLeaveRoomDialog(room.roomId)
+                    onTriggered: TimelineManager.openLeaveRoomDialog(roomId)
                 }
 
                 Platform.MenuItem {
                     text: qsTr("Settings")
-                    onTriggered: TimelineManager.openRoomSettings(room.roomId)
+                    onTriggered: TimelineManager.openRoomSettings(roomId)
                 }
 
             }
diff --git a/resources/qml/components/AvatarListTile.qml b/resources/qml/components/AvatarListTile.qml
index 36c26a9780024ae1ab333c93ecfa76adabce9e05..853266c64667f0d2bf17674407af735f20490d1b 100644
--- a/resources/qml/components/AvatarListTile.qml
+++ b/resources/qml/components/AvatarListTile.qml
@@ -23,6 +23,8 @@ Rectangle {
     required property int index
     required property int selectedIndex
     property bool crop: true
+    property alias roomid: avatar.roomid
+    property alias userid: avatar.userid
 
     color: background
     height: avatarSize + 2 * Nheko.paddingMedium
diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml
index e78213e0e698b74cf354a8e43e2681b8a0791b14..c028f4a21955fb361ad5821893e9fb1847600425 100644
--- a/resources/qml/dialogs/ImagePackEditorDialog.qml
+++ b/resources/qml/dialogs/ImagePackEditorDialog.qml
@@ -61,6 +61,7 @@ ApplicationWindow {
                 header: AvatarListTile {
                     title: imagePack.packname
                     avatarUrl: imagePack.avatarUrl
+                    roomid: imagePack.statekey
                     subtitle: imagePack.statekey
                     index: -1
                     selectedIndex: currentImageIndex
@@ -142,6 +143,7 @@ ApplicationWindow {
                         Layout.columnSpan: 2
                         url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/")
                         displayName: imagePack.packname
+                        roomid: imagePack.statekey
                         height: 130
                         width: 130
                         crop: false
@@ -219,6 +221,7 @@ ApplicationWindow {
                         Layout.columnSpan: 2
                         url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/")
                         displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode)
+                        roomid: displayName
                         height: 130
                         width: 130
                         crop: false
diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml
index b217abdd19faf95584b9d52641d78d677cdc0351..ca09ff27618a8f569aa7cfd220b537aa93421fcb 100644
--- a/resources/qml/dialogs/ImagePackSettingsDialog.qml
+++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml
@@ -112,6 +112,7 @@ ApplicationWindow {
                             return qsTr("Globally enabled pack");
                     }
                     selectedIndex: currentPackIndex
+                    roomid: currentPack.statekey
 
                     TapHandler {
                         onSingleTapped: currentPackIndex = index
@@ -135,6 +136,7 @@ ApplicationWindow {
                     property string packName: currentPack ? currentPack.packname : ""
                     property string attribution: currentPack ? currentPack.attribution : ""
                     property string avatarUrl: currentPack ? currentPack.avatarUrl : ""
+                    property string statekey: currentPack ? currentPack.statekey : ""
 
                     anchors.fill: parent
                     anchors.margins: Nheko.paddingLarge
@@ -143,6 +145,7 @@ ApplicationWindow {
                     Avatar {
                         url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/")
                         displayName: packinfo.packName
+                        roomid: packinfo.statekey
                         height: 100
                         width: 100
                         Layout.alignment: Qt.AlignHCenter
diff --git a/resources/qml/voip/ActiveCallBar.qml b/resources/qml/voip/ActiveCallBar.qml
index d44c5edf9e638cf1c83b8c7e48d0faa376663260..be69835655cfc0d6bbb646248249dd3460305777 100644
--- a/resources/qml/voip/ActiveCallBar.qml
+++ b/resources/qml/voip/ActiveCallBar.qml
@@ -34,14 +34,15 @@ Rectangle {
             width: Nheko.avatarSize
             height: Nheko.avatarSize
             url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
-            displayName: CallManager.callParty
+            userid: CallManager.callParty
+            displayName: CallManager.callPartyDisplayName
             onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
         }
 
         Label {
             Layout.leftMargin: 8
             font.pointSize: fontMetrics.font.pointSize * 1.1
-            text: CallManager.callParty
+            text: CallManager.callPartyDisplayName
             color: "#000000"
         }
 
diff --git a/resources/qml/voip/CallInvite.qml b/resources/qml/voip/CallInvite.qml
index 253fa25c6d626ecad0ea1f367a9dfdbadca9971b..1bd5eb265c2a27cc570ee02e3635a3497ff282e3 100644
--- a/resources/qml/voip/CallInvite.qml
+++ b/resources/qml/voip/CallInvite.qml
@@ -40,7 +40,7 @@ Popup {
         Label {
             Layout.alignment: Qt.AlignCenter
             Layout.topMargin: msgView.height / 25
-            text: CallManager.callParty
+            text: CallManager.callPartyDisplayName
             font.pointSize: fontMetrics.font.pointSize * 2
             color: Nheko.colors.windowText
         }
@@ -50,7 +50,8 @@ Popup {
             width: msgView.height / 5
             height: msgView.height / 5
             url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
-            displayName: CallManager.callParty
+            userid: CallManager.callParty
+            displayName: CallManager.callPartyDisplayName
         }
 
         ColumnLayout {
diff --git a/resources/qml/voip/CallInviteBar.qml b/resources/qml/voip/CallInviteBar.qml
index f6c1ecde8dc5034b2297f57fd37e6a576e58c0ff..10f8367a7361084268ef6a9fcb4c9818f6a9ebb0 100644
--- a/resources/qml/voip/CallInviteBar.qml
+++ b/resources/qml/voip/CallInviteBar.qml
@@ -41,14 +41,15 @@ Rectangle {
             width: Nheko.avatarSize
             height: Nheko.avatarSize
             url: CallManager.callPartyAvatarUrl.replace("mxc://", "image://MxcImage/")
-            displayName: CallManager.callParty
+            userid: CallManager.callParty
+            displayName: CallManager.callPartyDisplayName
             onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
         }
 
         Label {
             Layout.leftMargin: 8
             font.pointSize: fontMetrics.font.pointSize * 1.1
-            text: CallManager.callParty
+            text: CallManager.callPartyDisplayName
             color: "#000000"
         }
 
diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml
index 97932cc948e9f1331e08be54ecdebe41c6a842d1..c733012caddb7e5026fe488a05440dbeac319aed 100644
--- a/resources/qml/voip/PlaceCall.qml
+++ b/resources/qml/voip/PlaceCall.qml
@@ -79,6 +79,7 @@ Popup {
                 height: Nheko.avatarSize
                 url: room.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
                 displayName: room.roomName
+                roomid: room.roomid
                 onClicked: TimelineManager.openImageOverlay(room.avatarUrl(userid), room.data.eventId)
             }
 
diff --git a/src/Cache.cpp b/src/Cache.cpp
index 84e2ddc2aaf1fc16b9c4d1cc8f23c94fd8e0afea..9ebc61b947ea54487d1304efa4ab2069010a97f0 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -2776,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)
 {
@@ -4808,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 57a36d73aa81794cc0faef159781bf0876ebb2ee..f862643005798a57d4fca3c5175beaf0a2a3ee38 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 4a5c5c7613a34eeaf3cc11a1f84e9ca8bf4f116e..5f4d392a2d3578581faad432b123e95cad01ab1b 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 7780c80f681a549c8e331908062c40c1c723f1b3..43644d7e24ddf16bf69f707a8a1c179403cbdc56 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -106,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);
@@ -132,6 +136,9 @@ public:
         //! Retrieve all the user ids from a room.
         std::vector<std::string> roomMembers(const std::string &room_id);
 
+        //! Get the other user from an invite to a direct chat.
+        RoomMember getDirectInviteMember(const std::string &room_id);
+
         //! Check if the given user has power leve greater than than
         //! lowest power level of the given events.
         bool hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
@@ -310,7 +317,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 6d41f1c6fc727107c9853dc778329fcec56553e4..601c9d6b17e81e2883e016f56b84ca5e975263f3 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 1d9731916d334307779fa291c63ed021766b59fd..407b8366a6795ee637b08d95cafea96c8dc1beba 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 0000000000000000000000000000000000000000..3b819c7cbd396d25ca301b55fc48366a57a5e02d
--- /dev/null
+++ b/src/JdenticonProvider.cpp
@@ -0,0 +1,107 @@
+// 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);
+
+        QSvgRenderer renderer{
+          jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()};
+        renderer.render(&painter);
+
+        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 0000000000000000000000000000000000000000..bcda29c83abf6305b49dc5afcf9ea3489c23ab0e
--- /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 7eadc6df9a6a009e2ae62e5cc3399901d3ce7b22..b423304f818ad70284374c1c42051958b361c6b8 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 d423af9fd008d61264e7dce28aafd731e946ce11..d9ffb9b12268d48971ea52938a4bd8528146a44e 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/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index af32344c6039c5034f165a341c7f7f552fc3d9bd..7b01b0b8d21e8f31531acbe14c142c90d8ae1cb0 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 93b5321138cb8dccf1fc44bdd3e916cc76865029..bcd9439b57af69eb12b0bcaac220e88d574353ba 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/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 942a4b05c61fa3aa6002fe4df436d5af8daa925c..afe535603dbc6872a2edb10cff8548628ab33b79 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 6ac6da18694ace471402caf4f3976744580ef6e1..c0a87aee5540f92809453576daf41b91f8281d4a 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 78409e1dcd849687d9e548c1de732aeedb666051..ca303040a4e241b50e04b41f348c2e359728d9a8 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 417fbb7fbf9d5739837e6663a2c93d9b71b2c6f8..66e0622e09522f54ff5d9d12999fd5e00f5e051e 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 681cbe091447fea006ca8cd6164f35a3c40f07f2..ea231b03871ba5da2a9a562e2453b2e0c1c13b00 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 4dd5e996ba286c46da54df36fad8c30fec8a94ac..8991de553d94667027338120c99710bb5f52c514 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 b5bcd4ddf42f26edb9d5df685bd9b6ff1e07fccf..cc39714b13cb5d5084fac762224feb74e64128ae 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;