From 03e75e953241945fa833bcdd11bef5424010ede6 Mon Sep 17 00:00:00 2001 From: Khushboo Mehta Date: Tue, 6 Aug 2024 18:04:22 +0200 Subject: [PATCH] fix(@desktop/wallet): Creating the UI flow needed to select params for making a purchase via Mercuryo --- storybook/pages/BuyCryptoModalPage.qml | 68 +++++++- .../qmlTests/tests/tst_BuyCryptoModal.qml | 106 ++++++++++--- storybook/src/Models/ModelsData.qml | 1 + storybook/src/Models/OnRampProvidersModel.qml | 37 ++++- storybook/src/Models/TokensBySymbolModel.qml | 2 +- .../Wallet/stores/BuyCryptoStore.qml | 4 + .../stubs/AppLayouts/Wallet/stores/qmldir | 1 + ui/app/AppLayouts/Wallet/WalletLayout.qml | 28 +++- .../adaptors/TokenSelectorViewAdaptor.qml | 5 +- .../controls/BuyCryptoProvidersDelegate.qml | 48 ++++++ .../BuyCryptoProvidersLoadingDelegate.qml | 18 +++ ui/app/AppLayouts/Wallet/controls/qmldir | 2 + .../panels/BuyCryptoProvidersListPanel.qml | 86 +++++++++++ .../panels/SelectParamsForBuyCryptoPanel.qml | 136 ++++++++++++++++ .../AppLayouts/Wallet/panels/WalletFooter.qml | 3 +- ui/app/AppLayouts/Wallet/panels/qmldir | 2 + .../Wallet/popups/BuyCryptoModal.qml | 97 ------------ .../Wallet/popups/buy/BuyCryptoModal.qml | 143 +++++++++++++++++ .../popups/buy/BuyCryptoModalAdaptor.qml | 146 ++++++++++++++++++ .../Wallet/popups/buy/BuyCryptoParamsForm.qml | 19 +++ ui/app/AppLayouts/Wallet/popups/buy/qmldir | 3 + ui/app/AppLayouts/Wallet/popups/qmldir | 1 - .../Wallet/popups/swap/SwapModal.qml | 15 +- .../Wallet/stores/BuyCryptoStore.qml | 35 +++++ ui/app/AppLayouts/Wallet/stores/RootStore.qml | 2 - ui/app/AppLayouts/Wallet/stores/qmldir | 1 + ui/app/mainui/Popups.qml | 18 ++- .../assets/png/onRampProviders/mercuryo.png | Bin 0 -> 1686 bytes ui/imports/utils/Global.qml | 2 +- 29 files changed, 882 insertions(+), 147 deletions(-) create mode 100644 storybook/stubs/AppLayouts/Wallet/stores/BuyCryptoStore.qml create mode 100644 ui/app/AppLayouts/Wallet/controls/BuyCryptoProvidersDelegate.qml create mode 100644 ui/app/AppLayouts/Wallet/controls/BuyCryptoProvidersLoadingDelegate.qml create mode 100644 ui/app/AppLayouts/Wallet/panels/BuyCryptoProvidersListPanel.qml create mode 100644 ui/app/AppLayouts/Wallet/panels/SelectParamsForBuyCryptoPanel.qml delete mode 100644 ui/app/AppLayouts/Wallet/popups/BuyCryptoModal.qml create mode 100644 ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoModal.qml create mode 100644 ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoModalAdaptor.qml create mode 100644 ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoParamsForm.qml create mode 100644 ui/app/AppLayouts/Wallet/popups/buy/qmldir create mode 100644 ui/app/AppLayouts/Wallet/stores/BuyCryptoStore.qml create mode 100644 ui/imports/assets/png/onRampProviders/mercuryo.png diff --git a/storybook/pages/BuyCryptoModalPage.qml b/storybook/pages/BuyCryptoModalPage.qml index 55499ca1b8..45b3bfa1c3 100644 --- a/storybook/pages/BuyCryptoModalPage.qml +++ b/storybook/pages/BuyCryptoModalPage.qml @@ -1,16 +1,54 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 import Storybook 1.0 import Models 1.0 -import AppLayouts.Wallet.popups 1.0 +import StatusQ.Core.Backpressure 0.1 + +import AppLayouts.Wallet.popups.buy 1.0 +import AppLayouts.Wallet.stores 1.0 + +import shared.stores 1.0 SplitView { id: root orientation: Qt.Horizontal + QtObject { + id: d + property string uuid + property var debounceFetchProviderUrl: Backpressure.debounce(root, 500, function() { + d.buyCryptoStore.providerUrlReady(d.uuid, "xxxx") + }) + property var debounceFetchProvidersList: Backpressure.debounce(root, 500, function() { + d.buyCryptoStore.areProvidersLoading = false + }) + readonly property var buyCryptoStore: BuyCryptoStore { + readonly property var providersModel: OnRampProvidersModel{} + property bool areProvidersLoading + signal providerUrlReady(string uuid , string url) + + function fetchProviders() { + console.warn("fetchProviders called >>") + areProvidersLoading = true + d.debounceFetchProvidersList() + } + + function fetchProviderUrl(uuid, providerID, + isRecurrent, accountAddress = "", + chainID = 0, symbol = "") { + console.warn("fetchProviderUrl called >> uuid: ", uuid, "providerID: ",providerID + , "isRecurrent: ", isRecurrent, "accountAddress: ", accountAddress, + "chainID: ", chainID, "symbol: ", symbol) + d.uuid = uuid + d.debounceFetchProviderUrl() + } + } + } + PopupBackground { id: popupBg @@ -28,8 +66,34 @@ SplitView { BuyCryptoModal { id: buySellModal + anchors.centerIn: parent visible: true - onRampProvidersModel: OnRampProvidersModel{} + modal: false + closePolicy: Popup.CloseOnEscape + buyCryptoAdaptor: BuyCryptoModalAdaptor { + buyCryptoStore: d.buyCryptoStore + readonly property var currencyStore: CurrenciesStore {} + readonly property var assetsStore: WalletAssetsStore { + id: thisWalletAssetStore + walletTokensStore: TokensStore { + plainTokensBySymbolModel: TokensBySymbolModel {} + } + readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {} + assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel + } + buyCryptoFormData: buySellModal.buyCryptoInputParamsForm + walletAccountsModel: WalletAccountsModel{} + networksModel: NetworksModel.flatNetworks + areTestNetworksEnabled: true + groupedAccountAssetsModel: assetsStore.groupedAccountAssetsModel + plainTokensBySymbolModel: assetsStore.walletTokensStore.plainTokensBySymbolModel + currentCurrency: currencyStore.currentCurrency + } + buyCryptoInputParamsForm: BuyCryptoParamsForm{ + selectedWalletAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240" + selectedNetworkChainId: 11155111 + selectedTokenKey: "ETH" + } } } } diff --git a/storybook/qmlTests/tests/tst_BuyCryptoModal.qml b/storybook/qmlTests/tests/tst_BuyCryptoModal.qml index a67295c8a0..6556f1c784 100644 --- a/storybook/qmlTests/tests/tst_BuyCryptoModal.qml +++ b/storybook/qmlTests/tests/tst_BuyCryptoModal.qml @@ -5,17 +5,53 @@ import SortFilterProxyModel 0.2 import StatusQ.Core.Theme 0.1 import StatusQ.Core.Utils 0.1 as SQUtils +import StatusQ.Core.Backpressure 0.1 import Models 1.0 import utils 1.0 -import AppLayouts.Wallet.popups 1.0 +import AppLayouts.Wallet.popups.buy 1.0 +import AppLayouts.Wallet.stores 1.0 + +import shared.stores 1.0 Item { id: root width: 600 height: 800 + QtObject { + id: d + property string uuid + property var debounceFetchProviderUrl: Backpressure.debounce(root, 500, function() { + d.buyCryptoStore.providerUrlReady(d.uuid, "xxxx") + }) + property var debounceFetchProvidersList: Backpressure.debounce(root, 500, function() { + d.buyCryptoStore.areProvidersLoading = false + }) + readonly property var buyCryptoStore: BuyCryptoStore { + readonly property var providersModel: _onRampProvidersModel + property bool areProvidersLoading + signal providerUrlReady(string uuid , string url) + + function fetchProviders() { + console.warn("fetchProviders called >>") + areProvidersLoading = true + d.debounceFetchProvidersList() + } + + function fetchProviderUrl(uuid, providerID, + isRecurrent, accountAddress = "", + chainID = 0, symbol = "") { + console.warn("fetchProviderUrl called >> uuid: ", uuid, "providerID: ",providerID + , "isRecurrent: ", isRecurrent, "accountAddress: ", accountAddress, + "chainID: ", chainID, "symbol: ", symbol) + d.uuid = uuid + d.debounceFetchProviderUrl() + } + } + } + OnRampProvidersModel{ id: _onRampProvidersModel } @@ -24,17 +60,39 @@ Item { id: recurrentOnRampProvidersModel sourceModel: _onRampProvidersModel filters: ValueFilter { - roleName: "recurrentSiteUrl" - value: "" - inverted: true + roleName: "supportsRecurrentPurchase" + value: true } } Component { id: componentUnderTest BuyCryptoModal { - onRampProvidersModel: _onRampProvidersModel - onClosed: destroy() + id: buySellModal + buyCryptoAdaptor: BuyCryptoModalAdaptor { + buyCryptoStore: d.buyCryptoStore + readonly property var currencyStore: CurrenciesStore {} + readonly property var assetsStore: WalletAssetsStore { + id: thisWalletAssetStore + walletTokensStore: TokensStore { + plainTokensBySymbolModel: TokensBySymbolModel {} + } + readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {} + assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel + } + buyCryptoFormData: buySellModal.buyCryptoInputParamsForm + walletAccountsModel: WalletAccountsModel{} + networksModel: NetworksModel.flatNetworks + areTestNetworksEnabled: true + groupedAccountAssetsModel: assetsStore.groupedAccountAssetsModel + plainTokensBySymbolModel: assetsStore.walletTokensStore.plainTokensBySymbolModel + currentCurrency: currencyStore.currentCurrency + } + buyCryptoInputParamsForm: BuyCryptoParamsForm{ + selectedWalletAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240" + selectedNetworkChainId: 11155111 + selectedTokenKey: "ETH" + } } } @@ -73,15 +131,17 @@ Item { verify(!!feesText) compare(feesText.text, modelToCompareAgainst.get(i).fees) + /* TODO: fix when writing more tests for this functionality const externalLinkIcon = findChild(delegateUnderTest, "externalLinkIcon") verify(!!externalLinkIcon) compare(externalLinkIcon.icon, "tiny/external") - compare(externalLinkIcon.color, Theme.palette.baseColor1) + compare(externalLinkIcon.color, Theme.palette.baseColor1) */ // Hover over the item and check hovered state mouseMove(delegateUnderTest, delegateUnderTest.width/2, delegateUnderTest.height/2) verify(delegateUnderTest.sensor.containsMouse) - compare(externalLinkIcon.color, Theme.palette.directColor1) + /* TODO: fix when writing more tests for this functionality + compare(externalLinkIcon.color, Theme.palette.directColor1) */ verify(delegateUnderTest.color, Theme.palette.baseColor2) } } @@ -99,11 +159,11 @@ Item { launchPopup() // check if footer has Done button and action on button clicked - const footer = findChild(controlUnderTest, "footer") - verify(!!footer) - compare(footer.rightButtons.count, 1) - compare(footer.rightButtons.get(0).text, qsTr("Done")) - mouseClick(footer.rightButtons.get(0)) + compare(controlUnderTest.rightButtons.length, 2) + verify(!controlUnderTest.rightButtons[0].visible) + verify(controlUnderTest.rightButtons[1].visible) + compare(controlUnderTest.rightButtons[1].text, qsTr("Done")) + mouseClick(controlUnderTest.rightButtons[1]) // popup should be closed verify(!controlUnderTest.opened) @@ -140,6 +200,7 @@ Item { } function test_modalContent_OneTime_tab() { + notificationSpy.clear() // Launch modal launchPopup() @@ -152,11 +213,13 @@ Item { waitForRendering(providersList) verify(!!providersList) + tryCompare(controlUnderTest.buyCryptoAdaptor.buyCryptoStore, "areProvidersLoading", false) + mouseClick(tabBar.itemAt(0)) compare(tabBar.currentIndex, 0) - // verify that 3 items are listed - compare(providersList.count, 3) + // verify that 4 items are listed + compare(providersList.count, 4) // check if delegate contents are as expected testDelegateItems(providersList, _onRampProvidersModel) @@ -168,7 +231,7 @@ Item { tryCompare(notificationSpy, "count", 0) mouseClick(delegateUnderTest) tryCompare(notificationSpy, "count", 1) - compare(notificationSpy.signalArguments[0][0], _onRampProvidersModel.get(0).siteUrl) + compare(notificationSpy.signalArguments[0][0], "xxxx") compare(notificationSpy.signalArguments[0][1], _onRampProvidersModel.get(0).hostname) notificationSpy.clear() @@ -177,6 +240,7 @@ Item { } function test_modalContent_recurrent_tab() { + notificationSpy.clear() // Launch modal launchPopup() @@ -186,9 +250,9 @@ Item { // find providers list const providersList = findChild(controlUnderTest, "providersList") - waitForRendering(providersList) verify(!!providersList) + tryCompare(controlUnderTest.buyCryptoAdaptor.buyCryptoStore, "areProvidersLoading", false) // check data in "Recurrent" tab -------------------------------------------------------- mouseClick(tabBar.itemAt(1)) @@ -209,13 +273,9 @@ Item { tryCompare(notificationSpy, "count", 0) verify(controlUnderTest.opened) mouseClick(delegateUnderTest) - tryCompare(notificationSpy, "count", 1) - compare(notificationSpy.signalArguments[0][0], recurrentOnRampProvidersModel.get(0).recurrentSiteUrl) - compare(notificationSpy.signalArguments[0][1], recurrentOnRampProvidersModel.get(0).hostname) + tryCompare(notificationSpy, "count", 0) notificationSpy.clear() - - // popup should be closed - verify(!controlUnderTest.opened) + //TODO: add more test logic here for second page of selecting params } } } diff --git a/storybook/src/Models/ModelsData.qml b/storybook/src/Models/ModelsData.qml index c4a93e3776..6f3f2e6c64 100644 --- a/storybook/src/Models/ModelsData.qml +++ b/storybook/src/Models/ModelsData.qml @@ -87,5 +87,6 @@ QtObject { readonly property string latamex: Style.png("onRampProviders/latamex") readonly property string moonPay: Style.png("onRampProviders/moonPay") readonly property string ramp: Style.png("onRampProviders/ramp") + readonly property string mercuryo: Style.png("onRampProviders/mercuryo") } } diff --git a/storybook/src/Models/OnRampProvidersModel.qml b/storybook/src/Models/OnRampProvidersModel.qml index 5d877ed041..1433fb0582 100644 --- a/storybook/src/Models/OnRampProvidersModel.qml +++ b/storybook/src/Models/OnRampProvidersModel.qml @@ -3,31 +3,56 @@ import QtQuick 2.15 ListModel { readonly property var data: [ { + id: "1", name: "Ramp", description: "Global crypto to fiat flow", fees: "0.49% - 2.9%", logoUrl: ModelsData.onRampProviderImages.ramp, - siteUrl: "https://ramp.network/buy?hostApiKey=zrtf9u2uqebeyzcs37fu5857tktr3eg9w5tffove&swapAsset=DAI,ETH,USDC,USDT", hostname: "ramp.network", - recurrentSiteUrl: "" + supportsSinglePurchase: true, + supportsRecurrentPurchase: false, + supportedAssets:[], + urlsNeedParameters: false }, { + id: "2", name: "MoonPay", description: "The new standard for fiat to crypto", fees: "1% - 4.5%", logoUrl: ModelsData.onRampProviderImages.moonPay, - siteUrl: "https://buy.moonpay.com/?apiKey=pk_live_YQC6CQPA5qqDu0unEwHJyAYQyeIqFGR", hostname: "moonpay.com", - recurrentSiteUrl: "https://buy.moonpay.com/?apiKey=pk_live_ABCCQPA5qqDu0unEwHJyAYQyeIqFGR", + supportsSinglePurchase: true, + supportsRecurrentPurchase: false, + supportedAssets:[], + urlsNeedParameters: false }, { + id: "3", name: "Latamex", description: "Easily buy crypto in Argentina, Mexico, and Brazil", fees: "1% - 1.7%", logoUrl: ModelsData.onRampProviderImages.latamex, - siteUrl: "https://latamex.com/", hostname: "latamex.com", - recurrentSiteUrl: "", + supportsSinglePurchase: true, + supportsRecurrentPurchase: false, + supportedAssets:[], + urlsNeedParameters: false + }, + { + id: "4", + name: "Mercuryo", + description: "Mercuryo buy crypto in Argentina, Mexico, and Brazil", + fees: "1% - 1.7%", + logoUrl: ModelsData.onRampProviderImages.mercuryo, + hostname: "mercuryo.com", + supportsSinglePurchase: true, + supportsRecurrentPurchase: true, + supportedAssets:[ + { key: "111551110x0000000000000000000000000000000000000000", chainId: 11155111, address: "0x0000000000000000000000000000000000000000"}, + { key: "4200x0000000000000000000000000000000000000000", chainId: 420, address: "0x0000000000000000000000000000000000000000"}, + { key: "4200xf2edf1c091f683e3fb452497d9a98a49cba84669", chainId: 420, address: "0xf2edf1c091f683e3fb452497d9a98a49cba84669"}, + ], + urlsNeedParameters: true } ] diff --git a/storybook/src/Models/TokensBySymbolModel.qml b/storybook/src/Models/TokensBySymbolModel.qml index b2c63e8492..59b26deab7 100644 --- a/storybook/src/Models/TokensBySymbolModel.qml +++ b/storybook/src/Models/TokensBySymbolModel.qml @@ -82,7 +82,7 @@ ListModel { { chainId: 42161, address: "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1"}, { chainId: 5, address: "0xf2edf1c091f683e3fb452497d9a98a49cba84666"}, { chainId: 11155111, address: "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1"}, - { chainId: 420, address: "0xf2edf1c091f683e3fb452497d9a98a49cba84666"}, + { chainId: 420, address: "0xf2edf1c091f683e3fb452497d9a98a49cba84669"}, { chainId: 421613, address: "0xf2edf1c091f683e3fb452497d9a98a49cba84666"}, ], decimals: 18, diff --git a/storybook/stubs/AppLayouts/Wallet/stores/BuyCryptoStore.qml b/storybook/stubs/AppLayouts/Wallet/stores/BuyCryptoStore.qml new file mode 100644 index 0000000000..52d5a7a7f7 --- /dev/null +++ b/storybook/stubs/AppLayouts/Wallet/stores/BuyCryptoStore.qml @@ -0,0 +1,4 @@ +import QtQuick 2.15 + +QtObject { +} diff --git a/storybook/stubs/AppLayouts/Wallet/stores/qmldir b/storybook/stubs/AppLayouts/Wallet/stores/qmldir index ff6a31b584..56a5210303 100644 --- a/storybook/stubs/AppLayouts/Wallet/stores/qmldir +++ b/storybook/stubs/AppLayouts/Wallet/stores/qmldir @@ -4,3 +4,4 @@ singleton RootStore 1.0 RootStore.qml SwapStore 1.0 SwapStore.qml TokensStore 1.0 TokensStore.qml WalletAssetsStore 1.0 WalletAssetsStore.qml +BuyCryptoStore 1.0 BuyCryptoStore.qml diff --git a/ui/app/AppLayouts/Wallet/WalletLayout.qml b/ui/app/AppLayouts/Wallet/WalletLayout.qml index 42bcf4f36f..5672283622 100644 --- a/ui/app/AppLayouts/Wallet/WalletLayout.qml +++ b/ui/app/AppLayouts/Wallet/WalletLayout.qml @@ -17,6 +17,7 @@ import "views" import "stores" import "controls" import "popups/swap" +import "popups/buy" Item { id: root @@ -142,6 +143,10 @@ Item { selectedAccountAddress: RootStore.selectedAddress } + property BuyCryptoParamsForm buyFormData: BuyCryptoParamsForm { + selectedWalletAddress: RootStore.selectedAddress + } + function displayAllAddresses() { RootStore.showSavedAddresses = false RootStore.selectedAddress = "" @@ -165,6 +170,12 @@ Item { rightPanelStackView.currentItem.resetView() } } + + function getSelectedOrFirstNonWatchedAddress() { + return !!RootStore.selectedAddress ? + RootStore.selectedAddress : + StatusQUtils.ModelUtils.get(RootStore.nonWatchAccounts, 0, "address") + } } SignPhraseModal { @@ -212,9 +223,7 @@ Item { hasFloatingButtons: true }) onLaunchSwapModal: { - d.swapFormData.selectedAccountAddress = !!RootStore.selectedAddress ? - RootStore.selectedAddress : - StatusQUtils.ModelUtils.get(RootStore.nonWatchAccounts,0, "address") + d.swapFormData.selectedAccountAddress = d.getSelectedOrFirstNonWatchedAddress() d.swapFormData.selectedNetworkChainId = StatusQUtils.ModelUtils.getByKey(RootStore.filteredFlatModel, "layer", 1, "chainId") d.swapFormData.fromTokensKey = tokensKey d.swapFormData.defaultToTokenKey = RootStore.areTestNetworksEnabled ? Constants.swap.testStatusTokenKey : Constants.swap.mainnetStatusTokenKey @@ -336,9 +345,7 @@ Item { } onLaunchSwapModal: { d.swapFormData.fromTokensKey = "" - d.swapFormData.selectedAccountAddress = !!RootStore.selectedAddress ? - RootStore.selectedAddress : - StatusQUtils.ModelUtils.get(RootStore.nonWatchAccounts,0, "address") + d.swapFormData.selectedAccountAddress = d.getSelectedOrFirstNonWatchedAddress() d.swapFormData.selectedNetworkChainId = StatusQUtils.ModelUtils.getByKey(RootStore.filteredFlatModel, "layer", 1, "chainId") if(!!walletStore.currentViewedHoldingTokensKey && walletStore.currentViewedHoldingType === Constants.TokenType.ERC20) { d.swapFormData.fromTokensKey = walletStore.currentViewedHoldingTokensKey @@ -346,6 +353,15 @@ Item { d.swapFormData.defaultToTokenKey = RootStore.areTestNetworksEnabled ? Constants.swap.testStatusTokenKey : Constants.swap.mainnetStatusTokenKey Global.openSwapModalRequested(d.swapFormData) } + onLaunchBuyCryptoModal: { + d.buyFormData.selectedWalletAddress = d.getSelectedOrFirstNonWatchedAddress() + d.buyFormData.selectedNetworkChainId = StatusQUtils.ModelUtils.getByKey(RootStore.filteredFlatModel, "layer", 1, "chainId") + d.buyFormData.selectedTokenKey = Constants.ethToken + if(!!walletStore.currentViewedHoldingTokensKey && walletStore.currentViewedHoldingType === Constants.TokenType.ERC20) { + d.buyFormData.selectedTokenKey = walletStore.currentViewedHoldingTokensKey + } + Global.openBuyCryptoModalRequested(d.buyFormData) + } ModelEntry { id: selectedCommunityForCollectible diff --git a/ui/app/AppLayouts/Wallet/adaptors/TokenSelectorViewAdaptor.qml b/ui/app/AppLayouts/Wallet/adaptors/TokenSelectorViewAdaptor.qml index ac8033ee8b..1ef2095c97 100644 --- a/ui/app/AppLayouts/Wallet/adaptors/TokenSelectorViewAdaptor.qml +++ b/ui/app/AppLayouts/Wallet/adaptors/TokenSelectorViewAdaptor.qml @@ -175,8 +175,7 @@ QObject { readonly property string favoritesSectionId: "section_zzz" } - RolesRenamingModel { - id: renamedTokensBySymbolModel + readonly property RolesRenamingModel renamedTokensBySymbolModel: RolesRenamingModel { sourceModel: root.plainTokensBySymbolModel || null mapping: [ RoleRename { @@ -191,7 +190,7 @@ QObject { propagateResets: true sources: [ SourceModel { - model: renamedTokensBySymbolModel + model: root.renamedTokensBySymbolModel markerRoleValue: "plain_tokens_model" }, SourceModel { diff --git a/ui/app/AppLayouts/Wallet/controls/BuyCryptoProvidersDelegate.qml b/ui/app/AppLayouts/Wallet/controls/BuyCryptoProvidersDelegate.qml new file mode 100644 index 0000000000..0751cc0ca7 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/controls/BuyCryptoProvidersDelegate.qml @@ -0,0 +1,48 @@ +import QtQuick 2.15 + +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +import utils 1.0 + +StatusListItem { + id: root + + required property string name + required property string description + required property string logoUrl + required property string fees + required property bool urlsNeedParameters + + property bool isUrlLoading: false + + title: root.name + subTitle: root.description + asset.name: root.logoUrl + asset.isImage: true + statusListItemSubTitle.maximumLineCount: 1 + statusListItemComponentsSlot.spacing: 8 + components: [ + StatusTextWithLoadingState { + objectName: "feesText" + text: root.loading ? Constants.dummyText: root.fees + customColor: Theme.palette.baseColor1 + lineHeight: 24 + lineHeightMode: Text.FixedHeight + verticalAlignment: Text.AlignVCenter + loading: root.loading + }, + StatusIcon { + objectName: "externalLinkIcon" + icon: root.urlsNeedParameters ? "chevron-down": "tiny/external" + rotation: root.urlsNeedParameters ? 270: 0 + color: sensor.containsMouse ? Theme.palette.directColor1: Theme.palette.baseColor1 + visible: !root.loading && !root.isUrlLoading + }, + StatusLoadingIndicator { + visible: root.isUrlLoading + } + ] +} diff --git a/ui/app/AppLayouts/Wallet/controls/BuyCryptoProvidersLoadingDelegate.qml b/ui/app/AppLayouts/Wallet/controls/BuyCryptoProvidersLoadingDelegate.qml new file mode 100644 index 0000000000..e64d54995d --- /dev/null +++ b/ui/app/AppLayouts/Wallet/controls/BuyCryptoProvidersLoadingDelegate.qml @@ -0,0 +1,18 @@ +import QtQuick 2.15 + +import utils 1.0 + +BuyCryptoProvidersDelegate { + name: Constants.dummyText + description: Constants.dummyText + logoUrl: Constants.dummyText + urlsNeedParameters: false + fees: Constants.dummyText + + statusListItemSubTitle.loading: true + statusListItemTitle.loading: true + statusListItemIcon.loading: true + + loading: true + enabled: false +} diff --git a/ui/app/AppLayouts/Wallet/controls/qmldir b/ui/app/AppLayouts/Wallet/controls/qmldir index c13939bace..b5f0f06fa5 100644 --- a/ui/app/AppLayouts/Wallet/controls/qmldir +++ b/ui/app/AppLayouts/Wallet/controls/qmldir @@ -21,3 +21,5 @@ SwapExchangeButton 1.0 SwapExchangeButton.qml TokenSelector 1.0 TokenSelector.qml TokenSelectorNew 1.0 TokenSelectorNew.qml SwapProvidersTermsAndConditionsText 1.0 SwapProvidersTermsAndConditionsText.qml +BuyCryptoProvidersDelegate 1.0 BuyCryptoProvidersDelegate.qml +BuyCryptoProvidersLoadingDelegate 1.0 BuyCryptoProvidersLoadingDelegate.qml diff --git a/ui/app/AppLayouts/Wallet/panels/BuyCryptoProvidersListPanel.qml b/ui/app/AppLayouts/Wallet/panels/BuyCryptoProvidersListPanel.qml new file mode 100644 index 0000000000..2bf29fe4b0 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/panels/BuyCryptoProvidersListPanel.qml @@ -0,0 +1,86 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.0 +import QtQml.Models 2.15 +import SortFilterProxyModel 0.2 + +import StatusQ.Controls 0.1 +import StatusQ.Core 0.1 + +import AppLayouts.Wallet.controls 1.0 + +ColumnLayout { + id: root + + // required properties + required property bool providersLoading + // expected model structure: + // id, name, description, fees, logoUrl, hostname, supportsSinglePurchase, supportsRecurrentPurchase, supportedAssets, urlsNeedParameters + required property var providersModel + required property bool isUrlBeingFetched + required property string selectedProviderId + + // exposed api + property alias currentTabIndex: tabBar.currentIndex + signal providerSelected(string id) + + QtObject { + id: d + readonly property int loadingItemsCount: 5 + } + + spacing: 20 + + StatusSwitchTabBar { + id: tabBar + objectName: "tabBar" + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + StatusSwitchTabButton { + text: qsTr("One time") + } + StatusSwitchTabButton { + text: qsTr("Recurrent") + } + } + + StatusListView { + objectName: "providersList" + Layout.fillWidth: true + Layout.fillHeight: true + + DelegateModel { + id: regularModel + model: SortFilterProxyModel { + sourceModel: root.providersModel + filters: ValueFilter { + enabled: tabBar.currentIndex + roleName: "supportsRecurrentPurchase" + value: true + } + } + delegate: BuyCryptoProvidersDelegate { + required property var model + + width: ListView.view.width + name: model.name + description: model.description + logoUrl: model.logoUrl + fees: model.fees + urlsNeedParameters: model.urlsNeedParameters + isUrlLoading: root.isUrlBeingFetched && root.selectedProviderId === model.id + onClicked: root.providerSelected(model.id) + } + } + + DelegateModel { + id: loadingModel + model: d.loadingItemsCount + delegate: BuyCryptoProvidersLoadingDelegate { + required property var model + width: ListView.view.width + } + } + + model: root.providersLoading ? loadingModel : regularModel + } +} diff --git a/ui/app/AppLayouts/Wallet/panels/SelectParamsForBuyCryptoPanel.qml b/ui/app/AppLayouts/Wallet/panels/SelectParamsForBuyCryptoPanel.qml new file mode 100644 index 0000000000..b8a645bbcc --- /dev/null +++ b/ui/app/AppLayouts/Wallet/panels/SelectParamsForBuyCryptoPanel.qml @@ -0,0 +1,136 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.0 + +import StatusQ.Core 0.1 +import StatusQ.Core.Utils 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Popups 0.1 +import StatusQ.Components 0.1 +import StatusQ.Components.private 0.1 + +import AppLayouts.Wallet.controls 1.0 + +import utils 1.0 + +ColumnLayout { + id: root + + // required properties + required property var adaptor + required property var selectedProvider + required property string selectedTokenKey + required property int selectedNetworkChainId + required property var filteredFlatNetworksModel + + // exposed api + property alias searchString: holdingSelector.searchString + signal networkSelected(int chainId) + signal tokenSelected(string tokensKey) + + spacing: 20 + + StatusListItem { + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft + leftPadding: 0 + rightPadding: 0 + + title: qsTr("Buy via %1").arg(!!root.selectedProvider ? root.selectedProvider.name: "") + subTitle: qsTr("Select which network and asset") + statusListItemTitle.color: Theme.palette.directColor1 + asset.name: !!root.selectedProvider ? root.selectedProvider.logoUrl: "" + asset.isImage: true + color: Theme.palette.transparent + enabled: false + } + StatusMenuSeparator { + Layout.fillWidth: true + } + ColumnLayout { + Layout.fillWidth: true + spacing: 8 + StatusBaseText { + text: qsTr("Select network") + color: Theme.palette.directColor1 + font.pixelSize: 15 + lineHeight: 22 + lineHeightMode: Text.FixedHeight + verticalAlignment: Text.AlignVCenter + } + NetworkFilter { + objectName: "networkFilter" + Layout.fillWidth: true + control.popup.width: parent.width + multiSelection: false + showSelectionIndicator: false + flatNetworks: root.filteredFlatNetworksModel + selection: [root.selectedNetworkChainId] + onSelectionChanged: root.networkSelected(selection[0]) + } + } + ColumnLayout { + Layout.fillWidth: true + spacing: 8 + StatusBaseText { + text: qsTr("Select asset") + color: Theme.palette.directColor1 + font.pixelSize: 15 + lineHeight: 22 + lineHeightMode: Text.FixedHeight + verticalAlignment: Text.AlignVCenter + } + TokenSelector { + id: holdingSelector + Layout.fillWidth: true + model: root.adaptor.outputAssetsModel + popup.width: parent.width + contentItem: Loader { + height: 40 // by design + sourceComponent: !!holdingSelector.currentTokensKey ? selectedTokenCmp : nothingSelectedCmp + } + background: StatusComboboxBackground { + border.width: 1 + color: Theme.palette.transparent + } + onTokenSelected: root.tokenSelected(tokensKey) + Component.onCompleted: holdingSelector.selectToken(root.selectedTokenKey) + } + } + + Component { + id: nothingSelectedCmp + StatusBaseText { + objectName: "tokenSelectorContentItemText" + font.pixelSize: Style.current.additionalTextSize + font.weight: Font.Medium + color: Theme.palette.primaryColor1 + text: qsTr("Select asset") + } + } + + Component { + id: selectedTokenCmp + RowLayout { + spacing: Style.current.halfPadding + StatusRoundedImage { + objectName: "tokenSelectorIcon" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + image.source: ModelUtils.getByKey(holdingSelector.model, "tokensKey", holdingSelector.currentTokensKey, "iconSource") + } + StatusBaseText { + objectName: "tokenSelectorContentItemText" + font.pixelSize: 15 + color: Theme.palette.directColor1 + text: ModelUtils.getByKey(holdingSelector.model, "tokensKey", holdingSelector.currentTokensKey, "name") + } + StatusBaseText { + Layout.fillWidth: true + objectName: "tokenSelectorContentItemText" + font.pixelSize: 15 + color: Theme.palette.baseColor1 + text: ModelUtils.getByKey(holdingSelector.model, "tokensKey", holdingSelector.currentTokensKey, "symbol") + } + } + } +} diff --git a/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml b/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml index 3c95d98fd6..0c87c89f64 100644 --- a/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml +++ b/ui/app/AppLayouts/Wallet/panels/WalletFooter.qml @@ -33,6 +33,7 @@ Rectangle { signal launchSendModal(string fromAddress) signal launchBridgeModal() signal launchSwapModal() + signal launchBuyCryptoModal() color: Theme.palette.statusAppLayout.rightPanelBackgroundColor @@ -149,7 +150,7 @@ Rectangle { visible: d.buyActionAvailable icon.name: "token" text: qsTr("Buy") - onClicked: Global.openBuyCryptoModalRequested() + onClicked: root.launchBuyCryptoModal() } StatusFlatButton { diff --git a/ui/app/AppLayouts/Wallet/panels/qmldir b/ui/app/AppLayouts/Wallet/panels/qmldir index c3ce03421c..9a7d321f8f 100644 --- a/ui/app/AppLayouts/Wallet/panels/qmldir +++ b/ui/app/AppLayouts/Wallet/panels/qmldir @@ -10,3 +10,5 @@ TokenSelectorPanel 1.0 TokenSelectorPanel.qml WalletHeader 1.0 WalletHeader.qml WalletNftPreview 1.0 WalletNftPreview.qml WalletTxProgressBlock 1.0 WalletTxProgressBlock.qml +BuyCryptoProvidersListPanel 1.0 BuyCryptoProvidersListPanel.qml +SelectParamsForBuyCryptoPanel 1.0 SelectParamsForBuyCryptoPanel.qml diff --git a/ui/app/AppLayouts/Wallet/popups/BuyCryptoModal.qml b/ui/app/AppLayouts/Wallet/popups/BuyCryptoModal.qml deleted file mode 100644 index 0fd9c6c23b..0000000000 --- a/ui/app/AppLayouts/Wallet/popups/BuyCryptoModal.qml +++ /dev/null @@ -1,97 +0,0 @@ -import QtQuick 2.14 -import QtQuick.Layouts 1.0 -import QtQml.Models 2.14 -import SortFilterProxyModel 0.2 - -import StatusQ.Popups.Dialog 0.1 -import StatusQ.Controls 0.1 -import StatusQ.Components 0.1 -import StatusQ.Core 0.1 -import StatusQ.Core.Theme 0.1 -import StatusQ 0.1 - -import utils 1.0 - -StatusDialog { - id: root - - required property var onRampProvidersModel - - padding: Style.current.xlPadding - implicitWidth: 560 - implicitHeight: 436 - title: qsTr("Buy assets") - - ColumnLayout { - anchors.fill: parent - spacing: 20 - - StatusSwitchTabBar { - id: tabBar - objectName: "tabBar" - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - StatusSwitchTabButton { - text: qsTr("One time") - } - StatusSwitchTabButton { - text: qsTr("Recurrent") - } - } - - StatusListView { - id: providersList - objectName: "providersList" - Layout.fillWidth: true - Layout.fillHeight: true - model: SortFilterProxyModel { - sourceModel: !!root.onRampProvidersModel ? root.onRampProvidersModel : null - filters: ValueFilter { - enabled: tabBar.currentIndex - roleName: "recurrentSiteUrl" - value: "" - inverted: true - } - } - delegate: StatusListItem { - width: ListView.view.width - title: name - subTitle: description - asset.name: logoUrl - asset.isImage: true - statusListItemSubTitle.maximumLineCount: 1 - statusListItemComponentsSlot.spacing: 8 - components: [ - StatusBaseText { - objectName: "feesText" - text: fees - color: Theme.palette.baseColor1 - lineHeight: 24 - lineHeightMode: Text.FixedHeight - verticalAlignment: Text.AlignVCenter - }, - StatusIcon { - objectName: "externalLinkIcon" - icon: "tiny/external" - color: sensor.containsMouse ? Theme.palette.directColor1: Theme.palette.baseColor1 - } - ] - onClicked: { - let url = tabBar.currentIndex ? recurrentSiteUrl : siteUrl - Global.openLinkWithConfirmation(url, hostname) - root.close() - } - } - } - } - - footer: StatusDialogFooter { - objectName: "footer" - rightButtons: ObjectModel { - StatusButton { - text: qsTr("Done") - onClicked: root.close() - } - } - } -} diff --git a/ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoModal.qml b/ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoModal.qml new file mode 100644 index 0000000000..83d4997e8c --- /dev/null +++ b/ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoModal.qml @@ -0,0 +1,143 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.0 +import QtQml.Models 2.14 +import SortFilterProxyModel 0.2 + +import StatusQ.Popups 0.1 +import StatusQ.Popups.Dialog 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Core.Utils 0.1 +import StatusQ 0.1 + +import utils 1.0 + +import AppLayouts.Wallet.controls 1.0 +import AppLayouts.Wallet.adaptors 1.0 +import AppLayouts.Wallet.panels 1.0 + +StatusStackModal { + id: root + + required property BuyCryptoParamsForm buyCryptoInputParamsForm + required property BuyCryptoModalAdaptor buyCryptoAdaptor + + QtObject { + id: d + readonly property var buyButton: StatusButton { + height: root.finishButton.height + visible: !!root.replaceItem + borderColor: "transparent" + text: qsTr("Buy via %1").arg(!!root.buyCryptoAdaptor.selectedProvider ? root.buyCryptoAdaptor.selectedProvider.name: "") + loading: root.buyCryptoAdaptor.urlIsBeingFetched + onClicked: { + if(!!root.buyCryptoAdaptor.selectedProvider && !!root.buyCryptoAdaptor.selectedToken) { + root.buyCryptoAdaptor.fetchProviderUrl( + root.buyCryptoInputParamsForm.selectedProviderId, + buyCryptoProvidersListPanel.currentTabIndex, + root.buyCryptoInputParamsForm.selectedWalletAddress, + root.buyCryptoInputParamsForm.selectedNetworkChainId, + root.buyCryptoAdaptor.selectedToken.symbol + ) + } + } + enabled: root.buyCryptoInputParamsForm.filledCorrectly + } + } + + width: 560 + height: 515 + padding: Style.current.xlPadding + stackTitle: qsTr("Buy assets for %1").arg(!!buyCryptoAdaptor.selectedAccount ? buyCryptoAdaptor.selectedAccount.name: "") + rightButtons: [d.buyButton, finishButton] + finishButton: StatusButton { + text: qsTr("Done") + onClicked: root.close() + } + + Connections { + target: root.buyCryptoAdaptor + function onProviderUrlReady(url) { + if (!!root.buyCryptoAdaptor.selectedProvider && !!url) + Global.openLinkWithConfirmation(url, root.buyCryptoAdaptor.selectedProvider.hostname) + root.close() + } + } + + onOpened: root.buyCryptoAdaptor.fetchProviders() + onClosed: { + // reset the view + root.replaceItem = undefined + buyCryptoProvidersListPanel.currentTabIndex = 0 + root.buyCryptoAdaptor.reset() + root.buyCryptoInputParamsForm.resetFormData() + } + + stackItems: [ + BuyCryptoProvidersListPanel { + id: buyCryptoProvidersListPanel + providersLoading: root.buyCryptoAdaptor.providersLoading + providersModel: root.buyCryptoAdaptor.providersModel + selectedProviderId: root.buyCryptoInputParamsForm.selectedProviderId + isUrlBeingFetched: root.buyCryptoAdaptor.urlIsBeingFetched + onProviderSelected: { + root.buyCryptoInputParamsForm.selectedProviderId = id + if(!!root.buyCryptoAdaptor.selectedProvider) { + if(root.buyCryptoAdaptor.selectedProvider.urlsNeedParameters) { + root.replace(selectParamsPanel) + } else { + root.buyCryptoAdaptor.fetchProviderUrl(root.buyCryptoAdaptor.selectedProvider.id, currentTabIndex) + } + } + } + } + ] + + Component { + id: selectParamsPanel + SelectParamsForBuyCryptoPanel { + id: selectParamsPanelInst + adaptor: TokenSelectorViewAdaptor { + /* TODO these should be hadbled and perhaps improved under + https://github.com/status-im/status-desktop/issues/16025 */ + assetsModel: SortFilterProxyModel { + sourceModel: root.buyCryptoAdaptor.groupedAccountAssetsModelWithKey + filters: FastExpressionFilter { + expression: model.addressPerChain.rowCount() > 0 + expectedRoles: ["addressPerChain"] + } + } + plainTokensBySymbolModel: SortFilterProxyModel { + sourceModel: root.buyCryptoAdaptor.plainTokensBySymbolModelWithKey + filters: FastExpressionFilter { + expression: model.addressPerChain.rowCount() > 0 + expectedRoles: ["addressPerChain"] + } + } + flatNetworksModel: root.buyCryptoAdaptor.networksModel + currentCurrency: root.buyCryptoAdaptor.currentCurrency + + showAllTokens: true + enabledChainIds: root.buyCryptoInputParamsForm.selectedNetworkChainId !== -1 ? [root.buyCryptoInputParamsForm.selectedNetworkChainId] : [] + accountAddress: root.buyCryptoInputParamsForm.selectedWalletAddress + searchString: selectParamsPanelInst.searchString + } + selectedProvider: root.buyCryptoAdaptor.selectedProvider + selectedTokenKey: root.buyCryptoInputParamsForm.selectedTokenKey + selectedNetworkChainId: root.buyCryptoInputParamsForm.selectedNetworkChainId + filteredFlatNetworksModel: root.buyCryptoAdaptor.filteredFlatNetworksModel + onNetworkSelected: { + if (root.buyCryptoInputParamsForm.selectedNetworkChainId !== chainId) { + root.buyCryptoInputParamsForm.selectedNetworkChainId = chainId + } + } + onTokenSelected: { + if (root.buyCryptoInputParamsForm.selectedTokenKey !== tokensKey) { + root.buyCryptoInputParamsForm.selectedTokenKey = tokensKey + } + } + } + } +} diff --git a/ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoModalAdaptor.qml b/ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoModalAdaptor.qml new file mode 100644 index 0000000000..d3d2463519 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoModalAdaptor.qml @@ -0,0 +1,146 @@ +import QtQml 2.15 +import SortFilterProxyModel 0.2 + +import StatusQ 0.1 +import StatusQ.Core.Utils 0.1 + +import utils 1.0 + +import shared.stores 1.0 +import AppLayouts.Wallet 1.0 +import AppLayouts.Wallet.stores 1.0 as WalletStore + +QObject { + id: root + + required property WalletStore.BuyCryptoStore buyCryptoStore + required property BuyCryptoParamsForm buyCryptoFormData + required property var walletAccountsModel + required property var networksModel + required property bool areTestNetworksEnabled + required property var groupedAccountAssetsModel + required property var plainTokensBySymbolModel + required property string currentCurrency + + QtObject { + id: d + property string uuid + property bool urlIsBeingFetched + readonly property var selectedProviderSupportedArray: !!selectedProvider && !!selectedProvider.supportedAssets ? ModelUtils.modelToFlatArray(selectedProvider.supportedAssets, "key"): null + } + + signal providerUrlReady(string url) + + readonly property bool providersLoading: root.buyCryptoStore.areProvidersLoading + readonly property var providersModel: root.buyCryptoStore.providersModel + + readonly property bool urlIsBeingFetched: d.urlIsBeingFetched + + readonly property var selectedAccount: selectedAccountEntry.item + readonly property var selectedToken: selectedTokenEntry.item + readonly property var selectedProvider: selectedProviderEntry.item + + readonly property SortFilterProxyModel filteredFlatNetworksModel: SortFilterProxyModel { + sourceModel: root.networksModel + filters: ValueFilter { roleName: "isTest"; value: root.areTestNetworksEnabled } + } + + /* TODO evaluate if this is still needed after + https://github.com/status-im/status-desktop/issues/16025 */ + readonly property ObjectProxyModel plainTokensBySymbolModelWithKey: ObjectProxyModel { + sourceModel: root.plainTokensBySymbolModel + delegate: SortFilterProxyModel { + id: delegateRoot + readonly property var addressPerChain: this + sourceModel: model.addressPerChain + proxyRoles: JoinRole { + name: "key" + roleNames: ["chainId", "address"] + separator: "" + } + filters: FastExpressionFilter { + expression: !!d.selectedProviderSupportedArray ? d.selectedProviderSupportedArray.includes(model.key) : true + expectedRoles: ["key"] + } + } + + exposedRoles: ["addressPerChain"] + expectedRoles: ["addressPerChain"] + } + + /* TODO evaluate if this is still needed after + https://github.com/status-im/status-desktop/issues/16025 */ + readonly property ObjectProxyModel groupedAccountAssetsModelWithKey: ObjectProxyModel { + sourceModel: root.groupedAccountAssetsModel + delegate: SortFilterProxyModel { + id: delegateRoot1 + readonly property var addressPerChain: this + sourceModel: model.addressPerChain + proxyRoles: JoinRole { + name: "key" + roleNames: ["chainId", "address"] + separator: "" + } + filters: FastExpressionFilter { + expression: !!d.selectedProviderSupportedArray ? d.selectedProviderSupportedArray.includes(model.key) : true + expectedRoles: ["key"] + } + } + + exposedRoles: ["addressPerChain"] + expectedRoles: ["addressPerChain"] + } + + function reset() { + d.uuid = "" + d.urlIsBeingFetched = false + } + + function fetchProviders() { + root.buyCryptoStore.fetchProviders() + } + + function fetchProviderUrl( + providerID, + isRecurrent, + accountAddress = "", + chainID = 0, + symbol = "") { + // Identify new search with a different uuid + d.uuid = Utils.uuid() + d.urlIsBeingFetched = true + buyCryptoStore.fetchProviderUrl(d.uuid, providerID, isRecurrent, + accountAddress, chainID,symbol) + } + + Connections { + target: root.buyCryptoStore + function onProviderUrlReady(uuid, url) { + if(uuid === d.uuid) { + d.urlIsBeingFetched = false + root.providerUrlReady(url) + } + } + } + + ModelEntry { + id: selectedAccountEntry + sourceModel: root.walletAccountsModel + key: "address" + value: root.buyCryptoFormData.selectedWalletAddress + } + + ModelEntry { + id: selectedTokenEntry + sourceModel: root.plainTokensBySymbolModel + key: "key" + value: root.buyCryptoFormData.selectedTokenKey + } + + ModelEntry { + id: selectedProviderEntry + sourceModel: root.providersModel + key: "id" + value: root.buyCryptoFormData.selectedProviderId + } +} diff --git a/ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoParamsForm.qml b/ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoParamsForm.qml new file mode 100644 index 0000000000..80f5477796 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/popups/buy/BuyCryptoParamsForm.qml @@ -0,0 +1,19 @@ +import QtQml 2.15 + +QtObject { + id: root + + property string selectedWalletAddress: "" + property int selectedNetworkChainId: -1 + property string selectedTokenKey: "" + property string selectedProviderId: "" + + readonly property bool filledCorrectly: !!selectedWalletAddress && !!selectedTokenKey && selectedNetworkChainId !== -1 + + function resetFormData() { + selectedWalletAddress = "" + selectedNetworkChainId = -1 + selectedTokenKey = "" + selectedProviderId = "" + } +} diff --git a/ui/app/AppLayouts/Wallet/popups/buy/qmldir b/ui/app/AppLayouts/Wallet/popups/buy/qmldir new file mode 100644 index 0000000000..eba2dd5a1b --- /dev/null +++ b/ui/app/AppLayouts/Wallet/popups/buy/qmldir @@ -0,0 +1,3 @@ +BuyCryptoModal 1.0 BuyCryptoModal.qml +BuyCryptoModalAdaptor 1.0 BuyCryptoModalAdaptor.qml +BuyCryptoParamsForm 1.0 BuyCryptoParamsForm.qml \ No newline at end of file diff --git a/ui/app/AppLayouts/Wallet/popups/qmldir b/ui/app/AppLayouts/Wallet/popups/qmldir index 5f65e3c82a..fd18eefd8a 100644 --- a/ui/app/AppLayouts/Wallet/popups/qmldir +++ b/ui/app/AppLayouts/Wallet/popups/qmldir @@ -6,5 +6,4 @@ ReceiveModal 1.0 ReceiveModal.qml AddEditSavedAddressPopup 1.0 AddEditSavedAddressPopup.qml RemoveSavedAddressPopup 1.0 RemoveSavedAddressPopup.qml SavedAddressActivityPopup 1.0 SavedAddressActivityPopup.qml -BuyCryptoModal 1.0 BuyCryptoModal.qml SignTransactionModalBase 1.0 SignTransactionModalBase.qml diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml index 49b47629d7..c820905dc8 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml @@ -16,6 +16,7 @@ import shared.controls 1.0 import AppLayouts.Wallet.controls 1.0 import AppLayouts.Wallet.panels 1.0 +import AppLayouts.Wallet.popups.buy 1.0 StatusDialog { id: root @@ -61,6 +62,12 @@ StatusDialog { } readonly property bool isError: root.swapAdaptor.errorMessage !== "" + + readonly property BuyCryptoParamsForm buyFormData: BuyCryptoParamsForm { + selectedWalletAddress: root.swapInputParamsForm.selectedAccountAddress + selectedNetworkChainId: root.swapInputParamsForm.selectedNetworkChainId + selectedTokenKey: root.swapInputParamsForm.fromTokensKey + } } Connections { @@ -314,7 +321,13 @@ StatusDialog { text: root.swapAdaptor.errorMessage buttonText: root.swapAdaptor.isTokenBalanceInsufficient ? qsTr("Buy crypto") : qsTr("Buy ETH") buttonVisible: visible && (root.swapAdaptor.isTokenBalanceInsufficient || root.swapAdaptor.isEthBalanceInsufficient) - onButtonClicked: Global.openBuyCryptoModalRequested() + onButtonClicked: { + // value dont update correctly if not done from here + d.buyFormData.selectedWalletAddress = root.swapInputParamsForm.selectedAccountAddress + d.buyFormData.selectedNetworkChainId = root.swapInputParamsForm.selectedNetworkChainId + d.buyFormData.selectedTokenKey =root.swapInputParamsForm.fromTokensKey + Global.openBuyCryptoModalRequested(d.buyFormData) + } } } } diff --git a/ui/app/AppLayouts/Wallet/stores/BuyCryptoStore.qml b/ui/app/AppLayouts/Wallet/stores/BuyCryptoStore.qml new file mode 100644 index 0000000000..6c03812228 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/stores/BuyCryptoStore.qml @@ -0,0 +1,35 @@ +import QtQuick 2.15 + +QtObject { + id: root + + readonly property var providersModel: walletSectionBuySellCrypto.model + readonly property bool areProvidersLoading: walletSectionBuySellCrypto.isFetching + + signal providerUrlReady(string uuid , string url) + + function fetchProviders() { + walletSectionBuySellCrypto.fetchProviders() + } + + function fetchProviderUrl( + uuid, + providerID, + isRecurrent, + selectedWalletAddress = "", + chainID = 0, + symbol = "") { + walletSectionBuySellCrypto.fetchProviderUrl( + uuid, + providerID, + isRecurrent, + selectedWalletAddress, + chainID, + symbol + ) + } + + Component.onCompleted: { + walletSectionBuySellCrypto.providerUrlReady.connect(root.providerUrlReady) + } +} diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index e31a69bcf0..592540a99c 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -172,8 +172,6 @@ QtObject { d.initChainColors(flatNetworks) } - property var cryptoRampServicesModel: walletSectionBuySellCrypto.model - function resetCurrentViewedHolding(type) { currentViewedHoldingTokensKey = "" currentViewedHoldingID = "" diff --git a/ui/app/AppLayouts/Wallet/stores/qmldir b/ui/app/AppLayouts/Wallet/stores/qmldir index 61315b0082..ea2aee58a4 100644 --- a/ui/app/AppLayouts/Wallet/stores/qmldir +++ b/ui/app/AppLayouts/Wallet/stores/qmldir @@ -4,3 +4,4 @@ CollectiblesStore 1.0 CollectiblesStore.qml TokensStore 1.0 TokensStore.qml WalletAssetsStore 1.0 WalletAssetsStore.qml SwapStore 1.0 SwapStore.qml +BuyCryptoStore 1.0 BuyCryptoStore.qml diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 8f71444605..c64f3d335f 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -16,6 +16,7 @@ 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.popups.buy 1.0 import AppLayouts.Wallet.popups 1.0 import AppLayouts.Wallet.stores 1.0 as WalletStore @@ -392,8 +393,10 @@ QtObject { openPopup(swapModal, {swapInputParamsForm: parameters}) } - function openBuyCryptoModal() { - openPopup(buyCryptoModal) + function openBuyCryptoModal(parameters) { + openPopup(buyCryptoModal, { + buyCryptoInputParamsForm: parameters + }) } readonly property list _components: [ @@ -1242,7 +1245,16 @@ QtObject { Component { id: buyCryptoModal BuyCryptoModal { - onRampProvidersModel: WalletStore.RootStore.cryptoRampServicesModel + buyCryptoAdaptor: BuyCryptoModalAdaptor { + buyCryptoStore: WalletStore.BuyCryptoStore {} + buyCryptoFormData: buyCryptoInputParamsForm + walletAccountsModel: root.rootStore.accounts + networksModel: root.rootStore.profileSectionStore.walletStore.flatNetworks + areTestNetworksEnabled: root.rootStore.profileSectionStore.walletStore.areTestNetworksEnabled + groupedAccountAssetsModel: root.walletAssetsStore.groupedAccountAssetsModel + plainTokensBySymbolModel: root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel + currentCurrency: root.currencyStore.currentCurrency + } onClosed: destroy() } } diff --git a/ui/imports/assets/png/onRampProviders/mercuryo.png b/ui/imports/assets/png/onRampProviders/mercuryo.png new file mode 100644 index 0000000000000000000000000000000000000000..0a5b6381ee211d2f9f920620e11e7aa9b19db336 GIT binary patch literal 1686 zcmV;H25I?;P)aJvP6f&kqG}ch?J8oj;t0#hmXul=7LD0huBq&9J~=DN)!Eo;d#soKC(T~ z7hq()W`wI|92^G3IM`o+{{X0HYiolKA3nhQ_wS*rs|%)0n+CD5u`qMyOz0z0$YSXr z)qzcd$>2YkDzdY)VaJXgFmmKb*tTsO3?DumzI^!tA3uJC0RslW#fukV?b@|Lb5&PY zLuzU&j2=B2=FFJ`Cg77E3s%tE*4EZmoH1ht-n@BJcaNSudxn!IPsV-w_F+>~Q^2!V zuU;Wf%g_3{-AwLxyR{`t)g>K7Bgo`>k0Wgh(sm+kEv{Ix0yl5oEFNR8>fzR{TjK4MloV<2+OicduHm?sp@1!MMXG!_Uxb;nAn927jV|BS$Oi~ z$)MPK`t&I}olcb;+t#gHF$6euz4zwLn}B#>Z^p#L7|!DJ`S8Gj1G242Nl9uyFKpDP zQ2_+|_wO%<#wI*kfm&t zxwXA#&YY1zSJ2$tj1wkIFgz4mR904s_m?bLqDxM(#fukXNl6LXfV&^`$`-?h4TF}J z7PUQO$Pf|dva&L;0%w;=lP1CU@83QT)=+ zpFhK$J9ktC&aJY}qncuwVhqpFbZ+Irvc?$6!z? z#W6%LRonIT^`d*--QChoPEIzBvA!D1mU2*{JRVPc?-fNwMF9yiP}Y$4ph1J+<;#~6 zi(I*IthKkdOS_|^184!zSCD1`$r)_eumRSuUk{`l-2EHq9Ub-<1-odOf+tU&;Fc|0 zg2oa-5%ulcw}<7eMT-{U?c2Aps;Wwl3hHh#gUTmKX<8zjHPX}5(FmO4rc9Y)ix}tk z>({TN1!@CCI^aodt%&NUPoLC27V*7%_o5kC3pw+ck)!$%Nfporl9}KOqo+Ix7Ya2s zH7aQq?Vmq?q8T_u=FXj~OK!x75t5m>kg$OSCV13r#JO|lB=(dO%&|x!i&o&pk?02z zynOjGo;Yy=uU)%_HsA?Lb-h9N5Zt(NBc4BhUhQ+c-Gu3TyLIu~d`9e<$>aGAvNbL7Yoi6_dN+SC*TK2$utR-QPGT5PfE zy?z>w9`JvaGR4@jW8vGkZ$K9yptuCD*DI9{d5RYqG@c89Q>RV=DS_Je>(?)(7|;X9 z-k-Wp&_Z&8mg)71PR7N>N!DRGQsT@Et5&U&tYd&ZUJ|yn>KBdG{#e~mmWQOBn#jJRbF1Md$HFb3G4#>r#czK zKtVx)=p}upk+eqNTX5*mq2k#?hYm@u*1_MXcUiYVG^Fj;1Kn@TWQg;py=^ri$=_>y z7e5QzFhB-GvGF;A$(91teZUum%Mwbj*Q+J8gvmjS-N g{!5k`?!O`a1AAg07K1F~_5c6?07*qoM6N<$f@3!#7ytkO literal 0 HcmV?d00001 diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index b8274d3554..e8b2ed52d7 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -105,7 +105,7 @@ QtObject { signal openSwapModalRequested(var formDataParams) // BuyCrypto - signal openBuyCryptoModalRequested() + signal openBuyCryptoModalRequested(var formDataParams) // Metrics signal openMetricsEnablePopupRequested(string placement, var cb)