diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index fc3211363fa9c082ed7c8c3638709aefd700cff0..063284c14e2f621fb010d8ced0d829df3dfe34bf 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -94,6 +94,22 @@ Pane {
 
     }
 
+    Component {
+        id: allowedRoomSettingsComponent
+
+        AllowedRoomsSettingsDialog {
+        }
+
+    }
+
+    function showAllowedRoomsEditor(settings) {
+        var dialog = allowedRoomSettingsComponent.createObject(timelineRoot, {
+            "roomSettings": settings
+        });
+        dialog.show();
+        destroyOnClose(dialog);
+    }
+
     Component {
         id: roomMembersComponent
 
diff --git a/resources/qml/dialogs/AllowedRoomsSettingsDialog.qml b/resources/qml/dialogs/AllowedRoomsSettingsDialog.qml
new file mode 100644
index 0000000000000000000000000000000000000000..60ac06de66135c847e5751ba2246701373407dc5
--- /dev/null
+++ b/resources/qml/dialogs/AllowedRoomsSettingsDialog.qml
@@ -0,0 +1,178 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import "../ui"
+import Qt.labs.platform 1.1 as Platform
+import QtQuick 2.15
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.13
+import im.nheko 1.0
+
+ApplicationWindow {
+    id: allowedDialog
+
+    property var roomSettings
+
+    minimumWidth: 340
+    minimumHeight: 450
+    width: 450
+    height: 680
+    palette: Nheko.colors
+    color: Nheko.colors.window
+    modality: Qt.NonModal
+    flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+    title: qsTr("Allowed rooms settings")
+
+    Shortcut {
+        sequence: StandardKey.Cancel
+        onActivated: roomSettingsDialog.close()
+    }
+
+    ColumnLayout {
+        anchors.margins: Nheko.paddingMedium
+        anchors.fill: parent
+        spacing: 0
+
+
+        MatrixText {
+            text: qsTr("List of rooms that allow access to this room. Anyone who is in any of those rooms can join this room.")
+            font.pixelSize: Math.floor(fontMetrics.font.pixelSize * 1.1)
+            Layout.fillWidth: true
+            Layout.fillHeight: false
+            color: Nheko.colors.text
+            Layout.bottomMargin: Nheko.paddingMedium
+        }
+
+        ListView {
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+
+            id: view
+
+            clip: true
+
+            ScrollHelper {
+                flickable: parent
+                anchors.fill: parent
+            }
+
+            model: roomSettings.allowedRoomsModel
+            spacing: 4
+            cacheBuffer: 50
+
+            delegate: RowLayout {
+                anchors.left: parent.left
+                anchors.right: parent.right
+
+                ColumnLayout {
+                    Layout.fillWidth: true
+                    Text {
+                        Layout.fillWidth: true
+                        text: model.name
+                        color: Nheko.colors.text
+                        textFormat: Text.PlainText
+                    }
+
+                    Text {
+                        Layout.fillWidth: true
+                        text: model.isParent ? qsTr("Parent community") : qsTr("Other room")
+                        color: Nheko.colors.buttonText
+                        textFormat: Text.PlainText
+                    }
+                }
+
+                ToggleButton {
+                    checked: model.allowed
+                    Layout.alignment: Qt.AlignRight
+                    onCheckedChanged: model.allowed = checked
+                }
+            }
+        }
+
+        Column{
+            id: roomEntryCompleter
+            Layout.fillWidth: true
+
+            spacing: 1
+            z: 5
+
+            Completer {
+                id: roomCompleter
+
+                visible: roomEntry.text.length > 0
+                width: parent.width
+                roomId: allowedDialog.roomSettings.roomId
+                completerName: "room"
+                bottomToTop: true
+                fullWidth: true
+                avatarHeight: Nheko.avatarSize / 2
+                avatarWidth: Nheko.avatarSize / 2
+                centerRowContent: false
+                rowMargin: 2
+                rowSpacing: 2
+            }
+
+            MatrixTextField {
+                id: roomEntry
+
+                width: parent.width
+
+                placeholderText: qsTr("Enter additional rooms not in the list yet...")
+
+                //font.pixelSize: Math.ceil(quickSwitcher.textHeight * 0.6)
+                color: Nheko.colors.text
+                onTextEdited: {
+                    roomCompleter.completer.searchString = text;
+                }
+                Keys.onPressed: {
+                    if (event.key == Qt.Key_Up || event.key == Qt.Key_Backtab) {
+                        event.accepted = true;
+                        roomCompleter.up();
+                    } else if (event.key == Qt.Key_Down || event.key == Qt.Key_Tab) {
+                        event.accepted = true;
+                        if (event.key == Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))
+                        roomCompleter.up();
+                        else
+                        roomCompleter.down();
+                    } else if (event.matches(StandardKey.InsertParagraphSeparator)) {
+                        roomCompleter.finishCompletion();
+                        event.accepted = true;
+                    }
+                }
+            }
+
+        }
+
+        Connections {
+            function onCompletionSelected(id) {
+                console.log("selected: " + id);
+                roomSettings.allowedRoomsModel.addRoom(id);
+                roomEntry.clear();
+            }
+
+            function onCountChanged() {
+                if (roomCompleter.count > 0 && (roomCompleter.currentIndex < 0 || roomCompleter.currentIndex >= roomCompleter.count))
+                roomCompleter.currentIndex = 0;
+
+            }
+
+            target: roomCompleter
+        }
+
+    }
+
+    footer: DialogButtonBox {
+        id: dbb
+
+        standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
+        onAccepted: {
+            roomSettings.applyAllowedFromModel();
+            allowedDialog.close();
+        }
+        onRejected: allowedDialog.close()
+    }
+
+}
diff --git a/resources/qml/dialogs/RoomSettings.qml b/resources/qml/dialogs/RoomSettings.qml
index 137df6c462d3c19764d4f8118840ee2bad993d2f..33a6f6fa1a8b931f1c67fffada2acd03440eac6c 100644
--- a/resources/qml/dialogs/RoomSettings.qml
+++ b/resources/qml/dialogs/RoomSettings.qml
@@ -288,32 +288,98 @@ ApplicationWindow {
                 }
 
                 Label {
-                    text: qsTr("Room access")
+                    text: qsTr("Anyone can join")
                     Layout.fillWidth: true
                     color: Nheko.colors.text
                 }
 
