From 81d7ca32b0a5d8dfdc656be14b85182239e2fe1e Mon Sep 17 00:00:00 2001 From: Khushboo Mehta Date: Mon, 13 May 2024 19:23:01 +0200 Subject: [PATCH] feat(@desktop/wallet): Swap modal launch flows fixes #14628 --- storybook/pages/SwapModalPage.qml | 132 ++++++++++++++++++ ui/app/AppLayouts/Wallet/WalletLayout.qml | 25 +++- .../AppLayouts/Wallet/panels/WalletFooter.qml | 26 ++-- .../Wallet/popups/swap/SwapFormData.qml | 15 ++ .../Wallet/popups/swap/SwapModal.qml | 55 ++++++++ ui/app/AppLayouts/Wallet/popups/swap/qmldir | 2 + ui/app/AppLayouts/Wallet/stores/RootStore.qml | 8 +- .../AppLayouts/Wallet/views/LeftTabView.qml | 1 + .../AppLayouts/Wallet/views/RightTabView.qml | 6 +- ui/app/mainui/Popups.qml | 12 ++ ui/imports/shared/views/AssetsView.qml | 12 +- ui/imports/utils/Global.qml | 3 + 12 files changed, 280 insertions(+), 17 deletions(-) create mode 100644 storybook/pages/SwapModalPage.qml create mode 100644 ui/app/AppLayouts/Wallet/popups/swap/SwapFormData.qml create mode 100644 ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml create mode 100644 ui/app/AppLayouts/Wallet/popups/swap/qmldir diff --git a/storybook/pages/SwapModalPage.qml b/storybook/pages/SwapModalPage.qml new file mode 100644 index 0000000000..5917eb997e --- /dev/null +++ b/storybook/pages/SwapModalPage.qml @@ -0,0 +1,132 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Utils 0.1 +import StatusQ.Controls 0.1 + +import utils 1.0 +import Storybook 1.0 +import Models 1.0 + +import AppLayouts.Wallet.stores 1.0 +import AppLayouts.Wallet.popups.swap 1.0 + +SplitView { + id: root + + Logs { id: logs } + + orientation: Qt.Horizontal + + QtObject { + id: d + readonly property var tokenBySymbolModel: TokensBySymbolModel {} + } + + PopupBackground { + id: popupBg + + property var popupIntance: null + + SplitView.fillWidth: true + SplitView.fillHeight: true + + Button { + id: reopenButton + anchors.centerIn: parent + text: "Reopen" + enabled: !swapModal.visible + + onClicked: swapModal.open() + } + + SwapModal { + id: swapModal + visible: true + formData: SwapFormData { + selectedAccountIndex: accountComboBox.currentIndex + selectedNetworkChainId: { + if (NetworksModel.flatNetworks.count > 0) { + return ModelUtils.get(NetworksModel.flatNetworks, networksComboBox.currentIndex).chainId + } + return -1 + } + fromTokensKey: { + if (d.tokenBySymbolModel.count > 0) { + return ModelUtils.get(d.tokenBySymbolModel, fromTokenComboBox.currentIndex).key + } + return "" + } + fromTokenAmount: swapInput.text + toTokenKey: { + if (d.tokenBySymbolModel.count > 0) { + return ModelUtils.get(d.tokenBySymbolModel, toTokenComboBox.currentIndex).key + } + return "" + } + } + } + } + + Pane { + id: rightPanel + SplitView.minimumWidth: 300 + SplitView.preferredWidth: 300 + SplitView.minimumHeight: 300 + + ColumnLayout { + spacing: 10 + + StatusBaseText { + text:"Selected Account" + } + ComboBox { + id: accountComboBox + textRole: "name" + model: WalletSendAccountsModel {} + currentIndex: 0 + } + + StatusBaseText { + text: "Selected Network" + } + ComboBox { + id: networksComboBox + textRole: "chainName" + model: NetworksModel.flatNetworks + currentIndex: 0 + } + + StatusBaseText { + text: "From Token" + } + ComboBox { + id: fromTokenComboBox + textRole: "name" + model: d.tokenBySymbolModel + currentIndex: 0 + } + + StatusInput { + id: swapInput + Layout.preferredWidth: 100 + label: "Token mount to swap" + text: "100" + } + + StatusBaseText { + text: "To Token" + } + ComboBox { + id: toTokenComboBox + textRole: "name" + model: d.tokenBySymbolModel + currentIndex: 1 + } + } + } +} + +// category: Popups diff --git a/ui/app/AppLayouts/Wallet/WalletLayout.qml b/ui/app/AppLayouts/Wallet/WalletLayout.qml index be9b198708..ab515d5941 100644 --- a/ui/app/AppLayouts/Wallet/WalletLayout.qml +++ b/ui/app/AppLayouts/Wallet/WalletLayout.qml @@ -14,6 +14,7 @@ import "panels" import "views" import "stores" import "controls" +import "popups/swap" Item { id: root @@ -134,6 +135,15 @@ Item { RootStore.backButtonName = "" } + property SwapFormData swapFormData: SwapFormData { + selectedAccountIndex: RootStore.showAllAccounts ? 0 : leftTab.currentAccountIndex + selectedNetworkChainId: { + // Without this when we switch testnet mode, the correct network is not evaluated + RootStore.areTestNetworksEnabled + return StatusQUtils.ModelUtils.get(RootStore.filteredFlatModel, 0).chainId + } + } + function displayAllAddresses() { RootStore.showSavedAddresses = false RootStore.selectedAddress = "" @@ -203,6 +213,10 @@ Item { changingPreferredChainsEnabled: true, hasFloatingButtons: true }) + onLaunchSwapModal: { + d.swapFormData.fromTokensKey = tokensKey + Global.openSwapModalRequested(d.swapFormData) + } } } @@ -261,9 +275,9 @@ 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 + visible: !RootStore.showAllAccounts || Global.featureFlags.swapEnabled width: parent.width - height: RootStore.showAllAccounts ? implicitHeight : 61 + height: visible ? 61: implicitHeight walletStore: RootStore networkConnectionStore: root.networkConnectionStore isCommunityOwnershipTransfer: footer.isHoldingSelected && footer.isOwnerCommunityCollectible @@ -314,6 +328,13 @@ Item { root.sendModalPopup.onlyAssets = true root.sendModalPopup.open() } + onLaunchSwapModal: { + d.swapFormData.fromTokensKey = "" + if(!!walletStore.currentViewedHoldingTokensKey && walletStore.currentViewedHoldingType === Constants.TokenType.ERC20) { + d.swapFormData.fromTokensKey = walletStore.currentViewedHoldingTokensKey + } + Global.openSwapModalRequested(d.swapFormData) + } } } diff --git a/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml b/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml index 8e50c4a4e3..5d150893e8 100644 --- a/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml +++ b/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml @@ -25,13 +25,16 @@ Rectangle { signal launchShareAddressModal() signal launchSendModal() signal launchBridgeModal() + signal launchSwapModal() color: Theme.palette.statusAppLayout.rightPanelBackgroundColor QtObject { id: d - readonly property bool isCollectibleViewed: !!walletStore.currentViewedCollectible - readonly property bool isCollectibleSoulbound: d.isCollectibleViewed && walletStore.currentViewedCollectible.soulbound + readonly property bool isCollectibleViewed: !!walletStore.currentViewedHoldingID && + (walletStore.currentViewedHoldingType === Constants.TokenType.ERC721 || + walletStore.currentViewedHoldingType === Constants.TokenType.ERC1155) + readonly property bool isCollectibleSoulbound: isCollectibleViewed && !!walletStore.currentViewedCollectible && walletStore.currentViewedCollectible.soulbound } StatusModalDivider { @@ -51,12 +54,13 @@ Rectangle { interactive: !d.isCollectibleSoulbound && networkConnectionStore.sendBuyBridgeEnabled onClicked: root.launchSendModal() tooltip.text: d.isCollectibleSoulbound ? qsTr("Soulbound collectibles cannot be sent to another wallet") : networkConnectionStore.sendBuyBridgeToolTipText - visible: !walletStore.overview.isWatchOnlyAccount && walletStore.overview.canSend + visible: !walletStore.overview.isWatchOnlyAccount && walletStore.overview.canSend && !root.walletStore.showAllAccounts } StatusFlatButton { icon.name: "receive" text: qsTr("Receive") + visible: !root.walletStore.showAllAccounts onClicked: function () { launchShareAddressModal() } @@ -65,16 +69,16 @@ Rectangle { StatusFlatButton { icon.name: "bridge" text: qsTr("Bridge") - interactive: networkConnectionStore.sendBuyBridgeEnabled + interactive: !d.isCollectibleSoulbound && networkConnectionStore.sendBuyBridgeEnabled onClicked: root.launchBridgeModal() - tooltip.text: networkConnectionStore.sendBuyBridgeToolTipText - visible: !walletStore.overview.isWatchOnlyAccount && !root.isCommunityOwnershipTransfer && walletStore.overview.canSend + 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 } StatusFlatButton { id: buySellBtn - visible: !root.isCommunityOwnershipTransfer + visible: !root.isCommunityOwnershipTransfer && !root.walletStore.showAllAccounts icon.name: "token" text: qsTr("Buy") onClicked: function () { @@ -85,12 +89,12 @@ Rectangle { StatusFlatButton { id: swap - visible: !d.isCollectibleSoulbound && networkConnectionStore.sendBuyBridgeEnabled && Global.featureFlags.swapEnabled + interactive: !d.isCollectibleSoulbound && networkConnectionStore.sendBuyBridgeEnabled + visible: Global.featureFlags.swapEnabled && !walletStore.overview.isWatchOnlyAccount + tooltip.text: d.isCollectibleSoulbound ? qsTr("Soulbound collectibles cannot be swapped") : networkConnectionStore.sendBuyBridgeToolTipText icon.name: "swap" text: qsTr("Swap") - onClicked: function () { - console.warn("TODO: launch swap modal...") - } + onClicked: root.launchSwapModal() } } diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapFormData.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapFormData.qml new file mode 100644 index 0000000000..770f6a47d0 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapFormData.qml @@ -0,0 +1,15 @@ +import QtQuick 2.13 + +import utils 1.0 + +/* This is used so that there is an easy way to fill in the data +needed to launch the Swap Modal with pre-filled requisites. */ +QtObject { + id: root + property int selectedAccountIndex: 0 + property int selectedNetworkChainId: -1 + property string fromTokensKey: "" + property string fromTokenAmount: "" + property string toTokenKey: "" +} + diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml new file mode 100644 index 0000000000..49d295f517 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml @@ -0,0 +1,55 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.15 + +import utils 1.0 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Popups.Dialog 0.1 + +StatusDialog { + id: root + title: qsTr("Swap") + + // This should be the only property which should be used when being launched from elsewhere + property SwapFormData formData: SwapFormData {} + + bottomPadding: 16 + padding: 0 + + background: StatusDialogBackground { + implicitHeight: 846 + implicitWidth: 556 + color: Theme.palette.baseColor3 + } + + contentItem: Column { + spacing: 5 + StatusBaseText { + Layout.alignment: Qt.AlignHCenter + text: "This area is a temporary placeholder" + font.bold: true + } + StatusBaseText { + Layout.alignment: Qt.AlignHCenter + text: "Selected account index: %1".arg(formData.selectedAccountIndex) + } + StatusBaseText { + Layout.alignment: Qt.AlignHCenter + text: "Selected network: %1".arg(formData.selectedNetworkChainId) + } + StatusBaseText { + Layout.alignment: Qt.AlignHCenter + text: "Selected from token: %1".arg(formData.fromTokensKey) + } + StatusBaseText { + Layout.alignment: Qt.AlignHCenter + text: "from token amount: %1".arg(formData.fromTokenAmount) + } + StatusBaseText { + Layout.alignment: Qt.AlignHCenter + text: "Selected to token: %1".arg(formData.toTokenKey) + } + } +} + diff --git a/ui/app/AppLayouts/Wallet/popups/swap/qmldir b/ui/app/AppLayouts/Wallet/popups/swap/qmldir new file mode 100644 index 0000000000..01dff1bee0 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/popups/swap/qmldir @@ -0,0 +1,2 @@ +SwapModal 1.0 SwapModal.qml +SwapFormData 1.0 SwapFormData.qml diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index a2bf7c4182..bb70114d0c 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -152,20 +152,26 @@ QtObject { property var cryptoRampServicesModel: walletSectionBuySellCrypto.model function resetCurrentViewedHolding(type) { + currentViewedHoldingTokensKey = "" currentViewedHoldingID = "" currentViewedHoldingType = type } function setCurrentViewedHoldingType(type) { + currentViewedHoldingTokensKey = "" currentViewedHoldingID = "" currentViewedHoldingType = type } - function setCurrentViewedHolding(id, type) { + function setCurrentViewedHolding(id, tokensKey, type) { + currentViewedHoldingTokensKey = tokensKey currentViewedHoldingID = id currentViewedHoldingType = type } + property string currentViewedHoldingTokensKey: "" + /* TODO: should get rid if this eventually, we shouldnt be using token symbols + everywhere. Adding a new one currentViewedHoldingTokensKey aboce to not impact send/bridge flows */ property string currentViewedHoldingID: "" property int currentViewedHoldingType readonly property var currentViewedCollectible: collectiblesStore.detailedCollectible diff --git a/ui/app/AppLayouts/Wallet/views/LeftTabView.qml b/ui/app/AppLayouts/Wallet/views/LeftTabView.qml index 1dfcf000d2..f6531e2fb5 100644 --- a/ui/app/AppLayouts/Wallet/views/LeftTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/LeftTabView.qml @@ -27,6 +27,7 @@ Rectangle { id: root objectName: "walletLeftTab" + property alias currentAccountIndex: walletAccountsListView.currentIndex property var networkConnectionStore property var selectAllAccounts: function(){} property var changeSelectedAccount: function(){} diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index 04bb0c078e..8f21d0c44f 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -22,6 +22,7 @@ RightTabBaseView { property alias currentTabIndex: walletTabBar.currentIndex signal launchShareAddressModal() + signal launchSwapModal(string tokensKey) function resetView() { resetStack() @@ -152,7 +153,7 @@ RightTabBaseView { filterVisible: filterButton.checked onAssetClicked: { assetDetailView.token = token - RootStore.setCurrentViewedHolding(token.symbol, Constants.TokenType.ERC20) + RootStore.setCurrentViewedHolding(token.symbol, token.tokensKey, Constants.TokenType.ERC20) stack.currentIndex = 2 } onSendRequested: (symbol) => { @@ -166,6 +167,7 @@ RightTabBaseView { onSwitchToCommunityRequested: (communityId) => Global.switchToCommunity(communityId) onManageTokensRequested: Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.wallet, Constants.walletSettingsSubsection.manageAssets) + onLaunchSwapModal: root.launchSwapModal(tokensKey) } } Component { @@ -178,7 +180,7 @@ RightTabBaseView { filterVisible: filterButton.checked onCollectibleClicked: { RootStore.collectiblesStore.getDetailedCollectible(chainId, contractAddress, tokenId) - RootStore.setCurrentViewedHolding(uid, tokenType) + RootStore.setCurrentViewedHolding(uid, uid, tokenType) d.detailedCollectibleActivityController.resetFilter() d.detailedCollectibleActivityController.setFilterAddressesJson(JSON.stringify(RootStore.addressFilters.split(":"))) d.detailedCollectibleActivityController.setFilterChainsJson(JSON.stringify([chainId]), false) diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 1027809559..e95e07c860 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -15,6 +15,7 @@ import AppLayouts.Chat.popups 1.0 import AppLayouts.Profile.popups 1.0 import AppLayouts.Communities.popups 1.0 import AppLayouts.Communities.helpers 1.0 +import AppLayouts.Wallet.popups.swap 1.0 import AppLayouts.Wallet.stores 1.0 as WalletStore import AppLayouts.Chat.stores 1.0 as ChatStore @@ -89,6 +90,7 @@ QtObject { Global.openConfirmHideAssetPopup.connect(openConfirmHideAssetPopup) Global.openConfirmHideCollectiblePopup.connect(openConfirmHideCollectiblePopup) Global.openCommunityMemberMessagesPopupRequested.connect(openCommunityMemberMessagesPopup) + Global.openSwapModalRequested.connect(openSwapModal) } property var currentPopup @@ -384,6 +386,10 @@ QtObject { }) } + function openSwapModal(parameters) { + openPopup(swapModal, {formData: parameters}) + } + readonly property list _components: [ Component { id: removeContactConfirmationDialog @@ -1230,6 +1236,12 @@ QtObject { CommunityMemberMessagesPopup { onClosed: destroy() } + }, + Component { + id: swapModal + SwapModal { + onClosed: destroy() + } } ] } diff --git a/ui/imports/shared/views/AssetsView.qml b/ui/imports/shared/views/AssetsView.qml index e494e3a240..be2d47a6f6 100644 --- a/ui/imports/shared/views/AssetsView.qml +++ b/ui/imports/shared/views/AssetsView.qml @@ -43,6 +43,7 @@ ColumnLayout { signal assetClicked(var token) signal sendRequested(string symbol) signal receiveRequested(string symbol) + signal launchSwapModal(string tokensKey) signal switchToCommunityRequested(string communityId) signal manageTokensRequested() @@ -300,7 +301,8 @@ ColumnLayout { } else if (mouse.button === Qt.RightButton) { Global.openMenu(tokenContextMenu, this, {symbol: modelData.symbol, assetName: modelData.name, assetImage: symbolUrl, - communityId: modelData.communityId, communityName: modelData.communityName, communityImage: modelData.communityImage}) + communityId: modelData.communityId, communityName: modelData.communityName, + communityImage: modelData.communityImage, tokensKey: modelData.tokensKey}) } } onSwitchToCommunityRequested: root.switchToCommunityRequested(communityId) @@ -318,6 +320,7 @@ ColumnLayout { StatusMenu { onClosed: destroy() + property string tokensKey property string symbol property string assetName property string assetImage @@ -337,6 +340,13 @@ ColumnLayout { text: qsTr("Receive") onTriggered: root.receiveRequested(symbol) } + StatusAction { + icon.name: "swap" + text: qsTr("Swap") + enabled: Global.featureFlags.swapEnabled && !root.overview.isWatchOnlyAccount + visibleOnDisabled: Global.featureFlags.swapEnabled + onTriggered: root.launchSwapModal(tokensKey) + } StatusMenuSeparator {} StatusAction { icon.name: "settings" diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index 4607828a3f..6d7477396c 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -104,6 +104,9 @@ QtObject { signal openTestnetPopup() + // Swap + signal openSwapModalRequested(var formDataParams) + ///////////////////////////////////////////////////// // WalletConnect POC - to remove signal popupWalletConnect()