feat: [UI - Swap] Create swap sign dialog

- create a common base for "sign" transaction popups/modals:
`SignTransactionModalBase`
- add SwapSignModal, deriving from the above common modal base
- add it to StoryBook; integration TBD as part of a different followup
PR
- add QML tests

Fixes #14785
This commit is contained in:
Lukáš Tinkl 2024-07-01 17:56:44 +02:00 committed by Lukáš Tinkl
parent 57585652d0
commit 7146d5af5b
10 changed files with 951 additions and 0 deletions

View File

@ -0,0 +1,181 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import Storybook 1.0
import Models 1.0
import AppLayouts.Wallet.popups.swap 1.0
import utils 1.0
SplitView {
id: root
Logs { id: logs }
orientation: Qt.Horizontal
property var dialog
function createAndOpenDialog() {
dialog = dlgComponent.createObject(popupBg)
dialog.open()
}
Component.onCompleted: createAndOpenDialog()
QtObject {
id: priv
readonly property var accountsModel: WalletAccountsModel {}
readonly property var selectedAccount: selectedAccountEntry.item
readonly property var networksModel: NetworksModel.flatNetworks
readonly property var selectedNetwork: selectedNetworkEntry.item
}
ModelEntry {
id: selectedAccountEntry
sourceModel: priv.accountsModel
key: "address"
value: ctrlAccount.currentValue
}
ModelEntry {
id: selectedNetworkEntry
sourceModel: priv.networksModel
key: "chainId"
value: ctrlNetwork.currentValue
}
Item {
SplitView.fillWidth: true
SplitView.fillHeight: true
PopupBackground {
id: popupBg
anchors.fill: parent
Button {
anchors.centerIn: parent
text: "Reopen"
onClicked: createAndOpenDialog()
}
Component {
id: dlgComponent
SwapSignModal {
anchors.centerIn: parent
destroyOnClose: true
modal: false
closePolicy: Popup.NoAutoClose
fromTokenSymbol: ctrlFromSymbol.text
fromTokenAmount: ctrlFromAmount.text
fromTokenContractAddress: "0x6B175474E89094C44Da98b954EedeAC495271d0F"
toTokenSymbol: ctrlToSymbol.text
toTokenAmount: ctrltoAmount.text
toTokenContractAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7"
accountName: priv.selectedAccount.name
accountAddress: priv.selectedAccount.address
accountEmoji: priv.selectedAccount.emoji
accountColor: Utils.getColorForId(priv.selectedAccount.colorId)
networkShortName: priv.selectedNetwork.shortName
networkName: priv.selectedNetwork.chainName
networkIconPath: Style.svg(priv.selectedNetwork.iconUrl)
networkBlockExplorerUrl: priv.selectedNetwork.blockExplorerUrl
currentCurrency: "EUR"
fiatFees: "1.54"
cryptoFees: "0.001"
slippage: 0.5
loginType: ctrlLoginType.currentIndex
feesLoading: ctrlLoading.checked
}
}
}
}
LogsAndControlsPanel {
SplitView.minimumWidth: 250
SplitView.preferredWidth: 250
logsView.logText: logs.logText
ColumnLayout {
Layout.fillWidth: true
TextField {
Layout.fillWidth: true
id: ctrlFromSymbol
text: "DAI"
placeholderText: "From symbol"
}
TextField {
Layout.fillWidth: true
id: ctrlFromAmount
text: "100"
placeholderText: "From amount"
}
TextField {
Layout.fillWidth: true
id: ctrlToSymbol
text: "USDT"
placeholderText: "To symbol"
}
TextField {
Layout.fillWidth: true
id: ctrltoAmount
text: "100"
placeholderText: "To amount"
}
Text {
text: "Selected Account"
}
ComboBox {
id: ctrlAccount
textRole: "name"
valueRole: "address"
model: priv.accountsModel
currentIndex: 0
}
Text {
text: "Selected Network"
}
ComboBox {
id: ctrlNetwork
textRole: "chainName"
valueRole: "chainId"
model: priv.networksModel
currentIndex: 0
}
Switch {
id: ctrlLoading
text: "Fees loading"
}
Text {
text: "Login Type"
}
ComboBox {
id: ctrlLoginType
model: Constants.authenticationIconByType
}
}
}
}
// category: Popups
// https://www.figma.com/design/TS0eQX9dAZXqZtELiwKIoK/Swap---Milestone-1?node-id=3542-497191&t=ndwmuh3ZXlycGYWa-0

