chore(AmountToSend): AmountToSendNew is dead, long live AmountToSend

This commit is contained in:
Lukáš Tinkl 2024-08-14 23:56:48 +02:00 committed by Lukáš Tinkl
parent 114abc7015
commit 7c10b16b67
8 changed files with 359 additions and 693 deletions

View File

@ -1,159 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.settings 1.0
import shared.popups.send.views 1.0
SplitView {
orientation: Qt.Vertical
SplitView.fillWidth: true
Item {
SplitView.fillWidth: true
SplitView.fillHeight: true
AmountToSendNew {
id: amountToSend
anchors.centerIn: parent
interactive: interactiveCheckBox.checked
fiatInputInteractive: fiatInteractiveCheckBox.checked
markAsInvalid: markAsInvalidCheckBox.checked
mainInputLoading: ctrlMainInputLoading.checked
bottomTextLoading: ctrlBottomTextLoading.checked
caption: "Amount to send"
decimalPoint: decimalPointRadioButton.checked ? "." : ","
price: parseFloat(priceTextField.text)
multiplierIndex: multiplierIndexSpinBox.value
formatFiat: balance => `${balance.toLocaleString(Qt.locale())} USD`
formatBalance: balance => `${balance.toLocaleString(Qt.locale())} ETH`
}
}
Pane {
id: logsAndControlsPanel
SplitView.minimumHeight: 350
ColumnLayout {
spacing: 15
RowLayout {
Label {
text: "Price"
}
TextField {
id: priceTextField
text: "812.323"
}
}
RowLayout {
Label {
text: "Decimal point"
}
RadioButton {
id: decimalPointRadioButton
text: "."
}
RadioButton {
text: ","
checked: true
}
}
RowLayout {
Label {
text: "Multiplier index"
}
SpinBox {
id: multiplierIndexSpinBox
editable: true
value: 18
to: 30
}
}
RowLayout {
CheckBox {
id: interactiveCheckBox
text: "Interactive"
checked: true
}
CheckBox {
id: fiatInteractiveCheckBox
text: "Fiat mode interactive"
checked: true
}
CheckBox {
id: markAsInvalidCheckBox
text: "Mark as invalid"
}
CheckBox {
id: ctrlMainInputLoading
text: "Input loading"
}
CheckBox {
id: ctrlBottomTextLoading
text: "Bottom text loading"
}
}
Label {
font.bold: true
text: `fiat mode: ${amountToSend.fiatMode}, ` +
`valid: ${amountToSend.valid}, ` +
`empty: ${amountToSend.empty}, ` +
`amount: ${amountToSend.amount}`
}
RowLayout {
Label {
text: `Set value`
}
TextField {
id: amountTextField
text: "0.0012"
}
Button {
text: "SET"
onClicked: {
amountToSend.setValue(amountTextField.text)
}
}
}
}
}
Settings {
property alias multiplier: multiplierIndexSpinBox.value
}
}
// category: Components

View File

