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 id: d
readonly property SwapInputParamsForm swapInputParamsForm: SwapInputParamsForm { readonly property SwapInputParamsForm swapInputParamsForm: SwapInputParamsForm {
selectedNetworkChainId: ctrlSelectedNetworkChainId.currentValue selectedAccountAddress: ctrlAccount.currentValue ?? ""
selectedNetworkChainId: ctrlSelectedNetworkChainId.currentValue ?? -1
fromTokensKey: ctrlFromTokensKey.text fromTokensKey: ctrlFromTokensKey.text
fromTokenAmount: ctrlFromTokenAmount.text fromTokenAmount: ctrlFromTokenAmount.text
toTokenKey: ctrlToTokenKey.text toTokenKey: ctrlToTokenKey.text
toTokenAmount: ctrlToTokenAmount.text toTokenAmount: ctrlToTokenAmount.text
selectedAccountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"
} }
readonly property SwapModalAdaptor adaptor: SwapModalAdaptor { readonly property SwapModalAdaptor adaptor: SwapModalAdaptor {
@ -78,7 +78,11 @@ SplitView {
currencyStore: d.adaptor.currencyStore currencyStore: d.adaptor.currencyStore
flatNetworksModel: d.adaptor.swapStore.flatNetworks 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 tokenKey: d.swapInputParamsForm.fromTokensKey
tokenAmount: d.swapInputParamsForm.fromTokenAmount tokenAmount: d.swapInputParamsForm.fromTokenAmount
@ -99,8 +103,12 @@ SplitView {
} }
currencyStore: d.adaptor.currencyStore currencyStore: d.adaptor.currencyStore
flatNetworksModel: d.adaptor.filteredFlatNetworksModel flatNetworksModel: d.adaptor.swapStore.flatNetworks
processedAssetsModel: d.adaptor.processedAssetsModel processedAssetsModel: d.adaptor.walletAssetsStore.groupedAccountAssetsModel
selectedNetworkChainId: d.swapInputParamsForm.selectedNetworkChainId
selectedAccountAddress: d.swapInputParamsForm.selectedAccountAddress
nonInteractiveTokensKey: payPanel.selectedHoldingId
tokenKey: d.swapInputParamsForm.toTokenKey tokenKey: d.swapInputParamsForm.toTokenKey
tokenAmount: d.swapInputParamsForm.toTokenAmount 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 { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Label { Label {

View File

@ -35,13 +35,6 @@ SplitView {
function launchPopup() { function launchPopup() {
swapModal.createObject(root) 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{} readonly property SwapTransactionRoutes dummySwapTransactionRoutes: SwapTransactionRoutes{}
} }
@ -103,19 +96,11 @@ SplitView {
closePolicy: Popup.CloseOnEscape closePolicy: Popup.CloseOnEscape
destroyOnClose: true destroyOnClose: true
swapInputParamsForm: SwapInputParamsForm { swapInputParamsForm: SwapInputParamsForm {
selectedAccountAddress: accountComboBox.currentValue ?? ""
selectedNetworkChainId: d.getNetwork()
fromTokensKey: fromTokenComboBox.currentValue
fromTokenAmount: swapInput.text
toTokenKey: toTokenComboBox.currentValue
onSelectedAccountAddressChanged: { onSelectedAccountAddressChanged: {
if (selectedAccountAddress !== accountComboBox.currentValue) if (selectedAccountAddress !== accountComboBox.currentValue)
accountComboBox.currentIndex = accountComboBox.indexOfValue(selectedAccountAddress) accountComboBox.currentIndex = accountComboBox.indexOfValue(selectedAccountAddress)
} }
Binding on selectedAccountAddress { fromTokenAmount: swapInput.text
value: accountComboBox.currentValue ?? ""
}
} }
swapAdaptor: SwapModalAdaptor { swapAdaptor: SwapModalAdaptor {
swapStore: dSwapStore swapStore: dSwapStore
@ -132,12 +117,27 @@ SplitView {
Binding { Binding {
target: swapInputParamsForm target: swapInputParamsForm
property: "fromTokensKey" property: "fromTokensKey"
value: fromTokenComboBox.currentValue value: fromTokenComboBox.currentValue ?? ""
} }
Binding { Binding {
target: swapInputParamsForm target: swapInputParamsForm
property: "toTokenKey" 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 { ComboBox {
id: networksComboBox id: networksComboBox
textRole: "chainName" textRole: "chainName"
valueRole: "chainId"
model: d.filteredNetworksModel model: d.filteredNetworksModel
currentIndex: 0 currentIndex: 0
onCountChanged: currentIndex = 0 onCountChanged: currentIndex = 0
@ -201,7 +202,7 @@ SplitView {
StatusInput { StatusInput {
id: swapInput id: swapInput
Layout.preferredWidth: 100 Layout.preferredWidth: 250
label: "Token amount to swap" label: "Token amount to swap"
text: "" 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.stores 1.0
import AppLayouts.Wallet.panels 1.0 import AppLayouts.Wallet.panels 1.0
import AppLayouts.Wallet.popups.swap 1.0 import AppLayouts.Wallet.popups.swap 1.0
import AppLayouts.Wallet.adaptors 1.0
import shared.stores 1.0 import shared.stores 1.0
import SortFilterProxyModel 0.2
import Models 1.0 import Models 1.0
import Storybook 1.0 import Storybook 1.0
@ -45,6 +44,14 @@ Item {
} }
swapOutputData: SwapOutputData {} 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 { Component {
@ -53,8 +60,8 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
currencyStore: d.adaptor.currencyStore currencyStore: d.adaptor.currencyStore
flatNetworksModel: d.adaptor.filteredFlatNetworksModel flatNetworksModel: d.adaptor.swapStore.flatNetworks
processedAssetsModel: d.adaptor.processedAssetsModel processedAssetsModel: d.adaptor.walletAssetsStore.groupedAccountAssetsModel
} }
} }
@ -114,7 +121,8 @@ Item {
{ tag: "1234567890", tokenAmount: "1234567890", valid: true }, { tag: "1234567890", tokenAmount: "1234567890", valid: true },
{ tag: "1234567890.1234567890", tokenAmount: "1234567890.1234567890", valid: true }, { tag: "1234567890.1234567890", tokenAmount: "1234567890.1234567890", valid: true },
{ tag: "abc", tokenAmount: "abc", valid: false }, { 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") const holdingSelector = findChild(controlUnderTest, "holdingSelector")
verify(!!holdingSelector) verify(!!holdingSelector)
tryCompare(holdingSelector.selectedItem, "symbol", tokenSymbol) tryCompare(holdingSelector, "currentTokensKey", tokenSymbol)
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput") const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput) verify(!!amountToSendInput)
@ -182,13 +190,13 @@ Item {
mouseClick(holdingSelector) mouseClick(holdingSelector)
waitForRendering(holdingSelector) waitForRendering(holdingSelector)
const assetSelectorList = findChild(holdingSelector, "assetSelectorList") const assetSelectorList = findChild(holdingSelector, "tokenSelectorListview")
verify(!!assetSelectorList) verify(!!assetSelectorList)
waitForRendering(assetSelectorList) waitForRendering(assetSelectorList)
const sttDelegate = findChild(assetSelectorList, "AssetSelector_ItemDelegate_STT") const sttDelegate = findChild(assetSelectorList, "tokenSelectorAssetDelegate_STT")
verify(!!sttDelegate) verify(!!sttDelegate)
mouseClick(sttDelegate, 40, 40) // center might be covered by tags mouseClick(sttDelegate)
tryCompare(controlUnderTest, "selectedHoldingId", "STT") tryCompare(controlUnderTest, "selectedHoldingId", "STT")
@ -303,9 +311,6 @@ Item {
controlUnderTest = createTemporaryObject(componentUnderTest, root) controlUnderTest = createTemporaryObject(componentUnderTest, root)
verify(!!controlUnderTest) verify(!!controlUnderTest)
controlUnderTest.mainInputLoading = true
controlUnderTest.bottomTextLoading = true
const maxTagButton = findChild(controlUnderTest, "maxTagButton") const maxTagButton = findChild(controlUnderTest, "maxTagButton")
verify(!!maxTagButton) verify(!!maxTagButton)
verify(!maxTagButton.visible) verify(!maxTagButton.visible)
@ -313,42 +318,41 @@ Item {
const holdingSelector = findChild(controlUnderTest, "holdingSelector") const holdingSelector = findChild(controlUnderTest, "holdingSelector")
verify(!!holdingSelector) verify(!!holdingSelector)
const assetSelectorList = findChild(holdingSelector, "assetSelectorList") const assetSelectorList = findChild(holdingSelector, "tokenSelectorListview")
verify(!!assetSelectorList) verify(!!assetSelectorList)
const assetSelectorButton = findChild(controlUnderTest, "assetSelectorButton")
verify(!!assetSelectorButton)
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput") const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput) verify(!!amountToSendInput)
const bottomItemText = findChild(amountToSendInput, "bottomItemText") const bottomItemText = findChild(amountToSendInput, "bottomItemText")
verify(!!bottomItemText) verify(!!bottomItemText)
for (let i= 0; i < d.adaptor.processedAssetsModel.count; i++) { for (let i= 0; i < d.tokenSelectorAdaptor.outputAssetsModel.count; i++) {
let modelItemToTest = ModelUtils.get(d.adaptor.processedAssetsModel, i) let modelItemToTest = ModelUtils.get(d.tokenSelectorAdaptor.outputAssetsModel, i)
mouseClick(assetSelectorButton) mouseClick(holdingSelector)
waitForRendering(assetSelectorList) waitForRendering(assetSelectorList)
let delToTest = assetSelectorList.itemAtIndex(i) let delToTest = assetSelectorList.itemAtIndex(i)
verify(!!delToTest) verify(!!delToTest)
mouseClick(delToTest, 40, 40) // center might be covered by tags mouseClick(delToTest)
waitForRendering(maxTagButton) waitForRendering(controlUnderTest)
verify(maxTagButton.visible) verify(maxTagButton.visible)
verify(!maxTagButton.text.endsWith(modelItemToTest.symbol)) 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 // check input value and state
mouseClick(maxTagButton) mouseClick(maxTagButton)
waitForRendering(amountToSendInput) 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) compare(controlUnderTest.value, maxTagButton.maxSafeValue)
verify(modelItemToTest.currentBalance === 0 ? !controlUnderTest.valueValid : controlUnderTest.valueValid) verify(modelItemToTest.currentBalance === 0 ? !controlUnderTest.valueValid : controlUnderTest.valueValid)
compare(bottomItemText.text, d.adaptor.formatCurrencyAmount( compare(bottomItemText.text, d.adaptor.formatCurrencyAmount(
maxTagButton.maxSafeValue * amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount, maxTagButton.maxSafeValue * amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount,
d.adaptor.currencyStore.currentCurrency)) d.adaptor.currencyStore.currentCurrency))
amountToSendInput.input.input.edit.clear()
} }
} }
@ -366,12 +370,9 @@ Item {
const holdingSelector = findChild(controlUnderTest, "holdingSelector") const holdingSelector = findChild(controlUnderTest, "holdingSelector")
verify(!!holdingSelector) verify(!!holdingSelector)
const assetSelectorList = findChild(holdingSelector, "assetSelectorList") const assetSelectorList = findChild(holdingSelector, "tokenSelectorListview")
verify(!!assetSelectorList) verify(!!assetSelectorList)
const assetSelectorButton = findChild(controlUnderTest, "assetSelectorButton")
verify(!!assetSelectorButton)
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput") const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput) verify(!!amountToSendInput)
@ -388,22 +389,22 @@ Item {
compare(amountToSendInput.input.text, "5.42") compare(amountToSendInput.input.text, "5.42")
for (let i= 0; i < d.adaptor.processedAssetsModel.count; i++) { for (let i= 0; i < d.tokenSelectorAdaptor.outputAssetsModel.count; i++) {
let modelItemToTest = ModelUtils.get(d.adaptor.processedAssetsModel, i) let modelItemToTest = ModelUtils.get(d.tokenSelectorAdaptor.outputAssetsModel, i)
mouseClick(assetSelectorButton) mouseClick(holdingSelector)
waitForRendering(assetSelectorList) waitForRendering(holdingSelector)
let delToTest = assetSelectorList.itemAtIndex(i) let delToTest = assetSelectorList.itemAtIndex(i)
verify(!!delToTest) verify(!!delToTest)
mouseClick(delToTest, 40, 40) // center might be covered by tags mouseClick(delToTest)
// check input value and state // check input value and state
waitForRendering(amountToSendInput) waitForItemPolished(controlUnderTest)
compare(amountToSendInput.input.text, "5.42") compare(amountToSendInput.input.text, "5.42")
compare(bottomItemText.text, d.adaptor.formatCurrencyAmount( tryCompare(bottomItemText, "text", d.adaptor.formatCurrencyAmount(
numberTested * amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount, numberTested * amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount,
d.adaptor.currencyStore.currentCurrency)) d.adaptor.currencyStore.currentCurrency))
compare(controlUnderTest.value, numberTested) compare(controlUnderTest.value, numberTested)
compare(controlUnderTest.rawValue, AmountsArithmetic.fromNumber(amountToSendInput.input.text, modelItemToTest.decimals).toString()) compare(controlUnderTest.rawValue, AmountsArithmetic.fromNumber(amountToSendInput.input.text, modelItemToTest.decimals).toString())
compare(controlUnderTest.valueValid, numberTested <= maxTagButton.maxSafeValue) compare(controlUnderTest.valueValid, numberTested <= maxTagButton.maxSafeValue)
@ -415,7 +416,7 @@ Item {
function test_if_values_are_reset_after_setting_tokenAmount_as_empty() { function test_if_values_are_reset_after_setting_tokenAmount_as_empty() {
const tokenKeyToTest = "ETH" const tokenKeyToTest = "ETH"
let numberTestedString = "1.0001" 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, { controlUnderTest = createTemporaryObject(componentUnderTest, root, {
swapSide: SwapInputPanel.SwapSide.Pay, swapSide: SwapInputPanel.SwapSide.Pay,
tokenKey: tokenKeyToTest, tokenKey: tokenKeyToTest,
@ -442,12 +443,11 @@ Item {
numberTestedString = "" numberTestedString = ""
numberTested = 0 numberTested = 0
mouseClick(amountToSendInput)
controlUnderTest.tokenAmount = numberTestedString controlUnderTest.tokenAmount = numberTestedString
waitForRendering(amountToSendInput) waitForItemPolished(controlUnderTest)
compare(amountToSendInput.input.text, numberTestedString) tryCompare(amountToSendInput.input, "text", numberTestedString)
compare(controlUnderTest.value, numberTested) tryCompare(controlUnderTest, "value", numberTested)
compare(controlUnderTest.rawValue, AmountsArithmetic.fromNumber(numberTested, modelItemToTest.decimals).toString()) compare(controlUnderTest.rawValue, AmountsArithmetic.fromNumber(numberTested, modelItemToTest.decimals).toString())
compare(controlUnderTest.valueValid, false) compare(controlUnderTest.valueValid, false)
compare(controlUnderTest.selectedHoldingId, tokenKeyToTest) 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.popups.swap 1.0
import AppLayouts.Wallet.stores 1.0 import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet 1.0 import AppLayouts.Wallet 1.0
import AppLayouts.Wallet.adaptors 1.0
Item { Item {
id: root id: root
width: 600 width: 800
height: 400 height: 600
readonly property var dummySwapTransactionRoutes: SwapTransactionRoutes {} readonly property var dummySwapTransactionRoutes: SwapTransactionRoutes {}
@ -33,7 +34,7 @@ Item {
return wei/(10**decimals) return wei/(10**decimals)
} }
function fetchSuggestedRoutes(accountFrom, accountTo, amount, tokenFrom, tokenTo, function fetchSuggestedRoutes(accountFrom, accountTo, amount, tokenFrom, tokenTo,
disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) {} disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) {}
} }
readonly property var swapAdaptor: SwapModalAdaptor { readonly property var swapAdaptor: SwapModalAdaptor {
@ -52,6 +53,15 @@ Item {
swapOutputData: SwapOutputData{} 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 property SwapInputParamsForm swapFormData: null
Component { Component {
@ -67,17 +77,18 @@ Item {
} }
} }
SignalSpy {
id: formValuesChanged
target: swapFormData
signalName: "formValuesChanged"
}
TestCase { TestCase {
name: "SwapModal" name: "SwapModal"
when: windowShown when: windowShown
property SwapModal controlUnderTest: null property SwapModal controlUnderTest: null
readonly property SignalSpy formValuesChanged: SignalSpy {
target: root.swapFormData
signalName: "formValuesChanged"
}
// helper functions ------------------------------------------------------------- // helper functions -------------------------------------------------------------
function init() { function init() {
@ -86,6 +97,11 @@ Item {
controlUnderTest = createTemporaryObject(componentUnderTest, root, { swapInputParamsForm: root.swapFormData}) controlUnderTest = createTemporaryObject(componentUnderTest, root, { swapInputParamsForm: root.swapFormData})
} }
function cleanup() {
root.swapFormData.resetFormData()
formValuesChanged.clear()
}
function launchAndVerfyModal() { function launchAndVerfyModal() {
formValuesChanged.clear() formValuesChanged.clear()
verify(!!controlUnderTest) verify(!!controlUnderTest)
@ -415,6 +431,7 @@ Item {
for(let j =0; j< comboBoxList.model.count; j++) { for(let j =0; j< comboBoxList.model.count; j++) {
let accountDelegateUnderTest = comboBoxList.itemAtIndex(j) let accountDelegateUnderTest = comboBoxList.itemAtIndex(j)
verify(!!accountDelegateUnderTest) verify(!!accountDelegateUnderTest)
waitForItemPolished(accountDelegateUnderTest)
const inlineTagDelegate_0 = findChild(accountDelegateUnderTest, "inlineTagDelegate_0") const inlineTagDelegate_0 = findChild(accountDelegateUnderTest, "inlineTagDelegate_0")
verify(!!inlineTagDelegate_0) verify(!!inlineTagDelegate_0)
@ -504,6 +521,8 @@ Item {
// Launch popup // Launch popup
launchAndVerfyModal() launchAndVerfyModal()
waitForItemPolished(controlUnderTest.contentItem)
const maxFeesText = findChild(controlUnderTest, "maxFeesText") const maxFeesText = findChild(controlUnderTest, "maxFeesText")
verify(!!maxFeesText) verify(!!maxFeesText)
@ -676,8 +695,6 @@ Item {
verify(!!maxTagButton) verify(!!maxTagButton)
const holdingSelectorsContentItemText = findChild(payPanel, "holdingSelectorsContentItemText") const holdingSelectorsContentItemText = findChild(payPanel, "holdingSelectorsContentItemText")
verify(!!holdingSelectorsContentItemText) verify(!!holdingSelectorsContentItemText)
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel) waitForRendering(payPanel)
@ -688,10 +705,8 @@ Item {
verify(amountToSendInput.input.input.edit.cursorVisible) verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0)) compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0, root.swapAdaptor.currencyStore.currentCurrency)) 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(holdingSelectorsContentItemText.text, qsTr("Select asset"))
compare(holdingSelectorsTokenIcon.image.source, "")
verify(!holdingSelectorsTokenIcon.visible)
verify(!maxTagButton.visible) verify(!maxTagButton.visible)
compare(payPanel.selectedHoldingId, "") compare(payPanel.selectedHoldingId, "")
compare(payPanel.value, 0) compare(payPanel.value, 0)
@ -710,11 +725,13 @@ Item {
root.swapFormData.fromTokensKey = "ETH" root.swapFormData.fromTokensKey = "ETH"
root.swapFormData.fromTokenAmount = valueToExchangeString 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 // Launch popup
launchAndVerfyModal() launchAndVerfyModal()
waitForItemPolished(controlUnderTest.contentItem)
const payPanel = findChild(controlUnderTest, "payPanel") const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel) verify(!!payPanel)
const amountToSendInput = findChild(payPanel, "amountToSendInput") const amountToSendInput = findChild(payPanel, "amountToSendInput")
@ -730,20 +747,18 @@ Item {
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon") const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon) verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel)
compare(amountToSendInput.caption, qsTr("Pay")) compare(amountToSendInput.caption, qsTr("Pay"))
verify(amountToSendInput.interactive) verify(amountToSendInput.interactive)
compare(amountToSendInput.input.text, valueToExchangeString) tryCompare(amountToSendInput.input.input, "text", valueToExchangeString)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0)) compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
verify(amountToSendInput.input.input.edit.cursorVisible) tryCompare(amountToSendInput.input.input.edit, "cursorVisible", true)
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))
compare(holdingSelector.selectedItem, expectedToken) compare(holdingSelector.currentTokensKey, expectedToken.tokensKey)
compare(holdingSelectorsContentItemText.text, expectedToken.symbol) compare(holdingSelectorsContentItemText.text, expectedToken.symbol)
compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol)) compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol))
verify(holdingSelectorsTokenIcon.visible) verify(holdingSelectorsTokenIcon.visible)
verify(maxTagButton.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.selectedHoldingId, expectedToken.symbol)
compare(payPanel.value, valueToExchange) compare(payPanel.value, valueToExchange)
compare(payPanel.rawValue, SQUtils.AmountsArithmetic.fromNumber(valueToExchangeString, expectedToken.decimals).toString()) compare(payPanel.rawValue, SQUtils.AmountsArithmetic.fromNumber(valueToExchangeString, expectedToken.decimals).toString())
@ -777,8 +792,6 @@ Item {
verify(!!maxTagButton) verify(!!maxTagButton)
const holdingSelectorsContentItemText = findChild(payPanel, "holdingSelectorsContentItemText") const holdingSelectorsContentItemText = findChild(payPanel, "holdingSelectorsContentItemText")
verify(!!holdingSelectorsContentItemText) verify(!!holdingSelectorsContentItemText)
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel) waitForRendering(payPanel)
@ -787,11 +800,10 @@ Item {
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0)) compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
verify(amountToSendInput.input.input.edit.cursorVisible) verify(amountToSendInput.input.input.edit.cursorVisible)
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0, root.swapAdaptor.currencyStore.currentCurrency)) compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, null) compare(holdingSelector.currentTokensKey, "")
compare(holdingSelectorsContentItemText.text, "") compare(holdingSelectorsContentItemText.text, "Select asset")
verify(!holdingSelectorsTokenIcon.visible)
verify(!maxTagButton.visible) verify(!maxTagButton.visible)
compare(payPanel.selectedHoldingId, invalidValue) compare(payPanel.selectedHoldingId, "")
compare(payPanel.value, 0) compare(payPanel.value, 0)
compare(payPanel.rawValue, SQUtils.AmountsArithmetic.fromNumber("0", 0).toString()) compare(payPanel.rawValue, SQUtils.AmountsArithmetic.fromNumber("0", 0).toString())
verify(!payPanel.valueValid) verify(!payPanel.valueValid)
@ -809,11 +821,13 @@ Item {
root.swapFormData.fromTokensKey = "ETH" root.swapFormData.fromTokensKey = "ETH"
root.swapFormData.fromTokenAmount = valueToExchangeString 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 // Launch popup
launchAndVerfyModal() launchAndVerfyModal()
waitForItemPolished(controlUnderTest.contentItem)
const payPanel = findChild(controlUnderTest, "payPanel") const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel) verify(!!payPanel)
const amountToSendInput = findChild(payPanel, "amountToSendInput") const amountToSendInput = findChild(payPanel, "amountToSendInput")
@ -829,20 +843,18 @@ Item {
const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon") const holdingSelectorsTokenIcon = findChild(payPanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon) verify(!!holdingSelectorsTokenIcon)
waitForRendering(payPanel)
compare(amountToSendInput.caption, qsTr("Pay")) compare(amountToSendInput.caption, qsTr("Pay"))
verify(amountToSendInput.interactive) verify(amountToSendInput.interactive)
compare(amountToSendInput.input.text, valueToExchangeString) compare(amountToSendInput.input.text, valueToExchangeString)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0)) compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
verify(amountToSendInput.input.input.edit.cursorVisible) verify(amountToSendInput.input.input.edit.cursorVisible)
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))
compare(holdingSelector.selectedItem, expectedToken) compare(holdingSelector.currentTokensKey, expectedToken.tokensKey)
compare(holdingSelectorsContentItemText.text, expectedToken.symbol) compare(holdingSelectorsContentItemText.text, expectedToken.symbol)
compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol)) compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol))
verify(holdingSelectorsTokenIcon.visible) verify(holdingSelectorsTokenIcon.visible)
verify(maxTagButton.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.selectedHoldingId, expectedToken.symbol)
compare(payPanel.value, valueToExchange) compare(payPanel.value, valueToExchange)
compare(payPanel.rawValue, SQUtils.AmountsArithmetic.fromNumber(valueToExchangeString, expectedToken.decimals).toString()) compare(payPanel.rawValue, SQUtils.AmountsArithmetic.fromNumber(valueToExchangeString, expectedToken.decimals).toString())
@ -853,7 +865,6 @@ Item {
function test_modal_pay_input_switching_networks() { function test_modal_pay_input_switching_networks() {
// try setting value before popup is launched and check values // try setting value before popup is launched and check values
root.swapFormData.resetFormData()
let valueToExchange = 0.3 let valueToExchange = 0.3
let valueToExchangeString = valueToExchange.toString() let valueToExchangeString = valueToExchange.toString()
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(0).address
@ -871,11 +882,11 @@ Item {
for (let i=0; i< root.swapAdaptor.filteredFlatNetworksModel.count; i++) { for (let i=0; i< root.swapAdaptor.filteredFlatNetworksModel.count; i++) {
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(i).chainId root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(i).chainId
waitForRendering(payPanel) 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 // check states for the pay input selector
verify(maxTagButton.visible) 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(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(maxPossibleValue, expectedToken.symbol, {noSymbol: true})))
compare(payPanel.selectedHoldingId, expectedToken.symbol) compare(payPanel.selectedHoldingId, expectedToken.symbol)
compare(payPanel.valueValid, valueToExchange <= maxPossibleValue) compare(payPanel.valueValid, valueToExchange <= maxPossibleValue)
@ -911,7 +922,7 @@ Item {
verify(!amountToSendInput.input.input.edit.cursorVisible) verify(!amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0)) compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(0, root.swapAdaptor.currencyStore.currentCurrency)) 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(holdingSelectorsContentItemText.text, qsTr("Select asset"))
verify(!maxTagButton.visible) verify(!maxTagButton.visible)
compare(receivePanel.selectedHoldingId, "") compare(receivePanel.selectedHoldingId, "")
@ -931,11 +942,13 @@ Item {
root.swapFormData.toTokenKey = "STT" root.swapFormData.toTokenKey = "STT"
root.swapFormData.toTokenAmount = valueToReceiveString 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 // Launch popup
launchAndVerfyModal() launchAndVerfyModal()
waitForItemPolished(controlUnderTest.contentItem)
const receivePanel = findChild(controlUnderTest, "receivePanel") const receivePanel = findChild(controlUnderTest, "receivePanel")
verify(!!receivePanel) verify(!!receivePanel)
const amountToSendInput = findChild(receivePanel, "amountToSendInput") const amountToSendInput = findChild(receivePanel, "amountToSendInput")
@ -951,16 +964,14 @@ Item {
const holdingSelectorsTokenIcon = findChild(receivePanel, "holdingSelectorsTokenIcon") const holdingSelectorsTokenIcon = findChild(receivePanel, "holdingSelectorsTokenIcon")
verify(!!holdingSelectorsTokenIcon) verify(!!holdingSelectorsTokenIcon)
waitForRendering(receivePanel)
compare(amountToSendInput.caption, qsTr("Receive")) compare(amountToSendInput.caption, qsTr("Receive"))
// TODO: this should be come interactive under https://github.com/status-im/status-desktop/issues/15095 // TODO: this should be come interactive under https://github.com/status-im/status-desktop/issues/15095
verify(!amountToSendInput.interactive) verify(!amountToSendInput.interactive)
verify(!amountToSendInput.input.input.edit.cursorVisible) verify(!amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, valueToReceive.toLocaleString(Qt.locale(), 'f', -128)) compare(amountToSendInput.input.text, valueToReceive.toLocaleString(Qt.locale(), 'f', -128))
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0)) compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0))
compare(bottomItemText.text, root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToReceive * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency)) tryCompare(bottomItemText, "text", root.swapAdaptor.currencyStore.formatCurrencyAmount(valueToReceive * expectedToken.marketDetails.currencyPrice.amount, root.swapAdaptor.currencyStore.currentCurrency))
compare(holdingSelector.selectedItem, expectedToken) compare(holdingSelector.currentTokensKey, expectedToken.tokensKey)
compare(holdingSelectorsContentItemText.text, expectedToken.symbol) compare(holdingSelectorsContentItemText.text, expectedToken.symbol)
compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol)) compare(holdingSelectorsTokenIcon.image.source, Constants.tokenIcon(expectedToken.symbol))
verify(holdingSelectorsTokenIcon.visible) verify(holdingSelectorsTokenIcon.visible)
@ -974,22 +985,23 @@ Item {
} }
function test_modal_max_button_click_with_preset_pay_value() { 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 // try setting value before popup is launched and check values
let valueToExchange = 0.2 let valueToExchange = 0.2
let valueToExchangeString = valueToExchange.toString() let valueToExchangeString = valueToExchange.toString()
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId 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.fromTokensKey = "ETH"
root.swapFormData.fromTokenAmount = valueToExchangeString root.swapFormData.fromTokenAmount = valueToExchangeString
root.swapFormData.toTokenKey = "STT" 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") const payPanel = findChild(controlUnderTest, "payPanel")
verify(!!payPanel) verify(!!payPanel)
@ -1000,31 +1012,32 @@ Item {
const bottomItemText = findChild(payPanel, "bottomItemText") const bottomItemText = findChild(payPanel, "bottomItemText")
verify(!!bottomItemText) verify(!!bottomItemText)
waitForRendering(payPanel) let expectedToken = SQUtils.ModelUtils.getByKey(root.tokenSelectorAdaptor.outputAssetsModel, "tokensKey", "ETH")
let expectedToken = SQUtils.ModelUtils.getByKey(root.swapAdaptor.processedAssetsModel, "tokensKey", "ETH")
// check states for the pay input selector // check states for the pay input selector
verify(maxTagButton.visible) verify(maxTagButton.visible)
let maxPossibleValue = WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol) let maxPossibleValue = WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol)
let truncmaxPossibleValue = Math.trunc(maxPossibleValue*100)/100 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.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible) tryCompare(amountToSendInput.input.input.edit, "cursorVisible", true)
compare(amountToSendInput.input.text, valueToExchange.toLocaleString(Qt.locale(), 'f', -128)) tryCompare(amountToSendInput.input, "text", valueToExchange.toLocaleString(Qt.locale(), 'f', -128))
compare(amountToSendInput.input.placeholderText, LocaleUtils.numberToLocaleString(0)) 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 // click on max button
maxTagButton.clicked() mouseClick(maxTagButton)
waitForRendering(payPanel) 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.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible) verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, maxPossibleValue.toLocaleString(Qt.locale(), 'f', -128)) tryCompare(amountToSendInput.input, "text", maxPossibleValue === 0 ? "" : 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() closeAndVerfyModal()
} }
@ -1055,13 +1068,12 @@ Item {
waitForRendering(payPanel) 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 // check states for the pay input selector
verify(maxTagButton.visible) verify(maxTagButton.visible)
let maxPossibleValue = WalletUtils.calculateMaxSafeSendAmount(expectedToken.currentBalance, expectedToken.symbol) 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(maxPossibleValue, expectedToken.symbol, {noSymbol: true})))
compare(maxTagButton.text, qsTr("Max. %1").arg(root.swapAdaptor.currencyStore.formatCurrencyAmount(truncmaxPossibleValue, expectedToken.symbol, {noSymbol: true})))
verify(amountToSendInput.interactive) verify(amountToSendInput.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible) verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, "") compare(amountToSendInput.input.text, "")
@ -1070,14 +1082,14 @@ Item {
// click on max button // click on max button
maxTagButton.clicked() maxTagButton.clicked()
waitForRendering(payPanel) waitForItemPolished(payPanel)
compare(formValuesChanged.count, 5) tryCompare(formValuesChanged, "count", 5)
verify(amountToSendInput.interactive) verify(amountToSendInput.interactive)
verify(amountToSendInput.input.input.edit.cursorVisible) verify(amountToSendInput.input.input.edit.cursorVisible)
compare(amountToSendInput.input.text, maxPossibleValue.toLocaleString(Qt.locale(), 'f', -128)) 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() closeAndVerfyModal()
} }
@ -1086,7 +1098,7 @@ Item {
// test with pay value being set and not set // test with pay value being set and not set
let payValuesToTestWith = ["", "0.2"] 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 valueToExchangeString = payValuesToTestWith[index]
let valueToExchange = Number(valueToExchangeString) let valueToExchange = Number(valueToExchangeString)
@ -1112,18 +1124,18 @@ Item {
for (let i=0; i< root.swapAdaptor.nonWatchAccounts.count; i++) { for (let i=0; i< root.swapAdaptor.nonWatchAccounts.count; i++) {
root.swapFormData.selectedAccountAddress = root.swapAdaptor.nonWatchAccounts.get(i).address 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 // check states for the pay input selector
verify(maxTagButton.visible) 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(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.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") 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 // 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: 1, balance: "122082928968121891" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "1013151281976507736" }, { account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "1013151281976507736" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 421613, balance: "473057568699284613" }, { account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 421613, balance: "473057568699284613" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "307400931315122839" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 11155111, balance: "307400931315122839" }, { account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 11155111, balance: "307400931315122839" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "122082928968121891" }, { account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "122082928968121891" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 421613, balance: "0" }, { 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 popup: comboBox.popup
property alias currentIndex: comboBox.currentIndex property alias currentIndex: comboBox.currentIndex
property alias currentValue: comboBox.currentValue readonly property alias currentValue: comboBox.currentValue
property alias currentText: comboBox.currentText readonly property alias currentText: comboBox.currentText
property alias label: labelItem.text property alias label: labelItem.text
property alias validationError: validationErrorItem.text property alias validationError: validationErrorItem.text

View File

@ -23,6 +23,7 @@ QObject {
- balances: submodel -> [ chainId:int, account:string, balance:BigIntString, iconUrl:string ] - balances: submodel -> [ chainId:int, account:string, balance:BigIntString, iconUrl:string ]
Computed values: Computed values:
- currentBalance: double (amount of tokens)
- currencyBalance: double (e.g. `1000.42` in user's fiat currency) - 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) - 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) - 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: [ sorters: [
RoleSorter {
roleName: "sectionId"
},
RoleSorter { RoleSorter {
roleName: "currencyBalance" roleName: "currencyBalance"
sortOrder: Qt.DescendingOrder sortOrder: Qt.DescendingOrder
},
RoleSorter {
roleName: "name"
} }
] ]
} }
@ -94,6 +100,20 @@ QObject {
currencyBalance ? LocaleUtils.currencyAmountToLocaleString({amount: currencyBalance, symbol: root.currentCurrency, displayDecimals}) 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 readonly property var balances: this
sourceModel: joinModel 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"] expectedRoles: ["communityId", "balances", "decimals", "marketDetails"]
} }
} }

View File

@ -61,7 +61,7 @@ Control {
StatusBaseText { StatusBaseText {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: -12 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 wrapMode: Text.Wrap
color: Theme.palette.directColor5 color: Theme.palette.directColor5
} }

View File

@ -21,17 +21,10 @@ StatusButton {
locale: LocaleUtils.userInputLocale locale: LocaleUtils.userInputLocale
QtObject {
id: d
readonly property string maxInputBalanceFormatted:
root.formatCurrencyAmount(Math.trunc(root.maxSafeValue*100)/100, root.symbol)
}
implicitHeight: 22 implicitHeight: 22
type: valid ? StatusBaseButton.Type.Normal : StatusBaseButton.Type.Danger 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 horizontalPadding: 8
verticalPadding: 3 verticalPadding: 3

View File

@ -183,7 +183,9 @@ StatusComboBox {
value: root.selection[0] ?? -1 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 singleCelectionChainName: singleSelectionItem.item.chainName ?? ""
readonly property string titleText: { 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 CollectibleLinksTags 1.0 CollectibleLinksTags.qml
SwapExchangeButton 1.0 SwapExchangeButton.qml SwapExchangeButton 1.0 SwapExchangeButton.qml
EditSlippagePanel 1.0 EditSlippagePanel.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 StatusQ.Core.Theme 0.1
import AppLayouts.Wallet.controls 1.0 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.views 1.0
import shared.popups.send.panels 1.0
import utils 1.0 import utils 1.0
import shared.stores 1.0 import shared.stores 1.0
@ -28,11 +29,21 @@ Control {
required property var flatNetworksModel required property var flatNetworksModel
required property var processedAssetsModel required property var processedAssetsModel
property int selectedNetworkChainId: -1
property string selectedAccountAddress
property string nonInteractiveTokensKey
property string tokenKey property string tokenKey
onTokenKeyChanged: reevaluateSelectedId() onTokenKeyChanged: Qt.callLater(reevaluateSelectedId)
property string tokenAmount property string tokenAmount
onTokenAmountChanged: { 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 property int swapSide: SwapInputPanel.SwapSide.Pay
@ -41,17 +52,19 @@ Control {
property bool bottomTextLoading property bool bottomTextLoading
property bool interactive: true property bool interactive: true
function reevaluateSelectedId() {
if (!!tokenKey) {
holdingSelector.selectToken(tokenKey)
d.selectedHolding = SQUtils.ModelUtils.getByKey(holdingSelector.model, "tokensKey", holdingSelector.currentTokensKey)
}
}
// output API // output API
readonly property string selectedHoldingId: d.selectedHoldingId readonly property string selectedHoldingId: holdingSelector.currentTokensKey
readonly property double value: amountToSendInput.cryptoValueToSendFloat readonly property double value: amountToSendInput.cryptoValueToSendFloat
readonly property string rawValue: amountToSendInput.cryptoValueToSend readonly property string rawValue: amountToSendInput.cryptoValueToSend
readonly property bool valueValid: amountToSendInput.inputNumberValid readonly property bool valueValid: amountToSendInput.inputNumberValid
readonly property bool amountEnteredGreaterThanBalance: value > maxSendButton.maxSafeValue readonly property bool amountEnteredGreaterThanBalance: value > maxSendButton.maxSafeValue
function reevaluateSelectedId() {
if (!!tokenKey) {
Qt.callLater(d.setSelectedHoldingId, tokenKey, Constants.TokenType.ERC20)
}
}
// visual properties // visual properties
property int swapExchangeButtonWidth: 44 property int swapExchangeButtonWidth: 44
@ -76,35 +89,30 @@ Control {
QtObject { QtObject {
id: d id: d
function setSelectedHoldingId(holdingId, holdingType) { property var selectedHolding: SQUtils.ModelUtils.getByKey(holdingSelector.model, "tokensKey", holdingSelector.currentTokensKey)
let holding = SQUtils.ModelUtils.getByKey(root.processedAssetsModel, "symbol", holdingId)
d.selectedHoldingId = holdingId
d.setSelectedHolding(holding, holdingType)
}
function setSelectedHolding(holding, holdingType) { readonly property bool isSelectedHoldingValidAsset: !!selectedHolding
d.selectedHoldingType = holdingType readonly property double maxFiatBalance: isSelectedHoldingValidAsset ? selectedHolding.currencyBalance : 0
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 double maxCryptoBalance: isSelectedHoldingValidAsset ? selectedHolding.currentBalance : 0 readonly property double maxCryptoBalance: isSelectedHoldingValidAsset ? selectedHolding.currentBalance : 0
readonly property double maxInputBalance: amountToSendInput.inputIsFiat ? maxFiatBalance : maxCryptoBalance readonly property double maxInputBalance: amountToSendInput.inputIsFiat ? maxFiatBalance : maxCryptoBalance
readonly property string inputSymbol: amountToSendInput.inputIsFiat ? root.currencyStore.currentCurrency : readonly property string inputSymbol: amountToSendInput.inputIsFiat ? root.currencyStore.currentCurrency
!!d.selectedHolding && !!d.selectedHolding.symbol ? d.selectedHolding.symbol: "" : (!!selectedHolding ? selectedHolding.symbol : "")
property string searchText
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 { background: Shape {
id: shape id: shape
property int radius: 16 property int radius: Style.current.radius
property int leftTopRadius: radius property int leftTopRadius: radius
property int rightTopRadius: radius property int rightTopRadius: radius
property int leftBottomRadius: radius property int leftBottomRadius: radius
@ -190,14 +198,13 @@ Control {
objectName: "amountToSendInput" objectName: "amountToSendInput"
caption: root.caption caption: root.caption
interactive: root.interactive interactive: root.interactive
selectedHolding: d.selectedHolding selectedHolding: d.selectedHolding // FIXME shouldn't be necesary to pass the whole object
fiatInputInteractive: root.fiatInputInteractive fiatInputInteractive: root.fiatInputInteractive
input.input.edit.color: !input.valid ? Theme.palette.dangerColor1 : maxSendButton.hovered ? Theme.palette.baseColor1 input.input.edit.color: !input.valid ? Theme.palette.dangerColor1 : maxSendButton.hovered ? Theme.palette.baseColor1
: Theme.palette.directColor1 : Theme.palette.directColor1
multiplierIndex: d.isSelectedHoldingValidAsset && !!holdingSelector.selectedItem && !!holdingSelector.selectedItem.decimals multiplierIndex: !!d.selectedHolding ? d.selectedHolding.decimals : 0
? holdingSelector.selectedItem.decimals
: 0
maxInputBalance: (root.swapSide === SwapInputPanel.SwapSide.Receive || !d.isSelectedHoldingValidAsset) ? Number.POSITIVE_INFINITY maxInputBalance: (root.swapSide === SwapInputPanel.SwapSide.Receive || !d.isSelectedHoldingValidAsset) ? Number.POSITIVE_INFINITY
: maxSendButton.maxSafeValue : maxSendButton.maxSafeValue
@ -212,37 +219,14 @@ Control {
Item { Layout.fillHeight: true } Item { Layout.fillHeight: true }
HoldingSelector { TokenSelector {
id: holdingSelector id: holdingSelector
objectName: "holdingSelector" objectName: "holdingSelector"
Layout.rightMargin: d.isSelectedHoldingValidAsset ? -root.padding : 0 Layout.rightMargin: d.isSelectedHoldingValidAsset ? -root.padding : 0
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.preferredHeight: 38 model: d.adaptor.outputAssetsModel
nonInteractiveDelegateKey: root.nonInteractiveTokensKey
searchPlaceholderText: qsTr("Search asset name or symbol") onActivated: amountToSendInput.input.forceActiveFocus()
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
} }
Item { Layout.fillHeight: !maxSendButton.visible } 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.Theme 0.1
import StatusQ.Core.Utils 0.1 as SQUtils import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Popups.Dialog 0.1 import StatusQ.Popups.Dialog 0.1
import StatusQ.Controls 0.1
import shared.popups.send.controls 1.0 import shared.popups.send.controls 1.0
import shared.controls 1.0 import shared.controls 1.0
@ -37,11 +36,11 @@ StatusDialog {
QtObject { QtObject {
id: d id: d
property var debounceFetchSuggestedRoutes: Backpressure.debounce(root, 1000, function() { property var debounceFetchSuggestedRoutes: Backpressure.debounce(root, 1000, function() {
root.swapAdaptor.fetchSuggestedRoutes(payPanel.rawValue) root.swapAdaptor.fetchSuggestedRoutes(payPanel.rawValue)
}) })
function fetchSuggestedRoutes() { function fetchSuggestedRoutes() {
if (payPanel.valueValid) { if (payPanel.valueValid && !!payPanel.selectedHoldingId) {
root.swapAdaptor.newFetchReset() root.swapAdaptor.newFetchReset()
root.swapAdaptor.swapProposalLoading = true root.swapAdaptor.swapProposalLoading = true
debounceFetchSuggestedRoutes() debounceFetchSuggestedRoutes()
@ -59,6 +58,7 @@ StatusDialog {
payPanel.reevaluateSelectedId() payPanel.reevaluateSelectedId()
} }
function onSelectedNetworkChainIdChanged() { function onSelectedNetworkChainIdChanged() {
networkFilter.selection = [root.swapInputParamsForm.selectedNetworkChainId]
payPanel.reevaluateSelectedId() payPanel.reevaluateSelectedId()
} }
} }
@ -101,7 +101,6 @@ StatusDialog {
HeaderTitleText { HeaderTitleText {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
id: modalHeader
text: qsTr("Swap") text: qsTr("Swap")
} }
StatusBaseText { StatusBaseText {
@ -113,7 +112,6 @@ StatusDialog {
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
// TODO: update this once https://github.com/status-im/status-desktop/issues/14780 is ready
NetworkFilter { NetworkFilter {
id: networkFilter id: networkFilter
objectName: "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 currencyStore: root.swapAdaptor.currencyStore
flatNetworksModel: root.swapAdaptor.filteredFlatNetworksModel flatNetworksModel: root.swapAdaptor.swapStore.flatNetworks
processedAssetsModel: root.swapAdaptor.processedAssetsModel processedAssetsModel: root.swapAdaptor.walletAssetsStore.groupedAccountAssetsModel
tokenKey: root.swapInputParamsForm.fromTokensKey tokenKey: root.swapInputParamsForm.fromTokensKey
tokenAmount: { tokenAmount: root.swapInputParamsForm.fromTokenAmount
// Only update if there is different in amount displayed
if (root.swapInputParamsForm.fromTokenAmount !== selectedNetworkChainId: root.swapInputParamsForm.selectedNetworkChainId
SQUtils.AmountsArithmetic.fromString(value).toLocaleString(locale, 'f', -128)){ selectedAccountAddress: root.swapInputParamsForm.selectedAccountAddress
return root.swapInputParamsForm.fromTokenAmount nonInteractiveTokensKey: receivePanel.selectedHoldingId
}
return payPanel.tokenAmount
}
swapSide: SwapInputPanel.SwapSide.Pay swapSide: SwapInputPanel.SwapSide.Pay
swapExchangeButtonWidth: swapButton.width swapExchangeButtonWidth: swapButton.width
@ -194,12 +175,16 @@ StatusDialog {
} }
currencyStore: root.swapAdaptor.currencyStore currencyStore: root.swapAdaptor.currencyStore
flatNetworksModel: root.swapAdaptorfilteredFlatNetworksModel flatNetworksModel: root.swapAdaptor.swapStore.flatNetworks
processedAssetsModel: root.swapAdaptor.processedAssetsModel processedAssetsModel: root.swapAdaptor.walletAssetsStore.groupedAccountAssetsModel
tokenKey: root.swapInputParamsForm.toTokenKey tokenKey: root.swapInputParamsForm.toTokenKey
tokenAmount: root.swapAdaptor.validSwapProposalReceived && root.swapAdaptor.toToken ? root.swapAdaptor.swapOutputData.toTokenAmount: root.swapInputParamsForm.toTokenAmount 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 swapSide: SwapInputPanel.SwapSide.Receive
swapExchangeButtonWidth: swapButton.width swapExchangeButtonWidth: swapButton.width
@ -307,7 +292,6 @@ StatusDialog {
objectName: "maxFeesText" objectName: "maxFeesText"
text: qsTr("Max fees:") text: qsTr("Max fees:")
color: Theme.palette.directColor5 color: Theme.palette.directColor5
font.pixelSize: 15
font.weight: Font.Medium font.weight: Font.Medium
} }
StatusTextWithLoadingState { StatusTextWithLoadingState {
@ -319,7 +303,6 @@ StatusDialog {
root.swapAdaptor.currencyStore.currentCurrency) : root.swapAdaptor.currencyStore.currentCurrency) :
"--" "--"
customColor: Theme.palette.directColor4 customColor: Theme.palette.directColor4
font.pixelSize: 15
font.weight: Font.Medium font.weight: Font.Medium
loading: root.swapAdaptor.swapProposalLoading loading: root.swapAdaptor.swapProposalLoading
} }
@ -353,4 +336,3 @@ StatusDialog {
} }
} }
} }

View File

@ -23,8 +23,6 @@ QObject {
property bool validSwapProposalReceived: false property bool validSwapProposalReceived: false
property bool swapProposalLoading: false property bool swapProposalLoading: false
property bool showCommunityTokens
// To expose the selected from and to Token from the SwapModal // To expose the selected from and to Token from the SwapModal
readonly property var fromToken: fromTokenEntry.item readonly property var fromToken: fromTokenEntry.item
readonly property var toToken: toTokenEntry.item readonly property var toToken: toTokenEntry.item
@ -64,50 +62,6 @@ QObject {
filters: ValueFilter { roleName: "isTest"; value: root.swapStore.areTestNetworksEnabled } 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 { ModelEntry {
id: fromTokenEntry id: fromTokenEntry
sourceModel: root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel sourceModel: root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel
@ -134,27 +88,6 @@ QObject {
property string uuid 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 { readonly property SubmodelProxyModel filteredBalancesModel: SubmodelProxyModel {
sourceModel: root.walletAssetsStore.baseGroupedAccountAssetModel sourceModel: root.walletAssetsStore.baseGroupedAccountAssetModel
submodelRoleName: "balances" submodelRoleName: "balances"
@ -200,17 +133,6 @@ QObject {
formattedBalance: root.formatCurrencyAmount(.0 , root.fromToken.symbol) 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 { Connections {
@ -224,10 +146,11 @@ QObject {
if(txRoutes.suggestedRoutes.count === 1) { if(txRoutes.suggestedRoutes.count === 1) {
root.validSwapProposalReceived = true root.validSwapProposalReceived = true
root.swapOutputData.bestRoutes = txRoutes.suggestedRoutes 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 gasTimeEstimate = txRoutes.gasTimeEstimate
let totalTokenFeesInFiat = 0 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 totalTokenFeesInFiat = gasTimeEstimate.totalTokenFees * root.fromToken.marketDetails.currencyPrice.amount
root.swapOutputData.totalFees = root.currencyStore.getFiatValue(gasTimeEstimate.totalFeesInEth, Constants.ethToken) + totalTokenFeesInFiat root.swapOutputData.totalFees = root.currencyStore.getFiatValue(gasTimeEstimate.totalFeesInEth, Constants.ethToken) + totalTokenFeesInFiat
root.swapOutputData.approvalNeeded = ModelUtils.get(root.swapOutputData.bestRoutes, 0, "route").approvalRequired root.swapOutputData.approvalNeeded = ModelUtils.get(root.swapOutputData.bestRoutes, 0, "route").approvalRequired

View File

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