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
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
}

View File

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

View File

@ -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

View File

@ -8,7 +8,7 @@
#include <algorithm>
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<PermissionTypes::Type> 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<PermissionTypes::Type>(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;

View File

@ -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..
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <b>%1</b> has no intro message...").arg(root.communityName)
color: Theme.palette.directColor1
wrapMode: Text.WordWrap
}
}
}
StatusBaseText {
Layout.fillWidth: true
text: root.introMessage || qsTr("Community <b>%1</b> 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
}
}
]