diff --git a/storybook/pages/CommunitiesPortalDummyModel.qml b/storybook/pages/CommunitiesPortalDummyModel.qml index 863aae04a0..05c88ebb1a 100644 --- a/storybook/pages/CommunitiesPortalDummyModel.qml +++ b/storybook/pages/CommunitiesPortalDummyModel.qml @@ -3,6 +3,9 @@ import QtQuick 2.14 import Models 1.0 ListModel { + + readonly property var emptyModel: ListModel {} + Component.onCompleted: append([ { featured: true, @@ -97,8 +100,6 @@ ListModel { ]), permissionsModel: PermissionsModel.moreThanTwoInitialShortPermissionsModel, allTokenRequirementsMet: false - - }, { featured: false, @@ -136,7 +137,7 @@ ListModel { popularity: 4, available: true, tags: JSON.stringify([]), - permissionsModel: PermissionsModel.threeShortPermissionsModelData, + permissionsModel: PermissionsModel.channelsOnlyPermissionsModelNotMet, allTokenRequirementsMet: false }, { @@ -183,7 +184,7 @@ ListModel { popularity: 4, available: true, tags: JSON.stringify([]), - permissionsModel: PermissionsModel.threeShortPermissionsModel, + permissionsModel: PermissionsModel.channelsOnlyPermissionsModel, allTokenRequirementsMet: false }, { @@ -233,7 +234,8 @@ ListModel { activeMembers: 0, popularity: 4, available: true, - tags: JSON.stringify([]) + tags: JSON.stringify([]), + permissionsModel: emptyModel } ]) } diff --git a/storybook/pages/CommunitiesPortalLayoutPage.qml b/storybook/pages/CommunitiesPortalLayoutPage.qml index 579fe53014..03e967b2a1 100644 --- a/storybook/pages/CommunitiesPortalLayoutPage.qml +++ b/storybook/pages/CommunitiesPortalLayoutPage.qml @@ -32,9 +32,10 @@ SplitView { SplitView.fillHeight: true assetsModel: AssetsModel {} - collectiblesModel: CollectiblesModel {} + collectiblesModel: CollectiblesModel {} communitiesStore: CommunitiesStore { readonly property int unreadNotificationsCount: 42 + readonly property bool createCommunityEnabled: true readonly property string communityTags: ModelsData.communityTags readonly property var curatedCommunitiesModel: SortFilterProxyModel { diff --git a/storybook/src/Models/PermissionsModel.qml b/storybook/src/Models/PermissionsModel.qml index a90b8a2945..eca6cc5792 100644 --- a/storybook/src/Models/PermissionsModel.qml +++ b/storybook/src/Models/PermissionsModel.qml @@ -92,7 +92,8 @@ QtObject { channelsListModel: root.createChannelsModel2(), permissionType: PermissionTypes.Type.Member, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: false } ] @@ -103,7 +104,8 @@ QtObject { channelsListModel: root.createChannelsModel(), permissionType: PermissionTypes.Type.Admin, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: true } ] @@ -113,21 +115,24 @@ QtObject { channelsListModel: root.createChannelsModel2(), permissionType: PermissionTypes.Type.Member, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: false }, { holdingsListModel: root.createHoldingsModel2(), channelsListModel: root.createChannelsModel2(), permissionType: PermissionTypes.Type.Member, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: true }, { holdingsListModel: root.createHoldingsModel1(), channelsListModel: root.createChannelsModel2(), permissionType: PermissionTypes.Type.Member, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: false } ] @@ -137,14 +142,16 @@ QtObject { channelsListModel: root.createChannelsModel1(), permissionType: PermissionTypes.Type.Admin, permissionState: PermissionTypes.State.Approved, - isPrivate: true + isPrivate: true, + tokenCriteriaMet: false }, { holdingsListModel: root.createHoldingsModel2(), channelsListModel: root.createChannelsModel2(), permissionType: PermissionTypes.Type.Member, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: true } ] @@ -154,14 +161,16 @@ QtObject { channelsListModel: root.createChannelsModel1(), permissionType: PermissionTypes.Type.Admin, permissionState: PermissionTypes.State.Approved, - isPrivate: true + isPrivate: true, + tokenCriteriaMet: true }, { holdingsListModel: root.createHoldingsModel4(), channelsListModel: root.createChannelsModel2(), permissionType: PermissionTypes.Type.Member, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: false } ] @@ -171,21 +180,24 @@ QtObject { channelsListModel: root.createChannelsModel1(), permissionType: PermissionTypes.Type.Admin, permissionState: PermissionTypes.State.Approved, - isPrivate: true + isPrivate: true, + tokenCriteriaMet: false }, { holdingsListModel: root.createHoldingsModel1b(), channelsListModel: root.createChannelsModel2(), permissionType: PermissionTypes.Type.Member, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: false }, { holdingsListModel: root.createHoldingsModel2(), channelsListModel: root.createChannelsModel2(), permissionType: PermissionTypes.Type.Member, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: false } ] @@ -195,28 +207,32 @@ QtObject { channelsListModel: root.createChannelsModel1(), permissionType: PermissionTypes.Type.Admin, permissionState: PermissionTypes.State.Approved, - isPrivate: true + isPrivate: true, + tokenCriteriaMet: false }, { holdingsListModel: root.createHoldingsModel2(), channelsListModel: root.createChannelsModel2(), permissionType: PermissionTypes.Type.Member, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: false }, { holdingsListModel: root.createHoldingsModel3(), channelsListModel: root.createChannelsModel2(), permissionType: PermissionTypes.Type.Member, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: true }, { holdingsListModel: root.createHoldingsModel5(), channelsListModel: root.createChannelsModel2(), permissionType: PermissionTypes.Type.Member, permissionState: PermissionTypes.State.Approved, - isPrivate: false + isPrivate: false, + tokenCriteriaMet: true } ] diff --git a/ui/StatusQ/include/StatusQ/permissionutilsinternal.h b/ui/StatusQ/include/StatusQ/permissionutilsinternal.h index cab9226a3c..12379d6a0e 100644 --- a/ui/StatusQ/include/StatusQ/permissionutilsinternal.h +++ b/ui/StatusQ/include/StatusQ/permissionutilsinternal.h @@ -3,6 +3,8 @@ #include #include +#include + class QAbstractItemModel; namespace PermissionTypes { @@ -27,9 +29,15 @@ public: //!< Check whether the user can join the community and under which (highest possible) role //!< @return either: - //! - `NoPermissions` if the permissionsModel is empty or malformed - //! - `Member` if no such join permission(s) exist in the permissionsModel (e.g. when it has channel only permissions) + //! - `NoPermissions` if the permissionsModel is empty or malformed, or has no join type permissions //! - if satisfied: `TokenMaster`, `Admin`, or `Member`, in this order of relevance + //! - `Member` if no such join permission(s) exist in the permissionsModel (e.g. when it has channel only permissions) //! - `None` if no permission to join is satisfied (user can't join at all) Q_INVOKABLE int /*PermissionTypes::Type*/ isEligibleToJoinAs(QAbstractItemModel *permissionsModel) const; + + //!< @return true when the @p permissionsModel contains some kind of "join" permission; false when the community is free to join + Q_INVOKABLE bool isTokenGatedCommunity(QAbstractItemModel *permissionsModel) const; + +private: + std::optional isEligibleToJoinAsInternal(QAbstractItemModel *permissionsModel) const; }; diff --git a/ui/StatusQ/src/permissionutilsinternal.cpp b/ui/StatusQ/src/permissionutilsinternal.cpp index b126e98767..4f67a27daa 100644 --- a/ui/StatusQ/src/permissionutilsinternal.cpp +++ b/ui/StatusQ/src/permissionutilsinternal.cpp @@ -143,7 +143,8 @@ QJsonArray PermissionUtilsInternal::getUniquePermissionChannels(QAbstractItemMod return result; } -int /*PermissionTypes::Type*/ PermissionUtilsInternal::isEligibleToJoinAs(QAbstractItemModel *permissionsModel) const +std::optional PermissionUtilsInternal::isEligibleToJoinAsInternal( + QAbstractItemModel *permissionsModel) const { if (!permissionsModel) return PermissionTypes::Type::NoPermissions; @@ -160,8 +161,6 @@ int /*PermissionTypes::Type*/ PermissionUtilsInternal::isEligibleToJoinAs(QAbstr return PermissionTypes::Type::NoPermissions; } - QSet tmpRes; - bool hasMemberPermission{false}; constexpr auto isJoinTypePermission = [](PermissionTypes::Type type) { return type == PermissionTypes::Type::TokenMaster || type == PermissionTypes::Type::Admin || @@ -173,6 +172,8 @@ int /*PermissionTypes::Type*/ PermissionUtilsInternal::isEligibleToJoinAs(QAbstr }; const auto permissionsCount = permissionsModel->rowCount(); + bool hasMemberPermission{false}; + QSet tmpRes; for (int i = 0; i < permissionsCount; i++) { const auto permissionType = static_cast(permissionsModel->data(permissionsModel->index(i, 0), permissionTypeRole).toInt()); if (isJoinTypePermission(permissionType)) { @@ -191,10 +192,24 @@ int /*PermissionTypes::Type*/ PermissionUtilsInternal::isEligibleToJoinAs(QAbstr if (tmpRes.contains(PermissionTypes::Type::Admin)) return PermissionTypes::Type::Admin; - if (tmpRes.contains(PermissionTypes::Type::Member) || !hasMemberPermission) + if (tmpRes.contains(PermissionTypes::Type::Member)) return PermissionTypes::Type::Member; - return PermissionTypes::Type::None; + if (!hasMemberPermission) + return {}; // no join permissions -> free to join + + return PermissionTypes::Type::None; // not allowed to join due to permissions not satisfied +} + +int /*PermissionTypes::Type*/ PermissionUtilsInternal::isEligibleToJoinAs(QAbstractItemModel *permissionsModel) const +{ + return isEligibleToJoinAsInternal(permissionsModel).value_or(PermissionTypes::Type::Member); +} + +bool PermissionUtilsInternal::isTokenGatedCommunity(QAbstractItemModel *permissionsModel) const +{ + const auto result = isEligibleToJoinAsInternal(permissionsModel); + return result && *result > PermissionTypes::Type::NoPermissions; } QVariantMap PermissionUtilsInternal::getTokenByKey(QAbstractItemModel *model, diff --git a/ui/app/AppLayouts/Communities/CommunitiesPortalLayout.qml b/ui/app/AppLayouts/Communities/CommunitiesPortalLayout.qml index 4ade03c4a9..269cf5609b 100644 --- a/ui/app/AppLayouts/Communities/CommunitiesPortalLayout.qml +++ b/ui/app/AppLayouts/Communities/CommunitiesPortalLayout.qml @@ -194,7 +194,6 @@ StatusSectionLayout { visible: (d.searchMode && filteredCommunitiesModel.count === 0) || communitiesGrid.isEmpty text: qsTr("No communities found") color: Theme.palette.baseColor1 - font.pixelSize: 15 } } } diff --git a/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml b/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml index 39cb7906d0..b192b3b20a 100644 --- a/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml +++ b/ui/app/AppLayouts/Communities/helpers/PermissionsHelpers.qml @@ -63,6 +63,10 @@ QtObject { return Internal.PermissionUtils.isEligibleToJoinAs(model) } + function isTokenGatedCommunity(model) { + return Internal.PermissionUtils.isTokenGatedCommunity(model) + } + function setHoldingsTextFormat(type, name, amount, decimals) { if (typeof amount === "string") { amount = AmountsArithmetic.toNumber(AmountsArithmetic.fromString(amount), decimals) diff --git a/ui/app/AppLayouts/Communities/views/CommunitiesGridView.qml b/ui/app/AppLayouts/Communities/views/CommunitiesGridView.qml index 9cbafcbbae..e4dc9389d0 100644 --- a/ui/app/AppLayouts/Communities/views/CommunitiesGridView.qml +++ b/ui/app/AppLayouts/Communities/views/CommunitiesGridView.qml @@ -11,6 +11,7 @@ import utils 1.0 import SortFilterProxyModel 0.2 import AppLayouts.Communities.controls 1.0 +import AppLayouts.Communities.helpers 1.0 StatusScrollView { id: root @@ -65,7 +66,7 @@ StatusScrollView { readonly property string tags: model.tags readonly property var permissionsList: model.permissionsModel - readonly property bool requirementsMet: !!model.allTokenRequirementsMet ? model.allTokenRequirementsMet : false + readonly property bool isTokenGatedCommunity: PermissionsHelpers.isTokenGatedCommunity(permissionsList) JSONListModel { id: tagsJson @@ -85,15 +86,24 @@ StatusScrollView { categories: tagsJson.model memberCountVisible: model.joined || !model.encrypted - // Community restrictions - rigthHeaderComponent: PermissionsRow { - visible: !!card.permissionsList && card.permissionsList.count > 0 - assetsModel: root.assetsModel - collectiblesModel: root.collectiblesModel - model: card.permissionsList - requirementsMet: card.requirementsMet - overlappingBorder: 0 + Binding { + target: card + property: "rigthHeaderComponent" + when: card.isTokenGatedCommunity + value: Component { + PermissionsRow { + readonly property int eligibleToJoinAs: PermissionsHelpers.isEligibleToJoinAs(card.permissionsList) + + assetsModel: root.assetsModel + collectiblesModel: root.collectiblesModel + model: card.permissionsList + requirementsMet: eligibleToJoinAs === PermissionTypes.Type.Member + || eligibleToJoinAs === PermissionTypes.Type.Admin + || eligibleToJoinAs === PermissionTypes.Type.Owner + overlappingBorder: 0 + } + } } onClicked: root.cardClicked(communityId)