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.Layouts 1.15
import StatusQ.Core 0.1
import Qt.labs.settings 1.1
import shared.popups.send.views 1.0
import Storybook 1.0
import Models 1.0
SplitView {
id: root
orientation: Qt.Vertical
SplitView.fillWidth: true
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
Item {
SplitView.fillWidth: true
SplitView.fillHeight: true
Item {
SplitView.fillWidth: true
SplitView.fillHeight: true
AmountToSend {
id: amountToSend
AmountToSend {
id: amountToSendInput
isBridgeTx: false
interactive: true
selectedHolding: tokensBySymbolModel.data[0]
anchors.centerIn: parent
inputIsFiat: fiatInput.checked
interactive: interactiveCheckBox.checked
fiatInputInteractive: fiatInteractiveCheckBox.checked
markAsInvalid: markAsInvalidCheckBox.checked
maxInputBalance: inputIsFiat ? root.maxCryptoBalance*amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount
: root.maxCryptoBalance
currentCurrency: "USD"
formatCurrencyAmount: function(amount, symbol, options, locale) {
const currencyAmount = {
amount: amount,
symbol: symbol,
displayDecimals: root.decimals,
stripTrailingZeroes: true
}
return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale)
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"
}
onReCalculateSuggestedRoute: function() {
logs.logEvent("onReCalculateSuggestedRoute")
TextField {
id: priceTextField
text: "812.323"
}
}
}
LogsAndControlsPanel {
id: logsAndControlsPanel
SplitView.minimumHeight: 250
logsView.logText: logs.logText
ColumnLayout {
RowLayout {
Label {
Layout.topMargin: 10
Layout.fillWidth: true
text: "Max Crypto Balance"
text: "Decimal point"
}
TextField {
id: maxCryptoBalanceText
background: Rectangle { border.color: 'lightgrey' }
Layout.preferredWidth: 200
text: "1000000"
RadioButton {
id: decimalPointRadioButton
text: "."
}
RadioButton {
text: ","
checked: true
}
}
RowLayout {
Label {
Layout.topMargin: 10
Layout.fillWidth: true
text: "Decimals"
text: "Multiplier index"
}
TextField {
id: decimalsText
background: Rectangle { border.color: 'lightgrey' }
Layout.preferredWidth: 200
text: "6"
SpinBox {
id: multiplierIndexSpinBox
editable: true
value: 18
to: 30
}
}
RowLayout {
CheckBox {
id: interactiveCheckBox
text: "Interactive"
checked: true
}
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

View File

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

View File

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

View File

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

View File

@ -1,232 +1,284 @@
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.Controls 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Components 0.1
import StatusQ.Controls.Validators 0.1
import "../controls"
import StatusQ.Validators 0.1
import utils 1.0
import shared.controls 1.0
ColumnLayout {
Control {
id: root
readonly property alias input: topAmountToSendInput
readonly property bool inputNumberValid: !!input.text && !isNaN(d.parsedInput) && input.valid
/* Crypto value in a base unit as a string integer, e.g. 1000000000000000000
* for 1 ETH */
readonly property alias amount: d.amountBaseUnit
readonly property int minSendCryptoDecimals:
!inputIsFiat ? LocaleUtils.fractionalPartLength(d.inputNumber) : 0
readonly property int minReceiveCryptoDecimals:
!inputIsFiat ? minSendCryptoDecimals + 1 : 0
readonly property int minSendFiatDecimals:
inputIsFiat ? LocaleUtils.fractionalPartLength(d.inputNumber) : 0
readonly property int minReceiveFiatDecimals:
inputIsFiat ? minSendFiatDecimals + 1 : 0
/* In fiat mode the input value is meant to be a fiat value, conversely,
* crypto value otherwise. */
readonly property alias fiatMode: d.fiatMode
property var selectedHolding // Crypto asset symbol like ETH
property string currentCurrency // Fiat currency symbol like USD
/* Indicates whether toggling the fiatMode is enabled for the user */
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
property bool interactive: false
property bool inputIsFiat: false
/* Decimal point character to be displayed. Both "." and "," will be
* replaced by the provided decimal point on the fly */
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),
// as a string representing integer decimal
readonly property alias cryptoValueToSend: d.cryptoValueRawToSend
/* Price of one unit of given cryptocurrency (e.g. price for 1 ETH) */
property real price: 1.0
readonly property alias cryptoValueToSendFloat: d.cryptoValueToSend
property alias caption: captionText.text
property bool interactive: true
property var formatCurrencyAmount:
(amount, symbol, options = null, locale = null) => {}
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
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 {
id: d
property double cryptoValueToSend
property double fiatValueToSend
property bool fiatMode: false
Binding on cryptoValueToSend {
value: {
root.selectedHolding
if(!root.selectedHolding || !root.selectedHolding.marketDetails || !root.selectedHolding.marketDetails.currencyPrice) {
return 0
}
return root.inputIsFiat ? d.fiatValueToSend/root.selectedHolding.marketDetails.currencyPrice.amount
: d.inputNumber
}
delayed: true
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, "")
}
Binding on fiatValueToSend {
value: {
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
function localize(num) {
return num.replace(".", root.decimalPoint)
}
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: {
return SQUtils.AmountsArithmetic.fromNumber(
d.cryptoValueToSend, root.multiplierIndex).toString()
const multiplier = SQUtils.AmountsArithmetic.fromExponent(
root.multiplierIndex)
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 zeroString: {
let decimals = root.inputIsFiat ? 2 : 0
LocaleUtils.numberToLocaleString(0, decimals, topAmountToSendInput.locale)
}
readonly property string secondaryValue: {
const price = isNaN(root.price) ? 0 : root.price
readonly property double parsedInput:
LocaleUtils.numberFromLocaleString(topAmountToSendInput.text,
topAmountToSendInput.locale)
if (!d.fiatMode)
return SQUtils.AmountsArithmetic.times(
SQUtils.AmountsArithmetic.fromString(inputDelocalized),
SQUtils.AmountsArithmetic.fromNumber(
price * (10 ** root.fiatDecimalPlaces))).toFixed()
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
if (!price) // prevent div by zero below
return 0
readonly property Timer waitTimer: Timer {
interval: 1000
onTriggered: reCalculateSuggestedRoute()
const multiplier = SQUtils.AmountsArithmetic.fromExponent(
root.multiplierIndex)
return SQUtils.AmountsArithmetic.div(
SQUtils.AmountsArithmetic.times(
SQUtils.AmountsArithmetic.fromString(inputDelocalized),
multiplier),
SQUtils.AmountsArithmetic.fromNumber(price)).toFixed()
}
}
onMaxInputBalanceChanged: {
input.validate()
}
contentItem: ColumnLayout {
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.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) => {
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
}
}
]
visible: text.length > 0
TextMetrics {
id: textMetrics
text: topAmountToSendInput.placeholderText
font: topAmountToSendInput.placeholderFont
}
Keys.onReleased: {
const amount = LocaleUtils.numberFromLocaleString(
topAmountToSendInput.text,
locale)
if (!isNaN(amount))
d.waitTimer.restart()
}
visible: !root.mainInputLoading
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: "topAmountToSendInputLoadingComponent"
Layout.preferredWidth: topAmountToSendInput.width
Layout.preferredHeight: topAmountToSendInput.height
visible: root.mainInputLoading
objectName: "bottomItemTextLoadingComponent"
Layout.preferredWidth: bottomItem.width
Layout.preferredHeight: bottomItem.height
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
AmountToSend 1.0 AmountToSend.qml
AmountToSendNew 1.0 AmountToSendNew.qml
FeesView 1.0 FeesView.qml
NetworkCardsComponent 1.0 NetworkCardsComponent.qml
NetworkSelector 1.0 NetworkSelector.qml