From 3570b23d8ab4bad995c53acf4ae386c34978892e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Thu, 5 Jan 2023 16:08:44 -0500
Subject: [PATCH] swarmdetailspanel: show kicked contacts if administrator

This patch avoid for non-admins to try to re-add kicked members
as filtered out from the list. However kicked members are only
visible for administrators.

Change-Id: Ie01b7071c072d147bbc0f39e477cc24d7fd58b1a
---
 CMakeLists.txt                                |  2 +
 daemon                                        |  2 +-
 src/app/constant/JamiStrings.qml              |  1 +
 src/app/currentconversation.cpp               | 25 +++++-
 src/app/currentconversation.h                 |  4 +-
 src/app/currentconversationmembers.cpp        | 76 +++++++++++++++++++
 src/app/currentconversationmembers.h          | 59 ++++++++++++++
 .../mainview/components/AddMemberPanel.qml    |  5 +-
 src/app/mainview/components/ChatView.qml      | 18 +++--
 .../mainview/components/ChatViewHeader.qml    |  2 +-
 .../components/PluginHandlerPicker.qml        |  6 +-
 .../mainview/components/SwarmDetailsPanel.qml | 42 +++++-----
 .../SwarmParticipantContextMenu.qml           |  9 ++-
 src/app/messagesadapter.cpp                   |  7 ++
 src/app/messagesadapter.h                     |  1 +
 src/app/qmlregister.cpp                       |  1 +
 16 files changed, 216 insertions(+), 44 deletions(-)
 create mode 100644 src/app/currentconversationmembers.cpp
 create mode 100644 src/app/currentconversationmembers.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d4e24da0d..d0fdd65be 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -215,6 +215,7 @@ set(COMMON_SOURCES
   ${APP_SRC_DIR}/wizardviewstepmodel.cpp
   ${APP_SRC_DIR}/avatarregistry.cpp
   ${APP_SRC_DIR}/currentconversation.cpp
+  ${APP_SRC_DIR}/currentconversationmembers.cpp
   ${APP_SRC_DIR}/currentaccount.cpp
   ${APP_SRC_DIR}/videodevices.cpp
   ${APP_SRC_DIR}/videoprovider.cpp
@@ -275,6 +276,7 @@ set(COMMON_HEADERS
   ${APP_SRC_DIR}/wizardviewstepmodel.h
   ${APP_SRC_DIR}/avatarregistry.h
   ${APP_SRC_DIR}/currentconversation.h
+  ${APP_SRC_DIR}/currentconversationmembers.h
   ${APP_SRC_DIR}/currentaccount.h
   ${APP_SRC_DIR}/videodevices.h
   ${APP_SRC_DIR}/videoprovider.h
diff --git a/daemon b/daemon
index 47f3fd14a..3481da56c 160000
--- a/daemon
+++ b/daemon
@@ -1 +1 @@
-Subproject commit 47f3fd14ab0532ff94fd5317f885689928d9293b
+Subproject commit 3481da56c29e378bf3003d067c06e409d5c52c32
diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml
index 4872ddc00..59920b894 100644
--- a/src/app/constant/JamiStrings.qml
+++ b/src/app/constant/JamiStrings.qml
@@ -828,6 +828,7 @@ Item {
     property string goToConversation: qsTr("Go to conversation")
     property string promoteAdministrator: qsTr("Promote to administrator")
     property string kickMember: qsTr("Kick member")
+    property string reinstateMember: qsTr("Reinstate member")
     property string administrator: qsTr("Administrator")
     property string invited: qsTr("Invited")
     property string removeMember: qsTr("Remove member")
diff --git a/src/app/currentconversation.cpp b/src/app/currentconversation.cpp
index b4e4bb45b..a0f940dbe 100644
--- a/src/app/currentconversation.cpp
+++ b/src/app/currentconversation.cpp
@@ -24,6 +24,7 @@ CurrentConversation::CurrentConversation(LRCInstance* lrcInstance, QObject* pare
     : QObject(parent)
     , lrcInstance_(lrcInstance)
 {
+    uris_ = new CurrentConversationMembers(lrcInstance, this);
     // whenever the account changes, reconnect the new conversation model
     // for updates to the conversation and call state/id
     connect(lrcInstance_,
@@ -59,7 +60,23 @@ CurrentConversation::updateData()
         if (auto optConv = accInfo.conversationModel->getConversationForUid(convId)) {
             auto& convInfo = optConv->get();
             set_lastSelfMessageId(convInfo.lastSelfMessageId);
-            set_uris(convInfo.participantsUris());
+            QStringList uris, bannedUris;
+            auto isAdmin = false;
+            for (const auto& p : convInfo.participants) {
+                if (p.uri == accInfo.profileInfo.uri) {
+                    isAdmin = p.role == member::Role::ADMIN;
+                }
+                if (p.role == member::Role::BANNED) {
+                    bannedUris.push_back(p.uri);
+                } else {
+                    uris.push_back(p.uri);
+                }
+            }
+            if (isAdmin) {
+                for (const auto& banned : bannedUris)
+                    uris.push_back(banned);
+            }
+            uris_->setMembers(accountId, convId, uris);
             set_isSwarm(convInfo.isSwarm());
             set_isLegacy(convInfo.isLegacy());
             set_isCoreDialog(convInfo.isCoreDialog());
@@ -170,6 +187,12 @@ CurrentConversation::setInfo(const QString& key, const QString& value)
     accInfo.conversationModel->updateConversationInfos(convId, infos);
 }
 
+CurrentConversationMembers*
+CurrentConversation::uris() const
+{
+    return uris_;
+}
+
 void
 CurrentConversation::onConversationUpdated(const QString& convId)
 {
diff --git a/src/app/currentconversation.h b/src/app/currentconversation.h
index 84f640f64..a6245f7a2 100644
--- a/src/app/currentconversation.h
+++ b/src/app/currentconversation.h
@@ -19,6 +19,7 @@
 #pragma once
 
 #include "lrcinstance.h"
+#include "currentconversationmembers.h"
 
 #include <QObject>
 #include <QString>
@@ -32,7 +33,6 @@ class CurrentConversation final : public QObject
     QML_PROPERTY(QString, id)
     QML_PROPERTY(QString, title)
     QML_PROPERTY(QString, description)
-    QML_PROPERTY(QStringList, uris)
     QML_PROPERTY(bool, isSwarm)
     QML_PROPERTY(bool, isLegacy)
     QML_PROPERTY(bool, isCoreDialog)
@@ -66,6 +66,7 @@ public:
     Q_INVOKABLE QString getPreference(const QString& key) const;
     Q_INVOKABLE MapStringString getPreferences() const;
     Q_INVOKABLE void setInfo(const QString& key, const QString& value);
+    CurrentConversationMembers* uris() const;
 
 Q_SIGNALS:
     void scrollTo(const QString& msgId);
@@ -87,6 +88,7 @@ Q_SIGNALS:
 
 private:
     LRCInstance* lrcInstance_;
+    CurrentConversationMembers* uris_;
 
     void connectModel();
 };
diff --git a/src/app/currentconversationmembers.cpp b/src/app/currentconversationmembers.cpp
new file mode 100644
index 000000000..076ea5aa0
--- /dev/null
+++ b/src/app/currentconversationmembers.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "currentconversationmembers.h"
+
+#include <algorithm>
+#include <random>
+
+CurrentConversationMembers::CurrentConversationMembers(LRCInstance* lrcInstance, QObject* parent)
+    : QAbstractListModel(parent)
+    , lrcInstance_(lrcInstance)
+{}
+
+int
+CurrentConversationMembers::rowCount(const QModelIndex& parent) const
+{
+    if (parent.isValid())
+        return 0;
+    return members_.size();
+}
+
+void
+CurrentConversationMembers::setMembers(const QString& accountId,
+                                       const QString& convId,
+                                       const QStringList& members)
+{
+    beginResetModel();
+    accountId_ = accountId;
+    convId_ = convId;
+    members_ = members;
+    set_count(members.size());
+    endResetModel();
+}
+
+QVariant
+CurrentConversationMembers::data(const QModelIndex& index, int role) const
+{
+    if (!index.isValid())
+        return QVariant();
+
+    auto member = members_.at(index.row());
+
+    switch (role) {
+    case Members::Role::MemberUri:
+        return QVariant::fromValue(member);
+    case Members::Role::MemberRole:
+        return QVariant::fromValue(
+            lrcInstance_->getAccountInfo(accountId_).conversationModel->memberRole(convId_, member));
+    }
+    return QVariant();
+}
+
+QHash<int, QByteArray>
+CurrentConversationMembers::roleNames() const
+{
+    using namespace Members;
+    QHash<int, QByteArray> roles;
+#define X(role) roles[role] = #role;
+    MEMBERS_ROLES
+#undef X
+    return roles;
+}
diff --git a/src/app/currentconversationmembers.h b/src/app/currentconversationmembers.h
new file mode 100644
index 000000000..bd082c470
--- /dev/null
+++ b/src/app/currentconversationmembers.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include "lrcinstance.h"
+#include "appsettingsmanager.h"
+#include "qtutils.h"
+
+#include <QAbstractListModel>
+#include <QObject>
+
+#define MEMBERS_ROLES \
+    X(MemberUri) \
+    X(MemberRole)
+
+namespace Members {
+Q_NAMESPACE
+enum Role {
+    DummyRole = Qt::UserRole + 1,
+#define X(role) role,
+    MEMBERS_ROLES
+#undef X
+};
+Q_ENUM_NS(Role)
+} // namespace Members
+
+class CurrentConversationMembers : public QAbstractListModel
+{
+    Q_OBJECT
+    QML_PROPERTY(int, count)
+
+public:
+    explicit CurrentConversationMembers(LRCInstance* lrcInstance, QObject* parent = nullptr);
+    void setMembers(const QString& accountId, const QString& convId, const QStringList& members);
+
+    int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+    QHash<int, QByteArray> roleNames() const override;
+
+private:
+    LRCInstance* lrcInstance_;
+    QString accountId_;
+    QString convId_;
+    QStringList members_;
+};
\ No newline at end of file
diff --git a/src/app/mainview/components/AddMemberPanel.qml b/src/app/mainview/components/AddMemberPanel.qml
index dc8c02557..5c91db5d9 100644
--- a/src/app/mainview/components/AddMemberPanel.qml
+++ b/src/app/mainview/components/AddMemberPanel.qml
@@ -63,10 +63,9 @@ Rectangle {
             model: ContactAdapter.getContactSelectableModel(type)
 
             Connections {
-                enabled: visible
-                target: CurrentConversation
+                target: CurrentConversationMembers
 
-                function onUrisChanged(uris) {
+                function onCountChanged() {
                     contactPickerListView.model = ContactAdapter.getContactSelectableModel(type)
                 }
             }
diff --git a/src/app/mainview/components/ChatView.qml b/src/app/mainview/components/ChatView.qml
index 77b64b11c..38faae52b 100644
--- a/src/app/mainview/components/ChatView.qml
+++ b/src/app/mainview/components/ChatView.qml
@@ -157,13 +157,6 @@ Rectangle {
             Connections {
                 target: CurrentConversation
 
-                function onUrisChanged(uris) {
-                    if (CurrentConversation.uris.length >= 8 && addMemberPanel.visible) {
-                        swarmDetailsPanel.visible = false
-                        addMemberPanel.visible = !addMemberPanel.visible
-                    }
-                }
-
                 function onNeedsHost() {
                     viewCoordinator.presentDialog(
                                 appWindow,
@@ -171,6 +164,17 @@ Rectangle {
                 }
             }
 
+            Connections {
+                target: CurrentConversationMembers
+
+                function onCountChanged() {
+                    if (CurrentConversationMembers.count >= 8 && addMemberPanel.visible) {
+                        swarmDetailsPanel.visible = false
+                        addMemberPanel.visible = !addMemberPanel.visible
+                    }
+                }
+            }
+
             onAddToConversationClicked: {
                 swarmDetailsPanel.visible = false
                 if (addMemberPanel.visible) {
diff --git a/src/app/mainview/components/ChatViewHeader.qml b/src/app/mainview/components/ChatViewHeader.qml
index 599fef401..468f3da14 100644
--- a/src/app/mainview/components/ChatViewHeader.qml
+++ b/src/app/mainview/components/ChatViewHeader.qml
@@ -208,7 +208,7 @@ Rectangle {
                     normalColor: JamiTheme.chatviewBgColor
                     imageColor: JamiTheme.chatviewButtonColor
 
-                    visible: CurrentConversation.uris.length < 8 && addMemberVisibility
+                    visible: CurrentConversationMembers.count < 8 && addMemberVisibility
 
                     onClicked: addToConversationClicked()
                 }
diff --git a/src/app/mainview/components/PluginHandlerPicker.qml b/src/app/mainview/components/PluginHandlerPicker.qml
index 936cef4f8..59c5413ba 100644
--- a/src/app/mainview/components/PluginHandlerPicker.qml
+++ b/src/app/mainview/components/PluginHandlerPicker.qml
@@ -60,7 +60,7 @@ Popup {
                     if (isCall) {
                         pluginhandlerPickerListView.model = PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id)
                     } else {
-                        var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.uris[0]
+                        var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0]
                         pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(LRCInstance.currentAccountId, peerId)
                     }
                 }
@@ -72,7 +72,7 @@ Popup {
                     pluginhandlerPickerListView.model = PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id)
                 } else {
                     var accountId = LRCInstance.currentAccountId
-                    var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.uris[0]
+                    var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0]
                     PluginModel.toggleChatHandler(handlerId, accountId, peerId, !isLoaded)
                     pluginhandlerPickerListView.model = PluginAdapter.getChatHandlerSelectableModel(accountId, peerId)
                 }
@@ -127,7 +127,7 @@ Popup {
                         if (isCall) {
                             return PluginAdapter.getMediaHandlerSelectableModel(CurrentCall.id)
                         } else {
-                            var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversation.uris[0]
+                            var peerId = CurrentConversation.isSwarm ? CurrentConversation.id : CurrentConversationMembers[0]
                             return PluginAdapter.getChatHandlerSelectableModel(LRCInstance.currentAccountId, peerId)
                         }
                     }
diff --git a/src/app/mainview/components/SwarmDetailsPanel.qml b/src/app/mainview/components/SwarmDetailsPanel.qml
index 50db6f315..6f85610dc 100644
--- a/src/app/mainview/components/SwarmDetailsPanel.qml
+++ b/src/app/mainview/components/SwarmDetailsPanel.qml
@@ -238,7 +238,7 @@ Rectangle {
 
                     down: tabBar.currentIndex === 1
                     labelText: {
-                        var membersNb = CurrentConversation.uris.length;
+                        var membersNb = CurrentConversationMembers.count;
                         if (membersNb > 1)
                             return JamiStrings.members.arg(membersNb)
                         return JamiStrings.member
@@ -605,7 +605,7 @@ Rectangle {
                     }
                 }
 
-                model: CurrentConversation.uris
+                model: CurrentConversationMembers
                 delegate: ItemDelegate {
                     id: member
 
@@ -626,11 +626,11 @@ Rectangle {
                         id: memberMouseArea
 
                         anchors.fill: parent
-                        enabled: modelData !== CurrentAccount.uri
+                        enabled: MemberUri !== CurrentAccount.uri
                         acceptedButtons: Qt.RightButton
                         onClicked: function (mouse) {
                             var position = mapToItem(members, mouse.x, mouse.y)
-                            contextMenu.openMenuAt(position.x, position.y, modelData)
+                            contextMenu.openMenuAt(position.x, position.y, MemberUri)
                         }
                     }
 
@@ -640,19 +640,17 @@ Rectangle {
                         anchors.rightMargin: JamiTheme.preferredMarginSize
 
                         Avatar {
+                            id: avatar
                             width: JamiTheme.smartListAvatarSize
                             height: JamiTheme.smartListAvatarSize
                             Layout.leftMargin: JamiTheme.preferredMarginSize
                             Layout.topMargin: JamiTheme.preferredMarginSize / 2
                             z: -index
-                            opacity: {
-                                var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
-                                return role === Member.Role.INVITED ? 0.5 : 1
-                            }
+                            opacity: (MemberRole === Member.Role.INVITED || MemberRole === Member.Role.BANNED)? 0.5 : 1
 
-                            imageId: CurrentAccount.uri == modelData ? CurrentAccount.id : modelData
-                            showPresenceIndicator: UtilsAdapter.getContactPresence(CurrentAccount.id, modelData)
-                            mode: CurrentAccount.uri == modelData ? Avatar.Mode.Account : Avatar.Mode.Contact
+                            imageId: CurrentAccount.uri == MemberUri ? CurrentAccount.id : MemberUri
+                            showPresenceIndicator: UtilsAdapter.getContactPresence(CurrentAccount.id, MemberUri)
+                            mode: CurrentAccount.uri == MemberUri ? Avatar.Mode.Account : Avatar.Mode.Contact
                         }
 
                         ElidedTextLabel {
@@ -662,16 +660,12 @@ Rectangle {
                             Layout.topMargin: JamiTheme.preferredMarginSize / 2
                             Layout.fillWidth: true
 
-                            eText: UtilsAdapter.getContactBestName(CurrentAccount.id, modelData)
+                            eText: UtilsAdapter.getContactBestName(CurrentAccount.id, MemberUri)
                             maxWidth: width
 
                             font.pointSize: JamiTheme.participantFontSize
                             color: JamiTheme.primaryForegroundColor
-                            opacity: {
-                                var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
-                                return role === Member.Role.INVITED ? 0.5 : 1
-                            }
-
+                            opacity: (MemberRole === Member.Role.INVITED || MemberRole === Member.Role.BANNED)? 0.5 : 1
                             font.kerning: true
 
                             verticalAlignment: Text.AlignVCenter
@@ -682,27 +676,25 @@ Rectangle {
                         }
 
                         ElidedTextLabel {
-                            id: role
+                            id: roleLabel
 
                             Layout.preferredHeight: JamiTheme.preferredFieldHeight
                             Layout.topMargin: JamiTheme.preferredMarginSize / 2
 
                             eText: {
-                                var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
-                                if (role === Member.Role.ADMIN)
+                                if (MemberRole === Member.Role.ADMIN)
                                     return JamiStrings.administrator
-                                if (role === Member.Role.INVITED)
+                                if (MemberRole === Member.Role.INVITED)
                                     return JamiStrings.invited
+                                if (MemberRole === Member.Role.BANNED)
+                                    return JamiStrings.banned
                                 return ""
                             }
                             maxWidth: JamiTheme.preferredFieldWidth
 
                             font.pointSize: JamiTheme.participantFontSize
                             color: JamiTheme.textColorHovered
-                            opacity: {
-                                var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, modelData)
-                                return role === Member.Role.INVITED ? 0.5 : 1
-                            }
+                            opacity: (MemberRole === Member.Role.INVITED || MemberRole === Member.Role.BANNED)? 0.5 : 1
                             font.kerning: true
 
                             horizontalAlignment: Text.AlignRight
diff --git a/src/app/mainview/components/SwarmParticipantContextMenu.qml b/src/app/mainview/components/SwarmParticipantContextMenu.qml
index fbaff5551..f30526af0 100644
--- a/src/app/mainview/components/SwarmParticipantContextMenu.qml
+++ b/src/app/mainview/components/SwarmParticipantContextMenu.qml
@@ -74,12 +74,17 @@ ContextMenuAutoLoader {
         },
         GeneralMenuItem {
             id: kickMember
-            itemName: JamiStrings.kickMember
+            property var memberRole: UtilsAdapter.getParticipantRole(CurrentAccount.id, conversationId, participantUri)
+            itemName: memberRole === Member.Role.BANNED ? JamiStrings.reinstateMember : JamiStrings.kickMember
             iconSource: JamiResources.kick_member_svg
             canTrigger: role === Member.Role.ADMIN
 
             onClicked: {
-                MessagesAdapter.removeConversationMember(conversationId, participantUri)
+                if (memberRole === Member.Role.BANNED) {
+                    MessagesAdapter.addConversationMember(conversationId, participantUri)
+                } else {
+                    MessagesAdapter.removeConversationMember(conversationId, participantUri)
+                }
             }
         }
     ]
diff --git a/src/app/messagesadapter.cpp b/src/app/messagesadapter.cpp
index b7bd9d093..b96f68e06 100644
--- a/src/app/messagesadapter.cpp
+++ b/src/app/messagesadapter.cpp
@@ -516,6 +516,13 @@ MessagesAdapter::removeConversationMember(const QString& convUid, const QString&
     accInfo.conversationModel->removeConversationMember(convUid, memberUri);
 }
 
+void
+MessagesAdapter::addConversationMember(const QString& convUid, const QString& memberUri)
+{
+    auto& accInfo = lrcInstance_->getCurrentAccountInfo();
+    accInfo.conversationModel->addConversationMember(convUid, memberUri);
+}
+
 void
 MessagesAdapter::removeContact(const QString& convUid, bool banContact)
 {
diff --git a/src/app/messagesadapter.h b/src/app/messagesadapter.h
index bf5644d1e..afa1389d5 100644
--- a/src/app/messagesadapter.h
+++ b/src/app/messagesadapter.h
@@ -85,6 +85,7 @@ protected:
     Q_INVOKABLE void connectConversationModel();
     Q_INVOKABLE void sendConversationRequest();
     Q_INVOKABLE void removeConversation(const QString& convUid);
+    Q_INVOKABLE void addConversationMember(const QString& convUid, const QString& participantUri);
     Q_INVOKABLE void removeConversationMember(const QString& convUid, const QString& participantUri);
     Q_INVOKABLE void removeContact(const QString& convUid, bool banContact = false);
     Q_INVOKABLE void clearConversationHistory(const QString& accountId, const QString& convUid);
diff --git a/src/app/qmlregister.cpp b/src/app/qmlregister.cpp
index 80faf8f8f..bc7a88dd4 100644
--- a/src/app/qmlregister.cpp
+++ b/src/app/qmlregister.cpp
@@ -141,6 +141,7 @@ registerTypes(QQmlEngine* engine,
     QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, pluginAdapter, "PluginAdapter");
     QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentCall, "CurrentCall");
     QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentConversation, "CurrentConversation");
+    QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentConversation->uris(), "CurrentConversationMembers");
     QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentAccount, "CurrentAccount");
     QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, videoDevices, "VideoDevices");
     QML_REGISTERSINGLETONTYPE_POBJECT(NS_ADAPTERS, currentAccountToMigrate, "CurrentAccountToMigrate")
-- 
GitLab