From 0c8231a6b6107e0151e08433fd56df4bb4816f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Tinkl?= Date: Thu, 25 Apr 2024 11:47:40 +0200 Subject: [PATCH] fix: can't join an encrypted community - fix the corner case and allow the user to join a community without an explicitely stated "Member" permission - enable/disable the Share/Join/Save buttons when the permission to join check is ongoing, or when the permission check failed - display tooltips over the disabled buttons explaining why it's disabled - always display the eligibility button floating on top of the (scrollable) contents Fixes #14473 Fixes #14299 --- .../CommunityMembershipSetupDialogPage.qml | 11 ++- .../src/StatusQ/Controls/StatusToolTip.qml | 1 + .../StatusQ/Popups/Dialog/StatusDialog.qml | 3 +- ui/StatusQ/src/permissionutilsinternal.cpp | 16 ++-- ui/app/AppLayouts/Chat/stores/RootStore.qml | 1 + .../panels/SharedAddressesPanel.qml | 35 ++++++- .../SharedAddressesPermissionsPanel.qml | 2 + ui/app/AppLayouts/stores/RootStore.qml | 2 + ui/app/mainui/Popups.qml | 3 + .../popups/CommunityMembershipSetupDialog.qml | 91 +++++++++++++------ 10 files changed, 122 insertions(+), 43 deletions(-) diff --git a/storybook/pages/CommunityMembershipSetupDialogPage.qml b/storybook/pages/CommunityMembershipSetupDialogPage.qml index 980ceb3404..8e3811b1d9 100644 --- a/storybook/pages/CommunityMembershipSetupDialogPage.qml +++ b/storybook/pages/CommunityMembershipSetupDialogPage.qml @@ -55,6 +55,7 @@ SplitView { isInvitationPending: ctrlIsInvitationPending.checked requirementsCheckPending: ctrlRequirementsCheckPending.checked + joinPermissionsCheckSuccessful: ctrlJoinPermissionsCheckSuccessful.checked walletAccountsModel: WalletAccountsModel {} walletAssetsModel: root.walletAssetStore.groupedAccountAssetsModel @@ -80,7 +81,7 @@ SplitView { }) } } - } + } Item { SplitView.fillWidth: true @@ -218,6 +219,14 @@ Nemo enim 😋 ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, text: "Requirements check pending" } + CheckBox { + Layout.leftMargin: 12 + id: ctrlJoinPermissionsCheckSuccessful + visible: !ctrlIsInvitationPending.checked + text: "Join permission successful" + checked: true + } + Item { Layout.fillHeight: true } diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml index 0c6005cbe8..74c0a29e99 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusToolTip.qml @@ -20,6 +20,7 @@ ToolTip { implicitWidth: Math.min(maxWidth, implicitContentWidth + 16) padding: 8 + margins: 8 delay: 200 background: Item { id: statusToolTipBackground diff --git a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml index 6183776870..dfd9660230 100644 --- a/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml +++ b/ui/StatusQ/src/StatusQ/Popups/Dialog/StatusDialog.qml @@ -21,10 +21,11 @@ Dialog { \qmlproperty color backgroundColor This property decides the modal background color */ - property string backgroundColor: Theme.palette.statusModal.backgroundColor + property color backgroundColor: Theme.palette.statusModal.backgroundColor /*! \qmlproperty closeHandler This property decides the action to be performed when the close button is clicked. It allows to define + a custom function to be called when the popup is closed by the user. */ property var closeHandler: root.close diff --git a/ui/StatusQ/src/permissionutilsinternal.cpp b/ui/StatusQ/src/permissionutilsinternal.cpp index 9a7bad9672..b126e98767 100644 --- a/ui/StatusQ/src/permissionutilsinternal.cpp +++ b/ui/StatusQ/src/permissionutilsinternal.cpp @@ -8,7 +8,7 @@ #include namespace { -int roleByName(QAbstractItemModel* model, const QString &roleName) +constexpr int roleByName(QAbstractItemModel* model, const QString &roleName) { if (!model) return -1; @@ -161,18 +161,23 @@ int /*PermissionTypes::Type*/ PermissionUtilsInternal::isEligibleToJoinAs(QAbstr } QSet tmpRes; - bool hasAnyJoinPermission{false}; + bool hasMemberPermission{false}; constexpr auto isJoinTypePermission = [](PermissionTypes::Type type) { return type == PermissionTypes::Type::TokenMaster || type == PermissionTypes::Type::Admin || type == PermissionTypes::Type::Member; }; + constexpr auto isMemberPermission = [](PermissionTypes::Type type) { + return type == PermissionTypes::Type::Member; + }; + const auto permissionsCount = permissionsModel->rowCount(); for (int i = 0; i < permissionsCount; i++) { const auto permissionType = static_cast(permissionsModel->data(permissionsModel->index(i, 0), permissionTypeRole).toInt()); if (isJoinTypePermission(permissionType)) { - hasAnyJoinPermission = true; + if (isMemberPermission(permissionType)) + hasMemberPermission = true; const auto tokenCriteriaMet = permissionsModel->data(permissionsModel->index(i, 0), tokenCriteriaMetRole).toBool(); if (tokenCriteriaMet) { tmpRes.insert(permissionType); @@ -180,16 +185,13 @@ int /*PermissionTypes::Type*/ PermissionUtilsInternal::isEligibleToJoinAs(QAbstr } } - if (!hasAnyJoinPermission) - return PermissionTypes::Type::Member; - if (tmpRes.contains(PermissionTypes::Type::TokenMaster)) return PermissionTypes::Type::TokenMaster; if (tmpRes.contains(PermissionTypes::Type::Admin)) return PermissionTypes::Type::Admin; - if (tmpRes.contains(PermissionTypes::Type::Member)) + if (tmpRes.contains(PermissionTypes::Type::Member) || !hasMemberPermission) return PermissionTypes::Type::Member; return PermissionTypes::Type::None; diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index 215650bfb6..4caa7fe830 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -621,6 +621,7 @@ QtObject { readonly property string image: model.image readonly property bool joined: model.joined readonly property bool amIBanned: model.amIBanned + readonly property string introMessage: model.introMessage // add others when needed.. } } diff --git a/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml b/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml index e0f366e635..55adaf9106 100644 --- a/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml @@ -34,6 +34,7 @@ Control { required property int /*PermissionTypes.Type*/ eligibleToJoinAs property bool requirementsCheckPending: false + property bool joinPermissionsCheckSuccessful required property string communityId required property string communityName @@ -101,11 +102,27 @@ Control { onClicked: root.close() } - readonly property var saveButton: StatusButton { - enabled: d.dirty - type: d.lostCommunityPermission || d.lostChannelPermissions ? StatusBaseButton.Type.Danger : StatusBaseButton.Type.Normal - visible: root.isEditMode + readonly property string tooltipText: { + if (root.requirementsCheckPending) + return qsTr("Requirements check pending") + if (!root.joinPermissionsCheckSuccessful) + return qsTr("Checking permissions to join failed") + + return "" + } + + readonly property var saveButton: StatusButton { + visible: root.isEditMode + interactive: d.dirty && !root.requirementsCheckPending && root.joinPermissionsCheckSuccessful + loading: root.requirementsCheckPending + type: d.lostCommunityPermission || d.lostChannelPermissions ? StatusBaseButton.Type.Danger : StatusBaseButton.Type.Normal + tooltip.text: { + if (interactive) + return "" + + return d.tooltipText + } text: { if (d.lostCommunityPermission) { return qsTr("Save changes & leave %1").arg(root.communityName) @@ -147,7 +164,14 @@ Control { readonly property var shareAddressesButton: StatusButton { visible: !root.isEditMode - enabled: root.eligibleToJoinAs !== PermissionTypes.Type.None + interactive: root.eligibleToJoinAs !== PermissionTypes.Type.None && root.joinPermissionsCheckSuccessful + loading: root.requirementsCheckPending + tooltip.text: { + if (interactive) + return "" + + return d.tooltipText + } text: { if (d.selectedSharedAddressesCount === root.totalNumOfAddressesForSharing) { return qsTr("Share all addresses to join") @@ -306,6 +330,7 @@ Control { assetsModel: root.assetsModel collectiblesModel: root.collectiblesModel requirementsCheckPending: root.requirementsCheckPending + joinPermissionsCheckSuccessful: root.joinPermissionsCheckSuccessful communityName: root.communityName communityIcon: root.communityIcon eligibleToJoinAs: root.eligibleToJoinAs diff --git a/ui/app/AppLayouts/Communities/panels/SharedAddressesPermissionsPanel.qml b/ui/app/AppLayouts/Communities/panels/SharedAddressesPermissionsPanel.qml index 426eefb298..87f6c2efae 100644 --- a/ui/app/AppLayouts/Communities/panels/SharedAddressesPermissionsPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/SharedAddressesPermissionsPanel.qml @@ -32,6 +32,7 @@ Rectangle { property var collectiblesModel property bool requirementsCheckPending + property bool joinPermissionsCheckSuccessful readonly property bool lostPermissionToJoin: d.lostPermissionToJoin readonly property bool lostChannelPermissions: d.lostChannelPermissions @@ -256,6 +257,7 @@ Rectangle { CommunityEligibilityTag { id: eligibilityHintBubble + visible: !root.requirementsCheckPending && root.joinPermissionsCheckSuccessful eligibleToJoinAs: root.eligibleToJoinAs isEditMode: root.isEditMode isDirty: root.isDirty diff --git a/ui/app/AppLayouts/stores/RootStore.qml b/ui/app/AppLayouts/stores/RootStore.qml index 3d4433cbf8..da73279bf6 100644 --- a/ui/app/AppLayouts/stores/RootStore.qml +++ b/ui/app/AppLayouts/stores/RootStore.qml @@ -18,6 +18,8 @@ QtObject { property var communitiesModuleInst: communitiesModule property bool newVersionAvailable: false readonly property bool requirementsCheckPending: communitiesModuleInst.requirementsCheckPending + readonly property bool joinPermissionsCheckSuccessful: communitiesModuleInst.joinPermissionsCheckSuccessful + readonly property bool channelsPermissionsCheckSuccessful: communitiesModuleInst.channelsPermissionsCheckSuccessful property string latestVersion property string downloadURL diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 5dbed4aedd..d70910d566 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -705,6 +705,7 @@ QtObject { id: dialogRoot requirementsCheckPending: root.rootStore.requirementsCheckPending + joinPermissionsCheckSuccessful: root.rootStore.joinPermissionsCheckSuccessful walletAccountsModel: root.rootStore.walletAccountsModel walletCollectiblesModel: WalletStore.RootStore.collectiblesStore.allCollectiblesModel @@ -952,7 +953,9 @@ QtObject { communityName: chatStore.sectionDetails.name communityIcon: chatStore.sectionDetails.image + requirementsCheckPending: root.rootStore.requirementsCheckPending + joinPermissionsCheckSuccessful: root.rootStore.joinPermissionsCheckSuccessful introMessage: chatStore.sectionDetails.introMessage diff --git a/ui/imports/shared/popups/CommunityMembershipSetupDialog.qml b/ui/imports/shared/popups/CommunityMembershipSetupDialog.qml index 634dd0f158..576dba0162 100644 --- a/ui/imports/shared/popups/CommunityMembershipSetupDialog.qml +++ b/ui/imports/shared/popups/CommunityMembershipSetupDialog.qml @@ -29,6 +29,8 @@ StatusStackModal { required property string communityIcon required property bool requirementsCheckPending + property bool joinPermissionsCheckSuccessful + property string introMessage property bool isInvitationPending: false @@ -84,12 +86,31 @@ StatusStackModal { rightButtons: [d.shareButton, finishButton] finishButton: StatusButton { - enabled: { - if (root.isInvitationPending || d.accessType !== Constants.communityChatOnRequestAccess) + interactive: { + if (root.isInvitationPending) + return true + + if (root.requirementsCheckPending || !root.joinPermissionsCheckSuccessful) + return false + + if (d.accessType !== Constants.communityChatOnRequestAccess) return true return d.eligibleToJoinAs !== PermissionTypes.Type.None } + loading: root.requirementsCheckPending && !root.isInvitationPending + tooltip.text: { + if (interactive) + return "" + + if (root.requirementsCheckPending) + return qsTr("Requirements check pending") + + if (!root.joinPermissionsCheckSuccessful) + return qsTr("Checking permissions to join failed") + + return "" + } text: { if (root.isInvitationPending) { return qsTr("Cancel Membership Request") @@ -269,7 +290,7 @@ StatusStackModal { d.selectedSharedAddressesMap = tmpMap } - // Returns an object containing all selected addresses and selected airdrop address.s + // Returns an object containing all selected addresses and selected airdrop address function getSelectedAddresses() { const result = {addresses: [], airdropAddress: ""} for (const [key, value] of d.selectedSharedAddressesMap) { @@ -341,6 +362,7 @@ StatusStackModal { communityName: root.communityName communityIcon: root.communityIcon requirementsCheckPending: root.requirementsCheckPending + joinPermissionsCheckSuccessful: root.joinPermissionsCheckSuccessful walletAccountsModel: d.initialAddressesModel @@ -416,36 +438,47 @@ StatusStackModal { } stackItems: [ - StatusScrollView { - id: scrollView - contentWidth: availableWidth + Item { + implicitHeight: scrollView.contentHeight + scrollView.bottomPadding + eligibilityTag.anchors.bottomMargin - ColumnLayout { - spacing: 24 - width: scrollView.availableWidth + StatusScrollView { + id: scrollView + anchors.fill: parent + contentWidth: availableWidth + bottomPadding: 80 - StatusRoundedImage { - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: 64 - Layout.preferredHeight: Layout.preferredWidth - visible: ((image.status == Image.Loading) || - (image.status == Image.Ready)) && - !image.isError - image.source: root.communityIcon + ColumnLayout { + spacing: Style.current.bigPadding + width: parent.width + + StatusRoundedImage { + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 64 + Layout.preferredHeight: Layout.preferredWidth + visible: ((image.status == Image.Loading) || + (image.status == Image.Ready)) && + !image.isError + image.source: root.communityIcon + } + + StatusBaseText { + Layout.fillWidth: true + text: root.introMessage || qsTr("Community %1 has no intro message...").arg(root.communityName) + color: Theme.palette.directColor1 + wrapMode: Text.WordWrap + } } + } - StatusBaseText { - Layout.fillWidth: true - text: root.introMessage || qsTr("Community %1 has no intro message...").arg(root.communityName) - color: Theme.palette.directColor1 - wrapMode: Text.WordWrap - } - - CommunityEligibilityTag { - Layout.alignment: Qt.AlignHCenter - eligibleToJoinAs: d.eligibleToJoinAs - visible: !root.isInvitationPending && d.accessType === Constants.communityChatOnRequestAccess - } + CommunityEligibilityTag { + id: eligibilityTag + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: Style.current.bigPadding + eligibleToJoinAs: d.eligibleToJoinAs + isEditMode: root.isEditMode + visible: !root.isInvitationPending && !root.requirementsCheckPending && root.joinPermissionsCheckSuccessful && + d.accessType === Constants.communityChatOnRequestAccess } } ]