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:
parent
57585652d0
commit
7146d5af5b
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 |
Loading…
Reference in New Issue