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
This commit is contained in:
Lukáš Tinkl 2024-04-25 11:47:40 +02:00 committed by Lukáš Tinkl
parent 78acdca225
commit 0c8231a6b6
10 changed files with 122 additions and 43 deletions

View File

@ -55,6 +55,7 @@ SplitView {
isInvitationPending: ctrlIsInvitationPending.checked isInvitationPending: ctrlIsInvitationPending.checked
requirementsCheckPending: ctrlRequirementsCheckPending.checked requirementsCheckPending: ctrlRequirementsCheckPending.checked
joinPermissionsCheckSuccessful: ctrlJoinPermissionsCheckSuccessful.checked
walletAccountsModel: WalletAccountsModel {} walletAccountsModel: WalletAccountsModel {}
walletAssetsModel: root.walletAssetStore.groupedAccountAssetsModel walletAssetsModel: root.walletAssetStore.groupedAccountAssetsModel
@ -218,6 +219,14 @@ Nemo enim 😋 ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
text: "Requirements check pending" text: "Requirements check pending"
} }
CheckBox {
Layout.leftMargin: 12
id: ctrlJoinPermissionsCheckSuccessful
visible: !ctrlIsInvitationPending.checked
text: "Join permission successful"
checked: true
}
Item { Item {
Layout.fillHeight: true Layout.fillHeight: true
} }

View File

