fix/tx-comps: Update assets when tokens changed

When tokens are added/removed, the asset list in the AssetAndAmountInput is updated.

The selected asset can be specified by the parent component which is needed for things like sticker market where we need to set SNT as the fixed token.

Improved the validation for the component:
 - validate() can be called externally
 - validation display is handled internally and messages can be customised
 - validation error messages are handled by the Input component and validation UX is consistent with other tx components
This commit is contained in:
emizzle 2020-08-12 19:05:12 +10:00 committed by Iuri Matias
parent d07daac377
commit 3ff93c26e6
5 changed files with 214 additions and 169 deletions

View File

@ -217,6 +217,8 @@ QtObject:
proc toggleAsset*(self: WalletView, symbol: string, checked: bool, address: string, name: string, decimals: int, color: string) {.slot.} =
self.status.wallet.toggleAsset(symbol, checked, address, name, decimals, color)
for account in self.status.wallet.accounts:
self.accounts.updateAssetsInList(account.address, account.assetList)
proc updateView*(self: WalletView) =
self.totalFiatBalanceChanged()

View File

@ -1,7 +1,7 @@
import NimQml, Tables, random, strformat, json_serialization
import sequtils as sequtils
import account_item, asset_list
from ../../../status/wallet import WalletAccount
from ../../../status/wallet import WalletAccount, Asset
const accountColors* = ["#9B832F", "#D37EF4", "#1D806F", "#FA6565", "#7CDA00", "#887af9", "#8B3131"]
type
@ -99,3 +99,18 @@ QtObject:
proc forceUpdate*(self: AccountList) =
self.beginResetModel()
self.endResetModel()
proc hasAccount*(self: AccountList, address: string): bool =
result = self.accounts.anyIt(it.account.address == address)
proc updateAssetsInList*(self: AccountList, address: string, assets: seq[Asset]) =
if not self.hasAccount(address):
return
let topLeft = self.createIndex(0, 0, nil)
let bottomRight = self.createIndex(self.accounts.len, 0, nil)
self.accounts.apply(proc(it: var AccountView) =
if it.account.address == address:
it.assets.setNewData(assets))
self.dataChanged(topLeft, bottomRight, @[AccountRoles.Assets.int])

View File

