status-desktop/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml

341 lines
14 KiB
QML
Raw Normal View History

import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Popups.Dialog 0.1
import StatusQ.Controls 0.1
import shared.popups.send.controls 1.0
import shared.controls 1.0
import AppLayouts.Wallet.controls 1.0
import AppLayouts.Wallet.panels 1.0
StatusDialog {
id: root
/* This should be the only property which should be used to input
parameters to the modal when being launched from elsewhere */
required property SwapInputParamsForm swapInputParamsForm
required property SwapModalAdaptor swapAdaptor
objectName: "swapModal"
implicitWidth: 556
topPadding: 0
bottomPadding: Style.current.xlPadding
leftPadding: Style.current.xlPadding
rightPadding: Style.current.xlPadding
backgroundColor: Theme.palette.baseColor3
QtObject {
id: d
property var debounceFetchSuggestedRoutes: Backpressure.debounce(root, 1000, function() {
root.swapAdaptor.fetchSuggestedRoutes(payPanel.rawValue)
})
function fetchSuggestedRoutes() {
if (payPanel.valueValid) {
root.swapAdaptor.newFetchReset()
root.swapAdaptor.swapProposalLoading = true
debounceFetchSuggestedRoutes()
}
}
}
Connections {
target: root.swapInputParamsForm
function onFormValuesChanged() {
d.fetchSuggestedRoutes()
}
// refresh the selected asset in payPanel when account/network changes
function onSelectedAccountAddressChanged() {
payPanel.reevaluateSelectedId()
}
function onSelectedNetworkChainIdChanged() {
payPanel.reevaluateSelectedId()
}
}
Behavior on implicitHeight {
NumberAnimation { duration: 1000; easing.type: Easing.OutExpo; alwaysRunToEnd: true}
}
onClosed: root.swapAdaptor.reset()
header: AccountsModalHeader {
anchors.top: parent.top
anchors.topMargin: -height - 18
control.popup.width: 512
model: root.swapAdaptor.nonWatchAccounts
getNetworkShortNames: root.swapAdaptor.getNetworkShortNames
formatCurrencyAmount: root.swapAdaptor.formatCurrencyAmount
/* TODO: once the Account Header is reworked we simply should be
able to use an index and not this logic of selectedAccount being set */
selectedAccount: root.swapAdaptor.getSelectedAccountByAddress(root.swapInputParamsForm.selectedAccountAddress)
onSelectedIndexChanged: {
root.swapInputParamsForm.selectedAccountAddress = root.swapAdaptor.getSelectedAccountAddressByIndex(selectedIndex)
}
}
contentItem: ColumnLayout {
spacing: Style.current.padding
clip: true
// without this Column, the whole popup resizing when the network selector popup is clicked
Column {
Layout.fillWidth: true
spacing: 0
RowLayout {
width: parent.width
spacing: 12
HeaderTitleText {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
id: modalHeader
text: qsTr("Swap")
}
StatusBaseText {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
text: qsTr("On:")
color: Theme.palette.baseColor1
font.pixelSize: 13
lineHeight: 38
lineHeightMode: Text.FixedHeight
verticalAlignment: Text.AlignVCenter
}
// TODO: update this once https://github.com/status-im/status-desktop/issues/14780 is ready
NetworkFilter {
id: networkFilter
objectName: "networkFilter"
Layout.alignment: Qt.AlignVCenter
multiSelection: false
showRadioButtons: false
showTitle: false
flatNetworks: root.swapAdaptor.filteredFlatNetworksModel
onToggleNetwork: (network) => {
root.swapInputParamsForm.selectedNetworkChainId = network.chainId
}
Component.onCompleted: {
if(root.swapInputParamsForm.selectedNetworkChainId !== -1)
networkFilter.setChain(root.swapInputParamsForm.selectedNetworkChainId)
}
}
}
}
Item {
Layout.fillWidth: true
Layout.topMargin: 2
Layout.preferredHeight: payPanel.height + receivePanel.height + 4
SwapInputPanel {
id: payPanel
objectName: "payPanel"
anchors {
left: parent.left
right: parent.right
top: parent.top
}
currencyStore: root.swapAdaptor.currencyStore
flatNetworksModel: root.swapAdaptor.filteredFlatNetworksModel
processedAssetsModel: root.swapAdaptor.processedAssetsModel
tokenKey: root.swapInputParamsForm.fromTokensKey
tokenAmount: {
// Only update if there is different in amount displayed
if (root.swapInputParamsForm.fromTokenAmount !==
SQUtils.AmountsArithmetic.fromString(value).toLocaleString(locale, 'f', -128)){
return root.swapInputParamsForm.fromTokenAmount
}
return payPanel.tokenAmount
}
swapSide: SwapInputPanel.SwapSide.Pay
swapExchangeButtonWidth: swapButton.width
onSelectedHoldingIdChanged: root.swapInputParamsForm.fromTokensKey = selectedHoldingId
onValueChanged: root.swapInputParamsForm.fromTokenAmount = value.toLocaleString(locale, 'f', -128)
onValueValidChanged: d.fetchSuggestedRoutes()
}
SwapInputPanel {
id: receivePanel
objectName: "receivePanel"
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
currencyStore: root.swapAdaptor.currencyStore
flatNetworksModel: root.swapAdaptorfilteredFlatNetworksModel
processedAssetsModel: root.swapAdaptor.processedAssetsModel
tokenKey: root.swapInputParamsForm.toTokenKey
tokenAmount: root.swapAdaptor.validSwapProposalReceived && root.swapAdaptor.toToken ? root.swapAdaptor.swapOutputData.toTokenAmount: root.swapInputParamsForm.toTokenAmount
swapSide: SwapInputPanel.SwapSide.Receive
swapExchangeButtonWidth: swapButton.width
mainInputLoading: root.swapAdaptor.swapProposalLoading
bottomTextLoading: root.swapAdaptor.swapProposalLoading
onSelectedHoldingIdChanged: root.swapInputParamsForm.toTokenKey = selectedHoldingId
/* TODO: keep this input as disabled until the work for adding a param to handle to
and from tokens inputed is supported by backend under
https://github.com/status-im/status-desktop/issues/15095 */
interactive: false
}
SwapExchangeButton {
id: swapButton
anchors.centerIn: parent
}
}
/* TODO: remove! Needed only till sign after approval is implemented under
https://github.com/status-im/status-desktop/issues/14833 */
StatusButton {
text: "Final Swap after Approval"
visible: root.swapAdaptor.validSwapProposalReceived && root.swapAdaptor.swapOutputData.approvalNeeded
onClicked: {
swapAdaptor.sendSwapTx()
close()
}
}
EditSlippagePanel {
id: editSlippagePanel
objectName: "editSlippagePanel"
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
visible: editSlippageButton.checked
selectedToToken: root.swapAdaptor.toToken
toTokenAmount: root.swapAdaptor.swapOutputData.toTokenAmount
loading: root.swapAdaptor.swapProposalLoading
onSlippageValueChanged: {
root.swapInputParamsForm.selectedSlippage = slippageValue
}
}
ErrorTag {
objectName: "errorTag"
visible: root.swapAdaptor.swapOutputData.hasError || payPanel.amountEnteredGreaterThanBalance
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Style.current.smallPadding
text: {
if (payPanel.amountEnteredGreaterThanBalance) {
return qsTr("Insufficient funds for swap")
}
return qsTr("An error has occured, please try again")
}
buttonText: qsTr("Buy crypto")
buttonVisible: payPanel.amountEnteredGreaterThanBalance
onButtonClicked: Global.openBuyCryptoModalRequested()
}
}
footer: StatusDialogFooter {
color: Theme.palette.baseColor3
dropShadowEnabled: true
leftButtons: ObjectModel {
ColumnLayout {
Layout.leftMargin: Style.current.padding
spacing: 0
StatusBaseText {
objectName: "maxSlippageText"
text: qsTr("Max slippage:")
color: Theme.palette.directColor5
font.pixelSize: 15
font.weight: Font.Medium
}
RowLayout {
StatusBaseText {
objectName: "maxSlippageValue"
text: "%1%".arg(LocaleUtils.numberToLocaleString(root.swapInputParamsForm.selectedSlippage))
color: Theme.palette.directColor4
font.pixelSize: 15
font.weight: Font.Medium
}
StatusFlatButton {
id: editSlippageButton
objectName: "editSlippageButton"
checkable: true
checked: false
icon.name: "edit_pencil"
textColor: editSlippageButton.hovered ? Theme.palette.directColor1 : Theme.palette.directColor5
size: StatusBaseButton.Size.Tiny
hoverColor: Theme.palette.transparent
visible: !checked
}
}
}
}
rightButtons: ObjectModel {
RowLayout {
Layout.rightMargin: Style.current.padding
spacing: Style.current.bigPadding
ColumnLayout {
StatusBaseText {
objectName: "maxFeesText"
text: qsTr("Max fees:")
color: Theme.palette.directColor5
font.pixelSize: 15
font.weight: Font.Medium
}
StatusTextWithLoadingState {
objectName: "maxFeesValue"
text: loading ? Constants.dummyText :
root.swapAdaptor.validSwapProposalReceived ?
root.swapAdaptor.currencyStore.formatCurrencyAmount(
root.swapAdaptor.swapOutputData.totalFees,
root.swapAdaptor.currencyStore.currentCurrency) :
"--"
customColor: Theme.palette.directColor4
font.pixelSize: 15
font.weight: Font.Medium
loading: root.swapAdaptor.swapProposalLoading
}
}
StatusButton {
objectName: "signButton"
icon.name: "password"
/* TODO: Handling the next step agter approval of spending cap TBD under
https://github.com/status-im/status-desktop/issues/14833 */
text: root.swapAdaptor.validSwapProposalReceived &&
root.swapAdaptor.swapOutputData.approvalNeeded ?
qsTr("Approve %1").arg(!!root.swapAdaptor.fromToken ? root.swapAdaptor.fromToken.symbol: "") :
qsTr("Swap")
disabledColor: Theme.palette.directColor8
enabled: root.swapAdaptor.validSwapProposalReceived &&
editSlippagePanel.valid &&
!payPanel.amountEnteredGreaterThanBalance
onClicked: {
if (root.swapAdaptor.validSwapProposalReceived ){
if(root.swapAdaptor.swapOutputData.approvalNeeded) {
swapAdaptor.sendApproveTx()
}
else {
swapAdaptor.sendSwapTx()
close()
}
}
}
}
}
}
}
}