@ -20,6 +20,7 @@ ToolTip {
implicitWidth: Math.min(maxWidth, implicitContentWidth + 16) implicitWidth: Math.min(maxWidth, implicitContentWidth + 16)
padding: 8 padding: 8
margins: 8
delay: 200 delay: 200
background: Item { background: Item {
id: statusToolTipBackground id: statusToolTipBackground

View File

@ -21,10 +21,11 @@ Dialog {
\qmlproperty color backgroundColor \qmlproperty color backgroundColor
This property decides the modal background color This property decides the modal background color
*/ */
property string backgroundColor: Theme.palette.statusModal.backgroundColor property color backgroundColor: Theme.palette.statusModal.backgroundColor
/*! /*!
\qmlproperty closeHandler \qmlproperty closeHandler
This property decides the action to be performed when the close button is clicked. It allows to define 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 property var closeHandler: root.close

View File

@ -8,7 +8,7 @@
#include <algorithm> #include <algorithm>
namespace { namespace {
int roleByName(QAbstractItemModel* model, const QString &roleName) constexpr int roleByName(QAbstractItemModel* model, const QString &roleName)
{ {
if (!model) if (!model)
return -1; return -1;
@ -161,18 +161,23 @@ int /*PermissionTypes::Type*/ PermissionUtilsInternal::isEligibleToJoinAs(QAbstr
} }
QSet<PermissionTypes::Type> tmpRes; QSet<PermissionTypes::Type> tmpRes;
bool hasAnyJoinPermission{false}; bool hasMemberPermission{false};
constexpr auto isJoinTypePermission = [](PermissionTypes::Type type) { constexpr auto isJoinTypePermission = [](PermissionTypes::Type type) {
return type == PermissionTypes::Type::TokenMaster || return type == PermissionTypes::Type::TokenMaster ||
type == PermissionTypes::Type::Admin || type == PermissionTypes::Type::Admin ||
type == PermissionTypes::Type::Member; type == PermissionTypes::Type::Member;
}; };
constexpr auto isMemberPermission = [](PermissionTypes::Type type) {
return type == PermissionTypes::Type::Member;
};
const auto permissionsCount = permissionsModel->rowCount(); const auto permissionsCount = permissionsModel->rowCount();
for (int i = 0; i < permissionsCount; i++) { for (int i = 0; i < permissionsCount; i++) {
const auto permissionType = static_cast<PermissionTypes::Type>(permissionsModel->data(permissionsModel->index(i, 0), permissionTypeRole).toInt()); const auto permissionType = static_cast<PermissionTypes::Type>(permissionsModel->data(permissionsModel->index(i, 0), permissionTypeRole).toInt());
if (isJoinTypePermission(permissionType)) { if (isJoinTypePermission(permissionType)) {
hasAnyJoinPermission = true; if (isMemberPermission(permissionType))
hasMemberPermission = true;
const auto tokenCriteriaMet = permissionsModel->data(permissionsModel->index(i, 0), tokenCriteriaMetRole).toBool(); const auto tokenCriteriaMet = permissionsModel->data(permissionsModel->index(i, 0), tokenCriteriaMetRole).toBool();
if (tokenCriteriaMet) { if (tokenCriteriaMet) {
tmpRes.insert(permissionType); 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)) if (tmpRes.contains(PermissionTypes::Type::TokenMaster))
return PermissionTypes::Type::TokenMaster; return PermissionTypes::Type::TokenMaster;
if (tmpRes.contains(PermissionTypes::Type::Admin)) if (tmpRes.contains(PermissionTypes::Type::Admin))
return 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::Member;
return PermissionTypes::Type::None; return PermissionTypes::Type::None;

View File

@ -621,6 +621,7 @@ QtObject {
readonly property string image: model.image readonly property string image: model.image
readonly property bool joined: model.joined readonly property bool joined: model.joined
readonly property bool amIBanned: model.amIBanned readonly property bool amIBanned: model.amIBanned
readonly property string introMessage: model.introMessage
// add others when needed.. // add others when needed..
} }
} }

View File

@ -34,6 +34,7 @@ Control {
required property int /*PermissionTypes.Type*/ eligibleToJoinAs required property int /*PermissionTypes.Type*/ eligibleToJoinAs
property bool requirementsCheckPending: false property bool requirementsCheckPending: false
property bool joinPermissionsCheckSuccessful
required property string communityId required property string communityId
required property string communityName required property string communityName
@ -101,11 +102,27 @@ Control {
onClicked: root.close() onClicked: root.close()
} }
readonly property var saveButton: StatusButton { readonly property string tooltipText: {
enabled: d.dirty if (root.requirementsCheckPending)
type: d.lostCommunityPermission || d.lostChannelPermissions ? StatusBaseButton.Type.Danger : StatusBaseButton.Type.Normal return qsTr("Requirements check pending")
visible: root.isEditMode
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: { text: {
if (d.lostCommunityPermission) { if (d.lostCommunityPermission) {
return qsTr("Save changes & leave %1").arg(root.communityName) return qsTr("Save changes & leave %1").arg(root.communityName)
@ -147,7 +164,14 @@ Control {
readonly property var shareAddressesButton: StatusButton { readonly property var shareAddressesButton: StatusButton {
visible: !root.isEditMode 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: { text: {
if (d.selectedSharedAddressesCount === root.totalNumOfAddressesForSharing) { if (d.selectedSharedAddressesCount === root.totalNumOfAddressesForSharing) {
return qsTr("Share all addresses to join") return qsTr("Share all addresses to join")
@ -306,6 +330,7 @@ Control {
assetsModel: root.assetsModel assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel collectiblesModel: root.collectiblesModel
requirementsCheckPending: root.requirementsCheckPending requirementsCheckPending: root.requirementsCheckPending
joinPermissionsCheckSuccessful: root.joinPermissionsCheckSuccessful
communityName: root.communityName communityName: root.communityName
communityIcon: root.communityIcon communityIcon: root.communityIcon
eligibleToJoinAs: root.eligibleToJoinAs eligibleToJoinAs: root.eligibleToJoinAs

View File

@ -32,6 +32,7 @@ Rectangle {
property var collectiblesModel property var collectiblesModel
property bool requirementsCheckPending property bool requirementsCheckPending
property bool joinPermissionsCheckSuccessful
readonly property bool lostPermissionToJoin: d.lostPermissionToJoin readonly property bool lostPermissionToJoin: d.lostPermissionToJoin
readonly property bool lostChannelPermissions: d.lostChannelPermissions readonly property bool lostChannelPermissions: d.lostChannelPermissions
@ -256,6 +257,7 @@ Rectangle {
CommunityEligibilityTag { CommunityEligibilityTag {
id: eligibilityHintBubble id: eligibilityHintBubble
visible: !root.requirementsCheckPending && root.joinPermissionsCheckSuccessful
eligibleToJoinAs: root.eligibleToJoinAs eligibleToJoinAs: root.eligibleToJoinAs
isEditMode: root.isEditMode isEditMode: root.isEditMode
isDirty: root.isDirty isDirty: root.isDirty

View File

@ -18,6 +18,8 @@ QtObject {
property var communitiesModuleInst: communitiesModule property var communitiesModuleInst: communitiesModule
property bool newVersionAvailable: false property bool newVersionAvailable: false
readonly property bool requirementsCheckPending: communitiesModuleInst.requirementsCheckPending readonly property bool requirementsCheckPending: communitiesModuleInst.requirementsCheckPending
readonly property bool joinPermissionsCheckSuccessful: communitiesModuleInst.joinPermissionsCheckSuccessful
readonly property bool channelsPermissionsCheckSuccessful: communitiesModuleInst.channelsPermissionsCheckSuccessful
property string latestVersion property string latestVersion
property string downloadURL property string downloadURL

View File

@ -705,6 +705,7 @@ QtObject {
id: dialogRoot id: dialogRoot
requirementsCheckPending: root.rootStore.requirementsCheckPending requirementsCheckPending: root.rootStore.requirementsCheckPending
joinPermissionsCheckSuccessful: root.rootStore.joinPermissionsCheckSuccessful
walletAccountsModel: root.rootStore.walletAccountsModel walletAccountsModel: root.rootStore.walletAccountsModel
walletCollectiblesModel: WalletStore.RootStore.collectiblesStore.allCollectiblesModel walletCollectiblesModel: WalletStore.RootStore.collectiblesStore.allCollectiblesModel
@ -952,7 +953,9 @@ QtObject {
communityName: chatStore.sectionDetails.name communityName: chatStore.sectionDetails.name
communityIcon: chatStore.sectionDetails.image communityIcon: chatStore.sectionDetails.image
requirementsCheckPending: root.rootStore.requirementsCheckPending requirementsCheckPending: root.rootStore.requirementsCheckPending
joinPermissionsCheckSuccessful: root.rootStore.joinPermissionsCheckSuccessful
introMessage: chatStore.sectionDetails.introMessage introMessage: chatStore.sectionDetails.introMessage

View File

@ -29,6 +29,8 @@ StatusStackModal {
required property string communityIcon required property string communityIcon
required property bool requirementsCheckPending required property bool requirementsCheckPending
property bool joinPermissionsCheckSuccessful
property string introMessage property string introMessage
property bool isInvitationPending: false property bool isInvitationPending: false
@ -84,12 +86,31 @@ StatusStackModal {
rightButtons: [d.shareButton, finishButton] rightButtons: [d.shareButton, finishButton]
finishButton: StatusButton { finishButton: StatusButton {
enabled: { interactive: {
if (root.isInvitationPending || d.accessType !== Constants.communityChatOnRequestAccess) if (root.isInvitationPending)
return true
if (root.requirementsCheckPending || !root.joinPermissionsCheckSuccessful)
return false
if (d.accessType !== Constants.communityChatOnRequestAccess)
return true return true
return d.eligibleToJoinAs !== PermissionTypes.Type.None 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: { text: {
if (root.isInvitationPending) { if (root.isInvitationPending) {
return qsTr("Cancel Membership Request") return qsTr("Cancel Membership Request")
@ -269,7 +290,7 @@ StatusStackModal {
d.selectedSharedAddressesMap = tmpMap 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() { function getSelectedAddresses() {
const result = {addresses: [], airdropAddress: ""} const result = {addresses: [], airdropAddress: ""}
for (const [key, value] of d.selectedSharedAddressesMap) { for (const [key, value] of d.selectedSharedAddressesMap) {
@ -341,6 +362,7 @@ StatusStackModal {
communityName: root.communityName communityName: root.communityName
communityIcon: root.communityIcon communityIcon: root.communityIcon
requirementsCheckPending: root.requirementsCheckPending requirementsCheckPending: root.requirementsCheckPending
joinPermissionsCheckSuccessful: root.joinPermissionsCheckSuccessful
walletAccountsModel: d.initialAddressesModel walletAccountsModel: d.initialAddressesModel
@ -416,13 +438,18 @@ StatusStackModal {
} }
stackItems: [ stackItems: [
Item {
implicitHeight: scrollView.contentHeight + scrollView.bottomPadding + eligibilityTag.anchors.bottomMargin
StatusScrollView { StatusScrollView {
id: scrollView id: scrollView
anchors.fill: parent
contentWidth: availableWidth contentWidth: availableWidth
bottomPadding: 80
ColumnLayout { ColumnLayout {
spacing: 24 spacing: Style.current.bigPadding
width: scrollView.availableWidth width: parent.width
StatusRoundedImage { StatusRoundedImage {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@ -440,12 +467,18 @@ StatusStackModal {
color: Theme.palette.directColor1 color: Theme.palette.directColor1
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
}
}
CommunityEligibilityTag { CommunityEligibilityTag {
Layout.alignment: Qt.AlignHCenter id: eligibilityTag
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.current.bigPadding
eligibleToJoinAs: d.eligibleToJoinAs eligibleToJoinAs: d.eligibleToJoinAs
visible: !root.isInvitationPending && d.accessType === Constants.communityChatOnRequestAccess isEditMode: root.isEditMode
} visible: !root.isInvitationPending && !root.requirementsCheckPending && root.joinPermissionsCheckSuccessful &&
d.accessType === Constants.communityChatOnRequestAccess
} }
} }
] ]