diff --git a/ui/app/AppLayouts/Wallet/WalletLayout.qml b/ui/app/AppLayouts/Wallet/WalletLayout.qml index d811efa1b6..92c33bab1a 100644 --- a/ui/app/AppLayouts/Wallet/WalletLayout.qml +++ b/ui/app/AppLayouts/Wallet/WalletLayout.qml @@ -278,7 +278,7 @@ Item { readonly property bool isCommunityCollectible: !!walletStore.currentViewedCollectible ? walletStore.currentViewedCollectible.communityId !== "" : false readonly property bool isOwnerCommunityCollectible: isCommunityCollectible ? (walletStore.currentViewedCollectible.communityPrivilegesLevel === Constants.TokenPrivilegesLevel.Owner) : false - visible: !RootStore.showAllAccounts || Global.featureFlags.swapEnabled + visible: anyActionAvailable width: parent.width height: visible ? 61: implicitHeight walletStore: RootStore @@ -298,7 +298,7 @@ Item { changingPreferredChainsEnabled: true, hasFloatingButtons: true }) - onLaunchSendModal: { + onLaunchSendModal: (fromAddress) => { if(isCommunityOwnershipTransfer) { const tokenItem = walletStore.currentViewedCollectible const ownership = StatusQUtils.ModelUtils.get(tokenItem.ownership, 0) @@ -307,24 +307,19 @@ Item { footer.communityName, tokenItem.communityImage, { - "key": tokenItem.tokenId, - "privilegesLevel": tokenItem.communityPrivilegesLevel, - "chainId": tokenItem.chainId, - "name": tokenItem.name, - "artworkSource": tokenItem.artworkSource, - "accountAddress": ownership.accountAddress, - "tokenAddress": tokenItem.contractAddress + key: tokenItem.tokenId, + privilegesLevel: tokenItem.communityPrivilegesLevel, + chainId: tokenItem.chainId, + name: tokenItem.name, + artworkSource: tokenItem.artworkSource, + accountAddress: fromAddress, + tokenAddress: tokenItem.contractAddress }, root.sendModalPopup) return } - - if (isHoldingSelected) { - const tokenItem = walletStore.currentViewedCollectible - const ownership = StatusQUtils.ModelUtils.get(tokenItem.ownership, 0) - root.sendModalPopup.preSelectedAccountAddress = ownership.accountAddress - } // Common send modal popup: + root.sendModalPopup.preSelectedAccountAddress = fromAddress root.sendModalPopup.preSelectedSendType = Constants.SendType.Transfer root.sendModalPopup.preSelectedHoldingID = walletStore.currentViewedHoldingID root.sendModalPopup.preSelectedHoldingType = walletStore.currentViewedHoldingType @@ -353,7 +348,7 @@ Item { ModelEntry { id: selectedCommunityForCollectible - sourceModel: footer.isCommunityCollectible ? root.communitiesStore.communitiesList : null + sourceModel: !!footer.walletStore.currentViewedCollectible && footer.isCommunityCollectible ? root.communitiesStore.communitiesList : null key: "id" value: footer.walletStore.currentViewedCollectible.communityId } diff --git a/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml b/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml index 54a89a202b..00a317a261 100644 --- a/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml +++ b/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml @@ -19,6 +19,8 @@ import "../popups" Rectangle { id: root + readonly property alias anyActionAvailable: d.anyActionAvailable + property var walletStore property var networkConnectionStore required property TransactionStore transactionStore @@ -28,7 +30,7 @@ Rectangle { property string communityName: "" signal launchShareAddressModal() - signal launchSendModal() + signal launchSendModal(string fromAddress) signal launchBridgeModal() signal launchSwapModal() @@ -41,21 +43,45 @@ Rectangle { walletStore.currentViewedHoldingType === Constants.TokenType.ERC1155) readonly property bool isCollectibleSoulbound: isCollectibleViewed && !!walletStore.currentViewedCollectible && walletStore.currentViewedCollectible.soulbound - readonly property bool isCollectibleOwned: d.owningAccount.available + readonly property var collectibleOwnership: isCollectibleViewed && walletStore.currentViewedCollectible ? + walletStore.currentViewedCollectible.ownership : null - readonly property bool hideCollectibleTransferActions: isCollectibleViewed && !isCollectibleOwned + readonly property string userOwnedAddressForCollectible: !!walletStore.currentViewedHoldingID ? getFirstUserOwnedAddress(collectibleOwnership, root.walletStore.nonWatchAccounts) : "" - property string collectibleOwnerAddress - Binding on collectibleOwnerAddress { - when: d.isCollectibleViewed && !!root.walletStore.currentViewedCollectible && !!root.walletStore.currentViewedCollectible.ownership.ModelCount.count - value: SQUtils.ModelUtils.get(root.walletStore.currentViewedCollectible.ownership, 0).accountAddress - restoreMode: Binding.RestoreBindingOrValue - } + readonly property bool hideCollectibleTransferActions: isCollectibleViewed && !userOwnedAddressForCollectible - readonly property ModelEntry owningAccount: ModelEntry { - sourceModel: d.isCollectibleViewed ? root.walletStore.nonWatchAccounts : null - key: "address" - value: d.collectibleOwnerAddress ?? "" + /// Actions available + readonly property bool anyActionAvailable: sendActionAvailable + || receiveActionAvailable + || bridgeActionAvailable + || buyActionAvailable + || swapActionAvailable + + readonly property bool sendActionAvailable: !walletStore.overview.isWatchOnlyAccount + && walletStore.overview.canSend + && !d.hideCollectibleTransferActions + + readonly property bool receiveActionAvailable: !walletStore.showAllAccounts + + readonly property bool bridgeActionAvailable: !walletStore.overview.isWatchOnlyAccount + && !root.isCommunityOwnershipTransfer + && walletStore.overview.canSend + && !root.walletStore.showAllAccounts + && !d.hideCollectibleTransferActions + + readonly property bool buyActionAvailable: !root.isCommunityOwnershipTransfer && !root.walletStore.showAllAccounts + + readonly property bool swapActionAvailable: Global.featureFlags.swapEnabled && !walletStore.overview.isWatchOnlyAccount && !d.hideCollectibleTransferActions + + function getFirstUserOwnedAddress(ownershipModel, accountsModel) { + if (!ownershipModel) return "" + + for (let i = 0; i < ownershipModel.rowCount(); i++) { + const accountAddress = SQUtils.ModelUtils.get(ownershipModel, i, "accountAddress") + if (SQUtils.ModelUtils.contains(accountsModel, "address", accountAddress, Qt.CaseInsensitive)) + return accountAddress + } + return "" } } @@ -65,12 +91,14 @@ Rectangle { } RowLayout { + id: layout anchors.centerIn: parent height: parent.height - width: Math.min(parent.width, implicitWidth) + width: Math.min(root.width, implicitWidth) spacing: Style.current.padding StatusFlatButton { + id: sendButton Layout.fillWidth: true Layout.maximumWidth: implicitWidth objectName: "walletFooterSendButton" @@ -78,20 +106,17 @@ Rectangle { text: root.isCommunityOwnershipTransfer ? qsTr("Send Owner token to transfer %1 Community ownership").arg(root.communityName) : qsTr("Send") interactive: !d.isCollectibleSoulbound && networkConnectionStore.sendBuyBridgeEnabled onClicked: { - if (!!root.walletStore.selectedAddress) - root.transactionStore.setSenderAccount(root.walletStore.selectedAddress) - root.launchSendModal() + root.transactionStore.setSenderAccount(root.walletStore.selectedAddress) + root.launchSendModal(d.userOwnedAddressForCollectible) } tooltip.text: d.isCollectibleSoulbound ? qsTr("Soulbound collectibles cannot be sent to another wallet") : networkConnectionStore.sendBuyBridgeToolTipText - visible: !walletStore.overview.isWatchOnlyAccount - && walletStore.overview.canSend - && !d.hideCollectibleTransferActions + visible: d.sendActionAvailable } StatusFlatButton { icon.name: "receive" text: qsTr("Receive") - visible: !root.walletStore.showAllAccounts + visible: d.receiveActionAvailable onClicked: function () { root.transactionStore.setReceiverAccount(root.walletStore.selectedAddress) launchShareAddressModal() @@ -104,17 +129,13 @@ Rectangle { interactive: !d.isCollectibleSoulbound && networkConnectionStore.sendBuyBridgeEnabled onClicked: root.launchBridgeModal() tooltip.text: d.isCollectibleSoulbound ? qsTr("Soulbound collectibles cannot be bridged to another wallet") : networkConnectionStore.sendBuyBridgeToolTipText - visible: !walletStore.overview.isWatchOnlyAccount - && !root.isCommunityOwnershipTransfer - && walletStore.overview.canSend - && !root.walletStore.showAllAccounts - && !d.hideCollectibleTransferActions + visible: d.bridgeActionAvailable } StatusFlatButton { id: buySellBtn - visible: !root.isCommunityOwnershipTransfer && !root.walletStore.showAllAccounts + visible: d.buyActionAvailable icon.name: "token" text: qsTr("Buy") onClicked: Global.openBuyCryptoModalRequested() @@ -124,7 +145,7 @@ Rectangle { id: swap interactive: !d.isCollectibleSoulbound && networkConnectionStore.sendBuyBridgeEnabled - visible: Global.featureFlags.swapEnabled && !walletStore.overview.isWatchOnlyAccount && !d.hideCollectibleTransferActions + visible: d.swapActionAvailable tooltip.text: d.isCollectibleSoulbound ? qsTr("Soulbound collectibles cannot be swapped") : networkConnectionStore.sendBuyBridgeToolTipText icon.name: "swap" text: qsTr("Swap") diff --git a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml index 54f492b262..56ff9f2992 100644 --- a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml +++ b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml @@ -39,7 +39,7 @@ ColumnLayout { property bool isError: false // Indicates an error occurred while updating/fetching the collectibles list signal collectibleClicked(int chainId, string contractAddress, string tokenId, string uid, int tokenType) - signal sendRequested(string symbol, int tokenType) + signal sendRequested(string symbol, int tokenType, string fromAddress) signal receiveRequested(string symbol) signal switchToCommunityRequested(string communityId) signal manageTokensRequested() @@ -197,6 +197,17 @@ ColumnLayout { return AmountsArithmetic.toNumber(balance) } + function getFirstUserOwnedAddress(ownershipModel) { + if (!ownershipModel) return "" + + for (let i = 0; i < ownershipModel.rowCount(); i++) { + const accountAddress = ModelUtils.get(ownershipModel, i, "accountAddress") + if (ModelUtils.contains(root.ownedAccountsModel, "address", accountAddress, Qt.CaseInsensitive)) + return accountAddress + } + return "" + } + property FunctionAggregator hasAllTimestampsAggregator: FunctionAggregator { model: d.allCollectiblesModel initialValue: true @@ -467,17 +478,12 @@ ColumnLayout { onClicked: root.collectibleClicked(model.chainId, model.contractAddress, model.tokenId, model.symbol, model.tokenType) onRightClicked: { - let ownedByUser = false - const ownedByAccount = ModelUtils.get(model.ownership, 0) - if (!!ownedByAccount && !!ownedByAccount.accountAddress) { - ownedByUser = ModelUtils.contains(root.ownedAccountsModel, "address", ownedByAccount.accountAddress, Qt.CaseInsensitive) - } - + const userOwnedAddress = d.getFirstUserOwnedAddress(model.ownership) Global.openMenu(tokenContextMenu, this, {symbol: model.symbol, tokenName: model.name, tokenImage: model.imageUrl, communityId: model.communityId, communityName: model.communityName, communityImage: model.communityImage, tokenType: model.tokenType, - soulbound: model.soulbound, ownedByUser: ownedByUser}) + soulbound: model.soulbound, userOwnedAddress: userOwnedAddress}) } onSwitchToCommunityRequested: (communityId) => root.switchToCommunityRequested(communityId) } @@ -495,8 +501,9 @@ ColumnLayout { property string communityId property string communityName property string communityImage + property string userOwnedAddress property int tokenType - property bool ownedByUser + property bool ownedByUser: !!userOwnedAddress property bool soulbound // Show send button for owned collectibles @@ -508,7 +515,7 @@ ColumnLayout { visibleOnDisabled: true icon.name: "send" text: qsTr("Send") - onTriggered: root.sendRequested(symbol, tokenType) + onTriggered: root.sendRequested(tokenMenu.symbol, tokenMenu.tokenType, tokenMenu.userOwnedAddress) } onObjectAdded: tokenMenu.insertAction(0, object) onObjectRemoved: tokenMenu.removeAction(0) diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index ad7fcc401a..504176bcdd 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -265,31 +265,26 @@ RightTabBaseView { stack.currentIndex = 1 } - onSendRequested: (symbol, tokenType) => { + onSendRequested: (symbol, tokenType, fromAddress) => { const collectible = ModelUtils.getByKey(controller.sourceModel, "symbol", symbol) - if (collectible.communityPrivilegesLevel === Constants.TokenPrivilegesLevel.Owner) { - const ownerAccountItem = ModelUtils.get(collectible.ownership, 0) + if (!!collectible && collectible.communityPrivilegesLevel === Constants.TokenPrivilegesLevel.Owner) { Global.openTransferOwnershipPopup(collectible.communityId, collectible.communityName, collectible.communityImage, { - "key": collectible.tokenId, - "privilegesLevel": collectible.communityPrivilegesLevel, - "chainId": collectible.chainId, - "name": collectible.name, - "artworkSource": collectible.communityImage, - "accountAddress": ownerAccountItem.accountAddress, - "tokenAddress": collectible.contractAddress + key: collectible.tokenId, + privilegesLevel: collectible.communityPrivilegesLevel, + chainId: collectible.chainId, + name: collectible.name, + artworkSource: collectible.communityImage, + accountAddress: fromAddress, + tokenAddress: collectible.contractAddress }, root.sendModal) return - - } - - if (!!collectible) { - const ownerAccountItem = ModelUtils.get(collectible.ownership, 0) - root.sendModal.preSelectedAccountAddress = ownerAccountItem.accountAddress } + + root.sendModal.preSelectedAccountAddress = fromAddress root.sendModal.preSelectedHoldingID = symbol root.sendModal.preSelectedHoldingType = tokenType root.sendModal.preSelectedSendType = tokenType === Constants.TokenType.ERC721 ? diff --git a/ui/imports/shared/popups/send/SendModal.qml b/ui/imports/shared/popups/send/SendModal.qml index 2d7b6a7091..521349e071 100644 --- a/ui/imports/shared/popups/send/SendModal.qml +++ b/ui/imports/shared/popups/send/SendModal.qml @@ -107,7 +107,7 @@ StatusDialog { return Constants.NoError } - readonly property double maxFiatBalance: isSelectedHoldingValidAsset ? selectedHolding.currentCurrencyBalance : 0 + readonly property double maxFiatBalance: isSelectedHoldingValidAsset ? selectedHolding.currencyBalance : 0 readonly property double maxCryptoBalance: isSelectedHoldingValidAsset ? selectedHolding.currentBalance : 0 readonly property double maxInputBalance: amountToSend.fiatMode ? maxFiatBalance : maxCryptoBalance @@ -194,9 +194,9 @@ StatusDialog { } // To be removed once bridge is splitted to a different component: - if(d.isBridgeTx && !!popup.preSelectedAccount) { + if(d.isBridgeTx && !!popup.preSelectedAccountAddress) { // Default preselected type is `Helpers.RecipientAddressObjectType.Address` coinciding with bridge usecase - popup.preSelectedRecipient = popup.preSelectedAccount.address + popup.preSelectedRecipient = popup.preSelectedAccountAddress } if (!!popup.preSelectedHoldingID diff --git a/ui/imports/shared/views/HistoryView.qml b/ui/imports/shared/views/HistoryView.qml index 7ad8c05791..aeb6ca8622 100644 --- a/ui/imports/shared/views/HistoryView.qml +++ b/ui/imports/shared/views/HistoryView.qml @@ -311,7 +311,7 @@ ColumnLayout { if (!overview.isWatchOnlyAccount && !tx) return false return WalletStores.RootStore.isTxRepeatable(tx) - } + } onTriggered: { if (!tx)