From 3f77a1631780fc574b07580b22d26655fde3f05a Mon Sep 17 00:00:00 2001 From: Khushboo Mehta Date: Wed, 29 May 2024 17:42:26 +0200 Subject: [PATCH] feat(@desktop/wallet): Implement Network selection in SwapModal fixes #14750 --- storybook/pages/SwapModalPage.qml | 91 ++++++++------ storybook/qmlTests/tests/tst_SwapModal.qml | 113 +++++++++++++++++- .../src/Models/GroupedAccountsAssetsModel.qml | 10 +- ui/app/AppLayouts/Wallet/WalletLayout.qml | 2 + .../Wallet/controls/NetworkFilter.qml | 1 + .../Wallet/popups/swap/SwapModal.qml | 66 +++++++--- .../Wallet/popups/swap/SwapModalAdaptor.qml | 15 +-- ui/imports/shared/popups/send/SendModal.qml | 13 +- .../popups/send/controls/HeaderTitleText.qml | 13 ++ ui/imports/shared/popups/send/controls/qmldir | 1 + 10 files changed, 245 insertions(+), 80 deletions(-) create mode 100644 ui/imports/shared/popups/send/controls/HeaderTitleText.qml diff --git a/storybook/pages/SwapModalPage.qml b/storybook/pages/SwapModalPage.qml index a29c115e2d..e4c5af6252 100644 --- a/storybook/pages/SwapModalPage.qml +++ b/storybook/pages/SwapModalPage.qml @@ -32,6 +32,16 @@ SplitView { sourceModel: d.flatNetworksModel filters: ValueFilter { roleName: "isTest"; value: areTestNetworksEnabledCheckbox.checked } } + function launchPopup() { + swapModal.createObject(root) + } + function getNetwork() { + let selectedChain = -1 + if (networksComboBox.model.count > 0 && networksComboBox.currentIndex >= 0) { + selectedChain = ModelUtils.get(networksComboBox.model, networksComboBox.currentIndex, "chainId") + } + return selectedChain + } } PopupBackground { @@ -48,50 +58,52 @@ SplitView { text: "Reopen" enabled: !swapModal.visible - onClicked: swapModal.open() + onClicked: d.launchPopup() } - SwapModal { - id: swapModal - visible: true - swapInputParamsForm: SwapInputParamsForm { - selectedAccountIndex: accountComboBox.currentIndex - selectedNetworkChainId: { - if (networksComboBox.model.count > 0 && networksComboBox.currentIndex >= 0) { - return ModelUtils.get(networksComboBox.model, 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 "" + Component.onCompleted: d.launchPopup() + + SwapInputParamsForm { + id: swapInputForm + selectedAccountIndex: accountComboBox.currentIndex + selectedNetworkChainId: d.getNetwork() + fromTokensKey: { + if (d.tokenBySymbolModel.count > 0) { + return ModelUtils.get(d.tokenBySymbolModel, fromTokenComboBox.currentIndex, "key") } + return "" } - swapAdaptor: SwapModalAdaptor { - swapStore: SwapStore { - readonly property var accounts: d.accountsModel - readonly property var flatNetworks: d.flatNetworksModel - readonly property bool areTestNetworksEnabled: areTestNetworksEnabledCheckbox.checked + fromTokenAmount: swapInput.text + toTokenKey: { + if (d.tokenBySymbolModel.count > 0) { + return ModelUtils.get(d.tokenBySymbolModel, toTokenComboBox.currentIndex, "key") } - walletAssetsStore: WalletAssetsStore { - id: thisWalletAssetStore - walletTokensStore: TokensStore { - readonly property var plainTokensBySymbolModel: TokensBySymbolModel {} + return "" + } + } + + Component { + id: swapModal + SwapModal { + visible: true + swapInputParamsForm: swapInputForm + swapAdaptor: SwapModalAdaptor { + swapStore: SwapStore { + readonly property var accounts: d.accountsModel + readonly property var flatNetworks: d.flatNetworksModel + readonly property bool areTestNetworksEnabled: areTestNetworksEnabledCheckbox.checked + } + walletAssetsStore: WalletAssetsStore { + id: thisWalletAssetStore + walletTokensStore: TokensStore { + readonly property var plainTokensBySymbolModel: TokensBySymbolModel {} + } + readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {} + assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel + } + currencyStore: CurrenciesStore {} + swapFormData: swapInputForm } - readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {} - assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel - } - currencyStore: CurrenciesStore {} - swapFormData: swapModal.swapInputParamsForm } } } @@ -129,7 +141,7 @@ SplitView { } currentIndex: 0 onCurrentIndexChanged: { - swapModal.swapInputParamsForm.selectedAccountIndex = currentIndex + swapInputForm.selectedAccountIndex = currentIndex } } @@ -142,6 +154,7 @@ SplitView { model: d.filteredNetworksModel currentIndex: 0 onCountChanged: currentIndex = 0 + onCurrentIndexChanged: swapInputForm.selectedNetworkChainId = d.getNetwork() } StatusBaseText { diff --git a/storybook/qmlTests/tests/tst_SwapModal.qml b/storybook/qmlTests/tests/tst_SwapModal.qml index e2d1bb1be5..6a55c2687a 100644 --- a/storybook/qmlTests/tests/tst_SwapModal.qml +++ b/storybook/qmlTests/tests/tst_SwapModal.qml @@ -80,7 +80,7 @@ Item { function launchAccountSelectionPopup(accountsModalHeader) { // Launch account selection popup verify(!accountsModalHeader.control.popup.opened) - mouseClick(accountsModalHeader, Qt.LeftButton) + mouseClick(accountsModalHeader) waitForRendering(accountsModalHeader) verify(!!accountsModalHeader.control.popup.opened) return accountsModalHeader @@ -206,14 +206,13 @@ Item { for(let i =0; i< comboBoxList.model.count; i++) { let delegateUnderTest = comboBoxList.itemAtIndex(i) verify(!delegateUnderTest.modelData.fromToken) - verify(!delegateUnderTest.modelData.accountBalance) } // close account selection dropdown accountsModalHeader.control.popup.close() // set network chainId and fromTokensKey and verify balances in account selection dropdown - root.swapFormData.selectedNetworkChainId = root.swapAdaptor.__filteredFlatNetworksModel.get(0).chainId + root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId root.swapFormData.fromTokensKey = root.swapAdaptor.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel.get(0).key compare(controlUnderTest.swapInputParamsForm.selectedNetworkChainId, root.swapFormData.selectedNetworkChainId) compare(controlUnderTest.swapInputParamsForm.fromTokensKey, root.swapFormData.fromTokensKey) @@ -257,7 +256,7 @@ Item { let delegateUnderTest = comboBoxList.itemAtIndex(i) - mouseClick(delegateUnderTest, Qt.LeftButton) + mouseClick(delegateUnderTest) waitForRendering(delegateUnderTest) verify(accountsModalHeader.control.popup.closed) @@ -279,5 +278,111 @@ Item { } closeAndVerfyModal() } + + function test_network_default_and_selection() { + // Launch popup + launchAndVerfyModal() + + // get network comboBox + const networkComboBox = findChild(controlUnderTest, "networkFilter") + verify(!!networkComboBox) + + // check default value of network comboBox, should be mainnet + compare(root.swapFormData.selectedNetworkChainId, -1) + const networkComboBoxText = findChild(networkComboBox.control.contentItem, "contentItemText") + verify(!!networkComboBoxText) + compare(networkComboBoxText.text, root.swapAdaptor.filteredFlatNetworksModel.get(0).chainName) + + // lets ensure that the selected one is correctly set + for (let i=0; i balances.chainId === root.swapFormData.selectedNetworkChainId).filter(balances => balances.account === accountDelegateUnderTest.modelData.address) + verify(!!filteredBalances) + let accountBalance = filteredBalances.length > 0 ? filteredBalances[0]: { balance: "0", iconUrl: networkModelItem.iconUrl, chainColor: networkModelItem.chainColor} + verify(!!accountBalance) + let fromToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel, "key", root.swapFormData.fromTokensKey) + verify(!!fromToken) + let bigIntBalance = SQUtils.AmountsArithmetic.toNumber(accountBalance.balance, fromToken.decimals) + compare(inlineTagDelegate_0.title, root.swapAdaptor.formatCurrencyAmount(bigIntBalance, fromToken.symbol)) + } + // close account selection dropdown + accountsModalHeader.control.popup.close() + } + root.swapFormData.selectedNetworkChainId = -1 + networkComboBox.control.popup.close() + closeAndVerfyModal() + } } } diff --git a/storybook/src/Models/GroupedAccountsAssetsModel.qml b/storybook/src/Models/GroupedAccountsAssetsModel.qml index 95e61c083f..e1e000d8a1 100644 --- a/storybook/src/Models/GroupedAccountsAssetsModel.qml +++ b/storybook/src/Models/GroupedAccountsAssetsModel.qml @@ -6,7 +6,9 @@ ListModel { tokensKey: "DAI", balances: [ { account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 5, balance: "0" }, - { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 5, balance: "123456789123456789" } + { account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 11155111, balance: "0" }, + { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 5, balance: "123456789123456789" }, + { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 11155111, balance: "123456789123456789" } ] }, { @@ -15,6 +17,7 @@ ListModel { { account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "1013151281976507736" }, { account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 421613, balance: "473057568699284613" }, { account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 5, balance: "307400931315122839" }, + { account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 11155111, balance: "307400931315122839" }, { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "122082928968121891" }, { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 421613, balance: "0" }, { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 5, balance: "559133758939097000" } @@ -24,7 +27,10 @@ ListModel { tokensKey: "STT", balances: [ { account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 5, balance: "999999999998998500000000000016777216" }, - { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 5, balance: "1077000000000000000000" } + { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 5, balance: "1077000000000000000000" }, + { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "122082928968121891" }, + { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 421613, balance: "222000000000000000" }, + { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 11155111, balance: "559133758939097000" } ] }, { diff --git a/ui/app/AppLayouts/Wallet/WalletLayout.qml b/ui/app/AppLayouts/Wallet/WalletLayout.qml index 2187b4d708..6e1a585008 100644 --- a/ui/app/AppLayouts/Wallet/WalletLayout.qml +++ b/ui/app/AppLayouts/Wallet/WalletLayout.qml @@ -217,6 +217,7 @@ Item { }) onLaunchSwapModal: { d.swapFormData.selectedAccountIndex = d.selectedAccountIndex + d.swapFormData.selectedNetworkChainId = StatusQUtils.ModelUtils.getByKey(RootStore.filteredFlatModel, "layer", 1, "chainId") d.swapFormData.fromTokensKey = tokensKey Global.openSwapModalRequested(d.swapFormData) } @@ -334,6 +335,7 @@ Item { onLaunchSwapModal: { d.swapFormData.fromTokensKey = "" d.swapFormData.selectedAccountIndex = d.selectedAccountIndex + d.swapFormData.selectedNetworkChainId = StatusQUtils.ModelUtils.getByKey(RootStore.filteredFlatModel, "layer", 1, "chainId") if(!!walletStore.currentViewedHoldingTokensKey && walletStore.currentViewedHoldingType === Constants.TokenType.ERC20) { d.swapFormData.fromTokensKey = walletStore.currentViewedHoldingTokensKey } diff --git a/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml b/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml index 3106c4c12e..9e1025ca6c 100644 --- a/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml +++ b/ui/app/AppLayouts/Wallet/controls/NetworkFilter.qml @@ -95,6 +95,7 @@ StatusComboBox { visible: active } StatusBaseText { + objectName: "contentItemText" Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true font.pixelSize: Style.current.additionalTextSize diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml index 86608146a1..00189278c8 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml @@ -10,6 +10,8 @@ import StatusQ.Popups.Dialog 0.1 import shared.popups.send.controls 1.0 +import AppLayouts.Wallet.controls 1.0 + StatusDialog { id: root @@ -19,16 +21,13 @@ StatusDialog { required property SwapModalAdaptor swapAdaptor objectName: "swapModal" - title: qsTr("Swap") - bottomPadding: 16 - padding: 0 - - background: StatusDialogBackground { - implicitHeight: 846 - implicitWidth: 556 - color: Theme.palette.baseColor3 - } + implicitWidth: 556 + topPadding: 0 + bottomPadding: Style.current.padding + leftPadding: Style.current.xlPadding + rightPadding: Style.current.xlPadding + backgroundColor: Theme.palette.baseColor3 header: AccountsModalHeader { anchors.top: parent.top @@ -45,28 +44,57 @@ StatusDialog { } } - // This is a temporary placeholder while each of the components are being added. - contentItem: Column { + contentItem: ColumnLayout { spacing: 5 + + RowLayout { + Layout.fillWidth: true + spacing: 12 + HeaderTitleText { + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + id: modalHeader + text: qsTr("Swap") + } + StatusBaseText { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + text: qsTr("On:") + color: Theme.palette.baseColor1 + font.pixelSize: 13 + lineHeight: 38 + lineHeightMode: Text.FixedHeight + verticalAlignment: Text.AlignVCenter + } + // TODO: update this once https://github.com/status-im/status-desktop/issues/14780 is ready + NetworkFilter { + id: networkFilter + objectName: "networkFilter" + Layout.alignment: Qt.AlignVCenter + multiSelection: false + flatNetworks: root.swapAdaptor.filteredFlatNetworksModel + onToggleNetwork: (network) => { + root.swapInputParamsForm.selectedNetworkChainId = network.chainId + } + Component.onCompleted: { + if(root.swapInputParamsForm.selectedNetworkChainId !== -1) + networkFilter.setChain(root.swapInputParamsForm.selectedNetworkChainId) + } + } + } + + // This is a temporary placeholder while each of the components are being added. StatusBaseText { - Layout.alignment: Qt.AlignHCenter + topPadding: Style.current.padding text: qsTr("This area is a temporary placeholder") font.bold: true } StatusBaseText { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Selected network: %1").arg(swapInputParamsForm.selectedNetworkChainId) - } - StatusBaseText { - Layout.alignment: Qt.AlignHCenter text: qsTr("Selected from token: %1").arg(swapInputParamsForm.fromTokensKey) } StatusBaseText { - Layout.alignment: Qt.AlignHCenter text: qsTr("from token amount: %1").arg(swapInputParamsForm.fromTokenAmount) } StatusBaseText { - Layout.alignment: Qt.AlignHCenter text: qsTr("Selected to token: %1").arg(swapInputParamsForm.toTokenKey) } } diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml index 5e6e91a654..efa65f8eb5 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModalAdaptor.qml @@ -38,11 +38,16 @@ QObject { ] } + readonly property SortFilterProxyModel filteredFlatNetworksModel: SortFilterProxyModel { + sourceModel: root.swapStore.flatNetworks + filters: ValueFilter { roleName: "isTest"; value: root.swapStore.areTestNetworksEnabled } + } + function getNetworkShortNames(chainIds) { var networkString = "" let chainIdsArray = chainIds.split(":") for (let i = 0; i< chainIdsArray.length; i++) { - let nwShortName = ModelUtils.getByKey(root.__filteredFlatNetworksModel, "chainId", Number(chainIdsArray[i]), "shortName") + let nwShortName = ModelUtils.getByKey(root.filteredFlatNetworksModel, "chainId", Number(chainIdsArray[i]), "shortName") if(!!nwShortName) { networkString = networkString + nwShortName + ':' } @@ -64,10 +69,6 @@ QObject { // Internal properties and functions ----------------------------------------------------------------------------------------------------------------------------- readonly property var __fromToken: ModelUtils.getByKey(root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel, "key", root.swapFormData.fromTokensKey) - readonly property SortFilterProxyModel __filteredFlatNetworksModel: SortFilterProxyModel { - sourceModel: root.swapStore.flatNetworks - filters: ValueFilter { roleName: "isTest"; value: root.swapStore.areTestNetworksEnabled } - } SubmodelProxyModel { id: filteredBalancesModel @@ -81,7 +82,7 @@ QObject { } readonly property LeftJoinModel joinModel: LeftJoinModel { leftModel: submodel - rightModel: root.__filteredFlatNetworksModel + rightModel: root.filteredFlatNetworksModel joinRole: "chainId" } @@ -89,7 +90,7 @@ QObject { } function __processAccountBalance(address) { - let network = ModelUtils.getByKey(root.__filteredFlatNetworksModel, "chainId", root.swapFormData.selectedNetworkChainId) + let network = ModelUtils.getByKey(root.filteredFlatNetworksModel, "chainId", root.swapFormData.selectedNetworkChainId) if(!!network) { let balancesModel = ModelUtils.getByKey(filteredBalancesModel, "tokensKey", root.swapFormData.fromTokensKey, "balances") let accountBalance = ModelUtils.getByKey(balancesModel, "account", address) diff --git a/ui/imports/shared/popups/send/SendModal.qml b/ui/imports/shared/popups/send/SendModal.qml index 3a274f876e..d487c8f9d6 100644 --- a/ui/imports/shared/popups/send/SendModal.qml +++ b/ui/imports/shared/popups/send/SendModal.qml @@ -248,17 +248,12 @@ StatusDialog { RowLayout { spacing: 8 Layout.preferredHeight: 44 - StatusBaseText { + + HeaderTitleText { id: modalHeader - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - verticalAlignment: Text.AlignVCenter - text: d.isBridgeTx ? qsTr("Bridge") : qsTr("Send") - font.pixelSize: 28 - lineHeight: 38 - lineHeightMode: Text.FixedHeight - font.letterSpacing: -0.4 - color: Theme.palette.directColor1 Layout.maximumWidth: contentWidth + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + text: d.isBridgeTx ? qsTr("Bridge") : qsTr("Send") } HoldingSelector { diff --git a/ui/imports/shared/popups/send/controls/HeaderTitleText.qml b/ui/imports/shared/popups/send/controls/HeaderTitleText.qml new file mode 100644 index 0000000000..9cc33bd3ec --- /dev/null +++ b/ui/imports/shared/popups/send/controls/HeaderTitleText.qml @@ -0,0 +1,13 @@ +import QtQuick 2.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +StatusBaseText { + lineHeight: 38 + lineHeightMode: Text.FixedHeight + font.pixelSize: 28 + font.letterSpacing: -0.4 + verticalAlignment: Text.AlignVCenter + color: Theme.palette.directColor1 +} diff --git a/ui/imports/shared/popups/send/controls/qmldir b/ui/imports/shared/popups/send/controls/qmldir index ea6dab0709..c40dfdf1f4 100644 --- a/ui/imports/shared/popups/send/controls/qmldir +++ b/ui/imports/shared/popups/send/controls/qmldir @@ -10,3 +10,4 @@ AmountInputWithCursor 1.0 AmountInputWithCursor.qml BalanceExceeded 1.0 BalanceExceeded.qml CollectibleBackButtonWithInfo 1.0 CollectibleBackButtonWithInfo.qml CollectibleNestedDelegate 1.0 CollectibleNestedDelegate.qml +HeaderTitleText 1.0 HeaderTitleText.qml