@ -2,100 +2,158 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import StatusQ.Core 0.1 import Qt.labs.settings 1.1
import shared.popups.send.views 1.0 import shared.popups.send.views 1.0
import Storybook 1.0
import Models 1.0
SplitView { SplitView {
id: root orientation: Qt.Vertical
SplitView.fillWidth: true
readonly property var tokensBySymbolModel: TokensBySymbolModel {} Item {
readonly property double maxCryptoBalance: parseFloat(maxCryptoBalanceText.text)
readonly property int decimals: parseInt(decimalsText.text)
Logs { id: logs }
Component.onCompleted: amountToSendInput.input.forceActiveFocus()
SplitView {
orientation: Qt.Vertical
SplitView.fillWidth: true SplitView.fillWidth: true
SplitView.fillHeight: true
Item { AmountToSend {
SplitView.fillWidth: true id: amountToSend
SplitView.fillHeight: true
AmountToSend { anchors.centerIn: parent
id: amountToSendInput
isBridgeTx: false
interactive: true
selectedHolding: tokensBySymbolModel.data[0]
inputIsFiat: fiatInput.checked interactive: interactiveCheckBox.checked
fiatInputInteractive: fiatInteractiveCheckBox.checked
markAsInvalid: markAsInvalidCheckBox.checked
maxInputBalance: inputIsFiat ? root.maxCryptoBalance*amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount mainInputLoading: ctrlMainInputLoading.checked
: root.maxCryptoBalance bottomTextLoading: ctrlBottomTextLoading.checked
currentCurrency: "USD"
formatCurrencyAmount: function(amount, symbol, options, locale) { caption: "Amount to send"
const currencyAmount = {
amount: amount, decimalPoint: decimalPointRadioButton.checked ? "." : ","
symbol: symbol, price: parseFloat(priceTextField.text)
displayDecimals: root.decimals,
stripTrailingZeroes: true multiplierIndex: multiplierIndexSpinBox.value
}
return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale) formatFiat: balance => `${balance.toLocaleString(Qt.locale())} USD`
formatBalance: balance => `${balance.toLocaleString(Qt.locale())} ETH`
}
}
Pane {
id: logsAndControlsPanel
SplitView.minimumHeight: 350
ColumnLayout {
spacing: 15
RowLayout {
Label {
text: "Price"
} }
onReCalculateSuggestedRoute: function() {
logs.logEvent("onReCalculateSuggestedRoute") TextField {
id: priceTextField
text: "812.323"
} }
} }
}
LogsAndControlsPanel { RowLayout {
id: logsAndControlsPanel
SplitView.minimumHeight: 250
logsView.logText: logs.logText
ColumnLayout {
Label { Label {
Layout.topMargin: 10 text: "Decimal point"
Layout.fillWidth: true
text: "Max Crypto Balance"
} }
TextField { RadioButton {
id: maxCryptoBalanceText id: decimalPointRadioButton
background: Rectangle { border.color: 'lightgrey' }
Layout.preferredWidth: 200 text: "."
text: "1000000"
} }
RadioButton {
text: ","
checked: true
}
}
RowLayout {
Label { Label {
Layout.topMargin: 10 text: "Multiplier index"
Layout.fillWidth: true
text: "Decimals"
} }
TextField { SpinBox {
id: decimalsText id: multiplierIndexSpinBox
background: Rectangle { border.color: 'lightgrey' }
Layout.preferredWidth: 200 editable: true
text: "6" value: 18
to: 30
}
}
RowLayout {
CheckBox {
id: interactiveCheckBox
text: "Interactive"
checked: true
} }
CheckBox { CheckBox {
id: fiatInput id: fiatInteractiveCheckBox
text: "Fiat input value" text: "Fiat mode interactive"
checked: true
}
CheckBox {
id: markAsInvalidCheckBox
text: "Mark as invalid"
}
CheckBox {
id: ctrlMainInputLoading
text: "Input loading"
}
CheckBox {
id: ctrlBottomTextLoading
text: "Bottom text loading"
}
}
Label {
font.bold: true
text: `fiat mode: ${amountToSend.fiatMode}, ` +
`valid: ${amountToSend.valid}, ` +
`empty: ${amountToSend.empty}, ` +
`amount: ${amountToSend.amount}`
}
RowLayout {
Label {
text: `Set value`
}
TextField {
id: amountTextField
text: "0.0012"
}
Button {
text: "SET"
onClicked: {
amountToSend.setValue(amountTextField.text)
}
} }
} }
} }
} }
Settings {
property alias multiplier: multiplierIndexSpinBox.value
}
} }
// category: Components // category: Components

View File