-                ComboBox {
+                ToggleButton {
+                    id: publicRoomButton
+
                     enabled: roomSettings.canChangeJoinRules
-                    model: {
-                        let opts = [qsTr("Anyone and guests"), qsTr("Anyone"), qsTr("Invited users")];
-                        if (roomSettings.supportsKnocking)
-                            opts.push(qsTr("By knocking"));
+                    checked: !roomSettings.privateAccess
+                    Layout.alignment: Qt.AlignRight
+                }
 
-                        if (roomSettings.supportsRestricted)
-                            opts.push(qsTr("Restricted by membership in other rooms"));
+                Label {
+                    text: qsTr("Allow knocking")
+                    Layout.fillWidth: true
+                    color: Nheko.colors.text
+                    visible: knockingButton.visible
+                }
 
-                        if (roomSettings.supportsKnockRestricted)
-                            opts.push(qsTr("Restricted by membership in other rooms or by knocking"));
+                ToggleButton {
+                    id: knockingButton
 
-                        return opts;
+                    visible: !publicRoomButton.checked
+                    enabled: roomSettings.canChangeJoinRules && roomSettings.supportsKnocking
+                    checked: roomSettings.knockingEnabled
+                    onCheckedChanged: {
+                        if (checked && !roomSettings.supportsKnockRestricted) restrictedButton.checked = false;
                     }
-                    currentIndex: roomSettings.accessJoinRules
-                    onActivated: {
-                        roomSettings.changeAccessRules(index);
+                    Layout.alignment: Qt.AlignRight
+                }
+
+                Label {
+                    text: qsTr("Allow joining via other rooms")
+                    Layout.fillWidth: true
+                    color: Nheko.colors.text
+                    visible: restrictedButton.visible
+                }
+
+                ToggleButton {
+                    id: restrictedButton
+
+                    visible: !publicRoomButton.checked
+                    enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
+                    checked: roomSettings.restrictedEnabled
+                    onCheckedChanged: {
+                        if (checked && !roomSettings.supportsKnockRestricted) knockingButton.checked = false;
                     }
+                    Layout.alignment: Qt.AlignRight
+                }
+
+                Label {
+                    text: qsTr("Rooms to join via")
+                    Layout.fillWidth: true
+                    color: Nheko.colors.text
+                    visible: allowedRoomsButton.visible
+                }
+
+                Button {
+                    id: allowedRoomsButton
+
+                    visible: restrictedButton.checked && restrictedButton.visible
+                    enabled: roomSettings.canChangeJoinRules && roomSettings.supportsRestricted
+
+                    text: qsTr("Change")
+                    ToolTip.text: qsTr("Change the list of rooms users can join this room via. Usually this is the official community of this room.")
+                    onClicked: timelineRoot.showAllowedRoomsEditor(roomSettings)
+                    Layout.alignment: Qt.AlignRight
+                }
+
+                Label {
+                    text: qsTr("Allow guests to join")
+                    Layout.fillWidth: true
+                    color: Nheko.colors.text
+                }
+
+                ToggleButton {
+                    id: guestAccessButton
+
+                    enabled: roomSettings.canChangeJoinRules
+                    checked: roomSettings.guestAccess
+                    Layout.alignment: Qt.AlignRight
+                }
+
+                Button {
+                    visible: publicRoomButton.checked == roomSettings.privateAccess || knockingButton.checked != roomSettings.knockingEnabled || restrictedButton.checked != roomSettings.restrictedEnabled || guestAccessButton.checked != roomSettings.guestAccess || roomSettings.allowedRoomsModified
+                    enabled: roomSettings.canChangeJoinRules
+
+                    text: qsTr("Apply access rules")
+                    onClicked: roomSettings.changeAccessRules(!publicRoomButton.checked, guestAccessButton.checked, knockingButton.checked, restrictedButton.checked)
+                    Layout.columnSpan: 2
                     Layout.fillWidth: true
-                    WheelHandler{} // suppress scrolling changing values
                 }
 
                 Label {
diff --git a/resources/res.qrc b/resources/res.qrc
index c14ebd5f2c75eecf69069061682a102b801ca18d..27d9c081e536131fa4e1048be6154d3e0e51b6f0 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -166,6 +166,7 @@
         <file>qml/dialogs/ReadReceipts.qml</file>
         <file>qml/dialogs/RoomDirectory.qml</file>
         <file>qml/dialogs/RoomMembers.qml</file>
+        <file>qml/dialogs/AllowedRoomsSettingsDialog.qml</file>
         <file>qml/dialogs/RoomSettings.qml</file>
         <file>qml/dialogs/UserProfile.qml</file>
         <file>qml/emoji/EmojiPicker.qml</file>
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 5b8509999d42d498853ab34b5be0a97c467eb194..63cf28440ecc867cda963538b701f2a5b60a3261 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -148,6 +148,7 @@ MainWindow::registerQmlTypes()
     qRegisterMetaType<mtx::responses::PublicRoom>();
     qRegisterMetaType<mtx::responses::Profile>();
     qRegisterMetaType<CombinedImagePackModel *>();
+    qRegisterMetaType<RoomSettingsAllowedRoomsModel *>();
     qRegisterMetaType<mtx::events::collections::TimelineEvents>();
     qRegisterMetaType<std::vector<DeviceInfo>>();
 
diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp
index 2d124e1e6fb9a6d93c0264baf9c1c7df78f97297..31ae863efc93f4a94dfa5f781dbec118d6b10905 100644
--- a/src/ui/RoomSettings.cpp
+++ b/src/ui/RoomSettings.cpp
@@ -27,6 +27,7 @@ RoomSettings::RoomSettings(QString roomid, QObject *parent)
   : QObject(parent)
   , roomid_{std::move(roomid)}
 {
+    connect(this, &RoomSettings::accessJoinRulesChanged, &RoomSettings::allowedRoomsChanged);
     retrieveRoomInfo();
 
     // get room setting notifications
@@ -66,22 +67,15 @@ RoomSettings::RoomSettings(QString roomid, QObject *parent)
       });
 
     // access rules
-    if (info_.join_rule == state::JoinRule::Public) {
-        if (info_.guest_access) {
-            accessRules_ = 0;
-        } else {
-            accessRules_ = 1;
-        }
-    } else if (info_.join_rule == state::JoinRule::Invite) {
-        accessRules_ = 2;
-    } else if (info_.join_rule == state::JoinRule::Knock) {
-        accessRules_ = 3;
-    } else if (info_.join_rule == state::JoinRule::Restricted) {
-        accessRules_ = 4;
-    } else if (info_.join_rule == state::JoinRule::KnockRestricted) {
-        accessRules_ = 5;
-    }
+    this->accessRules_ = cache::client()
+                           ->getStateEvent<mtx::events::state::JoinRules>(roomid_.toStdString())
+                           .value_or(mtx::events::StateEvent<mtx::events::state::JoinRules>{})
+                           .content;
+    using mtx::events::state::AccessState;
+    guestRules_ = info_.guest_access ? AccessState::CanJoin : AccessState::Forbidden;
     emit accessJoinRulesChanged();
+
+    this->allowedRoomsModel = new RoomSettingsAllowedRoomsModel(this);
 }
 
 QString
@@ -158,10 +152,49 @@ RoomSettings::notifications()
     return notifications_;
 }
 
-int
-RoomSettings::accessJoinRules()
+bool
+RoomSettings::privateAccess() const
+{
+    return accessRules_.join_rule != mtx::events::state::JoinRule::Public;
+}
+
+bool
+RoomSettings::guestAccess() const
 {
-    return accessRules_;
+    return guestRules_ == mtx::events::state::AccessState::CanJoin;
+}
+bool
+RoomSettings::knockingEnabled() const
+{
+    return accessRules_.join_rule == mtx::events::state::JoinRule::Knock ||
+           accessRules_.join_rule == mtx::events::state::JoinRule::KnockRestricted;
+}
+bool
+RoomSettings::restrictedEnabled() const
+{
+    return accessRules_.join_rule == mtx::events::state::JoinRule::Restricted ||
+           accessRules_.join_rule == mtx::events::state::JoinRule::KnockRestricted;
+}
+
+QStringList
+RoomSettings::allowedRooms() const
+{
+    QStringList rooms;
+    rooms.reserve(accessRules_.allow.size());
+    for (const auto &e : accessRules_.allow) {
+        if (e.type == mtx::events::state::JoinAllowanceType::RoomMembership)
+            rooms.push_back(QString::fromStdString(e.room_id));
+    }
+    return rooms;
+}
+void
+RoomSettings::setAllowedRooms(QStringList rooms)
+{
+    accessRules_.allow.clear();
+    for (const auto &e : rooms) {
+        accessRules_.allow.push_back(
+          {mtx::events::state::JoinAllowanceType::RoomMembership, e.toStdString()});
+    }
 }
 
 void
@@ -254,24 +287,48 @@ RoomSettings::isEncryptionEnabled() const
 bool
 RoomSettings::supportsKnocking() const
 {
-    return info_.version != "" && info_.version != "1" && info_.version != "2" &&
-           info_.version != "3" && info_.version != "4" && info_.version != "5" &&
-           info_.version != "6";
+    const static std::set<std::string_view> unsupported{
+      "",
+      "1",
+      "2",
+      "3",
+      "4",
+      "5",
+      "6",
+    };
+    return !unsupported.count(info_.version);
 }
 bool
 RoomSettings::supportsRestricted() const
 {
-    return info_.version != "" && info_.version != "1" && info_.version != "2" &&
-           info_.version != "3" && info_.version != "4" && info_.version != "5" &&
-           info_.version != "6" && info_.version != "7";
+    const static std::set<std::string_view> unsupported{
+      "",
+      "1",
+      "2",
+      "3",
+      "4",
+      "5",
+      "6",
+      "7",
+    };
+    return !unsupported.count(info_.version);
 }
 bool
 RoomSettings::supportsKnockRestricted() const
 {
-    return info_.version != "" && info_.version != "1" && info_.version != "2" &&
-           info_.version != "3" && info_.version != "4" && info_.version != "5" &&
-           info_.version != "6" && info_.version != "7" && info_.version != "8" &&
-           info_.version != "9";
+    const static std::set<std::string_view> unsupported{
+      "",
+      "1",
+      "2",
+      "3",
+      "4",
+      "5",
+      "6",
+      "7",
+      "8",
+      "9",
+    };
+    return !unsupported.count(info_.version);
 }
 
 void
@@ -327,47 +384,41 @@ RoomSettings::changeNotifications(int currentIndex)
 }
 
 void
