// SPDX-FileCopyrightText: Nheko Contributors // // SPDX-License-Identifier: GPL-3.0-or-later import ".." import "../components" import QtQuick import QtQuick.Controls import QtQuick.Layouts import im.nheko ApplicationWindow { id: inviteDialogRoot property InviteesModel invitees property var friendsCompleter property var profile minimumWidth: 300 Component.onCompleted: { friendsCompleter = TimelineManager.completerFor("user", "friends") width = 600 } function addInvite(mxid, displayName, avatarUrl) { if (mxid.match("@.+?:.{3,}")) { invitees.addUser(mxid, displayName, avatarUrl); } else console.log("invalid mxid: " + mxid) } function cleanUpAndClose() { if (inviteeEntry.isValidMxid) addInvite(inviteeEntry.text, "", ""); invitees.accept(); close(); } title: qsTr("Invite users to %1").arg(invitees.room.plainRoomName) height: 380 width: 340 color: palette.window flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint Shortcut { sequence: "Ctrl+Enter" onActivated: cleanUpAndClose() } Shortcut { sequence: StandardKey.Cancel onActivated: inviteDialogRoot.close() } ColumnLayout { anchors.fill: parent anchors.margins: Nheko.paddingMedium spacing: Nheko.paddingMedium Flow { layoutDirection: Qt.LeftToRight Layout.fillWidth: true Layout.preferredHeight: implicitHeight spacing: 4 visible: !inviteesList.visible Repeater { id: inviteesRepeater model: invitees delegate: ItemDelegate { onClicked: invitees.removeUser(model.mxid) id: inviteeButton contentItem: Label { anchors.centerIn: parent id: inviteeUserid text: model.displayName != "" ? model.displayName : model.userid color: inviteeButton.hovered ? palette.highlightedText: palette.text maximumLineCount: 1 } background: Rectangle { border.color: palette.text color: inviteeButton.hovered ? palette.highlight : palette.window border.width: 1 radius: inviteeButton.height / 2 } } } } Label { text: qsTr("Search user") Layout.fillWidth: true color: palette.text } RowLayout { spacing: Nheko.paddingMedium MatrixTextField { id: inviteeEntry property bool isValidMxid: text.match("@.+?:.{3,}") backgroundColor: palette.window placeholderText: qsTr("@user:yourserver.example.com", "Example user id. The name 'user' can be localized however you want.") Layout.fillWidth: true onAccepted: { if (isValidMxid) { addInvite(text, "", ""); clear() } else if (userSearch.count > 0) { addInvite(userSearch.itemAtIndex(0).userid, userSearch.itemAtIndex(0).displayName, userSearch.itemAtIndex(0).avatarUrl) clear() } } 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(); } onTextChanged: { searchTimer.restart() if(isValidMxid) { profile = TimelineManager.getGlobalUserProfile(text); } else profile = null; } Timer { id: searchTimer interval: 350 onTriggered: { userSearch.model.setSearchString(parent.text) } } } ToggleButton { id: searchOnServer checked: false onClicked: userSearch.model.setSearchString(inviteeEntry.text) } MatrixText { text: qsTr("Search on Server") } } RowLayout { UserListRow { visible: inviteeEntry.isValidMxid id: del3 Layout.preferredWidth: inviteDialogRoot.width/2 Layout.alignment: Qt.AlignTop Layout.preferredHeight: implicitHeight displayName: profile? profile.displayName : "" avatarUrl: profile? profile.avatarUrl : "" userid: inviteeEntry.text onClicked: addInvite(inviteeEntry.text, displayName, avatarUrl) bgColor: del3.hovered ? palette.dark : inviteDialogRoot.color } ListView { visible: !inviteeEntry.isValidMxid id: userSearch model: searchOnServer.checked? userDirectory : friendsCompleter Layout.fillWidth: true Layout.fillHeight: true clip: true Loader { source: NHEKO_USE_KIRIGAMI ? "../components/KirigamiWheelHandler.qml" : "" } delegate: UserListRow { id: del2 width: ListView.view.width height: implicitHeight displayName: model.displayName userid: model.userid avatarUrl: model.avatarUrl onClicked: addInvite(userid, displayName, avatarUrl) bgColor: del2.hovered ? palette.dark : inviteDialogRoot.color } } Rectangle { Layout.fillHeight: true visible: inviteesList.visible Layout.preferredWidth: 1 color: Nheko.theme.separator } ListView { id: inviteesList Layout.fillWidth: true Layout.fillHeight: true model: invitees clip: true visible: inviteDialogRoot.width >= 500 Loader { source: NHEKO_USE_KIRIGAMI ? "../components/KirigamiWheelHandler.qml" : "" } delegate: UserListRow { id: del hoverEnabled: true width: ListView.view.width height: implicitHeight onClicked: TimelineManager.openGlobalUserProfile(model.mxid) userid: model.mxid avatarUrl: model.avatarUrl displayName: model.displayName bgColor: del.hovered ? palette.dark : inviteDialogRoot.color ImageButton { anchors.right: parent.right anchors.rightMargin: Nheko.paddingSmall anchors.top: parent.top anchors.topMargin: Nheko.paddingSmall id: removeButton image: ":/icons/icons/ui/dismiss.svg" onClicked: invitees.removeUser(model.mxid) } NhekoCursorShape { anchors.fill: parent cursorShape: Qt.PointingHandCursor } } } } } 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() } } }