From 17aaec2d536f496c9defa54f020afddc55cadd66 Mon Sep 17 00:00:00 2001 From: Khushboo Mehta Date: Wed, 14 Dec 2022 22:06:14 +0100 Subject: [PATCH] feat(@desktop/wallet): Send modal update fixes #8609 --- .../ens_usernames/io_interface.nim | 4 + .../profile_section/ens_usernames/module.nim | 14 ++ .../profile_section/ens_usernames/view.nim | 3 + .../service/transaction/async_tasks.nim | 42 ++++- src/app_service/service/transaction/dto.nim | 19 +- .../src/StatusQ/Controls/StatusBaseInput.qml | 4 +- .../Profile/stores/EnsUsernamesStore.qml | 6 + .../shared/controls/AmountInputWithCursor.qml | 6 +- ui/imports/shared/controls/GasSelector.qml | 13 +- .../shared/panels/StatusAssetSelector.qml | 68 ++++--- ui/imports/shared/popups/SendModal.qml | 169 +++++++----------- ui/imports/shared/stores/TransactionStore.qml | 4 + ui/imports/shared/views/AmountToReceive.qml | 77 ++++++++ ui/imports/shared/views/AmountToSend.qml | 145 +++++++++++++++ ui/imports/shared/views/FeesView.qml | 1 + ui/imports/shared/views/NetworkSelector.qml | 2 + .../NetworksAdvancedCustomRoutingView.qml | 1 + .../views/NetworksSimpleRoutingView.qml | 18 +- ui/imports/utils/Utils.qml | 11 ++ 19 files changed, 453 insertions(+), 154 deletions(-) create mode 100644 ui/imports/shared/views/AmountToReceive.qml create mode 100644 ui/imports/shared/views/AmountToSend.qml diff --git a/src/app/modules/main/profile_section/ens_usernames/io_interface.nim b/src/app/modules/main/profile_section/ens_usernames/io_interface.nim index fba4e7a381..cbe7768fac 100644 --- a/src/app/modules/main/profile_section/ens_usernames/io_interface.nim +++ b/src/app/modules/main/profile_section/ens_usernames/io_interface.nim @@ -85,6 +85,10 @@ method getFiatValue*(self: AccessInterface, cryptoBalance: string, cryptoSymbol: {.base.} = raise newException(ValueError, "No implementation available") +method getCryptoValue*(self: AccessInterface, fiatAmount: string, cryptoSymbol: string, fiatSymbol: string): string + {.base.} = + raise newException(ValueError, "No implementation available") + method getGasEthValue*(self: AccessInterface, gweiValue: string, gasLimit: string): string {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/profile_section/ens_usernames/module.nim b/src/app/modules/main/profile_section/ens_usernames/module.nim index fe8f332014..a9366a051e 100644 --- a/src/app/modules/main/profile_section/ens_usernames/module.nim +++ b/src/app/modules/main/profile_section/ens_usernames/module.nim @@ -339,6 +339,20 @@ method getFiatValue*(self: Module, cryptoBalance: string, cryptoSymbol: string, let value = floatCryptoBalance * price return fmt"{value}" +method getCryptoValue*(self: Module, fiatAmount: string, cryptoSymbol: string, fiatSymbol: string): string = + var fiatAmountBalance: float = 0 + try: + fiatAmountBalance = parseFloat(fiatAmount) + except ValueError: + return "0.00" + + if (fiatAmount == "" or cryptoSymbol == "" or fiatSymbol == ""): + return "0.00" + + let price = self.controller.getPrice(cryptoSymbol, fiatSymbol) + let value = fiatAmountBalance / price + return fmt"{value}" + method getGasEthValue*(self: Module, gweiValue: string, gasLimit: string): string {.slot.} = var gasLimitInt:int diff --git a/src/app/modules/main/profile_section/ens_usernames/view.nim b/src/app/modules/main/profile_section/ens_usernames/view.nim index 6e58e717ce..5cd0860c50 100644 --- a/src/app/modules/main/profile_section/ens_usernames/view.nim +++ b/src/app/modules/main/profile_section/ens_usernames/view.nim @@ -126,6 +126,9 @@ QtObject: proc getFiatValue*(self: View, cryptoBalance: string, cryptoSymbol: string, fiatSymbol: string): string {.slot.} = return self.delegate.getFiatValue(cryptoBalance, cryptoSymbol, fiatSymbol) + proc getCryptoValue*(self: View, fiatAmount: string, cryptoSymbol: string, fiatSymbol: string): string {.slot.} = + return self.delegate.getCryptoValue(fiatAmount, cryptoSymbol, fiatSymbol) + proc getGasEthValue*(self: View, gweiValue: string, gasLimit: string): string {.slot.} = return self.delegate.getGasEthValue(gweiValue, gasLimit) diff --git a/src/app_service/service/transaction/async_tasks.nim b/src/app_service/service/transaction/async_tasks.nim index 94ef285f36..f5672756ec 100644 --- a/src/app_service/service/transaction/async_tasks.nim +++ b/src/app_service/service/transaction/async_tasks.nim @@ -62,6 +62,39 @@ proc getFeesTotal*(paths: seq[TransactionPathDto]): Fees = fees.totalTime += path.estimatedTime return fees +proc getTotalAmountToReceive*(paths: seq[TransactionPathDto]): UInt256 = + var totalAmountToReceive: UInt256 = stint.u256(0) + for path in paths: + totalAmountToReceive += path.amountOut + + return totalAmountToReceive + +proc getToNetworksList*(paths: seq[TransactionPathDto]): seq[SendToNetwork] = + var networkMap: Table[int, SendToNetwork] = initTable[int, SendToNetwork]() + for path in paths: + if(networkMap.hasKey(path.toNetwork.chainId)): + networkMap[path.toNetwork.chainId].amountOut = networkMap[path.toNetwork.chainId].amountOut + path.amountOut + else: + networkMap[path.toNetwork.chainId] = SendToNetwork(chainId: path.toNetwork.chainId, chainName: path.toNetwork.chainName, iconUrl: path.toNetwork.iconURL, amountOut: path.amountOut) + return toSeq(networkMap.values) + +proc addFirstSimpleBridgeTxFlag(paths: seq[TransactionPathDto]) : seq[TransactionPathDto] = + let txPaths = paths + var firstSimplePath: bool = false + var firstBridgePath: bool = false + + for path in txPaths: + if path.bridgeName == "Simple": + if not firstSimplePath: + firstSimplePath = true + path.isFirstSimpleTx = true + else: + if not firstBridgePath: + firstBridgePath = false + path.isFirstBridgeTx = true + + return txPaths + const getSuggestedRoutesTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[GetSuggestedRoutesTaskArg](argEncoded) @@ -86,16 +119,17 @@ const getSuggestedRoutesTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall bestPaths.sort(sortAsc[TransactionPathDto]) let output = %*{ "suggestedRoutes": SuggestedRoutesDto( - best: bestPaths, - candidates: response["Candidates"].getElems().map(x => x.toTransactionPathDto()), - gasTimeEstimate: getFeesTotal(bestPaths)), + best: addFirstSimpleBridgeTxFlag(bestPaths), + gasTimeEstimate: getFeesTotal(bestPaths), + amountToReceive: getTotalAmountToReceive(bestPaths), + toNetworks: getToNetworksList(bestPaths)), "error": "" } arg.finish(output) except Exception as e: let output = %* { - "suggestedRoutes": SuggestedRoutesDto(best: @[], candidates: @[], gasTimeEstimate: Fees()), + "suggestedRoutes": SuggestedRoutesDto(best: @[], gasTimeEstimate: Fees(), amountToReceive: stint.u256(0), toNetworks: @[]), "error": fmt"Error getting suggested routes: {e.msg}" } arg.finish(output) diff --git a/src/app_service/service/transaction/dto.nim b/src/app_service/service/transaction/dto.nim index 694733bed7..caed3b1bb2 100644 --- a/src/app_service/service/transaction/dto.nim +++ b/src/app_service/service/transaction/dto.nim @@ -164,7 +164,8 @@ type cost*: float estimatedTime*: int amountInLocked*: bool - + isFirstSimpleTx*: bool + isFirstBridgeTx*: bool proc `$`*(self: TransactionPathDto): string = return fmt"""TransactionPath( @@ -179,7 +180,9 @@ proc `$`*(self: TransactionPathDto): string = bonderFees:{self.bonderFees}, cost:{self.cost}, estimatedTime:{self.estimatedTime}, - amountInLocked:{self.amountInLocked} + amountInLocked:{self.amountInLocked}, + isFirstSimpleTx:{self.isFirstSimpleTx}, + isFirstBridgeTx:{self.isFirstBridgeTx} )""" proc toTransactionPathDto*(jsonObj: JsonNode): TransactionPathDto = @@ -197,6 +200,8 @@ proc toTransactionPathDto*(jsonObj: JsonNode): TransactionPathDto = result.estimatedTime = jsonObj{"EstimatedTime"}.getInt discard jsonObj.getProp("GasAmount", result.gasAmount) discard jsonObj.getProp("AmountInLocked", result.amountInLocked) + result.isFirstSimpleTx = false + result.isFirstBridgeTx = false proc convertToTransactionPathDto*(jsonObj: JsonNode): TransactionPathDto = result = TransactionPathDto() @@ -219,8 +224,16 @@ type totalTokenFees*: float totalTime*: int +type + SendToNetwork* = ref object + chainId*: int + chainName*: string + iconUrl*: string + amountOut*: UInt256 + type SuggestedRoutesDto* = ref object best*: seq[TransactionPathDto] - candidates*: seq[TransactionPathDto] gasTimeEstimate*: Fees + amountToReceive*: UInt256 + toNetworks*: seq[SendToNetwork] diff --git a/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml b/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml index 9760ab4294..c079d60397 100644 --- a/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml +++ b/ui/StatusQ/src/StatusQ/Controls/StatusBaseInput.qml @@ -157,7 +157,7 @@ Item { \qmlproperty real StatusBaseInput::leftPadding This property sets the leftComponentLoader's left padding. */ - property real leftPadding: leftComponentLoader.item ? 8 : 16 + property real leftPadding: leftComponentLoader.item ? 6 : 16 /*! \qmlproperty real StatusBaseInput::rightPadding This property sets the right padding. @@ -308,7 +308,7 @@ Item { spacing: 2 anchors { fill: parent - leftMargin: root.leftPadding ? root.leftPadding : leftComponentLoader.item ? 6 : 16 + leftMargin: root.leftPadding rightMargin: root.rightPadding } diff --git a/ui/app/AppLayouts/Profile/stores/EnsUsernamesStore.qml b/ui/app/AppLayouts/Profile/stores/EnsUsernamesStore.qml index 1e671ef751..8d75e78423 100644 --- a/ui/app/AppLayouts/Profile/stores/EnsUsernamesStore.qml +++ b/ui/app/AppLayouts/Profile/stores/EnsUsernamesStore.qml @@ -122,6 +122,12 @@ QtObject { return ensUsernamesModule.getFiatValue(balance, cryptoSymbol, fiatSymbol) } + function getCryptoValue(balance, cryptoSymbol, fiatSymbol) { + if(!root.ensUsernamesModule) + return "" + return ensUsernamesModule.getCryptoValue(balance, cryptoSymbol, fiatSymbol) + } + function getGasEthValue(gweiValue, gasLimit) { if(!root.ensUsernamesModule) return "" diff --git a/ui/imports/shared/controls/AmountInputWithCursor.qml b/ui/imports/shared/controls/AmountInputWithCursor.qml index dad23ea1eb..6cf1112c80 100644 --- a/ui/imports/shared/controls/AmountInputWithCursor.qml +++ b/ui/imports/shared/controls/AmountInputWithCursor.qml @@ -5,6 +5,8 @@ import StatusQ.Controls 0.1 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 +import utils 1.0 + StatusInput { id: cursorInput @@ -16,8 +18,8 @@ StatusInput { placeholderText: "" input.edit.objectName: "amountInput" input.edit.cursorVisible: true - input.edit.font.pixelSize: 32 - input.placeholderFont.pixelSize: 32 + input.edit.font.pixelSize: Utils.getFontSizeBasedOnLetterCount(text) + input.placeholderFont.pixelSize: 34 input.leftPadding: 0 input.rightPadding: 0 input.topPadding: 0 diff --git a/ui/imports/shared/controls/GasSelector.qml b/ui/imports/shared/controls/GasSelector.qml index af27843712..d36ae070e9 100644 --- a/ui/imports/shared/controls/GasSelector.qml +++ b/ui/imports/shared/controls/GasSelector.qml @@ -43,7 +43,10 @@ Item { id: listItem color: Theme.palette.statusListItem.backgroundColor width: parent.width - asset.name: index == 0 ? "tiny/gas" : "" + asset.name: "tiny/gas" + asset.color: Theme.palette.directColor1 + statusListItemIcon.active: true + statusListItemIcon.opacity: modelData.isFirstSimpleTx title: qsTr("%1 transaction fee").arg(modelData.fromNetwork.chainName) subTitle: "%1 eth".arg(LocaleUtils.numberToLocaleString(parseFloat(totalGasAmount))) property string totalGasAmount : { @@ -69,12 +72,16 @@ Item { // Bridge Repeater { + id: bridgeRepeater model: root.bestRoutes - StatusListItem { + delegate: StatusListItem { id: listItem2 color: Theme.palette.statusListItem.backgroundColor width: parent.width - asset.name: index == 0 ? "tiny/bridge" : "" + asset.name: "tiny/bridge" + asset.color: Theme.palette.directColor1 + statusListItemIcon.active: true + statusListItemIcon.opacity: modelData.isFirstBridgeTx title: qsTr("%1 -> %2 bridge").arg(modelData.fromNetwork.chainName).arg(modelData.toNetwork.chainName) subTitle: "%1 %2".arg(LocaleUtils.numberToLocaleString(modelData.tokenFees)).arg(root.selectedTokenSymbol) visible: modelData.bridgeName !== "Simple" diff --git a/ui/imports/shared/panels/StatusAssetSelector.qml b/ui/imports/shared/panels/StatusAssetSelector.qml index 187a4f1b5d..6cc5fe14e6 100644 --- a/ui/imports/shared/panels/StatusAssetSelector.qml +++ b/ui/imports/shared/panels/StatusAssetSelector.qml @@ -12,6 +12,7 @@ import StatusQ.Components 0.1 import StatusQ.Core.Backpressure 1.0 import shared.controls 1.0 +import utils 1.0 Item { id: root @@ -51,6 +52,7 @@ Item { property string iconSource: "" property string text: "" property string searchString + property bool isTokenSelected: !!root.selectedAsset readonly property var updateSearchText: Backpressure.debounce(root, 1000, function(inputText) { d.searchString = inputText @@ -60,8 +62,8 @@ Item { StatusComboBox { id: comboBox objectName: "assetSelectorButton" - width: control.width - height: parent.height + width: d.isTokenSelected ? rowLayout.implicitWidth : 116 + height: 34 control.padding: 4 control.popup.width: 342 @@ -86,30 +88,17 @@ Item { control.background: Rectangle { color: "transparent" - border.width: 1 - border.color: comboBox.control.hovered ? Theme.palette.primaryColor2 : Theme.palette.directColor8 - radius: 16 - width: rowLayout.width - implicitHeight: 48 + border.width: d.isTokenSelected ? 0 : 1 + border.color: d.isTokenSelected ? "transparent" : Theme.palette.directColor7 + radius: 12 } contentItem: RowLayout { id: rowLayout - spacing: 8 - StatusBaseText { - Layout.leftMargin: 8 - Layout.alignment: Qt.AlignVCenter - font.pixelSize: 15 - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - color: !!root.selectedAsset ? Theme.palette.directColor1: Theme.palette.baseColor1 - font.weight: Font.Medium - text: !!root.selectedAsset ? d.text : placeholderText - } StatusRoundedImage { - Layout.preferredWidth: 40 - Layout.preferredHeight: 40 - Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: 21 + Layout.preferredHeight: 21 + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft visible: !!d.iconSource image.source: d.iconSource image.onStatusChanged: { @@ -118,9 +107,34 @@ Item { } } } - Item { - width: 8 - height: 0 + StatusBaseText { + Layout.alignment: Qt.AlignVCenter + font.pixelSize: 28 + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + color: Theme.palette.miscColor1 + text: d.text + visible: d.isTokenSelected + } + StatusIcon { + Layout.leftMargin: -3 + Layout.alignment: Qt.AlignVCenter + icon: "chevron-down" + width: 16 + height: 16 + color: Theme.palette.miscColor1 + visible: d.isTokenSelected + } + StatusBaseText { + Layout.maximumWidth: comboBox.width - Style.current.padding + Layout.alignment: Qt.AlignCenter + visible: !d.isTokenSelected + font.pixelSize: 15 + font.weight: Font.Medium + verticalAlignment: Text.AlignVCenter + color: Theme.palette.baseColor1 + elide: Qt.ElideRight + text: placeholderText } } @@ -215,11 +229,9 @@ Item { placeholderText: qsTr("Search for token or enter token address") onTextChanged: Qt.callLater(d.updateSearchText, text) input.clearable: true - leftPadding: 0 - rightPadding: 0 input.rightComponent: StatusIcon { - width: 24 - height: 24 + width: 16 + height: 16 color: Theme.palette.baseColor1 icon: "search" } diff --git a/ui/imports/shared/popups/SendModal.qml b/ui/imports/shared/popups/SendModal.qml index 0cc87ef840..dbd46e1ede 100644 --- a/ui/imports/shared/popups/SendModal.qml +++ b/ui/imports/shared/popups/SendModal.qml @@ -60,7 +60,7 @@ StatusDialog { popup.selectedAccount.address, recipientAddress, assetSelector.selectedAsset.symbol, - amountToSendInput.text, + amountToSendInput.cryptoValueToSend, d.uuid, JSON.stringify(popup.bestRoutes) ) @@ -69,7 +69,7 @@ StatusDialog { property var recalculateRoutesAndFees: Backpressure.debounce(popup, 600, function() { if(!!popup.selectedAccount && !!assetSelector.selectedAsset && d.recipientReady) { popup.isLoading = true - let amount = parseFloat(amountToSendInput.text) * Math.pow(10, assetSelector.selectedAsset.decimals) + let amount = Math.round(parseFloat(amountToSendInput.cryptoValueToSend) * Math.pow(10, assetSelector.selectedAsset.decimals)) popup.store.suggestedRoutes(popup.selectedAccount.address, amount.toString(16), assetSelector.selectedAsset.symbol, store.disabledChainIdsFromList, store.disabledChainIdsToList, store.preferredChainIds, popup.sendType, store.lockedInAmounts) @@ -78,13 +78,15 @@ StatusDialog { QtObject { id: d - readonly property double maxFiatBalance: assetSelector.selectedAsset ? assetSelector.selectedAsset.totalBalance: 0 + readonly property double maxFiatBalance: !!assetSelector.selectedAsset ? (amountToSendInput.cryptoFiatFlipped ? + assetSelector.selectedAsset.totalCurrencyBalance : + assetSelector.selectedAsset.totalBalance): 0 onMaxFiatBalanceChanged: { - floatValidator.top = maxFiatBalance - amountToSendInput.validate() + amountToSendInput.floatValidator.top = maxFiatBalance + amountToSendInput.input.validate() } - readonly property bool isReady: amountToSendInput.valid && !amountToSendInput.pending && recipientReady - readonly property bool errorMode: popup.isLoading || !isReady ? false : (networkSelector.bestRoutes && networkSelector.bestRoutes.length <= 0) || networkSelector.errorMode || isNaN(amountToSendInput.text) + readonly property bool isReady: amountToSendInput.input.valid && !amountToSendInput.input.pending && recipientReady + readonly property bool errorMode: popup.isLoading || !isReady ? false : (networkSelector.bestRoutes && networkSelector.bestRoutes.length <= 0) || networkSelector.errorMode || isNaN(amountToSendInput.input.text) readonly property bool recipientReady: (isAddressValid || isENSValid) && !recipientSelector.isPending property bool isAddressValid: Utils.isValidAddress(popup.addressText) property bool isENSValid: false @@ -97,6 +99,7 @@ StatusDialog { property string totalTimeEstimate property string totalFeesInEth property string totalFeesInFiat + property double totalAmountToReceive: 0 property Timer waitTimer: Timer { interval: 1500 @@ -124,14 +127,14 @@ StatusDialog { store.setDefaultPreferredDisabledChains() } - amountToSendInput.input.edit.forceActiveFocus() + amountToSendInput.input.input.edit.forceActiveFocus() if(!!popup.preSelectedAsset) { assetSelector.selectedAsset = popup.preSelectedAsset } if(!!popup.preDefinedAmountToSend) { - amountToSendInput.text = popup.preDefinedAmountToSend + amountToSendInput.input.text = popup.preDefinedAmountToSend } if(!!popup.preSelectedRecipient) { @@ -190,63 +193,30 @@ StatusDialog { anchors.leftMargin: Style.current.xlPadding anchors.rightMargin: Style.current.xlPadding z: 1 + spacing: 16 - Row { - spacing: 16 + RowLayout { + width: parent.width + spacing: 8 StatusBaseText { id: modalHeader - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft text: popup.isBridgeTx ? qsTr("Bridge") : qsTr("Send") - font.pixelSize: 15 + font.pixelSize: 28 + lineHeight: 38 + lineHeightMode: Text.FixedHeight + font.letterSpacing: -0.4 color: Theme.palette.directColor1 - } - StatusListItemTag { - bgColor: d.errorMode ? Theme.palette.dangerColor2 : Theme.palette.primaryColor3 - height: 22 - width: childrenRect.width - title: d.maxFiatBalance > 0 ? qsTr("Max: %1").arg(LocaleUtils.numberToLocaleString(d.maxFiatBalance)) : qsTr("No balances active") - closeButtonVisible: false - titleText.font.pixelSize: 12 - titleText.color: d.errorMode ? Theme.palette.dangerColor1 : Theme.palette.primaryColor1 - } - } - Item { - width: parent.width - height: amountToSendInput.height - AmountInputWithCursor { - id: amountToSendInput - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: -Style.current.padding - width: parent.width - assetSelector.width - placeholderText: assetSelector.selectedAsset ? "%1 %2".arg(LocaleUtils.numberToLocaleString(0, 2)).arg(assetSelector.selectedAsset.symbol) : LocaleUtils.numberToLocaleString(0, 2) - input.edit.color: d.errorMode ? Theme.palette.dangerColor1 : Theme.palette.directColor1 - input.edit.readOnly: !popup.interactive - validators: [ - StatusFloatValidator{ - id: floatValidator - bottom: 0 - top: d.maxFiatBalance - errorMessage: "" - } - ] - Keys.onReleased: { - let amount = amountToSendInput.text.trim() - - if (!Utils.containsOnlyDigits(amount) || isNaN(amount)) { - return - } - popup.recalculateRoutesAndFees() - } + Layout.maximumWidth: contentWidth } StatusAssetSelector { id: assetSelector - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop | Qt.AlignLeft enabled: popup.interactive assets: popup.selectedAccount && popup.selectedAccount.assets ? popup.selectedAccount.assets : [] defaultToken: Style.png("tokens/DEFAULT-TOKEN@3x") - placeholderText: popup.isBridgeTx ? qsTr("Select token to bridge") : qsTr("Select token to send") + placeholderText: qsTr("Select token") getCurrencyBalanceString: function (currencyBalance) { return "%1 %2".arg(Utils.toLocaleString(currencyBalance.toFixed(2), popup.store.locale, {"currency": true})).arg(popup.store.currentCurrency.toUpperCase()) } @@ -260,59 +230,48 @@ StatusDialog { return "" } onSelectedAssetChanged: { - if (!assetSelector.selectedAsset) { - return - } - if (amountToSendInput.text === "" || isNaN(amountToSendInput.text)) { + if (!assetSelector.selectedAsset || !!amountToSendInput.input.text || isNaN(amountToSendInput.input.text)) { return } popup.recalculateRoutesAndFees() } } - } - StatusInput { - id: txtFiatBalance - anchors.left: parent.left - anchors.leftMargin: -12 - leftPadding: 0 - rightPadding: 0 - font.weight: Font.Medium - font.pixelSize: 13 - input.placeholderFont.pixelSize: 13 - input.leftPadding: 0 - input.rightPadding: 0 - input.topPadding: 0 - input.bottomPadding: 0 - input.edit.padding: 0 - input.background.color: "transparent" - input.background.border.width: 0 - input.edit.color: txtFiatBalance.input.edit.activeFocus ? Theme.palette.directColor1 : Theme.palette.baseColor1 - input.edit.readOnly: true - text: { - if(!!assetSelector.selectedAsset) { - let fiatValue = popup.store.getFiatValue(amountToSendInput.text, assetSelector.selectedAsset.symbol, popup.store.currentCurrency) - return parseFloat(fiatValue) === 0 ? LocaleUtils.numberToLocaleString(parseFloat(fiatValue), 2) : LocaleUtils.numberToLocaleString(parseFloat(fiatValue)) - } - return LocaleUtils.numberToLocaleString(0, 2) - } - input.implicitHeight: Style.current.bigPadding - implicitWidth: txtFiatBalance.input.edit.contentWidth + 50 - input.rightComponent: StatusBaseText { - id: currencyText - text: popup.store.currentCurrency.toUpperCase() - font.pixelSize: 13 - color: Theme.palette.directColor5 - } - Keys.onReleased: { - let balance = txtFiatBalance.text.trim() - if (balance === "" || isNaN(balance)) { - return - } - // To-Do Not refactored yet - // amountToSendInput.text = root.getCryptoValue(balance, popup.store.currentCurrency, assetSelector.selectedAsset.symbol) + StatusListItemTag { + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.preferredHeight: 22 + title: d.maxFiatBalance > 0 ? qsTr("Max: %1").arg(LocaleUtils.numberToLocaleString(d.maxFiatBalance)) : qsTr("No balances active") + closeButtonVisible: false + titleText.font.pixelSize: 12 + bgColor: d.errorMode ? Theme.palette.dangerColor2 : Theme.palette.primaryColor3 + titleText.color: d.errorMode ? Theme.palette.dangerColor1 : Theme.palette.primaryColor1 + } + } + RowLayout { + width: parent.width + AmountToSend { + id: amountToSendInput + Layout.fillWidth:true + isBridgeTx: popup.isBridgeTx + interactive: popup.interactive + store: popup.store + selectedAsset: assetSelector.selectedAsset + errorMode: d.errorMode + maxFiatBalance: d.maxFiatBalance + onReCalculateSuggestedRoute: popup.recalculateRoutesAndFees() + } + AmountToReceive { + id: amountToReceive + Layout.alignment: Qt.AlignRight + Layout.fillWidth:true + visible: popup.bestRoutes !== undefined && popup.bestRoutes.length > 0 + store: popup.store + isLoading: popup.isLoading + selectedAsset: assetSelector.selectedAsset + isBridgeTx: popup.isBridgeTx + amountToReceive: d.totalAmountToReceive + cryptoFiatFlipped: amountToSendInput.cryptoFiatFlipped } } - TokenListView { id: tokenListRect anchors.left: parent.left @@ -336,7 +295,7 @@ StatusDialog { StatusScrollView { id: scrollView - topPadding: 0 + topPadding: 12 Layout.fillHeight: true Layout.preferredWidth: parent.width contentHeight: layout.height + Style.current.padding @@ -441,7 +400,7 @@ StatusDialog { store: popup.store interactive: popup.interactive selectedAccount: popup.selectedAccount - amountToSend: isNaN(parseFloat(amountToSendInput.text)) ? 0 : parseFloat(amountToSendInput.text) + amountToSend: isNaN(parseFloat(amountToSendInput.cryptoValueToSend)) ? 0 : parseFloat(amountToSendInput.cryptoValueToSend) requiredGasInEth:d.totalFeesInEth selectedAsset: assetSelector.selectedAsset onReCalculateSuggestedRoute: popup.recalculateRoutesAndFees() @@ -476,7 +435,7 @@ StatusDialog { maxFiatFees: popup.isLoading ? "..." : "%1 %2".arg(LocaleUtils.numberToLocaleString(d.totalFeesInFiat)).arg(popup.store.currentCurrency.toUpperCase()) totalTimeEstimate: popup.isLoading? "..." : d.totalTimeEstimate pending: d.isPendingTx || popup.isLoading - visible: d.isReady && !isNaN(amountToSendInput.text) && fees.isValid && !d.errorMode + visible: d.isReady && !isNaN(amountToSendInput.cryptoValueToSend) && fees.isValid && !d.errorMode onNextButtonClicked: popup.sendTransaction() } @@ -499,6 +458,8 @@ StatusDialog { d.totalFeesInEth = gasTimeEstimate.totalFeesInEth d.totalFeesInFiat = parseFloat(popup.store.getFiatValue( gasTimeEstimate.totalFeesInEth, "ETH", popup.store.currentCurrency)) + parseFloat(popup.store.getFiatValue(gasTimeEstimate.totalTokenFees, fees.selectedTokenSymbol, popup.store.currentCurrency)) + d.totalAmountToReceive = popup.store.getWei2Eth(response.suggestedRoutes.amountToReceive, assetSelector.selectedAsset.decimals) + networkSelector.toNetworksList = response.suggestedRoutes.toNetworks popup.isLoading = false } } diff --git a/ui/imports/shared/stores/TransactionStore.qml b/ui/imports/shared/stores/TransactionStore.qml index 3dfc5cdd5e..1b53b93d4c 100644 --- a/ui/imports/shared/stores/TransactionStore.qml +++ b/ui/imports/shared/stores/TransactionStore.qml @@ -69,6 +69,10 @@ QtObject { return profileSectionStore.ensUsernamesStore.getFiatValue(balance, cryptoSymbol, fiatSymbol) } + function getCryptoValue(balance, cryptoSymbol, fiatSymbol) { + return profileSectionStore.ensUsernamesStore.getCryptoValue(balance, cryptoSymbol, fiatSymbol) + } + function getGasEthValue(gweiValue, gasLimit) { return profileSectionStore.ensUsernamesStore.getGasEthValue(gweiValue, gasLimit) } diff --git a/ui/imports/shared/views/AmountToReceive.qml b/ui/imports/shared/views/AmountToReceive.qml new file mode 100644 index 0000000000..65923a8911 --- /dev/null +++ b/ui/imports/shared/views/AmountToReceive.qml @@ -0,0 +1,77 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.13 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 + +import utils 1.0 + +ColumnLayout { + id: root + + property var store + property var selectedAsset + property bool isLoading: false + property string amountToReceive + property bool isBridgeTx: false + property bool cryptoFiatFlipped: false + + QtObject { + id: d + function formatValue(value) { + const precision = (value === 0 ? 2 : 0) + return LocaleUtils.numberToLocaleString(value, precision) + } + readonly property string fiatValue: { + if(!root.selectedAsset || !amountToReceive) + return formatValue(0) + let cryptoValue = root.store.getFiatValue(amountToReceive, root.selectedAsset.symbol, root.store.currentCurrency) + return formatValue(parseFloat(cryptoValue)) + } + } + + StatusBaseText { + Layout.alignment: Qt.AlignRight | Qt.AlignTop + text: root.isBridgeTx ? qsTr("Amount Bridged") : qsTr("Recipient will get") + font.pixelSize: 13 + lineHeight: 18 + lineHeightMode: Text.FixedHeight + color: Theme.palette.directColor1 + } + RowLayout { + Layout.alignment: Qt.AlignRight + Layout.preferredHeight: 42 + StatusBaseText { + id: amountToReceiveText + Layout.alignment: Qt.AlignVCenter + text: isLoading ? "..." : cryptoFiatFlipped ? d.fiatValue: amountToReceive + font.pixelSize: Utils.getFontSizeBasedOnLetterCount(text) + color: Theme.palette.directColor1 + } + StatusBaseText { + Layout.alignment: Qt.AlignVCenter + text: isLoading ? "..." : root.store.currentCurrency.toUpperCase() + font.pixelSize: amountToReceiveText.font.pixelSize + color: Theme.palette.directColor1 + visible: cryptoFiatFlipped + } + } + RowLayout { + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + StatusBaseText { + id: txtFiatBalance + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + text: isLoading ? "..." : cryptoFiatFlipped ? amountToReceive : d.fiatValue + font.pixelSize: 13 + color: Theme.palette.directColor5 + } + StatusBaseText { + Layout.alignment: Qt.AlignTop + Layout.leftMargin: 4 + text: isLoading ? "..." : !cryptoFiatFlipped ? root.store.currentCurrency.toUpperCase() : !!root.selectedAsset ? root.selectedAsset.symbol.toUpperCase() : "" + font.pixelSize: 13 + color: Theme.palette.directColor5 + } + } +} + diff --git a/ui/imports/shared/views/AmountToSend.qml b/ui/imports/shared/views/AmountToSend.qml new file mode 100644 index 0000000000..1f65fa8262 --- /dev/null +++ b/ui/imports/shared/views/AmountToSend.qml @@ -0,0 +1,145 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.13 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls.Validators 0.1 + +import "../controls" + +import utils 1.0 + +ColumnLayout { + id: root + + property alias input: amountToSendInput + property alias floatValidator: floatValidator + + property var store + property var selectedAsset + property bool errorMode + property bool isBridgeTx: false + property bool interactive: false + property double maxFiatBalance + property bool cryptoFiatFlipped: false + property string cryptoValueToSend: !cryptoFiatFlipped ? amountToSendInput.text : txtFiatBalance.text + + signal reCalculateSuggestedRoute() + + QtObject { + id: d + readonly property string zeroString: formatValue(0, 2) + property Timer waitTimer: Timer { + interval: 1000 + onTriggered: reCalculateSuggestedRoute() + } + function formatValue(value, precision) { + const precisionVal = !!precision ? precision : (value === 0 ? 2 : 0) + return LocaleUtils.numberToLocaleString(value, precisionVal) + } + function getFiatValue(value) { + if(!root.selectedAsset || !value) + return zeroString + let cryptoValue = root.store.getFiatValue(value, root.selectedAsset.symbol, root.store.currentCurrency) + return formatValue(parseFloat(cryptoValue)) + } + function getCryptoValue(value) { + if(!root.selectedAsset || !value) + return zeroString + let cryptoValue = root.store.getCryptoValue(value, root.selectedAsset.symbol, root.store.currentCurrency) + return formatValue(parseFloat(cryptoValue)) + } + } + + onSelectedAssetChanged: { + if(!!root.selectedAsset) { + txtFiatBalance.text = !cryptoFiatFlipped ? d.getFiatValue(amountToSendInput.text): d.getCryptoValue(amountToSendInput.text) + } + } + + StatusBaseText { + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + text: root.isBridgeTx ? qsTr("Amount to bridge") : qsTr("Amount to send") + font.pixelSize: 13 + lineHeight: 18 + lineHeightMode: Text.FixedHeight + color: Theme.palette.directColor1 + } + RowLayout { + Layout.alignment: Qt.AlignLeft + AmountInputWithCursor { + id: amountToSendInput + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.maximumWidth: 163 + Layout.preferredWidth: (!!text) ? input.edit.paintedWidth : textMetrics.advanceWidth + placeholderText: d.zeroString + input.edit.color: root.errorMode ? Theme.palette.dangerColor1 : Theme.palette.directColor1 + input.edit.readOnly: !root.interactive + validators: [ + StatusFloatValidator { + id: floatValidator + bottom: 0 + top: root.maxFiatBalance + errorMessage: "" + } + ] + TextMetrics { + id: textMetrics + text: amountToSendInput.placeholderText + font: amountToSendInput.input.placeholder.font + } + Keys.onReleased: { + const amount = amountToSendInput.text.trim() + if (!Utils.containsOnlyDigits(amount) || isNaN(amount)) { + return + } + txtFiatBalance.text = !cryptoFiatFlipped ? d.getFiatValue(amount): d.getCryptoValue(amount) + d.waitTimer.restart() + } + } + StatusBaseText { + Layout.alignment: Qt.AlignVCenter + text: root.store.currentCurrency.toUpperCase() + font.pixelSize: amountToSendInput.input.edit.font.pixelSize + color: Theme.palette.baseColor1 + visible: cryptoFiatFlipped + } + } + Item { + id: fiatBalanceLayout + Layout.alignment: Qt.AlignLeft | Qt.AlignBottom + Layout.preferredWidth: txtFiatBalance.width + currencyText.width + Layout.preferredHeight: txtFiatBalance.height + StatusBaseText { + id: txtFiatBalance + anchors.top: parent.top + anchors.left: parent.left + text: d.getFiatValue(amountToSendInput.text) + font.pixelSize: 13 + color: Theme.palette.directColor5 + } + StatusBaseText { + id: currencyText + anchors.top: parent.top + anchors.left: txtFiatBalance.right + anchors.leftMargin: 4 + text: !cryptoFiatFlipped ? root.store.currentCurrency.toUpperCase() : !!root.selectedAsset ? root.selectedAsset.symbol.toUpperCase() : "" + font.pixelSize: 13 + color: Theme.palette.directColor5 + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + cryptoFiatFlipped = !cryptoFiatFlipped + amountToSendInput.validate() + if(!!amountToSendInput.text) { + const tempVal = Number.fromLocaleString(txtFiatBalance.text) + txtFiatBalance.text = !!amountToSendInput.text ? amountToSendInput.text : d.zeroString + amountToSendInput.text = tempVal + } + } + } + } +} + diff --git a/ui/imports/shared/views/FeesView.qml b/ui/imports/shared/views/FeesView.qml index 65b16562f9..218eb7d76b 100644 --- a/ui/imports/shared/views/FeesView.qml +++ b/ui/imports/shared/views/FeesView.qml @@ -36,6 +36,7 @@ Rectangle { Layout.alignment: Qt.AlignTop radius: 8 asset.name: "fees" + asset.color: Theme.palette.directColor1 } Column { Layout.alignment: Qt.AlignTop | Qt.AlignHCenter diff --git a/ui/imports/shared/views/NetworkSelector.qml b/ui/imports/shared/views/NetworkSelector.qml index 8a79bb6e72..92ac85f057 100644 --- a/ui/imports/shared/views/NetworkSelector.qml +++ b/ui/imports/shared/views/NetworkSelector.qml @@ -28,6 +28,7 @@ Item { property bool interactive: true property bool isBridgeTx: false property bool showUnpreferredNetworks: advancedNetworkRoutingPage.showUnpreferredNetworks + property var toNetworksList: [] signal reCalculateSuggestedRoute() @@ -79,6 +80,7 @@ Item { selectedAsset: root.selectedAsset selectedAccount: root.selectedAccount errorMode: root.errorMode + toNetworksList: root.toNetworksList weiToEth: function(wei) { return "%1 %2".arg(LocaleUtils.numberToLocaleString(parseFloat(store.getWei2Eth(wei, selectedAsset.decimals)))).arg(selectedAsset.symbol) } diff --git a/ui/imports/shared/views/NetworksAdvancedCustomRoutingView.qml b/ui/imports/shared/views/NetworksAdvancedCustomRoutingView.qml index 9368304937..5685a2eda0 100644 --- a/ui/imports/shared/views/NetworksAdvancedCustomRoutingView.qml +++ b/ui/imports/shared/views/NetworksAdvancedCustomRoutingView.qml @@ -37,6 +37,7 @@ ColumnLayout { Layout.alignment: Qt.AlignTop radius: 8 asset.name: "flash" + asset.color: Theme.palette.directColor1 } ColumnLayout { Layout.alignment: Qt.AlignTop diff --git a/ui/imports/shared/views/NetworksSimpleRoutingView.qml b/ui/imports/shared/views/NetworksSimpleRoutingView.qml index c6b141119c..0d3016bfb3 100644 --- a/ui/imports/shared/views/NetworksSimpleRoutingView.qml +++ b/ui/imports/shared/views/NetworksSimpleRoutingView.qml @@ -22,6 +22,7 @@ RowLayout { property bool isBridgeTx: false property var selectedAsset property var selectedAccount + property var toNetworksList: [] property var weiToEth: function(wei) {} property var reCalculateSuggestedRoute: function() {} property bool errorMode: false @@ -31,6 +32,7 @@ RowLayout { Layout.alignment: Qt.AlignTop radius: 8 asset.name: "flash" + asset.color: Theme.palette.directColor1 } ColumnLayout { Layout.alignment: Qt.AlignTop @@ -61,13 +63,13 @@ RowLayout { ScrollBar.horizontal.policy: ScrollBar.AsNeeded clip: true visible: !root.isLoading ? root.isBridgeTx ? true : root.bestRoutes !== undefined ? root.bestRoutes.length > 0 : true : false - Row { + Column { id: row spacing: Style.current.padding Repeater { id: repeater objectName: "networksList" - model: isBridgeTx ? store.allNetworks : root.bestRoutes + model: isBridgeTx ? store.allNetworks : root.toNetworksList delegate: isBridgeTx ? networkItem : routeItem } } @@ -89,15 +91,15 @@ RowLayout { Component { id: routeItem StatusListItem { - objectName: modelData.toNetwork.chainName + objectName: modelData.chainName leftPadding: 5 rightPadding: 5 - implicitWidth: 126 - title: modelData.toNetwork.chainName + implicitWidth: 410 + title: modelData.chainName subTitle: { - let index = store.lockedInAmounts.findIndex(lockedItem => lockedItem !== undefined && lockedItem.chainID === modelData.toNetwork.chainId) + let index = store.lockedInAmounts.findIndex(lockedItem => lockedItem !== undefined && lockedItem.chainID === modelData.chainId) if(!root.errorMode || index === -1) - return root.weiToEth(modelData.amountIn) + return root.weiToEth(modelData.amountOut) else { return root.weiToEth(parseInt(store.lockedInAmounts[index].value, 16)) } @@ -105,7 +107,7 @@ RowLayout { statusListItemSubTitle.color: root.errorMode ? Theme.palette.dangerColor1 : Theme.palette.primaryColor1 asset.width: 32 asset.height: 32 - asset.name: Style.svg("tiny/" + modelData.toNetwork.iconUrl) + asset.name: Style.svg("tiny/" + modelData.iconUrl) asset.isImage: true color: "transparent" } diff --git a/ui/imports/utils/Utils.qml b/ui/imports/utils/Utils.qml index 492104e60b..4c03267f77 100644 --- a/ui/imports/utils/Utils.qml +++ b/ui/imports/utils/Utils.qml @@ -800,6 +800,17 @@ QtObject { } } + function getFontSizeBasedOnLetterCount(text) { + if(text.length >= 12) + return 18 + if(text.length >= 10) + return 24 + if(text.length > 6) + return 28 + else + return 34 + } + // Leave this function at the bottom of the file as QT Creator messes up the code color after this function isPunct(c) { return /(!|\@|#|\$|%|\^|&|\*|\(|\)|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c)