From 10a8469b9e2a142a254b18329c4c901b141c398e Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Tue, 19 Mar 2024 09:41:41 +0100 Subject: [PATCH] fix(communities): deltas between designs and build for join token gated community flow This commit: - improves selection of addresses to reveal - keeps the selection state for the popup lifetime - brings higher granularity in terms of signed requests by keypairs - meets new requirements from the latest related Figma - merges edit shared addresses feature and request to join community features into a single component, cause the flow is logically the same, with the only difference that when editing revealed addresses we don't show the community intro screen Fixes at least points 3 and 4 from #13988 --- .../modules/main/communities/io_interface.nim | 2 +- src/app/modules/main/communities/module.nim | 39 +- src/app/modules/main/communities/view.nim | 10 +- .../main/wallet_section/io_interface.nim | 3 + .../modules/main/wallet_section/module.nim | 20 +- .../main/wallet_section/send/account_item.nim | 6 +- .../wallet_section/send/accounts_model.nim | 18 +- src/app/modules/main/wallet_section/view.nim | 15 +- src/app/modules/shared/wallet_utils.nim | 1 + ui/app/AppLayouts/Chat/ChatLayout.qml | 35 +- ui/app/AppLayouts/Chat/stores/RootStore.qml | 4 +- .../panels/SharedAddressesAccountSelector.qml | 53 ++- .../panels/SharedAddressesPanel.qml | 218 +++++------ .../panels/SharedAddressesSigningPanel.qml | 347 ++++++++++++++---- .../popups/SharedAddressesPopup.qml | 21 +- .../Communities/views/CommunityColumnView.qml | 34 +- .../Profile/views/CommunitiesView.qml | 27 +- ui/app/AppLayouts/Wallet/stores/RootStore.qml | 4 + ui/app/AppLayouts/stores/RootStore.qml | 4 +- ui/app/mainui/Popups.qml | 97 +++-- .../shared/popups/CommunityIntroDialog.qml | 333 ++++++++++++++--- 21 files changed, 940 insertions(+), 351 deletions(-) diff --git a/src/app/modules/main/communities/io_interface.nim b/src/app/modules/main/communities/io_interface.nim index e1586a146c..7dc4ec5f1b 100644 --- a/src/app/modules/main/communities/io_interface.nim +++ b/src/app/modules/main/communities/io_interface.nim @@ -224,7 +224,7 @@ method prepareKeypairsForSigning*(self: AccessInterface, communityId: string, en airdropAddress: string, editMode: bool) {.base.} = raise newException(ValueError, "No implementation available") -method signSharedAddressesForAllNonKeycardKeypairs*(self: AccessInterface) {.base.} = +method signProfileKeypairAndAllNonKeycardKeypairs*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") method signSharedAddressesForKeypair*(self: AccessInterface, keyUid: string, pin: string) {.base.} = diff --git a/src/app/modules/main/communities/module.nim b/src/app/modules/main/communities/module.nim index b6ac7a316f..d61fdb4a03 100644 --- a/src/app/modules/main/communities/module.nim +++ b/src/app/modules/main/communities/module.nim @@ -675,7 +675,7 @@ method shareCommunityChannelUrlWithChatKey*(self: Module, communityId: string, c method shareCommunityChannelUrlWithData*(self: Module, communityId: string, chatId: string): string = return self.controller.shareCommunityChannelUrlWithData(communityId, chatId) -proc signRevealedAddressesThatBelongToRegularKeypairs(self: Module): bool = +proc signRevealedAddressesForNonKeycardKeypairs(self: Module): bool = var signingParams: seq[SignParamsDto] for address, details in self.joiningCommunityDetails.addressesToShare.pairs: if details.signature.len > 0: @@ -702,8 +702,24 @@ proc signRevealedAddressesThatBelongToRegularKeypairs(self: Module): bool = let signatures = self.controller.signCommunityRequests(self.joiningCommunityDetails.communityId, signingParams) for i in 0 ..< len(signingParams): self.joiningCommunityDetails.addressesToShare[signingParams[i].address].signature = signatures[i] + self.view.keypairsSigningModel().setOwnershipVerified(self.joiningCommunityDetails.addressesToShare[signingParams[i].address].keyUid, true) return true +proc signRevealedAddressesForNonKeycardKeypairsAndEmitSignal(self: Module) = + if self.signRevealedAddressesForNonKeycardKeypairs() and self.joiningCommunityDetails.allSigned(): + self.view.sendAllSharedAddressesSignedSignal() + +proc anyProfileKeyPairAddressSelectedToBeRevealed(self: Module): bool = + let profileKeypair = self.controller.getKeypairByKeyUid(singletonInstance.userProfile.getKeyUid()) + if profileKeypair.isNil: + error "profile keypair not found" + return false + for acc in profileKeypair.accounts: + for addrToReveal in self.joiningCommunityDetails.addressesToShare.keys: + if cmpIgnoreCase(addrToReveal, acc.address) == 0: + return true + return false + method onUserAuthenticated*(self: Module, pin: string, password: string, keyUid: string) = if password == "" and pin == "": info "unsuccesful authentication" @@ -712,8 +728,16 @@ method onUserAuthenticated*(self: Module, pin: string, password: string, keyUid: self.joiningCommunityDetails.profilePassword = password self.joiningCommunityDetails.profilePin = pin - if self.signRevealedAddressesThatBelongToRegularKeypairs(): - self.view.sendSharedAddressesForAllNonKeycardKeypairsSignedSignal() + + # If any profile keypair address selected to be revealed and if the profile is a keycard user, we need to sign the request + # for revealed profile addresses first, then using pubic encryption key to sign other non keycard key pairs. + # If the profile is not a keycard user, we sign the request for it calling `signRevealedAddressesForNonKeycardKeypairs` function. + if keyUid == singletonInstance.userProfile.getKeyUid() and + singletonInstance.userProfile.getIsKeycardUser() and + self.anyProfileKeyPairAddressSelectedToBeRevealed(): + self.signSharedAddressesForKeypair(keyUid, pin) + return + self.signRevealedAddressesForNonKeycardKeypairsAndEmitSignal() method onDataSigned*(self: Module, keyUid: string, path: string, r: string, s: string, v: string, pin: string) = if keyUid.len == 0 or path.len == 0 or r.len == 0 or s.len == 0 or v.len == 0 or pin.len == 0: @@ -727,6 +751,11 @@ method onDataSigned*(self: Module, keyUid: string, path: string, r: string, s: s break self.signSharedAddressesForKeypair(keyUid, pin) + # Only if the signed request is for the profile revealed addresses, we need to try to sign other revealed addresses + # for non profile key pairs. If they are already signed or moved to keycard we skip them (handled in signRevealedAddressesForNonKeycardKeypairsAndEmitSignal) + if keyUid == singletonInstance.userProfile.getKeyUid(): + self.signRevealedAddressesForNonKeycardKeypairsAndEmitSignal() + method prepareKeypairsForSigning*(self: Module, communityId, ensName: string, addresses: string, airdropAddress: string, editMode: bool) = var addressesToShare: seq[string] @@ -778,7 +807,7 @@ method prepareKeypairsForSigning*(self: Module, communityId, ensName: string, ad ) self.joiningCommunityDetails.addressesToShare[param.address] = details -method signSharedAddressesForAllNonKeycardKeypairs*(self: Module) = +method signProfileKeypairAndAllNonKeycardKeypairs*(self: Module) = self.controller.authenticate() # if pin is provided we're signing on a keycard silently @@ -796,6 +825,8 @@ method signSharedAddressesForKeypair*(self: Module, keyUid: string, pin: string) self.controller.runSigningOnKeycard(keyUid, details.path, details.messageToBeSigned, pin) return self.view.keypairsSigningModel().setOwnershipVerified(keyUid, true) + if self.joiningCommunityDetails.allSigned(): + self.view.sendAllSharedAddressesSignedSignal() method joinCommunityOrEditSharedAddresses*(self: Module) = if not self.joiningCommunityDetails.allSigned(): diff --git a/src/app/modules/main/communities/view.nim b/src/app/modules/main/communities/view.nim index 09bf11fb9d..7d84ac2e6d 100644 --- a/src/app/modules/main/communities/view.nim +++ b/src/app/modules/main/communities/view.nim @@ -339,8 +339,8 @@ QtObject: proc prepareTokenModelForCommunity(self: View, communityId: string) {.slot.} = self.delegate.prepareTokenModelForCommunity(communityId) - proc signSharedAddressesForAllNonKeycardKeypairs*(self: View) {.slot.} = - self.delegate.signSharedAddressesForAllNonKeycardKeypairs() + proc signProfileKeypairAndAllNonKeycardKeypairs*(self: View) {.slot.} = + self.delegate.signProfileKeypairAndAllNonKeycardKeypairs() proc signSharedAddressesForKeypair*(self: View, keyUid: string) {.slot.} = self.delegate.signSharedAddressesForKeypair(keyUid, pin = "") @@ -815,9 +815,9 @@ QtObject: self.keypairsSigningModel.setItems(items) self.keypairsSigningModelChanged() - proc sharedAddressesForAllNonKeycardKeypairsSigned(self: View) {.signal.} - proc sendSharedAddressesForAllNonKeycardKeypairsSignedSignal*(self: View) = - self.sharedAddressesForAllNonKeycardKeypairsSigned() + proc allSharedAddressesSigned*(self: View) {.signal.} + proc sendAllSharedAddressesSignedSignal*(self: View) = + self.allSharedAddressesSigned() proc promoteSelfToControlNode*(self: View, communityId: string) {.slot.} = self.delegate.promoteSelfToControlNode(communityId) diff --git a/src/app/modules/main/wallet_section/io_interface.nim b/src/app/modules/main/wallet_section/io_interface.nim index 4944c49caf..095ebdc96d 100644 --- a/src/app/modules/main/wallet_section/io_interface.nim +++ b/src/app/modules/main/wallet_section/io_interface.nim @@ -120,3 +120,6 @@ method getRpcStats*(self: AccessInterface): string {.base.} = method resetRpcStats*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") + +method canProfileProveOwnershipOfProvidedAddresses*(self: AccessInterface, addresses: string): bool {.base.} = + raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/module.nim b/src/app/modules/main/wallet_section/module.nim index fda6f6f1b3..f511932fa3 100644 --- a/src/app/modules/main/wallet_section/module.nim +++ b/src/app/modules/main/wallet_section/module.nim @@ -1,4 +1,4 @@ -import NimQml, chronicles, sequtils, strutils, sugar +import NimQml, json, chronicles, sequtils, strutils, sugar import ./controller, ./view, ./filter import ./io_interface as io_interface @@ -471,3 +471,21 @@ method getRpcStats*(self: Module): string = method resetRpcStats*(self: Module) = self.view.resetRpcStats() + +method canProfileProveOwnershipOfProvidedAddresses*(self: Module, addresses: string): bool = + var addressesForProvingOwnership: seq[string] + try: + addressesForProvingOwnership = map(parseJson(addresses).getElems(), proc(x:JsonNode):string = x.getStr()) + except Exception as e: + error "Failed to parse addresses for proving ownership: ", msg=e.msg + return false + + for address in addressesForProvingOwnership: + let keypair = self.controller.getKeypairByAccountAddress(address) + if keypair.isNil: + return false + if keypair.keyUid == singletonInstance.userProfile.getKeyUid(): + continue + if keypair.migratedToKeycard(): + return false + return true \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/send/account_item.nim b/src/app/modules/main/wallet_section/send/account_item.nim index fcdc5bd760..cbaeddbafc 100644 --- a/src/app/modules/main/wallet_section/send/account_item.nim +++ b/src/app/modules/main/wallet_section/send/account_item.nim @@ -10,6 +10,7 @@ QtObject: canSend: bool proc setup*(self: AccountItem, + keyUid: string, name: string, address: string, colorId: string, @@ -29,7 +30,7 @@ QtObject: emoji, walletType, path = "", - keyUid = "", + keyUid = keyUid, keycardAccount = false, position, operability = wa_dto.AccountFullyOperable, @@ -43,6 +44,7 @@ QtObject: self.QObject.delete proc newAccountItem*( + keyUid: string = "", name: string = "", address: string = "", colorId: string = "", @@ -56,7 +58,7 @@ QtObject: canSend: bool = true, ): AccountItem = new(result, delete) - result.setup(name, address, colorId, emoji, walletType, currencyBalance, position, areTestNetworksEnabled, prodPreferredChainIds, testPreferredChainIds, canSend) + result.setup(keyUid, name, address, colorId, emoji, walletType, currencyBalance, position, areTestNetworksEnabled, prodPreferredChainIds, testPreferredChainIds, canSend) proc `$`*(self: AccountItem): string = result = "WalletSection-Send-Item(" diff --git a/src/app/modules/main/wallet_section/send/accounts_model.nim b/src/app/modules/main/wallet_section/send/accounts_model.nim index 6206f44a7f..db2157b8d6 100644 --- a/src/app/modules/main/wallet_section/send/accounts_model.nim +++ b/src/app/modules/main/wallet_section/send/accounts_model.nim @@ -5,13 +5,14 @@ import ../../../shared_models/currency_amount type ModelRole {.pure.} = enum - Name = UserRole + 1, - Address, - ColorId, - WalletType, - Emoji, - CurrencyBalance, - Position, + KeyUid = UserRole + 1 + Name + Address + ColorId + WalletType + Emoji + CurrencyBalance + Position PreferredSharingChainIds QtObject: @@ -48,6 +49,7 @@ QtObject: method roleNames(self: AccountsModel): Table[int, string] = { + ModelRole.KeyUid.int: "keyUid", ModelRole.Name.int:"name", ModelRole.Address.int:"address", ModelRole.ColorId.int:"colorId", @@ -75,6 +77,8 @@ QtObject: let enumRole = role.ModelRole case enumRole: + of ModelRole.KeyUid: + result = newQVariant(item.keyUid()) of ModelRole.Name: result = newQVariant(item.name()) of ModelRole.Address: diff --git a/src/app/modules/main/wallet_section/view.nim b/src/app/modules/main/wallet_section/view.nim index bbd6a7d848..d422189800 100644 --- a/src/app/modules/main/wallet_section/view.nim +++ b/src/app/modules/main/wallet_section/view.nim @@ -7,7 +7,7 @@ import ./io_interface import ../../shared_models/currency_amount import ./wallet_connect/controller as wcc -type +type ActivityControllerArray* = array[2, activityc.Controller] QtObject: @@ -36,11 +36,11 @@ QtObject: proc delete*(self: View) = self.QObject.delete - proc newView*(delegate: io_interface.AccessInterface, - activityController: activityc.Controller, - tmpActivityControllers: ActivityControllerArray, - activityDetailsController: activity_detailsc.Controller, - collectibleDetailsController: collectible_detailsc.Controller, + proc newView*(delegate: io_interface.AccessInterface, + activityController: activityc.Controller, + tmpActivityControllers: ActivityControllerArray, + activityDetailsController: activity_detailsc.Controller, + collectibleDetailsController: collectible_detailsc.Controller, wcController: wcc.Controller): View = new(result, delete) result.delegate = delegate @@ -259,3 +259,6 @@ QtObject: return self.delegate.getRpcStats() proc resetRpcStats*(self: View) {.slot.} = self.delegate.resetRpcStats() + + proc canProfileProveOwnershipOfProvidedAddresses*(self: View, addresses: string): bool {.slot.} = + return self.delegate.canProfileProveOwnershipOfProvidedAddresses(addresses) \ No newline at end of file diff --git a/src/app/modules/shared/wallet_utils.nim b/src/app/modules/shared/wallet_utils.nim index 0df58af902..e306e89ea4 100644 --- a/src/app/modules/shared/wallet_utils.nim +++ b/src/app/modules/shared/wallet_utils.nim @@ -60,6 +60,7 @@ proc walletAccountToWalletAccountsItem*(w: WalletAccountDto, keycardAccount: boo proc walletAccountToWalletSendAccountItem*(w: WalletAccountDto, chainIds: seq[int], enabledChainIds: seq[int], currencyBalance: float64, currencyFormat: CurrencyFormatDto, areTestNetworksEnabled: bool): wallet_send_account_item.AccountItem = return wallet_send_account_item.newAccountItem( + w.keyUid, w.name, w.address, w.colorId, diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index e40031188c..f512557ac5 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -122,9 +122,9 @@ StackLayout { Global.openPopup(communityIntroDialogPopup, { communityId: joinCommunityView.communityId, isInvitationPending: joinCommunityView.isInvitationPending, - name: communityData.name, + communityName: communityData.name, introMessage: communityData.introMessage, - imageSrc: communityData.image, + communityIcon: communityData.image, accessType: communityData.access }) } @@ -190,9 +190,9 @@ StackLayout { Global.openPopup(communityIntroDialogPopup, { communityId: chatView.communityId, isInvitationPending: root.rootStore.isMyCommunityRequestPending(chatView.communityId), - name: root.sectionItemModel.name, + communityName: root.sectionItemModel.name, introMessage: root.sectionItemModel.introMessage, - imageSrc: root.sectionItemModel.image, + communityIcon: root.sectionItemModel.image, accessType: root.sectionItemModel.access }) } @@ -205,8 +205,8 @@ StackLayout { Loader { id: communitySettingsLoader - active: root.rootStore.chatCommunitySectionModule.isCommunity() && - root.isPrivilegedUser && + active: root.rootStore.chatCommunitySectionModule.isCommunity() && + root.isPrivilegedUser && (root.currentIndex === 1 || !!communitySettingsLoader.item) // lazy load and preserve state after loading asynchronous: false // It's false on purpose. We want to load the component synchronously sourceComponent: CommunitySettingsView { @@ -272,8 +272,9 @@ StackLayout { property string communityId - loginType: root.rootStore.loginType walletAccountsModel: WalletStore.RootStore.nonWatchAccounts + canProfileProveOwnershipOfProvidedAddressesFn: WalletStore.RootStore.canProfileProveOwnershipOfProvidedAddresses + walletAssetsModel: walletAssetsStore.groupedAccountAssetsModel requirementsCheckPending: root.rootStore.requirementsCheckPending permissionsModel: { @@ -293,8 +294,8 @@ StackLayout { communityIntroDialog.keypairSigningModel = root.rootStore.communitiesModuleInst.keypairsSigningModel } - onSignSharedAddressesForAllNonKeycardKeypairs: { - root.rootStore.signSharedAddressesForAllNonKeycardKeypairs() + onSignProfileKeypairAndAllNonKeycardKeypairs: { + root.rootStore.signProfileKeypairAndAllNonKeycardKeypairs() } onSignSharedAddressesForKeypair: { @@ -321,9 +322,21 @@ StackLayout { Connections { target: root.rootStore.communitiesModuleInst - function onSharedAddressesForAllNonKeycardKeypairsSigned() { + function onAllSharedAddressesSigned() { + if (communityIntroDialog.profileProvesOwnershipOfSelectedAddresses) { + communityIntroDialog.joinCommunity() + communityIntroDialog.close() + return + } + + if (communityIntroDialog.allAddressesToRevealBelongToSingleNonProfileKeypair) { + communityIntroDialog.joinCommunity() + communityIntroDialog.close() + return + } + if (!!communityIntroDialog.replaceItem) { - communityIntroDialog.replaceLoader.item.sharedAddressesForAllNonKeycardKeypairsSigned() + communityIntroDialog.replaceLoader.item.allSigned() } } } diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index 6ff862a44d..1980a413e8 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -403,8 +403,8 @@ QtObject { communitiesModuleInst.prepareKeypairsForSigning(communityId, ensName, JSON.stringify(addressesToShare), airdropAddress, editMode) } - function signSharedAddressesForAllNonKeycardKeypairs() { - communitiesModuleInst.signSharedAddressesForAllNonKeycardKeypairs() + function signProfileKeypairAndAllNonKeycardKeypairs() { + communitiesModuleInst.signProfileKeypairAndAllNonKeycardKeypairs() } function signSharedAddressesForKeypair(keyUid) { diff --git a/ui/app/AppLayouts/Communities/panels/SharedAddressesAccountSelector.qml b/ui/app/AppLayouts/Communities/panels/SharedAddressesAccountSelector.qml index ecd247b529..48eb644a05 100644 --- a/ui/app/AppLayouts/Communities/panels/SharedAddressesAccountSelector.qml +++ b/ui/app/AppLayouts/Communities/panels/SharedAddressesAccountSelector.qml @@ -15,17 +15,16 @@ import utils 1.0 StatusListView { id: root + required property var selectedSharedAddressesMap // Map[address, [selected, isAirdrop]] + property var walletAssetsModel property bool hasPermissions property var uniquePermissionTokenKeys - // read/write properties - property string selectedAirdropAddress - property var selectedSharedAddresses: [] - property var getCurrencyAmount: function (balance, symbol){} - signal addressesChanged() + signal toggleAddressSelection(string keyUid, string address) + signal airdropAddressSelected (string address) leftMargin: d.absLeftMargin topMargin: Style.current.padding @@ -35,6 +34,8 @@ StatusListView { QtObject { id: d + readonly property int selectedSharedAddressesCount: root.selectedSharedAddressesMap.size + // UI readonly property int absLeftMargin: 12 @@ -46,10 +47,6 @@ StatusListView { exclusive: false } - function selectFirstAvailableAirdropAddress() { - root.selectedAirdropAddress = ModelUtils.modelToFlatArray(root.model, "address").find(address => selectedSharedAddresses.includes(address)) - } - function getTotalBalance(balances, decimals, symbol) { let totalBalance = 0 for(let i=0; i 1 // last cannot be unchecked + checked: { + let obj = root.selectedSharedAddressesMap.get(listItem.address) + if (!!obj) { + return obj.isAirdrop + } + return false + } + enabled: shareAddressCheckbox.checked && d.selectedSharedAddressesCount > 1 // last cannot be unchecked visible: shareAddressCheckbox.checked opacity: enabled ? 1.0 : 0.3 - onCheckedChanged: if (checked) root.selectedAirdropAddress = listItem.address - onToggled: root.addressesChanged() + + onToggled: { + root.airdropAddressSelected(listItem.address) + } StatusToolTip { text: qsTr("Use this address for any Community airdrops") @@ -161,25 +166,11 @@ StatusListView { ButtonGroup.group: d.addressesGroup anchors.verticalCenter: parent.verticalCenter checkable: true - checked: root.selectedSharedAddresses.some((address) => address.toLowerCase() === listItem.address ) - enabled: !(root.selectedSharedAddresses.length === 1 && checked) // last cannot be unchecked + checked: root.selectedSharedAddressesMap.has(listItem.address) + enabled: !(d.selectedSharedAddressesCount === 1 && checked) // last cannot be unchecked + onToggled: { - // handle selected addresses - const index = root.selectedSharedAddresses.findIndex((address) => address.toLowerCase() === listItem.address) - const selectedSharedAddressesCopy = Object.assign([], root.selectedSharedAddresses) // deep copy - if (index === -1) { - selectedSharedAddressesCopy.push(listItem.address) - } else { - selectedSharedAddressesCopy.splice(index, 1) - } - root.selectedSharedAddresses = selectedSharedAddressesCopy - - // switch to next available airdrop address when unchecking - if (!checked && listItem.address === root.selectedAirdropAddress.toLowerCase()) { - d.selectFirstAvailableAirdropAddress() - } - - root.addressesChanged() + root.toggleAddressSelection(model.keyUid, listItem.address) } } ] diff --git a/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml b/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml index 81671d902a..fa84e7c42d 100644 --- a/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/SharedAddressesPanel.qml @@ -24,13 +24,18 @@ import shared.panels 1.0 Control { id: root - property bool isEditMode + required property string componentUid + required property bool isEditMode + required property var selectedSharedAddressesMap // Map[address, [keyUid, selected, isAirdrop] + property var currentSharedAddressesMap // Map[address, [keyUid, selected, isAirdrop] + required property int totalNumOfAddressesForSharing + required property bool profileProvesOwnershipOfSelectedAddresses + required property bool allAddressesToRevealBelongToSingleNonProfileKeypair property bool requirementsCheckPending: false required property string communityName required property string communityIcon - property int loginType: Constants.LoginType.Password required property var walletAssetsModel required property var walletAccountsModel // name, address, emoji, colorId, assets @@ -38,52 +43,16 @@ Control { required property var assetsModel required property var collectiblesModel - readonly property string title: isEditMode ? qsTr("Edit which addresses you share with %1").arg(communityName) - : qsTr("Select addresses to share with %1").arg(communityName) + readonly property string title: isEditMode ? qsTr("Edit which addresses you share with %1").arg(root.communityName) + : qsTr("Select addresses to share with %1").arg(root.communityName) - readonly property var buttons: ObjectModel { - StatusFlatButton { - visible: root.isEditMode - borderColor: Theme.palette.baseColor2 - text: qsTr("Cancel") - onClicked: root.close() - } - StatusButton { - enabled: d.dirty - type: d.lostCommunityPermission || d.lostChannelPermissions ? StatusBaseButton.Type.Danger : StatusBaseButton.Type.Normal - visible: root.isEditMode - icon.name: type === StatusBaseButton.Type.Normal && d.selectedAddressesDirty? - !root.isEditMode? Constants.authenticationIconByType[root.loginType] : "" - : "" - text: d.lostCommunityPermission ? qsTr("Save changes & leave %1").arg(root.communityName) : - d.lostChannelPermissions ? qsTr("Save changes & update my permissions") - : qsTr("Prove ownership") - onClicked: { - root.prepareForSigning(root.selectedAirdropAddress, root.selectedSharedAddresses) - } - } - StatusButton { - visible: !root.isEditMode - text: qsTr("Share selected addresses to join") - onClicked: { - root.shareSelectedAddressesClicked(root.selectedAirdropAddress, root.selectedSharedAddresses) - root.close() - } - } - // NB no more buttons after this, see property `rightButtons` below - } - - readonly property var rightButtons: [buttons.get(buttons.count-1)] // "magically" used by CommunityIntroDialog StatusStackModal impl - - property var selectedSharedAddresses: [] - property string selectedAirdropAddress + readonly property var rightButtons: root.isEditMode? [d.cancelButton, d.saveButton] : [d.shareAddressesButton] property var getCurrencyAmount: function (balance, symbol){} - signal sharedAddressesChanged(string airdropAddress, var sharedAddresses) - signal shareSelectedAddressesClicked(string airdropAddress, var sharedAddresses) - signal prepareForSigning(string airdropAddress, var sharedAddresses) - + signal toggleAddressSelection(string keyUid, string address) + signal airdropAddressSelected (string address) + signal shareSelectedAddressesClicked() signal close() padding: 0 @@ -94,69 +63,108 @@ Control { // internal logic readonly property bool hasPermissions: root.permissionsModel && root.permissionsModel.count + readonly property int selectedSharedAddressesCount: root.selectedSharedAddressesMap.size - // initial state (not bindings, we want a static snapshot of the initial state) - property var initialSelectedSharedAddresses: [] - property string initialSelectedAirdropAddress - - // dirty state handling - readonly property bool selectedAddressesDirty: !SQInternal.ModelUtils.isSameArray(d.initialSelectedSharedAddresses, root.selectedSharedAddresses) - readonly property bool selectedAirdropAddressDirty: root.selectedAirdropAddress !== d.initialSelectedAirdropAddress - readonly property bool dirty: selectedAddressesDirty || selectedAirdropAddressDirty + readonly property bool dirty: { + if (root.currentSharedAddressesMap.size !== root.selectedSharedAddressesMap.size) { + return true + } + for (const [key, value] of root.currentSharedAddressesMap) { + const obj = root.selectedSharedAddressesMap.get(key) + if (!obj || value.selected !== obj.selected || value.isAirdrop !== obj.isAirdrop) { + return true + } + } + return false + } // warning states readonly property bool lostCommunityPermission: root.isEditMode && permissionsView.lostPermissionToJoin readonly property bool lostChannelPermissions: root.isEditMode && permissionsView.lostChannelPermissions - } - Component.onCompleted: { - // initialize the state - d.initialSelectedSharedAddresses = root.selectedSharedAddresses.length ? root.selectedSharedAddresses - : filteredAccountsModel.count ? ModelUtils.modelToFlatArray(filteredAccountsModel, "address") - : [] - d.initialSelectedAirdropAddress = !!root.selectedAirdropAddress ? root.selectedAirdropAddress - : d.initialSelectedSharedAddresses.length ? d.initialSelectedSharedAddresses[0] : "" - root.selectedSharedAddresses = accountSelector.selectedSharedAddresses - root.selectedAirdropAddress = accountSelector.selectedAirdropAddress - } - - function setOldSharedAddresses(oldSharedAddresses) { - d.initialSelectedSharedAddresses = oldSharedAddresses - accountSelector.selectedSharedAddresses = Qt.binding(() => d.initialSelectedSharedAddresses) - accountSelector.applyChange() - } - - function setOldAirdropAddress(oldAirdropAddress) { - d.initialSelectedAirdropAddress = oldAirdropAddress - accountSelector.selectedAirdropAddress = Qt.binding(() => d.initialSelectedAirdropAddress) - accountSelector.applyChange() - } - - SortFilterProxyModel { - id: filteredAccountsModel - sourceModel: root.walletAccountsModel - filters: ValueFilter { - roleName: "walletType" - value: Constants.watchWalletType - inverted: true + readonly property var cancelButton: StatusFlatButton { + visible: root.isEditMode + borderColor: Theme.palette.baseColor2 + text: qsTr("Cancel") + onClicked: root.close() } - sorters: [ - ExpressionSorter { - function isGenerated(modelData) { - return modelData.walletType === Constants.generatedWalletType + + readonly property var saveButton: StatusButton { + enabled: d.dirty + type: d.lostCommunityPermission || d.lostChannelPermissions ? StatusBaseButton.Type.Danger : StatusBaseButton.Type.Normal + visible: root.isEditMode + + text: { + if (d.lostCommunityPermission) { + return qsTr("Save changes & leave %1").arg(root.communityName) + } + if (d.lostChannelPermissions) { + return qsTr("Save changes & update my permissions") + } + if (d.selectedSharedAddressesCount === root.totalNumOfAddressesForSharing) { + return qsTr("Reveal all addresses") + } + return qsTr("Reveal %n address(s)", "", d.selectedSharedAddressesCount) + } + + icon.name: { + if (!d.lostCommunityPermission + && !d.lostChannelPermissions + && root.profileProvesOwnershipOfSelectedAddresses) { + if (userProfile.usingBiometricLogin) { + return "touch-id" + } + + if (userProfile.isKeycardUser) { + return "keycard" + } + + return "password" + } + if (root.allAddressesToRevealBelongToSingleNonProfileKeypair) { + return "keycard" } - expression: { - return isGenerated(modelLeft) - } - }, - RoleSorter { - roleName: "position" - }, - RoleSorter { - roleName: "name" + return "" } - ] + + onClicked: { + root.shareSelectedAddressesClicked() + } + } + + readonly property var shareAddressesButton: StatusButton { + visible: !root.isEditMode + text: { + if (d.selectedSharedAddressesCount === root.totalNumOfAddressesForSharing) { + return qsTr("Share all addresses to join") + } + return qsTr("Share %n address(s) to join", "", d.selectedSharedAddressesCount) + } + + icon.name: { + if (root.profileProvesOwnershipOfSelectedAddresses) { + if (userProfile.usingBiometricLogin) { + return "touch-id" + } + + if (userProfile.isKeycardUser) { + return "keycard" + } + + return "password" + } + if (root.allAddressesToRevealBelongToSingleNonProfileKeypair) { + return "keycard" + } + + return "" + } + + onClicked: { + root.shareSelectedAddressesClicked() + } + } } contentItem: ColumnLayout { @@ -184,14 +192,16 @@ Control { Layout.fillHeight: !hasPermissions model: root.walletAccountsModel walletAssetsModel: root.walletAssetsModel - selectedSharedAddresses: d.initialSelectedSharedAddresses - selectedAirdropAddress: d.initialSelectedAirdropAddress - onAddressesChanged: accountSelector.applyChange() - function applyChange() { - root.selectedSharedAddresses = selectedSharedAddresses - root.selectedAirdropAddress = selectedAirdropAddress - root.sharedAddressesChanged(selectedAirdropAddress, selectedSharedAddresses) + selectedSharedAddressesMap: root.selectedSharedAddressesMap + + onToggleAddressSelection: { + root.toggleAddressSelection(keyUid, address) } + + onAirdropAddressSelected: { + root.airdropAddressSelected(address) + } + getCurrencyAmount: function (balance, symbol){ return root.getCurrencyAmount(balance, symbol) } diff --git a/ui/app/AppLayouts/Communities/panels/SharedAddressesSigningPanel.qml b/ui/app/AppLayouts/Communities/panels/SharedAddressesSigningPanel.qml index 39cb9e68bb..55654b21a4 100644 --- a/ui/app/AppLayouts/Communities/panels/SharedAddressesSigningPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/SharedAddressesSigningPanel.qml @@ -13,46 +13,67 @@ import SortFilterProxyModel 0.2 ColumnLayout { id: root + required property string componentUid + required property bool isEditMode property var keypairSigningModel - readonly property string title: qsTr("Prove ownership of keypairs") + required property var selectedSharedAddressesMap // Map[address, [keyUid, selected, isAirdrop] + required property int totalNumOfAddressesForSharing + + required property string communityName + readonly property string title: root.isEditMode? + qsTr("Save addresses you share with %1").arg(root.communityName) + : qsTr("Request to join %1").arg(root.communityName) readonly property var rightButtons: [d.rightBtn] - readonly property bool allSigned: regularKeypairs.visible == d.sharedAddressesForAllNonKeycardKeypairsSigned && - keycardKeypairs.visible == d.allKeycardKeypairsSigned signal joinCommunity() - signal signSharedAddressesForAllNonKeycardKeypairs() + signal signProfileKeypairAndAllNonKeycardKeypairs() signal signSharedAddressesForKeypair(string keyUid) - function sharedAddressesForAllNonKeycardKeypairsSigned() { - d.sharedAddressesForAllNonKeycardKeypairsSigned = true + function allSigned() { + d.allSigned = true } QtObject { id: d - property bool sharedAddressesForAllNonKeycardKeypairsSigned: false - property bool allKeycardKeypairsSigned: false + readonly property int selectedSharedAddressesCount: root.selectedSharedAddressesMap.size + + property bool allSigned: false + + readonly property bool anyOfSelectedAddressesToRevealBelongToProfileKeypair: { + for (const [key, value] of root.selectedSharedAddressesMap) { + if (value.keyUid === userProfile.keyUid) { + return true + } + } + return false + } + + readonly property bool thereAreMoreThanOneNonProfileRegularKeypairs: nonProfileRegularKeypairs.count > 1 + + readonly property bool allNonProfileRegularKeypairsSigned: { + for (let i = 0; i < nonProfileRegularKeypairs.model.count; ++i) { + const item = nonProfileRegularKeypairs.model.get(i) + if (!!item && !item.keyPair.ownershipVerified) { + return false + } + } + return true + } readonly property var rightBtn: StatusButton { - enabled: root.allSigned - text: qsTr("Share your addresses to join") + enabled: d.allSigned + text: { + if (d.selectedSharedAddressesCount === root.totalNumOfAddressesForSharing) { + return qsTr("Share all addresses to join") + } + return qsTr("Share %n address(s) to join", "", d.selectedSharedAddressesCount) + } onClicked: { root.joinCommunity() } } - - function reEvaluateSignedKeypairs() { - let allKeypairsSigned = true - for(var i = 0; i< keycardKeypairs.model.count; i++) { - if(!keycardKeypairs.model.get(i).keyPair.ownershipVerified) { - allKeypairsSigned = false - break - } - } - - d.allKeycardKeypairsSigned = allKeypairsSigned - } } ColumnLayout { @@ -61,43 +82,41 @@ ColumnLayout { spacing: Style.current.padding + StatusBaseText { + Layout.preferredWidth: parent.width + elide: Text.ElideRight + font.pixelSize: Constants.keycard.general.fontSize2 + text: qsTr("To share %n address(s) with %1, authenticate the associated keypairs...", "", d.selectedSharedAddressesCount).arg(root.communityName) + } + RowLayout { Layout.fillWidth: true - visible: regularKeypairs.visible + visible: nonKeycardProfileKeypair.visible StatusBaseText { Layout.fillWidth: true - text: qsTr("Keypairs we need an authentication for") + text: qsTr("Stored on device") font.pixelSize: Constants.keycard.general.fontSize2 color: Theme.palette.baseColor1 wrapMode: Text.WordWrap } - - StatusButton { - text: d.sharedAddressesForAllNonKeycardKeypairsSigned? qsTr("Authenticated") : qsTr("Authenticate") - enabled: !d.sharedAddressesForAllNonKeycardKeypairsSigned - icon.name: userProfile.usingBiometricLogin? "touch-id" : "password" - - onClicked: { - root.signSharedAddressesForAllNonKeycardKeypairs() - } - } } StatusListView { - id: regularKeypairs + id: nonKeycardProfileKeypair Layout.fillWidth: true - Layout.preferredHeight: regularKeypairs.contentHeight - visible: regularKeypairs.model.count > 0 + Layout.preferredHeight: nonKeycardProfileKeypair.contentHeight + visible: nonKeycardProfileKeypair.model.count > 0 spacing: Style.current.padding model: SortFilterProxyModel { sourceModel: root.keypairSigningModel filters: ExpressionFilter { - expression: !model.keyPair.migratedToKeycard + expression: model.keyPair.keyUid === userProfile.keyUid && !userProfile.isKeycardUser } } delegate: KeyPairItem { + id: kpOnDeviceDelegate width: ListView.view.width sensor.hoverEnabled: false additionalInfoForProfileKeypair: "" @@ -109,22 +128,80 @@ ColumnLayout { keyPairImage: model.keyPair.image keyPairDerivedFrom: model.keyPair.derivedFrom keyPairAccounts: model.keyPair.accounts + + components: [ + StatusButton { + text: qsTr("Authenticate") + visible: !model.keyPair.ownershipVerified + icon.name: { + if (userProfile.usingBiometricLogin) { + return "touch-id" + } + + if (userProfile.isKeycardUser) { + return "keycard" + } + + return "password" + } + + onClicked: { + root.signProfileKeypairAndAllNonKeycardKeypairs() + } + }, + StatusButton { + text: qsTr("Authenticated") + visible: model.keyPair.ownershipVerified + enabled: false + normalColor: "transparent" + disabledColor: "transparent" + disabledTextColor: Theme.palette.successColor1 + icon.name: "checkmark-circle" + } + ] + + SequentialAnimation { + running: model.keyPair.ownershipVerified + PropertyAnimation { + target: kpOnDeviceDelegate + property: "color" + to: Theme.palette.successColor3 + duration: 500 + } + PropertyAnimation { + target: kpOnDeviceDelegate + property: "color" + to: Theme.palette.baseColor2 + duration: 1500 + } + } } } Item { - visible: regularKeypairs.visible && keycardKeypairs.visible + visible: nonKeycardProfileKeypair.visible Layout.fillWidth: true Layout.preferredHeight: Style.current.xlPadding } - StatusBaseText { + RowLayout { Layout.fillWidth: true + visible: keycardKeypairs.visible - text: qsTr("Keypairs that need to be singed using appropriate Keycard") - font.pixelSize: Constants.keycard.general.fontSize2 - color: Theme.palette.baseColor1 - wrapMode: Text.WordWrap + + StatusBaseText { + text: qsTr("Stored on keycard") + font.pixelSize: Constants.keycard.general.fontSize2 + color: Theme.palette.baseColor1 + wrapMode: Text.WordWrap + } + + StatusIcon { + Layout.preferredHeight: 20 + Layout.preferredWidth: 20 + color: Theme.palette.baseColor1 + icon: "keycard" + } } StatusListView { @@ -140,6 +217,7 @@ ColumnLayout { } } delegate: KeyPairItem { + id: kpOnKeycardDelegate width: ListView.view.width sensor.hoverEnabled: !model.keyPair.ownershipVerified additionalInfoForProfileKeypair: "" @@ -153,28 +231,173 @@ ColumnLayout { keyPairAccounts: model.keyPair.accounts components: [ - StatusBaseText { - font.weight: Font.Medium - font.underline: mouseArea.containsMouse - font.pixelSize: Theme.primaryTextFontSize - color: model.keyPair.ownershipVerified? Theme.palette.baseColor1 : Theme.palette.primaryColor1 - text: model.keyPair.ownershipVerified? qsTr("Signed") : qsTr("Sign") - MouseArea { - id: mouseArea - anchors.fill: parent - cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton - hoverEnabled: !model.keyPair.ownershipVerified - enabled: !model.keyPair.ownershipVerified - onEnabledChanged: { - d.reEvaluateSignedKeypairs() - } - onClicked: { - root.signSharedAddressesForKeypair(model.keyPair.keyUid) + StatusButton { + text: qsTr("Authenticate") + visible: !model.keyPair.ownershipVerified + icon.name: "keycard" + + onClicked: { + if (model.keyPair.keyUid === userProfile.keyUid) { + root.signProfileKeypairAndAllNonKeycardKeypairs() + return } + root.signSharedAddressesForKeypair(model.keyPair.keyUid) } + }, + StatusButton { + text: qsTr("Authenticated") + visible: model.keyPair.ownershipVerified + enabled: false + normalColor: "transparent" + disabledColor: "transparent" + disabledTextColor: Theme.palette.successColor1 + icon.name: "checkmark-circle" } ] + + SequentialAnimation { + running: model.keyPair.ownershipVerified + PropertyAnimation { + target: kpOnKeycardDelegate + property: "color" + to: Theme.palette.successColor3 + duration: 500 + } + PropertyAnimation { + target: kpOnKeycardDelegate + property: "color" + to: Theme.palette.baseColor2 + duration: 1500 + } + } + } + } + + Item { + visible: keycardKeypairs.visible + Layout.fillWidth: true + Layout.preferredHeight: Style.current.xlPadding + } + + RowLayout { + Layout.fillWidth: true + spacing: 8 + visible: nonProfileRegularKeypairs.visible + + StatusBaseText { + Layout.preferredWidth: !d.anyOfSelectedAddressesToRevealBelongToProfileKeypair && + d.thereAreMoreThanOneNonProfileRegularKeypairs? + 370 + : -1 + Layout.fillWidth: true + text: !d.anyOfSelectedAddressesToRevealBelongToProfileKeypair && + d.thereAreMoreThanOneNonProfileRegularKeypairs? + qsTr("Authenticate via “%1” keypair").arg(userProfile.name) + : qsTr("The following keypairs will be authenticated via “%1” keypair").arg(userProfile.name) + font.pixelSize: Constants.keycard.general.fontSize2 + color: Theme.palette.baseColor1 + wrapMode: Text.WrapAnywhere + } + + StatusButton { + Layout.rightMargin: 16 + text: qsTr("Authenticate") + visible: !d.anyOfSelectedAddressesToRevealBelongToProfileKeypair + && d.thereAreMoreThanOneNonProfileRegularKeypairs + && !d.allNonProfileRegularKeypairsSigned + icon.name: { + if (userProfile.usingBiometricLogin) { + return "touch-id" + } + + if (userProfile.isKeycardUser) { + return "keycard" + } + + return "password" + } + + onClicked: { + root.signProfileKeypairAndAllNonKeycardKeypairs() + } + } + } + + StatusListView { + id: nonProfileRegularKeypairs + Layout.fillWidth: true + Layout.preferredHeight: nonProfileRegularKeypairs.contentHeight + visible: nonProfileRegularKeypairs.model.count > 0 + spacing: Style.current.padding + model: SortFilterProxyModel { + sourceModel: root.keypairSigningModel + filters: ExpressionFilter { + expression: !model.keyPair.migratedToKeycard && model.keyPair.keyUid !== userProfile.keyUid + } + } + delegate: KeyPairItem { + id: dependantKpOnDeviceDelegate + width: ListView.view.width + sensor.hoverEnabled: false + additionalInfoForProfileKeypair: "" + + keyPairType: model.keyPair.pairType + keyPairKeyUid: model.keyPair.keyUid + keyPairName: model.keyPair.name + keyPairIcon: model.keyPair.icon + keyPairImage: model.keyPair.image + keyPairDerivedFrom: model.keyPair.derivedFrom + keyPairAccounts: model.keyPair.accounts + + components: [ + StatusButton { + Layout.rightMargin: 16 + text: qsTr("Authenticate") + visible: !d.anyOfSelectedAddressesToRevealBelongToProfileKeypair + && !d.thereAreMoreThanOneNonProfileRegularKeypairs + && !model.keyPair.ownershipVerified + icon.name: { + if (userProfile.usingBiometricLogin) { + return "touch-id" + } + + if (userProfile.isKeycardUser) { + return "keycard" + } + + return "password" + } + + onClicked: { + root.signProfileKeypairAndAllNonKeycardKeypairs() + } + }, + StatusButton { + text: qsTr("Authenticated") + visible: model.keyPair.ownershipVerified + enabled: false + normalColor: "transparent" + disabledColor: "transparent" + disabledTextColor: Theme.palette.successColor1 + icon.name: "checkmark-circle" + } + ] + + SequentialAnimation { + running: model.keyPair.ownershipVerified + PropertyAnimation { + target: dependantKpOnDeviceDelegate + property: "color" + to: Theme.palette.successColor3 + duration: 500 + } + PropertyAnimation { + target: dependantKpOnDeviceDelegate + property: "color" + to: Theme.palette.baseColor2 + duration: 1500 + } + } } } } diff --git a/ui/app/AppLayouts/Communities/popups/SharedAddressesPopup.qml b/ui/app/AppLayouts/Communities/popups/SharedAddressesPopup.qml index 5fc059abc0..20f28d748d 100644 --- a/ui/app/AppLayouts/Communities/popups/SharedAddressesPopup.qml +++ b/ui/app/AppLayouts/Communities/popups/SharedAddressesPopup.qml @@ -10,6 +10,12 @@ import AppLayouts.Communities.panels 1.0 import utils 1.0 +/**************************************************** + This file is not in use any more. + + TODO: remove it + ****************************************************/ + StatusDialog { id: root @@ -35,7 +41,7 @@ StatusDialog { signal prepareForSigning(string airdropAddress, var sharedAddresses) signal editRevealedAddresses() - signal signSharedAddressesForAllNonKeycardKeypairs() + signal signProfileKeypairAndAllNonKeycardKeypairs() signal signSharedAddressesForKeypair(string keyUid) function setOldSharedAddresses(oldSharedAddresses) { @@ -71,6 +77,8 @@ StatusDialog { property var oldSharedAddresses property string oldAirdropAddress + property int selectedSharedAddressesCount + property var selectAddressesPanelButtons: ObjectModel {} readonly property var signingPanelButtons: ObjectModel { StatusFlatButton { @@ -136,6 +144,7 @@ StatusDialog { d.displaySigningPanel = true } onSharedAddressesChanged: { + d.selectedSharedAddressesCount = sharedAddresses.length root.sharedAddressesChanged(airdropAddress, sharedAddresses) } onClose: root.close() @@ -147,12 +156,16 @@ StatusDialog { Component { id: sharedAddressesSigningPanelComponent - SharedAddressesSigningPanel { + SharedAddressesSigningPanel { + totalNumOfAddressesForSharing: root.walletAccountsModel.count + numOfSelectedAddressesForSharing: d.selectedSharedAddressesCount + + communityName: root.communityName keypairSigningModel: root.keypairSigningModel - onSignSharedAddressesForAllNonKeycardKeypairs: { - root.signSharedAddressesForAllNonKeycardKeypairs() + onSignProfileKeypairAndAllNonKeycardKeypairs: { + root.signProfileKeypairAndAllNonKeycardKeypairs() } onSignSharedAddressesForKeypair: { diff --git a/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml b/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml index 96ece0bf82..5756e3e737 100644 --- a/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml +++ b/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml @@ -305,7 +305,7 @@ Item { chatListPopupMenu: ChatContextMenuView { id: chatContextMenuView - showDebugOptions: root.store.isDebugEnabledfir + showDebugOptions: root.store.isDebugEnabledfir // TODO pass the chatModel in its entirety instead of fetching the JSOn using just the id openHandler: function (id) { @@ -536,12 +536,14 @@ Item { isInvitationPending: d.invitationPending requirementsCheckPending: root.store.requirementsCheckPending - name: communityData.name + communityName: communityData.name introMessage: communityData.introMessage - imageSrc: communityData.image + communityIcon: communityData.image accessType: communityData.access - loginType: root.store.loginType + walletAccountsModel: WalletStore.RootStore.nonWatchAccounts + canProfileProveOwnershipOfProvidedAddressesFn: WalletStore.RootStore.canProfileProveOwnershipOfProvidedAddresses + walletAssetsModel: walletAssetsStore.groupedAccountAssetsModel permissionsModel: { root.store.prepareTokenModelForCommunity(communityData.id) @@ -560,8 +562,8 @@ Item { communityIntroDialog.keypairSigningModel = root.store.communitiesModuleInst.keypairsSigningModel } - onSignSharedAddressesForAllNonKeycardKeypairs: { - root.store.signSharedAddressesForAllNonKeycardKeypairs() + onSignProfileKeypairAndAllNonKeycardKeypairs: { + root.store.signProfileKeypairAndAllNonKeycardKeypairs() } onSignSharedAddressesForKeypair: { @@ -589,9 +591,21 @@ Item { Connections { target: root.store.communitiesModuleInst - function onSharedAddressesForAllNonKeycardKeypairsSigned() { + function onAllSharedAddressesSigned() { + if (communityIntroDialog.profileProvesOwnershipOfSelectedAddresses) { + communityIntroDialog.joinCommunity() + communityIntroDialog.close() + return + } + + if (communityIntroDialog.allAddressesToRevealBelongToSingleNonProfileKeypair) { + communityIntroDialog.joinCommunity() + communityIntroDialog.close() + return + } + if (!!communityIntroDialog.replaceItem) { - communityIntroDialog.replaceLoader.item.sharedAddressesForAllNonKeycardKeypairsSigned() + communityIntroDialog.replaceLoader.item.allSigned() } } } @@ -628,7 +642,7 @@ Item { property int channelPosition: -1 property var deleteChatConfirmationDialog - + onCreateCommunityChannel: function (chName, chDescription, chEmoji, chColor, chCategoryId, hideIfPermissionsNotMet) { root.store.createCommunityChannel(chName, chDescription, chEmoji, chColor, @@ -649,7 +663,7 @@ Item { onAddPermissions: function (permissions) { for (var i = 0; i < permissions.length; i++) { - root.store.permissionsStore.createPermission(permissions[i].holdingsListModel, + root.store.permissionsStore.createPermission(permissions[i].holdingsListModel, permissions[i].permissionType, permissions[i].isPrivate, permissions[i].channelsListModel) diff --git a/ui/app/AppLayouts/Profile/views/CommunitiesView.qml b/ui/app/AppLayouts/Profile/views/CommunitiesView.qml index bf9b95e2b3..ae80dec39c 100644 --- a/ui/app/AppLayouts/Profile/views/CommunitiesView.qml +++ b/ui/app/AppLayouts/Profile/views/CommunitiesView.qml @@ -211,9 +211,9 @@ SettingsContentBase { Global.openPopup(communityIntroDialogPopup, { communityId: communityId, isInvitationPending: root.rootStore.isMyCommunityRequestPending(communityId), - name: name, + communityName: name, introMessage: introMessage, - imageSrc: imageSrc, + communityIcon: imageSrc, accessType: accessType }) } @@ -236,8 +236,9 @@ SettingsContentBase { } } - loginType: chatStore.loginType walletAccountsModel: WalletStore.RootStore.nonWatchAccounts + canProfileProveOwnershipOfProvidedAddressesFn: WalletStore.RootStore.canProfileProveOwnershipOfProvidedAddresses + walletAssetsModel: walletAssetsStore.groupedAccountAssetsModel requirementsCheckPending: root.rootStore.requirementsCheckPending permissionsModel: { @@ -257,8 +258,8 @@ SettingsContentBase { communityIntroDialog.keypairSigningModel = chatStore.communitiesModuleInst.keypairsSigningModel } - onSignSharedAddressesForAllNonKeycardKeypairs: { - chatStore.signSharedAddressesForAllNonKeycardKeypairs() + onSignProfileKeypairAndAllNonKeycardKeypairs: { + chatStore.signProfileKeypairAndAllNonKeycardKeypairs() } onSignSharedAddressesForKeypair: { @@ -280,9 +281,21 @@ SettingsContentBase { Connections { target: chatStore.communitiesModuleInst - function onSharedAddressesForAllNonKeycardKeypairsSigned() { + function onAllSharedAddressesSigned() { + if (communityIntroDialog.profileProvesOwnershipOfSelectedAddresses) { + communityIntroDialog.joinCommunity() + communityIntroDialog.close() + return + } + + if (communityIntroDialog.allAddressesToRevealBelongToSingleNonProfileKeypair) { + communityIntroDialog.joinCommunity() + communityIntroDialog.close() + return + } + if (!!communityIntroDialog.replaceItem) { - communityIntroDialog.replaceLoader.item.sharedAddressesForAllNonKeycardKeypairsSigned() + communityIntroDialog.replaceLoader.item.allSigned() } } } diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index f2c693ae6a..4476af798f 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -209,6 +209,10 @@ QtObject { } } + function canProfileProveOwnershipOfProvidedAddresses(addresses) { + return walletSection.canProfileProveOwnershipOfProvidedAddresses(JSON.stringify(addresses)) + } + function setHideSignPhraseModal(value) { localAccountSensitiveSettings.hideSignPhraseModal = value; } diff --git a/ui/app/AppLayouts/stores/RootStore.qml b/ui/app/AppLayouts/stores/RootStore.qml index a8c1421c2e..a12d28ac09 100644 --- a/ui/app/AppLayouts/stores/RootStore.qml +++ b/ui/app/AppLayouts/stores/RootStore.qml @@ -241,8 +241,8 @@ QtObject { communitiesModuleInst.prepareKeypairsForSigning(communityId, ensName, JSON.stringify(addressesToShare), airdropAddress, editMode) } - function signSharedAddressesForAllNonKeycardKeypairs() { - communitiesModuleInst.signSharedAddressesForAllNonKeycardKeypairs() + function signProfileKeypairAndAllNonKeycardKeypairs() { + communitiesModuleInst.signProfileKeypairAndAllNonKeycardKeypairs() } function signSharedAddressesForKeypair(keyUid) { diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 08be0fb504..36cc60a1d3 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -263,9 +263,9 @@ QtObject { imageSrc, accessType, isInvitationPending) { openPopup(communityIntroDialogPopup, {communityId: communityId, - name: name, + communityName: name, introMessage: introMessage, - imageSrc: imageSrc, + communityIcon: imageSrc, accessType: accessType, isInvitationPending: isInvitationPending }) @@ -275,9 +275,9 @@ QtObject { openPopup(communityIntroDialogPopup, {communityId: communityId, stackTitle: qsTr("Share addresses with %1's owner").arg(name), - name: name, + communityName: name, introMessage: qsTr("Share addresses to rejoin %1").arg(name), - imageSrc: imageSrc, + communityIcon: imageSrc, accessType: Constants.communityChatOnRequestAccess, isInvitationPending: false }) @@ -354,7 +354,7 @@ QtObject { tokenImage: tokenImage }) } - + function openConfirmHideAssetPopup(assetSymbol, assetName, assetImage, isCommunityToken) { openPopup(confirmHideAssetPopup, { assetSymbol, assetName, assetImage, isCommunityToken }) } @@ -682,9 +682,12 @@ QtObject { CommunityIntroDialog { id: communityIntroDialog property string communityId - loginType: root.rootStore.loginType + requirementsCheckPending: root.rootStore.requirementsCheckPending + walletAccountsModel: root.rootStore.walletAccountsModel + canProfileProveOwnershipOfProvidedAddressesFn: WalletStore.RootStore.canProfileProveOwnershipOfProvidedAddresses + walletAssetsModel: walletAssetsStore.groupedAccountAssetsModel permissionsModel: { root.rootStore.prepareTokenModelForCommunity(communityIntroDialog.communityId) @@ -703,8 +706,8 @@ QtObject { communityIntroDialog.keypairSigningModel = root.rootStore.communitiesModuleInst.keypairsSigningModel } - onSignSharedAddressesForAllNonKeycardKeypairs: { - root.rootStore.signSharedAddressesForAllNonKeycardKeypairs() + onSignProfileKeypairAndAllNonKeycardKeypairs: { + root.rootStore.signProfileKeypairAndAllNonKeycardKeypairs() } onSignSharedAddressesForKeypair: { @@ -739,9 +742,21 @@ QtObject { Connections { target: root.rootStore.communitiesModuleInst - function onSharedAddressesForAllNonKeycardKeypairsSigned() { + function onAllSharedAddressesSigned() { + if (communityIntroDialog.profileProvesOwnershipOfSelectedAddresses) { + communityIntroDialog.joinCommunity() + communityIntroDialog.close() + return + } + + if (communityIntroDialog.allAddressesToRevealBelongToSingleNonProfileKeypair) { + communityIntroDialog.joinCommunity() + communityIntroDialog.close() + return + } + if (!!communityIntroDialog.replaceItem) { - communityIntroDialog.replaceLoader.item.sharedAddressesForAllNonKeycardKeypairsSigned() + communityIntroDialog.replaceLoader.item.allSigned() } } } @@ -886,24 +901,9 @@ QtObject { Component { id: editSharedAddressesPopupComponent - SharedAddressesPopup { + CommunityIntroDialog { id: editSharedAddressesPopup - readonly property var oldSharedAddresses: root.rootStore.myRevealedAddressesForCurrentCommunity - readonly property string oldAirdropAddress: root.rootStore.myRevealedAirdropAddressForCurrentCommunity - - onOldSharedAddressesChanged: { - editSharedAddressesPopup.setOldSharedAddresses( - editSharedAddressesPopup.oldSharedAddresses - ) - } - - onOldAirdropAddressChanged: { - editSharedAddressesPopup.setOldAirdropAddress( - editSharedAddressesPopup.oldAirdropAddress - ) - } - property string communityId readonly property var chatStore: ChatStore.RootStore { @@ -914,10 +914,17 @@ QtObject { } } + isEditMode: true + + currentSharedAddresses: root.rootStore.myRevealedAddressesForCurrentCommunity + currentAirdropAddress: root.rootStore.myRevealedAirdropAddressForCurrentCommunity + communityName: chatStore.sectionDetails.name communityIcon: chatStore.sectionDetails.image requirementsCheckPending: root.rootStore.requirementsCheckPending - loginType: chatStore.loginType + + canProfileProveOwnershipOfProvidedAddressesFn: WalletStore.RootStore.canProfileProveOwnershipOfProvidedAddresses + walletAccountsModel: root.rootStore.walletAccountsModel walletAssetsModel: walletAssetsStore.groupedAccountAssetsModel permissionsModel: { @@ -927,16 +934,22 @@ QtObject { assetsModel: chatStore.assetsModel collectiblesModel: chatStore.collectiblesModel - onSharedAddressesChanged: root.rootStore.updatePermissionsModel( - editSharedAddressesPopup.communityId, sharedAddresses) + getCurrencyAmount: function (balance, symbol) { + return root.currencyStore.getCurrencyAmount(balance, symbol) + } + + onSharedAddressesUpdated: { + root.rootStore.updatePermissionsModel(editSharedAddressesPopup.communityId, sharedAddresses) + } + onPrepareForSigning: { root.rootStore.prepareKeypairsForSigning(editSharedAddressesPopup.communityId, "", sharedAddresses, airdropAddress, true) editSharedAddressesPopup.keypairSigningModel = root.rootStore.communitiesModuleInst.keypairsSigningModel } - onSignSharedAddressesForAllNonKeycardKeypairs: { - root.rootStore.signSharedAddressesForAllNonKeycardKeypairs() + onSignProfileKeypairAndAllNonKeycardKeypairs: { + root.rootStore.signProfileKeypairAndAllNonKeycardKeypairs() } onSignSharedAddressesForKeypair: { @@ -952,13 +965,23 @@ QtObject { Connections { target: root.rootStore.communitiesModuleInst - function onSharedAddressesForAllNonKeycardKeypairsSigned() { - editSharedAddressesPopup.sharedAddressesForAllNonKeycardKeypairsSigned() - } - } + function onAllSharedAddressesSigned() { + if (editSharedAddressesPopup.profileProvesOwnershipOfSelectedAddresses) { + editSharedAddressesPopup.editRevealedAddresses() + editSharedAddressesPopup.close() + return + } - getCurrencyAmount: function (balance, symbol) { - return root.currencyStore.getCurrencyAmount(balance, symbol) + if (editSharedAddressesPopup.allAddressesToRevealBelongToSingleNonProfileKeypair) { + editSharedAddressesPopup.editRevealedAddresses() + editSharedAddressesPopup.close() + return + } + + if (!!editSharedAddressesPopup.replaceItem) { + editSharedAddressesPopup.replaceLoader.item.allSigned() + } + } } } }, diff --git a/ui/imports/shared/popups/CommunityIntroDialog.qml b/ui/imports/shared/popups/CommunityIntroDialog.qml index 775079e4bb..dcbe0ba500 100644 --- a/ui/imports/shared/popups/CommunityIntroDialog.qml +++ b/ui/imports/shared/popups/CommunityIntroDialog.qml @@ -18,48 +18,98 @@ import SortFilterProxyModel 0.2 StatusStackModal { id: root - property string name + property bool isEditMode: false + + required property string communityName + required property string communityIcon + required property bool requirementsCheckPending + property string introMessage property int accessType - property url imageSrc + property bool isInvitationPending: false - property int loginType: Constants.LoginType.Password required property var walletAccountsModel // name, address, emoji, colorId - property var walletAssetsModel + required property var walletAssetsModel required property var permissionsModel // id, key, permissionType, holdingsListModel, channelsListModel, isPrivate, tokenCriteriaMet required property var assetsModel required property var collectiblesModel - required property bool requirementsCheckPending - 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 signSharedAddressesForAllNonKeycardKeypairs() + signal editRevealedAddresses() + signal signProfileKeypairAndAllNonKeycardKeypairs() signal signSharedAddressesForKeypair(string keyUid) signal cancelMembershipRequest() signal sharedAddressesUpdated(var sharedAddresses) width: 640 // by design padding: 0 - stackTitle: root.accessType === Constants.communityChatOnRequestAccess ? qsTr("Request to join %1").arg(name) : qsTr("Welcome to %1").arg(name) + stackTitle: root.accessType === Constants.communityChatOnRequestAccess ? + qsTr("Request to join %1").arg(root.communityName) + : qsTr("Welcome to %1").arg(root.communityName) rightButtons: [d.shareButton, finishButton] finishButton: StatusButton { - text: root.isInvitationPending ? - qsTr("Cancel Membership Request") - : root.accessType === Constants.communityChatOnRequestAccess? - qsTr("Prove ownership") - : qsTr("Join %1").arg(root.name) - + text: { + if (root.isInvitationPending) { + return qsTr("Cancel Membership Request") + } else if (root.accessType === Constants.communityChatOnRequestAccess) { + if (d.selectedSharedAddressesCount === d.totalNumOfAddressesForSharing) { + return qsTr("Share all addresses to join") + } + return qsTr("Share %n address(s) to join", "", d.selectedSharedAddressesCount) + } + return qsTr("Join %1").arg(root.communityName) + } type: root.isInvitationPending ? StatusBaseButton.Type.Danger : StatusBaseButton.Type.Normal + icon.name: { + 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() @@ -67,15 +117,48 @@ StatusStackModal { return } - root.prepareForSigning(d.selectedAirdropAddress, d.selectedSharedAddresses) - root.replace(sharedAddressesSigningPanelComponent) + 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 var tempAddressesModel: SortFilterProxyModel { + 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 + + property var initialAddressesModel: SortFilterProxyModel { sourceModel: root.walletAccountsModel sorters: [ ExpressionSorter { @@ -96,58 +179,188 @@ StatusStackModal { ] } - // all non-watched addresses by default, unless selected otherwise below in SharedAddressesPanel - property var selectedSharedAddresses: tempAddressesModel.count ? ModelUtils.modelToFlatArray(tempAddressesModel, "address") : [] - property string selectedAirdropAddress: selectedSharedAddresses.length ? selectedSharedAddresses[0] : "" + 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()[0].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.s + 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) { + let isAirdrop = i === 0 + if (root.isEditMode) { + if (root.currentSharedAddresses.indexOf(obj.address) === -1) { + continue + } + isAirdrop = obj.address.toLowerCase() === root.currentAirdropAddress.toLowerCase() + } + tmpSharedAddressesMap.set(obj.address, {keyUid: obj.keyUid, selected: true, isAirdrop: isAirdrop}) + tmpCurrentSharedAddressesMap.set(obj.address, {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: Theme.palette.baseColor2 + borderColor: "transparent" text: qsTr("Select addresses to share") - onClicked: root.replace(sharedAddressesPanelComponent) + 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 { - communityName: root.name - communityIcon: root.imageSrc - loginType: root.loginType + componentUid: d.shareAddressesUid + isEditMode: root.isEditMode + communityName: root.communityName + communityIcon: root.communityIcon requirementsCheckPending: root.requirementsCheckPending - walletAccountsModel: SortFilterProxyModel { - sourceModel: root.walletAccountsModel - sorters: [ - ExpressionSorter { - function isGenerated(modelData) { - return modelData.walletType === Constants.generatedWalletType - } - expression: { - return isGenerated(modelLeft) - } - }, - RoleSorter { - roleName: "position" - }, - RoleSorter { - roleName: "name" - } - ] - } + walletAccountsModel: d.initialAddressesModel + selectedSharedAddressesMap: d.selectedSharedAddressesMap + currentSharedAddressesMap: d.currentSharedAddressesMap + + totalNumOfAddressesForSharing: d.totalNumOfAddressesForSharing + profileProvesOwnershipOfSelectedAddresses: root.profileProvesOwnershipOfSelectedAddresses + allAddressesToRevealBelongToSingleNonProfileKeypair: root.allAddressesToRevealBelongToSingleNonProfileKeypair + walletAssetsModel: root.walletAssetsModel 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.selectedAirdropAddress = airdropAddress - d.selectedSharedAddresses = sharedAddresses - root.replaceItem = undefined // go back, unload us - } - onSharedAddressesChanged: { - root.sharedAddressesUpdated(sharedAddresses) + d.proceedToSigningOrSubmitRequest(d.shareAddressesUid) } + getCurrencyAmount: function (balance, symbol){ return root.getCurrencyAmount(balance, symbol) } @@ -158,10 +371,16 @@ StatusStackModal { id: sharedAddressesSigningPanelComponent SharedAddressesSigningPanel { + componentUid: d.signingPanelUid + isEditMode: root.isEditMode + totalNumOfAddressesForSharing: d.totalNumOfAddressesForSharing + selectedSharedAddressesMap: d.selectedSharedAddressesMap + + communityName: root.communityName keypairSigningModel: root.keypairSigningModel - onSignSharedAddressesForAllNonKeycardKeypairs: { - root.signSharedAddressesForAllNonKeycardKeypairs() + onSignProfileKeypairAndAllNonKeycardKeypairs: { + root.signProfileKeypairAndAllNonKeycardKeypairs() } onSignSharedAddressesForKeypair: { @@ -169,7 +388,11 @@ StatusStackModal { } onJoinCommunity: { - root.joinCommunity() + if (root.isEditMode) { + root.editRevealedAddresses() + } else { + root.joinCommunity() + } root.close() } } @@ -191,12 +414,12 @@ StatusStackModal { visible: ((image.status == Image.Loading) || (image.status == Image.Ready)) && !image.isError - image.source: root.imageSrc + image.source: root.communityIcon } StatusBaseText { Layout.fillWidth: true - text: root.introMessage || qsTr("Community %1 has no intro message...").arg(root.name) + text: root.introMessage || qsTr("Community %1 has no intro message...").arg(root.communityName) color: Theme.palette.directColor1 wrapMode: Text.WordWrap }