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,25 +2,11 @@ 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
readonly property var tokensBySymbolModel: TokensBySymbolModel {}
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 orientation: Qt.Vertical
SplitView.fillWidth: true SplitView.fillWidth: true
@ -29,73 +15,145 @@ SplitView {
SplitView.fillHeight: true SplitView.fillHeight: true
AmountToSend { AmountToSend {
id: amountToSendInput id: amountToSend
isBridgeTx: false
interactive: true
selectedHolding: tokensBySymbolModel.data[0]
inputIsFiat: fiatInput.checked anchors.centerIn: parent
maxInputBalance: inputIsFiat ? root.maxCryptoBalance*amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount interactive: interactiveCheckBox.checked
: root.maxCryptoBalance fiatInputInteractive: fiatInteractiveCheckBox.checked
currentCurrency: "USD" markAsInvalid: markAsInvalidCheckBox.checked
formatCurrencyAmount: function(amount, symbol, options, locale) {
const currencyAmount = { mainInputLoading: ctrlMainInputLoading.checked
amount: amount, bottomTextLoading: ctrlBottomTextLoading.checked
symbol: symbol,
displayDecimals: root.decimals, caption: "Amount to send"
stripTrailingZeroes: true
} decimalPoint: decimalPointRadioButton.checked ? "." : ","
return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale) price: parseFloat(priceTextField.text)
}
onReCalculateSuggestedRoute: function() { multiplierIndex: multiplierIndexSpinBox.value
logs.logEvent("onReCalculateSuggestedRoute")
} formatFiat: balance => `${balance.toLocaleString(Qt.locale())} USD`
formatBalance: balance => `${balance.toLocaleString(Qt.locale())} ETH`
} }
} }
LogsAndControlsPanel { Pane {
id: logsAndControlsPanel id: logsAndControlsPanel
SplitView.minimumHeight: 250 SplitView.minimumHeight: 350
logsView.logText: logs.logText
ColumnLayout { ColumnLayout {
spacing: 15
RowLayout {
Label { Label {
Layout.topMargin: 10 text: "Price"
Layout.fillWidth: true
text: "Max Crypto Balance"
} }
TextField { TextField {
id: maxCryptoBalanceText id: priceTextField
background: Rectangle { border.color: 'lightgrey' }
Layout.preferredWidth: 200 text: "812.323"
text: "1000000" }
} }
RowLayout {
Label { Label {
Layout.topMargin: 10 text: "Decimal point"
Layout.fillWidth: true
text: "Decimals"
} }
TextField { RadioButton {
id: decimalsText id: decimalPointRadioButton
background: Rectangle { border.color: 'lightgrey' }
Layout.preferredWidth: 200 text: "."
text: "6" }
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 { 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,223 +1,274 @@
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) {
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 return 0
}
return root.inputIsFiat ? d.fiatValueToSend/root.selectedHolding.marketDetails.currencyPrice.amount
: d.inputNumber
}
delayed: true
}
Binding on fiatValueToSend { const multiplier = SQUtils.AmountsArithmetic.fromExponent(
value: { root.multiplierIndex)
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: "" return SQUtils.AmountsArithmetic.div(
SQUtils.AmountsArithmetic.times(
readonly property string cryptoValueRawToSend: { SQUtils.AmountsArithmetic.fromString(inputDelocalized),
return SQUtils.AmountsArithmetic.fromNumber( multiplier),
d.cryptoValueToSend, root.multiplierIndex).toString() SQUtils.AmountsArithmetic.fromNumber(price)).toFixed()
}
// Crypto value should be represented by 0 and fiat with 0.00
readonly property string zeroString: {
let decimals = root.inputIsFiat ? 2 : 0
LocaleUtils.numberToLocaleString(0, decimals, topAmountToSendInput.locale)
}
readonly property double parsedInput:
LocaleUtils.numberFromLocaleString(topAmountToSendInput.text,
topAmountToSendInput.locale)
readonly property double inputNumber:
// we should still calculate if value entered is greater than max safe value
!!input.text && !isNaN(d.parsedInput) && d.parsedInput >= 0 ? d.parsedInput : 0
readonly property Timer waitTimer: Timer {
interval: 1000
onTriggered: reCalculateSuggestedRoute()
} }
} }
onMaxInputBalanceChanged: { contentItem: ColumnLayout {
input.validate()
}
onSelectedHoldingChanged: {
input.validate()
}
StatusBaseText { StatusBaseText {
text: root.caption id: captionText
Layout.fillWidth: true
visible: text.length > 0
font.pixelSize: 13 font.pixelSize: 13
lineHeight: 18 lineHeight: 18
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
color: Theme.palette.directColor1 color: Theme.palette.directColor1
elide: Text.ElideRight
} }
RowLayout { RowLayout {
Layout.fillWidth: true StyledTextField {
id: topItem id: textField
objectName: "amountToSend_textField"
AmountInputWithCursor {
id: topAmountToSendInput
Layout.fillWidth: true Layout.fillWidth: true
Layout.maximumWidth: 250
Layout.preferredWidth: !!text ? input.edit.paintedWidth + 2 implicitHeight: 44
: textMetrics.advanceWidth padding: 0
placeholderText: d.zeroString background: null
input.edit.color: input.valid ? Theme.palette.directColor1
readOnly: !root.interactive
color: text.length === 0 || (root.valid && !root.markAsInvalid)
? Theme.palette.directColor1
: Theme.palette.dangerColor1 : Theme.palette.dangerColor1
input.edit.readOnly: !root.interactive
validationMode: StatusInput.ValidationMode.Always
validators: [
StatusValidator {
errorMessage: ""
validate: (text) => { placeholderText: {
const num = LocaleUtils.numberFromLocaleString(topAmountToSendInput.text, if (!d.fiatMode || root.fiatDecimalPlaces === 0)
topAmountToSendInput.locale) return "0"
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 { return "0" + root.decimalPoint
id: textMetrics + "0".repeat(root.fiatDecimalPlaces)
text: topAmountToSendInput.placeholderText
font: topAmountToSendInput.placeholderFont
} }
Keys.onReleased: { font.pixelSize: Utils.getFontSizeBasedOnLetterCount(text)
const amount = LocaleUtils.numberFromLocaleString(
topAmountToSendInput.text,
locale)
if (!isNaN(amount))
d.waitTimer.restart()
}
validator: AmountValidator {
id: validator
maxDecimalDigits: d.fiatMode ? root.fiatDecimalPlaces
: root.multiplierIndex
locale: root.locale.name
}
visible: !root.mainInputLoading visible: !root.mainInputLoading
} }
LoadingComponent { LoadingComponent {
objectName: "topAmountToSendInputLoadingComponent" objectName: "topAmountToSendInputLoadingComponent"
Layout.preferredWidth: topAmountToSendInput.width Layout.preferredWidth: textField.width
Layout.preferredHeight: topAmountToSendInput.height Layout.preferredHeight: textField.height
visible: root.mainInputLoading visible: root.mainInputLoading
} }
} }
StatusBaseText { StatusBaseText {
Layout.maximumWidth: parent.width
id: bottomItem id: bottomItem
objectName: "bottomItemText" objectName: "bottomItemText"
readonly property double bottomAmountToSend: inputIsFiat ? d.cryptoValueToSend Layout.fillWidth: true
: d.fiatValueToSend
readonly property string bottomAmountSymbol: inputIsFiat ? d.selectedSymbol text: {
: root.currentCurrency 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 elide: Text.ElideMiddle
text: root.formatCurrencyAmount(bottomAmountToSend, bottomAmountSymbol)
font.pixelSize: 13 font.pixelSize: 13
color: Theme.palette.directColor5 color: Theme.palette.directColor5
MouseArea { MouseArea {
objectName: "amountToSend_mouseArea"
anchors.fill: parent anchors.fill: parent
cursorShape: enabled ? Qt.PointingHandCursor : undefined cursorShape: enabled ? Qt.PointingHandCursor : undefined
enabled: root.fiatInputInteractive && !!root.selectedHolding enabled: root.fiatInputInteractive
onClicked: { onClicked: {
topAmountToSendInput.validate() const secondaryValue = d.secondaryValue
if(!!topAmountToSendInput.text) {
topAmountToSendInput.text = root.formatCurrencyAmount( d.fiatMode = !d.fiatMode
bottomItem.bottomAmountToSend,
bottomItem.bottomAmountSymbol, if (textField.length === 0)
{ noSymbol: true, rawAmount: true }, return
topAmountToSendInput.locale)
} const decimalPlaces = d.fiatMode ? root.fiatDecimalPlaces
root.inputIsFiat = !root.inputIsFiat : root.multiplierIndex
d.waitTimer.restart() 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 visible: !root.bottomTextLoading
@ -229,4 +280,5 @@ ColumnLayout {
Layout.preferredHeight: bottomItem.height Layout.preferredHeight: bottomItem.height
visible: root.bottomTextLoading 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