View File

@ -0,0 +1,243 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtTest 1.15
import QtQml 2.15
import Models 1.0
import StatusQ.Core.Utils 0.1 as SQUtils
import AppLayouts.Wallet.popups.swap 1.0
import utils 1.0
Item {
id: root
width: 600
height: 400
Component {
id: componentUnderTest
SwapSignModal {
anchors.centerIn: parent
fromTokenSymbol: "DAI"
fromTokenAmount: "100.07"
fromTokenContractAddress: "0x6B175474E89094C44Da98b954EedeAC495271d0F"
toTokenSymbol: "USDT"
toTokenAmount: "142.07"
toTokenContractAddress: "0xdAC17F958D2ee523a2206206994597C13D831ec7"
accountName: "Hot wallet (generated)"
accountAddress: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881"
accountEmoji: "🚗"
accountColor: Utils.getColorForId(Constants.walletAccountColors.primary)
networkShortName: Constants.networkShortChainNames.mainnet
networkName: "Mainnet"
networkIconPath: Style.svg("network/Network=Ethereum")
networkBlockExplorerUrl: "https://etherscan.io/"
currentCurrency: "EUR"
fiatFees: "1.54"
cryptoFees: "0.001"
slippage: 0.2
loginType: Constants.LoginType.Password
}
}
SignalSpy {
id: signalSpyAccepted
target: controlUnderTest
signalName: "accepted"
}
SignalSpy {
id: signalSpyRejected
target: controlUnderTest
signalName: "rejected"
}
property SwapSignModal controlUnderTest: null
TestCase {
name: "SwapSignModal"
when: windowShown
function init() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
signalSpyAccepted.clear()
signalSpyRejected.clear()
}
function test_basicGeometry() {
verify(!!controlUnderTest)
verify(controlUnderTest.width > 0)
verify(controlUnderTest.height > 0)
}
function test_fromToProps() {
verify(!!controlUnderTest)
controlUnderTest.fromTokenSymbol = "SNT"
controlUnderTest.fromTokenAmount = "1000.123456789"
controlUnderTest.fromTokenContractAddress = "Oxdeadbeef"
controlUnderTest.toTokenSymbol = "ETH"
controlUnderTest.toTokenAmount = "1.42"
controlUnderTest.toTokenContractAddress = "0xdeadcaff"
// title & subtitle
compare(controlUnderTest.title, qsTr("Sign Swap"))
compare(controlUnderTest.subtitle, qsTr("1000.1235 SNT to 1.42 ETH")) // subtitle rounded amounts
// info box
const headerText = findChild(controlUnderTest.contentItem, "headerText")
verify(!!headerText)
compare(headerText.text, qsTr("Swap 1000.123456789 SNT to 1.42 ETH in %1 on %2").arg(controlUnderTest.accountName).arg(controlUnderTest.networkName))
const fromImage = findChild(controlUnderTest.contentItem, "fromImage")
verify(!!fromImage)
compare(fromImage.image.source, Constants.tokenIcon(controlUnderTest.fromTokenSymbol))
const toImage = findChild(controlUnderTest.contentItem, "toImage")
verify(!!toImage)
compare(toImage.image.source, Constants.tokenIcon(controlUnderTest.toTokenSymbol))
// pay box
const payBox = findChild(controlUnderTest.contentItem, "payBox")
verify(!!payBox)
compare(payBox.caption, qsTr("Pay"))
compare(payBox.primaryText, "%1 %2".arg(controlUnderTest.fromTokenAmount).arg(controlUnderTest.fromTokenSymbol))
compare(payBox.secondaryText, SQUtils.Utils.elideAndFormatWalletAddress(controlUnderTest.fromTokenContractAddress))
// receive box
const receiveBox = findChild(controlUnderTest.contentItem, "receiveBox")
verify(!!receiveBox)
compare(receiveBox.caption, qsTr("Receive"))
compare(receiveBox.primaryText, "%1 %2".arg(controlUnderTest.toTokenAmount).arg(controlUnderTest.toTokenSymbol))
compare(receiveBox.secondaryText, SQUtils.Utils.elideAndFormatWalletAddress(controlUnderTest.toTokenContractAddress))
}
function test_accountInfo() {
verify(!!controlUnderTest)
// account box
const accountBox = findChild(controlUnderTest.contentItem, "accountBox")
verify(!!accountBox)
compare(accountBox.caption, qsTr("In account"))
compare(accountBox.primaryText, controlUnderTest.accountName)
compare(accountBox.secondaryText, SQUtils.Utils.elideAndFormatWalletAddress(controlUnderTest.accountAddress))
compare(accountBox.asset.emoji, controlUnderTest.accountEmoji)
compare(accountBox.asset.color, controlUnderTest.accountColor)
}
function test_networkInfo() {
verify(!!controlUnderTest)
// network box
const networkBox = findChild(controlUnderTest.contentItem, "networkBox")
verify(!!networkBox)
compare(networkBox.caption, qsTr("Network"))
compare(networkBox.primaryText, controlUnderTest.networkName)
compare(networkBox.icon, controlUnderTest.networkIconPath)
}
function test_feesInfo() {
verify(!!controlUnderTest)
// network box
const feesBox = findChild(controlUnderTest.contentItem, "feesBox")
verify(!!feesBox)
compare(feesBox.caption, qsTr("Fees"))
compare(feesBox.primaryText, qsTr("Max. fees on %1").arg(controlUnderTest.networkName))
const fiatFeesText = findChild(feesBox, "fiatFeesText")
verify(!!fiatFeesText)
compare(fiatFeesText.text, "%1 %2".arg(controlUnderTest.fiatFees).arg(controlUnderTest.currentCurrency))
const cryptoFeesText = findChild(feesBox, "cryptoFeesText")
verify(!!cryptoFeesText)
compare(cryptoFeesText.text, "%1 ETH".arg(controlUnderTest.cryptoFees))
}
function test_loginType_data() {
return [
{ tag: "password", loginType: Constants.LoginType.Password, iconName: "password" },
{ tag: "touchId", loginType: Constants.LoginType.Biometrics, iconName: "touch-id" },
{ tag: "keycard", loginType: Constants.LoginType.Keycard, iconName: "keycard" }
]
}
function test_loginType(data) {
const loginType = data.loginType
const iconName = data.iconName
verify(!!controlUnderTest)
controlUnderTest.loginType = loginType
const signButton = findChild(controlUnderTest.footer, "signButton")
verify(!!signButton)
compare(signButton.icon.name, iconName)
}
function test_loading() {
verify(!!controlUnderTest)
compare(controlUnderTest.feesLoading, false)
const signButton = findChild(controlUnderTest.footer, "signButton")
verify(!!signButton)
compare(signButton.interactive, true)
const fiatFeesText = findChild(controlUnderTest.footer, "footerFiatFeesText")
verify(!!fiatFeesText)
compare(fiatFeesText.loading, false)
controlUnderTest.feesLoading = true
compare(signButton.interactive, false)
compare(fiatFeesText.loading, true)
}
function test_footerInfo() {
verify(!!controlUnderTest)
const fiatFeesText = findChild(controlUnderTest.footer, "footerFiatFeesText")
verify(!!fiatFeesText)
compare(fiatFeesText.text, "%1 %2".arg(controlUnderTest.fiatFees).arg(controlUnderTest.currentCurrency))
const maxSlippageText = findChild(controlUnderTest.footer, "footerMaxSlippageText")
verify(!!maxSlippageText)
compare(maxSlippageText.text, "%1%".arg(controlUnderTest.slippage))
}
function test_signButton() {
verify(!!controlUnderTest)
const signButton = findChild(controlUnderTest.footer, "signButton")
verify(!!signButton)
compare(signButton.interactive, true)
signButton.clicked()
compare(signalSpyAccepted.count, 1)
compare(controlUnderTest.opened, false)
compare(controlUnderTest.result, Dialog.Accepted)
}
function test_rejectButton() {
verify(!!controlUnderTest)
const rejectButton = findChild(controlUnderTest.footer, "rejectButton")
verify(!!rejectButton)
compare(rejectButton.interactive, true)
rejectButton.clicked()
compare(signalSpyRejected.count, 1)
compare(controlUnderTest.opened, false)
compare(controlUnderTest.result, Dialog.Rejected)
}
}
}