-RoomSettings::changeAccessRules(int index)
+RoomSettings::changeAccessRules(bool private_,
+                                bool guestsAllowed,
+                                bool knockingAllowed,
+                                bool restrictedAllowed)
 {
     using namespace mtx::events::state;
 
-    auto guest_access = [](int index) -> state::GuestAccess {
+    auto guest_access = [guestsAllowed]() -> state::GuestAccess {
         state::GuestAccess event;
 
-        if (index == 0)
+        if (guestsAllowed)
             event.guest_access = state::AccessState::CanJoin;
         else
             event.guest_access = state::AccessState::Forbidden;
 
         return event;
-    }(index);
+    }();
 
-    auto join_rule = [](int index) -> state::JoinRules {
-        state::JoinRules event;
+    auto join_rule = [this, private_, knockingAllowed, restrictedAllowed]() -> state::JoinRules {
+        state::JoinRules event = this->accessRules_;
 
-        switch (index) {
-        case 0:
-        case 1:
+        if (!private_) {
             event.join_rule = state::JoinRule::Public;
-            break;
-        case 2:
-            event.join_rule = state::JoinRule::Invite;
-            break;
-        case 3:
+        } else if (knockingAllowed && restrictedAllowed && supportsKnockRestricted()) {
+            event.join_rule = state::JoinRule::KnockRestricted;
+        } else if (knockingAllowed && supportsKnocking()) {
             event.join_rule = state::JoinRule::Knock;
-            break;
-        case 4:
+        } else if (restrictedAllowed && supportsRestricted()) {
             event.join_rule = state::JoinRule::Restricted;
-            break;
-        case 5:
-            event.join_rule = state::JoinRule::KnockRestricted;
-            break;
-        default:
+        } else {
             event.join_rule = state::JoinRule::Invite;
         }
 
         return event;
-    }(index);
+    }();
 
     updateAccessRules(roomid_.toStdString(), join_rule, guest_access);
 }
@@ -445,13 +496,16 @@ RoomSettings::updateAccessRules(const std::string &room_id,
                                 const mtx::events::state::JoinRules &join_rule,
                                 const mtx::events::state::GuestAccess &guest_access)
 {
-    isLoading_ = true;
+    isLoading_            = true;
+    allowedRoomsModified_ = false;
     emit loadingChanged();
+    emit allowedRoomsModifiedChanged();
 
     http::client()->send_state_event(
       room_id,
       join_rule,
-      [this, room_id, guest_access](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+      [this, room_id, guest_access, join_rule](const mtx::responses::EventId &,
+                                               mtx::http::RequestErr err) {
           if (err) {
               nhlog::net()->warn("failed to send m.room.join_rule: {} {}",
                                  static_cast<int>(err->status_code),
@@ -465,7 +519,7 @@ RoomSettings::updateAccessRules(const std::string &room_id,
           http::client()->send_state_event(
             room_id,
             guest_access,
-            [this](const mtx::responses::EventId &, mtx::http::RequestErr err) {
+            [this, join_rule](const mtx::responses::EventId &, mtx::http::RequestErr err) {
                 if (err) {
                     nhlog::net()->warn("failed to send m.room.guest_access: {} {}",
                                        static_cast<int>(err->status_code),
@@ -475,6 +529,9 @@ RoomSettings::updateAccessRules(const std::string &room_id,
 
                 isLoading_ = false;
                 emit loadingChanged();
+
+                this->accessRules_ = join_rule;
+                emit accessJoinRulesChanged();
             });
       });
 }
@@ -576,3 +633,101 @@ RoomSettings::updateAvatar()
             });
       });
 }
+
+RoomSettingsAllowedRoomsModel::RoomSettingsAllowedRoomsModel(RoomSettings *parent)
+  : QAbstractListModel(parent)
+  , settings(parent)
+{
+    this->allowedRoomIds = settings->allowedRooms();
+
+    auto prIds = cache::client()->getParentRoomIds(settings->roomId().toStdString());
+    for (const auto &prId : prIds) {
+        this->parentSpaces.insert(QString::fromStdString(prId));
+    }
+
+    this->listedRoomIds = QStringList(parentSpaces.begin(), parentSpaces.end());
+
+    for (const auto &e : this->allowedRoomIds) {
+        if (!this->parentSpaces.count(e))
+            this->listedRoomIds.push_back(e);
+    }
+}
+
+QHash<int, QByteArray>
+RoomSettingsAllowedRoomsModel::roleNames() const
+{
+    return {
+      {Roles::Name, "name"},
+      {Roles::IsAllowed, "allowed"},
+      {Roles::IsSpaceParent, "isParent"},
+    };
+}
+
+int
+RoomSettingsAllowedRoomsModel::rowCount(const QModelIndex &) const
+{
+    return listedRoomIds.size();
+}
+
+QVariant
+RoomSettingsAllowedRoomsModel::data(const QModelIndex &index, int role) const
+{
+    if (index.row() < 0 || index.row() > listedRoomIds.size())
+        return {};
+
+    if (role == Roles::IsAllowed) {
+        return allowedRoomIds.contains(listedRoomIds.at(index.row()));
+    } else if (role == Roles::IsSpaceParent) {
+        return parentSpaces.find(listedRoomIds.at(index.row())) != parentSpaces.cend();
+    } else if (role == Roles::Name) {
+        auto id   = listedRoomIds.at(index.row());
+        auto info = cache::client()->getRoomInfo({
+          id.toStdString(),
+        });
+        if (!info.empty())
+            return QString::fromStdString(info[id].name);
+        else
+            return "";
+    } else {
+        return {};
+    }
+}
+
+bool
+RoomSettingsAllowedRoomsModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+    if (index.row() < 0 || index.row() > listedRoomIds.size())
+        return false;
+
+    if (role != Roles::IsAllowed)
+        return false;
+
+    if (value.toBool()) {
+        if (!allowedRoomIds.contains(listedRoomIds.at(index.row())))
+            allowedRoomIds.push_back(listedRoomIds.at(index.row()));
+    } else {
+        allowedRoomIds.removeAll(listedRoomIds.at(index.row()));
+    }
+
+    return true;
+}
+
+void
+RoomSettingsAllowedRoomsModel::addRoom(QString room)
+{
+    if (listedRoomIds.contains(room) || !room.startsWith('!'))
+        return;
+
+    beginInsertRows(QModelIndex(), listedRoomIds.size(), listedRoomIds.size());
+    listedRoomIds.push_back(room);
+    allowedRoomIds.push_back(room);
+    endInsertRows();
+}
+
+void
+RoomSettings::applyAllowedFromModel()
+{
+    this->setAllowedRooms(this->allowedRoomsModel->allowedRoomIds);
+    this->allowedRoomsModified_ = true;
+    emit allowedRoomsModifiedChanged();
+}
diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h
index 4cb5bcf4c3cf27c8e1712bde065848015ffea13a..35698310ea3d9b57c04ec3401c82c3657cafec76 100644
--- a/src/ui/RoomSettings.h
+++ b/src/ui/RoomSettings.h
@@ -5,10 +5,13 @@
 
 #pragma once
 
+#include <QAbstractListModel>
 #include <QObject>
 #include <QSet>
 #include <QString>
 
+#include <unordered_set>
+
 #include <mtx/events/event_type.hpp>
 #include <mtx/events/guest_access.hpp>
 
@@ -27,6 +30,43 @@ signals:
     void stopLoading();
 };
 
+class RoomSettings;
+
+class RoomSettingsAllowedRoomsModel : public QAbstractListModel
+{
+    Q_OBJECT
+
+public:
+    enum Roles
+    {
+        Name,
+        IsAllowed,
+        IsSpaceParent,
+    };
+
+    explicit RoomSettingsAllowedRoomsModel(RoomSettings *parent);
+
+    QHash<int, QByteArray> roleNames() const override;
+    int rowCount(const QModelIndex &) const override;
+    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+    bool
+    setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override;
+    Q_INVOKABLE void addRoom(QString room);
+
+    Qt::ItemFlags flags(const QModelIndex &) const override
+    {
+        return Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled |
+               Qt::ItemNeverHasChildren;
+    }
+
+    QStringList allowedRoomIds;
+
+private:
+    QStringList listedRoomIds;
+    std::unordered_set<QString> parentSpaces;
+    RoomSettings *settings;
+};
+
 class RoomSettings : public QObject
 {
     Q_OBJECT
@@ -39,7 +79,10 @@ class RoomSettings : public QObject
     Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY avatarUrlChanged)
     Q_PROPERTY(int memberCount READ memberCount CONSTANT)
     Q_PROPERTY(int notifications READ notifications NOTIFY notificationsChanged)
-    Q_PROPERTY(int accessJoinRules READ accessJoinRules NOTIFY accessJoinRulesChanged)
+    Q_PROPERTY(bool privateAccess READ privateAccess NOTIFY accessJoinRulesChanged)
+    Q_PROPERTY(bool guestAccess READ guestAccess NOTIFY accessJoinRulesChanged)
+    Q_PROPERTY(bool knockingEnabled READ knockingEnabled NOTIFY accessJoinRulesChanged)
+    Q_PROPERTY(bool restrictedEnabled READ restrictedEnabled NOTIFY accessJoinRulesChanged)
     Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)
     Q_PROPERTY(bool canChangeAvatar READ canChangeAvatar CONSTANT)
     Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT)
