From 2850ee75ee2b37dc70991966e09344c6e0c60b67 Mon Sep 17 00:00:00 2001 From: Patryk Osmaczko Date: Wed, 23 Mar 2022 16:38:23 +0100 Subject: [PATCH] desktop(community-settings): add overview and members settings this is first iteration for: #4938 --- ui/app/AppLayouts/Chat/ChatLayout.qml | 202 ++------------ .../Chat/layouts/SettingsPageLayout.qml | 150 +++++++++++ .../CommunityEditSettingsPanel.qml | 251 ++++++++++++++++++ .../CommunityMembersSettingsPanel.qml | 169 ++++++++++++ .../CommunityOverviewSettingsPanel.qml | 155 +++++++++++ .../CommunityWelcomeBannerPanel.qml | 9 +- ui/app/AppLayouts/Chat/views/ChatView.qml | 199 ++++++++++++++ .../Chat/views/CommunityColumnView.qml | 10 +- .../Chat/views/CommunitySettingsView.qml | 183 +++++++++++++ ui/app/mainui/AppMain.qml | 18 +- 10 files changed, 1150 insertions(+), 196 deletions(-) create mode 100644 ui/app/AppLayouts/Chat/layouts/SettingsPageLayout.qml create mode 100644 ui/app/AppLayouts/Chat/panels/communities/CommunityEditSettingsPanel.qml create mode 100644 ui/app/AppLayouts/Chat/panels/communities/CommunityMembersSettingsPanel.qml create mode 100644 ui/app/AppLayouts/Chat/panels/communities/CommunityOverviewSettingsPanel.qml create mode 100644 ui/app/AppLayouts/Chat/views/ChatView.qml create mode 100644 ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index 832313f952..08619fc518 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -1,202 +1,50 @@ -import QtQuick 2.13 -import QtQuick.Controls 2.13 -import Qt.labs.settings 1.0 +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 import utils 1.0 -import shared 1.0 -import shared.panels 1.0 -import shared.popups 1.0 -import shared.status 1.0 -import shared.views.chat 1.0 - -import StatusQ.Layout 0.1 -import StatusQ.Popups 0.1 import "views" -import "panels" -import "panels/communities" -import "popups" -import "helpers" -import "controls" import "stores" -StatusAppThreePanelLayout { +StackLayout { id: root property var contactsStore - property bool hasAddedContacts: root.contactsStore.myContactsModel.count > 0 - - // Not Refactored - property var messageStore - property RootStore rootStore: RootStore { contactsStore: root.contactsStore } - property Component pinnedMessagesListPopupComponent - property var emojiPopup - property bool stickersLoaded: false - signal profileButtonClicked() - signal openAppSearch() + property alias chatView: chatView - Connections { - target: root.rootStore.stickersStore.stickersModule - onStickerPacksLoaded: { - root.stickersLoaded = true; - } - } + clip: true - Connections { - target: root.rootStore.chatCommunitySectionModule - onActiveItemChanged: { - root.rootStore.openCreateChat = false; - } - } - -// property var onActivated: function () { -// root.rootStore.chatsModelInst.channelView.restorePreviousActiveChannel(); -// chatColumn.onActivated(); -// } - - leftPanel: Loader { - id: contactColumnLoader - sourceComponent: root.rootStore.chatCommunitySectionModule.isCommunity()? - communtiyColumnComponent : - contactsColumnComponent - } - - centerPanel: ChatColumnView { - id: chatColumn - parentModule: root.rootStore.chatCommunitySectionModule - rootStore: root.rootStore + ChatView { + id: chatView contactsStore: root.contactsStore - chatSectionModule: root.rootStore.chatCommunitySectionModule - pinnedMessagesPopupComponent: root.pinnedMessagesListPopupComponent - stickersLoaded: root.stickersLoaded - emojiPopup: root.emojiPopup - //chatGroupsListViewCount: contactColumnLoader.item.chatGroupsListViewCount - onOpenStickerPackPopup: { - Global.openPopup(statusStickerPackClickPopup, {packId: stickerPackId} ) - } - onOpenAppSearch: { - root.openAppSearch(); - } + rootStore: root.rootStore + + onCommunityInfoButtonClicked: root.currentIndex = 1 + onCommunityManageButtonClicked: root.currentIndex = 1 } - showRightPanel: { - if (root.rootStore.openCreateChat || - !localAccountSensitiveSettings.showOnlineUsers || - !localAccountSensitiveSettings.expandUsersList) { - return false - } + Loader { + active: root.rootStore.chatCommunitySectionModule.isCommunity() - let chatContentModule = root.rootStore.currentChatContentModule() - if (!chatContentModule - || chatContentModule.chatDetails.type === Constants.chatType.publicChat) - { - // New communities have no chats, so no chatContentModule or it is a public chat - return false - } - // Check if user list is available as an option for particular chat content module - return chatContentModule.chatDetails.isUsersListAvailable - } - - rightPanel: userListComponent - - Component { - id: userListComponent - UserListPanel { + sourceComponent: CommunitySettingsView { rootStore: root.rootStore - label: localAccountSensitiveSettings.communitiesEnabled && - root.rootStore.chatCommunitySectionModule.isCommunity() ? - //% "Members" - qsTrId("members-label") : - qsTr("Last seen") - messageContextMenu: quickActionMessageOptionsMenu - usersModule: { - let chatContentModule = root.rootStore.currentChatContentModule() - if (!chatContentModule || !chatContentModule.usersModule) { - // New communities have no chats, so no chatContentModule - return {} - } - return chatContentModule.usersModule - } - } - } + chatCommunitySectionModule: root.rootStore.chatCommunitySectionModule + community: root.rootStore.mainModuleInst ? root.rootStore.mainModuleInst.activeSection + || {} : {} - Component { - id: contactsColumnComponent - ContactsColumnView { - chatSectionModule: root.rootStore.chatCommunitySectionModule - store: root.rootStore - contactsStore: root.contactsStore - emojiPopup: root.emojiPopup - onOpenProfileClicked: { - root.profileButtonClicked(); - } + onBackToCommunityClicked: root.currentIndex = 0 - onOpenAppSearch: { - root.openAppSearch() - } - } - } - - Component { - id: communtiyColumnComponent - CommunityColumnView { - communitySectionModule: root.rootStore.chatCommunitySectionModule - store: root.rootStore - emojiPopup: root.emojiPopup - hasAddedContacts: root.hasAddedContacts - pinnedMessagesPopupComponent: root.pinnedMessagesListPopupComponent - } - } - - Component { - id: groupInfoPopupComponent - GroupInfoPopup { - chatSectionModule: root.rootStore.chatCommunitySectionModule - store: root.rootStore - pinnedMessagesPopupComponent: root.pinnedMessagesListPopupComponent - } - } - - Component { - id: statusStickerPackClickPopup - StatusStickerPackClickPopup{ - store: root.rootStore - onClosed: { - destroy(); - } - } - } - - ConfirmationDialog { - id: removeContactConfirmationDialog - // % "Remove contact" - header.title: qsTrId("remove-contact") - //% "Are you sure you want to remove this contact?" - confirmationText: qsTrId("are-you-sure-you-want-to-remove-this-contact-") - onConfirmButtonClicked: { - let pk = chatColumn.contactToRemove - if (Utils.getContactDetailsAsJson(pk).isContact) { - root.contactsStore.removeContact(pk) - } - removeContactConfirmationDialog.parentPopup.close(); - removeContactConfirmationDialog.close(); - } - } - - MessageContextMenuView { - id: quickActionMessageOptionsMenu - store: root.rootStore - - onOpenProfileClicked: { - Global.openProfilePopup(publicKey) - } - onCreateOneToOneChat: { - Global.changeAppSectionBySectionType(Constants.appSection.chat) - root.rootStore.chatCommunitySectionModule.createOneToOneChat(communityId, chatId, ensName) + // TODO: remove me when migration to new settings is done + onOpenLegacyPopupClicked: Global.openPopup(communityProfilePopup, { + "store": root.rootStore, + "community": community, + "communitySectionModule": chatCommunitySectionModule + }) } } } diff --git a/ui/app/AppLayouts/Chat/layouts/SettingsPageLayout.qml b/ui/app/AppLayouts/Chat/layouts/SettingsPageLayout.qml new file mode 100644 index 0000000000..c33f3def7f --- /dev/null +++ b/ui/app/AppLayouts/Chat/layouts/SettingsPageLayout.qml @@ -0,0 +1,150 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 + +import QtGraphicalEffects 1.13 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 + +Item { + id: root + + // required + property string title + property Component content + + // optional + property string previousPage + property bool dirty: false + + readonly property Item contentItem: contentLoader.item + + signal previousPageClicked + signal saveChangesClicked + signal resetChangesClicked + + function reloadContent() { + contentLoader.active = false + contentLoader.active = true + } + + function notifyDirty() { + toastAnimation.running = true + saveChangesButton.forceActiveFocus() + } + + implicitWidth: layout.implicitWidth + implicitHeight: layout.implicitHeight + + ColumnLayout { + id: layout + + anchors.fill: parent + + spacing: 16 + + Item { + implicitHeight: 32 + + StatusBaseText { + visible: root.previousPage + text: "<- " + root.previousPage + color: Theme.palette.primaryColor1 + font.pixelSize: 15 + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.previousPageClicked() + } + } + } + + StatusBaseText { + Layout.leftMargin: 32 + + text: root.title + color: Theme.palette.directColor1 + font.pixelSize: 26 + font.bold: true + } + + Loader { + id: contentLoader + Layout.fillWidth: true + Layout.fillHeight: true + + sourceComponent: root.content + } + } + + Rectangle { + id: toastMessage + + anchors { + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + bottomMargin: 16 + } + + height: toastContent.height + 16 + width: toastContent.width + 16 + + opacity: root.dirty ? 1 : 0 + color: Theme.palette.statusToastMessage.backgroundColor + radius: 8 + border.color: Theme.palette.dangerColor2 + border.width: 2 + layer.enabled: true + layer.effect: DropShadow { + verticalOffset: 3 + radius: 8 + samples: 15 + fast: true + cached: true + color: Theme.palette.dangerColor2 + spread: 0.1 + } + + NumberAnimation on border.width { + id: toastAnimation + from: 0 + to: 4 + loops: 2 + duration: 600 + + onFinished: toastMessage.border.width = 2 + } + + RowLayout { + id: toastContent + + x: 8 + y: 8 + + StatusBaseText { + text: qsTr("Changes detected!") + color: Theme.palette.directColor1 + } + + StatusButton { + text: qsTr("Cancel") + type: StatusBaseButton.Type.Danger + onClicked: root.resetChangesClicked() + } + + StatusButton { + id: saveChangesButton + + text: qsTr("Save changes") + onClicked: root.saveChangesClicked() + } + } + + Behavior on opacity { + NumberAnimation {} + } + } +} + diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityEditSettingsPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityEditSettingsPanel.qml new file mode 100644 index 0000000000..05fdc8ee1b --- /dev/null +++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityEditSettingsPanel.qml @@ -0,0 +1,251 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 +import QtQuick.Controls 2.14 +import QtQuick.Dialogs 1.3 +import QtGraphicalEffects 1.13 + +import utils 1.0 +import shared.panels 1.0 +import shared.popups 1.0 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Layout 0.1 +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Controls.Validators 0.1 + +Flickable { + id: root + + property alias name: nameInput.text + property alias description: descriptionTextInput.text + property alias color: colorDialog.color + property alias image: addImageButton.selectedImage + readonly property alias imageAx: imageCropperModal.aX + readonly property alias imageAy: imageCropperModal.aY + readonly property alias imageBx: imageCropperModal.bX + readonly property alias imageBy: imageCropperModal.bY + + contentWidth: layout.width + contentHeight: layout.height + clip: true + interactive: contentHeight > height + flickableDirection: Flickable.VerticalFlick + + ColumnLayout { + id: layout + + width: root.width + spacing: 12 + + StatusInput { + id: nameInput + + Layout.fillWidth: true + + leftPadding: 0 + rightPadding: 0 + label: qsTr("Community name") + charLimit: 30 + input.placeholderText: qsTr("A catchy name") + validators: [ + StatusMinLengthValidator { + minLength: 1 + errorMessage: Utils.getErrorMessage(nameInput.errors, + qsTr("community name")) + } + ] + validationMode: StatusInput.ValidationMode.Always + + Component.onCompleted: nameInput.input.forceActiveFocus(Qt.MouseFocusReason) + } + + StatusInput { + id: descriptionTextInput + + Layout.fillWidth: true + + leftPadding: 0 + rightPadding: 0 + label: qsTr("Description") + charLimit: 140 + + input.placeholderText: qsTr("What your community is about") + input.multiline: true + input.implicitHeight: 88 + + validators: [ + StatusMinLengthValidator { + minLength: 1 + errorMessage: Utils.getErrorMessage( + descriptionTextInput.errors, + qsTr("community description")) + } + ] + validationMode: StatusInput.ValidationMode.Always + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 8 + + StatusBaseText { + //% "Thumbnail image" + text: qsTrId("thumbnail-image") + font.pixelSize: 15 + color: Theme.palette.directColor1 + } + + Item { + Layout.fillWidth: true + + implicitHeight: addImageButton.height + 32 + + Rectangle { + id: addImageButton + + property string selectedImage: "" + + anchors.centerIn: parent + color: imagePreview.visible ? "transparent" : Style.current.inputBackground + width: 128 + height: width + radius: width / 2 + + FileDialog { + id: imageDialog + //% "Please choose an image" + title: qsTrId("please-choose-an-image") + folder: shortcuts.pictures + nameFilters: [//% "Image files (*.jpg *.jpeg *.png)" + qsTrId("image-files----jpg---jpeg---png-")] + onAccepted: { + addImageButton.selectedImage = imageDialog.fileUrls[0] + imageCropperModal.open() + } + } + + Rectangle { + id: imagePreviewCropper + clip: true + width: parent.width + height: parent.height + radius: parent.width / 2 + visible: !!addImageButton.selectedImage + + Image { + id: imagePreview + visible: !!addImageButton.selectedImage + source: addImageButton.selectedImage + fillMode: Image.PreserveAspectFit + width: parent.width + height: parent.height + } + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + anchors.centerIn: parent + width: imageCropperModal.width + height: imageCropperModal.height + radius: width / 2 + } + } + } + + Item { + id: addImageCenter + visible: !imagePreview.visible + width: uploadText.width + height: childrenRect.height + anchors.centerIn: parent + + SVGImage { + id: imageImg + source: Style.svg("images_icon") + width: 20 + height: 18 + anchors.horizontalCenter: parent.horizontalCenter + } + + StatusBaseText { + id: uploadText + //% "Upload" + text: qsTrId("upload") + anchors.top: imageImg.bottom + anchors.topMargin: 5 + font.pixelSize: 15 + color: Theme.palette.baseColor1 + } + } + + StatusRoundButton { + type: StatusRoundButton.Type.Secondary + icon.name: "add" + anchors.top: parent.top + anchors.right: parent.right + anchors.rightMargin: Style.current.halfPadding + highlighted: sensor.containsMouse + } + + MouseArea { + id: sensor + hoverEnabled: true + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: imageDialog.open() + } + + ImageCropperModal { + id: imageCropperModal + selectedImage: addImageButton.selectedImage + ratio: "1:1" + } + } + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 8 + + StatusBaseText { + text: qsTrId("Community colour") + font.pixelSize: 15 + color: Theme.palette.directColor1 + } + + StatusPickerButton { + Layout.fillWidth: true + + property string validationError: "" + + bgColor: colorDialog.colorSelected ? colorDialog.color : Theme.palette.baseColor2 + contentColor: colorDialog.colorSelected ? Theme.palette.indirectColor1 : Theme.palette.baseColor1 + text: colorDialog.colorSelected ? colorDialog.color.toString( + ).toUpperCase() : qsTr("Pick a color") + + onClicked: colorDialog.open() + onTextChanged: { + if (colorDialog.colorSelected) { + validationError = Utils.validateAndReturnError( + text, + Utils.Validate.NoEmpty | Utils.Validate.TextHexColor) + } + } + + ColorDialog { + id: colorDialog + property bool colorSelected: true + color: Theme.palette.primaryColor1 + onAccepted: colorSelected = true + } + } + } + + Item { + Layout.fillHeight: true + } + } +} + diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityMembersSettingsPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityMembersSettingsPanel.qml new file mode 100644 index 0000000000..1ba5ea5154 --- /dev/null +++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityMembersSettingsPanel.qml @@ -0,0 +1,169 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 +import QtQuick.Controls 2.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 +import StatusQ.Popups 0.1 + +import utils 1.0 +import shared.controls.chat 1.0 + +import "../../layouts" + +SettingsPageLayout { + id: root + + property var membersModel + + property bool editable: true + property int pendingRequests + + signal membershipRequestsClicked() + signal userProfileClicked(string id) + signal kickUserClicked(string id) + signal banUserClicked(string id) + + title: qsTr("Members") + + content: ColumnLayout { + spacing: 8 + + StatusInput { + id: memberSearch + + Layout.fillWidth: true + + leftPadding: 0 + rightPadding: 0 + input.placeholderText: qsTr("Member name") + } + + StatusContactRequestsIndicatorListItem { + id: memberRequestsButton + + Layout.fillWidth: true + + visible: root.editable && root.pendingRequests > 0 + //% "Membership requests" + title: qsTrId("membership-requests") + requestsCount: root.pendingRequests + sensor.onClicked: root.membershipRequestsClicked() + } + + Rectangle { + Layout.fillWidth: true + + implicitHeight: 1 + visible: memberRequestsButton.visible + color: Theme.palette.statusPopupMenu.separatorColor + } + + StatusBaseText { + Layout.alignment: Qt.AlignHCenter + + visible: memberList.count === 0 + //% "Community members will appear here" + text: qsTrId("community-members-will-appear-here") + font.pixelSize: 15 + color: Theme.palette.baseColor1 + } + + StatusBaseText { + Layout.alignment: Qt.AlignHCenter + + visible: !!memberSearch.input.text && memberList.height == 0 + //% "No contacts found" + text: qsTrId("no-contacts-found") + font.pixelSize: 15 + color: Theme.palette.baseColor1 + } + + ListView { + id: memberList + + Layout.fillWidth: true + Layout.fillHeight: true + + model: root.membersModel + clip: true + + // TODO: use StatusMemberListItem (it does not behave correctly right now) + delegate: StatusListItem { + id: memberItem + + readonly property bool itsMe: model.id.toLowerCase() === userProfile.pubKey.toLowerCase() + readonly property bool isOnline: model.onlineStatus === Constants.userStatus.online + + width: memberList.width + + // FIXME: use QSortFilterProxyModel instead + visible: memberSearch.input.text === "" || title.toLowerCase().includes(memberSearch.input.text.toLowerCase()) + height: visible ? implicitHeight : 0 + + title: { + if (memberItem.itsMe) { + //% "You" + return qsTrId("You") + } + return !model.name.endsWith(".eth") ? model.name : Utils.removeStatusEns(model.name) + } + subTitle: model.id.substring(0, 5) + "..." + model.id.substring(model.id.length - 3) + + statusListItemIcon { + name: model.name + badge { + visible: true + color: memberItem.isOnline ? Theme.palette.successColor1 : Theme.palette.baseColor1 + } + } + + image { + width: 40 + height: 40 + source: model.icon + isIdenticon: false + } + + icon { + width: 40 + height: 40 + color: Style.current.background + textColor: Style.current.secondaryText + letterSize: Math.max(4, root.imageWidth / 2.4) + charactersLen: 2 + isLetterIdenticon: true + } + + ringSettings { + ringSpecModel: model.isAdded ? "" : Utils.getColorHashAsJson(model.id) + ringPxSize: Math.max(icon.width / 24.0) + } + + onClicked: root.userProfileClicked(model.id) + + components: [ + StatusButton { + visible: root.editable && !memberItem.itsMe + text: qsTr("Ban") + type: StatusBaseButton.Type.Danger + size: StatusBaseButton.Size.Tiny + + onClicked: root.banUserClicked(model.id) + }, + + StatusButton { + visible: root.editable && !memberItem.itsMe + text: qsTr("Kick") + type: StatusBaseButton.Type.Danger + size: StatusBaseButton.Size.Tiny + + onClicked: root.kickUserClicked(model.id) + } + ] + } + } + } +} diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityOverviewSettingsPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityOverviewSettingsPanel.qml new file mode 100644 index 0000000000..f33e11da41 --- /dev/null +++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityOverviewSettingsPanel.qml @@ -0,0 +1,155 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 +import QtQuick.Controls 2.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 + +import "../../layouts" + +StackLayout { + id: root + + property string name + property string description + property string image + property color color + property bool editable: false + property bool owned: false + + signal edited(Item item) // item containing edited fields (name, description, image, color) + + clip: true + + SettingsPageLayout { + title: qsTr("Overview") + + content: ColumnLayout { + spacing: 16 + + RowLayout { + Layout.fillWidth: true + + spacing: 16 + + StatusSmartIdenticon { + name: root.name + + icon { + width: 80 + height: 80 + isLetterIdenticon: !root.image + color: root.color + letterSize: width / 2.4 + } + + image { + width: 80 + height: 80 + source: root.image + } + } + + ColumnLayout { + Layout.fillWidth: true + + StatusBaseText { + id: nameText + Layout.fillWidth: true + font.pixelSize: 24 + color: Theme.palette.directColor1 + wrapMode: Text.WordWrap + text: root.name + } + + StatusBaseText { + id: descriptionText + Layout.fillWidth: true + font.pixelSize: 15 + color: Theme.palette.directColor1 + wrapMode: Text.WordWrap + text: root.description + } + } + + StatusButton { + visible: root.editable + text: qsTr("Edit Community") + onClicked: root.currentIndex = 1 + } + } + + Rectangle { + Layout.fillWidth: true + + implicitHeight: 1 + visible: root.editable + color: Theme.palette.statusPopupMenu.separatorColor + } + + RowLayout { + Layout.fillWidth: true + + visible: root.owned + + StatusIcon { + icon: "info" + } + + StatusBaseText { + Layout.fillWidth: true + text: qsTr("This node is the Community Owner Node. For your Community to function correctly try to keep this computer with Status running and onlinie as much as possible.") + font.pixelSize: 15 + color: Theme.palette.directColor1 + wrapMode: Text.WordWrap + } + } + + Item { + Layout.fillHeight: true + } + } + } + + SettingsPageLayout { + id: editCommunityPage + + previousPage: qsTr("Overview") + title: qsTr("Edit Community") + + content: CommunityEditSettingsPanel { + name: root.name + description: root.description + color: root.color + image: root.image + + Component.onCompleted: { + editCommunityPage.dirty = + Qt.binding(() => { + return root.name != name || + root.description != description || + root.image != image || + root.color != color + }) + } + } + + onPreviousPageClicked: { + if (dirty) { + notifyDirty() + } else { + root.currentIndex = 0 + } + } + + onSaveChangesClicked: { + root.currentIndex = 0 + root.edited(contentItem) + reloadContent() + } + + onResetChangesClicked: reloadContent() + } +} diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityWelcomeBannerPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityWelcomeBannerPanel.qml index 7dd66d97a4..9a6644fe2c 100644 --- a/ui/app/AppLayouts/Chat/panels/communities/CommunityWelcomeBannerPanel.qml +++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityWelcomeBannerPanel.qml @@ -26,6 +26,8 @@ Rectangle { property var communitySectionModule property bool hasAddedContacts + signal manageCommunityClicked() + MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton @@ -104,10 +106,7 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: Style.current.padding - onClicked: Global.openPopup(communityProfilePopup, { - store: rootStore, - community: root.activeCommunity, - communitySectionModule: root.communitySectionModule - }) + + onClicked: root.manageCommunityClicked() } } diff --git a/ui/app/AppLayouts/Chat/views/ChatView.qml b/ui/app/AppLayouts/Chat/views/ChatView.qml new file mode 100644 index 0000000000..4002ee648f --- /dev/null +++ b/ui/app/AppLayouts/Chat/views/ChatView.qml @@ -0,0 +1,199 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import Qt.labs.settings 1.0 + +import utils 1.0 +import shared 1.0 +import shared.panels 1.0 +import shared.popups 1.0 +import shared.status 1.0 +import shared.views.chat 1.0 + +import StatusQ.Layout 0.1 +import StatusQ.Popups 0.1 + +import "." +import "../panels" +import "../panels/communities" +import "../popups" +import "../helpers" +import "../controls" +import "../stores" + +StatusAppThreePanelLayout { + id: root + + property var contactsStore + property bool hasAddedContacts: root.contactsStore.myContactsModel.count > 0 + + // Not Refactored + property var messageStore + + property RootStore rootStore + + property Component pinnedMessagesListPopupComponent + property var emojiPopup + property bool stickersLoaded: false + + signal communityInfoButtonClicked() + signal communityManageButtonClicked() + signal profileButtonClicked() + signal openAppSearch() + + Connections { + target: root.rootStore.stickersStore.stickersModule + onStickerPacksLoaded: { + root.stickersLoaded = true; + } + } + + Connections { + target: root.rootStore.chatCommunitySectionModule + onActiveItemChanged: { + root.rootStore.openCreateChat = false; + } + } + + leftPanel: Loader { + id: contactColumnLoader + sourceComponent: root.rootStore.chatCommunitySectionModule.isCommunity()? + communtiyColumnComponent : + contactsColumnComponent + } + + centerPanel: ChatColumnView { + id: chatColumn + parentModule: root.rootStore.chatCommunitySectionModule + rootStore: root.rootStore + contactsStore: root.contactsStore + chatSectionModule: root.rootStore.chatCommunitySectionModule + pinnedMessagesPopupComponent: root.pinnedMessagesListPopupComponent + stickersLoaded: root.stickersLoaded + emojiPopup: root.emojiPopup + onOpenStickerPackPopup: { + Global.openPopup(statusStickerPackClickPopup, {packId: stickerPackId} ) + } + onOpenAppSearch: { + root.openAppSearch(); + } + } + + showRightPanel: { + if (root.rootStore.openCreateChat || + !localAccountSensitiveSettings.showOnlineUsers || + !localAccountSensitiveSettings.expandUsersList) { + return false + } + + let chatContentModule = root.rootStore.currentChatContentModule() + if (!chatContentModule + || chatContentModule.chatDetails.type === Constants.chatType.publicChat) + { + // New communities have no chats, so no chatContentModule or it is a public chat + return false + } + // Check if user list is available as an option for particular chat content module + return chatContentModule.chatDetails.isUsersListAvailable + } + + rightPanel: userListComponent + + Component { + id: userListComponent + UserListPanel { + rootStore: root.rootStore + label: localAccountSensitiveSettings.communitiesEnabled && + root.rootStore.chatCommunitySectionModule.isCommunity() ? + //% "Members" + qsTrId("members-label") : + qsTr("Last seen") + messageContextMenu: quickActionMessageOptionsMenu + usersModule: { + let chatContentModule = root.rootStore.currentChatContentModule() + if (!chatContentModule || !chatContentModule.usersModule) { + // New communities have no chats, so no chatContentModule + return {} + } + return chatContentModule.usersModule + } + } + } + + Component { + id: contactsColumnComponent + ContactsColumnView { + chatSectionModule: root.rootStore.chatCommunitySectionModule + store: root.rootStore + contactsStore: root.contactsStore + emojiPopup: root.emojiPopup + onOpenProfileClicked: { + root.profileButtonClicked(); + } + + onOpenAppSearch: { + root.openAppSearch() + } + } + } + + Component { + id: communtiyColumnComponent + CommunityColumnView { + communitySectionModule: root.rootStore.chatCommunitySectionModule + store: root.rootStore + emojiPopup: root.emojiPopup + hasAddedContacts: root.hasAddedContacts + pinnedMessagesPopupComponent: root.pinnedMessagesListPopupComponent + onInfoButtonClicked: root.communityInfoButtonClicked() + onManageButtonClicked: root.communityManageButtonClicked() + } + } + + Component { + id: groupInfoPopupComponent + GroupInfoPopup { + chatSectionModule: root.rootStore.chatCommunitySectionModule + store: root.rootStore + pinnedMessagesPopupComponent: root.pinnedMessagesListPopupComponent + } + } + + Component { + id: statusStickerPackClickPopup + StatusStickerPackClickPopup{ + store: root.rootStore + onClosed: { + destroy(); + } + } + } + + ConfirmationDialog { + id: removeContactConfirmationDialog + // % "Remove contact" + header.title: qsTrId("remove-contact") + //% "Are you sure you want to remove this contact?" + confirmationText: qsTrId("are-you-sure-you-want-to-remove-this-contact-") + onConfirmButtonClicked: { + let pk = chatColumn.contactToRemove + if (Utils.getContactDetailsAsJson(pk).isContact) { + root.contactsStore.removeContact(pk) + } + removeContactConfirmationDialog.parentPopup.close(); + removeContactConfirmationDialog.close(); + } + } + + MessageContextMenuView { + id: quickActionMessageOptionsMenu + store: root.rootStore + + onOpenProfileClicked: { + Global.openProfilePopup(publicKey) + } + onCreateOneToOneChat: { + Global.changeAppSectionBySectionType(Constants.appSection.chat) + root.rootStore.chatCommunitySectionModule.createOneToOneChat(communityId, chatId, ensName) + } + } +} diff --git a/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml b/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml index 1104c4ed3b..09f132714e 100644 --- a/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml +++ b/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml @@ -34,6 +34,9 @@ Item { //property int chatGroupsListViewCount: communityChatListAndCategories.chatList.count property Component pinnedMessagesPopupComponent + signal infoButtonClicked + signal manageButtonClicked + StatusChatInfoToolBar { id: communityHeader anchors.top: parent.top @@ -49,11 +52,7 @@ Item { chatInfoButton.image.source: communityData.image chatInfoButton.icon.color: communityData.color menuButton.visible: communityData.amISectionAdmin && communityData.canManageUsers - chatInfoButton.onClicked: Global.openPopup(communityProfilePopup, { - store: root.store, - community: communityData, - communitySectionModule: root.communitySectionModule - }) + chatInfoButton.onClicked: root.infoButtonClicked() popupMenu: StatusPopupMenu { StatusMenuItem { @@ -360,6 +359,7 @@ Item { store: root.store hasAddedContacts: root.hasAddedContacts communitySectionModule: root.communitySectionModule + onManageCommunityClicked: root.manageButtonClicked() } } } diff --git a/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml b/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml new file mode 100644 index 0000000000..24e552d693 --- /dev/null +++ b/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml @@ -0,0 +1,183 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 +import QtQuick.Controls 2.14 +import QtQuick.Dialogs 1.3 +import QtGraphicalEffects 1.13 + +import utils 1.0 +import shared.panels 1.0 +import shared.popups 1.0 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Layout 0.1 +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Controls.Validators 0.1 + +import "../panels/communities" +import "../layouts" + +StatusAppTwoPanelLayout { + id: root + + // TODO: get this model from backend? + property var model: [{name: qsTr("Overview"), icon: "help"}, + {name: qsTr("Members"), icon: "group-chat"}, +// {name: qsTr("Permissions"), icon: "objects"}, +// {name: qsTr("Tokens"), icon: "token"}, +// {name: qsTr("Airdrops"), icon: "airdrop"}, +// {name: qsTr("Token sales"), icon: "token-sale"}, +// {name: qsTr("Subscriptions"), icon: "subscription"}, + {name: qsTr("Notifications"), icon: "notification"}] + + property var rootStore + property var community + property var chatCommunitySectionModule + + signal backToCommunityClicked + signal openLegacyPopupClicked // TODO: remove me when migration to new settings is done + + leftPanel: ColumnLayout { + anchors { + fill: parent + margins: 8 + topMargin: 16 + bottomMargin: 16 + } + + spacing: 16 + + StatusNavigationPanelHeadline { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Settings") + } + + ListView { + id: listView + + Layout.fillWidth: true + implicitHeight: contentItem.childrenRect.height + + model: root.model + delegate: StatusNavigationListItem { + width: listView.width + title: modelData.name + icon.name: modelData.icon + selected: d.currentIndex == index + onClicked: d.currentIndex = index + } + } + + Item { + Layout.fillHeight: true + } + + // TODO: remove me when migration to new settings is done + StatusBaseText { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Open legacy popup (to be removed)") + color: Theme.palette.baseColor1 + font.pixelSize: 10 + font.underline: true + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.openLegacyPopupClicked() + } + } + + StatusBaseText { + Layout.alignment: Qt.AlignHCenter + text: "<- " + qsTr("Back to community") + color: Theme.palette.baseColor1 + font.pixelSize: 15 + font.underline: true + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.backToCommunityClicked() + hoverEnabled: true + } + } + } + + rightPanel: Loader { + anchors.fill: parent + anchors.margins: 16 + + active: root.community + + sourceComponent: StackLayout { + currentIndex: d.currentIndex + + CommunityOverviewSettingsPanel { + name: root.community.name + description: root.community.description + image: root.community.image + color: root.community.color + editable: root.community.amISectionAdmin + + onEdited: { + root.chatCommunitySectionModule.editCommunity( + Utils.filterXSS(item.name), + Utils.filterXSS(item.description), + root.community.access, + false, // FIXME + item.color.toString().toUpperCase(), + item.image === root.community.image ? "" : item.image, + item.imageAx, + item.imageAy, + item.imageBx, + item.imageBy, + ) + } + } + + CommunityMembersSettingsPanel { + membersModel: root.community.members + editable: root.community.amISectionAdmin + pendingRequests: root.community.pendingRequestsToJoin ? root.community.pendingRequestsToJoin.count : 0 + + onUserProfileClicked: Global.openProfilePopup(id) + onKickUserClicked: root.rootStore.removeUserFromCommunity(id) + onBanUserClicked: console.debug("NOT IMPLEMENTED") // TODO: implement me + onMembershipRequestsClicked: Global.openPopup(membershipRequestPopup, { + communitySectionModule: root.chatCommunitySectionModule + }) + } + + SettingsPageLayout { + title: qsTr("Notifications") + + content: ColumnLayout { + StatusListItem { + Layout.fillWidth: true + + title: qsTr("Enabled") + icon.name: "notification" + sensor.cursorShape: Qt.ArrowCursor + components: [ + StatusSwitch { + checked: !root.community.muted + onClicked: root.chatCommunitySectionModule.setCommunityMuted(!checked) + } + ] + } + + Item { + Layout.fillHeight: true + } + } + } + } + } + + + QtObject { + id: d + property int currentIndex: 0 + } +} diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index f5ccd3fee5..d3591f84ff 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -511,17 +511,17 @@ Item { Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.fillHeight: true - pinnedMessagesListPopupComponent: pinnedMessagesPopupComponent - emojiPopup: statusEmojiPopup + chatView.pinnedMessagesListPopupComponent: pinnedMessagesPopupComponent + chatView.emojiPopup: statusEmojiPopup contactsStore: appMain.rootStore.contactStore rootStore.emojiReactionsModel: appMain.rootStore.emojiReactionsModel - onProfileButtonClicked: { + chatView.onProfileButtonClicked: { Global.changeAppSectionBySectionType(Constants.appSection.profile); } - onOpenAppSearch: { + chatView.onOpenAppSearch: { appSearch.openSearchPopup() } @@ -601,17 +601,17 @@ Item { Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.fillHeight: true - pinnedMessagesListPopupComponent: pinnedMessagesPopupComponent - emojiPopup: statusEmojiPopup + chatView.pinnedMessagesListPopupComponent: pinnedMessagesPopupComponent + chatView.emojiPopup: statusEmojiPopup contactsStore: appMain.rootStore.contactStore rootStore.emojiReactionsModel: appMain.rootStore.emojiReactionsModel - onProfileButtonClicked: { + chatView.onProfileButtonClicked: { Global.changeAppSectionBySectionType(Constants.appSection.profile); } - onOpenAppSearch: { + chatView.onOpenAppSearch: { appSearch.openSearchPopup() } @@ -699,7 +699,7 @@ Item { } } - + DropArea { id: dragTarget