diff --git a/ui/app/AppLayouts/Chat/controls/community/CollectiblesPanel.qml b/ui/app/AppLayouts/Chat/controls/community/CollectiblesPanel.qml index eca6a268463..f2bfa59372f 100644 --- a/ui/app/AppLayouts/Chat/controls/community/CollectiblesPanel.qml +++ b/ui/app/AppLayouts/Chat/controls/community/CollectiblesPanel.qml @@ -4,7 +4,8 @@ import QtQuick.Layouts 1.14 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 -import StatusQ.Controls.Validators 0.1 + +import shared.controls 1.0 ColumnLayout { id: root @@ -13,10 +14,16 @@ ColumnLayout { property alias collectibleName: pickerButton.text property url collectibleImage - property alias amount: amountInput.text + property alias amountText: amountInput.text + property alias amount: amountInput.amount + readonly property bool amountValid: amountInput.valid && amountInput.text.length > 0 signal pickerClicked + function setAmount(amount) { + amountInput.setAmount(amount) + } + spacing: 0 StatusPickerButton { @@ -50,30 +57,14 @@ ColumnLayout { StatusSwitch { id: specificAmountSwitch } } - StatusInput { + AmountInput { id: amountInput + visible: specificAmountSwitch.checked + Layout.fillWidth: true Layout.topMargin: 8 - visible: specificAmountSwitch.checked - minimumHeight: 36 - maximumHeight: 36 - topPadding: 0 - bottomPadding: 0 - font.pixelSize: 13 - rightPadding: amountText.implicitWidth + amountText.anchors.rightMargin + leftPadding - input.placeholderText: "0" - validationMode: StatusInput.ValidationMode.IgnoreInvalidInput - validators: StatusFloatValidator { bottom: 0 } - StatusBaseText { - id: amountText - anchors.right: parent.right - anchors.rightMargin: 13 - anchors.verticalCenter: parent.verticalCenter - text: qsTr("Amount") - color: Theme.palette.baseColor1 - font.pixelSize: 13 - } + allowDecimals: false } } diff --git a/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml b/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml index d718a8cd06a..680e026a07f 100644 --- a/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml +++ b/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml @@ -14,9 +14,9 @@ StatusDropdown { property var store property string tokenKey: "" - property string collectibleKey: "" - property real tokenAmount: 0 + + property string collectibleKey: "" property real collectibleAmount: 1 property bool collectiblesSpecificAmount: false @@ -36,6 +36,8 @@ StatusDropdown { function reset() { d.currentHoldingType = HoldingTypes.Type.Token d.operator = SQ.Utils.Operators.None + d.tokenAmountText = "" + d.collectibleAmountText = "" root.tokenKey = "" root.collectibleKey = "" @@ -96,6 +98,9 @@ StatusDropdown { property int holdingsTabMode: HoldingsTabs.Mode.Add property int extendedDropdownType: ExtendedDropdownContent.Type.Tokens + property string tokenAmountText: "" + property string collectibleAmountText: "" + property int currentHoldingType: HoldingTypes.Type.Token property int operator: SQ.Utils.Operators.None @@ -279,26 +284,17 @@ StatusDropdown { id: tokensPanel tokenName: d.defaultTokenNameText - amount: root.tokenAmount === 0 ? "" : root.tokenAmount.toString() - onAmountChanged: root.tokenAmount = Number(amount) + amountText: d.tokenAmountText + onAmountTextChanged: d.tokenAmountText = amountText + + readonly property real effectiveAmount: amountValid ? amount : 0 + onEffectiveAmountChanged: root.tokenAmount = effectiveAmount onPickerClicked: { d.extendedDropdownType = ExtendedDropdownContent.Type.Tokens statesStack.push(d.extendedState) } - Connections { - target: d - - function onAddClicked() { - root.addToken(root.tokenKey, root.tokenAmount, d.operator) - } - - function onUpdateClicked() { - root.updateToken(root.tokenKey, root.tokenAmount) - } - } - readonly property string tokenKey: root.tokenKey onTokenKeyChanged: { @@ -312,6 +308,23 @@ StatusDropdown { tokensPanel.tokenImage = "" } } + + Component.onCompleted: { + if (d.tokenAmountText.length === 0 && root.tokenAmount) + tokensPanel.setAmount(root.tokenAmount) + } + + Connections { + target: d + + function onAddClicked() { + root.addToken(root.tokenKey, root.tokenAmount, d.operator) + } + + function onUpdateClicked() { + root.updateToken(root.tokenKey, root.tokenAmount) + } + } } } @@ -322,9 +335,11 @@ StatusDropdown { id: collectiblesPanel collectibleName: d.defaultCollectibleNameText + amountText: d.collectibleAmountText + onAmountTextChanged: d.collectibleAmountText = amountText - amount: root.collectibleAmount === 0 ? "" : root.collectibleAmount.toString() - onAmountChanged: root.collectibleAmount = Number(amount) + readonly property real effectiveAmount: amountValid ? amount : 0 + onEffectiveAmountChanged: root.collectibleAmount = effectiveAmount specificAmount: root.collectiblesSpecificAmount onSpecificAmountChanged: root.collectiblesSpecificAmount = specificAmount @@ -334,15 +349,24 @@ StatusDropdown { statesStack.push(d.extendedState) } + Component.onCompleted: { + if (d.collectibleAmountText.length === 0 && root.collectibleAmount) + collectiblesPanel.setAmount(root.collectibleAmount) + } + + function getAmount() { + return specificAmount ? effectiveAmount : 1 + } + Connections { target: d function onAddClicked() { - root.addCollectible(root.collectibleKey, root.collectibleAmount, d.operator) + root.addCollectible(root.collectibleKey, collectiblesPanel.getAmount(), d.operator) } function onUpdateClicked() { - root.updateCollectible(root.collectibleKey, root.collectibleAmount) + root.updateCollectible(root.collectibleKey, collectiblesPanel.getAmount()) } } @@ -416,5 +440,5 @@ StatusDropdown { } } } - } + } } diff --git a/ui/app/AppLayouts/Chat/controls/community/TokensPanel.qml b/ui/app/AppLayouts/Chat/controls/community/TokensPanel.qml index a46fade3a42..17cf026f807 100644 --- a/ui/app/AppLayouts/Chat/controls/community/TokensPanel.qml +++ b/ui/app/AppLayouts/Chat/controls/community/TokensPanel.qml @@ -4,17 +4,24 @@ import QtQuick.Layouts 1.14 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 -import StatusQ.Controls.Validators 0.1 + +import shared.controls 1.0 ColumnLayout { id: root property alias tokenName: pickerButton.text property url tokenImage - property alias amount: amountInput.text + property alias amountText: amountInput.text + property alias amount: amountInput.amount + readonly property bool amountValid: amountInput.valid && amountInput.text.length > 0 signal pickerClicked + function setAmount(amount) { + amountInput.setAmount(amount) + } + spacing: 0 StatusPickerButton { @@ -31,29 +38,10 @@ ColumnLayout { onClicked: pickerClicked() } - StatusInput { + AmountInput { id: amountInput Layout.fillWidth: true Layout.topMargin: 8 - minimumHeight: 36 - maximumHeight: 36 - topPadding: 0 - bottomPadding: 0 - font.pixelSize: 13 - rightPadding: amountText.implicitWidth + amountText.anchors.rightMargin + leftPadding - input.placeholderText: "0" - validationMode: StatusInput.ValidationMode.IgnoreInvalidInput - validators: StatusFloatValidator { bottom: 0 } - - StatusBaseText { - id: amountText - anchors.right: parent.right - anchors.rightMargin: 13 - anchors.verticalCenter: parent.verticalCenter - text: qsTr("Amount") - color: Theme.palette.baseColor1 - font.pixelSize: 13 - } } } diff --git a/ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml b/ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml index d020479d5a6..9635253bdda 100644 --- a/ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml +++ b/ui/app/AppLayouts/Chat/views/communities/CommunityNewPermissionView.qml @@ -68,7 +68,7 @@ Flickable { switch (type) { case HoldingTypes.Type.Token: case HoldingTypes.Type.Collectible: - return qsTr("%1 %2").arg(amount.toString()).arg(name) + return `${LocaleUtils.numberToLocaleString(amount)} ${name}` case HoldingTypes.Type.Ens: if (name) return qsTr("ENS username on '%1' domain").arg(name) @@ -182,9 +182,6 @@ Flickable { const modelItem = tokensSelector.itemsModel.get(index) - dropdown.openFlow(HoldingsDropdown.FlowType.Update) - dropdown.setActiveTab(modelItem.type) - switch(modelItem.type) { case HoldingTypes.Type.Token: dropdown.tokenKey = modelItem.key @@ -204,6 +201,9 @@ Flickable { console.warn("Unsupported holdings type.") } + dropdown.openFlow(HoldingsDropdown.FlowType.Update) + dropdown.setActiveTab(modelItem.type) + editedIndex = index } } diff --git a/ui/imports/shared/controls/AmountInput.qml b/ui/imports/shared/controls/AmountInput.qml new file mode 100644 index 00000000000..a371fd5a8ee --- /dev/null +++ b/ui/imports/shared/controls/AmountInput.qml @@ -0,0 +1,81 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +import utils 1.0 + +Input { + id: root + + property int maximumLength: 10 + property var locale: Qt.locale() + + readonly property alias amount: d.amount + readonly property bool valid: validationError.length === 0 + property bool allowDecimals: true + + validationErrorTopMargin: 8 + fontPixelSize: 13 + customHeight: 36 + placeholderText: locale.zeroDigit + + textField.rightPadding: labelText.implicitWidth + labelText.anchors.rightMargin + + textField.leftPadding + + function setAmount(amount) { + root.text = LocaleUtils.numberToLocaleString(amount, -1, root.locale) + } + + QtObject { + id: d + + property real amount: 0 + } + + validator: DoubleValidator { + id: doubleValidator + + decimals: root.allowDecimals ? 100 : 0 + bottom: 0 + notation: DoubleValidator.StandardNotation + locale: root.locale.name + } + + onTextChanged: { + if (!allowDecimals) + text = text.replace(root.locale.decimalPoint, "") + + if(text.length === 0) { + d.amount = 0 + root.validationError = "" + return + } + + if (text.length > root.maximumLength) { + root.validationError = qsTr("The maximum number of characters is %1").arg(root.maximumLength) + return + } + + try { + d.amount = Number.fromLocaleString(root.locale, text) || 0 + root.validationError = "" + } catch (err) { + root.validationError = qsTr("Invalid amount format") + } + } + + StatusBaseText { + id: labelText + + parent: root.textField + + anchors.right: parent.right + anchors.rightMargin: 13 + anchors.verticalCenter: parent.verticalCenter + text: qsTr("Amount") + color: Theme.palette.baseColor1 + font.pixelSize: 13 + } +} diff --git a/ui/imports/shared/controls/qmldir b/ui/imports/shared/controls/qmldir index 25d352f9479..41b2ad4a955 100644 --- a/ui/imports/shared/controls/qmldir +++ b/ui/imports/shared/controls/qmldir @@ -1,4 +1,5 @@ AddressInput 1.0 AddressInput.qml +AmountInput 1.0 AmountInput.qml AssetAndAmountInput 1.0 AssetAndAmountInput.qml AssetDelegate 1.0 AssetDelegate.qml ContactSelector 1.0 ContactSelector.qml diff --git a/ui/imports/utils/LocaleUtils.qml b/ui/imports/utils/LocaleUtils.qml new file mode 100644 index 00000000000..5703b144ab5 --- /dev/null +++ b/ui/imports/utils/LocaleUtils.qml @@ -0,0 +1,22 @@ +pragma Singleton + +import QtQml 2.14 + +QtObject { + + function fractionalPartLength(num) { + if (Number.isInteger(num)) + return 0 + + return num.toString().split('.')[1].length + } + + function numberToLocaleString(num, precision = -1, locale = null) { + locale = locale || Qt.locale() + + if (precision === -1) + precision = fractionalPartLength(num) + + return num.toLocaleString(locale, 'f', precision) + } +} diff --git a/ui/imports/utils/qmldir b/ui/imports/utils/qmldir index e843f9b24da..00732b632c7 100644 --- a/ui/imports/utils/qmldir +++ b/ui/imports/utils/qmldir @@ -1,8 +1,9 @@ -singleton Style 1.0 Style.qml -singleton Utils 1.0 Utils.qml -singleton Global 1.0 Global.qml -singleton Constants 1.0 Constants.qml -singleton SelectedMessage 1.0 SelectedMessage.qml -singleton Backpressure 1.0 Backpressure/Backpressure.qml Audio 1.0 Audio.qml Tracer 1.0 Tracer.qml +singleton Backpressure 1.0 Backpressure/Backpressure.qml +singleton Constants 1.0 Constants.qml +singleton Global 1.0 Global.qml +singleton LocaleUtils 1.0 LocaleUtils.qml +singleton SelectedMessage 1.0 SelectedMessage.qml +singleton Style 1.0 Style.qml +singleton Utils 1.0 Utils.qml