@@ -49,6 +92,11 @@ class RoomSettings : public QObject
     Q_PROPERTY(bool supportsKnocking READ supportsKnocking CONSTANT)
     Q_PROPERTY(bool supportsRestricted READ supportsRestricted CONSTANT)
     Q_PROPERTY(bool supportsKnockRestricted READ supportsKnockRestricted CONSTANT)
+    Q_PROPERTY(
+      QStringList allowedRooms READ allowedRooms WRITE setAllowedRooms NOTIFY allowedRoomsChanged)
+    Q_PROPERTY(RoomSettingsAllowedRoomsModel *allowedRoomsModel MEMBER allowedRoomsModel CONSTANT)
+    Q_PROPERTY(
+      bool allowedRoomsModified READ allowedRoomsModified NOTIFY allowedRoomsModifiedChanged)
 
 public:
     RoomSettings(QString roomid, QObject *parent = nullptr);
@@ -62,7 +110,10 @@ public:
     QString roomAvatarUrl();
     int memberCount() const;
     int notifications();
-    int accessJoinRules();
+    bool privateAccess() const;
+    bool guestAccess() const;
+    bool knockingEnabled() const;
+    bool restrictedEnabled() const;
     bool isLoading() const;
     //! Whether the user has enough power level to send m.room.join_rules events.
     bool canChangeJoinRules() const;
