diff --git a/resources/icons/lock_black_24dp.svg b/resources/icons/lock_black_24dp.svg
new file mode 100644
index 0000000000000000000000000000000000000000..1a38f2f686ad14b6451387f19aab794297de8e37
--- /dev/null
+++ b/resources/icons/lock_black_24dp.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g fill="none"><path d="M0 0h24v24H0V0z"/><path d="M0 0h24v24H0V0z" opacity=".87"/></g><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6zm9 14H6V10h12v10zm-6-3c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/></svg>
\ No newline at end of file
diff --git a/src/conversationlistmodelbase.cpp b/src/conversationlistmodelbase.cpp
index 4e92d8698f74f20a37ab09c26f4cb81fe1d45d47..25eb8b821935f2881ea2a19e92aa06e46f3a3b74 100644
--- a/src/conversationlistmodelbase.cpp
+++ b/src/conversationlistmodelbase.cpp
@@ -149,6 +149,8 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
             }
         return ret;
     }
+    case Role::ReadOnly:
+        return QVariant(item.readOnly);
     default:
         break;
     }
diff --git a/src/conversationlistmodelbase.h b/src/conversationlistmodelbase.h
index 2359d15ca498349f6afb121a12ec7e5cbb478122..36979d3fc90beab3873f9d77f53007c9e484c6c2 100644
--- a/src/conversationlistmodelbase.h
+++ b/src/conversationlistmodelbase.h
@@ -48,7 +48,8 @@
     X(IsRequest) \
     X(Mode) \
     X(Uris) \
-    X(Monikers)
+    X(Monikers) \
+    X(ReadOnly)
 
 namespace ConversationList {
 Q_NAMESPACE
diff --git a/src/conversationsadapter.cpp b/src/conversationsadapter.cpp
index 497f347bde8f56e1e3a6001fb08b42458ac0d28d..74e2567f166944474f57eece1e70845c5aa5427b 100644
--- a/src/conversationsadapter.cpp
+++ b/src/conversationsadapter.cpp
@@ -274,8 +274,14 @@ ConversationsAdapter::onProfileUpdated(const QString& contactUri)
 }
 
 void
-ConversationsAdapter::onConversationUpdated(const QString&)
+ConversationsAdapter::onConversationUpdated(const QString& convId)
 {
+    // this could be the result of a member event
+    auto& convInfo = lrcInstance_->getConversationFromConvUid(convId);
+    if (convInfo.uid.isEmpty())
+        return;
+    set_currentConvIsReadOnly(convInfo.readOnly);
+
     updateConversationFilterData();
 }
 
@@ -283,8 +289,6 @@ void
 ConversationsAdapter::onFilterChanged()
 {
     updateConversationFilterData();
-    if (!lrcInstance_->get_selectedConvUid().isEmpty())
-        Q_EMIT indexRepositionRequested();
 }
 
 void
@@ -437,7 +441,8 @@ ConversationsAdapter::getConvInfoMap(const QString& convId)
             {"needsSyncing", convInfo.needsSyncing},
             {"isAudioOnly", isAudioOnly},
             {"callState", static_cast<int>(callState)},
-            {"callStackViewShouldShow", callStackViewShouldShow}};
+            {"callStackViewShouldShow", callStackViewShouldShow},
+            {"readOnly", convInfo.readOnly}};
 }
 
 bool
diff --git a/src/conversationsadapter.h b/src/conversationsadapter.h
index 60a066100023b1728aa83b8d198fb7bb26ce5d09..cae66565ba4e56f44d24bfa1564b745b0f73717a 100644
--- a/src/conversationsadapter.h
+++ b/src/conversationsadapter.h
@@ -36,6 +36,7 @@ class ConversationsAdapter final : public QmlAdapterBase
     QML_PROPERTY(bool, filterRequests)
     QML_PROPERTY(int, totalUnreadMessageCount)
     QML_PROPERTY(int, pendingRequestCount)
