feat(@desktop/wallet): Implement the advanced/custom view with simple same network transfers

fixes #6268
This commit is contained in:
Khushboo Mehta 2022-07-01 13:24:32 +02:00 committed by Khushboo-dev-cpp
parent 4f3a6b46d3
commit 35a5ab57f0
24 changed files with 840 additions and 301 deletions

View File

@ -9,6 +9,7 @@ QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
all: Model
enabled: Model
layer1: Model
layer2: Model
@ -23,6 +24,7 @@ QtObject:
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.delegate = delegate
result.all = newModel()
result.layer1 = newModel()
result.layer2 = newModel()
result.enabled = newModel()
@ -41,6 +43,15 @@ QtObject:
self.areTestNetworksEnabled = areTestNetworksEnabled
self.areTestNetworksEnabledChanged()
proc allChanged*(self: View) {.signal.}
proc getAll(self: View): QVariant {.slot.} =
return newQVariant(self.all)
QtProperty[QVariant] all:
read = getAll
notify = allChanged
proc layer1Changed*(self: View) {.signal.}
proc getLayer1(self: View): QVariant {.slot.} =
@ -88,10 +99,12 @@ QtObject:
balance,
))
self.all.setItems(items)
self.layer1.setItems(items.filter(i => i.getLayer() == 1))
self.layer2.setItems(items.filter(i => i.getLayer() == 2))
self.enabled.setItems(items.filter(i => i.getIsEnabled()))
self.allChanged()
self.layer1Changed()
self.layer2Changed()
self.enabledChanged()

View File

@ -97,8 +97,8 @@ proc suggestedFees*(self: Controller, chainId: int): string =
let suggestedFees = self.transactionService.suggestedFees(chainId)
return suggestedFees.toJson()
proc suggestedRoutes*(self: Controller, account: string, amount: float64, token: string): string =
let suggestedRoutes = self.transactionService.suggestedRoutes(account, amount, token)
proc suggestedRoutes*(self: Controller, account: string, amount: float64, token: string, disabledChainIDs: seq[uint64]): string =
let suggestedRoutes = self.transactionService.suggestedRoutes(account, amount, token, disabledChainIDs)
return suggestedRoutes.toJson()
proc getChainIdForChat*(self: Controller): int =

View File

@ -55,7 +55,7 @@ method transactionWasSent*(self: AccessInterface, result: string) {.base.} =
method suggestedFees*(self: AccessInterface, chainId: int): string {.base.} =
raise newException(ValueError, "No implementation available")
method suggestedRoutes*(self: AccessInterface, account: string, amount: float64, token: string): string {.base.} =
method suggestedRoutes*(self: AccessInterface, account: string, amount: float64, token: string, disabledChainIDs: seq[uint64]): string {.base.} =
raise newException(ValueError, "No implementation available")
method getChainIdForChat*(self: AccessInterface): int =

View File

@ -102,8 +102,8 @@ method transactionWasSent*(self: Module, result: string) =
method suggestedFees*(self: Module, chainId: int): string =
return self.controller.suggestedFees(chainId)
method suggestedRoutes*(self: Module, account: string, amount: float64, token: string): string =
return self.controller.suggestedRoutes(account, amount, token)
method suggestedRoutes*(self: Module, account: string, amount: float64, token: string, disabledChainIDs: seq[uint64]): string =
return self.controller.suggestedRoutes(account, amount, token, disabledChainIDs)
method getChainIdForChat*(self: Module): int =
return self.controller.getChainIdForChat()

View File

@ -1,4 +1,4 @@
import NimQml, tables, stint, json, strformat, sequtils, strutils
import NimQml, tables, stint, json, strformat, sequtils, strutils, sugar
import ./item
import ./model
@ -125,15 +125,22 @@ QtObject:
proc suggestedFees*(self: View, chainId: int): string {.slot.} =
return self.delegate.suggestedFees(chainId)
proc suggestedRoutes*(self: View, account: string, amount: string, token: string): string {.slot.} =
proc suggestedRoutes*(self: View, account: string, amount: string, token: string, disabledChainIDs: string): string {.slot.} =
var parsedAmount = 0.0
var seqDisabledChainIds = seq[uint64] : @[]
try:
for chainID in disabledChainIDs.split(','):
seqDisabledChainIds.add(parseUInt(chainID))
except:
discard
try:
parsedAmount = parsefloat(amount)
except:
discard
return self.delegate.suggestedRoutes(account, parsedAmount, token)
return self.delegate.suggestedRoutes(account, parsedAmount, token, seqDisabledChainIds)
proc getChainIdForChat*(self: View): int {.slot.} =
return self.delegate.getChainIdForChat()

View File

@ -111,3 +111,17 @@ QtObject:
return true
return false
proc hasGas*(self: Model, chainId: int, nativeGasSymbol: string, requiredGas: float): bool {.slot.} =
for item in self.items:
if(item.getSymbol() != nativeGasSymbol):
continue
for balance in item.getBalances().items:
if (balance.chainId != chainId):
continue
if(balance.balance >= requiredGas):
return true
return false

View File

@ -369,8 +369,8 @@ QtObject:
eip1559Enabled: response{"eip1559Enabled"}.getbool,
)
proc suggestedRoutes*(self: Service, account: string, amount: float64, token: string): SuggestedRoutes =
let response = eth.suggestedRoutes(account, amount, token)
proc suggestedRoutes*(self: Service, account: string, amount: float64, token: string, disabledChainIDs: seq[uint64]): SuggestedRoutes =
let response = eth.suggestedRoutes(account, amount, token, disabledChainIDs)
return SuggestedRoutes(
networks: Json.decode($response.result{"networks"}, seq[NetworkDto])
)

View File

