fix: from/to account not showing correctly

Fixes #1202

I had to revert the loader changes that switched from/to as it was causes quite a lot of logistical complexity. Instead of using Loaders, we are setting the type of account (account or contact), and it is being displayed appropriately. There is a very slight deviation from the design, however it is consistent with the design for other transaction previews.

feat: add BalanceValidator

Shows an exclamation icon next to the "from" account when the balance for the requested asset is too low.

This is useful when the user starts the transaction wizard on the TransactionPreview step.
This commit is contained in:
Pascal Precht 2020-10-16 15:21:57 +02:00
parent 32a4afe037
commit 0a9852758d
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
8 changed files with 174 additions and 134 deletions

View File

@ -64,7 +64,9 @@ ModalPopup {
id: selectRecipient
accounts: walletModel.accounts
contacts: profileModel.addedContacts
label: qsTr("From")
label: root.isRequested ?
qsTr("From") :
qsTr("To")
readOnly: true
anchors.top: separator.bottom
anchors.topMargin: 10
@ -95,21 +97,31 @@ ModalPopup {
}
TransactionFormGroup {
id: group3
//% "Transaction preview"
headerText: qsTrId("transaction-preview")
headerText: root.isRequested ?
qsTr("Preview") :
//% "Transaction preview"
qsTrId("transaction-preview")
footerText: root.finalButtonLabel
TransactionPreview {
id: pvwTransaction
width: stack.width
fromAccount: selectFromAccount.selectedAccount
toAccount: selectRecipient.selectedRecipient
fromAccount: root.isRequested ? selectRecipient.selectedRecipient : selectFromAccount.selectedAccount
toAccount: root.isRequested ? selectFromAccount.selectedAccount : selectRecipient.selectedRecipient
asset: txtAmount.selectedAsset
amount: { "value": txtAmount.selectedAmount, "fiatValue": txtAmount.selectedFiatAmount }
currency: walletModel.defaultCurrency
reset: function() {
fromAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
toAccount = Qt.binding(function() { return selectRecipient.selectedRecipient })
fromAccount = Qt.binding(function() {
return root.isRequested ?
selectRecipient.selectedRecipient :
selectFromAccount.selectedAccount
})
toAccount = Qt.binding(function() {
return root.isRequested ?
selectFromAccount.selectedAccount :
selectRecipient.selectedRecipient
})
asset = Qt.binding(function() { return txtAmount.selectedAsset })
amount = Qt.binding(function() { return { "value": txtAmount.selectedAmount, "fiatValue": txtAmount.selectedFiatAmount } })
}

View File

@ -96,7 +96,6 @@ ModalPopup {
AccountSelector {
id: selectFromAccount
accounts: walletModel.accounts
selectedAccount: root.selectedAccount
currency: walletModel.defaultCurrency
width: stack.width
//% "Choose account"
@ -105,7 +104,6 @@ ModalPopup {
minRequiredAssetBalance: parseFloat(root.selectedAmount)
reset: function() {
accounts = Qt.binding(function() { return walletModel.accounts })
selectedAccount = Qt.binding(function() { return root.selectedAccount })
showBalanceForAssetSymbol = Qt.binding(function() { return root.selectedAsset.symbol })
minRequiredAssetBalance = Qt.binding(function() { return parseFloat(root.selectedAmount) })
}
@ -176,12 +174,12 @@ ModalPopup {
id: gasValidator
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
selectedAccount: root.selectedAccount
selectedAccount: selectFromAccount.selectedAccount
selectedAmount: parseFloat(root.selectedAmount)
selectedAsset: root.selectedAsset
selectedGasEthValue: gasSelector.selectedGasEthValue
reset: function() {
selectedAccount = Qt.binding(function() { return root.selectedAccount })
selectedAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
selectedAmount = Qt.binding(function() { return parseFloat(root.selectedAmount) })
selectedAsset = Qt.binding(function() { return root.selectedAsset })
selectedGasEthValue = Qt.binding(function() { return gasSelector.selectedGasEthValue })
@ -199,24 +197,23 @@ ModalPopup {
onNextClicked: function() {
stack.push(groupSignTx, StackView.Immediate)
}
isValid: groupSelectAcct.isValid && groupSelectGas.isValid && gasValidator.isValid && pvwTransaction.isValid
isValid: groupSelectAcct.isValid && groupSelectGas.isValid && pvwTransaction.isValid
TransactionPreview {
id: pvwTransaction
width: stack.width
fromAccount: root.selectedAccount
fromAccount: selectFromAccount.selectedAccount
gas: {
"value": gasSelector.selectedGasEthValue,
"symbol": "ETH",
"fiatValue": gasSelector.selectedGasFiatValue
}
toAccount: root.selectedRecipient
toAccount: selectRecipient.selectedRecipient
asset: root.selectedAsset
amount: { "value": root.selectedAmount, "fiatValue": root.selectedFiatAmount }
currency: walletModel.defaultCurrency
outgoing: root.outgoing
reset: function() {
fromAccount = Qt.binding(function() { return root.selectedAccount })
fromAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
gas = Qt.binding(function() {
return {
"value": gasSelector.selectedGasEthValue,
@ -224,7 +221,7 @@ ModalPopup {
"fiatValue": gasSelector.selectedGasFiatValue
}
})
toAccount = Qt.binding(function() { return root.selectedRecipient })
toAccount = Qt.binding(function() { return selectRecipient.selectedRecipient })
asset = Qt.binding(function() { return root.selectedAsset })
amount = Qt.binding(function() { return { "value": root.selectedAmount, "fiatValue": root.selectedFiatAmount } })
}
@ -237,12 +234,12 @@ ModalPopup {
id: gasValidator2
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
selectedAccount: root.selectedAccount
selectedAccount: selectFromAccount.selectedAccount
selectedAmount: parseFloat(root.selectedAmount)
selectedAsset: root.selectedAsset
selectedGasEthValue: gasSelector.selectedGasEthValue
reset: function() {
selectedAccount = Qt.binding(function() { return root.selectedAccount })
selectedAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
selectedAmount = Qt.binding(function() { return parseFloat(root.selectedAmount) })
selectedAsset = Qt.binding(function() { return root.selectedAsset })
selectedGasEthValue = Qt.binding(function() { return gasSelector.selectedGasEthValue })

View File

@ -39,8 +39,8 @@ Item {
switch (root.state) {
case Constants.pending:
case Constants.confirmed:
case Constants.addressRequested: return isCurrentUser
case Constants.transactionRequested:
case Constants.addressRequested: return isCurrentUser
case Constants.declined:
case Constants.transactionDeclined:
case Constants.addressReceived: return !isCurrentUser
@ -82,7 +82,7 @@ Item {
color: Style.current.secondaryText
text: {
if (root.state === Constants.transactionRequested) {
let prefix = root.outgoing ? "↑ " : "↓ "
let prefix = root.outgoing ? "↓ ": "↑ "
return prefix + qsTr("Transaction request")
}
return root.outgoing ?
@ -176,7 +176,7 @@ Item {
active: !root.isError && (
(root.state === Constants.addressRequested && !root.outgoing) ||
(root.state === Constants.addressReceived && root.outgoing) ||
(root.state === Constants.transactionRequested && root.outgoing)
(root.state === Constants.transactionRequested && !root.outgoing)
)
sourceComponent: root.outgoing ? signAndSendComponent : acceptTransactionComponent
anchors.top: bubbleLoader.active ? bubbleLoader.bottom : valueContainer.bottom

View File

@ -7,7 +7,6 @@ Item {
id: root
width: parent.width
height: childrenRect.height + Style.current.halfPadding
// property bool outgoing: true
Separator {
id: separator
@ -48,7 +47,6 @@ Item {
onOpened: {
walletModel.getGasPricePredictions()
}
selectedAccount: {}
selectedRecipient: {
return {
address: commandParametersObject.address,
@ -60,7 +58,6 @@ Item {
selectedAsset: token
selectedAmount: tokenAmount
selectedFiatAmount: fiatValue
outgoing: root.outgoing
}
}

View File

@ -66,6 +66,9 @@ Item {
console.warn(qsTrId("cannot-find-asset---1---ensure-this-asset-has-been-added-to-the-token-list-").arg(showBalanceForAssetSymbol))
}
}
if (!selectedAccount.type) {
selectedAccount.type = RecipientSelector.Type.Account
}
validate()
}

View File

@ -0,0 +1,54 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import "../imports"
import "./status"
IconButton {
id: root
property var account
property double amount
property var asset
property bool isValid: true
property var reset: function() {}
clickable: false
width: 13.33
height: 13.33
iconWidth: width
iconHeight: height
iconName: "exclamation_outline"
color: Style.current.transparent
visible: !isValid
onAccountChanged: validate()
onAmountChanged: validate()
onAssetChanged: validate()
function resetInternal() {
account = undefined
amount = 0
asset = undefined
isValid = true
}
function validate() {
let isValid = true
if (!(account && account.assets && asset && amount > 0)) {
return root.isValid
}
const currAcctAsset = Utils.findAssetBySymbol(account.assets, asset.symbol)
if (currAcctAsset && currAcctAsset.value < amount) {
isValid = false
}
root.isValid = isValid
return isValid
}
StatusToolTip {
id: tooltip
visible: parent.hovered
width: 100
text: qsTr("Insufficient balance")
}
}

View File

@ -15,7 +15,7 @@ RoundButton {
icon.width: iconWidth
icon.height: iconHeight
id: btnAddContainer
id: root
width: 36
height: 36
radius: width / 2
@ -29,8 +29,10 @@ RoundButton {
id: imgIcon
fillMode: Image.PreserveAspectFit
source: "../app/img/" + parent.iconName + ".svg"
width: btnAddContainer.iconWidth
height: btnAddContainer.iconHeight
width: root.iconWidth
height: root.iconHeight
sourceSize.height: height * 2
sourceSize.width: width * 2
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
@ -76,14 +78,14 @@ RoundButton {
}
onClicked: {
if (btnAddContainer.clickable) {
if (root.clickable) {
imgIcon.state = "rotated"
}
}
MouseArea {
id: mouseArea
visible: btnAddContainer.clickable
visible: root.clickable
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor

View File

@ -23,8 +23,7 @@ Item {
// Creates a mouse area around the "network fee". When clicked, triggers
// the "gasClicked" signal
property bool isGasEditable: false
property bool isValid: true
property bool outgoing: true
property alias isValid: balanceValidator.isValid
function resetInternal() {
fromAccount = undefined
@ -32,38 +31,14 @@ Item {
asset = undefined
amount = undefined
gas = undefined
isValid = true
balanceValidator.resetInternal()
balanceValidator.reset()
}
function validate() {
let isValid = true
imgInsufficientBalance.visible = false
console.log(">>> [TransactionPreview.validate] outgoing:", outgoing)
if (outgoing && hasInsufficientBalance()) {
isValid = false
imgInsufficientBalance.visible = true
}
root.isValid = isValid
return isValid
}
function hasInsufficientBalance() {
if (!root.asset || !root.fromAccount || !root.fromAccount.assets || !root.amount) {
return true
}
const currAcctAsset = Utils.findAssetBySymbol(root.fromAccount.assets, root.asset.symbol)
if (!currAcctAsset) return true
return currAcctAsset.value < root.amount.value
}
onAssetChanged: validate()
onFromAccountChanged: validate()
Column {
id: content
anchors.left: parent.left
anchors.right: parent.right
LabelValueRow {
id: itmFrom
//% "From"
@ -71,9 +46,8 @@ Item {
value: Item {
id: itmFromValue
anchors.fill: parent
anchors.verticalCenter: parent.verticalCenter
function needsRightPadding() {
return imgInsufficientBalance.visible || fromArrow.visible
return !balanceValidator.isValid || fromArrow.visible
}
Row {
spacing: Style.current.halfPadding
@ -94,24 +68,30 @@ Item {
id: imgFromWallet
sourceSize.height: 18
sourceSize.width: 18
visible: !!root.fromAccount ? root.fromAccount.type === RecipientSelector.Type.Account : true
horizontalAlignment: Image.AlignLeft
width: itmFromValue.needsRightPadding() ? (Style.current.halfPadding + sourceSize.width) : undefined // adding width to add addl spacing to image
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../app/img/walletIcon.svg"
ColorOverlay {
visible: parent.visible
anchors.fill: parent
source: parent
color: root.fromAccount ? root.fromAccount.iconColor : Style.current.blue
color: root.fromAccount && root.fromAccount.iconColor ? root.fromAccount.iconColor : Style.current.blue
}
}
SVGImage {
id: imgInsufficientBalance
width: 13
visible: false
BalanceValidator {
id: balanceValidator
account: root.fromAccount
amount: !!(root.amount && root.amount.value) ? parseFloat(root.amount.value) : 0.0
asset: root.asset
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../app/img/exclamation_outline.svg"
reset: function() {
account = Qt.binding(function() { return root.fromAccount })
amount = Qt.binding(function() { return !!(root.amount && root.amount.value) ? parseFloat(root.amount.value) : 0.0 })
asset = Qt.binding(function() { return root.asset })
}
}
SVGImage {
id: fromArrow
@ -210,74 +190,69 @@ Item {
}
}
]
value: Item {
id: recipientRoot
anchors.fill: parent
anchors.verticalCenter: parent.verticalCenter
StyledText {
id: txtToPrimary
font.pixelSize: 15
height: 22
anchors.left: parent.left
anchors.right: txtToSeparator.left
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
StyledText {
id: txtToSeparator
font.pixelSize: 15
height: 22
text: " • "
visible: txtToSecondary.visible && txtToSecondary.width > 0
color: Style.current.secondaryText
anchors.right: txtToSecondary.left
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
id: txtToSecondary
visible: true
font.pixelSize: 15
height: 22
color: Style.current.secondaryText
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
}
TextMetrics {
id: metSecondary
elideWidth: 102
elide: Text.ElideMiddle
}
SVGImage {
id: imgToWallet
visible: false
sourceSize.height: 18
sourceSize.width: 18
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../app/img/walletIcon.svg"
}
ColorOverlay {
id: ovlToWallet
anchors.fill: imgToWallet
visible: false
source: imgToWallet
}
StatusImageIdenticon {
id: idtToContact
visible: false
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: 32
height: 32
}
StyledText {
id: txtToPrimary
font.pixelSize: 15
height: 22
anchors.left: parent.left
anchors.right: txtToSeparator.left
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
StyledText {
id: txtToSeparator
font.pixelSize: 15
height: 22
text: " • "
visible: txtToSecondary.visible && txtToSecondary.width > 0
color: Style.current.secondaryText
anchors.right: txtToSecondary.left
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
id: txtToSecondary
visible: true
font.pixelSize: 15
height: 22
color: Style.current.secondaryText
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignRight
verticalAlignment: Text.AlignVCenter
}
TextMetrics {
id: metSecondary
elideWidth: 102
elide: Text.ElideMiddle
}
SVGImage {
id: imgToWallet
visible: false
sourceSize.height: 18
sourceSize.width: 18
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: "../app/img/walletIcon.svg"
}
ColorOverlay {
id: ovlToWallet
anchors.fill: imgToWallet
visible: false
source: imgToWallet
}
StatusImageIdenticon {
id: idtToContact
visible: false
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
width: 32
height: 32
}
}
LabelValueRow {