status-desktop/ui/imports/shared/popups/send/views/AmountToSend.qml

341 lines
12 KiB
QML

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.Controls 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
/* Boolean flag decides whether divider is shown between main input
area and bottom text */
property bool dividerVisible
/* Boolean flag decides whether the main input text area's fontsize reduces
as more and more characters are added.
True by default */
property bool progressivePixelReduction: true
/* This string holds the current main input area's currency symbol (fiat or crypto)
Not set = not shown */
property string selectedSymbol
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()
}
/* Incase we dont have progressing font size reduction we only
allow users to enter digits until there is place left */
function getMaxDigitsAllowed() {
if (root.progressivePixelReduction) {
return validator.maxDecimalDigits + validator.maxIntegralDigits
} else {
let availableSpaceForAmount = root.availableWidth - currencyField.contentWidth - layout.spacing
return Math.floor(availableSpaceForAmount/textMetrics.tightBoundingRect.width)
}
}
readonly property TextMetrics textMetrics: TextMetrics {
font: textField.font
text: "0"
}
}
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 {
id: layout
spacing: 4
StatusTextField {
id: textField
objectName: "amountToSend_textField"
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:{
if (!root.progressivePixelReduction)
return 34
return Utils.getFontSizeBasedOnLetterCount(text)
}
validator: AmountValidator {
id: validator
maxDigits: d.getMaxDigitsAllowed()
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: currencyField
objectName: "amountToSend_currencyField"
Layout.alignment: Qt.AlignVCenter
Layout.topMargin: 10
color: Theme.palette.directColor5
text: selectedSymbol
font.pixelSize: Theme.additionalTextSize
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
Layout.bottomMargin: 12
color: Theme.palette.baseColor2
visible: root.dividerVisible
}
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
}
}
}