diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandModal.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandModal.qml index 08deef5a5e..4f0e19a122 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandModal.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/ChatCommandModal.qml @@ -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 } }) } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/SignTransactionModal.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/SignTransactionModal.qml index 5823895ec3..ed94e222a0 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/SignTransactionModal.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatComponents/SignTransactionModal.qml @@ -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 }) diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml index ed49117510..5a7bb60b5b 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionBubble.qml @@ -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 diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/SendTransactionButton.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/SendTransactionButton.qml index eabeb08319..4436dd6fa4 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/SendTransactionButton.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/TransactionComponents/SendTransactionButton.qml @@ -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 } } diff --git a/ui/shared/AccountSelector.qml b/ui/shared/AccountSelector.qml index 8a929384d6..c43cabc8fe 100644 --- a/ui/shared/AccountSelector.qml +++ b/ui/shared/AccountSelector.qml @@ -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() } diff --git a/ui/shared/BalanceValidator.qml b/ui/shared/BalanceValidator.qml new file mode 100644 index 0000000000..55315c56ec --- /dev/null +++ b/ui/shared/BalanceValidator.qml @@ -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") + } +} diff --git a/ui/shared/IconButton.qml b/ui/shared/IconButton.qml index 47629d2816..8c2b30f50e 100644 --- a/ui/shared/IconButton.qml +++ b/ui/shared/IconButton.qml @@ -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 diff --git a/ui/shared/TransactionPreview.qml b/ui/shared/TransactionPreview.qml index b4f9cc8782..c4b23412ec 100644 --- a/ui/shared/TransactionPreview.qml +++ b/ui/shared/TransactionPreview.qml @@ -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 {