@@ -76,14 +127,22 @@ public:
     bool supportsKnocking() const;
     bool supportsRestricted() const;
     bool supportsKnockRestricted() const;
+    QStringList allowedRooms() const;
+    void setAllowedRooms(QStringList rooms);
+    bool allowedRoomsModified() const { return allowedRoomsModified_; }
 
     Q_INVOKABLE void enableEncryption();
     Q_INVOKABLE void updateAvatar();
-    Q_INVOKABLE void changeAccessRules(int index);
+    Q_INVOKABLE void changeAccessRules(bool private_,
+                                       bool guestsAllowed,
+                                       bool knockingAllowed,
+                                       bool restrictedAllowed);
     Q_INVOKABLE void changeNotifications(int currentIndex);
     Q_INVOKABLE void changeTopic(QString topic);
     Q_INVOKABLE void changeName(QString name);
 
+    Q_INVOKABLE void applyAllowedFromModel();
+
 signals:
     void loadingChanged();
     void roomNameChanged();
@@ -92,7 +151,9 @@ signals:
     void encryptionChanged();
     void notificationsChanged();
     void accessJoinRulesChanged();
+    void allowedRoomsChanged();
     void displayError(const QString &errorMessage);
+    void allowedRoomsModifiedChanged();
 
 public slots:
     void stopLoading();
@@ -106,9 +167,14 @@ private:
 
 private:
     QString roomid_;
-    bool usesEncryption_ = false;
-    bool isLoading_      = false;
+    bool usesEncryption_       = false;
+    bool isLoading_            = false;
+    bool allowedRoomsModified_ = false;
     RoomInfo info_;
     int notifications_ = 0;
-    int accessRules_   = 0;
+
+    mtx::events::state::JoinRules accessRules_;
+    mtx::events::state::AccessState guestRules_ = mtx::events::state::AccessState::Forbidden;
+
+    RoomSettingsAllowedRoomsModel *allowedRoomsModel;
 };