feat(Swap): display the exchange rate approximation in swap

Show the exchange rate approximation in swap. The exchange rate shows when the swap input is valid.
It has 3 states:
1. invisibile - when the pay/receive input is empty
2. visible and loading - while fetching the routes. Happens whenever the swap input changes AND the input is valid
3. visbile, with data - once the routes have been fetched
This commit is contained in:
Alex Jbanca 2025-02-10 17:58:28 +02:00 committed by Alex Jbanca
parent 85e12f8761
commit 36872b6f1c
4 changed files with 192 additions and 5 deletions

View File

@ -11,7 +11,7 @@ SplitView {
QtObject {
id: d
readonly property var sizesModel: [StatusBaseButton.Size.Tiny, StatusBaseButton.Size.Small, StatusBaseButton.Size.Large]
readonly property var sizesModel: [StatusBaseButton.Size.XSmall, StatusBaseButton.Size.Tiny, StatusBaseButton.Size.Small, StatusBaseButton.Size.Large]
readonly property string effectiveEmoji: ctrlEmojiEnabled.checked ? ctrlEmoji.text : ""
readonly property int effectiveTextPosition: ctrlTextPosLeft.checked ? StatusBaseButton.TextPosition.Left
@ -30,16 +30,17 @@ SplitView {
anchors.centerIn: parent
rowSpacing: 10
columnSpacing: 10
columns: 4
columns: 5
Label { text: "" }
Label { text: "XSmall" }
Label { text: "Tiny" }
Label { text: "Small" }
Label { text: "Large" }
Label {
text: "StatusButton"
Layout.columnSpan: 4
Layout.columnSpan: 5
font.bold: true
}
@ -128,7 +129,7 @@ SplitView {
Label {
text: "StatusFlatButton"
Layout.columnSpan: 4
Layout.columnSpan: 5
font.bold: true
}

View File

@ -1988,5 +1988,104 @@ Item {
closeAndVerfyModal()
}
function test_exchange_rate() {
// Asset chosen but no pay value set state -------------------------------------------------------------------------------
root.swapFormData.fromTokenAmount = "1"
root.swapFormData.selectedAccountAddress = "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"
root.swapFormData.selectedNetworkChainId = 11155111
root.swapFormData.fromTokensKey = "ETH"
root.swapFormData.toTokenKey = ""
// Launch popup
launchAndVerfyModal()
const quoteItem = findChild(controlUnderTest, "quoteApproximationRight")
verify(!!quoteItem)
const sellItem = findChild(controlUnderTest, "quoteApproximationLeft")
verify(!!sellItem)
const priceItem = findChild(controlUnderTest, "quoteApproximationPrice")
verify(!!priceItem)
const invertQuoteApproximation = findChild(controlUnderTest, "invertQuoteApproximation")
verify(!!invertQuoteApproximation)
verify(!sellItem.visible)
verify(!quoteItem.visible)
verify(!priceItem.visible)
verify(!invertQuoteApproximation.visible)
fetchSuggestedRoutesCalled.clear()
root.swapFormData.toTokenKey = "STT"
tryCompare(fetchSuggestedRoutesCalled, "count", 1)
tryCompare(sellItem, "visible", true)
compare(sellItem.text, "1 ETH ≈ ")
verify(quoteItem.visible)
verify(quoteItem.loading)
verify(!priceItem.visible)
verify(!invertQuoteApproximation.visible)
// emit routes ready
let txHasRouteNoApproval = root.dummySwapTransactionRoutes.txHasRouteNoApproval
txHasRouteNoApproval.uuid = root.swapAdaptor.uuid
txHasRouteNoApproval.amountToReceive = "1000000000000000000" // "1" in STT
root.swapStore.suggestedRoutesReady(txHasRouteNoApproval, "", "")
tryVerify( () => quoteItem.visible)
verify(!quoteItem.loading)
verify(priceItem.visible)
verify(invertQuoteApproximation.visible)
tryCompare(sellItem, "text", "1 ETH ≈ ")
tryCompare(quoteItem, "text", "1 STT ")
verify(priceItem.text.startsWith("(1 ")) //Hardcoded to crypto amount input - 1 in our case
fetchSuggestedRoutesCalled.clear()
root.swapFormData.fromTokenAmount = "2"
// Back to loading states
tryCompare(fetchSuggestedRoutesCalled, "count", 1)
tryCompare(quoteItem, "visible", true)
verify(quoteItem.loading)
verify(!priceItem.visible)
verify(!invertQuoteApproximation.visible)
// emit routes ready
txHasRouteNoApproval = root.dummySwapTransactionRoutes.txHasRouteNoApproval
txHasRouteNoApproval.uuid = root.swapAdaptor.uuid
txHasRouteNoApproval.amountToReceive = "4000000000000000000" // "4" in STT
root.swapStore.suggestedRoutesReady(txHasRouteNoApproval, "", "")
tryCompare(sellItem, "text", "1 ETH ≈ ")
tryCompare(quoteItem, "text", "2 STT ")
verify(priceItem.text.startsWith("(1 ")) //Hardcoded to crypto amount input - 1 in our case
verify(invertQuoteApproximation.visible)
mouseClick(invertQuoteApproximation)
tryCompare(sellItem, "text", "1 STT ≈ ")
tryCompare(quoteItem, "text", "0.5 ETH ")
// resetting to default
fetchSuggestedRoutesCalled.clear()
root.swapFormData.fromTokenAmount = "1"
// Back to loading states
tryCompare(fetchSuggestedRoutesCalled, "count", 1)
tryCompare(quoteItem, "visible", true)
verify(quoteItem.loading)
verify(!priceItem.visible)
// emit routes ready
txHasRouteNoApproval = root.dummySwapTransactionRoutes.txHasRouteNoApproval
txHasRouteNoApproval.uuid = root.swapAdaptor.uuid
txHasRouteNoApproval.amountToReceive = "1000000000000000000" // "1" in STT
root.swapStore.suggestedRoutesReady(txHasRouteNoApproval, "", "")
tryCompare(sellItem, "text", "1 ETH ≈ ")
tryCompare(quoteItem, "text", "1 STT ")
}
}
}

View File

@ -11,6 +11,7 @@ Button {
id: root
enum Size {
XSmall,
Tiny,
Small,
Large
@ -72,6 +73,8 @@ Button {
readonly property bool iconOnly: root.display === AbstractButton.IconOnly || root.text === ""
readonly property int iconSize: {
switch(root.size) {
case StatusBaseButton.Size.XSmall:
return 13
case StatusBaseButton.Size.Tiny:
return 16
case StatusBaseButton.Size.Small:
@ -94,6 +97,8 @@ Button {
}
if (root.icon.name) {
switch (size) {
case StatusBaseButton.Size.XSmall:
return 6
case StatusBaseButton.Size.Tiny:
return Theme.halfPadding
case StatusBaseButton.Size.Small:
@ -110,6 +115,8 @@ Button {
return isRoundIcon ? 8 : spacing
}
switch (size) {
case StatusBaseButton.Size.XSmall:
return 3
case StatusBaseButton.Size.Tiny:
return 5
case StatusBaseButton.Size.Small:

View File

@ -176,7 +176,7 @@ StatusDialog {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
text: qsTr("On:")
color: Theme.palette.baseColor1
font.pixelSize: 13
font.pixelSize: Theme.additionalTextSize
lineHeight: 38
lineHeightMode: Text.FixedHeight
verticalAlignment: Text.AlignVCenter
@ -317,6 +317,86 @@ StatusDialog {
}
}
RowLayout {
id: approximationRow
property bool inversedOrder: false
readonly property SwapInputPanel leftPanel: inversedOrder ? receivePanel : payPanel
readonly property SwapInputPanel rightPanel: inversedOrder ? payPanel : receivePanel
readonly property string lhsSymbol: leftPanel.tokenKey ?? ""
readonly property double lhsAmount: leftPanel.value
readonly property string rhsSymbol: rightPanel.tokenKey ?? ""
readonly property double rhsAmount: rightPanel.value
readonly property int rhsDecimals: rightPanel.rawValueMultiplierIndex
readonly property bool amountLoading: receivePanel.mainInputLoading || payPanel.mainInputLoading
readonly property string quote: !!lhsAmount && !!rhsAmount ? SQUtils.AmountsArithmetic.div(
SQUtils.AmountsArithmetic.fromNumber(rhsAmount),
SQUtils.AmountsArithmetic.fromNumber(lhsAmount)).toFixed(rhsDecimals) : 1
readonly property string price: root.swapAdaptor.currencyStore.getFiatValue(1, lhsSymbol)
function formatCurrency(amount, symbol) {
return root.swapAdaptor.currencyStore.formatCurrencyAmount(amount, symbol,
{ roundingMode: LocaleUtils.RoundingMode.Down, stripTrailingZeroes: true })
}
visible: root.swapAdaptor.validSwapProposalReceived || root.swapAdaptor.swapProposalLoading
spacing: 0
onVisibleChanged: inversedOrder = false // restore to default
onAmountLoadingChanged: inversedOrder = false // restore to default
StatusBaseText {
objectName: "quoteApproximationLeft"
text: "%1 ≈ ".arg(approximationRow.formatCurrency(1, approximationRow.lhsSymbol))
color: Theme.palette.directColor4
font {
weight: Font.Medium
pixelSize: Theme.additionalTextSize
}
}
StatusTextWithLoadingState {
Layout.preferredWidth: loading ? 40 : implicitWidth
objectName: "quoteApproximationRight"
text: "%1 ".arg(approximationRow.formatCurrency(approximationRow.quote, approximationRow.rhsSymbol))
customColor: Theme.palette.directColor4
font {
weight: Font.Medium
pixelSize: Theme.additionalTextSize
}
loading: approximationRow.amountLoading
}
StatusBaseText {
id: quoteApproximation
objectName: "quoteApproximationPrice"
text: "(%1)".arg(approximationRow.formatCurrency(
approximationRow.price,
root.swapAdaptor.currencyStore.currentCurrency
))
color: Theme.palette.directColor5
font {
weight: Font.Medium
pixelSize: Theme.additionalTextSize
}
visible: !approximationRow.amountLoading
}
StatusFlatButton {
objectName: "invertQuoteApproximation"
icon.name: "swap"
size: StatusBaseButton.Size.XSmall
onClicked: approximationRow.inversedOrder = !approximationRow.inversedOrder
hoverColor: "transparent"
textColor: hovered ? Theme.palette.directColor4 : Theme.palette.directColor5
visible: !approximationRow.amountLoading
}
}
EditSlippagePanel {
id: editSlippagePanel
objectName: "editSlippagePanel"