feat:[UI - Swap] Create new TokenSelector component

- create new dedicated (asset) token selector component
- integrate it into `SwapInputPanel` and `SwapModal`
- add respective SB page and QML tests suite

Fixes #14783
This commit is contained in:
Lukáš Tinkl 2024-06-19 00:51:49 +02:00 committed by Lukáš Tinkl
parent afde836517
commit 42533b8c61
18 changed files with 871 additions and 331 deletions

View File

@ -30,12 +30,12 @@ SplitView {
id: d
readonly property SwapInputParamsForm swapInputParamsForm: SwapInputParamsForm {
selectedNetworkChainId: ctrlSelectedNetworkChainId.currentValue
selectedAccountAddress: ctrlAccount.currentValue ?? ""
selectedNetworkChainId: ctrlSelectedNetworkChainId.currentValue ?? -1
fromTokensKey: ctrlFromTokensKey.text
fromTokenAmount: ctrlFromTokenAmount.text
toTokenKey: ctrlToTokenKey.text
toTokenAmount: ctrlToTokenAmount.text
selectedAccountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"
}
readonly property SwapModalAdaptor adaptor: SwapModalAdaptor {
@ -78,7 +78,11 @@ SplitView {
currencyStore: d.adaptor.currencyStore
flatNetworksModel: d.adaptor.swapStore.flatNetworks
processedAssetsModel: d.adaptor.processedAssetsModel
processedAssetsModel: d.adaptor.walletAssetsStore.groupedAccountAssetsModel
selectedNetworkChainId: d.swapInputParamsForm.selectedNetworkChainId
selectedAccountAddress: d.swapInputParamsForm.selectedAccountAddress
nonInteractiveTokensKey: receivePanel.selectedHoldingId
tokenKey: d.swapInputParamsForm.fromTokensKey
tokenAmount: d.swapInputParamsForm.fromTokenAmount
@ -99,8 +103,12 @@ SplitView {
}
currencyStore: d.adaptor.currencyStore
flatNetworksModel: d.adaptor.filteredFlatNetworksModel
processedAssetsModel: d.adaptor.processedAssetsModel
flatNetworksModel: d.adaptor.swapStore.flatNetworks
processedAssetsModel: d.adaptor.walletAssetsStore.groupedAccountAssetsModel
selectedNetworkChainId: d.swapInputParamsForm.selectedNetworkChainId
selectedAccountAddress: d.swapInputParamsForm.selectedAccountAddress
nonInteractiveTokensKey: payPanel.selectedHoldingId
tokenKey: d.swapInputParamsForm.toTokenKey
tokenAmount: d.swapInputParamsForm.toTokenAmount
@ -146,6 +154,24 @@ SplitView {
}
}
RowLayout {
Layout.fillWidth: true
Label { text: "Account:" }
ComboBox {
Layout.fillWidth: true
id: ctrlAccount
textRole: "name"
valueRole: "address"
displayText: currentText || "All accounts"
model: SortFilterProxyModel {
sourceModel: d.adaptor.swapStore.accounts
sorters: RoleSorter { roleName: "position" }
}
currentIndex: -1
}
}
RowLayout {
Layout.fillWidth: true
Label {

View File

@ -35,13 +35,6 @@ SplitView {
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
}
readonly property SwapTransactionRoutes dummySwapTransactionRoutes: SwapTransactionRoutes{}
}
@ -103,19 +96,11 @@ SplitView {
closePolicy: Popup.CloseOnEscape
destroyOnClose: true
swapInputParamsForm: SwapInputParamsForm {
selectedAccountAddress: accountComboBox.currentValue ?? ""
selectedNetworkChainId: d.getNetwork()
fromTokensKey: fromTokenComboBox.currentValue
fromTokenAmount: swapInput.text
toTokenKey: toTokenComboBox.currentValue
onSelectedAccountAddressChanged: {
if (selectedAccountAddress !== accountComboBox.currentValue)
accountComboBox.currentIndex = accountComboBox.indexOfValue(selectedAccountAddress)
}
Binding on selectedAccountAddress {
value: accountComboBox.currentValue ?? ""
}
fromTokenAmount: swapInput.text
}
swapAdaptor: SwapModalAdaptor {
swapStore: dSwapStore
@ -132,12 +117,27 @@ SplitView {
Binding {
target: swapInputParamsForm
property: "fromTokensKey"
value: fromTokenComboBox.currentValue
value: fromTokenComboBox.currentValue ?? ""
}
Binding {
target: swapInputParamsForm
property: "toTokenKey"
value: toTokenComboBox.currentValue
value: toTokenComboBox.currentValue ?? ""
}
Binding {
target: swapInputParamsForm
property: "selectedNetworkChainId"
value: networksComboBox.currentValue ?? -1
}
Binding {
target: swapInputParamsForm
property: "selectedAccountAddress"
value: accountComboBox.currentValue ?? ""
}
Binding {
target: swapInputParamsForm
property: "fromTokenAmount"
value: swapInput.text
}
}
}
@ -184,6 +184,7 @@ SplitView {
ComboBox {
id: networksComboBox
textRole: "chainName"
valueRole: "chainId"
model: d.filteredNetworksModel
currentIndex: 0
onCountChanged: currentIndex = 0
@ -201,7 +202,7 @@ SplitView {
StatusInput {
id: swapInput
Layout.preferredWidth: 100
Layout.preferredWidth: 250
label: "Token amount to swap"
text: ""
}

View File

@ -0,0 +1,164 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Window 2.15
import StatusQ.Core.Theme 0.1
import Storybook 1.0
import Models 1.0
import SortFilterProxyModel 0.2
import AppLayouts.Wallet.controls 1.0
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.adaptors 1.0
import shared.stores 1.0
import utils 1.0
SplitView {
id: root
orientation: Qt.Vertical
Logs { id: logs }
QtObject {
id: d
readonly property var flatNetworks: NetworksModel.flatNetworks
readonly property var currencyStore: CurrenciesStore {}
readonly property var assetsStore: WalletAssetsStore {
id: thisWalletAssetStore
walletTokensStore: TokensStore {
plainTokensBySymbolModel: TokensBySymbolModel {}
}
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
readonly property var walletAccountsModel: WalletAccountsModel {}
readonly property var adaptor: TokenSelectorViewAdaptor {
assetsModel: d.assetsStore.groupedAccountAssetsModel
flatNetworksModel: d.flatNetworks
currentCurrency: d.currencyStore.currentCurrency
enabledChainIds: ctrlNetwork.currentValue ? [ctrlNetwork.currentValue] : []
accountAddress: ctrlAccount.currentValue ?? ""
showCommunityAssets: ctrlShowCommunityAssets.checked
searchString: tokenSelector.searchString
}
}
Pane {
SplitView.fillWidth: true
SplitView.fillHeight: true
background: Rectangle {
color: Theme.palette.baseColor3
}
TokenSelector {
id: tokenSelector
anchors.centerIn: parent
nonInteractiveDelegateKey: ctrlNonInteractiveDelegateKey.text
model: d.adaptor.outputAssetsModel
onTokenSelected: (tokensKey) => {
console.warn("!!! TOKEN SELECTED:", tokensKey)
logs.logEvent("TokenSelector::onTokenSelected", ["tokensKey"], arguments)
}
onActivated: ctrlSelectedAsset.currentIndex = ctrlSelectedAsset.indexOfValue(currentTokensKey)
}
}
LogsAndControlsPanel {
SplitView.minimumHeight: 320
SplitView.preferredHeight: 320
logsView.logText: logs.logText
RowLayout {
anchors.fill: parent
ColumnLayout {
RowLayout {
Layout.fillWidth: true
Label { text: "Selected asset:" }
ComboBox {
Layout.fillWidth: true
id: ctrlSelectedAsset
model: d.assetsStore.groupedAccountAssetsModel
textRole: "name"
valueRole: "tokensKey"
displayText: currentText || "N/A"
onActivated: tokenSelector.selectToken(currentValue)
}
TextField {
id: ctrlNonInteractiveDelegateKey
placeholderText: "Non interactive delegate token key"
}
}
Button {
text: "Reset"
onClicked: {
tokenSelector.reset()
ctrlSelectedAsset.currentIndex = -1
ctrlNonInteractiveDelegateKey.clear()
}
}
Switch {
id: ctrlShowCommunityAssets
text: "Show community assets"
}
RowLayout {
Layout.fillWidth: true
Label { text: "Network:" }
ComboBox {
Layout.fillWidth: true
id: ctrlNetwork
textRole: "chainName"
valueRole: "chainId"
displayText: currentText || "All networks"
model: d.flatNetworks
currentIndex: -1
}
}
Label {
Layout.alignment: Qt.AlignRight
text: "Selected: %1".arg(ctrlNetwork.currentValue ? ctrlNetwork.currentValue.toString() : "All")
}
RowLayout {
Layout.fillWidth: true
Label { text: "Account:" }
ComboBox {
Layout.fillWidth: true
id: ctrlAccount
textRole: "name"
valueRole: "address"
displayText: currentText || "All accounts"
model: SortFilterProxyModel {
sourceModel: d.walletAccountsModel
sorters: RoleSorter { roleName: "position" }
}
currentIndex: -1
}
}
Label {
Layout.alignment: Qt.AlignRight
text: "Selected: %1".arg(ctrlAccount.currentValue ?? "all")
}
Item { Layout.fillHeight: true }
}
}
}
}
// category: Controls
// https://www.figma.com/design/TS0eQX9dAZXqZtELiwKIoK/Swap---Milestone-1?node-id=3406-231273&t=Ncl9lN1umbGEMxOn-0

View File

@ -9,11 +9,10 @@ import StatusQ.Core.Utils 0.1
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.panels 1.0
import AppLayouts.Wallet.popups.swap 1.0
import AppLayouts.Wallet.adaptors 1.0
import shared.stores 1.0
import SortFilterProxyModel 0.2
import Models 1.0
import Storybook 1.0
@ -45,6 +44,14 @@ Item {
}
swapOutputData: SwapOutputData {}
}
readonly property var tokenSelectorAdaptor: TokenSelectorViewAdaptor {
assetsModel: d.adaptor.walletAssetsStore.groupedAccountAssetsModel
flatNetworksModel: d.adaptor.swapStore.flatNetworks
currentCurrency: d.adaptor.currencyStore.currentCurrency
accountAddress: d.adaptor.swapFormData.selectedAccountAddress
}
}
Component {
@ -53,8 +60,8 @@ Item {
anchors.centerIn: parent
currencyStore: d.adaptor.currencyStore
flatNetworksModel: d.adaptor.filteredFlatNetworksModel
processedAssetsModel: d.adaptor.processedAssetsModel
flatNetworksModel: d.adaptor.swapStore.flatNetworks
processedAssetsModel: d.adaptor.walletAssetsStore.groupedAccountAssetsModel
}
}
@ -114,7 +121,8 @@ Item {
{ tag: "1234567890", tokenAmount: "1234567890", valid: true },
{ tag: "1234567890.1234567890", tokenAmount: "1234567890.1234567890", valid: true },
{ tag: "abc", tokenAmount: "abc", valid: false },
{ tag: "NaN", tokenAmount: "NaN", valid: false }
{ tag: "NaN", tokenAmount: NaN, valid: false },
{ tag: "<empty>", tokenAmount: "", valid: false }
]
}
@ -136,7 +144,7 @@ Item {
const holdingSelector = findChild(controlUnderTest, "holdingSelector")
verify(!!holdingSelector)
tryCompare(holdingSelector.selectedItem, "symbol", tokenSymbol)
tryCompare(holdingSelector, "currentTokensKey", tokenSymbol)
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput)
@ -182,13 +190,13 @@ Item {
mouseClick(holdingSelector)
waitForRendering(holdingSelector)
const assetSelectorList = findChild(holdingSelector, "assetSelectorList")
const assetSelectorList = findChild(holdingSelector, "tokenSelectorListview")
verify(!!assetSelectorList)
waitForRendering(assetSelectorList)
const sttDelegate = findChild(assetSelectorList, "AssetSelector_ItemDelegate_STT")
const sttDelegate = findChild(assetSelectorList, "tokenSelectorAssetDelegate_STT")
verify(!!sttDelegate)
mouseClick(sttDelegate, 40, 40) // center might be covered by tags
mouseClick(sttDelegate)
tryCompare(controlUnderTest, "selectedHoldingId", "STT")
@ -303,9 +311,6 @@ Item {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
verify(!!controlUnderTest)
controlUnderTest.mainInputLoading = true
controlUnderTest.bottomTextLoading = true
const maxTagButton = findChild(controlUnderTest, "maxTagButton")
verify(!!maxTagButton)
verify(!maxTagButton.visible)
@ -313,42 +318,41 @@ Item {
const holdingSelector = findChild(controlUnderTest, "holdingSelector")
verify(!!holdingSelector)
const assetSelectorList = findChild(holdingSelector, "assetSelectorList")
const assetSelectorList = findChild(holdingSelector, "tokenSelectorListview")
verify(!!assetSelectorList)
const assetSelectorButton = findChild(controlUnderTest, "assetSelectorButton")
verify(!!assetSelectorButton)
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput)
const bottomItemText = findChild(amountToSendInput, "bottomItemText")
verify(!!bottomItemText)
for (let i= 0; i < d.adaptor.processedAssetsModel.count; i++) {
let modelItemToTest = ModelUtils.get(d.adaptor.processedAssetsModel, i)
mouseClick(assetSelectorButton)
for (let i= 0; i < d.tokenSelectorAdaptor.outputAssetsModel.count; i++) {
let modelItemToTest = ModelUtils.get(d.tokenSelectorAdaptor.outputAssetsModel, i)
mouseClick(holdingSelector)
waitForRendering(assetSelectorList)
let delToTest = assetSelectorList.itemAtIndex(i)
verify(!!delToTest)
mouseClick(delToTest, 40, 40) // center might be covered by tags
mouseClick(delToTest)
waitForRendering(maxTagButton)
waitForRendering(controlUnderTest)
verify(maxTagButton.visible)
verify(!maxTagButton.text.endsWith(modelItemToTest.symbol))
compare(maxTagButton.type, modelItemToTest.currentBalance === 0 ? StatusBaseButton.Type.Danger : StatusBaseButton.Type.Normal)
tryCompare(maxTagButton, "type", modelItemToTest.currentBalance === 0 ? StatusBaseButton.Type.Danger : StatusBaseButton.Type.Normal)
// check input value and state
mouseClick(maxTagButton)
waitForRendering(amountToSendInput)
compare(amountToSendInput.input.text, modelItemToTest.currentBalance === 0 ? "" : maxTagButton.maxSafeValueAsString)
tryCompare(amountToSendInput.input, "text", modelItemToTest.currentBalance === 0 ? "" : maxTagButton.maxSafeValueAsString)
compare(controlUnderTest.value, maxTagButton.maxSafeValue)
verify(modelItemToTest.currentBalance === 0 ? !controlUnderTest.valueValid : controlUnderTest.valueValid)
compare(bottomItemText.text, d.adaptor.formatCurrencyAmount(
maxTagButton.maxSafeValue * amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount,
d.adaptor.currencyStore.currentCurrency))
amountToSendInput.input.input.edit.clear()
}
}
@ -366,12 +370,9 @@ Item {
const holdingSelector = findChild(controlUnderTest, "holdingSelector")
verify(!!holdingSelector)
const assetSelectorList = findChild(holdingSelector, "assetSelectorList")
const assetSelectorList = findChild(holdingSelector, "tokenSelectorListview")
verify(!!assetSelectorList)
const assetSelectorButton = findChild(controlUnderTest, "assetSelectorButton")
verify(!!assetSelectorButton)
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput)
@ -388,22 +389,22 @@ Item {
compare(amountToSendInput.input.text, "5.42")
for (let i= 0; i < d.adaptor.processedAssetsModel.count; i++) {
let modelItemToTest = ModelUtils.get(d.adaptor.processedAssetsModel, i)
mouseClick(assetSelectorButton)
waitForRendering(assetSelectorList)
for (let i= 0; i < d.tokenSelectorAdaptor.outputAssetsModel.count; i++) {
let modelItemToTest = ModelUtils.get(d.tokenSelectorAdaptor.outputAssetsModel, i)
mouseClick(holdingSelector)
waitForRendering(holdingSelector)
let delToTest = assetSelectorList.itemAtIndex(i)
verify(!!delToTest)
mouseClick(delToTest, 40, 40) // center might be covered by tags
mouseClick(delToTest)
// check input value and state
waitForRendering(amountToSendInput)
waitForItemPolished(controlUnderTest)
compare(amountToSendInput.input.text, "5.42")
compare(bottomItemText.text, d.adaptor.formatCurrencyAmount(
numberTested * amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount,
d.adaptor.currencyStore.currentCurrency))
tryCompare(bottomItemText, "text", d.adaptor.formatCurrencyAmount(
numberTested * amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount,
d.adaptor.currencyStore.currentCurrency))
compare(controlUnderTest.value, numberTested)
compare(controlUnderTest.rawValue, AmountsArithmetic.fromNumber(amountToSendInput.input.text, modelItemToTest.decimals).toString())
compare(controlUnderTest.valueValid, numberTested <= maxTagButton.maxSafeValue)
@ -415,7 +416,7 @@ Item {
function test_if_values_are_reset_after_setting_tokenAmount_as_empty() {
const tokenKeyToTest = "ETH"
let numberTestedString = "1.0001"
let modelItemToTest = ModelUtils.getByKey(d.adaptor.processedAssetsModel, "tokensKey", tokenKeyToTest)
let modelItemToTest = ModelUtils.getByKey(d.tokenSelectorAdaptor.outputAssetsModel, "tokensKey", tokenKeyToTest)
controlUnderTest = createTemporaryObject(componentUnderTest, root, {
swapSide: SwapInputPanel.SwapSide.Pay,
tokenKey: tokenKeyToTest,
@ -442,12 +443,11 @@ Item {
numberTestedString = ""
numberTested = 0
mouseClick(amountToSendInput)
controlUnderTest.tokenAmount = numberTestedString
waitForRendering(amountToSendInput)
waitForItemPolished(controlUnderTest)
compare(amountToSendInput.input.text, numberTestedString)
compare(controlUnderTest.value, numberTested)
tryCompare(amountToSendInput.input, "text", numberTestedString)
tryCompare(controlUnderTest, "value", numberTested)
compare(controlUnderTest.rawValue, AmountsArithmetic.fromNumber(numberTested, modelItemToTest.decimals).toString())
compare(controlUnderTest.valueValid, false)
compare(controlUnderTest.selectedHoldingId, tokenKeyToTest)

View File

@ -16,11 +16,12 @@ import shared.stores 1.0
import AppLayouts.Wallet.popups.swap 1.0
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet 1.0
import AppLayouts.Wallet.adaptors 1.0
Item {
id: root
width: 600
height: 400
width: 800
height: 600
readonly property var dummySwapTransactionRoutes: SwapTransactionRoutes {}
@ -33,7 +34,7 @@ Item {
return wei/(10**decimals)
}
function fetchSuggestedRoutes(accountFrom, accountTo, amount, tokenFrom, tokenTo,
disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) {}
disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) {}
}
readonly property var swapAdaptor: SwapModalAdaptor {
@ -52,6 +53,15 @@ Item {
swapOutputData: SwapOutputData{}
}
readonly property var tokenSelectorAdaptor: TokenSelectorViewAdaptor {
assetsModel: swapAdaptor.walletAssetsStore.groupedAccountAssetsModel
flatNetworksModel: swapStore.flatNetworks
currentCurrency: swapAdaptor.currencyStore
enabledChainIds: !!root.swapFormData && root.swapFormData.selectedNetworkChainId !== - 1 ? [root.swapFormData.selectedNetworkChainId] : []
accountAddress: !!root.swapFormData && root.swapFormData.selectedAccountAddress
}
property SwapInputParamsForm swapFormData: null
Component {
@ -67,17 +77,18 @@ Item {
}
}
SignalSpy {
id: formValuesChanged
target: swapFormData
signalName: "formValuesChanged"
}
TestCase {
name: "SwapModal"
when: windowShown
property SwapModal controlUnderTest: null
readonly property SignalSpy formValuesChanged: SignalSpy {
target: root.swapFormData
signalName: "formValuesChanged"
}
// helper functions -------------------------------------------------------------
function init() {
@ -86,6 +97,11 @@ Item {
controlUnderTest = createTemporaryObject(componentUnderTest, root, { swapInputParamsForm: root.swapFormData})
}
function cleanup() {
root.swapFormData.resetFormData()
formValuesChanged.clear()
}
function launchAndVerfyModal() {
formValuesChanged.clear()
verify(!!controlUnderTest)
@ -415,6 +431,7 @@ Item {
for(let j =0; j< comboBoxList.model.count; j++) {
let accountDelegateUnderTest = comboBoxList.itemAtIndex(j)
verify(!!accountDelegateUnderTest)
waitForItemPolished(accountDelegateUnderTest)
const inlineTagDelegate_0 = findChild(accountDelegateUnderTest, "inlineTagDelegate_0")
verify(!!inlineTagDelegate_0)
@ -504,6 +521,8 @@ Item {
// Launch popup
launchAndVerfyModal()
waitForItemPolished(controlUnderTest.contentItem)
const maxFeesText = findChild(controlUnderTest, "maxFeesText")
verify(!!maxFeesText)
@ -676,8 +695,6 @@ Item {
verify(!!maxTagButton)
const holdingSelectorsContentItemText = findChild(payPanel, "holdingSelectorsContentItemText")
verify(!!holdingSelectorsContentItemText)
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel)
@ -688,10 +705,8 @@ Item {
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, undefined)
compare(holdingSelector.currentTokensKey, "")
compare(holdingSelectorsContentItemText.text, qsTr("Select asset"))
compare(holdingSelectorsTokenIcon.image.source, "")
verify(!holdingSelectorsTokenIcon.visible)
verify(!maxTagButton.visible)
compare(payPanel.selectedHoldingId, "")
compare(payPanel.value, 0)
@ -710,11 +725,13 @@ Item {
root.swapFormData.fromTokensKey = "ETH"
root.swapFormData.fromTokenAmount = valueToExchangeString
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
let expectedToken = SQUtils.ModelUtils.getByKey(root.tokenSelectorAdaptor.outputAssetsModel, "tokensKey", "ETH")
// Launch popup
launchAndVerfyModal()
waitForItemPolished(controlUnderTest.contentItem)
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
const amountToSendInput = findChild(payPanel, "amountToSendInput")
@ -730,20 +747,18 @@ Item {
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel)
compare(amountToSendInput.caption, qsTr("Pay"))
verify(amountToSendInput.interactive)
compare(amountToSendInput.input.text, valueToExchangeString)
tryCompare(amountToSendInput.input.input, "text", valueToExchangeString)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToExchange * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, expectedToken)
tryCompare(amountToSendInput.input.input.edit, "cursorVisible", true)
tryCompare(bottomItemText, "text", root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToExchange * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.currentTokensKey, expectedToken.tokensKey)
compare(holdingSelectorsContentItemText.text, expectedToken.symbol)
compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol))
verify(holdingSelectorsTokenIcon.visible)
verify(maxTagButton.visible)
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(Math.trunc(WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)*100)/100, expectedToken.symbol, {noSymbol: true})))
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol), expectedToken.symbol, {noSymbol: true})))
compare(payPanel.selectedHoldingId, expectedToken.symbol)
compare(payPanel.value, valueToExchange)
compare(payPanel.rawValue, SQUtils.AmountsArithmetic.fromNumber(valueToExchangeString, expectedToken.decimals).toString())
@ -777,8 +792,6 @@ Item {
verify(!!maxTagButton)
const holdingSelectorsContentItemText = findChild(payPanel, "holdingSelectorsContentItemText")
verify(!!holdingSelectorsContentItemText)
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel)
@ -787,11 +800,10 @@ Item {
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, null)
compare(holdingSelectorsContentItemText.text, "")
verify(!holdingSelectorsTokenIcon.visible)
compare(holdingSelector.currentTokensKey, "")
compare(holdingSelectorsContentItemText.text, "Select asset")
verify(!maxTagButton.visible)
compare(payPanel.selectedHoldingId, invalidValue)
compare(payPanel.selectedHoldingId, "")
compare(payPanel.value, 0)
compare(payPanel.rawValue, SQUtils.AmountsArithmetic.fromNumber("0", 0).toString())
verify(!payPanel.valueValid)
@ -809,11 +821,13 @@ Item {
root.swapFormData.fromTokensKey = "ETH"
root.swapFormData.fromTokenAmount = valueToExchangeString
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
let expectedToken = SQUtils.ModelUtils.getByKey(root.tokenSelectorAdaptor.outputAssetsModel, "tokensKey", "ETH")
// Launch popup
launchAndVerfyModal()
waitForItemPolished(controlUnderTest.contentItem)
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
const amountToSendInput = findChild(payPanel, "amountToSendInput")
@ -829,20 +843,18 @@ Item {
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel)
compare(amountToSendInput.caption, qsTr("Pay"))
verify(amountToSendInput.interactive)
compare(amountToSendInput.input.text, valueToExchangeString)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToExchange * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, expectedToken)
tryCompare(bottomItemText, "text", root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToExchange * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.currentTokensKey, expectedToken.tokensKey)
compare(holdingSelectorsContentItemText.text, expectedToken.symbol)
compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol))
verify(holdingSelectorsTokenIcon.visible)
verify(maxTagButton.visible)
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(Math.trunc(WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)*100)/100, expectedToken.symbol, {noSymbol: true})))
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol), expectedToken.symbol, {noSymbol: true})))
compare(payPanel.selectedHoldingId, expectedToken.symbol)
compare(payPanel.value, valueToExchange)
compare(payPanel.rawValue, SQUtils.AmountsArithmetic.fromNumber(valueToExchangeString, expectedToken.decimals).toString())
@ -853,7 +865,6 @@ Item {
function test_modal_pay_input_switching_networks() {
// try setting value before popup is launched and check values
root.swapFormData.resetFormData()
let valueToExchange = 0.3
let valueToExchangeString = valueToExchange.toString()
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address
@ -871,11 +882,11 @@ Item {
for (let i=0; i< root.swapAdaptor.filteredFlatNetworksModel.count; i++) {
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(i).chainId
waitForRendering(payPanel)
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
let expectedToken = SQUtils.ModelUtils.getByKey(root.tokenSelectorAdaptor.outputAssetsModel, "tokensKey", "ETH")
// check states for the pay input selector
verify(maxTagButton.visible)
let maxPossibleValue = Math.trunc(WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)*100)/100
let maxPossibleValue = WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue, expectedToken.symbol, {noSymbol: true})))
compare(payPanel.selectedHoldingId, expectedToken.symbol)
compare(payPanel.valueValid, valueToExchange <= maxPossibleValue)
@ -911,7 +922,7 @@ Item {
verify(!amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, undefined)
compare(holdingSelector.currentTokensKey, "")
compare(holdingSelectorsContentItemText.text, qsTr("Select asset"))
verify(!maxTagButton.visible)
compare(receivePanel.selectedHoldingId, "")
@ -931,11 +942,13 @@ Item {
root.swapFormData.toTokenKey = "STT"
root.swapFormData.toTokenAmount = valueToReceiveString
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "STT")
let expectedToken = SQUtils.ModelUtils.getByKey(root.tokenSelectorAdaptor.outputAssetsModel, "tokensKey", "STT")
// Launch popup
launchAndVerfyModal()
waitForItemPolished(controlUnderTest.contentItem)
const receivePanel = findChild(controlUnderTest, "receivePanel")
verify(!!receivePanel)
const amountToSendInput = findChild(receivePanel, "amountToSendInput")
@ -951,16 +964,14 @@ Item {
const holdingSelectorsTokenIcon = findChild(receivePanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(receivePanel)
compare(amountToSendInput.caption, qsTr("Receive"))
// TODO: this should be come interactive under https://github.com/status-im/status-desktop/issues/15095
verify(!amountToSendInput.interactive)
verify(!amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, valueToReceive.toLocaleString(Qt.locale(), 'f', -128))
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToReceive * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, expectedToken)
tryCompare(bottomItemText, "text", root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToReceive * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.currentTokensKey, expectedToken.tokensKey)
compare(holdingSelectorsContentItemText.text, expectedToken.symbol)
compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol))
verify(holdingSelectorsTokenIcon.visible)
@ -974,22 +985,23 @@ Item {
}
function test_modal_max_button_click_with_preset_pay_value() {
// Launch popup
launchAndVerfyModal()
// The default is the first account. Setting the second account to test switching accounts
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(1).address
formValuesChanged.clear()
// try setting value before popup is launched and check values
let valueToExchange = 0.2
let valueToExchangeString = valueToExchange.toString()
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address
// The default is the first account. Setting the second account to test switching accounts
root.swapFormData.fromTokensKey = "ETH"
root.swapFormData.fromTokenAmount = valueToExchangeString
root.swapFormData.toTokenKey = "STT"
compare(formValuesChanged.count, 5)
compare(formValuesChanged.count, 6)
// Launch popup
launchAndVerfyModal()
// The default is the first account. Setting the second account to test switching accounts
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(1).address
waitForItemPolished(controlUnderTest.contentItem)
const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel)
@ -1000,31 +1012,32 @@ Item {
const bottomItemText = findChild(payPanel, "bottomItemText")
verify(!!bottomItemText)
waitForRendering(payPanel)
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
let expectedToken = SQUtils.ModelUtils.getByKey(root.tokenSelectorAdaptor.outputAssetsModel, "tokensKey", "ETH")
// check states for the pay input selector
verify(maxTagButton.visible)
let maxPossibleValue = WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)
let truncmaxPossibleValue = Math.trunc(maxPossibleValue*100)/100
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(truncmaxPossibleValue, expectedToken.symbol, {noSymbol: true})))
compare(maxTagButton.text, qsTr("Max. %1").arg(truncmaxPossibleValue === 0 ? Qt.locale().zeroDigit
: root.swapAdaptor.currencyStore.formatCurrencyAmount(truncmaxPossibleValue, expectedToken.symbol, {noSymbol: true})))
waitForItemPolished(amountToSendInput)
verify(amountToSendInput.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, valueToExchange.toLocaleString(Qt.locale(), 'f', -128))
tryCompare(amountToSendInput.input.input.edit, "cursorVisible", true)
tryCompare(amountToSendInput.input, "text", valueToExchange.toLocaleString(Qt.locale(), 'f', -128))
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToExchange * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
tryCompare(bottomItemText, "text", root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToExchange * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
// click on max button
maxTagButton.clicked()
waitForRendering(payPanel)
mouseClick(maxTagButton)
waitForItemPolished(payPanel)
compare(formValuesChanged.count, 6)
// FIXME flaky; value is 2 in isolation, 3 in TestCase run
//tryCompare(formValuesChanged, "count", 3)
verify(amountToSendInput.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, maxPossibleValue.toLocaleString(Qt.locale(), 'f', -128))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
tryCompare(amountToSendInput.input, "text", maxPossibleValue === 0 ? "" : maxPossibleValue.toLocaleString(Qt.locale(), 'f', -128))
tryCompare(bottomItemText, "text", root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
closeAndVerfyModal()
}
@ -1055,13 +1068,12 @@ Item {
waitForRendering(payPanel)
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
let expectedToken = SQUtils.ModelUtils.getByKey(root.tokenSelectorAdaptor.outputAssetsModel, "tokensKey", "ETH")
// check states for the pay input selector
verify(maxTagButton.visible)
let maxPossibleValue = WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)
let truncmaxPossibleValue = Math.trunc(maxPossibleValue*100)/100
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(truncmaxPossibleValue, expectedToken.symbol, {noSymbol: true})))
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue, expectedToken.symbol, {noSymbol: true})))
verify(amountToSendInput.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, "")
@ -1070,14 +1082,14 @@ Item {
// click on max button
maxTagButton.clicked()
waitForRendering(payPanel)
waitForItemPolished(payPanel)
compare(formValuesChanged.count, 5)
tryCompare(formValuesChanged, "count", 5)
verify(amountToSendInput.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, maxPossibleValue.toLocaleString(Qt.locale(), 'f', -128))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
tryCompare(bottomItemText, "text", root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
closeAndVerfyModal()
}
@ -1086,7 +1098,7 @@ Item {
// test with pay value being set and not set
let payValuesToTestWith = ["", "0.2"]
for (let index = 0; index < payValuesToTestWith.length; index ++) {
for (let index = 0; index < payValuesToTestWith.length; index++) {
let valueToExchangeString = payValuesToTestWith[index]
let valueToExchange = Number(valueToExchangeString)
@ -1112,18 +1124,18 @@ Item {
for (let i=0; i< root.swapAdaptor.nonWatchAccounts.count; i++) {
root.swapFormData.selectedAccountAddress = root.swapAdaptor.nonWatchAccounts.get(i).address
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
let expectedToken = SQUtils.ModelUtils.getByKey(root.tokenSelectorAdaptor.outputAssetsModel, "tokensKey", "ETH")
waitForRendering(payPanel)
waitForItemPolished(controlUnderTest.contentItem)
// check states for the pay input selector
verify(maxTagButton.visible)
let maxPossibleValue = Math.trunc(WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)*100)/100
let maxPossibleValue = WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)
compare(maxTagButton.text, qsTr("Max. %1").arg(maxPossibleValue === 0 ? Qt.locale().zeroDigit : root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue, expectedToken.symbol, {noSymbol: true, minDecimals: 0})))
compare(payPanel.selectedHoldingId, expectedToken.symbol)
compare(payPanel.valueValid, !!root.swapFormData.fromTokenAmount && valueToExchange <= maxPossibleValue)
tryCompare(payPanel, "valueValid", !!valueToExchangeString && valueToExchange <= maxPossibleValue)
compare(payPanel.value, valueToExchange)
tryCompare(payPanel, "value", valueToExchange)
compare(payPanel.rawValue, !!valueToExchangeString ? SQUtils.AmountsArithmetic.fromNumber(valueToExchangeString, expectedToken.decimals).toString(): "0")
// check if tag is visible in case amount entered to exchange is greater than max balance to send

View File

@ -0,0 +1,221 @@
import QtQuick 2.15
import QtTest 1.15
import QtQml 2.15
import Models 1.0
import AppLayouts.Wallet.controls 1.0
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.adaptors 1.0
Item {
id: root
width: 600
height: 400
QtObject {
id: d
readonly property var flatNetworks: NetworksModel.flatNetworks
readonly property var assetsStore: WalletAssetsStore {
id: thisWalletAssetStore
walletTokensStore: TokensStore {
plainTokensBySymbolModel: TokensBySymbolModel {}
}
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
readonly property var adaptor: TokenSelectorViewAdaptor {
assetsModel: d.assetsStore.groupedAccountAssetsModel
flatNetworksModel: d.flatNetworks
currentCurrency: "USD"
Binding on searchString {
value: controlUnderTest ? controlUnderTest.searchString : ""
restoreMode: Binding.RestoreNone
}
}
}
Component {
id: componentUnderTest
TokenSelector {
anchors.centerIn: parent
model: d.adaptor.outputAssetsModel
}
}
SignalSpy {
id: signalSpy
target: controlUnderTest
signalName: "tokenSelected"
}
property TokenSelector controlUnderTest: null
TestCase {
name: "TokenSelector"
when: windowShown
function init() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
signalSpy.clear()
}
function test_basicGeometry() {
verify(!!controlUnderTest)
verify(controlUnderTest.width > 0)
verify(controlUnderTest.height > 0)
}
function test_clickEthToken() {
verify(!!controlUnderTest)
mouseClick(controlUnderTest)
waitForItemPolished(controlUnderTest)
const listview = findChild(controlUnderTest.popup.contentItem, "tokenSelectorListview")
verify(!!listview)
waitForItemPolished(listview)
const tokensKey = "ETH"
const delegate = findChild(listview, "tokenSelectorAssetDelegate_%1".arg(tokensKey))
verify(!!delegate)
tryCompare(delegate, "tokensKey", tokensKey)
// click the delegate, verify the signal has been fired and has the correct "tokensKey" as argument
mouseClick(delegate)
tryCompare(signalSpy, "count", 1)
compare(signalSpy.signalArguments[0][0], tokensKey)
compare(controlUnderTest.currentTokensKey, tokensKey)
// close the popup, reopen and verify our token is highlighted
controlUnderTest.popup.close()
mouseClick(controlUnderTest)
tryCompare(controlUnderTest.popup, "opened", true)
tryCompare(delegate, "highlighted", true)
}
function test_clickNonInteractiveToken() {
verify(!!controlUnderTest)
const tokensKey = "STT"
controlUnderTest.nonInteractiveDelegateKey = tokensKey
mouseClick(controlUnderTest)
waitForItemPolished(controlUnderTest)
const listview = findChild(controlUnderTest.popup.contentItem, "tokenSelectorListview")
verify(!!listview)
waitForItemPolished(listview)
const delegate = findChild(listview, "tokenSelectorAssetDelegate_%1".arg(tokensKey))
verify(!!delegate)
tryCompare(delegate, "tokensKey", tokensKey)
tryCompare(delegate, "interactive", false)
mouseClick(delegate)
tryCompare(signalSpy, "count", 0)
tryCompare(controlUnderTest, "currentTokensKey", "")
}
function test_selectToken() {
verify(!!controlUnderTest)
const tokensKey = "STT"
controlUnderTest.selectToken(tokensKey)
tryCompare(signalSpy, "count", 1)
compare(signalSpy.signalArguments[0][0], tokensKey)
tryCompare(controlUnderTest, "currentTokensKey", tokensKey)
const listview = findChild(controlUnderTest.popup.contentItem, "tokenSelectorListview")
verify(!!listview)
mouseClick(controlUnderTest)
const delegate = findChild(listview, "tokenSelectorAssetDelegate_%1".arg(tokensKey))
verify(!!delegate)
tryCompare(delegate, "tokensKey", tokensKey)
tryCompare(delegate, "highlighted", true)
}
function test_selectNonexistingToken() {
verify(!!controlUnderTest)
const tokensKey = "0x6b175474e89094c44da98b954eedeac495271d0f" // MET
// not available by default
controlUnderTest.selectToken(tokensKey)
tryCompare(signalSpy, "count", 1)
compare(signalSpy.signalArguments[0][0], "")
tryCompare(controlUnderTest, "currentTokensKey", "")
// enable community assets, now should be available, try to select it
d.adaptor.showCommunityAssets = true
controlUnderTest.selectToken(tokensKey)
tryCompare(signalSpy, "count", 2)
compare(signalSpy.signalArguments[1][0], tokensKey)
tryCompare(controlUnderTest, "currentTokensKey", tokensKey)
// disable community assets to simulate token gone
d.adaptor.showCommunityAssets = false
// control should reset itself back
tryCompare(signalSpy, "count", 3)
compare(signalSpy.signalArguments[2][0], "")
tryCompare(controlUnderTest, "currentTokensKey", "")
}
function test_search() {
verify(!!controlUnderTest)
mouseClick(controlUnderTest)
waitForItemPolished(controlUnderTest)
const originalCount = controlUnderTest.count
verify(originalCount > 0)
// verify the search box has focus
const searchBox = findChild(controlUnderTest.popup.contentItem, "searchBox")
verify(!!searchBox)
tryCompare(searchBox.input.edit, "focus", true)
// type "dAi"
keyClick(Qt.Key_D)
keyClick(Qt.Key_A, Qt.ShiftModifier)
keyClick(Qt.Key_I)
// search yields 1 result
waitForItemPolished(controlUnderTest)
tryCompare(controlUnderTest, "count", 1)
// closing the popup should clear the search and put the view back to original count
controlUnderTest.popup.close()
mouseClick(controlUnderTest)
tryCompare(searchBox.input.edit, "text", "")
tryCompare(controlUnderTest, "count", originalCount)
}
function test_sections() {
verify(!!controlUnderTest)
d.adaptor.enabledChainIds = [10] // filter Optimism chain only
mouseClick(controlUnderTest)
waitForItemPolished(controlUnderTest)
const listview = findChild(controlUnderTest.popup.contentItem, "tokenSelectorListview")
verify(!!listview)
waitForItemPolished(listview)
const sttDelegate = findChild(listview, "tokenSelectorAssetDelegate_STT")
verify(!!sttDelegate)
tryCompare(sttDelegate, "tokensKey", "STT")
compare(sttDelegate.ListView.section, "Your assets on Optimism")
const ethDelegate = findChild(listview, "tokenSelectorAssetDelegate_ETH")
verify(!!ethDelegate)
tryCompare(ethDelegate, "tokensKey", "ETH")
compare(ethDelegate.ListView.section, "Popular assets")
}
}
}

View File

@ -19,11 +19,9 @@ ListModel {
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 1, balance: "122082928968121891" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "1013151281976507736" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 421613, balance: "473057568699284613" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "307400931315122839" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 11155111, balance: "307400931315122839" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "122082928968121891" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 421613, balance: "0" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "559133758939097000" }
]
},
{

View File

@ -20,8 +20,8 @@ Item {
property alias popup: comboBox.popup
property alias currentIndex: comboBox.currentIndex
property alias currentValue: comboBox.currentValue
property alias currentText: comboBox.currentText
readonly property alias currentValue: comboBox.currentValue
readonly property alias currentText: comboBox.currentText
property alias label: labelItem.text
property alias validationError: validationErrorItem.text

View File

@ -23,6 +23,7 @@ QObject {
- balances: submodel -> [ chainId:int, account:string, balance:BigIntString, iconUrl:string ]
Computed values:
- currentBalance: double (amount of tokens)
- currencyBalance: double (e.g. `1000.42` in user's fiat currency)
- currencyBalanceAsString: string (e.g. "1 000,42 CZK" formatted as a string according to the user's locale)
- balanceAsString: string (`1.42` formatted as e.g. "1,42" in user's locale)
@ -63,11 +64,16 @@ QObject {
}
]
// FIXME optionally sort/filter by wallet controller as well
sorters: [
RoleSorter {
roleName: "sectionId"
},
RoleSorter {
roleName: "currencyBalance"
sortOrder: Qt.DescendingOrder
},
RoleSorter {
roleName: "name"
}
]
}
@ -94,6 +100,20 @@ QObject {
currencyBalance ? LocaleUtils.currencyAmountToLocaleString({amount: currencyBalance, symbol: root.currentCurrency, displayDecimals})
: ""
readonly property string sectionId: {
if (root.enabledChainIds.length === 1) {
return currentBalance ? "section_%1".arg(root.enabledChainIds[0]) : "section_zzz"
}
return ""
}
readonly property string sectionName: {
if (root.enabledChainIds.length === 1) {
return currentBalance ? qsTr("Your assets on %1").arg(ModelUtils.getByKey(root.flatNetworksModel, "chainId", root.enabledChainIds[0], "chainName"))
: qsTr("Popular assets")
}
return ""
}
readonly property var balances: this
sourceModel: joinModel
@ -160,7 +180,7 @@ QObject {
}
}
exposedRoles: ["balances", "currencyBalance", "currencyBalanceAsString", "balanceAsString"]
exposedRoles: ["balances", "currentBalance", "currencyBalance", "currencyBalanceAsString", "balanceAsString", "sectionId", "sectionName"]
expectedRoles: ["communityId", "balances", "decimals", "marketDetails"]
}
}

View File

@ -61,7 +61,7 @@ Control {
StatusBaseText {
Layout.fillWidth: true
Layout.topMargin: -12
text: qsTr("Maximum deviation in price due to market volatility and liquidity allowed before the swap is cancelled. (0.5% default).")
text: qsTr("Maximum deviation in price due to market volatility and liquidity allowed before the swap is cancelled. (%L1% default).").arg(slippageSelector.defaultValue)
wrapMode: Text.Wrap
color: Theme.palette.directColor5
}

View File

@ -21,17 +21,10 @@ StatusButton {
locale: LocaleUtils.userInputLocale
QtObject {
id: d
readonly property string maxInputBalanceFormatted:
root.formatCurrencyAmount(Math.trunc(root.maxSafeValue*100)/100, root.symbol)
}
implicitHeight: 22
type: valid ? StatusBaseButton.Type.Normal : StatusBaseButton.Type.Danger
text: qsTr("Max. %1").arg(value === 0 ? locale.zeroDigit : d.maxInputBalanceFormatted)
text: qsTr("Max. %1").arg(value === 0 ? locale.zeroDigit : root.formatCurrencyAmount(maxSafeValue, root.symbol))
horizontalPadding: 8
verticalPadding: 3

View File

@ -183,7 +183,9 @@ StatusComboBox {
value: root.selection[0] ?? -1
}
readonly property string singleSelectionIconUrl: singleSelectionItem.item.iconUrl ?? ""
readonly property string singleSelectionIconUrl: singleSelectionItem.item.iconUrl ? (singleSelectionItem.item.isTest ? singleSelectionItem.item.iconUrl + "-test"
: singleSelectionItem.item.iconUrl)
: ""
readonly property string singleCelectionChainName: singleSelectionItem.item.chainName ?? ""
readonly property string titleText: {

View File

@ -0,0 +1,212 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.0
import StatusQ 0.1
import StatusQ.Components 0.1
import StatusQ.Components.private 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups.Dialog 0.1
import AppLayouts.Wallet.views 1.0
import utils 1.0
import shared.controls 1.0
ComboBox {
id: root
// expected model structure:
// tokensKey, name, symbol, decimals, currencyBalanceAsString (computed), marketDetails, balances -> [ chainId, address, balance, iconUrl ]
// input API
property string nonInteractiveDelegateKey
// output API
readonly property string currentTokensKey: d.currentTokensKey
readonly property alias searchString: searchBox.text
/**
Emitted when a token gets selected
*/
signal tokenSelected(string tokensKey)
// manipulation
function selectToken(tokensKey) {
const idx = ModelUtils.indexOf(model, "tokensKey", tokensKey)
if (idx === -1) {
console.warn("TokenSelector::selectToken: unknown tokensKey:", tokensKey)
tokensKey = ""
}
currentIndex = idx
d.currentTokensKey = tokensKey
root.tokenSelected(tokensKey)
}
function reset() {
selectToken("")
}
QtObject {
id: d
// NB: internal tracking; the ComboBox currentValue is not persistent,
// i.e. relying on currentValue is not safe
property string currentTokensKey
readonly property bool isTokenSelected: !!currentTokensKey
// NB: handle cases when our currently selected token disappears from the model -> reset
readonly property Connections _conn: Connections {
target: model ?? null
function onModelReset() {
if (d.isTokenSelected && !root.popup.opened)
root.selectToken(d.currentTokensKey)
}
function onRowsRemoved() {
if (d.isTokenSelected && !root.popup.opened)
root.selectToken(d.currentTokensKey)
}
}
}
font.family: Theme.palette.baseFont.name
font.pixelSize: Style.current.additionalTextSize
spacing: Style.current.halfPadding
verticalPadding: 10
leftPadding: 12
rightPadding: leftPadding + indicator.width + spacing
opacity: enabled ? 1 : 0.3
popup.width: 380
popup.x: root.width - popup.width
popup.y: root.height
popup.margins: Style.current.halfPadding
popup.background: Rectangle {
color: Theme.palette.statusSelect.menuItemBackgroundColor
radius: Style.current.radius
layer.enabled: true
layer.effect: DropShadow {
horizontalOffset: 0
verticalOffset: 4
radius: 12
samples: 25
spread: 0.2
color: Theme.palette.dropShadow
}
}
popup.contentItem: ColumnLayout {
spacing: 0
SearchBox {
Layout.fillWidth: true
id: searchBox
objectName: "searchBox"
input.leftPadding: root.leftPadding
input.rightPadding: root.leftPadding
minimumHeight: 56
maximumHeight: 56
placeholderText: qsTr("Search asset name or symbol")
input.showBackground: false
focus: visible
onVisibleChanged: if (!visible) input.edit.clear()
}
StatusDialogDivider {
Layout.fillWidth: true
visible: listview.count
}
StatusListView {
id: listview
objectName: "tokenSelectorListview"
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
Layout.fillHeight: true
model: root.popup.visible ? root.delegateModel : null
currentIndex: root.highlightedIndex
section.property: "sectionName"
section.delegate: StatusBaseText {
required property string section
width: parent.width
elide: Text.ElideRight
text: section
color: Theme.palette.baseColor1
padding: Style.current.padding
}
}
}
background: StatusComboboxBackground {
border.width: 0
color: {
if (d.isTokenSelected)
return "transparent"
return root.hovered ? Theme.palette.primaryColor2 : Theme.palette.primaryColor3
}
}
contentItem: Loader {
height: 40 // by design
sourceComponent: d.isTokenSelected ? iconTextContentItem : textContentItem
}
indicator: StatusComboboxIndicator {
anchors.right: parent.right
anchors.rightMargin: root.leftPadding
anchors.verticalCenter: parent.verticalCenter
color: Theme.palette.primaryColor1
}
delegate: TokenSelectorAssetDelegate {
required property var model
required property int index
highlighted: tokensKey === d.currentTokensKey
interactive: tokensKey !== root.nonInteractiveDelegateKey
tokensKey: model.tokensKey
name: model.name
symbol: model.symbol
currencyBalanceAsString: model.currencyBalanceAsString
balancesModel: model.balances
onAssetSelected: (tokensKey) => root.selectToken(tokensKey)
}
Component {
id: textContentItem
StatusBaseText {
objectName: "holdingSelectorsContentItemText"
font.pixelSize: root.font.pixelSize
font.weight: Font.Medium
color: Theme.palette.primaryColor1
text: qsTr("Select asset")
}
}
Component {
id: iconTextContentItem
RowLayout {
readonly property string currentSymbol: d.isTokenSelected ? ModelUtils.getByKey(model, "tokensKey", d.currentTokensKey, "symbol")
: ""
spacing: root.spacing
StatusRoundedImage {
objectName: "holdingSelectorsTokenIcon"
Layout.preferredWidth: 20
Layout.preferredHeight: 20
image.source: Constants.tokenIcon(parent.currentSymbol)
}
StatusBaseText {
objectName: "holdingSelectorsContentItemText"
font.pixelSize: 28
color: root.hovered ? Theme.palette.blue : Theme.palette.darkBlue
text: parent.currentSymbol
}
}
}
}

View File

@ -18,3 +18,4 @@ ConnectedDappsButton 1.0 ConnectedDappsButton.qml
CollectibleLinksTags 1.0 CollectibleLinksTags.qml
SwapExchangeButton 1.0 SwapExchangeButton.qml
EditSlippagePanel 1.0 EditSlippagePanel.qml
TokenSelector 1.0 TokenSelector.qml

View File

@ -11,9 +11,10 @@ import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Core.Theme 0.1
import AppLayouts.Wallet.controls 1.0
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.adaptors 1.0
import shared.popups.send.views 1.0
import shared.popups.send.panels 1.0
import utils 1.0
import shared.stores 1.0
@ -28,11 +29,21 @@ Control {
required property var flatNetworksModel
required property var processedAssetsModel
property int selectedNetworkChainId: -1
property string selectedAccountAddress
property string nonInteractiveTokensKey
property string tokenKey
onTokenKeyChanged: reevaluateSelectedId()
onTokenKeyChanged: Qt.callLater(reevaluateSelectedId)
property string tokenAmount
onTokenAmountChanged: {
Qt.callLater(() => amountToSendInput.input.text = !!tokenAmount ? SQUtils.AmountsArithmetic.fromString(tokenAmount).toLocaleString(locale, 'f', -128): "")
if (tokenAmount === "") {
amountToSendInput.input.input.edit.clear()
return
}
Qt.callLater(() => amountToSendInput.input.text =
SQUtils.AmountsArithmetic.fromString(tokenAmount).toFixed().replace('.', LocaleUtils.userInputLocale.decimalPoint))
}
property int swapSide: SwapInputPanel.SwapSide.Pay
@ -41,17 +52,19 @@ Control {
property bool bottomTextLoading
property bool interactive: true
function reevaluateSelectedId() {
if (!!tokenKey) {
holdingSelector.selectToken(tokenKey)
d.selectedHolding = SQUtils.ModelUtils.getByKey(holdingSelector.model, "tokensKey", holdingSelector.currentTokensKey)
}
}
// output API
readonly property string selectedHoldingId: d.selectedHoldingId
readonly property string selectedHoldingId: holdingSelector.currentTokensKey
readonly property double value: amountToSendInput.cryptoValueToSendFloat
readonly property string rawValue: amountToSendInput.cryptoValueToSend
readonly property bool valueValid: amountToSendInput.inputNumberValid
readonly property bool amountEnteredGreaterThanBalance: value > maxSendButton.maxSafeValue
function reevaluateSelectedId() {
if (!!tokenKey) {
Qt.callLater(d.setSelectedHoldingId, tokenKey, Constants.TokenType.ERC20)
}
}
// visual properties
property int swapExchangeButtonWidth: 44
@ -76,35 +89,30 @@ Control {
QtObject {
id: d
function setSelectedHoldingId(holdingId, holdingType) {
let holding = SQUtils.ModelUtils.getByKey(root.processedAssetsModel, "symbol", holdingId)
d.selectedHoldingId = holdingId
d.setSelectedHolding(holding, holdingType)
}
property var selectedHolding: SQUtils.ModelUtils.getByKey(holdingSelector.model, "tokensKey", holdingSelector.currentTokensKey)
function setSelectedHolding(holding, holdingType) {
d.selectedHoldingType = holdingType
d.selectedHolding = holding
holdingSelector.setSelectedItem(holding, holdingType)
}
property var selectedHolding: null
property var selectedHoldingType: Constants.TokenType.Unknown
property string selectedHoldingId
readonly property bool isSelectedHoldingValidAsset: !!selectedHolding && selectedHoldingType === Constants.TokenType.ERC20
readonly property double maxFiatBalance: isSelectedHoldingValidAsset ? selectedHolding.currentCurrencyBalance : 0
readonly property bool isSelectedHoldingValidAsset: !!selectedHolding
readonly property double maxFiatBalance: isSelectedHoldingValidAsset ? selectedHolding.currencyBalance : 0
readonly property double maxCryptoBalance: isSelectedHoldingValidAsset ? selectedHolding.currentBalance : 0
readonly property double maxInputBalance: amountToSendInput.inputIsFiat ? maxFiatBalance : maxCryptoBalance
readonly property string inputSymbol: amountToSendInput.inputIsFiat ? root.currencyStore.currentCurrency :
!!d.selectedHolding && !!d.selectedHolding.symbol ? d.selectedHolding.symbol: ""
property string searchText
readonly property string inputSymbol: amountToSendInput.inputIsFiat ? root.currencyStore.currentCurrency
: (!!selectedHolding ? selectedHolding.symbol : "")
readonly property var adaptor: TokenSelectorViewAdaptor {
assetsModel: root.processedAssetsModel
flatNetworksModel: root.flatNetworksModel
currentCurrency: root.currencyStore.currentCurrency
enabledChainIds: root.selectedNetworkChainId !== -1 ? [root.selectedNetworkChainId] : []
accountAddress: root.selectedAccountAddress || ""
searchString: holdingSelector.searchString
}
}
background: Shape {
id: shape
property int radius: 16
property int radius: Style.current.radius
property int leftTopRadius: radius
property int rightTopRadius: radius
property int leftBottomRadius: radius
@ -190,14 +198,13 @@ Control {
objectName: "amountToSendInput"
caption: root.caption
interactive: root.interactive
selectedHolding: d.selectedHolding
selectedHolding: d.selectedHolding // FIXME shouldn't be necesary to pass the whole object
fiatInputInteractive: root.fiatInputInteractive
input.input.edit.color: !input.valid ? Theme.palette.dangerColor1 : maxSendButton.hovered ? Theme.palette.baseColor1
: Theme.palette.directColor1
multiplierIndex: d.isSelectedHoldingValidAsset && !!holdingSelector.selectedItem && !!holdingSelector.selectedItem.decimals
? holdingSelector.selectedItem.decimals
: 0
multiplierIndex: !!d.selectedHolding ? d.selectedHolding.decimals : 0
maxInputBalance: (root.swapSide === SwapInputPanel.SwapSide.Receive || !d.isSelectedHoldingValidAsset) ? Number.POSITIVE_INFINITY
: maxSendButton.maxSafeValue
@ -212,37 +219,14 @@ Control {
Item { Layout.fillHeight: true }
HoldingSelector {
TokenSelector {
id: holdingSelector
objectName: "holdingSelector"
Layout.rightMargin: d.isSelectedHoldingValidAsset ? -root.padding : 0
Layout.alignment: Qt.AlignRight
Layout.preferredHeight: 38
searchPlaceholderText: qsTr("Search asset name or symbol")
assetsModel: SortFilterProxyModel {
sourceModel: root.processedAssetsModel
filters: FastExpressionFilter {
function search(symbol, name, searchString) {
return (symbol.toUpperCase().includes(searchString.toUpperCase())
|| name.toUpperCase().includes(searchString.toUpperCase()))
}
expression: search(model.symbol, model.name, d.searchText)
expectedRoles: ["symbol", "name"]
}
}
networksModel: root.flatNetworksModel
formatCurrentCurrencyAmount: function(balance) {
return root.currencyStore.formatCurrencyAmount(balance, root.currencyStore.currentCurrency)
}
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals) {
return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals, {noSymbol: true})
}
onItemSelected: {
d.setSelectedHoldingId(holdingId, holdingType)
amountToSendInput.input.forceActiveFocus()
}
onSearchTextChanged: d.searchText = searchText
model: d.adaptor.outputAssetsModel
nonInteractiveDelegateKey: root.nonInteractiveTokensKey
onActivated: amountToSendInput.input.forceActiveFocus()
}
Item { Layout.fillHeight: !maxSendButton.visible }

View File

@ -9,7 +9,6 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Popups.Dialog 0.1
import StatusQ.Controls 0.1
import shared.popups.send.controls 1.0
import shared.controls 1.0
@ -37,11 +36,11 @@ StatusDialog {
QtObject {
id: d
property var debounceFetchSuggestedRoutes: Backpressure.debounce(root, 1000, function() {
root.swapAdaptor.fetchSuggestedRoutes(payPanel.rawValue)
root.swapAdaptor.fetchSuggestedRoutes(payPanel.rawValue)
})
function fetchSuggestedRoutes() {
if (payPanel.valueValid) {
if (payPanel.valueValid && !!payPanel.selectedHoldingId) {
root.swapAdaptor.newFetchReset()
root.swapAdaptor.swapProposalLoading = true
debounceFetchSuggestedRoutes()
@ -59,6 +58,7 @@ StatusDialog {
payPanel.reevaluateSelectedId()
}
function onSelectedNetworkChainIdChanged() {
networkFilter.selection = [root.swapInputParamsForm.selectedNetworkChainId]
payPanel.reevaluateSelectedId()
}
}
@ -66,7 +66,7 @@ StatusDialog {
Behavior on implicitHeight {
NumberAnimation { duration: 1000; easing.type: Easing.OutExpo; alwaysRunToEnd: true}
}
onClosed: root.swapAdaptor.reset()
header: Item {
@ -101,7 +101,6 @@ StatusDialog {
HeaderTitleText {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
id: modalHeader
text: qsTr("Swap")
}
StatusBaseText {
@ -113,7 +112,6 @@ StatusDialog {
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"
@ -129,20 +127,6 @@ StatusDialog {
}
}
}
Connections {
target: root.swapInputParamsForm
function onSelectedNetworkChainIdChanged() {
networkFilter.setChain(root.swapInputParamsForm.selectedNetworkChainId)
}
}
Connections {
target: root.swapInputParamsForm
function onSelectedNetworkChainIdChanged() {
networkFilter.selection = [root.swapInputParamsForm.selectedNetworkChainId]
}
}
}
}
@ -162,18 +146,15 @@ StatusDialog {
}
currencyStore: root.swapAdaptor.currencyStore
flatNetworksModel: root.swapAdaptor.filteredFlatNetworksModel
processedAssetsModel: root.swapAdaptor.processedAssetsModel
flatNetworksModel: root.swapAdaptor.swapStore.flatNetworks
processedAssetsModel: root.swapAdaptor.walletAssetsStore.groupedAccountAssetsModel
tokenKey: root.swapInputParamsForm.fromTokensKey
tokenAmount: {
// Only update if there is different in amount displayed
if (root.swapInputParamsForm.fromTokenAmount !==
SQUtils.AmountsArithmetic.fromString(value).toLocaleString(locale, 'f', -128)){
return root.swapInputParamsForm.fromTokenAmount
}
return payPanel.tokenAmount
}
tokenAmount: root.swapInputParamsForm.fromTokenAmount
selectedNetworkChainId: root.swapInputParamsForm.selectedNetworkChainId
selectedAccountAddress: root.swapInputParamsForm.selectedAccountAddress
nonInteractiveTokensKey: receivePanel.selectedHoldingId
swapSide: SwapInputPanel.SwapSide.Pay
swapExchangeButtonWidth: swapButton.width
@ -194,12 +175,16 @@ StatusDialog {
}
currencyStore: root.swapAdaptor.currencyStore
flatNetworksModel: root.swapAdaptorfilteredFlatNetworksModel
processedAssetsModel: root.swapAdaptor.processedAssetsModel
flatNetworksModel: root.swapAdaptor.swapStore.flatNetworks
processedAssetsModel: root.swapAdaptor.walletAssetsStore.groupedAccountAssetsModel
tokenKey: root.swapInputParamsForm.toTokenKey
tokenAmount: root.swapAdaptor.validSwapProposalReceived && root.swapAdaptor.toToken ? root.swapAdaptor.swapOutputData.toTokenAmount: root.swapInputParamsForm.toTokenAmount
selectedNetworkChainId: root.swapInputParamsForm.selectedNetworkChainId
selectedAccountAddress: root.swapInputParamsForm.selectedAccountAddress
nonInteractiveTokensKey: payPanel.selectedHoldingId
swapSide: SwapInputPanel.SwapSide.Receive
swapExchangeButtonWidth: swapButton.width
@ -307,7 +292,6 @@ StatusDialog {
objectName: "maxFeesText"
text: qsTr("Max fees:")
color: Theme.palette.directColor5
font.pixelSize: 15
font.weight: Font.Medium
}
StatusTextWithLoadingState {
@ -319,7 +303,6 @@ StatusDialog {
root.swapAdaptor.currencyStore.currentCurrency) :
"--"
customColor: Theme.palette.directColor4
font.pixelSize: 15
font.weight: Font.Medium
loading: root.swapAdaptor.swapProposalLoading
}
@ -353,4 +336,3 @@ StatusDialog {
}
}
}

View File

@ -23,8 +23,6 @@ QObject {
property bool validSwapProposalReceived: false
property bool swapProposalLoading: false
property bool showCommunityTokens
// To expose the selected from and to Token from the SwapModal
readonly property var fromToken: fromTokenEntry.item
readonly property var toToken: toTokenEntry.item
@ -64,50 +62,6 @@ QObject {
filters: ValueFilter { roleName: "isTest"; value: root.swapStore.areTestNetworksEnabled }
}
// Model prepared to provide filtered and sorted assets as per the advanced Settings in token management
readonly property var processedAssetsModel: SortFilterProxyModel {
property real displayAssetsBelowBalanceThresholdAmount: root.walletAssetsStore.walletTokensStore.getDisplayAssetsBelowBalanceThresholdDisplayAmount()
sourceModel: d.assetsWithFilteredBalances
proxyRoles: [
FastExpressionRole {
name: "currentBalance"
expression: {
// FIXME recalc when selectedNetworkChainId changes
root.swapFormData.selectedNetworkChainId
return d.getTotalBalance(model.balances, model.decimals)
}
expectedRoles: ["balances", "decimals"]
},
FastExpressionRole {
name: "currentCurrencyBalance"
expression: {
if (!!model.marketDetails) {
return model.currentBalance * model.marketDetails.currencyPrice.amount
}
return 0
}
expectedRoles: ["marketDetails", "currentBalance"]
}
]
filters: [
FastExpressionFilter {
expression: {
root.walletAssetsStore.assetsController.revision
if (!root.walletAssetsStore.assetsController.filterAcceptsSymbol(model.symbol)) // explicitely hidden
return false
if (!!model.communityId)
return root.showCommunityTokens
if (root.walletAssetsStore.walletTokensStore.displayAssetsBelowBalance)
return model.currentCurrencyBalance > processedAssetsModel.displayAssetsBelowBalanceThresholdAmount
return true
}
expectedRoles: ["symbol", "communityId", "currentCurrencyBalance"]
}
]
// FIXME sort by assetsController instead, to have the sorting/order as in the main wallet view
}
ModelEntry {
id: fromTokenEntry
sourceModel: root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel
@ -134,27 +88,6 @@ QObject {
property string uuid
// Internal model filtering balances by the account selected in the AccountsModalHeader
readonly property SubmodelProxyModel assetsWithFilteredBalances: SubmodelProxyModel {
sourceModel: root.walletAssetsStore.groupedAccountAssetsModel
submodelRoleName: "balances"
delegateModel: SortFilterProxyModel {
sourceModel: submodel
filters: [
ValueFilter {
roleName: "chainId"
value: root.swapFormData.selectedNetworkChainId
enabled: root.swapFormData.selectedNetworkChainId !== -1
},
ValueFilter {
roleName: "account"
value: root.swapFormData.selectedAccountAddress
}
]
}
}
readonly property SubmodelProxyModel filteredBalancesModel: SubmodelProxyModel {
sourceModel: root.walletAssetsStore.baseGroupedAccountAssetModel
submodelRoleName: "balances"
@ -200,17 +133,6 @@ QObject {
formattedBalance: root.formatCurrencyAmount(.0 , root.fromToken.symbol)
}
}
/* Internal function to calculate total balance */
function getTotalBalance(balances, decimals, chainIds = [root.swapFormData.selectedNetworkChainId]) {
let totalBalance = 0
for(let i=0; i<balances.count; i++) {
let balancePerAddressPerChain = ModelUtils.get(balances, i)
if (chainIds.includes(-1) || chainIds.includes(balancePerAddressPerChain.chainId))
totalBalance += AmountsArithmetic.toNumber(balancePerAddressPerChain.balance, decimals)
}
return totalBalance
}
}
Connections {
@ -224,10 +146,11 @@ QObject {
if(txRoutes.suggestedRoutes.count === 1) {
root.validSwapProposalReceived = true
root.swapOutputData.bestRoutes = txRoutes.suggestedRoutes
root.swapOutputData.toTokenAmount = root.swapStore.getWei2Eth(txRoutes.amountToReceive, root.toToken.decimals).toString()
root.swapOutputData.toTokenAmount = AmountsArithmetic.div(AmountsArithmetic.fromString(txRoutes.amountToReceive), AmountsArithmetic.fromNumber(1, root.toToken.decimals)).toString()
let gasTimeEstimate = txRoutes.gasTimeEstimate
let totalTokenFeesInFiat = 0
if (!!root.fromToken && !!root.fromToken .marketDetails && !!root.fromToken.marketDetails.currencyPrice)
if (!!root.fromToken && !!root.fromToken.marketDetails && !!root.fromToken.marketDetails.currencyPrice)
totalTokenFeesInFiat = gasTimeEstimate.totalTokenFees * root.fromToken.marketDetails.currencyPrice.amount
root.swapOutputData.totalFees = root.currencyStore.getFiatValue(gasTimeEstimate.totalFeesInEth, Constants.ethToken) + totalTokenFeesInFiat
root.swapOutputData.approvalNeeded = ModelUtils.get(root.swapOutputData.bestRoutes, 0, "route").approvalRequired

View File

@ -8,6 +8,7 @@ Control {
id: root
property double value: d.defaultValue
readonly property double defaultValue: d.defaultValue
readonly property bool valid: customInput.activeFocus && customInput.valid
|| buttons.value !== null
readonly property bool isEdited: root.value !== d.defaultValue