View File

@ -0,0 +1,49 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import utils 1.0
StatusFlatButton {
id: root
required property string symbol
required property string contractAddress
required property string networkName
required property string explorerName
required property string networkBlockExplorerUrl
signal openLink(string link)
icon.name: "more"
icon.color: highlighted ? Theme.palette.directColor1 : Theme.palette.directColor5
highlighted: moreMenu.opened
onClicked: moreMenu.popup(-moreMenu.width + width, height + 4)
StatusMenu {
id: moreMenu
StatusAction {
//: e.g. "View Optimism DAI contract address on Optimistic"
text: qsTr("View %1 %2 contract address on %3").arg(root.networkName).arg(root.symbol).arg(root.explorerName)
icon.name: "external-link"
onTriggered: {
var link = "%1/%2/%3".arg(root.networkBlockExplorerUrl).arg(Constants.networkExplorerLinks.addressPath).arg(root.contractAddress)
root.openLink(link)
}
}
StatusSuccessAction {
text: qsTr("Copy contract address")
successText: qsTr("Copied")
icon.name: "copy"
onTriggered: Utils.copyToClipboard(root.contractAddress)
}
}
}

View File

@ -0,0 +1,48 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import utils 1.0
ColumnLayout {
id: root
property string caption
property string primaryText
property string secondaryText
property string icon
property string badge
property alias asset: listItem.asset
property alias components: listItem.components
StatusBaseText {
text: root.caption
font.pixelSize: Style.current.additionalTextSize
}
StatusListItem {
id: listItem
Layout.fillWidth: true
Layout.preferredHeight: 76
title: root.primaryText
statusListItemTitle.font.pixelSize: Style.current.additionalTextSize
statusListItemTitle.elide: Text.ElideMiddle
subTitle: root.secondaryText
statusListItemSubTitle.font.pixelSize: Style.current.additionalTextSize
asset.name: root.icon
asset.isImage: true
border.width: 1
border.color: Theme.palette.baseColor2
sensor.enabled: false
statusListItemIcon.bridgeBadge.width: 16
statusListItemIcon.bridgeBadge.height: 16
statusListItemIcon.bridgeBadge.border.width: 1
statusListItemIcon.bridgeBadge.image.source: root.badge
statusListItemIcon.bridgeBadge.visible: !!root.badge
}
}

