fix(community): Token gating info when permission not met and set to private

- hide the permission from the `PermissionsRow` when it's set to private
and the conditions are not met
- display a tooltip "(Not) eligible to join" over the lock icon
- show the same info in both community portal and profile dialog's
community showcase tab
- speedup searching/filtering in the community portal
- fixup and extend the SB pages to demonstrate the new behavior

Fixes #14747
This commit is contained in:
Lukáš Tinkl 2024-10-03 18:50:43 +02:00 committed by Lukáš Tinkl
parent fe9308fa1e
commit 235162dc01
8 changed files with 124 additions and 61 deletions

View File

@ -8,14 +8,14 @@ ListModel {
Component.onCompleted: append([ Component.onCompleted: append([
{ {
featured: true, featured: false,
id: "id1", id: "id1",
loaded: true, loaded: true,
icon: ModelsData.icons.status, icon: ModelsData.icons.status,
banner: ModelsData.banners.status, banner: ModelsData.banners.status,
color: "blue", color: "blue",
name: "Status.im", name: "Status.app",
description: "Your portal to Web3. Secure wallet. dApp browser. Private messaging. All-in-one.", description: "Your portal to Web3. Secure wallet. Private messaging. Requires secret tokens to join",
members: 130, members: 130,
activeMembers: 61, activeMembers: 61,
popularity: 4, popularity: 4,
@ -42,8 +42,8 @@ ListModel {
"emoji": "💼", "emoji": "💼",
}, },
]), ]),
permissionsModel: PermissionsModel.shortPermissionsModel, permissionsModel: PermissionsModel.privatePermissionsMemberNotMetModel,
allTokenRequirementsMet: true allTokenRequirementsMet: false
}, },
{ {
featured: true, featured: true,
@ -168,7 +168,7 @@ ListModel {
}, },
]), ]),
permissionsModel: PermissionsModel.threeShortPermissionsModel, permissionsModel: PermissionsModel.threeShortPermissionsModel,
allTokenRequirementsMet: true allTokenRequirementsMet: false
}, },
{ {
featured: false, featured: false,
@ -218,7 +218,7 @@ ListModel {
"emoji": "💼", "emoji": "💼",
}, },
]), ]),
permissionsModel: PermissionsModel.longPermissionsModel, permissionsModel: PermissionsModel.twoLongPermissionsModel,
allTokenRequirementsMet: true allTokenRequirementsMet: true
}, },
{ {
@ -235,7 +235,8 @@ ListModel {
popularity: 4, popularity: 4,
available: true, available: true,
tags: JSON.stringify([]), tags: JSON.stringify([]),
permissionsModel: emptyModel permissionsModel: emptyModel,
allTokenRequirementsMet: false
} }
]) ])
} }

View File

