chore(AmountToSend): AmountToSendNew is dead, long live AmountToSend
This commit is contained in:
parent
114abc7015
commit
7c10b16b67
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
|
@ -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
|
||||
|
||||
|
|
|
@ -480,7 +480,7 @@ StatusDialog {
|
|||
RowLayout {
|
||||
visible: d.isSelectedHoldingValidAsset && !d.isCollectiblesTransfer
|
||||
|
||||
AmountToSendNew {
|
||||
AmountToSend {
|
||||
id: amountToSend
|
||||
|
||||
caption: d.isBridgeTx ? qsTr("Amount to bridge")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue