From 7b54bf31b44f351b9e2ebfa4451a7356d775f342 Mon Sep 17 00:00:00 2001 From: Noelia Date: Thu, 9 Jun 2022 17:27:14 +0200 Subject: [PATCH] feat(CommunitySettings / Permissions): Added `welcome` page and `How holds` tokens dropdown - Added functionality as experimental advanced view button feat(CommunitySettings / Permissions): Added welcome page - Enabled new permissions tab. - Created welcome page layout. - Added permissions welcome image. - Fixed top margin content in `SettingsPanelLayout` to fit designs. Closes #6036 feat(CommunitySettings/Permissions): Created `new permission` page - Added `new permission` page. - Created first card layout. - Added navigation between `welcome` and `newPermission` views. - Improvements in base community settings layout pages. Closes #6037 feat(CommunitySettings/Permissions): `Who holds` tokens dropdown component creation and integration - Tokens dropdown component creation: main view, operators view and extended view. - Logic to add new token and change operator. Part of #6038 --- .../local_account_sensitive_settings.nim | 15 + .../controls/community/HoldingsDropdown.qml | 277 ++++++++++++++++++ .../community/TokensListDropdownContent.qml | 113 +++++++ .../Chat/layouts/SettingsPageLayout.qml | 28 +- .../CommunityPermissionsSettingsPanel.qml | 50 ++++ ui/app/AppLayouts/Chat/stores/RootStore.qml | 1 + .../Chat/views/CommunitySettingsView.qml | 30 +- .../CommunityNewPermissionView.qml | 127 ++++++++ .../CommunityWelcomePermissionsView.qml | 139 +++++++++ .../Profile/stores/AdvancedStore.qml | 4 + .../AppLayouts/Profile/views/AdvancedView.qml | 17 ++ ui/imports/assets/icons/add.svg | 3 + ui/imports/assets/icons/condition-Or.svg | 5 + ui/imports/assets/icons/contact_verified.svg | 5 + .../png/community/permissions21_3_1.png | Bin 0 -> 24199 bytes .../shared/panels/CurveSeparatorWithText.qml | 45 +++ ui/imports/shared/panels/qmldir | 1 + 17 files changed, 829 insertions(+), 31 deletions(-) create mode 100644 ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml create mode 100644 ui/app/AppLayouts/Chat/controls/community/TokensListDropdownContent.qml create mode 100644 ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml create mode 100644 ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml create mode 100644 ui/app/AppLayouts/Chat/views/communities/CommunityWelcomePermissionsView.qml create mode 100644 ui/imports/assets/icons/add.svg create mode 100644 ui/imports/assets/icons/condition-Or.svg create mode 100644 ui/imports/assets/icons/contact_verified.svg create mode 100644 ui/imports/assets/png/community/permissions21_3_1.png create mode 100644 ui/imports/shared/panels/CurveSeparatorWithText.qml diff --git a/src/app/global/local_account_sensitive_settings.nim b/src/app/global/local_account_sensitive_settings.nim index 6fa33a5d8e..4da0242ef8 100644 --- a/src/app/global/local_account_sensitive_settings.nim +++ b/src/app/global/local_account_sensitive_settings.nim @@ -8,6 +8,8 @@ const LSS_KEY_WALLET_SPLIT_VIEW* = "walletSplitView" const LSS_KEY_PROFILE_SPLIT_VIEW* = "profileSplitView" const LSS_KEY_IS_WALLET_ENABLED* = "isExperimentalWalletEnabled" const DEFAULT_IS_WALLET_ENABLED = false +const LSS_KEY_IS_COMMUNITY_PERMISSIONS_ENABLED* = "isExperimentalCommunityPermissionsEnabled" +const DEFAULT_IS_COMMUNITY_PERMISSIONS_ENABLED = false const LSS_KEY_NODE_MANAGEMENT_ENABLED* = "nodeManagementEnabled" const DEFAULT_NODE_MANAGEMENT_ENABLED = false const LSS_KEY_IS_BROWSER_ENABLED* = "isExperimentalBrowserEnabled" @@ -191,6 +193,18 @@ QtObject: write = setProfileSplitView notify = profileSplitViewChanged + proc isCommunityPermissionsEnabledChanged*(self: LocalAccountSensitiveSettings) {.signal.} + proc getIsCommunityPermissionsEnabled*(self: LocalAccountSensitiveSettings): bool {.slot.} = + getSettingsProp[bool](self, LSS_KEY_IS_COMMUNITY_PERMISSIONS_ENABLED, newQVariant(DEFAULT_IS_COMMUNITY_PERMISSIONS_ENABLED)) + proc setIsCommunityPermissionsEnabled*(self: LocalAccountSensitiveSettings, value: bool) {.slot.} = + setSettingsProp(self, LSS_KEY_IS_COMMUNITY_PERMISSIONS_ENABLED, newQVariant(value)): + self.isCommunityPermissionsEnabledChanged() + + QtProperty[bool] isCommunityPermissionsEnabled: + read = getIsCommunityPermissionsEnabled + write = setIsCommunityPermissionsEnabled + notify = isCommunityPermissionsEnabledChanged + proc isWalletEnabledChanged*(self: LocalAccountSensitiveSettings) {.signal.} proc getIsWalletEnabled*(self: LocalAccountSensitiveSettings): bool {.slot.} = getSettingsProp[bool](self, LSS_KEY_IS_WALLET_ENABLED, newQVariant(DEFAULT_IS_WALLET_ENABLED)) @@ -740,6 +754,7 @@ QtObject: of LSS_KEY_WALLET_SPLIT_VIEW: self.walletSplitViewChanged() of LSS_KEY_PROFILE_SPLIT_VIEW: self.profileSplitViewChanged() of LSS_KEY_IS_WALLET_ENABLED: self.isWalletEnabledChanged() + of LSS_KEY_IS_COMMUNITY_PERMISSIONS_ENABLED: self.isCommunityPermissionsEnabledChanged() of LSS_KEY_NODE_MANAGEMENT_ENABLED: self.nodeManagementEnabledChanged() of LSS_KEY_IS_BROWSER_ENABLED: self.isBrowserEnabledChanged() of LSS_KEY_SHOW_ONLINE_USERS: self.showOnlineUsersChanged() diff --git a/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml b/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml new file mode 100644 index 0000000000..d253148596 --- /dev/null +++ b/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml @@ -0,0 +1,277 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.14 +import QtQuick.Controls 2.13 +import QtGraphicalEffects 1.13 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Controls.Validators 0.1 +import StatusQ.Core.Utils 0.1 as SQ + +import utils 1.0 + +StatusDropdown { + id: root + + property real tokenAmountValue: 0 + property string tokenName: d.defaultTokenNameText + property url tokenImage: "" + property int operator: SQ.Utils.Operators.None + property bool withOperatorSelector: true + + signal addToken(string tokenText, url tokenImage, int operator) + + function reset() { + root.tokenAmountValue = 0 + root.tokenName = d.defaultTokenNameText + root.tokenImage = "" + root.operator = SQ.Utils.Operators.None + } + + QtObject { + id: d + + readonly property bool ready: root.tokenAmountValue > 0 && root.tokenName !== d.defaultTokenNameText + + // By design values: + readonly property int initialHeight: 232 + readonly property int mainHeight: 256 + readonly property int operatorsHeight: 96 + readonly property int extendedHeight: 417 + readonly property int defaultWidth: 289 + readonly property int operatorsWidth: 159 + + property string defaultTokenNameText: qsTr("Choose token") + + function selectInitState() { + if(root.withOperatorSelector) + loader.sourceComponent = operatorsSelectorView + else + loader.sourceComponent = tabsView + } + } + + width: d.defaultWidth + height: d.initialHeight + + contentItem: Loader { + id: loader + anchors.fill: parent + sourceComponent: root.withOperatorSelector ? operatorsSelectorView : tabsView + + onSourceComponentChanged: { + if(sourceComponent == tokensExtendedView) { + root.height = Math.min(item.contentHeight + item.anchors.topMargin + item.anchors.bottomMargin, d.extendedHeight) + root.width = d.defaultWidth + } + else if(sourceComponent == operatorsSelectorView) { + root.height = d.operatorsHeight + root.width = d.operatorsWidth + } + else if(sourceComponent == tabsView && root.withOperatorSelector) { + root.height = d.mainHeight + root.width = d.defaultWidth + } + else if(sourceComponent == tabsView && !root.withOperatorSelector) { + root.height = d.initialHeight + root.width = d.defaultWidth + } + } + } + + onOpened: d.selectInitState() + onClosed: root.reset() + onWithOperatorSelectorChanged: d.selectInitState() + + Component { + id: tabsView + + ColumnLayout { + anchors.fill: parent + anchors.margins: 8 + anchors.topMargin: 16 + spacing: 8 + StatusIconTextButton { + visible: root.withOperatorSelector + Layout.leftMargin: 8 + spacing: 0 + statusIcon: "next" + icon.width: 12 + icon.height: 12 + iconRotation: 180 + text: qsTr("Back") + onClicked: loader.sourceComponent = operatorsSelectorView + } + StatusSwitchTabBar { + id: tabBar + Layout.preferredWidth: 273 // by design + Layout.preferredHeight: 36 // by design + StatusSwitchTabButton { + text: qsTr("Token") + fontPixelSize: 13 + } + StatusSwitchTabButton { + text: qsTr("Collectibles") + fontPixelSize: 13 + enabled: false // TODO + } + StatusSwitchTabButton { + text: qsTr("ENS") + fontPixelSize: 13 + enabled: false // TODO + } + } + StackLayout { + Layout.fillWidth: true + currentIndex: tabBar.currentIndex + // Tokens layout definition: + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + StatusPickerButton { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.preferredHeight: 36 + bgColor: Theme.palette.baseColor5 + contentColor: Theme.palette.directColor1 + text: root.tokenName + font.pixelSize: 13 + image.source: root.tokenImage + onClicked: loader.sourceComponent = tokensExtendedView + } + + StatusInput { + Layout.fillWidth: true + minimumHeight: 36 + maximumHeight: 36 + topPadding: 0 + bottomPadding: 0 + text: root.tokenAmountValue == 0 ? "" : root.tokenAmountValue.toString() + font.pixelSize: 13 + rightPadding: amountText.implicitWidth + amountText.anchors.rightMargin + leftPadding + input.placeholderText: "0" + validationMode: StatusInput.ValidationMode.IgnoreInvalidInput + validators: StatusFloatValidator { bottom: 0 } + + StatusBaseText { + id: amountText + anchors.right: parent.right + anchors.rightMargin: 13 + anchors.verticalCenter: parent.verticalCenter + text: qsTr("Amount") + color: Theme.palette.baseColor1 + font.pixelSize: 13 + } + onTextChanged: root.tokenAmountValue = Number(text) + } + + // Just a filler + Item { Layout.fillHeight: true} + + StatusButton { + enabled: d.ready + text: qsTr("Add") + height: 44 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + onClicked: { root.addToken(root.tokenAmountValue.toString() + " " + root.tokenName, root.tokenImage, root.operator) } + } + } // End of Tokens Layout definition + + // TODO + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + // TODO + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + } + + Component { + id: operatorsSelectorView + ColumnLayout { + anchors.fill: parent + anchors.margins: 8 + StatusPickerButton { + Layout.fillWidth: true + Layout.preferredHeight: 36 + horizontalPadding: 12 + spacing: 10 + bgColor: Theme.palette.primaryColor3 + contentColor: Theme.palette.primaryColor1 + image.source: Style.svg("add") + text: qsTr("And...") + image.height: 12 + image.width: 12 + font.pixelSize: 13 + onClicked: { + root.operator = SQ.Utils.Operators.And + loader.sourceComponent = tabsView + } + } + StatusPickerButton { + Layout.fillWidth: true + Layout.preferredHeight: 36 + horizontalPadding: 12 + spacing: 10 + bgColor: Theme.palette.primaryColor3 + contentColor: Theme.palette.primaryColor1 + image.source: Style.svg("condition-Or") + image.height: 12 + image.width: 12 + text: qsTr("Or...") + font.pixelSize: 13 + onClicked: { + root.operator = SQ.Utils.Operators.Or + loader.sourceComponent = tabsView + } + } + } + } + + Component { + id: tokensExtendedView + + // TODO: It probabily will be a reusable component for collectibles and channels + TokensListDropdownContent { + anchors.fill: parent + anchors.topMargin: 8 + anchors.bottomMargin: 8 + headerModel: ListModel { + ListElement { index: 0; icon: "next"; iconSize: 12; description: qsTr("Back"); rotation: 180; spacing: 0 } + ListElement { index: 1; icon: "add"; iconSize: 16; description: qsTr("Mint token"); rotation: 0; spacing: 8 } + ListElement { index: 2; icon: "invite-users"; iconSize: 16; description: qsTr("Import existing token"); rotation: 180; spacing: 8 } + } + // TODO: Replace to real data, now dummy model + model: ListModel { + ListElement {imageSource: "qrc:imports/assets/png/tokens/SOCKS.png"; name: "Unisocks"; shortName: "SOCKS"; selected: false; category: "Community tokens"} + ListElement {imageSource: "qrc:imports/assets/png/tokens/ZRX.png"; name: "Ox"; shortName: "ZRX"; selected: false; category: "Listed tokens"} + ListElement {imageSource: "qrc:imports/assets/png/tokens/CUSTOM-TOKEN.png"; name: "1inch"; shortName: "ZRX"; selected: false; category: "Listed tokens"} + ListElement {imageSource: "qrc:imports/assets/png/tokens/CUSTOM-TOKEN.png"; name: "Aave"; shortName: "AAVE"; selected: false; category: "Listed tokens"} + ListElement {imageSource: "qrc:imports/assets/png/tokens/CUSTOM-TOKEN.png"; name: "Amp"; shortName: "AMP"; selected: false; category: "Listed tokens"} + } + onHeaderItemClicked: { + if(index === 0) loader.sourceComponent = tabsView // Go back + // TODO: + else if(index === 1) console.log("TODO: Mint token") + else if(index === 2) console.log("TODO: Import existing token") + } + onItemClicked: { + // Go back + loader.sourceComponent = tabsView + + // Update new token info + root.tokenName = shortName + root.tokenImage = imageSource + } + } + } +} diff --git a/ui/app/AppLayouts/Chat/controls/community/TokensListDropdownContent.qml b/ui/app/AppLayouts/Chat/controls/community/TokensListDropdownContent.qml new file mode 100644 index 0000000000..0b70251cd0 --- /dev/null +++ b/ui/app/AppLayouts/Chat/controls/community/TokensListDropdownContent.qml @@ -0,0 +1,113 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.14 +import QtQuick.Controls 2.13 +import QtGraphicalEffects 1.13 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 + + +StatusListView { + id: root + + property var headerModel + + signal headerItemClicked(int index) + signal itemClicked(string name, string shortName, url imageSource) + + implicitWidth: 273 + currentIndex: -1 + clip: true + headerPositioning: ListView.OverlayHeader + header: Rectangle { + z: 3 // Above delegate (z=1) and above section.delegate (z = 2) + color: Theme.palette.statusPopupMenu.backgroundColor + width: root.width + height: columnHeader.implicitHeight + 2 * columnHeader.anchors.topMargin + ColumnLayout { + id: columnHeader + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 16 + anchors.rightMargin: anchors.leftMargin + anchors.topMargin: 8 + anchors.bottomMargin: 2 * anchors.topMargin + spacing: 20 + Repeater { + model: root.headerModel + delegate: StatusIconTextButton { + z: 3 // Above delegate (z=1) and above section.delegate (z = 2) + spacing: model.spacing + statusIcon: model.icon + icon.width: model.iconSize + icon.height: model.iconSize + iconRotation: model.rotation + text: model.description + onClicked: root.headerItemClicked(model.index) + } + } + } + }// End of Header + delegate: Rectangle { + width: ListView.view.width + height: 44 // by design + color: mouseArea.containsMouse ? Theme.palette.baseColor4 : "transparent" + RowLayout { + anchors.fill: parent + anchors.leftMargin: 14 + spacing: 8 + StatusRoundedImage { + Layout.alignment: Qt.AlignVCenter + image.source: model.imageSource + visible: model.imageSource.toString() !== "" + Layout.preferredWidth: 28 + Layout.preferredHeight: Layout.preferredWidth + } + ColumnLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: 0 + StatusBaseText { + Layout.fillWidth: true + text: model.name + color: Theme.palette.directColor1 + font.pixelSize: 13 + clip: true + elide: Text.ElideRight + } + StatusBaseText { + Layout.fillWidth: true + text: model.shortName + color: Theme.palette.baseColor1 + font.pixelSize: 12 + clip: true + elide: Text.ElideRight + } + } + } + MouseArea { + id: mouseArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onClicked: { root.itemClicked(model.name, model.shortName, model.imageSource) } + } + }// End of Item + section.property: "category" + section.criteria: ViewSection.FullString + section.delegate: Item { + width: ListView.view.width + height: 34 // by design + StatusBaseText { + anchors.leftMargin: 18 + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text: section + color: Theme.palette.baseColor1 + font.pixelSize: 12 + elide: Text.ElideRight + } + }// End of Category item +}// End of Root diff --git a/ui/app/AppLayouts/Chat/layouts/SettingsPageLayout.qml b/ui/app/AppLayouts/Chat/layouts/SettingsPageLayout.qml index 6434077400..01fdab803d 100644 --- a/ui/app/AppLayouts/Chat/layouts/SettingsPageLayout.qml +++ b/ui/app/AppLayouts/Chat/layouts/SettingsPageLayout.qml @@ -5,6 +5,8 @@ import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 +import utils 1.0 +import shared.panels 1.0 import shared.popups 1.0 Item { @@ -44,24 +46,18 @@ Item { id: layout anchors.fill: parent - spacing: 16 - Item { + StatusIconTextButton { 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() - } - } + visible: root.previousPage + spacing: 8 + statusIcon: "arrow" + icon.width: 24 + icon.height: 24 + text: root.previousPage + font.pixelSize: 15 + onClicked: root.previousPageClicked() } StatusBaseText { @@ -77,6 +73,7 @@ Item { id: contentLoader Layout.fillWidth: true Layout.fillHeight: true + Layout.topMargin: 16 Layout.leftMargin: 24 Layout.rightMargin: 24 @@ -98,4 +95,3 @@ Item { onSaveChangesClicked: root.saveChangesClicked() } } - diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml new file mode 100644 index 0000000000..343d195750 --- /dev/null +++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml @@ -0,0 +1,50 @@ +import QtQuick 2.14 + +import "../../layouts" +import "../../views/communities" + +SettingsPageLayout { + id: root + + QtObject { + id: d + + readonly property string welcomeViewState: "WELCOME" + readonly property string newPermissionViewState: "NEWPERMISSION" + } + + state: d.welcomeViewState // Initial state + states: [ + State { + name: d.welcomeViewState + PropertyChanges {target: root; title: qsTr("Permissions")} + PropertyChanges {target: root; previousPage: ""} + PropertyChanges {target: root; content: welcomeView} + }, + State { + name: d.newPermissionViewState + PropertyChanges {target: root; title: qsTr("New permission")} + PropertyChanges {target: root; previousPage: qsTr("Permissions")} + PropertyChanges {target: root; content: newPermissionView} + } + ] + + onPreviousPageClicked: { + if(root.state === d.newPermissionViewState) { + root.state = d.welcomeViewState + } + } + + // Community Permissions possible view contents: + Component { + id: welcomeView + CommunityWelcomePermissionsView { + onAddPermission: root.state = d.newPermissionViewState + } + } + + Component { + id: newPermissionView + CommunityNewPermissionView { } + } +} diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index 17d03fe760..5fadaf1ef4 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -126,6 +126,7 @@ QtObject { property var communitiesModuleInst: communitiesModule property var communitiesList: communitiesModuleInst.model + property bool communityPermissionsEnabled: localAccountSensitiveSettings.isCommunityPermissionsEnabled property var userProfileInst: userProfile diff --git a/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml b/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml index f954dc3ca4..51f237f478 100644 --- a/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml +++ b/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml @@ -23,17 +23,18 @@ StatusAppTwoPanelLayout { id: root // TODO: get this model from backend? - property var settingsMenuModel: [ - {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"} - ] + property var settingsMenuModel: root.rootStore.communityPermissionsEnabled ? [{name: qsTr("Overview"), icon: "help"}, + {name: qsTr("Members"), icon: "group-chat"}, + {name: qsTr("Permissions"), icon: "objects"}] : + [{name: qsTr("Overview"), icon: "help"}, + {name: qsTr("Members"), icon: "group-chat"}] + // TODO: Next community settings options: + // {name: qsTr("Tokens"), icon: "token"}, + // {name: qsTr("Airdrops"), icon: "airdrop"}, + // {name: qsTr("Token sales"), icon: "token-sale"}, + // {name: qsTr("Subscriptions"), icon: "subscription"}, - property var rootStore + property var rootStore property var community property var chatCommunitySectionModule property bool hasAddedContacts: false @@ -122,12 +123,8 @@ StatusAppTwoPanelLayout { rightPanel: Loader { anchors.fill: parent - anchors.leftMargin: 28 - anchors.topMargin: 23 - anchors.margins: 16 - + anchors.margins: 32 active: root.community - sourceComponent: StackLayout { currentIndex: d.currentIndex @@ -199,9 +196,12 @@ StatusAppTwoPanelLayout { communitySectionModule: root.chatCommunitySectionModule }) } + + CommunityPermissionsSettingsPanel {} } } + onSettingsMenuModelChanged: d.currentIndex = 0 QtObject { id: d diff --git a/ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml b/ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml new file mode 100644 index 0000000000..ae993856f5 --- /dev/null +++ b/ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml @@ -0,0 +1,127 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 + +import utils 1.0 +import shared.panels 1.0 + +import "../../../Chat/controls/community" + +Flickable { + id: root + + signal createPermission() + + QtObject { + id: d + property bool isPrivate: false + } + + contentWidth: mainLayout.width + contentHeight: mainLayout.height + clip: true + flickableDirection: Flickable.AutoFlickIfNeeded + + ColumnLayout { + id: mainLayout + width: 560 // by design + spacing: 0 + CurveSeparatorWithText { + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 14 + text: qsTr("Anyone") + } + StatusItemSelector { + id: tokensSelector + Layout.fillWidth: true + icon: Style.svg("contact_verified") + title: qsTr("Who holds") + defaultItemText: qsTr("Example: 10 SNT") + andOperatorText: qsTr("and") + orOperatorText: qsTr("or") + popupItem: HoldingsDropdown { + id: dropdown + withOperatorSelector: tokensSelector.itemsModel.count > 0 + onAddToken: { + tokensSelector.addItem(tokenText, tokenImage, operator) + dropdown.close() + } + } + } + Rectangle { + Layout.leftMargin: 16 + Layout.preferredWidth: 2 + Layout.preferredHeight: 24 + color: Style.current.separator + } + StatusItemSelector { + Layout.fillWidth: true + icon: Style.svg("profile/security") + iconSize: 24 + title: qsTr("Is allowed to") + defaultItemText: qsTr("Example: View and post") + } + Rectangle { + Layout.leftMargin: 16 + Layout.preferredWidth: 2 + Layout.preferredHeight: 24 + color: Style.current.separator + } + StatusItemSelector { + Layout.fillWidth: true + icon: Style.svg("create-category") + iconSize: 24 + title: qsTr("In") + defaultItemText: qsTr("Example: `#general` channel") + } + Separator { + Layout.topMargin: 24 + } + RowLayout { + Layout.topMargin: 12 + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: Layout.leftMargin + spacing: 16 + StatusRoundIcon { + icon.name: "hide" + } + ColumnLayout { + Layout.fillWidth: true + StatusBaseText { + text: qsTr("Private") + color: Theme.palette.directColor1 + font.pixelSize: 15 + } + StatusBaseText { + Layout.fillWidth: true + Layout.fillHeight: true + text: qsTr("Make this permission private to hide it from members who don’t meet it’s requirements") + color: Theme.palette.baseColor1 + font.pixelSize: 15 + lineHeight: 1.2 + wrapMode: Text.WordWrap + elide: Text.ElideRight + clip: true + } + } + StatusSwitch { + checked: d.isPrivate + onToggled: { d.isPrivate = checked } + } + } + StatusButton { + Layout.topMargin: 24 + text: qsTr("Create permission") + enabled: false + Layout.preferredHeight: 44 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + onClicked: root.createPermission() + } + } +} diff --git a/ui/app/AppLayouts/Chat/views/communities/CommunityWelcomePermissionsView.qml b/ui/app/AppLayouts/Chat/views/communities/CommunityWelcomePermissionsView.qml new file mode 100644 index 0000000000..473f434ae6 --- /dev/null +++ b/ui/app/AppLayouts/Chat/views/communities/CommunityWelcomePermissionsView.qml @@ -0,0 +1,139 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 + +import utils 1.0 + +Flickable { + id: root + + signal addPermission() + + contentWidth: mainLayout.width + contentHeight: mainLayout.height + mainLayout.anchors.topMargin + clip: true + flickableDirection: Flickable.AutoFlickIfNeeded + + ColumnLayout { + id: mainLayout + width: 560 // by design + spacing: 24 + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: contentColumn.implicitHeight + contentColumn.anchors.topMargin + contentColumn.anchors.bottomMargin + color: "transparent" + radius: 16 + border.color: Theme.palette.baseColor5 + clip: true + + ColumnLayout { + id: contentColumn + anchors.fill: parent + anchors.margins: 16 + anchors.bottomMargin: 32 + spacing: 8 + clip: true + Image { + Layout.preferredWidth: 257 + Layout.preferredHeight: Layout.preferredWidth + Layout.alignment: Qt.AlignHCenter + source: Style.png("community/permissions21_3_1") + mipmap: true + } + StatusBaseText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + text: qsTr("Permissions") + font.pixelSize: 17 + font.weight: Font.Bold + color: Theme.palette.directColor1 + } + StatusBaseText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + text: qsTr("You can manage your community by creating and issuing membership and access permissions") + lineHeight: 1.2 + font.pixelSize: 15 + color: Theme.palette.baseColor1 + wrapMode: Text.WordWrap + } + ColumnLayout { + id: checkersColumn + property int rowChildSpacing: 10 + property color rowIconColor: Theme.palette.primaryColor1 + property string rowIconName: "checkmark-circle" + property int rowFontSize: 15 + property color rowTextColor: Theme.palette.directColor1 + property double rowTextLineHeight: 1.2 + + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft + spacing: 10 + RowLayout { + Layout.fillWidth: true + spacing: checkersColumn.rowChildSpacing + StatusIcon { + icon: checkersColumn.rowIconName + color: checkersColumn.rowIconColor + } + StatusBaseText { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + text: qsTr("Give individual members access to private channels") + lineHeight: checkersColumn.rowTextLineHeight + font.pixelSize: checkersColumn.rowFontSize + color: checkersColumn.rowTextColor + wrapMode: Text.WordWrap + } + } + RowLayout { + Layout.fillWidth: true + spacing: checkersColumn.rowChildSpacing + StatusIcon { + icon: checkersColumn.rowIconName + color: checkersColumn.rowIconColor + } + StatusBaseText { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + text: qsTr("Monetise your community with subscriptions and fees") + lineHeight: checkersColumn.rowTextLineHeight + font.pixelSize: checkersColumn.rowFontSize + color: Theme.palette.directColor1 + wrapMode: Text.WordWrap + } + } + RowLayout { + Layout.fillWidth: true + spacing: checkersColumn.rowChildSpacing + StatusIcon { + icon: checkersColumn.rowIconName + color: checkersColumn.rowIconColor + } + StatusBaseText { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + text: qsTr("Require holding a token or NFT to obtain exclusive membership rights") + lineHeight: checkersColumn.rowTextLineHeight + font.pixelSize: checkersColumn.rowFontSize + color: checkersColumn.rowTextColor + wrapMode: Text.WordWrap + } + } + } + } + } + + StatusButton { + text: qsTr("Add permission") + height: 44 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + onClicked: root.addPermission() + } + } +} diff --git a/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml b/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml index 3f9abe64c4..82dd6d8576 100644 --- a/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml +++ b/ui/app/AppLayouts/Profile/stores/AdvancedStore.qml @@ -33,6 +33,7 @@ QtObject { readonly property string gifWidget: "gifWidget" readonly property string communityHistoryArchiveSupport: "communityHistoryArchiveSupport" readonly property string communitiesPortal: "communitiesPortal" + readonly property string communityPermissions: "communityPermissions" } function logDir() { @@ -130,5 +131,8 @@ QtObject { else if (feature === experimentalFeatures.gifWidget) { localAccountSensitiveSettings.isGifWidgetEnabled = !localAccountSensitiveSettings.isGifWidgetEnabled } + else if (feature === experimentalFeatures.communityPermissions) { + localAccountSensitiveSettings.isCommunityPermissionsEnabled = !localAccountSensitiveSettings.isCommunityPermissionsEnabled + } } } diff --git a/ui/app/AppLayouts/Profile/views/AdvancedView.qml b/ui/app/AppLayouts/Profile/views/AdvancedView.qml index aad789efca..709006670b 100644 --- a/ui/app/AppLayouts/Profile/views/AdvancedView.qml +++ b/ui/app/AppLayouts/Profile/views/AdvancedView.qml @@ -163,6 +163,23 @@ SettingsContentBase { } } + // TODO: replace with StatusQ component + StatusSettingsLineButton { + anchors.leftMargin: 0 + anchors.rightMargin: 0 + text: qsTr("Community Permissions Settings") + isSwitch: true + switchChecked: localAccountSensitiveSettings.isCommunityPermissionsEnabled + onClicked: { + if (!localAccountSensitiveSettings.isCommunityPermissionsEnabled) { + confirmationPopup.experimentalFeature = root.advancedStore.experimentalFeatures.communityPermissions + confirmationPopup.open() + } else { + root.advancedStore.toggleExperimentalFeature(root.advancedStore.experimentalFeatures.communityPermissions) + } + } + } + StatusSectionHeadline { anchors.left: parent.left anchors.right: parent.right diff --git a/ui/imports/assets/icons/add.svg b/ui/imports/assets/icons/add.svg new file mode 100644 index 0000000000..6c147debd0 --- /dev/null +++ b/ui/imports/assets/icons/add.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/imports/assets/icons/condition-Or.svg b/ui/imports/assets/icons/condition-Or.svg new file mode 100644 index 0000000000..c0b38a1a97 --- /dev/null +++ b/ui/imports/assets/icons/condition-Or.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/imports/assets/icons/contact_verified.svg b/ui/imports/assets/icons/contact_verified.svg new file mode 100644 index 0000000000..e0305776f8 --- /dev/null +++ b/ui/imports/assets/icons/contact_verified.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/imports/assets/png/community/permissions21_3_1.png b/ui/imports/assets/png/community/permissions21_3_1.png new file mode 100644 index 0000000000000000000000000000000000000000..7776721c866b4269c8f132f871bd15325eaf2757 GIT binary patch literal 24199 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