feat(SendModal): New AmountToSend component
In comparison to the previous version it provides simpler API and better validation based on AmountValidator. Old version will be removed after full integration of the new version.
This commit is contained in:
parent
84be1d9da7
commit
613fa4d19f
|
@ -0,0 +1,138 @@
|
|||
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
|
||||
markAsInvalid: markAsInvalidCheckBox.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: markAsInvalidCheckBox
|
||||
|
||||
text: "Mark as invalid"
|
||||
}
|
||||
}
|
||||
|
||||
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
|
|
@ -0,0 +1,173 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import QtTest 1.15
|
||||
|
||||
import shared.popups.send.views 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
Component {
|
||||
id: componentUnderTest
|
||||
|
||||
AmountToSendNew {
|
||||
decimalPoint: "."
|
||||
}
|
||||
}
|
||||
|
||||
property AmountToSendNew amountToSend
|
||||
|
||||
TestCase {
|
||||
name: "AmountToSendNew"
|
||||
when: windowShown
|
||||
|
||||
function type(key, times = 1) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
keyPress(key)
|
||||
keyRelease(key)
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
amountToSend = createTemporaryObject(componentUnderTest, root)
|
||||
}
|
||||
|
||||
function test_empty() {
|
||||
compare(amountToSend.valid, false)
|
||||
compare(amountToSend.empty, true)
|
||||
compare(amountToSend.amount, "0")
|
||||
compare(amountToSend.fiatMode, false)
|
||||
}
|
||||
|
||||
function test_settingValueInCryptoMode() {
|
||||
const textField = findChild(amountToSend, "amountToSend_textField")
|
||||
|
||||
amountToSend.multiplierIndex = 3
|
||||
amountToSend.setValue("2.5")
|
||||
|
||||
compare(textField.text, "2.5")
|
||||
compare(amountToSend.amount, "2500")
|
||||
compare(amountToSend.valid, true)
|
||||
|
||||
amountToSend.setValue("2.12345678")
|
||||
|
||||
compare(textField.text, "2.123")
|
||||
compare(amountToSend.amount, "2123")
|
||||
compare(amountToSend.valid, true)
|
||||
|
||||
amountToSend.setValue("2.1239")
|
||||
|
||||
compare(textField.text, "2.124")
|
||||
compare(amountToSend.amount, "2124")
|
||||
compare(amountToSend.valid, true)
|
||||
|
||||
amountToSend.setValue(".1239")
|
||||
|
||||
compare(textField.text, "0.124")
|
||||
compare(amountToSend.amount, "124")
|
||||
compare(amountToSend.valid, true)
|
||||
|
||||
amountToSend.setValue("1.0000")
|
||||
|
||||
compare(textField.text, "1")
|
||||
compare(amountToSend.amount, "1000")
|
||||
compare(amountToSend.valid, true)
|
||||
|
||||
amountToSend.setValue("0.0000")
|
||||
|
||||
compare(textField.text, "0")
|
||||
compare(amountToSend.amount, "0")
|
||||
compare(amountToSend.valid, true)
|
||||
|
||||
amountToSend.setValue("x")
|
||||
|
||||
compare(textField.text, "NaN")
|
||||
compare(amountToSend.amount, "0")
|
||||
compare(amountToSend.valid, false)
|
||||
|
||||
// exceeding maxium allowed integral part
|
||||
amountToSend.setValue("1234567890000")
|
||||
compare(textField.text, "1234567890000")
|
||||
compare(amountToSend.amount, "0")
|
||||
compare(amountToSend.valid, false)
|
||||
}
|
||||
|
||||
function test_settingValueInFiatMode() {
|
||||
const textField = findChild(amountToSend, "amountToSend_textField")
|
||||
const mouseArea = findChild(amountToSend, "amountToSend_mouseArea")
|
||||
|
||||
amountToSend.price = 0.5
|
||||
amountToSend.multiplierIndex = 3
|
||||
|
||||
mouseClick(mouseArea)
|
||||
compare(amountToSend.fiatMode, true)
|
||||
|
||||
amountToSend.setValue("2.5")
|
||||
|
||||
compare(textField.text, "2.50")
|
||||
compare(amountToSend.amount, "5000")
|
||||
compare(amountToSend.valid, true)
|
||||
|
||||
amountToSend.setValue("2.12345678")
|
||||
|
||||
compare(textField.text, "2.12")
|
||||
compare(amountToSend.amount, "4240")
|
||||
compare(amountToSend.valid, true)
|
||||
|
||||
amountToSend.setValue("2.129")
|
||||
|
||||
compare(textField.text, "2.13")
|
||||
compare(amountToSend.amount, "4260")
|
||||
compare(amountToSend.valid, true)
|
||||
|
||||
// exceeding maxium allowed integral part
|
||||
amountToSend.setValue("1234567890000")
|
||||
compare(textField.text, "1234567890000.00")
|
||||
compare(amountToSend.amount, "0")
|
||||
compare(amountToSend.valid, false)
|
||||
}
|
||||
|
||||
function test_switchingMode() {
|
||||
const textField = findChild(amountToSend, "amountToSend_textField")
|
||||
const mouseArea = findChild(amountToSend, "amountToSend_mouseArea")
|
||||
|
||||
amountToSend.price = 0.5
|
||||
amountToSend.multiplierIndex = 3
|
||||
|
||||
amountToSend.setValue("10.5")
|
||||
compare(amountToSend.amount, "10500")
|
||||
|
||||
mouseClick(mouseArea)
|
||||
compare(amountToSend.fiatMode, true)
|
||||
compare(textField.text, "5.25")
|
||||
compare(amountToSend.amount, "10500")
|
||||
|
||||
mouseClick(mouseArea)
|
||||
compare(amountToSend.fiatMode, false)
|
||||
compare(textField.text, "10.5")
|
||||
compare(amountToSend.amount, "10500")
|
||||
|
||||
mouseClick(mouseArea)
|
||||
compare(amountToSend.fiatMode, true)
|
||||
amountToSend.price = 0.124
|
||||
amountToSend.setValue("1")
|
||||
compare(textField.text, "1.00")
|
||||
|
||||
mouseClick(mouseArea)
|
||||
compare(amountToSend.fiatMode, false)
|
||||
compare(textField.text, "8.065")
|
||||
compare(amountToSend.amount, "8065")
|
||||
}
|
||||
|
||||
function test_clear() {
|
||||
const textField = findChild(amountToSend, "amountToSend_textField")
|
||||
|
||||
amountToSend.setValue("10.5")
|
||||
amountToSend.clear()
|
||||
|
||||
compare(amountToSend.amount, "0")
|
||||
compare(textField.text, "")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
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.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 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 dispalyed. 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 amout 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
|
||||
|
||||
/* 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(",", ".") : "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))).round().toFixed()
|
||||
|
||||
const multiplier = SQUtils.AmountsArithmetic.fromExponent(
|
||||
root.multiplierIndex)
|
||||
|
||||
return SQUtils.AmountsArithmetic.div(
|
||||
SQUtils.AmountsArithmetic.times(
|
||||
SQUtils.AmountsArithmetic.fromString(inputDelocalized),
|
||||
multiplier),
|
||||
SQUtils.AmountsArithmetic.fromNumber(price)).round().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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.interactive
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
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