@ -13,10 +13,10 @@ Item {
Component { Component {
id: componentUnderTest id: componentUnderTest
AmountToSendNew {} AmountToSend {}
} }
property AmountToSendNew amountToSend property AmountToSend amountToSend
SignalSpy { SignalSpy {
id: amountChangedSpy id: amountChangedSpy
@ -25,7 +25,7 @@ Item {
} }
TestCase { TestCase {
name: "AmountToSendNew" name: "AmountToSend"
when: windowShown when: windowShown
function type(key, times = 1) { function type(key, times = 1) {

View File

@ -213,7 +213,7 @@ Control {
Layout.preferredWidth: parent.width*.66 Layout.preferredWidth: parent.width*.66
Layout.fillHeight: true Layout.fillHeight: true
AmountToSendNew { AmountToSend {
readonly property bool balanceExceeded: readonly property bool balanceExceeded:
SQUtils.AmountsArithmetic.fromNumber(maxSendButton.maxSafeCryptoValue, multiplierIndex).cmp(amount) === -1 SQUtils.AmountsArithmetic.fromNumber(maxSendButton.maxSafeCryptoValue, multiplierIndex).cmp(amount) === -1

View File

@ -480,7 +480,7 @@ StatusDialog {
RowLayout { RowLayout {
visible: d.isSelectedHoldingValidAsset && !d.isCollectiblesTransfer visible: d.isSelectedHoldingValidAsset && !d.isCollectiblesTransfer
AmountToSendNew { AmountToSend {
id: amountToSend id: amountToSend
caption: d.isBridgeTx ? qsTr("Amount to bridge") caption: d.isBridgeTx ? qsTr("Amount to bridge")

View File

@ -1,232 +1,284 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1 as SQUtils import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Controls.Validators 0.1 import StatusQ.Validators 0.1
import "../controls"
import utils 1.0 import utils 1.0
import shared.controls 1.0
ColumnLayout { Control {
id: root id: root
readonly property alias input: topAmountToSendInput /* Crypto value in a base unit as a string integer, e.g. 1000000000000000000
readonly property bool inputNumberValid: !!input.text && !isNaN(d.parsedInput) && input.valid * for 1 ETH */
readonly property alias amount: d.amountBaseUnit
readonly property int minSendCryptoDecimals: /* In fiat mode the input value is meant to be a fiat value, conversely,
!inputIsFiat ? LocaleUtils.fractionalPartLength(d.inputNumber) : 0 * crypto value otherwise. */
readonly property int minReceiveCryptoDecimals: readonly property alias fiatMode: d.fiatMode
!inputIsFiat ? minSendCryptoDecimals + 1 : 0
readonly property int minSendFiatDecimals:
inputIsFiat ? LocaleUtils.fractionalPartLength(d.inputNumber) : 0
readonly property int minReceiveFiatDecimals:
inputIsFiat ? minSendFiatDecimals + 1 : 0
property var selectedHolding // Crypto asset symbol like ETH /* Indicates whether toggling the fiatMode is enabled for the user */
property string currentCurrency // Fiat currency symbol like USD property bool fiatInputInteractive: interactive
property int multiplierIndex // How divisible the token is, 18 for ETH /* Indicates if input represent valid number. E.g. empty input or containing
* only decimal point is not valid. */
readonly property alias valid: textField.acceptableInput
readonly property bool empty: textField.length === 0
property double maxInputBalance // TODO: remove, temporarily for backward compatibility. External components
// should not rely on formatted amount because formatting rules are internal
// detail of that component.
readonly property alias text: textField.text
property bool isBridgeTx: false /* Decimal point character to be displayed. Both "." and "," will be
property bool interactive: false * replaced by the provided decimal point on the fly */
property bool inputIsFiat: false property alias decimalPoint: validator.decimalPoint
property string caption: isBridgeTx ? qsTr("Amount to bridge") : qsTr("Amount to send") /* Number of fiat decimal places used to limit allowed decimal places in
* fiatMode */
property int fiatDecimalPlaces: 2
property bool fiatInputInteractive: true /* Specifies how divisible given cryptocurrency is, e.g. 18 for ETH. Used
* for limiting allowed decimal places and computing final amount as an
* integer value */
property int multiplierIndex: 18
// Crypto value to send expressed in base units (like wei for ETH), /* Price of one unit of given cryptocurrency (e.g. price for 1 ETH) */
// as a string representing integer decimal property real price: 1.0
readonly property alias cryptoValueToSend: d.cryptoValueRawToSend
readonly property alias cryptoValueToSendFloat: d.cryptoValueToSend property alias caption: captionText.text
property bool interactive: true
property var formatCurrencyAmount: readonly property bool cursorVisible: textField.cursorVisible
(amount, symbol, options = null, locale = null) => {} readonly property alias placeholderText: textField.placeholderText
/* Loading states for the input and text below */
property bool mainInputLoading property bool mainInputLoading
property bool bottomTextLoading property bool bottomTextLoading
signal reCalculateSuggestedRoute() /* Allows mark input as invalid when it's valid number but doesn't satisfy
* arbitrary external criteria, e.g. is higher than maximum expected value. */
property bool markAsInvalid: false
/* Methods for formatting crypto and fiat value expecting double values,
e.g. 1.0 for 1 ETH or 1.0 for 1 USD. */
property var formatFiat: balance =>
`${balance.toLocaleString(Qt.locale())} FIAT`
property var formatBalance: balance =>
`${balance.toLocaleString(Qt.locale())} CRYPTO`
/* Allows to set value to be displayed. The value is expected to be a not
localized string like "1", "1.1" or "0.000000023400234222". Provided
value will be formatted and displayed. Depending on the fiatMode flag
it will affect output amount appropriately. */
function setValue(valueString) {
if (!valueString)
valueString = "0"
const decimalPlaces = d.fiatMode ? root.fiatDecimalPlaces
: root.multiplierIndex
const stringNumber = SQUtils.AmountsArithmetic.fromString(
valueString).toFixed(decimalPlaces)
const trimmed = d.fiatMode
? stringNumber
: d.removeDecimalTrailingZeros(stringNumber)
textField.text = d.localize(trimmed)
}
function clear() {
textField.clear()
}
function forceActiveFocus() {
textField.forceActiveFocus()
}
QtObject { QtObject {
id: d id: d
property double cryptoValueToSend property bool fiatMode: false
property double fiatValueToSend
Binding on cryptoValueToSend { readonly property string inputDelocalized:
value: { root.valid && textField.length !== 0
root.selectedHolding ? textField.text.replace(root.decimalPoint, ".") : "0"
if(!root.selectedHolding || !root.selectedHolding.marketDetails || !root.selectedHolding.marketDetails.currencyPrice) {
return 0 function removeDecimalTrailingZeros(num) {
} if (!num.includes("."))
return root.inputIsFiat ? d.fiatValueToSend/root.selectedHolding.marketDetails.currencyPrice.amount return num
: d.inputNumber
} return num.replace(/\.?0*$/g, "")
delayed: true
} }
Binding on fiatValueToSend { function localize(num) {
value: { return num.replace(".", root.decimalPoint)
root.selectedHolding
if(!root.selectedHolding || !root.selectedHolding.marketDetails || !root.selectedHolding.marketDetails.currencyPrice) {
return 0
}
return root.inputIsFiat ? d.inputNumber
: d.cryptoValueToSend * root.selectedHolding.marketDetails.currencyPrice.amount
}
delayed: true
} }
readonly property string selectedSymbol: !!root.selectedHolding && !!root.selectedHolding.symbol ? root.selectedHolding.symbol: "" readonly property string amountBaseUnit: {
if (d.fiatMode)
return secondaryValue
readonly property string cryptoValueRawToSend: { const multiplier = SQUtils.AmountsArithmetic.fromExponent(
return SQUtils.AmountsArithmetic.fromNumber( root.multiplierIndex)
d.cryptoValueToSend, root.multiplierIndex).toString()
return SQUtils.AmountsArithmetic.times(
SQUtils.AmountsArithmetic.fromString(inputDelocalized),
multiplier).toFixed()
} }
// Crypto value should be represented by 0 and fiat with 0.00 readonly property string secondaryValue: {
readonly property string zeroString: { const price = isNaN(root.price) ? 0 : root.price
let decimals = root.inputIsFiat ? 2 : 0
LocaleUtils.numberToLocaleString(0, decimals, topAmountToSendInput.locale)
}
readonly property double parsedInput: if (!d.fiatMode)
LocaleUtils.numberFromLocaleString(topAmountToSendInput.text, return SQUtils.AmountsArithmetic.times(
topAmountToSendInput.locale) SQUtils.AmountsArithmetic.fromString(inputDelocalized),
SQUtils.AmountsArithmetic.fromNumber(
price * (10 ** root.fiatDecimalPlaces))).toFixed()
readonly property double inputNumber: if (!price) // prevent div by zero below
// we should still calculate if value entered is greater than max safe value return 0
!!input.text && !isNaN(d.parsedInput) && d.parsedInput >= 0 ? d.parsedInput : 0
readonly property Timer waitTimer: Timer { const multiplier = SQUtils.AmountsArithmetic.fromExponent(
interval: 1000 root.multiplierIndex)
onTriggered: reCalculateSuggestedRoute()
return SQUtils.AmountsArithmetic.div(
SQUtils.AmountsArithmetic.times(
SQUtils.AmountsArithmetic.fromString(inputDelocalized),
multiplier),
SQUtils.AmountsArithmetic.fromNumber(price)).toFixed()
} }
} }
onMaxInputBalanceChanged: { contentItem: ColumnLayout {
input.validate() StatusBaseText {
} id: captionText
onSelectedHoldingChanged: {
input.validate()
}
StatusBaseText {
text: root.caption
font.pixelSize: 13
lineHeight: 18
lineHeightMode: Text.FixedHeight
color: Theme.palette.directColor1
}
RowLayout {
Layout.fillWidth: true
id: topItem
AmountInputWithCursor {
id: topAmountToSendInput
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: 250
Layout.preferredWidth: !!text ? input.edit.paintedWidth + 2
: textMetrics.advanceWidth
placeholderText: d.zeroString
input.edit.color: input.valid ? Theme.palette.directColor1
: Theme.palette.dangerColor1
input.edit.readOnly: !root.interactive
validationMode: StatusInput.ValidationMode.Always
validators: [
StatusValidator {
errorMessage: ""
validate: (text) => { visible: text.length > 0
const num = LocaleUtils.numberFromLocaleString(topAmountToSendInput.text,
topAmountToSendInput.locale)
if (isNaN(num) || num <= 0 || num > root.maxInputBalance) {
return false
}
if(!root.selectedHolding || !root.selectedHolding.marketDetails || !root.selectedHolding.marketDetails.currencyPrice) {
return false
}
const cryptoValueToSend = root.inputIsFiat ? num / root.selectedHolding.marketDetails.currencyPrice.amount : num
const cryptoValueToSendRaw = SQUtils.AmountsArithmetic.fromNumber(cryptoValueToSend, root.multiplierIndex).toString()
return cryptoValueToSendRaw >= 1
}
}
]
TextMetrics { font.pixelSize: 13
id: textMetrics lineHeight: 18
text: topAmountToSendInput.placeholderText lineHeightMode: Text.FixedHeight
font: topAmountToSendInput.placeholderFont color: Theme.palette.directColor1
} elide: Text.ElideRight
Keys.onReleased: {
const amount = LocaleUtils.numberFromLocaleString(
topAmountToSendInput.text,
locale)
if (!isNaN(amount))
d.waitTimer.restart()
}
visible: !root.mainInputLoading
} }
RowLayout {
StyledTextField {
id: textField
objectName: "amountToSend_textField"
Layout.fillWidth: true
implicitHeight: 44
padding: 0
background: null
readOnly: !root.interactive
color: text.length === 0 || (root.valid && !root.markAsInvalid)
? Theme.palette.directColor1
: Theme.palette.dangerColor1
placeholderText: {
if (!d.fiatMode || root.fiatDecimalPlaces === 0)
return "0"
return "0" + root.decimalPoint
+ "0".repeat(root.fiatDecimalPlaces)
}
font.pixelSize: Utils.getFontSizeBasedOnLetterCount(text)
validator: AmountValidator {
id: validator
maxDecimalDigits: d.fiatMode ? root.fiatDecimalPlaces
: root.multiplierIndex
locale: root.locale.name
}
visible: !root.mainInputLoading
}
LoadingComponent {
objectName: "topAmountToSendInputLoadingComponent"
Layout.preferredWidth: textField.width
Layout.preferredHeight: textField.height
visible: root.mainInputLoading
}
}
StatusBaseText {
id: bottomItem
objectName: "bottomItemText"
Layout.fillWidth: true
text: {
const divisor = SQUtils.AmountsArithmetic.fromExponent(
d.fiatMode ? root.multiplierIndex
: root.fiatDecimalPlaces)
const divided = SQUtils.AmountsArithmetic.div(
SQUtils.AmountsArithmetic.fromString(
d.secondaryValue), divisor)
const asNumber = SQUtils.AmountsArithmetic.toNumber(divided)
return d.fiatMode ? root.formatBalance(asNumber)
: root.formatFiat(asNumber)
}
elide: Text.ElideMiddle
font.pixelSize: 13
color: Theme.palette.directColor5
MouseArea {
objectName: "amountToSend_mouseArea"
anchors.fill: parent
cursorShape: enabled ? Qt.PointingHandCursor : undefined
enabled: root.fiatInputInteractive
onClicked: {
const secondaryValue = d.secondaryValue
d.fiatMode = !d.fiatMode
if (textField.length === 0)
return
const decimalPlaces = d.fiatMode ? root.fiatDecimalPlaces
: root.multiplierIndex
const divisor = SQUtils.AmountsArithmetic.fromExponent(
decimalPlaces)
const stringNumber = SQUtils.AmountsArithmetic.div(
SQUtils.AmountsArithmetic.fromString(secondaryValue),
divisor).toFixed(decimalPlaces)
const trimmed = d.fiatMode
? stringNumber
: d.removeDecimalTrailingZeros(stringNumber)
textField.text = d.localize(trimmed)
}
}
visible: !root.bottomTextLoading
}
LoadingComponent { LoadingComponent {
objectName: "topAmountToSendInputLoadingComponent" objectName: "bottomItemTextLoadingComponent"
Layout.preferredWidth: topAmountToSendInput.width Layout.preferredWidth: bottomItem.width
Layout.preferredHeight: topAmountToSendInput.height Layout.preferredHeight: bottomItem.height
visible: root.mainInputLoading visible: root.bottomTextLoading
} }
} }
StatusBaseText {
Layout.maximumWidth: parent.width
id: bottomItem
objectName: "bottomItemText"
readonly property double bottomAmountToSend: inputIsFiat ? d.cryptoValueToSend
: d.fiatValueToSend
readonly property string bottomAmountSymbol: inputIsFiat ? d.selectedSymbol
: root.currentCurrency
elide: Text.ElideMiddle
text: root.formatCurrencyAmount(bottomAmountToSend, bottomAmountSymbol)
font.pixelSize: 13
color: Theme.palette.directColor5
MouseArea {
anchors.fill: parent
cursorShape: enabled ? Qt.PointingHandCursor : undefined
enabled: root.fiatInputInteractive && !!root.selectedHolding
onClicked: {
topAmountToSendInput.validate()
if(!!topAmountToSendInput.text) {
topAmountToSendInput.text = root.formatCurrencyAmount(
bottomItem.bottomAmountToSend,
bottomItem.bottomAmountSymbol,
{ noSymbol: true, rawAmount: true },
topAmountToSendInput.locale)
}
root.inputIsFiat = !root.inputIsFiat
d.waitTimer.restart()
}
}
visible: !root.bottomTextLoading
}
LoadingComponent {
objectName: "bottomItemTextLoadingComponent"
Layout.preferredWidth: bottomItem.width
Layout.preferredHeight: bottomItem.height
visible: root.bottomTextLoading
}
} }

View File

@ -1,284 +0,0 @@
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.Core.Utils 0.1 as SQUtils
import StatusQ.Components 0.1
import StatusQ.Validators 0.1
import utils 1.0
import shared.controls 1.0
Control {
id: root
/* Crypto value in a base unit as a string integer, e.g. 1000000000000000000
* for 1 ETH */
readonly property alias amount: d.amountBaseUnit
/* In fiat mode the input value is meant to be a fiat value, conversely,
* crypto value otherwise. */
readonly property alias fiatMode: d.fiatMode
/* Indicates whether toggling the fiatMode is enabled for the user */
property bool fiatInputInteractive: interactive
/* Indicates if input represent valid number. E.g. empty input or containing
* only decimal point is not valid. */
readonly property alias valid: textField.acceptableInput
readonly property bool empty: textField.length === 0
// TODO: remove, temporarily for backward compatibility. External components
// should not rely on formatted amount because formatting rules are internal
// detail of that component.
readonly property alias text: textField.text
/* Decimal point character to be displayed. Both "." and "," will be
* replaced by the provided decimal point on the fly */
property alias decimalPoint: validator.decimalPoint
/* Number of fiat decimal places used to limit allowed decimal places in
* fiatMode */
property int fiatDecimalPlaces: 2
/* Specifies how divisible given cryptocurrency is, e.g. 18 for ETH. Used
* for limiting allowed decimal places and computing final amount as an
* integer value */
property int multiplierIndex: 18
/* Price of one unit of given cryptocurrency (e.g. price for 1 ETH) */
property real price: 1.0
property alias caption: captionText.text
property bool interactive: true
readonly property bool cursorVisible: textField.cursorVisible
readonly property alias placeholderText: textField.placeholderText
/* Loading states for the input and text below */
property bool mainInputLoading
property bool bottomTextLoading
/* Allows mark input as invalid when it's valid number but doesn't satisfy
* arbitrary external criteria, e.g. is higher than maximum expected value. */
property bool markAsInvalid: false
/* Methods for formatting crypto and fiat value expecting double values,
e.g. 1.0 for 1 ETH or 1.0 for 1 USD. */
property var formatFiat: balance =>
`${balance.toLocaleString(Qt.locale())} FIAT`
property var formatBalance: balance =>
`${balance.toLocaleString(Qt.locale())} CRYPTO`
/* Allows to set value to be displayed. The value is expected to be a not
localized string like "1", "1.1" or "0.000000023400234222". Provided
value will be formatted and displayed. Depending on the fiatMode flag
it will affect output amount appropriately. */
function setValue(valueString) {
if (!valueString)
valueString = "0"
const decimalPlaces = d.fiatMode ? root.fiatDecimalPlaces
: root.multiplierIndex
const stringNumber = SQUtils.AmountsArithmetic.fromString(
valueString).toFixed(decimalPlaces)
const trimmed = d.fiatMode
? stringNumber
: d.removeDecimalTrailingZeros(stringNumber)
textField.text = d.localize(trimmed)
}
function clear() {
textField.clear()
}
function forceActiveFocus() {
textField.forceActiveFocus()
}
QtObject {
id: d
property bool fiatMode: false
readonly property string inputDelocalized:
root.valid && textField.length !== 0
? textField.text.replace(root.decimalPoint, ".") : "0"
function removeDecimalTrailingZeros(num) {
if (!num.includes("."))
return num
return num.replace(/\.?0*$/g, "")
}
function localize(num) {
return num.replace(".", root.decimalPoint)
}
readonly property string amountBaseUnit: {
if (d.fiatMode)
return secondaryValue
const multiplier = SQUtils.AmountsArithmetic.fromExponent(
root.multiplierIndex)
return SQUtils.AmountsArithmetic.times(
SQUtils.AmountsArithmetic.fromString(inputDelocalized),
multiplier).toFixed()
}
readonly property string secondaryValue: {
const price = isNaN(root.price) ? 0 : root.price
if (!d.fiatMode)
return SQUtils.AmountsArithmetic.times(
SQUtils.AmountsArithmetic.fromString(inputDelocalized),
SQUtils.AmountsArithmetic.fromNumber(
price * (10 ** root.fiatDecimalPlaces))).toFixed()
if (!price) // prevent div by zero below
return 0
const multiplier = SQUtils.AmountsArithmetic.fromExponent(
root.multiplierIndex)
return SQUtils.AmountsArithmetic.div(
SQUtils.AmountsArithmetic.times(
SQUtils.AmountsArithmetic.fromString(inputDelocalized),
multiplier),
SQUtils.AmountsArithmetic.fromNumber(price)).toFixed()
}
}
contentItem: ColumnLayout {
StatusBaseText {
id: captionText
Layout.fillWidth: true
visible: text.length > 0
font.pixelSize: 13
lineHeight: 18
lineHeightMode: Text.FixedHeight
color: Theme.palette.directColor1
elide: Text.ElideRight
}
RowLayout {
StyledTextField {
id: textField
objectName: "amountToSend_textField"
Layout.fillWidth: true
implicitHeight: 44
padding: 0
background: null
readOnly: !root.interactive
color: text.length === 0 || (root.valid && !root.markAsInvalid)
? Theme.palette.directColor1
: Theme.palette.dangerColor1
placeholderText: {
if (!d.fiatMode || root.fiatDecimalPlaces === 0)
return "0"
return "0" + root.decimalPoint
+ "0".repeat(root.fiatDecimalPlaces)
}
font.pixelSize: Utils.getFontSizeBasedOnLetterCount(text)
validator: AmountValidator {
id: validator
maxDecimalDigits: d.fiatMode ? root.fiatDecimalPlaces
: root.multiplierIndex
locale: root.locale.name
}
visible: !root.mainInputLoading
}
LoadingComponent {
objectName: "topAmountToSendInputLoadingComponent"
Layout.preferredWidth: textField.width
Layout.preferredHeight: textField.height
visible: root.mainInputLoading
}
}
StatusBaseText {
id: bottomItem
objectName: "bottomItemText"
Layout.fillWidth: true
text: {
const divisor = SQUtils.AmountsArithmetic.fromExponent(
d.fiatMode ? root.multiplierIndex
: root.fiatDecimalPlaces)
const divided = SQUtils.AmountsArithmetic.div(
SQUtils.AmountsArithmetic.fromString(
d.secondaryValue), divisor)
const asNumber = SQUtils.AmountsArithmetic.toNumber(divided)
return d.fiatMode ? root.formatBalance(asNumber)
: root.formatFiat(asNumber)
}
elide: Text.ElideMiddle
font.pixelSize: 13
color: Theme.palette.directColor5
MouseArea {
objectName: "amountToSend_mouseArea"
anchors.fill: parent
cursorShape: enabled ? Qt.PointingHandCursor : undefined
enabled: root.fiatInputInteractive
onClicked: {
const secondaryValue = d.secondaryValue
d.fiatMode = !d.fiatMode
if (textField.length === 0)
return
const decimalPlaces = d.fiatMode ? root.fiatDecimalPlaces
: root.multiplierIndex
const divisor = SQUtils.AmountsArithmetic.fromExponent(
decimalPlaces)
const stringNumber = SQUtils.AmountsArithmetic.div(
SQUtils.AmountsArithmetic.fromString(secondaryValue),
divisor).toFixed(decimalPlaces)
const trimmed = d.fiatMode
? stringNumber
: d.removeDecimalTrailingZeros(stringNumber)
textField.text = d.localize(trimmed)
}
}
visible: !root.bottomTextLoading
}
LoadingComponent {
objectName: "bottomItemTextLoadingComponent"
Layout.preferredWidth: bottomItem.width
Layout.preferredHeight: bottomItem.height
visible: root.bottomTextLoading
}
}
}

View File

@ -1,6 +1,5 @@
AmountToReceive 1.0 AmountToReceive.qml AmountToReceive 1.0 AmountToReceive.qml
AmountToSend 1.0 AmountToSend.qml AmountToSend 1.0 AmountToSend.qml
AmountToSendNew 1.0 AmountToSendNew.qml
FeesView 1.0 FeesView.qml FeesView 1.0 FeesView.qml
NetworkCardsComponent 1.0 NetworkCardsComponent.qml NetworkCardsComponent 1.0 NetworkCardsComponent.qml
NetworkSelector 1.0 NetworkSelector.qml NetworkSelector 1.0 NetworkSelector.qml