+    QML_PROPERTY(bool, currentConvIsReadOnly)
 
 public:
     explicit ConversationsAdapter(SystemTray* systemTray,
@@ -56,7 +57,6 @@ Q_SIGNALS:
     void showSearchStatus(const QString& status);
 
     void navigateToWelcomePageRequested();
-    void indexRepositionRequested();
     void conversationReady(const QString& convId);
 
 private Q_SLOTS:
diff --git a/src/mainview/components/ConversationSmartListContextMenu.qml b/src/mainview/components/ConversationSmartListContextMenu.qml
index a23d486b8e6165a68189088cb481e56481a90da1..dd9fe7ff6106493de60ebfb13b52cdbd4fa47855 100644
--- a/src/mainview/components/ConversationSmartListContextMenu.qml
+++ b/src/mainview/components/ConversationSmartListContextMenu.qml
@@ -45,7 +45,7 @@ ContextMenuAutoLoader {
         GeneralMenuItem {
             id: startVideoCallItem
 
-            canTrigger: !hasCall
+            canTrigger: !hasCall && !ConversationsAdapter.currentConvIsReadOnly
             itemName: JamiStrings.startVideoCall
             iconSource: JamiResources.videocam_24dp_svg
             onClicked: {
@@ -59,7 +59,7 @@ ContextMenuAutoLoader {
         GeneralMenuItem {
             id: startAudioCall
 
-            canTrigger: !hasCall
+            canTrigger: !hasCall && !ConversationsAdapter.currentConvIsReadOnly
             itemName: JamiStrings.startAudioCall
             iconSource: JamiResources.place_audiocall_24dp_svg
             onClicked: {
diff --git a/src/mainview/components/MessageWebView.qml b/src/mainview/components/MessageWebView.qml
index 35707086aafd8bb63071d7eb1e5ac3497e415fb5..84aac50b8eb41d4d76166dfee8ab51ebf273362f 100644
--- a/src/mainview/components/MessageWebView.qml
+++ b/src/mainview/components/MessageWebView.qml
@@ -114,9 +114,10 @@ Rectangle {
     Connections {
         target: MessagesAdapter
 
-        function onChangeInvitationViewRequest(show, isSwarm, needsSyncing,
-                                               title, convId) {
-            if (show)
+        function onSetChatViewMode(showInvitationPage,
+                                   isSwarm, needsSyncing,
+                                   title, convId) {
+            if (showInvitationPage)
                 root.mode = MessageWebView.Mode.Invitation
             else {
                 root.mode = MessageWebView.Mode.Chat
@@ -129,6 +130,16 @@ Rectangle {
         }
     }
 
+    Connections {
+        target: ConversationsAdapter
+
+        function onCurrentConvIsReadOnlyChanged() {
+            var isVisible = !ConversationsAdapter.currentConvIsReadOnly
+            setMessagingHeaderButtonsVisible(isVisible)
+            messageWebViewFooter.visible = isVisible
+        }
+    }
+
     QtObject {
         id: jsBridgeObject
 
diff --git a/src/mainview/components/MessageWebViewFooter.qml b/src/mainview/components/MessageWebViewFooter.qml
index ac60be724a84f4b975c85377364df034ddd42ccb..48821c80fd846b6ec10b2d50fa09c96723f08273 100644
--- a/src/mainview/components/MessageWebViewFooter.qml
+++ b/src/mainview/components/MessageWebViewFooter.qml
@@ -83,8 +83,13 @@ Rectangle {
             messageBar.textAreaObj.pasteText()
         }
 
-        function onChangeInvitationViewRequest(show, isSwarm) {
-            var footerVisibility = show ? !isSwarm : !show
+        function onSetChatViewMode(showInvitationPage,
+                                   isSwarm, needsSyncing,
+                                   title, convId, readOnly) {
+            var footerVisibility = showInvitationPage ?
+                        !isSwarm :
+                        !showInvitationPage
+            footerVisibility &= !readOnly
             messageBar.visible = footerVisibility
             dataTransferSendContainer.visible = footerVisibility
             root.visible = footerVisibility
diff --git a/src/mainview/components/MessageWebViewHeader.qml b/src/mainview/components/MessageWebViewHeader.qml
index 7d39aeda74450f2bd2e121b8839412f2d6a9766b..bc53aac5315820be33dfb68b80e7b6c202fc61f1 100644
--- a/src/mainview/components/MessageWebViewHeader.qml
+++ b/src/mainview/components/MessageWebViewHeader.qml
@@ -27,7 +27,7 @@ import net.jami.Adapters 1.0
 import "../../commoncomponents"
 
 Rectangle {
-    id: messagingHeaderRect
+    id: root
 
     property string userAliasLabelText
     property string userUserNameLabelText
@@ -72,9 +72,9 @@ Rectangle {
 
             onClicked: {
                 if (backToWelcomeViewButtonSource === JamiResources.back_24dp_svg)
-                    messagingHeaderRect.backClicked()
+                    root.backClicked()
                 else
-                    messagingHeaderRect.needToHideConversationInCall()
+                    root.needToHideConversationInCall()
             }
         }
 
@@ -84,7 +84,7 @@ Rectangle {
             Layout.alignment: Qt.AlignLeft | Qt.AlignTop
 
             // Width + margin.
-            Layout.preferredWidth: messagingHeaderRect.width
+            Layout.preferredWidth: root.width
                                    - backToWelcomeViewButton.width - buttonGroup.width - 45
             Layout.fillHeight: true
             Layout.topMargin: 7
diff --git a/src/mainview/components/SmartListItemDelegate.qml b/src/mainview/components/SmartListItemDelegate.qml
index 52a6ff309d430a8ff7efbf484817214fb30b4ef2..9aae5880ba470de151affdcf6506dcb4a2615cb2 100644
--- a/src/mainview/components/SmartListItemDelegate.qml
+++ b/src/mainview/components/SmartListItemDelegate.qml
@@ -107,8 +107,15 @@ ItemDelegate {
             }
         }
 
+        // read-only conversation indicator
+        ResponsiveImage {
+            visible: ReadOnly
+            source: JamiResources.lock_black_24dp_svg
+            color: JamiTheme.primaryForegroundColor
+        }
+
         ColumnLayout {
-            visible: InCall || UnreadMessagesCount
+            visible: (InCall || UnreadMessagesCount) && !ReadOnly
             Layout.preferredWidth: childrenRect.width
             Layout.fillHeight: true
             spacing: 2
@@ -153,9 +160,11 @@ ItemDelegate {
         ListView.view.model.select(index)
         if (LRCInstance.currentAccountType === Profile.Type.SIP)
             CallAdapter.placeAudioOnlyCall()
-        else
-            CallAdapter.placeCall()
-
+        else {
+            if (!ConversationsAdapter.currentConvIsReadOnly) {
+                CallAdapter.placeCall()
+            }
+        }
         // TODO: factor this out (visible should be observing)
         communicationPageMessageWebView.setSendContactRequestButtonVisible(false)
     }
diff --git a/src/messagesadapter.cpp b/src/messagesadapter.cpp
index 9782c9b83462b31c439c1f0896e35251111de2f0..9e9843fa285999593a77f645dee640a8267d0550 100644
--- a/src/messagesadapter.cpp
+++ b/src/messagesadapter.cpp
@@ -62,21 +62,24 @@ MessagesAdapter::setupChatView(const QVariantMap& convInfo)
     bool isLegacy = !convInfo["isSwarm"].toBool();
     bool isRequest = convInfo["isRequest"].toBool();
     bool needsSyncing = convInfo["needsSyncing"].toBool();
+    bool readOnly = convInfo["readOnly"].toBool();
 
     QMetaObject::invokeMethod(qmlObj_,
                               "setSendContactRequestButtonVisible",
                               Q_ARG(QVariant, isLegacy && isRequest));
     QMetaObject::invokeMethod(qmlObj_,
                               "setMessagingHeaderButtonsVisible",
-                              Q_ARG(QVariant, !(isLegacy && (isRequest || needsSyncing))));
+                              Q_ARG(QVariant,
+                                    !readOnly && !(isLegacy && (isRequest || needsSyncing))));
 
     setMessagesVisibility(false);
     setIsSwarm(!isLegacy);
-    Q_EMIT changeInvitationViewRequest(isRequest || needsSyncing,
-                                       isLegacy,
-                                       needsSyncing,
-                                       convInfo["title"].toString(),
-                                       convInfo["convId"].toString());
+    Q_EMIT setChatViewMode(isRequest || needsSyncing,
+                           isLegacy,
+                           needsSyncing,
+                           convInfo["title"].toString(),
+                           convInfo["convId"].toString(),
+                           readOnly);
 
     Utils::oneShotConnect(qmlObj_, SIGNAL(messagesCleared()), this, SLOT(slotMessagesCleared()));
     clearChatView();
@@ -369,14 +372,16 @@ MessagesAdapter::setConversationProfileData(const lrc::api::conversation::Info&
         QMetaObject::invokeMethod(qmlObj_,
                                   "setMessagingHeaderButtonsVisible",
                                   Q_ARG(QVariant,
-                                        !(convInfo.isSwarm()
-                                          && (convInfo.isRequest || convInfo.needsSyncing))));
-
-        Q_EMIT changeInvitationViewRequest(convInfo.isRequest or convInfo.needsSyncing,
-                                           convInfo.isSwarm(),
-                                           convInfo.needsSyncing,
-                                           title,
-                                           convInfo.uid);
+                                        !convInfo.readOnly
+                                            && !(convInfo.isSwarm()
+                                                 && (convInfo.isRequest || convInfo.needsSyncing))));
+
+        Q_EMIT setChatViewMode(convInfo.isRequest || convInfo.needsSyncing,
+                               convInfo.isSwarm(),
+                               convInfo.needsSyncing,
+                               title,
+                               convInfo.uid,
+                               convInfo.readOnly);
         if (convInfo.isSwarm())
             return;
 
@@ -593,7 +598,7 @@ MessagesAdapter::refuseInvitation(const QString& convUid)
 {
     const auto currentConvUid = convUid.isEmpty() ? lrcInstance_->get_selectedConvUid() : convUid;
     lrcInstance_->getCurrentConversationModel()->removeConversation(currentConvUid, false);
-    changeInvitationViewRequest(false);
+    Q_EMIT setChatViewMode(false);
 }
 
 void
@@ -601,7 +606,7 @@ MessagesAdapter::blockConversation(const QString& convUid)
 {
     const auto currentConvUid = convUid.isEmpty() ? lrcInstance_->get_selectedConvUid() : convUid;
     lrcInstance_->getCurrentConversationModel()->removeConversation(currentConvUid, true);
-    changeInvitationViewRequest(false);
+    Q_EMIT setChatViewMode(false);
 }
 
 void
diff --git a/src/messagesadapter.h b/src/messagesadapter.h
index 69f5d01036d33412858cb9cac9451ca5c87182de..f8651c468455b2886ce15b65ace819e5dfe10328 100644
--- a/src/messagesadapter.h
+++ b/src/messagesadapter.h
@@ -93,11 +93,12 @@ Q_SIGNALS:
     void newMessageBarPlaceholderText(QString placeholderText);
     void newFilePasted(QString filePath);
     void newTextPasted();
-    void changeInvitationViewRequest(bool show,
-                                     bool isSwarm = false,
-                                     bool needsSyncing = false,
-                                     QString contactTitle = {},
-                                     QString contactUri = {});
+    void setChatViewMode(bool showInvitationPage,
+                         bool isSwarm = false,
+                         bool needsSyncing = false,
+                         QString contactTitle = {},
+                         QString contactUri = {},
+                         bool readOnly = false);
 
 private Q_SLOTS:
     void slotMessagesCleared();