summary refs log tree commit diff
path: root/resources/qml/dialogs
diff options
context:
space:
mode:
authorLoren Burkholder <computersemiexpert@outlook.com>2021-09-24 23:27:57 -0400
committerLoren Burkholder <computersemiexpert@outlook.com>2021-09-29 20:16:46 -0400
commit4e020645f118055b94eda5704573854e4b81cf80 (patch)
tree29f194dc60f38c97ba036b7e49180dbd0be6e3eb /resources/qml/dialogs
parentTranslated using Weblate (Dutch) (diff)
downloadnheko-4e020645f118055b94eda5704573854e4b81cf80.tar.xz
Reorganize all the dialogs into the dialogs folder
Diffstat (limited to 'resources/qml/dialogs')
-rw-r--r--resources/qml/dialogs/InviteDialog.qml160
-rw-r--r--resources/qml/dialogs/RawMessageDialog.qml52
-rw-r--r--resources/qml/dialogs/ReadReceipts.qml131
-rw-r--r--resources/qml/dialogs/RoomDirectory.qml216
-rw-r--r--resources/qml/dialogs/RoomMembers.qml180
-rw-r--r--resources/qml/dialogs/RoomSettings.qml305
-rw-r--r--resources/qml/dialogs/UserProfile.qml327
7 files changed, 1371 insertions, 0 deletions
diff --git a/resources/qml/dialogs/InviteDialog.qml b/resources/qml/dialogs/InviteDialog.qml
new file mode 100644
index 00000000..86c176be
--- /dev/null
+++ b/resources/qml/dialogs/InviteDialog.qml
@@ -0,0 +1,160 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import im.nheko 1.0
+
+ApplicationWindow {
+    id: inviteDialogRoot
+
+    property string roomId
+    property string plainRoomName
+    property InviteesModel invitees
+
+    function addInvite() {
+        if (inviteeEntry.isValidMxid) {
+            invitees.addUser(inviteeEntry.text);
+            inviteeEntry.clear();
+        }
+    }
+
+    function cleanUpAndClose() {
+        if (inviteeEntry.isValidMxid)
+            addInvite();
+
+        invitees.accept();
+        close();
+    }
+
+    title: qsTr("Invite users to %1").arg(plainRoomName)
+    height: 380
+    width: 340
+    palette: Nheko.colors
+    color: Nheko.colors.window
+    flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+    Component.onCompleted: Nheko.reparent(inviteDialogRoot)
+
+    Shortcut {
+        sequence: "Ctrl+Enter"
+        onActivated: cleanUpAndClose()
+    }
+
+    Shortcut {
+        sequence: StandardKey.Cancel
+        onActivated: inviteDialogRoot.close()
+    }
+
+    ColumnLayout {
+        anchors.fill: parent
+        anchors.margins: Nheko.paddingMedium
+        spacing: Nheko.paddingMedium
+
+        Label {
+            text: qsTr("User ID to invite")
+            Layout.fillWidth: true
+            color: Nheko.colors.text
+        }
+
+        RowLayout {
+            spacing: Nheko.paddingMedium
+
+            MatrixTextField {
+                id: inviteeEntry
+
+                property bool isValidMxid: text.match("@.+?:.{3,}")
+
+                backgroundColor: Nheko.colors.window
+                placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.")
+                Layout.fillWidth: true
+                onAccepted: {
+                    if (isValidMxid)
+                        addInvite();
+
+                }
+                Component.onCompleted: forceActiveFocus()
+                Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier))
+                Keys.onPressed: {
+                    if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier))
+                        cleanUpAndClose();
+
+                }
+            }
+
+            Button {
+                text: qsTr("Add")
+                enabled: inviteeEntry.isValidMxid
+                onClicked: addInvite()
+            }
+
+        }
+
+        ListView {
+            id: inviteesList
+
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            model: invitees
+
+            delegate: RowLayout {
+                spacing: Nheko.paddingMedium
+
+                Avatar {
+                    width: Nheko.avatarSize
+                    height: Nheko.avatarSize
+                    userid: model.mxid
+                    url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+                    displayName: model.displayName
+                    onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
+                }
+
+                ColumnLayout {
+                    spacing: Nheko.paddingSmall
+
+                    Label {
+                        text: model.displayName
+                        color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window)
+                        font.pointSize: fontMetrics.font.pointSize
+                    }
+
+                    Label {
+                        text: model.mxid
+                        color: Nheko.colors.buttonText
+                        font.pointSize: fontMetrics.font.pointSize * 0.9
+                    }
+
+                    Item {
+                        Layout.fillHeight: true
+                        Layout.fillWidth: true
+                    }
+
+                }
+
+            }
+
+        }
+
+    }
+
+    footer: DialogButtonBox {
+        id: buttons
+
+        Button {
+            text: qsTr("Invite")
+            DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
+            enabled: invitees.count > 0
+            onClicked: cleanUpAndClose()
+        }
+
+        Button {
+            text: qsTr("Cancel")
+            DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole
+            onClicked: inviteDialogRoot.close()
+        }
+
+    }
+
+}
diff --git a/resources/qml/dialogs/RawMessageDialog.qml b/resources/qml/dialogs/RawMessageDialog.qml
new file mode 100644
index 00000000..c171de7e
--- /dev/null
+++ b/resources/qml/dialogs/RawMessageDialog.qml
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import im.nheko 1.0
+
+ApplicationWindow {
+    id: rawMessageRoot
+
+    property alias rawMessage: rawMessageView.text
+
+    height: 420
+    width: 420
+    palette: Nheko.colors
+    color: Nheko.colors.window
+    flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+    Component.onCompleted: Nheko.reparent(rawMessageRoot)
+
+    Shortcut {
+        sequence: StandardKey.Cancel
+        onActivated: rawMessageRoot.close()
+    }
+
+    ScrollView {
+        anchors.margins: Nheko.paddingMedium
+        anchors.fill: parent
+        palette: Nheko.colors
+        padding: Nheko.paddingMedium
+
+        TextArea {
+            id: rawMessageView
+
+            font: Nheko.monospaceFont()
+            color: Nheko.colors.text
+            readOnly: true
+
+            background: Rectangle {
+                color: Nheko.colors.base
+            }
+
+        }
+
+    }
+
+    footer: DialogButtonBox {
+        standardButtons: DialogButtonBox.Ok
+        onAccepted: rawMessageRoot.close()
+    }
+
+}
diff --git a/resources/qml/dialogs/ReadReceipts.qml b/resources/qml/dialogs/ReadReceipts.qml
new file mode 100644
index 00000000..e825dd81
--- /dev/null
+++ b/resources/qml/dialogs/ReadReceipts.qml
@@ -0,0 +1,131 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+import im.nheko 1.0
+
+ApplicationWindow {
+    id: readReceiptsRoot
+
+    property ReadReceiptsProxy readReceipts
+    property Room room
+
+    height: 380
+    width: 340
+    minimumHeight: 380
+    minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium
+    palette: Nheko.colors
+    color: Nheko.colors.window
+    flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+    Component.onCompleted: Nheko.reparent(readReceiptsRoot)
+
+    Shortcut {
+        sequence: StandardKey.Cancel
+        onActivated: readReceiptsRoot.close()
+    }
+
+    ColumnLayout {
+        anchors.fill: parent
+        anchors.margins: Nheko.paddingMedium
+        spacing: Nheko.paddingMedium
+
+        Label {
+            id: headerTitle
+
+            color: Nheko.colors.text
+            Layout.alignment: Qt.AlignCenter
+            text: qsTr("Read receipts")
+            font.pointSize: fontMetrics.font.pointSize * 1.5
+        }
+
+        ScrollView {
+            palette: Nheko.colors
+            padding: Nheko.paddingMedium
+            ScrollBar.horizontal.visible: false
+            Layout.fillHeight: true
+            Layout.minimumHeight: 200
+            Layout.fillWidth: true
+
+            ListView {
+                id: readReceiptsList
+
+                clip: true
+                spacing: Nheko.paddingMedium
+                boundsBehavior: Flickable.StopAtBounds
+                model: readReceipts
+
+                delegate: RowLayout {
+                    spacing: Nheko.paddingMedium
+
+                    Avatar {
+                        width: Nheko.avatarSize
+                        height: Nheko.avatarSize
+                        userid: model.mxid
+                        url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+                        displayName: model.displayName
+                        onClicked: room.openUserProfile(model.mxid)
+                        ToolTip.visible: avatarHover.hovered
+                        ToolTip.text: model.mxid
+
+                        HoverHandler {
+                            id: avatarHover
+                        }
+
+                    }
+
+                    ColumnLayout {
+                        spacing: Nheko.paddingSmall
+
+                        Label {
+                            text: model.displayName
+                            color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window)
+                            font.pointSize: fontMetrics.font.pointSize
+                            ToolTip.visible: displayNameHover.hovered
+                            ToolTip.text: model.mxid
+
+                            TapHandler {
+                                onSingleTapped: room.openUserProfile(userId)
+                            }
+
+                            CursorShape {
+                                anchors.fill: parent
+                                cursorShape: Qt.PointingHandCursor
+                            }
+
+                            HoverHandler {
+                                id: displayNameHover
+                            }
+
+                        }
+
+                        Label {
+                            text: model.timestamp
+                            color: Nheko.colors.buttonText
+                            font.pointSize: fontMetrics.font.pointSize * 0.9
+                        }
+
+                        Item {
+                            Layout.fillHeight: true
+                            Layout.fillWidth: true
+                        }
+
+                    }
+
+                }
+
+            }
+
+        }
+
+    }
+
+    footer: DialogButtonBox {
+        standardButtons: DialogButtonBox.Ok
+        onAccepted: readReceiptsRoot.close()
+    }
+
+}
diff --git a/resources/qml/dialogs/RoomDirectory.qml b/resources/qml/dialogs/RoomDirectory.qml
new file mode 100644
index 00000000..5c27fc26
--- /dev/null
+++ b/resources/qml/dialogs/RoomDirectory.qml
@@ -0,0 +1,216 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import "../ui"
+import QtQuick 2.9
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.3
+import im.nheko 1.0
+
+ApplicationWindow {
+    id: roomDirectoryWindow
+
+    property RoomDirectoryModel publicRooms
+
+    visible: true
+    minimumWidth: 650
+    minimumHeight: 420
+    palette: Nheko.colors
+    color: Nheko.colors.window
+    modality: Qt.WindowModal
+    flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+    Component.onCompleted: Nheko.reparent(roomDirectoryWindow)
+    title: qsTr("Explore Public Rooms")
+
+    Shortcut {
+        sequence: StandardKey.Cancel
+        onActivated: roomDirectoryWindow.close()
+    }
+
+    ListView {
+        id: roomDirView
+
+        anchors.fill: parent
+        model: publicRooms
+
+        ScrollHelper {
+            flickable: parent
+            anchors.fill: parent
+            enabled: !Settings.mobileMode
+        }
+
+        delegate: Rectangle {
+            id: roomDirDelegate
+
+            property color background: Nheko.colors.window
+            property color importantText: Nheko.colors.text
+            property color unimportantText: Nheko.colors.buttonText
+            property int avatarSize: fontMetrics.lineSpacing * 4
+
+            color: background
+            height: avatarSize + Nheko.paddingLarge
+            width: ListView.view.width
+
+            RowLayout {
+                spacing: Nheko.paddingMedium
+                anchors.fill: parent
+                anchors.margins: Nheko.paddingLarge
+                implicitHeight: textContent.height
+
+                Avatar {
+                    id: roomAvatar
+
+                    Layout.alignment: Qt.AlignVCenter
+                    width: avatarSize
+                    height: avatarSize
+                    url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+                    roomid: model.roomid
+                    displayName: model.name
+                }
+
+                ColumnLayout {
+                    id: textContent
+
+                    Layout.alignment: Qt.AlignLeft
+                    width: parent.width - avatar.width
+                    Layout.preferredWidth: parent.width - avatar.width
+                    spacing: Nheko.paddingSmall
+
+                    ElidedLabel {
+                        Layout.alignment: Qt.AlignBottom
+                        color: roomDirDelegate.importantText
+                        elideWidth: textContent.width - numMembersRectangle.width - buttonRectangle.width
+                        font.pixelSize: fontMetrics.font.pixelSize * 1.1
+                        fullText: model.name
+                    }
+
+                    RowLayout {
+                        id: roomDescriptionRow
+
+                        Layout.preferredWidth: parent.width
+                        spacing: Nheko.paddingSmall
+                        Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
+                        Layout.preferredHeight: fontMetrics.lineSpacing * 4
+
+                        Label {
+                            id: roomTopic
+
+                            color: roomDirDelegate.unimportantText
+                            Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
+                            font.pixelSize: fontMetrics.font.pixelSize
+                            elide: Text.ElideRight
+                            maximumLineCount: 2
+                            Layout.fillWidth: true
+                            text: model.topic
+                            verticalAlignment: Text.AlignVCenter
+                            wrapMode: Text.WordWrap
+                        }
+
+                        Item {
+                            id: numMembersRectangle
+
+                            Layout.margins: Nheko.paddingSmall
+                            width: roomCount.width
+
+                            Label {
+                                id: roomCount
+
+                                color: roomDirDelegate.unimportantText
+                                anchors.centerIn: parent
+                                font.pixelSize: fontMetrics.font.pixelSize
+                                text: model.numMembers.toString()
+                            }
+
+                        }
+
+                        Item {
+                            id: buttonRectangle
+
+                            Layout.margins: Nheko.paddingSmall
+                            width: joinRoomButton.width
+
+                            Button {
+                                id: joinRoomButton
+
+                                visible: model.canJoin
+                                anchors.centerIn: parent
+                                text: "Join"
+                                onClicked: publicRooms.joinRoom(model.index)
+                            }
+
+                        }
+
+                    }
+
+                }
+
+            }
+
+        }
+
+        footer: Item {
+            anchors.horizontalCenter: parent.horizontalCenter
+            width: parent.width
+            visible: !publicRooms.reachedEndOfPagination && publicRooms.loadingMoreRooms
+            // hacky but works
+            height: loadingSpinner.height + 2 * Nheko.paddingLarge
+            anchors.margins: Nheko.paddingLarge
+
+            Spinner {
+                id: loadingSpinner
+
+                anchors.centerIn: parent
+                anchors.margins: Nheko.paddingLarge
+                running: visible
+                foreground: Nheko.colors.mid
+            }
+
+        }
+
+    }
+
+    publicRooms: RoomDirectoryModel {
+    }
+
+    header: RowLayout {
+        id: searchBarLayout
+
+        spacing: Nheko.paddingMedium
+        width: parent.width
+        implicitHeight: roomSearch.height
+
+        MatrixTextField {
+            id: roomSearch
+
+            Layout.fillWidth: true
+            selectByMouse: true
+            font.pixelSize: fontMetrics.font.pixelSize
+            padding: Nheko.paddingMedium
+            color: Nheko.colors.text
+            placeholderText: qsTr("Search for public rooms")
+            onTextChanged: searchTimer.restart()
+        }
+
+        MatrixTextField {
+            id: chooseServer
+            Layout.minimumWidth: 0.3 * header.width
+            Layout.maximumWidth: 0.3 * header.width
+
+            padding: Nheko.paddingMedium
+            color: Nheko.colors.text
+            placeholderText: qsTr("Choose custom homeserver")
+            onTextChanged: publicRooms.setMatrixServer(text)
+        }
+
+        Timer {
+            id: searchTimer
+
+            interval: 350
+            onTriggered: roomDirView.model.setSearchTerm(roomSearch.text)
+        }
+
+    }
+
+}
diff --git a/resources/qml/dialogs/RoomMembers.qml b/resources/qml/dialogs/RoomMembers.qml
new file mode 100644
index 00000000..b2806292
--- /dev/null
+++ b/resources/qml/dialogs/RoomMembers.qml
@@ -0,0 +1,180 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import "../ui"
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import QtQuick.Window 2.13
+import im.nheko 1.0
+
+ApplicationWindow {
+    id: roomMembersRoot
+
+    property MemberList members
+    property Room room
+
+    title: qsTr("Members of %1").arg(members.roomName)
+    height: 650
+    width: 420
+    minimumHeight: 420
+    palette: Nheko.colors
+    color: Nheko.colors.window
+    flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+    Component.onCompleted: Nheko.reparent(roomMembersRoot)
+
+    Shortcut {
+        sequence: StandardKey.Cancel
+        onActivated: roomMembersRoot.close()
+    }
+
+    ColumnLayout {
+        anchors.fill: parent
+        anchors.margins: Nheko.paddingMedium
+        spacing: Nheko.paddingMedium
+
+        Avatar {
+            id: roomAvatar
+
+            width: 130
+            height: width
+            roomid: members.roomId
+            displayName: members.roomName
+            Layout.alignment: Qt.AlignHCenter
+            url: members.avatarUrl.replace("mxc://", "image://MxcImage/")
+            onClicked: TimelineManager.openRoomSettings(members.roomId)
+        }
+
+        ElidedLabel {
+            font.pixelSize: fontMetrics.font.pixelSize * 2
+            fullText: qsTr("%n people in %1", "Summary above list of members", members.memberCount).arg(members.roomName)
+            Layout.alignment: Qt.AlignHCenter
+            elideWidth: parent.width - Nheko.paddingMedium
+        }
+
+        ImageButton {
+            Layout.alignment: Qt.AlignHCenter
+            image: ":/icons/icons/ui/add-square-button.png"
+            hoverEnabled: true
+            ToolTip.visible: hovered
+            ToolTip.text: qsTr("Invite more people")
+            onClicked: TimelineManager.openInviteUsers(members.roomId)
+        }
+
+        ScrollView {
+            palette: Nheko.colors
+            padding: Nheko.paddingMedium
+            ScrollBar.horizontal.visible: false
+            Layout.fillHeight: true
+            Layout.minimumHeight: 200
+            Layout.fillWidth: true
+
+            ListView {
+                id: memberList
+
+                clip: true
+                spacing: Nheko.paddingMedium
+                boundsBehavior: Flickable.StopAtBounds
+                model: members
+
+                ScrollHelper {
+                    flickable: parent
+                    anchors.fill: parent
+                    enabled: !Settings.mobileMode
+                }
+
+                delegate: RowLayout {
+                    id: del
+
+                    width: ListView.view.width
+                    spacing: Nheko.paddingMedium
+
+                    Avatar {
+                        id: avatar
+
+                        width: Nheko.avatarSize
+                        height: Nheko.avatarSize
+                        userid: model.mxid
+                        url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
+                        displayName: model.displayName
+                        onClicked: Rooms.currentRoom.openUserProfile(model.mxid)
+                    }
+
+                    ColumnLayout {
+                        spacing: Nheko.paddingSmall
+
+                        ElidedLabel {
+                            fullText: model.displayName
+                            color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window)
+                            font.pixelSize: fontMetrics.font.pixelSize
+                            elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
+                        }
+
+                        ElidedLabel {
+                            fullText: model.mxid
+                            color: Nheko.colors.buttonText
+                            font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
+                            elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
+                        }
+
+                        Item {
+                            Layout.fillHeight: true
+                            Layout.fillWidth: true
+                        }
+
+                    }
+
+                    EncryptionIndicator {
+                        id: encryptInd
+
+                        Layout.alignment: Qt.AlignRight
+                        visible: room.isEncrypted
+                        encrypted: room.isEncrypted
+                        trust: encrypted ? model.trustlevel : Crypto.Unverified
+                        ToolTip.text: {
+                            if (!encrypted)
+                                return qsTr("This room is not encrypted!");
+
+                            switch (trust) {
+                            case Crypto.Verified:
+                                return qsTr("This user is verified.");
+                            case Crypto.TOFU:
+                                return qsTr("This user isn't verified, but is still using the same master key from the first time you met.");
+                            default:
+                                return qsTr("This user has unverified devices!");
+                            }
+                        }
+                    }
+
+                }
+
+                footer: Item {
+                    width: parent.width
+                    visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
+                    // use the default height if it's visible, otherwise no height at all
+                    height: membersLoadingSpinner.height
+                    anchors.margins: Nheko.paddingMedium
+
+                    Spinner {
+                        id: membersLoadingSpinner
+
+                        anchors.centerIn: parent
+                        height: visible ? 35 : 0
+                    }
+
+                }
+
+            }
+
+        }
+
+    }
+
+    footer: DialogButtonBox {
+        standardButtons: DialogButtonBox.Ok
+        onAccepted: roomMembersRoot.close()
+    }
+
+}
diff --git a/resources/qml/dialogs/RoomSettings.qml b/resources/qml/dialogs/RoomSettings.qml
new file mode 100644
index 00000000..0e7749ce
--- /dev/null
+++ b/resources/qml/dialogs/RoomSettings.qml
@@ -0,0 +1,305 @@
+// SPDX-FileCopyrightText: 2021 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: roomSettingsDialog
+
+    property var roomSettings
+
+    minimumWidth: 450
+    minimumHeight: 680
+    palette: Nheko.colors
+    color: Nheko.colors.window
+    modality: Qt.NonModal
+    flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+    Component.onCompleted: Nheko.reparent(roomSettingsDialog)
+    title: qsTr("Room Settings")
+
+    Shortcut {
+        sequence: StandardKey.Cancel
+        onActivated: roomSettingsDialog.close()
+    }
+
+    ColumnLayout {
+        id: contentLayout1
+
+        anchors.fill: parent
+        anchors.margins: 10
+        spacing: 10
+
+        Avatar {
+            url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
+            roomid: roomSettings.roomId
+            displayName: roomSettings.roomName
+            height: 130
+            width: 130
+            Layout.alignment: Qt.AlignHCenter
+            onClicked: {
+                if (roomSettings.canChangeAvatar)
+                    roomSettings.updateAvatar();
+
+            }
+        }
+
+        Spinner {
+            Layout.alignment: Qt.AlignHCenter
+            visible: roomSettings.isLoading
+            foreground: Nheko.colors.mid
+            running: roomSettings.isLoading
+        }
+
+        Text {
+            id: errorText
+
+            color: "red"
+            visible: opacity > 0
+            opacity: 0
+            Layout.alignment: Qt.AlignHCenter
+        }
+
+        SequentialAnimation {
+            id: hideErrorAnimation
+
+            running: false
+
+            PauseAnimation {
+                duration: 4000
+            }
+
+            NumberAnimation {
+                target: errorText
+                property: 'opacity'
+                to: 0
+                duration: 1000
+            }
+
+        }
+
+        Connections {
+            target: roomSettings
+            onDisplayError: {
+                errorText.text = errorMessage;
+                errorText.opacity = 1;
+                hideErrorAnimation.restart();
+            }
+        }
+
+        ColumnLayout {
+            Layout.alignment: Qt.AlignHCenter
+
+            MatrixText {
+                text: roomSettings.roomName
+                font.pixelSize: fontMetrics.font.pixelSize * 2
+                Layout.alignment: Qt.AlignHCenter
+            }
+
+            MatrixText {
+                text: qsTr("%1 member(s)").arg(roomSettings.memberCount)
+                Layout.alignment: Qt.AlignHCenter
+
+                TapHandler {
+                    onTapped: TimelineManager.openRoomMembers(roomSettings.roomId)
+                }
+
+                CursorShape {
+                    cursorShape: Qt.PointingHandCursor
+                    anchors.fill: parent
+                }
+
+            }
+
+        }
+
+        ImageButton {
+            Layout.alignment: Qt.AlignHCenter
+            image: ":/icons/icons/ui/edit.png"
+            visible: roomSettings.canChangeNameAndTopic
+            onClicked: roomSettings.openEditModal()
+        }
+
+        ScrollView {
+            Layout.fillHeight: true
+            Layout.alignment: Qt.AlignHCenter
+            Layout.fillWidth: true
+            Layout.leftMargin: Nheko.paddingLarge
+            Layout.rightMargin: Nheko.paddingLarge
+
+            TextArea {
+                text: TimelineManager.escapeEmoji(roomSettings.roomTopic)
+                wrapMode: TextEdit.WordWrap
+                textFormat: TextEdit.RichText
+                readOnly: true
+                background: null
+                selectByMouse: true
+                color: Nheko.colors.text
+                horizontalAlignment: TextEdit.AlignHCenter
+                onLinkActivated: Nheko.openLink(link)
+
+                CursorShape {
+                    anchors.fill: parent
+                    cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
+                }
+
+            }
+
+        }
+
+        GridLayout {
+            columns: 2
+            rowSpacing: Nheko.paddingLarge
+
+            MatrixText {
+                text: qsTr("SETTINGS")
+                font.bold: true
+            }
+
+            Item {
+                Layout.fillWidth: true
+            }
+
+            MatrixText {
+                text: qsTr("Notifications")
+                Layout.fillWidth: true
+            }
+
+            ComboBox {
+                model: [qsTr("Muted"), qsTr("Mentions only"), qsTr("All messages")]
+                currentIndex: roomSettings.notifications
+                onActivated: {
+                    roomSettings.changeNotifications(index);
+                }
+                Layout.fillWidth: true
+            }
+
+            MatrixText {
+                text: qsTr("Room access")
+                Layout.fillWidth: true
+            }
+
+            ComboBox {
+                enabled: roomSettings.canChangeJoinRules
+                model: {
+                    let opts = [qsTr("Anyone and guests"), qsTr("Anyone"), qsTr("Invited users")];
+                    if (roomSettings.supportsKnocking)
+                        opts.push(qsTr("By knocking"));
+
+                    if (roomSettings.supportsRestricted)
+                        opts.push(qsTr("Restricted by membership in other rooms"));
+
+                    return opts;
+                }
+                currentIndex: roomSettings.accessJoinRules
+                onActivated: {
+                    roomSettings.changeAccessRules(index);
+                }
+                Layout.fillWidth: true
+            }
+
+            MatrixText {
+                text: qsTr("Encryption")
+            }
+
+            ToggleButton {
+                id: encryptionToggle
+
+                checked: roomSettings.isEncryptionEnabled
+                onClicked: {
+                    if (roomSettings.isEncryptionEnabled) {
+                        checked = true;
+                        return ;
+                    }
+                    confirmEncryptionDialog.open();
+                }
+                Layout.alignment: Qt.AlignRight
+            }
+
+            Platform.MessageDialog {
+                id: confirmEncryptionDialog
+
+                title: qsTr("End-to-End Encryption")
+                text: qsTr("Encryption is currently experimental and things might break unexpectedly. <br>
+                            Please take note that it can't be disabled afterwards.")
+                modality: Qt.Modal
+                onAccepted: {
+                    if (roomSettings.isEncryptionEnabled)
+                        return ;
+
+                    roomSettings.enableEncryption();
+                }
+                onRejected: {
+                    encryptionToggle.checked = false;
+                }
+                buttons: Dialog.Ok | Dialog.Cancel
+            }
+
+            MatrixText {
+                text: qsTr("Sticker & Emote Settings")
+            }
+
+            Button {
+                text: qsTr("Change")
+                ToolTip.text: qsTr("Change what packs are enabled, remove packs or create new ones")
+                onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId)
+                Layout.alignment: Qt.AlignRight
+            }
+
+            Item {
+                // for adding extra space between sections
+                Layout.fillWidth: true
+            }
+
+            Item {
+                // for adding extra space between sections
+                Layout.fillWidth: true
+            }
+
+            MatrixText {
+                text: qsTr("INFO")
+                font.bold: true
+            }
+
+            Item {
+                Layout.fillWidth: true
+            }
+
+            MatrixText {
+                text: qsTr("Internal ID")
+            }
+
+            MatrixText {
+                text: roomSettings.roomId
+                font.pixelSize: fontMetrics.font.pixelSize * 1.2
+                Layout.alignment: Qt.AlignRight
+            }
+
+            MatrixText {
+                text: qsTr("Room Version")
+            }
+
+            MatrixText {
+                text: roomSettings.roomVersion
+                font.pixelSize: fontMetrics.font.pixelSize * 1.2
+                Layout.alignment: Qt.AlignRight
+            }
+
+        }
+
+        DialogButtonBox {
+            Layout.fillWidth: true
+            standardButtons: DialogButtonBox.Ok
+            onAccepted: close()
+        }
+
+    }
+
+}
diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml
new file mode 100644
index 00000000..9bf548e3
--- /dev/null
+++ b/resources/qml/dialogs/UserProfile.qml
@@ -0,0 +1,327 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import "../device-verification"
+import "../ui"
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.13
+import im.nheko 1.0
+
+ApplicationWindow {
+    // this does not work in ApplicationWindow, just in Window
+    //transientParent: Nheko.mainwindow()
+
+    id: userProfileDialog
+
+    property var profile
+
+    height: 650
+    width: 420
+    minimumWidth: 150
+    minimumHeight: 150
+    palette: Nheko.colors
+    color: Nheko.colors.window
+    title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile")
+    modality: Qt.NonModal
+    flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+    Component.onCompleted: Nheko.reparent(userProfileDialog)
+
+    Shortcut {
+        sequence: StandardKey.Cancel
+        onActivated: userProfileDialog.close()
+    }
+
+
+    ListView {
+
+        ScrollHelper {
+            flickable: parent
+            anchors.fill: parent
+            enabled: !Settings.mobileMode
+        }
+
+        header: ColumnLayout {
+            id: contentL
+            width: devicelist.width
+
+            spacing: 10
+
+            Avatar {
+                url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
+                height: 130
+                width: 130
+                displayName: profile.displayName
+                id: displayAvatar
+                userid: profile.userid
+                Layout.alignment: Qt.AlignHCenter
+                onClicked: TimelineManager.openImageOverlay(profile.avatarUrl, "")
+
+                ImageButton {
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change avatar globally.") : qsTr("Change avatar. Will only apply to this room.")
+                    anchors.left: displayAvatar.left
+                    anchors.top: displayAvatar.top
+                    anchors.leftMargin: Nheko.paddingMedium
+                    anchors.topMargin: Nheko.paddingMedium
+                    visible: profile.isSelf
+                    image: ":/icons/icons/ui/edit.png"
+                    onClicked: profile.changeAvatar()
+                }
+            }
+
+            Spinner {
+                Layout.alignment: Qt.AlignHCenter
+                running: profile.isLoading
+                visible: profile.isLoading
+                foreground: Nheko.colors.mid
+            }
+
+            Text {
+                id: errorText
+
+                color: "red"
+                visible: opacity > 0
+                opacity: 0
+                Layout.alignment: Qt.AlignHCenter
+            }
+
+            SequentialAnimation {
+                id: hideErrorAnimation
+
+                running: false
+
+                PauseAnimation {
+                    duration: 4000
+                }
+
+                NumberAnimation {
+                    target: errorText
+                    property: 'opacity'
+                    to: 0
+                    duration: 1000
+                }
+
+            }
+
+            Connections {
+                function onDisplayError(errorMessage) {
+                    errorText.text = errorMessage;
+                    errorText.opacity = 1;
+                    hideErrorAnimation.restart();
+                }
+
+                target: profile
+            }
+
+            TextInput {
+                id: displayUsername
+
+                property bool isUsernameEditingAllowed
+
+                readOnly: !isUsernameEditingAllowed
+                text: profile.displayName
+                font.pixelSize: 20
+                color: TimelineManager.userColor(profile.userid, Nheko.colors.window)
+                font.bold: true
+                Layout.alignment: Qt.AlignHCenter
+                selectByMouse: true
+                onAccepted: {
+                    profile.changeUsername(displayUsername.text);
+                    displayUsername.isUsernameEditingAllowed = false;
+                }
+
+                ImageButton {
+                    visible: profile.isSelf
+                    anchors.leftMargin: Nheko.paddingSmall
+                    anchors.left: displayUsername.right
+                    anchors.verticalCenter: displayUsername.verticalCenter
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: profile.isGlobalUserProfile ? qsTr("Change display name globally.") : qsTr("Change display name. Will only apply to this room.")
+                    image: displayUsername.isUsernameEditingAllowed ? ":/icons/icons/ui/checkmark.png" : ":/icons/icons/ui/edit.png"
+                    onClicked: {
+                        if (displayUsername.isUsernameEditingAllowed) {
+                            profile.changeUsername(displayUsername.text);
+                            displayUsername.isUsernameEditingAllowed = false;
+                        } else {
+                            displayUsername.isUsernameEditingAllowed = true;
+                            displayUsername.focus = true;
+                            displayUsername.selectAll();
+                        }
+                    }
+                }
+
+            }
+
+            MatrixText {
+                text: profile.userid
+                Layout.alignment: Qt.AlignHCenter
+            }
+
+
+            RowLayout {
+                visible: !profile.isGlobalUserProfile
+                Layout.alignment: Qt.AlignHCenter
+                spacing: Nheko.paddingSmall
+                MatrixText {
+                    id: displayRoomname
+                    text: qsTr("Room: %1").arg(profile.room?profile.room.roomName:"")
+                    ToolTip.text: qsTr("This is a room-specific profile. The user's name and avatar may be different from their global versions.")
+                    ToolTip.visible: ma.hovered
+                    HoverHandler {
+                        id: ma
+                    }
+                }
+
+                ImageButton {
+                    image: ":/icons/icons/ui/world.png"
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: qsTr("Open the global profile for this user.")
+                    onClicked: profile.openGlobalProfile()
+                }
+            }
+
+            Button {
+                id: verifyUserButton
+
+                text: qsTr("Verify")
+                Layout.alignment: Qt.AlignHCenter
+                enabled: profile.userVerified != Crypto.Verified
+                visible: profile.userVerified != Crypto.Verified && !profile.isSelf && profile.userVerificationEnabled
+                onClicked: profile.verify()
+            }
+
+            Image {
+                Layout.preferredHeight: 16
+                Layout.preferredWidth: 16
+                source: "image://colorimage/:/icons/icons/ui/lock.png?" + ((profile.userVerified == Crypto.Verified) ? "green" : Nheko.colors.buttonText)
+                visible: profile.userVerified != Crypto.Unverified
+                Layout.alignment: Qt.AlignHCenter
+            }
+
+            RowLayout {
+                // ImageButton{
+                //     image:":/icons/icons/ui/volume-off-indicator.png"
+                //     Layout.margins: {
+                //         left: 5
+                //         right: 5
+                //     }
+                //     ToolTip.visible: hovered
+                //     ToolTip.text: qsTr("Ignore messages from this user.")
+                //     onClicked : {
+                //         profile.ignoreUser()
+                //     }
+                // }
+
+                Layout.alignment: Qt.AlignHCenter
+                Layout.bottomMargin: 10
+                spacing: Nheko.paddingSmall
+
+                ImageButton {
+                    image: ":/icons/icons/ui/black-bubble-speech.png"
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: qsTr("Start a private chat.")
+                    onClicked: profile.startChat()
+                }
+
+                ImageButton {
+                    image: ":/icons/icons/ui/round-remove-button.png"
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: qsTr("Kick the user.")
+                    onClicked: profile.kickUser()
+                    visible: !profile.isGlobalUserProfile && profile.room.permissions.canKick()
+                }
+
+                ImageButton {
+                    image: ":/icons/icons/ui/do-not-disturb-rounded-sign.png"
+                    hoverEnabled: true
+                    ToolTip.visible: hovered
+                    ToolTip.text: qsTr("Ban the user.")
+                    onClicked: profile.banUser()
+                    visible: !profile.isGlobalUserProfile && profile.room.permissions.canBan()
+                }
+
+            }
+        }
+
+        id: devicelist
+        Layout.fillHeight: true
+        Layout.fillWidth: true
+        clip: true
+        spacing: 8
+        boundsBehavior: Flickable.StopAtBounds
+        model: profile.deviceList
+        anchors.fill: parent
+        anchors.margins: 10
+
+
+        delegate: RowLayout {
+            width: devicelist.width
+            spacing: 4
+
+            ColumnLayout {
+                spacing: 0
+
+                Text {
+                    Layout.fillWidth: true
+                    Layout.alignment: Qt.AlignLeft
+                    elide: Text.ElideRight
+                    font.bold: true
+                    color: Nheko.colors.text
+                    text: model.deviceId
+                }
+
+                Text {
+                    Layout.fillWidth: true
+                    Layout.alignment: Qt.AlignRight
+                    elide: Text.ElideRight
+                    color: Nheko.colors.text
+                    text: model.deviceName
+                }
+
+            }
+
+            Image {
+                Layout.preferredHeight: 16
+                Layout.preferredWidth: 16
+                source: ((model.verificationStatus == VerificationStatus.VERIFIED) ? "image://colorimage/:/icons/icons/ui/lock.png?green" : ((model.verificationStatus == VerificationStatus.UNVERIFIED) ? "image://colorimage/:/icons/icons/ui/unlock.png?yellow" : "image://colorimage/:/icons/icons/ui/unlock.png?red"))
+            }
+
+            Button {
+                id: verifyButton
+
+                visible: (!profile.userVerificationEnabled && !profile.isSelf) || (profile.isSelf && (model.verificationStatus != VerificationStatus.VERIFIED || !profile.userVerificationEnabled))
+                text: (model.verificationStatus != VerificationStatus.VERIFIED) ? qsTr("Verify") : qsTr("Unverify")
+                onClicked: {
+                    if (model.verificationStatus == VerificationStatus.VERIFIED)
+                        profile.unverify(model.deviceId);
+                    else
+                        profile.verify(model.deviceId);
+                }
+            }
+
+        }
+        footerPositioning: ListView.OverlayFooter
+        footer: DialogButtonBox {
+            z: 2
+            width: devicelist.width
+            alignment: Qt.AlignRight
+            standardButtons: DialogButtonBox.Ok
+            onAccepted: userProfileDialog.close()
+            background: Rectangle {
+                anchors.fill: parent
+                color: Nheko.colors.window
+            }
+        }
+
+    }
+
+}