View File

@ -7,3 +7,5 @@ ManageCollectiblesPanel 1.0 ManageCollectiblesPanel.qml
ManageHiddenPanel 1.0 ManageHiddenPanel.qml
DAppsWorkflow 1.0 DAppsWorkflow.qml
SwapInputPanel 1.0 SwapInputPanel.qml
ContractInfoButtonWithMenu 1.0 ContractInfoButtonWithMenu.qml
SignInfoBox 1.0 SignInfoBox.qml

View File

@ -0,0 +1,178 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import StatusQ.Popups.Dialog 0.1
import shared.controls 1.0
import utils 1.0
StatusDialog {
id: root
required property int loginType // RootStore.loginType -> Constants.LoginType enum
property Component headerIconComponent
property bool feesLoading
property ObjectModel leftFooterContents
property ObjectModel rightFooterContents: ObjectModel {
RowLayout {
Layout.rightMargin: 4
spacing: Style.current.halfPadding
StatusFlatButton {
objectName: "rejectButton"
Layout.preferredHeight: signButton.height
text: qsTr("Reject")
onClicked: root.reject() // close and emit rejected() signal
}
StatusButton {
objectName: "signButton"
id: signButton
interactive: !root.feesLoading
icon.name: Constants.authenticationIconByType[root.loginType]
text: qsTr("Sign")
onClicked: root.accept() // close and emit accepted() signal
}
}
}
property color gradientColor: backgroundColor
property url fromImageSource
property url toImageSource
property alias headerMainText: headerMainText.text
property alias headerSubTextLayout: headerSubTextLayout.children
property string infoTagText
default property alias contents: contentsLayout.children
width: 480
padding: 0
function formatBigNumber(number: string, decimals = -1) {
if (!number)
return ""
const big = SQUtils.AmountsArithmetic.fromString(number)
const resultNum = decimals === -1 ? big.toFixed() : big.round(decimals).toFixed()
return resultNum.replace('.', Qt.locale().decimalPoint)
}
header: StatusDialogHeader {
visible: root.title || root.subtitle
headline.title: root.title
headline.subtitle: root.subtitle
actions.closeButton.onClicked: root.closeHandler()
leftComponent: root.headerIconComponent
}
footer: StatusDialogFooter {
dropShadowEnabled: true
leftButtons: root.leftFooterContents
rightButtons: root.rightFooterContents
}
StatusScrollView {
id: scrollView
anchors.fill: parent
contentWidth: availableWidth
topPadding: 0
bottomPadding: 0
ColumnLayout {
anchors.left: parent.left
anchors.leftMargin: 4
anchors.right: parent.right
anchors.rightMargin: 4
spacing: 0
// header box with gradient
Rectangle {
Layout.fillWidth: true
Layout.leftMargin: -parent.anchors.leftMargin - scrollView.leftPadding
Layout.rightMargin: -parent.anchors.rightMargin - scrollView.rightPadding
Layout.preferredHeight: 266 // design
gradient: Gradient {
GradientStop { position: 0.0; color: root.gradientColor }
GradientStop { position: 1.0; color: root.backgroundColor }
}
ColumnLayout {
width: 336 // by design
spacing: 12
anchors.centerIn: parent
Row {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 4
spacing: -10
StatusRoundedImage {
objectName: "fromImage"
width: 42
height: 42
border.width: 2
border.color: "transparent"
image.source: root.fromImageSource
}
StatusRoundedImage {
objectName: "toImage"
width: 42
height: 42
border.width: 2
border.color: Theme.palette.statusBadge.foregroundColor
image.source: root.toImageSource
}
}
StatusBaseText {
id: headerMainText
objectName: "headerText"
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
font.weight: Font.DemiBold
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
horizontalAlignment: Text.AlignHCenter
lineHeightMode: Text.FixedHeight
lineHeight: 22
}
RowLayout {
id: headerSubTextLayout
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
spacing: 4
}
InformationTag {
id: infoTag
Layout.alignment: Qt.AlignHCenter
asset.name: "info"
tagPrimaryLabel.text: root.infoTagText
visible: !!root.infoTagText
}
}
}
StatusDialogDivider {
Layout.fillWidth: true
Layout.bottomMargin: Style.current.bigPadding
}
ColumnLayout {
Layout.fillWidth: true
id: contentsLayout
}
}
}
}

