486 lines
18 KiB
QML
486 lines
18 KiB
QML
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15
|
|
import QtQuick.Layouts 1.15
|
|
|
|
import utils 1.0
|
|
|
|
import StatusQ 0.1
|
|
import StatusQ.Core 0.1
|
|
import StatusQ.Core.Theme 0.1
|
|
import StatusQ.Controls 0.1
|
|
import StatusQ.Components 0.1
|
|
import StatusQ.Popups 0.1
|
|
import StatusQ.Core.Utils 0.1
|
|
|
|
import AppLayouts.Communities.panels 1.0
|
|
import AppLayouts.Communities.controls 1.0
|
|
import AppLayouts.Communities.helpers 1.0
|
|
|
|
import SortFilterProxyModel 0.2
|
|
|
|
StatusStackModal {
|
|
id: root
|
|
|
|
destroyOnClose: true
|
|
property bool isEditMode: false
|
|
|
|
required property string communityId
|
|
required property string communityName
|
|
required property string communityIcon
|
|
required property bool requirementsCheckPending
|
|
|
|
property bool checkingPermissionToJoinInProgress
|
|
property bool joinPermissionsCheckCompletedWithoutErrors
|
|
|
|
property string introMessage
|
|
|
|
property bool isInvitationPending: false
|
|
|
|
required property var walletAccountsModel // name, address, emoji, colorId
|
|
|
|
required property var walletAssetsModel
|
|
required property var walletCollectiblesModel
|
|
|
|
required property var permissionsModel // id, key, permissionType, holdingsListModel, channelsListModel, isPrivate, tokenCriteriaMet
|
|
required property var assetsModel
|
|
required property var collectiblesModel
|
|
|
|
property var keypairSigningModel
|
|
|
|
property var currentSharedAddresses: []
|
|
onCurrentSharedAddressesChanged: d.reEvaluateModels()
|
|
property string currentAirdropAddress: ""
|
|
onCurrentAirdropAddressChanged: d.reEvaluateModels()
|
|
|
|
property var getCurrencyAmount: function (balance, symbol){}
|
|
|
|
property var canProfileProveOwnershipOfProvidedAddressesFn: function(addresses) { return false }
|
|
|
|
readonly property bool profileProvesOwnershipOfSelectedAddresses: {
|
|
d.selectedSharedAddressesMap // needed for binding
|
|
const obj = d.getSelectedAddresses()
|
|
return root.canProfileProveOwnershipOfProvidedAddressesFn(obj.addresses)
|
|
}
|
|
|
|
readonly property bool allAddressesToRevealBelongToSingleNonProfileKeypair: {
|
|
const keyUids = new Set()
|
|
for (const [key, value] of d.selectedSharedAddressesMap) {
|
|
keyUids.add(value.keyUid)
|
|
}
|
|
return keyUids.size === 1 && !keyUids.has(userProfile.keyUid)
|
|
}
|
|
|
|
signal prepareForSigning(string airdropAddress, var sharedAddresses)
|
|
signal joinCommunity()
|
|
signal editRevealedAddresses()
|
|
signal signProfileKeypairAndAllNonKeycardKeypairs()
|
|
signal signSharedAddressesForKeypair(string keyUid)
|
|
signal cancelMembershipRequest()
|
|
signal sharedAddressesUpdated(var sharedAddresses)
|
|
|
|
width: 640 // by design
|
|
padding: 0
|
|
stackTitle: d.accessType === Constants.communityChatOnRequestAccess ?
|
|
qsTr("Request to join %1").arg(root.communityName)
|
|
: qsTr("Welcome to %1").arg(root.communityName)
|
|
|
|
rightButtons: [d.shareButton, finishButton]
|
|
|
|
finishButton: StatusButton {
|
|
interactive: {
|
|
if (root.isInvitationPending || d.accessType !== Constants.communityChatOnRequestAccess)
|
|
return true
|
|
|
|
if (root.checkingPermissionToJoinInProgress || !root.joinPermissionsCheckCompletedWithoutErrors)
|
|
return false
|
|
|
|
return d.eligibleToJoinAs !== PermissionTypes.Type.None
|
|
}
|
|
loading: root.checkingPermissionToJoinInProgress && !root.isInvitationPending
|
|
tooltip.text: {
|
|
if (interactive)
|
|
return ""
|
|
|
|
if (root.checkingPermissionToJoinInProgress)
|
|
return qsTr("Requirements check pending")
|
|
|
|
if (!root.joinPermissionsCheckCompletedWithoutErrors)
|
|
return qsTr("Checking permissions to join failed")
|
|
|
|
return ""
|
|
}
|
|
text: {
|
|
if (root.isInvitationPending) {
|
|
return qsTr("Cancel Membership Request")
|
|
}
|
|
if (d.selectedSharedAddressesCount === d.totalNumOfAddressesForSharing) {
|
|
return qsTr("Share all addresses to join")
|
|
}
|
|
return qsTr("Share %n address(s) to join", "", d.selectedSharedAddressesCount)
|
|
}
|
|
type: root.isInvitationPending ? StatusBaseButton.Type.Danger
|
|
: StatusBaseButton.Type.Normal
|
|
|
|
icon.name: {
|
|
if (root.isInvitationPending)
|
|
return ""
|
|
|
|
if (root.profileProvesOwnershipOfSelectedAddresses) {
|
|
if (userProfile.usingBiometricLogin) {
|
|
return "touch-id"
|
|
}
|
|
|
|
if (userProfile.isKeycardUser) {
|
|
return "keycard"
|
|
}
|
|
|
|
return "password"
|
|
}
|
|
if (root.allAddressesToRevealBelongToSingleNonProfileKeypair) {
|
|
return "keycard"
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
onClicked: {
|
|
if (root.isInvitationPending) {
|
|
root.cancelMembershipRequest()
|
|
root.close()
|
|
return
|
|
}
|
|
|
|
d.proceedToSigningOrSubmitRequest(d.communityIntroUid)
|
|
}
|
|
}
|
|
|
|
backButton: StatusBackButton {
|
|
visible: !!root.replaceLoader.item
|
|
&& !(root.replaceLoader.item.componentUid === d.shareAddressesUid && root.isEditMode)
|
|
|
|
onClicked: {
|
|
if (d.backActionGoesTo === d.communityIntroUid) {
|
|
if (root.replaceItem) {
|
|
root.replaceItem = undefined
|
|
}
|
|
return
|
|
}
|
|
|
|
if (d.backActionGoesTo === d.shareAddressesUid) {
|
|
d.backActionGoesTo = d.communityIntroUid
|
|
root.replace(sharedAddressesPanelComponent)
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
Layout.minimumWidth: implicitWidth
|
|
}
|
|
|
|
QtObject {
|
|
id: d
|
|
|
|
readonly property string communityIntroUid: "community-intro"
|
|
readonly property string shareAddressesUid: "shared-addresses"
|
|
readonly property string signingPanelUid: "signing-panel"
|
|
property string backActionGoesTo: d.communityIntroUid
|
|
|
|
readonly property int totalNumOfAddressesForSharing: root.walletAccountsModel.count
|
|
|
|
property var currentSharedAddressesMap: new Map() // Map[address, [keyUid, selected, isAirdrop]] - used in edit mode only
|
|
property var selectedSharedAddressesMap: new Map() // Map[address, [keyUid, selected, isAirdrop]]
|
|
|
|
readonly property int selectedSharedAddressesCount: d.selectedSharedAddressesMap.size
|
|
|
|
readonly property int accessType: d.eligibleToJoinAs !== -1 ? Constants.communityChatOnRequestAccess
|
|
: Constants.communityChatPublicAccess
|
|
property int eligibleToJoinAs: PermissionsHelpers.isEligibleToJoinAs(root.permissionsModel)
|
|
readonly property var _con: Connections {
|
|
target: root.permissionsModel
|
|
ignoreUnknownSignals: true
|
|
function onTokenCriteriaUpdated() {
|
|
d.eligibleToJoinAs = PermissionsHelpers.isEligibleToJoinAs(root.permissionsModel)
|
|
}
|
|
}
|
|
|
|
readonly property var initialAddressesModel: SortFilterProxyModel {
|
|
sourceModel: root.walletAccountsModel
|
|
}
|
|
|
|
function proceedToSigningOrSubmitRequest(uidOfComponentThisFunctionIsCalledFrom) {
|
|
const selected = d.getSelectedAddresses()
|
|
root.prepareForSigning(selected.airdropAddress, selected.addresses)
|
|
if (root.profileProvesOwnershipOfSelectedAddresses) {
|
|
root.signProfileKeypairAndAllNonKeycardKeypairs()
|
|
return
|
|
}
|
|
if (root.allAddressesToRevealBelongToSingleNonProfileKeypair) {
|
|
if (d.selectedSharedAddressesMap.size === 0) {
|
|
console.error("selected shared addresses must not be empty")
|
|
return
|
|
}
|
|
const keyUid = d.selectedSharedAddressesMap.values().next().value.keyUid
|
|
root.signSharedAddressesForKeypair(keyUid)
|
|
return
|
|
}
|
|
|
|
d.backActionGoesTo = uidOfComponentThisFunctionIsCalledFrom
|
|
root.replace(sharedAddressesSigningPanelComponent)
|
|
}
|
|
|
|
// This function deletes/adds it the address from/to the map.
|
|
function toggleAddressSelection(keyUid, address) {
|
|
const tmpMap = d.selectedSharedAddressesMap
|
|
|
|
const lAddress = address.toLowerCase()
|
|
const obj = tmpMap.get(lAddress)
|
|
if (!!obj) {
|
|
if (tmpMap.size === 1) {
|
|
console.error("cannot remove the last selected address")
|
|
}
|
|
tmpMap.delete(lAddress)
|
|
if (obj.isAirdrop) {
|
|
d.selectAirdropAddressForTheFirstSelectedAddress()
|
|
}
|
|
} else {
|
|
tmpMap.set(lAddress, {keyUid: keyUid, selected: true, isAirdrop: false})
|
|
}
|
|
|
|
d.selectedSharedAddressesMap = tmpMap
|
|
}
|
|
|
|
// This function selects new airdrop address, invalidating old airdrop address selection.
|
|
function selectAirdropAddressForTheFirstSelectedAddress() {
|
|
const tmpMap = d.selectedSharedAddressesMap
|
|
|
|
// clear previous airdrop address
|
|
for (const [key, value] of tmpMap) {
|
|
if (!value.isAirdrop) {
|
|
d.selectedSharedAddressesMap.set(key, {keyUid: value.keyUid, selected: value.selected, isAirdrop: true})
|
|
break
|
|
}
|
|
}
|
|
|
|
d.selectedSharedAddressesMap = tmpMap
|
|
}
|
|
|
|
// This function selects new airdrop address, invalidating old airdrop address selection.
|
|
function selectAirdropAddress(address) {
|
|
const tmpMap = d.selectedSharedAddressesMap
|
|
|
|
// clear previous airdrop address
|
|
for (const [key, value] of tmpMap) {
|
|
if (value.isAirdrop) {
|
|
tmpMap.set(key, {keyUid: value.keyUid, selected: value.selected, isAirdrop: false})
|
|
break
|
|
}
|
|
}
|
|
|
|
// set new airdrop address
|
|
const lAddress = address.toLowerCase()
|
|
const obj = tmpMap.get(lAddress)
|
|
if (!obj) {
|
|
console.error("cannot set airdrop address for unselected address")
|
|
return
|
|
}
|
|
obj.isAirdrop = true
|
|
tmpMap.set(lAddress, obj)
|
|
|
|
d.selectedSharedAddressesMap = tmpMap
|
|
}
|
|
|
|
// Returns an object containing all selected addresses and selected airdrop address
|
|
function getSelectedAddresses() {
|
|
const result = {addresses: [], airdropAddress: ""}
|
|
for (const [key, value] of d.selectedSharedAddressesMap) {
|
|
if (value.selected) {
|
|
result.addresses.push(key)
|
|
}
|
|
if (value.isAirdrop) {
|
|
result.airdropAddress = key
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
function reEvaluateModels() {
|
|
const tmpSharedAddressesMap = new Map()
|
|
const tmpCurrentSharedAddressesMap = new Map()
|
|
for (let i=0; i < d.initialAddressesModel.count; ++i){
|
|
const obj = d.initialAddressesModel.get(i)
|
|
|
|
if (!!obj) {
|
|
const lAddress = obj.address.toLowerCase()
|
|
|
|
let isAirdrop = i === 0
|
|
if (root.isEditMode) {
|
|
if (root.currentSharedAddresses.indexOf(obj.address) === -1) {
|
|
continue
|
|
}
|
|
isAirdrop = lAddress === root.currentAirdropAddress.toLowerCase()
|
|
}
|
|
tmpSharedAddressesMap.set(lAddress, {keyUid: obj.keyUid, selected: true, isAirdrop: isAirdrop})
|
|
tmpCurrentSharedAddressesMap.set(lAddress, {keyUid: obj.keyUid, selected: true, isAirdrop: isAirdrop})
|
|
}
|
|
}
|
|
|
|
d.selectedSharedAddressesMap = tmpSharedAddressesMap
|
|
if (root.isEditMode) {
|
|
d.currentSharedAddressesMap = new Map(tmpCurrentSharedAddressesMap)
|
|
}
|
|
}
|
|
|
|
readonly property var shareButton: StatusFlatButton {
|
|
height: finishButton.height
|
|
visible: !root.isInvitationPending && !root.replaceItem
|
|
borderColor: "transparent"
|
|
text: qsTr("Select addresses to share")
|
|
onClicked: {
|
|
d.backActionGoesTo = d.communityIntroUid
|
|
root.replace(sharedAddressesPanelComponent)
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
d.reEvaluateModels()
|
|
|
|
if (root.isEditMode) {
|
|
d.backActionGoesTo = d.shareAddressesUid
|
|
root.replace(sharedAddressesPanelComponent)
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: sharedAddressesPanelComponent
|
|
SharedAddressesPanel {
|
|
componentUid: d.shareAddressesUid
|
|
isEditMode: root.isEditMode
|
|
|
|
communityId: root.communityId
|
|
communityName: root.communityName
|
|
communityIcon: root.communityIcon
|
|
requirementsCheckPending: root.requirementsCheckPending
|
|
checkingPermissionToJoinInProgress: root.checkingPermissionToJoinInProgress
|
|
joinPermissionsCheckCompletedWithoutErrors: root.joinPermissionsCheckCompletedWithoutErrors
|
|
|
|
walletAccountsModel: d.initialAddressesModel
|
|
|
|
selectedSharedAddressesMap: d.selectedSharedAddressesMap
|
|
currentSharedAddressesMap: d.currentSharedAddressesMap
|
|
|
|
totalNumOfAddressesForSharing: d.totalNumOfAddressesForSharing
|
|
eligibleToJoinAs: d.eligibleToJoinAs
|
|
|
|
profileProvesOwnershipOfSelectedAddresses: root.profileProvesOwnershipOfSelectedAddresses
|
|
allAddressesToRevealBelongToSingleNonProfileKeypair: root.allAddressesToRevealBelongToSingleNonProfileKeypair
|
|
|
|
walletAssetsModel: root.walletAssetsModel
|
|
walletCollectiblesModel: root.walletCollectiblesModel
|
|
|
|
permissionsModel: root.permissionsModel
|
|
assetsModel: root.assetsModel
|
|
collectiblesModel: root.collectiblesModel
|
|
|
|
onClose: {
|
|
root.close()
|
|
}
|
|
|
|
onToggleAddressSelection: {
|
|
d.toggleAddressSelection(keyUid, address)
|
|
|
|
const obj = d.getSelectedAddresses()
|
|
root.sharedAddressesUpdated(obj.addresses)
|
|
}
|
|
|
|
onAirdropAddressSelected: {
|
|
d.selectAirdropAddress(address)
|
|
}
|
|
|
|
onShareSelectedAddressesClicked: {
|
|
d.proceedToSigningOrSubmitRequest(d.shareAddressesUid)
|
|
}
|
|
|
|
getCurrencyAmount: function (balance, symbol){
|
|
return root.getCurrencyAmount(balance, symbol)
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: sharedAddressesSigningPanelComponent
|
|
SharedAddressesSigningPanel {
|
|
componentUid: d.signingPanelUid
|
|
isEditMode: root.isEditMode
|
|
totalNumOfAddressesForSharing: d.totalNumOfAddressesForSharing
|
|
selectedSharedAddressesMap: d.selectedSharedAddressesMap
|
|
|
|
communityName: root.communityName
|
|
keypairSigningModel: root.keypairSigningModel
|
|
|
|
onSignProfileKeypairAndAllNonKeycardKeypairs: {
|
|
root.signProfileKeypairAndAllNonKeycardKeypairs()
|
|
}
|
|
|
|
onSignSharedAddressesForKeypair: {
|
|
root.signSharedAddressesForKeypair(keyUid)
|
|
}
|
|
|
|
onJoinCommunity: {
|
|
if (root.isEditMode) {
|
|
root.editRevealedAddresses()
|
|
} else {
|
|
root.joinCommunity()
|
|
}
|
|
root.close()
|
|
}
|
|
}
|
|
}
|
|
|
|
stackItems: [
|
|
Item {
|
|
implicitHeight: scrollView.contentHeight + scrollView.bottomPadding + eligibilityTag.anchors.bottomMargin
|
|
|
|
StatusScrollView {
|
|
id: scrollView
|
|
anchors.fill: parent
|
|
contentWidth: availableWidth
|
|
bottomPadding: 80
|
|
|
|
ColumnLayout {
|
|
spacing: Theme.bigPadding
|
|
width: parent.width
|
|
|
|
StatusRoundedImage {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: 64
|
|
Layout.preferredHeight: width
|
|
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.Wrap
|
|
}
|
|
}
|
|
}
|
|
|
|
CommunityEligibilityTag {
|
|
id: eligibilityTag
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: Theme.bigPadding
|
|
eligibleToJoinAs: d.eligibleToJoinAs
|
|
isEditMode: root.isEditMode
|
|
visible: !root.isInvitationPending && !root.checkingPermissionToJoinInProgress && root.joinPermissionsCheckCompletedWithoutErrors &&
|
|
d.accessType === Constants.communityChatOnRequestAccess
|
|
}
|
|
}
|
|
]
|
|
}
|