@ -35,7 +35,8 @@ Item {
}
function validate() {
selectRecipient.validate()
const isRecipientValid = selectRecipient.validate()
const isAssetAndAmountValid = txtAmount.validate()
if (txtPassword.text === "") {
//% "You need to enter a password"
passwordValidationError = qsTrId("you-need-to-enter-a-password")
@ -46,20 +47,7 @@ Item {
passwordValidationError = ""
}
if (txtAmount.text === "") {
//% "You need to enter an amount"
amountValidationError = qsTrId("you-need-to-enter-an-amount")
} else if (isNaN(txtAmount.text)) {
//% "This needs to be a number"
amountValidationError = qsTrId("this-needs-to-be-a-number")
} else if (parseFloat(txtAmount.text) > parseFloat(selectAsset.selectedAsset.Value)) {
//% "Amount needs to be lower than your balance (%1)"
amountValidationError = qsTrId("amount-needs-to-be-lower-than-your-balance-(%1)").arg(selectedAccountValue)
} else {
amountValidationError = ""
}
return passwordValidationError === "" && toValidationError === "" && amountValidationError === ""
return passwordValidationError === "" && toValidationError === "" && amountValidationError === "" && isRecipientValid && isAssetAndAmountValid
}
anchors.left: parent.left
@ -83,13 +71,12 @@ Item {
}
AssetAndAmountInput {
id: txtAmount
selectedAccount: walletModel.currentAccount
defaultCurrency: walletModel.defaultCurrency
errorMessage: amountValidationError
anchors.top: parent.top
getFiatValue: walletModel.getFiatValue
getCryptoValue: walletModel.getCryptoValue
id: txtAmount
selectedAccount: walletModel.currentAccount
defaultCurrency: walletModel.defaultCurrency
anchors.top: parent.top
getFiatValue: walletModel.getFiatValue
getCryptoValue: walletModel.getCryptoValue
}
AccountSelector {
@ -101,7 +88,7 @@ Item {
anchors.left: parent.left
anchors.right: parent.right
onSelectedAccountChanged: {
txtAmount.selectedAccount = selectFromAccount.selectedAccount
txtAmount.selectedAccount = selectFromAccount.selectedAccount
}
}

View File

@ -5,157 +5,193 @@ import QtGraphicalEffects 1.13
import "../imports"
Item {
property string errorMessage: ""
property string defaultCurrency: "USD"
property string fiatBalance: "0.00"
property alias text: inputAmount.text
property var selectedAccount
property var getFiatValue: function () {}
property var getCryptoValue: function () {}
property string balanceErrorMessage: qsTr("Insufficient balance")
property string greaterThan0ErrorMessage: qsTr("Must be greater than 0")
//% "This needs to be a number"
property string invalidInputErrorMessage: qsTrId("this-needs-to-be-a-number")
//% "You need to enter an amount"
property string noInputErrorMessage: qsTrId("you-need-to-enter-an-amount")
property string defaultCurrency: "USD"
property string fiatBalance: "0.00"
property alias text: inputAmount.text
property var selectedAccount
property alias selectedAsset: selectAsset.selectedAsset
property var getFiatValue: function () {}
property var getCryptoValue: function () {}
property bool isDirty: false
id: root
id: root
height: inputAmount.height + txtFiatBalance.height + txtFiatBalance.anchors.topMargin
anchors.right: parent.right
anchors.left: parent.left
height: inputAmount.height + (inputAmount.validationError ? -16 - inputAmount.validationErrorTopMargin : 0) + txtFiatBalance.height + txtFiatBalance.anchors.topMargin
anchors.right: parent.right
anchors.left: parent.left
onSelectedAccountChanged: {
txtBalance.text = selectAsset.selectedAsset.value
}
Item {
anchors.right: parent.right
anchors.left: parent.left
anchors.top: parent.top
height: txtBalanceDesc.height
StyledText {
id: txtBalanceDesc
text: qsTr("Balance: ")
anchors.right: txtBalance.left
font.weight: Font.Medium
font.pixelSize: 13
color: parseFloat(inputAmount.text) > parseFloat(txtBalance.text) ? Style.current.red : Style.current.secondaryText
}
StyledText {
id: txtBalance
property bool hovered: false
text: selectAsset.selectedAsset.value
anchors.right: parent.right
font.weight: Font.Medium
font.pixelSize: 13
color: {
if (txtBalance.hovered) {
return Style.current.textColor
}
return parseFloat(inputAmount.text) > parseFloat(txtBalance.text) ? Style.current.red : Style.current.secondaryText
}
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
hoverEnabled: true
onExited: {
txtBalance.hovered = false
}
onEntered: {
txtBalance.hovered = true
}
onClicked: {
inputAmount.text = selectAsset.selectedAsset.value
txtFiatBalance.text = root.getFiatValue(inputAmount.text, selectAsset.selectedAsset.symbol, root.defaultCurrency)
}
}
}
}
Input {
id: inputAmount
label: qsTr("Asset & Amount")
placeholderText: "0.00"
validationError: root.errorMessage
anchors.top: parent.top
customHeight: 56
Keys.onReleased: {
let amount = inputAmount.text.trim()
if (isNaN(amount)) {
return
function validate(checkDirty) {
let isValid = true
let error = ""
const hasTyped = checkDirty ? isDirty : true
const balance = parseFloat(txtBalance.text || "0.00")
const input = parseFloat(inputAmount.text || "0.00")
const noInput = inputAmount.text === ""
if (noInput && hasTyped) {
error = noInputErrorMessage
isValid = false
} else if (isNaN(inputAmount.text)) {
error = invalidInputErrorMessage
isValid = false
} else if (input === 0.00 && hasTyped) {
error = greaterThan0ErrorMessage
isValid = false
} else if (input > balance && !noInput) {
error = balanceErrorMessage
isValid = false
}
if (amount === "") {
txtFiatBalance.text = "0.00"
if (!isValid) {
inputAmount.validationError = error
txtBalanceDesc.color = Style.current.danger
txtBalance.color = Style.current.danger
} else {
txtFiatBalance.text = root.getFiatValue(amount, selectAsset.selectedAsset.symbol, root.defaultCurrency)
inputAmount.validationError = ""
txtBalanceDesc.color = Style.current.secondaryText
txtBalance.color = Qt.binding(function() { return txtBalance.hovered ? Style.current.textColor : Style.current.secondaryText })
}
}
}
return isValid
}
AssetSelector {
id: selectAsset
assets: root.selectedAccount.assets
width: 86
height: 28
anchors.top: inputAmount.top
anchors.topMargin: Style.current.bigPadding + 14
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
onSelectedAssetChanged: {
inputAmount.text = selectAsset.selectedAsset.value
txtBalance.text = selectAsset.selectedAsset.value
txtFiatBalance.text = root.getFiatValue(inputAmount.text, selectAsset.selectedAsset.symbol, root.defaultCurrency)
}
}
onSelectedAccountChanged: {
if (!selectAsset.selectedAsset) {
return
}
txtBalance.text = selectAsset.selectedAsset.value
}
Item {
height: txtFiatBalance.height
anchors.left: parent.left
anchors.top: inputAmount.bottom
anchors.topMargin: inputAmount.labelMargin
Item {
anchors.right: parent.right
anchors.left: parent.left
anchors.top: parent.top
height: txtBalanceDesc.height
StyledTextField {
id: txtFiatBalance
anchors.left: parent.left
anchors.top: parent.top
color: txtFiatBalance.activeFocus ? Style.current.textColor : Style.current.secondaryText
font.weight: Font.Medium
font.pixelSize: 12
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: root.fiatBalance
selectByMouse: true
background: Rectangle {
color: Style.current.transparent
}
padding: 0
Keys.onReleased: {
let balance = txtFiatBalance.text.trim()
if (balance === "" || isNaN(balance)) {
return
}
inputAmount.text = root.getCryptoValue(balance, root.defaultCurrency, selectAsset.selectedAsset.symbol)
}
}
StyledText {
id: txtBalanceDesc
text: qsTr("Balance: ")
anchors.right: txtBalance.left
font.weight: Font.Medium
font.pixelSize: 13
color: Style.current.secondaryText
}
StyledText {
id: txtFiatSymbol
text: root.defaultCurrency.toUpperCase()
font.weight: Font.Medium
font.pixelSize: 12
color: Style.current.secondaryText
anchors.top: parent.top
anchors.left: txtFiatBalance.right
anchors.leftMargin: 2
}
}
StyledText {
id: txtBalance
property bool hovered: false
text: selectAsset.selectedAsset ? selectAsset.selectedAsset.value : "0.00"
anchors.right: parent.right
font.weight: Font.Medium
font.pixelSize: 13
color: hovered ? Style.current.textColor : Style.current.secondaryText
onTextChanged: {
root.validate(true)
}
StyledText {
text: root.errorMessage != "" ? root.errorMessage : qsTr("Insufficient balance")
anchors.right: parent.right
anchors.top: inputAmount.bottom
anchors.topMargin: inputAmount.labelMargin
font.weight: Font.Medium
font.pixelSize: 12
color: Style.current.red
visible: parseFloat(inputAmount.text) > parseFloat(txtBalance.text) || root.errorMessage != ""
}
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
hoverEnabled: true
onExited: {
txtBalance.hovered = false
}
onEntered: {
txtBalance.hovered = true
}
onClicked: {
inputAmount.text = selectAsset.selectedAsset.value
txtFiatBalance.text = root.getFiatValue(inputAmount.text, selectAsset.selectedAsset.symbol, root.defaultCurrency)
}
}
}
}
Input {
id: inputAmount
label: qsTr("Asset & Amount")
placeholderText: "0.00"
anchors.top: parent.top
customHeight: 56
validationErrorAlignment: TextEdit.AlignRight
validationErrorTopMargin: 8
Keys.onReleased: {
root.isDirty = true
let amount = inputAmount.text.trim()
if (isNaN(amount)) {
return
}
if (amount === "") {
txtFiatBalance.text = "0.00"
} else {
txtFiatBalance.text = root.getFiatValue(amount, selectAsset.selectedAsset.symbol, root.defaultCurrency)
}
}
onTextChanged: {
root.validate(true)
}
}
AssetSelector {
id: selectAsset
assets: root.selectedAccount.assets
width: 86
height: 28
anchors.top: inputAmount.top
anchors.topMargin: Style.current.bigPadding + 14
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
onSelectedAssetChanged: {
txtBalance.text = selectAsset.selectedAsset.value
if (inputAmount.text === "" || isNan(inputAmount.text)) {
return
}
txtFiatBalance.text = root.getFiatValue(inputAmount.text, selectAsset.selectedAsset.symbol, root.defaultCurrency)
}
}
Item {
height: txtFiatBalance.height
anchors.left: parent.left
anchors.top: inputAmount.bottom
anchors.topMargin: inputAmount.validationError ? -16 : inputAmount.validationErrorTopMargin
StyledTextField {
id: txtFiatBalance
anchors.left: parent.left
anchors.top: parent.top
color: txtFiatBalance.activeFocus ? Style.current.textColor : Style.current.secondaryText
font.weight: Font.Medium
font.pixelSize: 12
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: root.fiatBalance
selectByMouse: true
background: Rectangle {
color: Style.current.transparent
}
padding: 0
Keys.onReleased: {
let balance = txtFiatBalance.text.trim()
if (balance === "" || isNaN(balance)) {
return
}
inputAmount.text = root.getCryptoValue(balance, root.defaultCurrency, selectAsset.selectedAsset.symbol)
}
}
StyledText {
id: txtFiatSymbol
text: root.defaultCurrency.toUpperCase()
font.weight: Font.Medium
font.pixelSize: 12
color: Style.current.secondaryText
anchors.top: parent.top
anchors.left: txtFiatBalance.right
anchors.leftMargin: 2
}
}
}

View File

@ -11,6 +11,13 @@ Item {
width: 86
height: 24
onSelectedAssetChanged: {
if (selectedAsset && selectedAsset.symbol) {
iconImg.source = "../app/img/tokens/" + selectedAsset.symbol.toUpperCase() + ".png"
selectedTextField.text = selectedAsset.symbol.toUpperCase()
}
}
Select {
id: select
model: root.assets
@ -32,12 +39,10 @@ Item {
sourceSize.width: 24
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../app/img/tokens/" + selectedAsset.symbol.toUpperCase() + ".png"
}
StyledText {
id: selectedTextField
text: selectedAsset.symbol.toUpperCase()
anchors.left: iconImg.right
anchors.leftMargin: 4
anchors.verticalCenter: parent.verticalCenter
@ -58,7 +63,7 @@ Item {
property bool isLastItem: index === assets.rowCount() - 1
Component.onCompleted: {
if (isFirstItem) {
if (!selectedAsset && isFirstItem) {
root.selectedAsset = { address, name, value, symbol, fiatBalanceDisplay, fiatBalance }
}
}