@ -28,6 +28,6 @@ proc suggestedFees*(chainId: int): RpcResponse[JsonNode] {.raises: [Exception].}
let payload = %* [chainId]
return core.callPrivateRPC("wallet_getSuggestedFees", payload)
proc suggestedRoutes*(account: string, amount: float64, token: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [account, amount, token]
proc suggestedRoutes*(account: string, amount: float64, token: string, disabledChainIDs: seq[uint64]): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [account, amount, token, disabledChainIDs]
return core.callPrivateRPC("wallet_getSuggestedRoutes", payload)

@ -1 +1 @@
Subproject commit 8fc073a749c5499c4cd3f43bd3382e215476e1fa
Subproject commit e0fd627050b45440132acce69c8c317f4efe129a

View File

@ -6,6 +6,8 @@ import StatusQ.Core.Utils 0.1 as StatusQUtils
QtObject {
id: root
property string locale: localAppSettings.locale
property var contactsStore
property bool openCreateChat: false
@ -473,6 +475,36 @@ QtObject {
return userProfile.getPubKey()
}
property var allNetworks: networksModule.all
property var disabledChainIds: []
function addRemoveDisabledChain(suggestedRoutes, chainID, isDisbaled) {
if(isDisbaled) {
for(var i = 0; i < suggestedRoutes.length;i++) {
if(suggestedRoutes[i].chainId === chainID) {
disabledChainIds.push(suggestedRoutes[i].chainId)
}
}
}
else {
for(var i = 0; i < disabledChainIds.length;i++) {
if(disabledChainIds[i] === chainID) {
disabledChainIds.splice(i, 1)
}
}
}
}
function checkIfDisabledByUser(chainID) {
for(var i = 0; i < disabledChainIds.length;i++) {
if(disabledChainIds[i] === chainID) {
return true
}
}
return false
}
function getFiatValue(balance, cryptoSymbo, fiatSymbol) {
return profileSectionModule.ensUsernamesModule.getFiatValue(balance, cryptoSymbo, fiatSymbol)
}
@ -526,7 +558,7 @@ QtObject {
return JSON.parse(walletSectionTransactions.suggestedFees(chainId))
}
function suggestedRoutes(account, amount, token) {
return JSON.parse(walletSectionTransactions.suggestedRoutes(account, amount, token)).networks
function suggestedRoutes(account, amount, token, disabledChainIds) {
return JSON.parse(walletSectionTransactions.suggestedRoutes(account, amount, token, disabledChainIds)).networks
}
}

View File

@ -4,6 +4,9 @@ import "../Profile/stores"
QtObject {
id: root
property string locale: localAppSettings.locale
property var mainModuleInst: mainModule
property var aboutModuleInst: aboutModule
property var communitiesModuleInst: communitiesModule
@ -66,9 +69,34 @@ QtObject {
property var walletSectionTransactionsInst: walletSectionTransactions
property var savedAddressesModel: walletSectionSavedAddresses.model
property var allNetworks: networksModule.all
property var disabledChainIds: []
function addRemoveDisabledChain(suggestedRoutes, chainID, isDisbaled) {
if(isDisbaled) {
disabledChainIds.push(chainID)
}
else {
for(var i = 0; i < disabledChainIds.length;i++) {
if(disabledChainIds[i] === chainID) {
disabledChainIds.splice(i, 1)
}
}
}
}
function checkIfDisabledByUser(chainID) {
for(var i = 0; i < disabledChainIds.length;i++) {
if(disabledChainIds[i] === chainID) {
return true
}
}
return false
}
function getEtherscanLink() {
return profileSectionModule.ensUsernamesModule.getEtherscanLink()
}
@ -125,8 +153,8 @@ QtObject {
return walletSectionTransactions.getChainIdForBrowser()
}
function suggestedRoutes(account, amount, token) {
return JSON.parse(walletSectionTransactions.suggestedRoutes(account, amount, token)).networks
function suggestedRoutes(account, amount, token, disabledChainIds) {
return JSON.parse(walletSectionTransactions.suggestedRoutes(account, amount, token, disabledChainIds)).networks
}
function hex2Eth(value) {

View File

@ -33,7 +33,7 @@ StatusInput {
SequentialAnimation {
loops: Animation.Infinite
running: visible && input.edit.cursorVisible
running: input.edit.cursorVisible
PropertyAction {
target: cursor

View File

@ -0,0 +1,34 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
ColumnLayout {
id: balancedExceededError
property bool transferPossible: false
property double amountToSend: 0
StatusIcon {
Layout.alignment: Qt.AlignHCenter
visible: !balancedExceededError.transferPossible && balancedExceededError.amountToSend > 0
icon: "cancel"
color: Theme.palette.dangerColor1
}
StatusBaseText {
Layout.alignment: Qt.AlignHCenter
font.pixelSize: 15
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: Theme.palette.dangerColor1
text: balancedExceededError.amountToSend > 0 ? qsTr("Balance exceeded"): qsTr("No networks available")
wrapMode: Text.WordWrap
}
}

View File

@ -189,7 +189,7 @@ Item {
height: 16
}
StatusBaseText {
text: currentContact.address
text: currentContact.publicKey
width: 85
elide: Text.ElideMiddle
color: Theme.palette.baseColor1

View File

@ -7,6 +7,7 @@ import shared.panels 1.0
import shared.controls 1.0
import shared.controls.chat 1.0
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
Item {
id: root
@ -14,7 +15,6 @@ Item {
height: visible ? Style.current.smallPadding + prioritytext.height +
(advancedMode ? advancedModeItemGroup.height : selectorButtons.height) : 0
property var suggestedFees: ({
eip1559Enabled: true
})
@ -30,7 +30,6 @@ Item {
property int estimatedTxTimeFlag: Constants.transactionEstimatedTime.unknown
property int chainId: 1
property alias selectedTipLimit: inputPerGasTipLimit.text
property alias selectedOverallLimit: inputGasPrice.text
@ -109,8 +108,8 @@ Item {
}
function checkOptimal() {
if (!optimalGasButton.gasRadioBtn.checked) {
optimalGasButton.gasRadioBtn.toggle()
if (!optimalGasButton.checked) {
optimalGasButton.toggle()
}
}
@ -191,10 +190,13 @@ Item {
color: Style.current.secondaryText
}
StatusFlatButton {
id: buttonAdvanced
StatusButton {
anchors.verticalCenter: prioritytext.verticalCenter
anchors.right: parent.right
height: 22
defaultTopPadding: 2
defaultBottomPadding: 2
size: StatusBaseButton.Size.Tiny
visible: root.suggestedFees.eip1559Enabled
text: advancedMode ?
qsTr("Use suggestions") :
@ -210,24 +212,19 @@ Item {
anchors.topMargin: Style.current.halfPadding
spacing: 11
ButtonGroup {
id: gasGroup
onClicked: updateGasEthValue()
}
GasSelectorButton {
id: lowGasButton
buttonGroup: gasGroup
text: qsTr("Low")
price: {
if (!root.suggestedFees.eip1559Enabled) return root.suggestedFees.gasPrice;
return formatDec(root.suggestedFees.maxFeePerGasL, 6)
}
primaryText: qsTr("Low")
gasLimit: inputGasLimit ? inputGasLimit.text : ""
getGasEthValue: root.getGasEthValue
getFiatValue: root.getFiatValue
defaultCurrency: root.defaultCurrency
onChecked: {
price: {
if (!root.suggestedFees.eip1559Enabled) return root.suggestedFees.gasPrice;
return formatDec(root.suggestedFees.maxFeePerGasL, 6)
}
onCheckedChanged: {
if(checked) {
if (root.suggestedFees.eip1559Enabled){
inputPerGasTipLimit.text = formatDec(root.suggestedFees.maxPriorityFeePerGas, 2);
inputGasPrice.text = formatDec(root.suggestedFees.maxFeePerGasL, 2);
@ -238,10 +235,11 @@ Item {
root.checkLimits()
}
}
}
GasSelectorButton {
id: optimalGasButton
buttonGroup: gasGroup
text: qsTr("Optimal")
primaryText: qsTr("Optimal")
price: {
if (!root.suggestedFees.eip1559Enabled) {
// Setting the gas price field here because the binding didn't work
@ -255,7 +253,8 @@ Item {
getGasEthValue: root.getGasEthValue
getFiatValue: root.getFiatValue
defaultCurrency: root.defaultCurrency
onChecked: {
onCheckedChanged: {
if(checked) {
if (root.suggestedFees.eip1559Enabled){
inputPerGasTipLimit.text = formatDec(root.suggestedFees.maxPriorityFeePerGas, 2);
inputGasPrice.text = formatDec(root.suggestedFees.maxFeePerGasM, 2);
@ -266,11 +265,11 @@ Item {
root.checkLimits()
}
}
}
GasSelectorButton {
id: highGasButton
buttonGroup: gasGroup
text: qsTr("High")
primaryText: qsTr("High")
price: {
if (!root.suggestedFees.eip1559Enabled) return root.suggestedFees.gasPrice;
return formatDec(root.suggestedFees.maxFeePerGasH,6);
@ -279,7 +278,8 @@ Item {
getGasEthValue: root.getGasEthValue
getFiatValue: root.getFiatValue
defaultCurrency: root.defaultCurrency
onChecked: {
onCheckedChanged: {
if(checked) {
if (root.suggestedFees.eip1559Enabled){
inputPerGasTipLimit.text = formatDec(root.suggestedFees.maxPriorityFeePerGas, 2);
inputGasPrice.text = formatDec(root.suggestedFees.maxFeePerGasH, 2);
@ -291,6 +291,7 @@ Item {
}
}
}
}
Item {
id: advancedModeItemGroup

View File

@ -1,95 +1,93 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtQuick.Layouts 1.0
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
import shared.panels 1.0
import shared.controls 1.0
// TODO: use StatusQ components
Rectangle {
property var buttonGroup
property string text: qsTr("Low")
StatusRadioButton {
id: gasRectangle
property string primaryText: qsTr("Low")
property string gasLimit
property double price: 1
property string defaultCurrency: "USD"
property bool hovered: false
property bool checkedByDefault: false
property double price: 1
property var getGasEthValue: function () {}
property var getFiatValue: function () {}
property alias gasRadioBtn: gasRadioBtn
function formatDec(num, dec){
return Math.round((num + Number.EPSILON) * Math.pow(10, dec)) / Math.pow(10, dec)
}
QtObject {
id: d
property double fiatValue: getFiatValue(ethValue, "ETH", defaultCurrency)
property double ethValue: {
if (!gasLimit) {
return 0
}
return formatDec(parseFloat(getGasEthValue(gasRectangle.price, gasLimit)), 6)
}
property double fiatValue: getFiatValue(ethValue, "ETH", defaultCurrency)
signal checked()
}
id: gasRectangle
border.color: hovered || gasRadioBtn.checked ? Style.current.primary : Style.current.border
border.width: 1
color: Style.current.transparent
width: 130
height: 120
clip: true
radius: Style.current.radius
width: contentItem.implicitWidth
StatusRadioButton {
id: gasRadioBtn
ButtonGroup.group: buttonGroup
anchors.horizontalCenter: parent.horizontalCenter
// To-do Use StatusCard instead. It crashes if I use StatusCard and
// already spent 2 days on this, so leaving it out for now
contentItem: Rectangle {
id: card
implicitHeight: 76
implicitWidth: 128
radius: 8
color: gasRectangle.checked || mouseArea.containsMouse ? "transparent": Theme.palette.baseColor4
border.color: gasRectangle.checked || mouseArea.containsMouse ? Theme.palette.primaryColor2: Theme.palette.baseColor4
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 14
checked: gasRectangle.checkedByDefault
onCheckedChanged: {
if (checked) {
gasRectangle.checked()
}
}
}
StyledText {
id: gasText
text: gasRectangle.text
anchors.leftMargin: 8
anchors.rightMargin: 8
anchors.topMargin: 8
StatusBaseText {
id: primaryText
font.pixelSize: 15
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: gasRadioBtn.bottom
anchors.topMargin: 6
font.weight: Font.Medium
elide: Text.ElideRight
text: gasRectangle.primaryText
color: Theme.palette.directColor1
}
StyledText {
id: ethText
text: gasRectangle.ethValue + " ETH"
StatusBaseText {
id: secondaryLabel
font.pixelSize: 13
color: Style.current.secondaryText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: gasText.bottom
anchors.topMargin: 4
font.weight: Font.Medium
text: d.ethValue + " ETH"
color: Theme.palette.primaryColor1
}
StatusBaseText {
id: tertiaryText
font.pixelSize: 10
text: d.fiatValue + " " + gasRectangle.defaultCurrency.toUpperCase()
color: Theme.palette.directColor5
}
StyledText {
id: fiatText
text: `${gasRectangle.fiatValue} ${gasRectangle.defaultCurrency}`
font.pixelSize: 13
color: Style.current.secondaryText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: ethText.bottom
}
MouseArea {
id: mouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: gasRectangle.hovered = true
onExited: gasRectangle.hovered = false
onClicked: gasRadioBtn.toggle()
onClicked: gasRectangle.toggle()
}
}
indicator: Item {
width:card.width
height: card.height
}
}

View File

@ -54,9 +54,10 @@ StatusModal {
)
}
property var recalculateRoutesAndFees: Backpressure.debounce(popup, 600, function() {
property var recalculateRoutesAndFees: Backpressure.debounce(popup, 600, function(disabledChainIds) {
if (disabledChainIds === undefined) disabledChainIds = []
networkSelector.suggestedRoutes = popup.store.suggestedRoutes(
popup.selectedAccount.address, amountToSendInput.text, assetSelector.selectedAsset.symbol
popup.selectedAccount.address, amountToSendInput.text, assetSelector.selectedAsset.symbol, disabledChainIds
)
if (networkSelector.suggestedRoutes.length) {
networkSelector.selectedNetwork = networkSelector.suggestedRoutes[0]
@ -73,6 +74,7 @@ StatusModal {
id: d
readonly property string maxFiatBalance: Utils.stripTrailingZeros(parseFloat(assetSelector.selectedAsset.totalBalance).toFixed(4))
readonly property bool isReady: amountToSendInput.valid && !amountToSendInput.pending && recipientSelector.isValid && !recipientSelector.isPending
readonly property bool errorMode: networkSelector.suggestedRoutes && networkSelector.suggestedRoutes.length <= 0 || networkSelector.errorMode
onIsReadyChanged: {
if(!isReady && stack.isLastGroup)
stack.back()
@ -83,7 +85,7 @@ StatusModal {
height: 595
showHeader: false
showFooter: false
showAdvancedFooter: d.isReady && gasValidator.isValid
showAdvancedFooter: d.isReady && !isNaN(parseFloat(amountToSendInput.text)) && gasValidator.isValid
showAdvancedHeader: true
onSelectedAccountChanged: popup.recalculateRoutesAndFees()
@ -91,12 +93,6 @@ StatusModal {
onOpened: {
amountToSendInput.input.edit.forceActiveFocus()
assetSelector.assets = Qt.binding(function() {
if (popup.selectedAccount) {
return popup.selectedAccount.assets
}
})
if(popup.launchedFromChat) {
recipientSelector.selectedType = RecipientSelector.Type.Contact
recipientSelector.readOnly = true
@ -145,6 +141,8 @@ StatusModal {
titleText.font.pixelSize: 12
Layout.preferredHeight: 22
Layout.preferredWidth: childrenRect.width
color: d.errorMode ? Theme.palette.dangerColor2 : Theme.palette.primaryColor3
titleText.color: d.errorMode ? Theme.palette.dangerColor1 : Theme.palette.primaryColor1
}
}
Item {
@ -157,6 +155,7 @@ StatusModal {
width: parent.width - assetSelector.width
input.placeholderText: "0.00" + " " + assetSelector.selectedAsset.symbol
errorMessageCmp.anchors.rightMargin: -100
input.edit.color: d.errorMode ? Theme.palette.dangerColor1 : Theme.palette.directColor1
validators: [
StatusFloatValidator{
id: floatValidator
@ -184,6 +183,7 @@ StatusModal {
id: assetSelector
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
assets: popup.selectedAccount.assets
defaultToken: Style.png("tokens/DEFAULT-TOKEN@3x")
getCurrencyBalanceString: function (currencyBalance) {
return Utils.toLocaleString(currencyBalance.toFixed(2), popup.store.locale, {"currency": true}) + " " + popup.store.currentCurrency.toUpperCase()
@ -269,12 +269,12 @@ StatusModal {
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
contentHeight: recipientSelector.height + addressSelector.height + networkSelector.height + gasSelector.height + gasValidator.height
contentHeight: recipientSelector.height + addressSelector.height + networkSelector.height + fees.height + Style.current.halfPadding
clip: true
// To-do use standard StatusInput component once the flow for ens name resolution is clear
RecipientSelector {
anchors.top: assetAndAmmountSelector.bottom
anchors.top: parent.top
anchors.topMargin: Style.current.halfPadding
anchors.right: parent.right
anchors.left: parent.left
@ -308,24 +308,53 @@ StatusModal {
NetworkSelector {
id: networkSelector
store: popup.store
selectedAccount: popup.selectedAccount
anchors.top: addressSelector.bottom
anchors.right: parent.right
anchors.left: parent.left
amountToSend: isNaN(parseFloat(amountToSendInput.text)) ? 0 : parseFloat(amountToSendInput.text)
requiredGasInEth: gasSelector.selectedGasEthValue
assets: popup.selectedAccount.assets
selectedAsset: assetSelector.selectedAsset
onNetworkChanged: function(chainId) {
gasSelector.suggestedFees = popup.store.suggestedFees(chainId)
gasSelector.updateGasEthValue()
}
onReCalculateSuggestedRoute: popup.recalculateRoutesAndFees(disabledChainIds)
}
Rectangle {
id: fees
radius: 13
color: Theme.palette.indirectColor1
anchors.top: networkSelector.bottom
width: parent.width
height: gasSelector.visible || gasValidator.visible ? feesLayout.height + gasValidator.height : 0
RowLayout {
id: feesLayout
spacing: 10
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: Style.current.padding
StatusRoundIcon {
id: feesIcon
Layout.alignment: Qt.AlignTop
radius: 8
icon.name: "fees"
}
ColumnLayout {
Layout.alignment: Qt.AlignTop
GasSelector {
id: gasSelector
anchors.top: networkSelector.bottom
Layout.preferredWidth: fees.width - feesIcon.width - Style.current.xlPadding
getGasEthValue: popup.store.getGasEthValue
getFiatValue: popup.store.getFiatValue
getEstimatedTime: popup.store.getEstimatedTime
defaultCurrency: popup.store.currentCurrency
chainId: networkSelector.selectedNetwork.chainId
width: stack.width
chainId: networkSelector.selectedNetwork && networkSelector.selectedNetwork.chainId ? networkSelector.selectedNetwork.chainId : 1
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
if (!(popup.selectedAccount && popup.selectedAccount.address &&
recipientSelector.selectedRecipient && recipientSelector.selectedRecipient.address &&
@ -336,17 +365,19 @@ StatusModal {
return
}
var chainID = networkSelector.selectedNetwork ? networkSelector.selectedNetwork.chainId: 1
let gasEstimate = JSON.parse(popup.store.estimateGas(
popup.selectedAccount.address,
recipientSelector.selectedRecipient.address,
assetSelector.selectedAsset.symbol,
amountToSendInput.text,
networkSelector.selectedNetwork.chainId || Global.currentChainId,
chainID || Global.currentChainId,
""))
if (!gasEstimate.success) {
console.warn(qsTr("Error estimating gas: %1").arg(gasEstimate.error.message))
console.warn(qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message))
return
}
@ -354,16 +385,19 @@ StatusModal {
defaultGasLimit = selectedGasLimit
})
}
GasValidator {
id: gasValidator
anchors.top: gasSelector.bottom
anchors.horizontalCenter: undefined
Layout.alignment: Qt.AlignHCenter
selectedAccount: popup.selectedAccount
selectedAmount: amountToSendInput.text === "" ? 0.0
: parseFloat(amountToSendInput.text)
selectedAmount: amountToSendInput.text === "" ? 0.0 :
parseFloat(amountToSendInput.text)
selectedAsset: assetSelector.selectedAsset
selectedGasEthValue: gasSelector.selectedGasEthValue
selectedNetwork: networkSelector.selectedNetwork
selectedNetwork: networkSelector.selectedNetwork ? networkSelector.selectedNetwork: null
}
}
}
}
}
}

View File

@ -0,0 +1,195 @@
import QtQuick 2.13
import QtQuick.Layouts 1.13
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
Item {
id: networkCardsComponent
property var assets
property var store
property string locale: ""
property var selectedAsset
property var suggestedRoutes
property bool customMode: false
property var selectedNetwork
property var selectedAccount
property var allNetworks
property double amountToSend: 0
property double requiredGasInEth: 0
property bool errorMode: (d.customAmountToSend > amountToSend) ||
(d.customAmountToSend < amountToSend) ||
(d.customAmountToReceive > amountToSend) ||
(d.customAmountToReceive < amountToSend)
signal reCalculateSuggestedRoute(var disabled)
QtObject {
id: d
property var selectedFromNetwork
property var selectedToNetwork
property double customAmountToSend: 0
property double customAmountToReceive: 0
function getBalance(chainID) {
for(var i=0; i< selectedAsset.balances.count; i++) {
if(selectedAsset.balances.rowData(i, "chainId") === chainID.toString()) {
return selectedAsset.balances.rowData(i, "balance")
}
}
}
onSelectedFromNetworkChanged: {
canvas.clear()
canvas.requestPaint()
}
onSelectedToNetworkChanged: {
canvas.clear()
canvas.requestPaint()
}
}
width: 410
height: networkCardsLayout.height
RowLayout {
id: networkCardsLayout
width: parent.width
ColumnLayout {
id: fromNetworksLayout
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
spacing: 12
StatusBaseText {
font.pixelSize: 10
color: Theme.palette.baseColor1
text: qsTr("Your Balances").toUpperCase()
}
Repeater {
model: networkCardsComponent.allNetworks
StatusCard {
id: fromNetwork
property var tokenBalanceOnChain: Utils.toLocaleString(parseFloat(d.getBalance(model.chainId)).toFixed(4), locale, {"currency": true})
property var hasGas: assets.hasGas(model.chainId, model.nativeCurrencySymbol, requiredGasInEth + parseFloat(amountToSend))
primaryText: model.chainName
secondaryText: (parseFloat(tokenBalanceOnChain) === 0 && amountToSend !== 0) ?
qsTr("No Balance") : !hasGas ? qsTr("No Gas") :
(selectedNetwork && selectedNetwork.chainName === model.chainName) ?
amountToSend: 0
tertiaryText: qsTr("BALANCE: ") + tokenBalanceOnChain
state: tokenBalanceOnChain === 0 || !hasGas ? "unavailable" : networkCardsComponent.errorMode ? "error" : "default"
cardIcon.source: Style.png(model.iconUrl)
disabledText: qsTr("Disabled")
advancedMode: networkCardsComponent.customMode
advancedInputText: (selectedNetwork && selectedNetwork.chainName === model.chainName) ? amountToSend: 0
Component.onCompleted: {
disabled = store.checkIfDisabledByUser(model.chainId)
if(selectedNetwork && selectedNetwork.chainName === model.chainName)
d.selectedFromNetwork = this
}
Connections {
target: networkCardsComponent
onSelectedNetworkChanged: {
if(selectedNetwork.chainName === model.chainName) {
d.selectedFromNetwork = fromNetwork
}
}
}
onClicked: {
store.addRemoveDisabledChain(suggestedRoutes, model.chainId, disabled)
reCalculateSuggestedRoute(store.disabledChainIds)
}
onAdvancedInputTextChanged: {
if(selectedNetwork && selectedNetwork.chainName === model.chainName) {
d.customAmountToSend = isNaN(parseFloat(advancedInputText)) ? 0 : parseFloat(advancedInputText)
}
}
}
}
}
ColumnLayout {
id: toNetworksLayout
Layout.alignment: Qt.AlignRight | Qt.AlignTop
spacing: 12
StatusBaseText {
Layout.alignment: Qt.AlignRight
Layout.maximumWidth: 70
font.pixelSize: 10
color: Theme.palette.baseColor1
text: selectedAccount.address
elide: Text.ElideMiddle
}
Repeater {
model: networkCardsComponent.allNetworks
StatusCard {
id: toCard
primaryText: model.chainName
secondaryText: (selectedNetwork && selectedNetwork.chainName === model.chainName) ? amountToSend: 0
tertiaryText: ""
// To-do preferred in not something that is supported yet
state: networkCardsComponent.errorMode ? "error" : "default"
// opacity: preferred ? 1 : 0
cardIcon.source: Style.png(model.iconUrl)
disabledText: qsTr("Disabled")
advancedMode: networkCardsComponent.customMode
advancedInputText: (selectedNetwork && selectedNetwork.chainName === model.chainName) ? amountToSend: 0
Component.onCompleted: {
disabled = store.checkIfDisabledByUser(model.chainId)
if(selectedNetwork && selectedNetwork.chainName === model.chainName)
d.selectedToNetwork = this
}
Connections {
target: networkCardsComponent
onSelectedNetworkChanged: {
if(selectedNetwork && selectedNetwork.chainName === model.chainName)
d.selectedToNetwork = toCard
}
}
onClicked: {
store.addRemoveDisabledChain(suggestedRoutes, model.chainId, disabled)
reCalculateSuggestedRoute(store.disabledChainIds)
}
onAdvancedInputTextChanged: {
if(selectedNetwork && selectedNetwork.chainName === model.chainName)
d.customAmountToReceive = isNaN(parseFloat(advancedInputText)) ? 0 : parseFloat(advancedInputText)
}
}
}
}
}
Canvas {
id: canvas
x: networkCardsLayout.x + fromNetworksLayout.x
y: networkCardsLayout.y
width: networkCardsLayout.width
height: networkCardsLayout.height
function clear() {
if(available) {
var ctx = getContext("2d");
if(ctx)
ctx.reset()
}
}
onPaint: {
if(d.selectedFromNetwork && d.selectedToNetwork) {
// Get the canvas context
var ctx = getContext("2d");
StatusQUtils.Utils.drawArrow(ctx, d.selectedFromNetwork.x + d.selectedFromNetwork.width,
d.selectedFromNetwork.y + d.selectedFromNetwork.height/2,
toNetworksLayout.x + d.selectedToNetwork.x,
d.selectedToNetwork.y + d.selectedToNetwork.height/2,
'#627EEA')
}
}
}
}

View File

@ -1,10 +1,7 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick 2.13
import QtQuick.Layouts 1.13
import QtQuick.Dialogs 1.3
import utils 1.0
import shared.stores 1.0
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
@ -12,117 +9,117 @@ import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import "../panels"
import "../controls"
import "../views"
Item {
id: root
height: visible ? stackLayout.height + 2* Style.current.xlPadding : 0
height: visible ? tabBar.height + stackLayout.height + 2* Style.current.xlPadding : 0
property var store
property var suggestedRoutes
property var selectedNetwork
property var selectedAccount
property var selectedAsset
property var assets
property double amountToSend: 0
property double requiredGasInEth: 0
property bool errorMode: customNetworkRoutingPage.errorMode
signal networkChanged(int chainId)
signal reCalculateSuggestedRoute(var disabledChainIds)
property var suggestedRoutes: ""
property var selectedNetwork: ""
QtObject {
id: d
readonly property int backgroundRectRadius: 13
readonly property string backgroundRectColor: Theme.palette.indirectColor1
}
StatusSwitchTabBar {
id: tabBar
anchors.top: parent.top
anchors.topMargin: Style.current.bigPadding
anchors.horizontalCenter: parent.horizontalCenter
StatusSwitchTabButton {
text: qsTr("Simple")
}
StatusSwitchTabButton {
text: qsTr("Advanced")
}
StatusSwitchTabButton {
text: qsTr("Custom")
}
}
StackLayout {
id: stackLayout
anchors.top: parent.top
anchors.topMargin: Style.current.xlPadding
height: simpleLayout.height
anchors.top: tabBar.bottom
anchors.topMargin: Style.current.bigPadding
height: currentIndex == 0 ? networksSimpleRoutingPage.height + networksSimpleRoutingPage.anchors.margins + Style.current.bigPadding:
currentIndex == 1 ? advancedNetworkRoutingPage.height + advancedNetworkRoutingPage.anchors.margins + Style.current.bigPadding:
customNetworkRoutingPage.height + customNetworkRoutingPage.anchors.margins + Style.current.bigPadding
width: parent.width
currentIndex: 0
currentIndex: tabBar.currentIndex
ColumnLayout {
id: simpleLayout
Layout.fillWidth: true
spacing: 24
Rectangle {
id: networksRect
radius: 13
color: Theme.palette.indirectColor1
Layout.fillWidth: true
Layout.preferredHeight: layout.height + 24
ColumnLayout {
id: layout
id: simple
radius: d.backgroundRectRadius
color: d.backgroundRectColor
NetworksSimpleRoutingView {
id: networksSimpleRoutingPage
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 16
spacing: 20
RowLayout {
spacing: 10
StatusRoundIcon {
Layout.alignment: Qt.AlignTop
radius: 8
icon.name: "flash"
}
ColumnLayout {
StatusBaseText {
Layout.maximumWidth: 410
font.pixelSize: 15
font.weight: Font.Medium
color: Theme.palette.directColor1
text: qsTr("Networks")
wrapMode: Text.WordWrap
}
StatusBaseText {
Layout.maximumWidth: 410
font.pixelSize: 15
color: Theme.palette.baseColor1
text: qsTr("Choose a network to use for the transaction")
wrapMode: Text.WordWrap
anchors.margins: Style.current.padding
selectedNetwork: root.selectedNetwork
suggestedRoutes: root.suggestedRoutes
amountToSend: root.amountToSend
onNetworkChanged: {
root.selectedNetwork = network
root.networkChanged(network.chainId)
}
}
}
StatusBaseText {
visible: suggestedRoutes.length === 0
font.pixelSize: 15
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: Theme.palette.dangerColor1
text: qsTr("No networks available")
wrapMode: Text.WordWrap
}
Item {
Layout.fillWidth: true
height: 50
ScrollView {
width: parent.width
contentWidth: row.width
contentHeight: row.height + 10
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
ScrollBar.horizontal.policy: ScrollBar.AlwaysOn
clip: true
Row {
id: row
spacing: 16
Repeater {
id: repeater
model: suggestedRoutes
StatusListItem {
id: item
implicitWidth: 126
title: modelData.chainName
subTitle: ""
image.source: Style.png("networks/" + modelData.chainName.toLowerCase())
image.width: 32
image.height: 32
leftPadding: 5
rightPadding: 5
color: "transparent"
border.color: Style.current.primary
border.width: root.selectedNetwork.chainId === modelData.chainId ? 1 : 0
onClicked: {
root.selectedNetwork = modelData
root.networkChanged(modelData.chainId)
}
}
}
}
}
Rectangle {
id: advanced
radius: d.backgroundRectRadius
color: d.backgroundRectColor
NetworksAdvancedCustomRoutingView {
id: advancedNetworkRoutingPage
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: Style.current.padding
store: root.store
assets: root.assets
selectedNetwork: root.selectedNetwork
selectedAccount: root.selectedAccount
amountToSend: root.amountToSend
requiredGasInEth: root.requiredGasInEth
selectedAsset: root.selectedAsset
suggestedRoutes: root.suggestedRoutes
onReCalculateSuggestedRoute: root.reCalculateSuggestedRoute(disabledChainIds)
}
}
Rectangle {
id: custom
radius: d.backgroundRectRadius
color: d.backgroundRectColor
NetworksAdvancedCustomRoutingView {
id: customNetworkRoutingPage
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: Style.current.padding
customMode: true
store: root.store
assets: root.assets
selectedNetwork: root.selectedNetwork
selectedAccount: root.selectedAccount
amountToSend: root.amountToSend
requiredGasInEth: root.requiredGasInEth
selectedAsset: root.selectedAsset
suggestedRoutes: root.suggestedRoutes
onReCalculateSuggestedRoute: root.reCalculateSuggestedRoute(disabledChainIds)
}
}
}

View File

@ -0,0 +1,95 @@
import QtQuick 2.13
import QtQuick.Layouts 1.13
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import "../controls"
ColumnLayout {
id: networksAdvancedCustomView
property var store
property var assets
property var selectedNetwork: ""
property var selectedAccount
property double amountToSend: 0
property double requiredGasInEth: 0
property bool customMode: false
property var selectedAsset
property var suggestedRoutes
property bool errorMode: networksLoader.item ? networksLoader.item.errorMode : false
signal reCalculateSuggestedRoute(var disabledChainIds)
onSelectedNetworkChanged: {
networksLoader.active = false
networksLoader.active = true
}
RowLayout {
spacing: 10
StatusRoundIcon {
Layout.alignment: Qt.AlignTop
radius: 8
icon.name: "flash"
}
ColumnLayout {
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
RowLayout {
Layout.maximumWidth: 410
StatusBaseText {
Layout.maximumWidth: 410
font.pixelSize: 15
font.weight: Font.Medium
color: Theme.palette.directColor1
text: qsTr("Networks")
wrapMode: Text.WordWrap
}
StatusButton {
Layout.alignment: Qt.AlignRight
Layout.preferredHeight: 22
defaultTopPadding: 0
defaultBottomPadding: 0
size: StatusBaseButton.Size.Small
icon.name: "hide"
text: qsTr("Show Unpreferred Networks")
}
}
StatusBaseText {
Layout.maximumWidth: 410
font.pixelSize: 15
color: Theme.palette.baseColor1
text: qsTr("The networks where the receipient will receive tokens. Amounts calculated automatically for the lowest cost.")
wrapMode: Text.WordWrap
}
Loader {
id: networksLoader
Layout.topMargin: Style.current.padding
active: false
visible: active
sourceComponent: NetworkCardsComponent {
store: networksAdvancedCustomView.store
selectedNetwork: networksAdvancedCustomView.selectedNetwork
selectedAccount: networksAdvancedCustomView.selectedAccount
allNetworks: networksAdvancedCustomView.store.allNetworks
amountToSend: networksAdvancedCustomView.amountToSend
customMode: networksAdvancedCustomView.customMode
requiredGasInEth: networksAdvancedCustomView.requiredGasInEth
assets: networksAdvancedCustomView.assets
selectedAsset: networksAdvancedCustomView.selectedAsset
locale: networksAdvancedCustomView.store.locale
suggestedRoutes: networksAdvancedCustomView.suggestedRoutes
onReCalculateSuggestedRoute: networksAdvancedCustomView.reCalculateSuggestedRoute(disabled)
}
}
}
}
}

View File

@ -0,0 +1,90 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import "../controls"
RowLayout {
id: networksSimpleRoutingView
property var selectedNetwork
property var suggestedRoutes
property double amountToSend: 0
signal networkChanged(var network)
spacing: 10
StatusRoundIcon {
Layout.alignment: Qt.AlignTop
radius: 8
icon.name: "flash"
}
ColumnLayout {
Layout.alignment: Qt.AlignTop
StatusBaseText {
Layout.maximumWidth: 410
font.pixelSize: 15
font.weight: Font.Medium
color: Theme.palette.directColor1
text: qsTr("Networks")
wrapMode: Text.WordWrap
}
StatusBaseText {
Layout.maximumWidth: 410
font.pixelSize: 15
color: Theme.palette.baseColor1
text: qsTr("Choose a network to use for the transaction")
wrapMode: Text.WordWrap
}
BalanceExceeded {
Layout.topMargin: Style.current.bigPadding
Layout.alignment: Qt.AlignCenter
visible: !transferPossible
transferPossible: networksSimpleRoutingView.suggestedRoutes ? networksSimpleRoutingView.suggestedRoutes.length > 0 : false
amountToSend: networksSimpleRoutingView.amountToSend
}
ScrollView {
Layout.fillWidth: true
Layout.preferredHeight: row.height + 10
Layout.topMargin: Style.current.bigPadding
contentWidth: row.width
contentHeight: row.height + 10
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
clip: true
visible: networksSimpleRoutingView.suggestedRoutes ? networksSimpleRoutingView.suggestedRoutes.length > 0 : false
Row {
id: row
spacing: Style.current.padding
Repeater {
id: repeater
model: networksSimpleRoutingView.suggestedRoutes
StatusListItem {
id: item
leftPadding: 5
rightPadding: 5
implicitWidth: 126
title: modelData.chainName
subTitle: ""
image.width: 32
image.height: 32
image.source: Style.png("networks/" + modelData.chainName.toLowerCase())
color: "transparent"
border.color: Style.current.primary
border.width: networksSimpleRoutingView.selectedNetwork !== undefined ? networksSimpleRoutingView.selectedNetwork.chainId === modelData.chainId ? 1 : 0 : 0
onClicked: networksSimpleRoutingView.networkChanged(modelData)
}
}
}
}
}
}

View File

@ -134,7 +134,7 @@ Item {
width: visible ? parent.width: 0
height: visible ? 64 : 0
title: model.name
subTitle: Utils.toLocaleString(model.currencyBalance.toFixed(2), popup.store.locale, {"model.currency": true}) + " " + popup.store.currentCurrency.toUpperCase()
subTitle: Utils.toLocaleString(model.currencyBalance.toFixed(2), store.locale, {"model.currency": true}) + " " + store.currentCurrency.toUpperCase()
icon.emoji: !!model.emoji ? model.emoji: ""
icon.color: model.color
icon.name: !model.emoji ? "filled-account": ""
@ -173,7 +173,7 @@ Item {
}
delegate: StatusListItem {
property bool isIncoming: to === popup.store.currentAccount.address
property bool isIncoming: to === store.currentAccount.address
width: visible ? parent.width: 0
height: visible ? 64 : 0
title: isIncoming ? from : to
@ -187,14 +187,14 @@ Item {
height: 15
width: 15
color: isIncoming ? Style.current.success : Style.current.danger
icon: isIncoming ? "down" : "up"
icon: isIncoming ? "arrow-down" : "arrow-up"
rotation: 45
},
StatusBaseText {
id: contactsLabel
font.pixelSize: 15
color: Theme.palette.directColor1
text: popup.store.hex2Eth(value)
text: store.hex2Eth(value)
}
]
onClicked: contactSelected(title, RecipientSelector.Type.Address)

View File

@ -355,6 +355,7 @@ QtObject {
return qsTr("%1D").arg(diffDay)
}
// To-do move to Wallet Store, this should not be under Utils.
function findAssetByChainAndSymbol(chainIdToFind, assets, symbolToFind) {
for(var i=0; i<assets.rowCount(); i++) {
const symbol = assets.rowData(i, "symbol")

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 35c4001e5727c3bd5f59995a4d1698ed2888f5f8
Subproject commit b2ce92fd41a3b899749e2ed2ebd94ec8d4e1859f