@ -22,7 +22,7 @@ SplitView {
Popups { Popups {
popupParent: root popupParent: root
sharedRootStore: SharedStores.RootStore {} sharedRootStore: SharedStores.RootStore {}
rootStore: AppLayoutStores.RootStore rootStore: AppLayoutStores.RootStore {}
communityTokensStore: SharedStores.CommunityTokensStore {} communityTokensStore: SharedStores.CommunityTokensStore {}
} }

View File

@ -101,6 +101,22 @@ SplitView {
requirementsMet: permissionsMetCheckEditor.checked requirementsMet: permissionsMetCheckEditor.checked
} }
Label {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
text: "Private + unmet permissions:"
}
PermissionsRow {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.bottomMargin: spacing
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
model: PermissionsModel.privatePermissionsMemberNotMetModel
requirementsMet: false
}
Label { Label {
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
@ -139,7 +155,7 @@ SplitView {
} }
Label { Label {
text: "Row heigh:" text: "Row height:"
} }
Slider { Slider {

View File

@ -115,7 +115,7 @@ ListModel {
members: [{ pubKey: "0xdeadbeef" }], members: [{ pubKey: "0xdeadbeef" }],
membersCount: 1, membersCount: 1,
loading: false, loading: false,
permissionsModel: null, permissionsModel: PermissionsModel.privatePermissionsMemberNotMetModel,
allTokenRequirementsMet: false allTokenRequirementsMet: false
}, },
{ {

View File

@ -85,6 +85,24 @@ QtObject {
} }
] ]
readonly property var privatePermissionsMemberModelNotMetData: [
{
holdingsListModel: root.createHoldingsModel4(),
channelsListModel: root.createChannelsModel1(),
permissionType: PermissionTypes.Type.Member,
permissionState: PermissionTypes.State.Approved,
isPrivate: true,
tokenCriteriaMet: false
},
{
holdingsListModel: root.createHoldingsModel2(),
channelsListModel: root.createChannelsModel2(),
permissionType: PermissionTypes.Type.Member,
permissionState: PermissionTypes.State.Approved,
isPrivate: true,
tokenCriteriaMet: false
}
]
readonly property var shortPermissionsModelData: [ readonly property var shortPermissionsModelData: [
{ {
@ -554,6 +572,17 @@ QtObject {
} }
} }
readonly property ListModel privatePermissionsMemberNotMetModel: ListModel {
readonly property ModelChangeGuard guard: ModelChangeGuard {
model: root.privatePermissionsMemberNotMetModel
}
Component.onCompleted: {
append(privatePermissionsMemberModelNotMetData)
guard.enabled = true
}
}
readonly property var shortPermissionsModel: ListModel { readonly property var shortPermissionsModel: ListModel {
readonly property ModelChangeGuard guard: ModelChangeGuard { readonly property ModelChangeGuard guard: ModelChangeGuard {
model: root.shortPermissionsModel model: root.shortPermissionsModel

View File

@ -1,10 +1,10 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.15
import StatusQ 0.1 import StatusQ 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Popups 0.1 import StatusQ.Popups 0.1
@ -71,21 +71,19 @@ StatusSectionLayout {
sourceModel: root.communitiesStore.curatedCommunitiesModel sourceModel: root.communitiesStore.curatedCommunitiesModel
filters: [ filters: [
ExpressionFilter { SQUtils.SearchFilter {
enabled: d.searchMode roleName: "name"
expression: { searchPhrase: searcher.text
searcher.text
return name.toLowerCase().includes(searcher.text.toLowerCase())
}
}, },
ExpressionFilter { FastExpressionFilter {
expression: { expression: {
return filteredCommunitiesModel.selectedTagsPredicate(communityTags.selectedTagsNames, model.tags) return filteredCommunitiesModel.selectedTagsPredicate(communityTags.selectedTagsNames, model.tags)
} }
expectedRoles: ["tags"]
}, },
FastExpressionFilter { ValueFilter {
expression: !model.amIBanned roleName: "amIBanned"
expectedRoles: ["amIBanned"] value: false
} }
] ]
} }

View File

@ -1,7 +1,8 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.14 import QtQuick.Layouts 1.15
import StatusQ 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 import StatusQ.Core.Utils 0.1
@ -10,6 +11,8 @@ import StatusQ.Controls 0.1
import AppLayouts.Communities.views 1.0 import AppLayouts.Communities.views 1.0
import SortFilterProxyModel 0.2
/*! /*!
\qmltype PermissionsRow \qmltype PermissionsRow
\inherits Control \inherits Control
@ -17,7 +20,7 @@ import AppLayouts.Communities.views 1.0
\brief It is a permissions row control that provides information about community tokens permissions. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-controls2-control.html}{Control}. \brief It is a permissions row control that provides information about community tokens permissions. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-controls2-control.html}{Control}.
The \c PermissionsRow is the token permissions representation row component. The \c PermissionsRow is the token permissions representation row component.
It has different ui abreviations / permutations depending on the tokens and permissons the permissions model provides. It has different ui abreviations / permutations depending on the tokens and permissions the permissions model provides.
Example of how to use it: Example of how to use it:
\qml \qml
@ -112,8 +115,22 @@ Control {
property bool dotsVisible: false property bool dotsVisible: false
readonly property var filteredModel: SortFilterProxyModel {
sourceModel: root.model
filters: FastExpressionFilter {
expression: {
if (model.isPrivate) {
return model.tokenCriteriaMet
}
return true
}
expectedRoles: ["isPrivate", "tokenCriteriaMet"]
}
}
function buildShortModel(model) { function buildShortModel(model) {
shortModel.clear() shortModel.clear()
dotsVisible = false
if(!model) if(!model)
return return
@ -170,7 +187,7 @@ Control {
implicitHeight: 24 implicitHeight: 24
spacing: 4 spacing: 4
padding: 1 padding: 4
background: Rectangle { background: Rectangle {
color: root.backgroundColor color: root.backgroundColor
@ -179,19 +196,14 @@ Control {
} }
contentItem: RowLayout { contentItem: RowLayout {
id: container
anchors.centerIn: parent
anchors.margins: root.padding
spacing: root.spacing spacing: root.spacing
StatusIcon { StatusIcon {
Layout.preferredHeight: container.height - 6 Layout.fillHeight: true
Layout.preferredWidth: Layout.preferredHeight Layout.preferredWidth: height
Layout.leftMargin: 4
icon: root.requirementsMet ? "tiny/unlocked" : "tiny/locked" icon: root.requirementsMet ? "tiny/unlocked" : "tiny/locked"
color: Theme.palette.baseColor1 color: root.hovered ? Theme.palette.directColor1 : Theme.palette.baseColor1
} }
Repeater { Repeater {
@ -216,8 +228,8 @@ Control {
} }
StatusRoundedComponent { StatusRoundedComponent {
Layout.preferredHeight: container.height Layout.fillHeight: true
Layout.preferredWidth: Layout.preferredHeight Layout.preferredWidth: height
visible: d.dotsVisible visible: d.dotsVisible
color: Theme.palette.baseColor3 color: Theme.palette.baseColor3
@ -232,21 +244,21 @@ Control {
width: height width: height
} }
} }
}
onModelChanged: d.buildShortModel(root.model) StatusToolTip {
Connections { text: root.requirementsMet ? qsTr("Eligible to join") : qsTr("Not eligible to join")
target: root.model visible: root.hovered
function onCountChanged() {
d.buildShortModel(root.model)
} }
} }
Component.onCompleted: d.buildShortModel(root.model)
ModelChangeTracker {
model: d.filteredModel
onRevisionChanged: d.buildShortModel(d.filteredModel)
}
ListModel { id: shortModel } ListModel { id: shortModel }
component SinglePermissionRow: RowLayout {
component SinglePermissionRow: RowLayout {
id: singlePermissionItem id: singlePermissionItem
readonly property int maxVisualTokens: 3 readonly property int maxVisualTokens: 3
@ -284,7 +296,7 @@ Control {
Connections { Connections {
target: singlePermissionItem.model target: singlePermissionItem.model
function onCountChanged() { function onCountChanged() {
buildTokensRowModel(singlePermissionItem.model) singlePermissionItem.buildTokensRowModel(singlePermissionItem.model)
} }
} }
Component.onCompleted: buildTokensRowModel(singlePermissionItem.model) Component.onCompleted: buildTokensRowModel(singlePermissionItem.model)
@ -300,8 +312,8 @@ Control {
} }
StatusRoundedImage { StatusRoundedImage {
Layout.preferredHeight: container.height Layout.fillHeight: true
Layout.preferredWidth: Layout.preferredHeight Layout.preferredWidth: height
z: index z: index
image.source: model.imageSource image.source: model.imageSource
@ -313,8 +325,8 @@ Control {
StatusRoundedComponent { StatusRoundedComponent {
visible: singlePermissionItem.plusElementVisible visible: singlePermissionItem.plusElementVisible
Layout.preferredHeight: container.height Layout.fillHeight: true
Layout.preferredWidth: Layout.preferredHeight Layout.preferredWidth: height
z: d.maxTokens z: d.maxTokens
color: Theme.palette.baseColor3 color: Theme.palette.baseColor3

View File

@ -1,8 +1,6 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import AppLayouts.Communities.controls 1.0
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
@ -11,6 +9,9 @@ import StatusQ.Popups 0.1
import utils 1.0 import utils 1.0
import AppLayouts.Communities.controls 1.0
import AppLayouts.Communities.helpers 1.0
Item { Item {
id: root id: root
@ -48,7 +49,8 @@ Item {
delegate: StatusCommunityCard { delegate: StatusCommunityCard {
id: profileDialogCommunityCard id: profileDialogCommunityCard
readonly property var permissionsList: model.permissionsModel readonly property var permissionsList: model.permissionsModel
readonly property bool requirementsMet: !!model.allTokenRequirementsMet ? model.allTokenRequirementsMet : false readonly property bool isTokenGatedCommunity: PermissionsHelpers.isTokenGatedCommunity(permissionsList)
cardSize: StatusCommunityCard.Size.Small cardSize: StatusCommunityCard.Size.Small
implicitWidth: GridView.view.cellWidth - Style.current.padding implicitWidth: GridView.view.cellWidth - Style.current.padding
implicitHeight: GridView.view.cellHeight - Style.current.padding implicitHeight: GridView.view.cellHeight - Style.current.padding
@ -60,7 +62,7 @@ Item {
asset.width: 32 asset.width: 32
asset.height: 32 asset.height: 32
name: model.name ?? "" name: model.name ?? ""
memberCountVisible: false memberCountVisible: model.joined || !model.encrypted
banner: model.bannerImageData ?? "" banner: model.bannerImageData ?? ""
descriptionFontSize: 12 descriptionFontSize: 12
descriptionFontColor: Theme.palette.baseColor1 descriptionFontColor: Theme.palette.baseColor1
@ -80,8 +82,7 @@ Item {
// Community restrictions // Community restrictions
bottomRowComponent: (model.joined && !root.readOnly) ? bottomRowComponent: (model.joined && !root.readOnly) ?
communityMembershipComponent : communityMembershipComponent :
!!profileDialogCommunityCard.permissionsList && profileDialogCommunityCard.permissionsList.count > 0 ? isTokenGatedCommunity ? permissionsRowComponent : null
permissionsRowComponent : null
Component { Component {
id: communityMembershipComponent id: communityMembershipComponent
@ -116,13 +117,17 @@ Item {
Component { Component {
id: permissionsRowComponent id: permissionsRowComponent
PermissionsRow { PermissionsRow {
hoverEnabled: false readonly property int eligibleToJoinAs: PermissionsHelpers.isEligibleToJoinAs(profileDialogCommunityCard.permissionsList)
assetsModel: root.globalAssetsModel assetsModel: root.globalAssetsModel
collectiblesModel: root.globalCollectiblesModel collectiblesModel: root.globalCollectiblesModel
model: profileDialogCommunityCard.permissionsList model: profileDialogCommunityCard.permissionsList
requirementsMet: profileDialogCommunityCard.requirementsMet requirementsMet: eligibleToJoinAs === PermissionTypes.Type.Member
|| eligibleToJoinAs === PermissionTypes.Type.Admin
|| eligibleToJoinAs === PermissionTypes.Type.Owner
backgroundBorderColor: Theme.palette.baseColor2 backgroundBorderColor: Theme.palette.baseColor2
backgroundRadius: 20 backgroundRadius: 20
fontPixelSize: 10
} }
} }
@ -174,6 +179,8 @@ Item {
root.copyToClipboard(contextMenu.url) root.copyToClipboard(contextMenu.url)
} }
} }
onClosed: destroy()
} }
} }
} }