View File

@ -7,3 +7,4 @@ AddEditSavedAddressPopup 1.0 AddEditSavedAddressPopup.qml
RemoveSavedAddressPopup 1.0 RemoveSavedAddressPopup.qml
SavedAddressActivityPopup 1.0 SavedAddressActivityPopup.qml
BuyCryptoModal 1.0 BuyCryptoModal.qml
SignTransactionModalBase 1.0 SignTransactionModalBase.qml

View File

@ -0,0 +1,245 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import AppLayouts.Wallet.panels 1.0
import AppLayouts.Wallet.popups 1.0
import utils 1.0
SignTransactionModalBase {
id: root
required property string fromTokenSymbol
required property string fromTokenAmount
required property string fromTokenContractAddress
required property string toTokenSymbol
required property string toTokenAmount
required property string toTokenContractAddress
required property string accountName
required property string accountAddress
required property string accountEmoji
required property color accountColor
required property string networkShortName // e.g. "oeth"
required property string networkName // e.g. "Optimism"
required property string networkIconPath // e.g. `Style.svg("network/Network=Optimism")`
required property string networkBlockExplorerUrl
required property string currentCurrency
required property string fiatFees
required property string cryptoFees
required property double slippage
property string serviceProviderName: "Paraswap"
property string serviceProviderURL: "https://www.paraswap.io/" // TODO https://github.com/status-im/status-desktop/issues/15329
title: qsTr("Sign Swap")
//: e.g. (swap) 100 DAI to 100 USDT
subtitle: qsTr("%1 %2 to %3 %4").arg(formatBigNumber(fromTokenAmount, 4)).arg(fromTokenSymbol) // FIXME get the correct number of decimals to display from the "symbol"
.arg(formatBigNumber(toTokenAmount, 4)).arg(toTokenSymbol)
gradientColor: Utils.setColorAlpha(root.accountColor, 0.05) // 5% of wallet color
fromImageSource: Constants.tokenIcon(root.fromTokenSymbol)
toImageSource: Constants.tokenIcon(root.toTokenSymbol)
//: e.g. "Swap 100 DAI to 100 USDT in <account name> on <network chain name>"
headerMainText: qsTr("Swap %1 %2 to %3 %4 in %5 on %6").arg(formatBigNumber(root.fromTokenAmount)).arg(root.fromTokenSymbol)
.arg(formatBigNumber(root.toTokenAmount)).arg(root.toTokenSymbol).arg(root.accountName).arg(root.networkName)
headerSubTextLayout: [
StatusBaseText {
font.pixelSize: Style.current.additionalTextSize
text: qsTr("Powered by")
},
StatusLinkText {
Layout.topMargin: 1 // compensate for the underline
text: root.serviceProviderName
normalColor: Theme.palette.directColor1
linkColor: Theme.palette.directColor1
font.weight: Font.Normal
onClicked: d.openLinkWithConfirmation(root.serviceProviderURL)
},
StatusIcon {
Layout.leftMargin: -2
width: 16
height: 16
icon: "external-link"
color: Theme.palette.directColor1
}
]
infoTagText: qsTr("Review all details before signing")
readonly property var d: QtObject {
id: d
function openLinkWithConfirmation(linkUrl) {
Global.openLinkWithConfirmation(linkUrl, SQUtils.StringUtils.extractDomainFromLink(linkUrl))
}
function getExplorerName() {
if (root.networkShortName === Constants.networkShortChainNames.arbitrum) {
return qsTr("Arbiscan")
}
if (root.networkShortName === Constants.networkShortChainNames.optimism) {
return qsTr("Optimistic")
}
return qsTr("Etherscan")
}
}
headerIconComponent: StatusSmartIdenticon {
asset.name: "filled-account"
asset.emoji: root.accountEmoji
asset.color: root.accountColor
asset.isLetterIdenticon: !!root.accountEmoji
asset.bgWidth: 40
asset.bgHeight: 40
bridgeBadge.visible: true
bridgeBadge.border.width: 2
bridgeBadge.color: Style.current.darkBlue
bridgeBadge.image.source: Style.svg("sign")
}
leftFooterContents: ObjectModel {
RowLayout {
Layout.leftMargin: 4
spacing: Style.current.bigPadding
ColumnLayout {
spacing: 2
StatusBaseText {
text: qsTr("Max fees:")
color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
}
StatusTextWithLoadingState {
objectName: "footerFiatFeesText"
text: "%1 %2".arg(formatBigNumber(root.fiatFees)).arg(root.currentCurrency)
loading: root.feesLoading
}
}
ColumnLayout {
spacing: 2
StatusBaseText {
text: qsTr("Max slippage:")
color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
}
StatusBaseText {
objectName: "footerMaxSlippageText"
text: "%1%".arg(LocaleUtils.numberToLocaleString(root.slippage))
}
}
}
}
// Pay
SignInfoBox {
Layout.fillWidth: true
Layout.bottomMargin: Style.current.bigPadding
objectName: "payBox"
caption: qsTr("Pay")
primaryText: "%1 %2".arg(formatBigNumber(root.fromTokenAmount)).arg(root.fromTokenSymbol)
secondaryText: SQUtils.Utils.elideAndFormatWalletAddress(root.fromTokenContractAddress)
icon: Constants.tokenIcon(root.fromTokenSymbol)
badge: root.networkIconPath
components: [
ContractInfoButtonWithMenu {
symbol: root.fromTokenSymbol
contractAddress: root.fromTokenContractAddress
networkName: root.networkName
explorerName: d.getExplorerName()
networkBlockExplorerUrl: root.networkBlockExplorerUrl
onOpenLink: (link) => d.openLinkWithConfirmation(link)
}
]
}
// Receive
SignInfoBox {
Layout.fillWidth: true
Layout.bottomMargin: Style.current.bigPadding
objectName: "receiveBox"
caption: qsTr("Receive")
primaryText: "%1 %2".arg(formatBigNumber(root.toTokenAmount)).arg(root.toTokenSymbol)
secondaryText: SQUtils.Utils.elideAndFormatWalletAddress(root.toTokenContractAddress)
icon: Constants.tokenIcon(root.toTokenSymbol)
badge: root.networkIconPath
components: [
ContractInfoButtonWithMenu {
symbol: root.toTokenSymbol
contractAddress: root.toTokenContractAddress
networkName: root.networkName
explorerName: d.getExplorerName()
networkBlockExplorerUrl: root.networkBlockExplorerUrl
onOpenLink: (link) => d.openLinkWithConfirmation(link)
}
]
}
// Account
SignInfoBox {
Layout.fillWidth: true
Layout.bottomMargin: Style.current.bigPadding
objectName: "accountBox"
caption: qsTr("In account")
primaryText: root.accountName
secondaryText: SQUtils.Utils.elideAndFormatWalletAddress(root.accountAddress)
asset.name: !!root.accountEmoji ? "" : "filled-account"
asset.emoji: root.accountEmoji
asset.color: root.accountColor
asset.isLetterIdenticon: !!root.accountEmoji
asset.bgWidth: 40
asset.bgHeight: 40
}
// Network
SignInfoBox {
Layout.fillWidth: true
Layout.bottomMargin: Style.current.bigPadding
objectName: "networkBox"
caption: qsTr("Network")
primaryText: root.networkName
icon: root.networkIconPath
}
// Fees
SignInfoBox {
Layout.fillWidth: true
Layout.bottomMargin: Style.current.bigPadding
objectName: "feesBox"
caption: qsTr("Fees")
primaryText: qsTr("Max. fees on %1").arg(root.networkName)
secondaryText: " "
components: [
ColumnLayout {
spacing: 2
StatusBaseText {
objectName: "fiatFeesText"
Layout.alignment: Qt.AlignRight
text: "%1 %2".arg(formatBigNumber(root.fiatFees)).arg(root.currentCurrency)
horizontalAlignment: Text.AlignRight
font.pixelSize: Style.current.additionalTextSize
}
StatusBaseText {
objectName: "cryptoFeesText"
Layout.alignment: Qt.AlignRight
text: "%1 ETH".arg(formatBigNumber(root.cryptoFees))
horizontalAlignment: Text.AlignRight
font.pixelSize: Style.current.additionalTextSize
color: Theme.palette.baseColor1
}
}
]
}
}

View File

@ -5,3 +5,4 @@ SwapOutputData 1.0 SwapOutputData.qml
SwapSignApprovePopup 1.0 SwapSignApprovePopup.qml
SwapSignApproveInputForm 1.0 SwapSignApproveInputForm.qml
SwapSignApproveAdaptor 1.0 SwapSignApproveAdaptor.qml
SwapSignModal 1.0 SwapSignModal.qml

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.94122 13.5754C8.81123 13.0164 7.49212 12.006 6.63284 10.8064C5.28198 8.92063 4.88556 6.61534 6.75652 5.22758C8.15856 4.18762 10.5029 5.48584 11.3944 7.49844C12.5823 10.1801 12.3839 14.3158 10.1731 17.8114C9.06164 19.5688 7.34882 19.3917 6.75652 18.3817C5.93885 16.9874 6.78632 13.9372 10.6765 11.9922C14.072 10.2944 15.0228 11.1546 14.8265 12.0758C14.7572 12.401 13.9916 13.73 15.012 13.73C15.8915 13.73 16.6483 12.6413 17.3619 12.0758C18.271 11.3554 19.217 11.1328 19.1023 12.4889C19.0595 12.9958 19.2789 14.0856 20.5157 13.4672" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 705 B