feat(@desktop/wallet): Update network card and routing lines in SendModal

fixes #8714
This commit is contained in:
Khushboo Mehta 2022-12-19 14:02:56 +01:00 committed by Khushboo-dev-cpp
parent c50cf988a7
commit eb2ec7c1af
13 changed files with 401 additions and 237 deletions

View File

@ -25,11 +25,14 @@ Item {
id: card
Layout.alignment: Qt.AlignVCenter
primaryText: "Mainnet"
secondaryText: state === "unavailable" ? "No Gas" : "75"
secondaryText: state === "unavailable" ? "No Gas" : "75,0000000"
tertiaryText: state === "unpreferred" ? "UNPREFERRED" : "BALANCE: " + 250
cardIconName: "status"
advancedInputText: "75"
advancedInputText: "75,0000000"
disabledText: "Disabled"
onCardLocked: locked = isLocked
disableText: "Disable"
enableText: "Enable"
}
StatusComboBox {
@ -87,6 +90,9 @@ Item {
advancedMode: card.advancedMode
advancedInputText: tokensToSend
disabledText: "Disabled"
onCardLocked: locked = isLocked
disableText: "Disable"
enableText: "Enable"
}
}
}
@ -106,6 +112,9 @@ Item {
advancedMode: card.advancedMode
advancedInputText: tokensToReceive
disabledText: "Disabled"
onCardLocked: locked = isLocked
disableText: "Disable"
enableText: "Enable"
}
}
}

View File

@ -1,9 +1,11 @@
import QtQuick 2.13
import QtQuick.Layouts 1.14
import QtGraphicalEffects 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
/*!
\qmltype StatusCard
@ -26,6 +28,9 @@ import StatusQ.Controls 0.1
tertiaryText: "BALANCE: " + 250
cardIconName: "status"
advancedMode: false
disableText: "Disable"
enableText: "enable"
maxAdvancedValue: 100
}
\endqml
For a list of components available see StatusQ.
@ -87,6 +92,16 @@ Rectangle {
Used to set Tertiary text in the StatusCard
*/
property alias tertiaryText: tertiaryText.text
/*!
\qmlproperty alias StatusCard::disableText
Used to set disable text in the StatusCard
*/
property alias disableText: disableText.text
/*!
\qmlproperty alias StatusCard::enableText
Used to set enableText text in the StatusCard
*/
property string enableText
/*!
\qmlproperty alias StatusCard::advancedInputText
Used to set text in the StatusInput in advancedMode
@ -138,7 +153,11 @@ Rectangle {
This property exposes the card icon posistion to help draw the network routes
*/
property real cardIconPosition: layout.y + cardIcon.y + cardIcon.height/2
/*!
\qmlproperty real StatusCard::maxAdvancedValue
This property holds the max value in the advanced input that can be entered by the user
*/
property real maxAdvancedValue
/*!
\qmlsignal StatusCard::clicked
This signal is emitted when the card is clicked
@ -162,126 +181,178 @@ Rectangle {
*/
state: "default"
implicitHeight: advancedInput.visible ? 90 : 76
implicitWidth: 128
implicitHeight: 90
implicitWidth: 160
radius: 8
border.width: 1
border.color: Theme.palette.primaryColor2
// This is used to create a shadow around the rectangle when hovered
// it was needed to be done this way because it doesnt work with the
// main rect when it is transparent in its "default" state
Rectangle {
id: dummyRect
anchors.fill: parent
radius: root.radius
opacity: 0
}
DropShadow {
anchors.fill: dummyRect
verticalOffset: 0
horizontalOffset: 0
radius: 8
samples: 17
source: dummyRect
color: Theme.palette.dropShadow
visible: sensor.containsMouse
z: root.z - 1
}
MouseArea {
id: sensor
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
hoverEnabled: true
enabled: root.clickable && root.state !== "unavailable"
onClicked: {
disabled = !disabled
if(!advancedMode)
disabled = !disabled
root.clicked()
}
}
RowLayout {
ColumnLayout {
id: layout
spacing: 0
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.leftMargin: 8
anchors.rightMargin: 8
anchors.topMargin: 8
ColumnLayout {
Layout.maximumWidth: root.width - cardIcon.width - 24
RowLayout {
Layout.preferredWidth: layout.width
StatusBaseText {
id: primaryText
Layout.maximumWidth: parent.width
Layout.maximumWidth: layout.width - cardIcon.width - 24
font.pixelSize: 15
font.weight: Font.Medium
elide: Text.ElideRight
lineHeight: 22
lineHeightMode: Text.FixedHeight
verticalAlignment: Text.AlignVCenter
}
RowLayout {
id: basicInput
StatusBaseText {
id: secondaryLabel
Layout.maximumWidth: root.width - cardIcon.width - errorIcon.width - 24
elide: Text.ElideRight
font.pixelSize: 13
font.weight: Font.Medium
}
StatusIcon {
id: errorIcon
width: 14
height: 14
Layout.alignment: Qt.AlignTop
icon: "tiny/warning"
color: Theme.palette.pinColor1
}
}
StatusInput {
id: advancedInput
property bool tempLock: false
implicitWidth: 80
maximumHeight: 32
topPadding: 0
bottomPadding: 0
leftPadding: 8
rightPadding: 5
input.edit.font.pixelSize: 13
input.edit.readOnly: disabled
input.rightComponent: Row {
width: implicitWidth
spacing: 4
StatusFlatRoundButton {
anchors.verticalCenter: parent.verticalCenter
width: 12
height: 12
icon.name: root.locked && advancedInput.tempLock ? "lock" : "unlock"
icon.width: 12
icon.height: 12
icon.color: root.locked && advancedInput.tempLock? Theme.palette.primaryColor1 : Theme.palette.baseColor1
type: StatusFlatRoundButton.Type.Secondary
enabled: !disabled
onClicked: {
advancedInput.tempLock = !advancedInput.tempLock
root.cardLocked(advancedInput.tempLock)
}
}
StatusFlatRoundButton {
width: 14
height: 14
icon.name: "clear"
icon.width: 14
icon.height: 14
icon.color: Theme.palette.baseColor1
type: StatusFlatRoundButton.Type.Secondary
onClicked: advancedInput.input.edit.clear()
}
}
text: root.preCalculatedAdvancedText
onTextChanged: {
advancedInput.tempLock = false
waitTimer.restart()
}
Timer {
id: waitTimer
interval: lockTimeout
onTriggered: {
advancedInput.tempLock = true
if(!!advancedInput.text && root.preCalculatedAdvancedText !== advancedInput.text) {
root.cardLocked(advancedInput.tempLock)
}
}
}
}
StatusBaseText {
id: tertiaryText
Layout.maximumWidth: root.width - 12
elide: Text.ElideRight
font.pixelSize: 10
StatusIcon {
id: cardIcon
Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.preferredHeight: 16
Layout.preferredWidth: 16
mipmap: true
}
}
StatusIcon {
id: cardIcon
Layout.alignment: Qt.AlignTop | Qt.AlignRight
RowLayout {
id: basicInput
Layout.preferredHeight: 32
Layout.preferredWidth: 32
mipmap: true
StatusBaseText {
id: secondaryLabel
Layout.fillWidth: true
elide: Text.ElideRight
font.pixelSize: 13
lineHeight: 18
lineHeightMode: Text.FixedHeight
verticalAlignment: Text.AlignVCenter
}
StatusIcon {
id: errorIcon
Layout.alignment: Qt.AlignVCenter
width: 14
height: 14
icon: "tiny/warning"
color: Theme.palette.pinColor1
}
}
StatusInput {
id: advancedInput
property bool tempLock: false
Layout.preferredWidth: layout.width
maximumHeight: 32
topPadding: 0
bottomPadding: 0
leftPadding: 8
rightPadding: 5
input.edit.font.pixelSize: 13
input.edit.readOnly: disabled
input.background.radius: 4
input.background.color: input.edit.activeFocus ? "transparent" : Theme.palette.directColor8
input.background.border.color: input.edit.activeFocus ? Theme.palette.primaryColor2 : "transparent"
input.rightComponent: Row {
width: implicitWidth
spacing: 4
visible: root.state !== "error"
StatusFlatRoundButton {
anchors.verticalCenter: parent.verticalCenter
width: 12
height: 12
icon.name: root.locked && advancedInput.tempLock ? "lock" : "unlock"
icon.width: 12
icon.height: 12
icon.color: root.locked && advancedInput.tempLock ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
type: StatusFlatRoundButton.Type.Secondary
enabled: !disabled
onClicked: {
advancedInput.tempLock = !advancedInput.tempLock
root.locked = advancedInput.tempLock
root.cardLocked(advancedInput.tempLock)
}
}
}
validators: [
StatusFloatValidator {
id: floatValidator
bottom: 0
top: root.maxAdvancedValue
errorMessage: ""
}
]
text: root.preCalculatedAdvancedText
onTextChanged: {
advancedInput.tempLock = false
waitTimer.restart()
}
Timer {
id: waitTimer
interval: lockTimeout
onTriggered: {
advancedInput.tempLock = true
if(!!advancedInput.text && root.preCalculatedAdvancedText !== advancedInput.text) {
root.locked = advancedInput.tempLock
root.cardLocked(advancedInput.tempLock)
}
}
}
}
StatusBaseText {
id: tertiaryText
Layout.maximumWidth: layout.width
Layout.preferredHeight: 20
elide: Text.ElideRight
font.pixelSize: 10
lineHeight: 14
lineHeightMode: Text.FixedHeight
verticalAlignment: Text.AlignVCenter
}
StatusBaseText {
id: disableText
Layout.maximumWidth: layout.width
Layout.preferredHeight: 20
elide: Text.ElideRight
font.weight: Font.Medium
color: Theme.palette.primaryColor1
font.pixelSize: 13
lineHeight: 18
lineHeightMode: Text.FixedHeight
verticalAlignment: Text.AlignVCenter
}
}
@ -294,7 +365,7 @@ Rectangle {
}
PropertyChanges {
target: root
border.color: disabled ? "transparent" : Theme.palette.primaryColor2
border.color: Theme.palette.primaryColor2
}
PropertyChanges {
target: primaryText
@ -306,7 +377,9 @@ Rectangle {
}
PropertyChanges {
target: secondaryLabel
color: disabled ? Theme.palette.directColor5: Theme.palette.primaryColor1
color: disabled ? sensor.containsMouse ?
Theme.palette.primaryColor1 :
Theme.palette.directColor5: Theme.palette.primaryColor1
}
PropertyChanges {
target: secondaryLabel
@ -314,7 +387,11 @@ Rectangle {
}
PropertyChanges {
target: secondaryLabel
text: disabled ? disabledText : secondaryText
text: disabled ? sensor.containsMouse ? root.enableText : disabledText : secondaryText
}
PropertyChanges {
target: secondaryLabel
font.weight: disabled && sensor.containsMouse ? Font.Medium : Font.Normal
}
PropertyChanges {
target: tertiaryText
@ -322,7 +399,11 @@ Rectangle {
}
PropertyChanges {
target: tertiaryText
visible: tertiaryText.text
visible: advancedMode ? tertiaryText.text : (!sensor.containsMouse && tertiaryText.text) || disabled
}
PropertyChanges {
target: disableText
visible: sensor.containsMouse && !advancedMode && !disabled
}
PropertyChanges {
target: cardIcon
@ -338,7 +419,8 @@ Rectangle {
}
PropertyChanges {
target: advancedInput
input.edit.color: Theme.palette.directColor1
input.edit.color: input.edit.activeFocus || !root.locked ?
Theme.palette.directColor1 : Theme.palette.directColor5
}
PropertyChanges {
target: basicInput
@ -353,7 +435,7 @@ Rectangle {
}
PropertyChanges {
target: root
border.color: disabled ? "transparent" : Theme.palette.primaryColor2
border.color: Theme.palette.primaryColor2
}
PropertyChanges {
target: primaryText
@ -365,7 +447,9 @@ Rectangle {
}
PropertyChanges {
target: secondaryLabel
color: disabled ? Theme.palette.directColor5: Theme.palette.dangerColor1
color: disabled ? sensor.containsMouse ?
Theme.palette.primaryColor1 :
Theme.palette.directColor5: Theme.palette.dangerColor1
}
PropertyChanges {
target: secondaryLabel
@ -373,7 +457,11 @@ Rectangle {
}
PropertyChanges {
target: secondaryLabel
text: disabled ? disabledText : secondaryText
text: disabled ? sensor.containsMouse ? root.enableText : disabledText : secondaryText
}
PropertyChanges {
target: secondaryLabel
font.weight: disabled && sensor.containsMouse ? Font.Medium : Font.Normal
}
PropertyChanges {
target: tertiaryText
@ -381,7 +469,11 @@ Rectangle {
}
PropertyChanges {
target: tertiaryText
visible: tertiaryText.text
visible: advancedMode ? tertiaryText.text : (!sensor.containsMouse && tertiaryText.text) || disabled
}
PropertyChanges {
target: disableText
visible: sensor.containsMouse && !advancedMode && !disabled
}
PropertyChanges {
target: cardIcon
@ -397,7 +489,7 @@ Rectangle {
}
PropertyChanges {
target: advancedInput
input.edit.color: disabled ? Theme.palette.directColor5 : Theme.palette.dangerColor1
input.edit.color: Theme.palette.dangerColor1
}
PropertyChanges {
target: basicInput
@ -412,7 +504,7 @@ Rectangle {
}
PropertyChanges {
target: root
border.color: disabled ? "transparent": Theme.palette.pinColor2
border.color: Theme.palette.pinColor2
}
PropertyChanges {
target: primaryText
@ -424,7 +516,9 @@ Rectangle {
}
PropertyChanges {
target: secondaryLabel
color: disabled ? Theme.palette.directColor5 : Theme.palette.pinColor1
color: disabled ? sensor.containsMouse ?
Theme.palette.primaryColor1 :
Theme.palette.directColor5 : Theme.palette.pinColor1
}
PropertyChanges {
target: secondaryLabel
@ -432,15 +526,23 @@ Rectangle {
}
PropertyChanges {
target: secondaryLabel
text: disabled ? disabledText : secondaryText
text: disabled ? sensor.containsMouse ? root.enableText : disabledText : secondaryText
}
PropertyChanges {
target: secondaryLabel
font.weight: disabled && sensor.containsMouse ? Font.Medium : Font.Normal
}
PropertyChanges {
target: tertiaryText
color: disabled ? Theme.palette.directColor5 : Theme.palette.pinColor1
color: Theme.palette.pinColor1
}
PropertyChanges {
target: tertiaryText
visible: tertiaryText.text
visible: advancedMode ? tertiaryText.text : (!sensor.containsMouse && tertiaryText.text) || disabled
}
PropertyChanges {
target: disableText
visible: sensor.containsMouse && !advancedMode && !disabled
}
PropertyChanges {
target: cardIcon
@ -456,7 +558,7 @@ Rectangle {
}
PropertyChanges {
target: advancedInput
input.edit.color: Theme.palette.directColor1
input.edit.color: root.locked ? Theme.palette.directColor5 : Theme.palette.directColor1
}
PropertyChanges {
target: basicInput
@ -493,13 +595,21 @@ Rectangle {
target: secondaryLabel
text: secondaryText
}
PropertyChanges {
target: secondaryLabel
font.weight: disabled && sensor.containsMouse ? Font.Medium : Font.Normal
}
PropertyChanges {
target: tertiaryText
color: Theme.palette.directColor5
}
PropertyChanges {
target: tertiaryText
visible: tertiaryText.text
visible: advancedMode ? tertiaryText.text : (!sensor.containsMouse && tertiaryText.text) || disabled
}
PropertyChanges {
target: disableText
visible: sensor.containsMouse && !advancedMode && !disabled
}
PropertyChanges {
target: cardIcon

View File

@ -82,72 +82,75 @@ QtObject {
// function to draw arrow
function drawArrow(context, fromx, fromy, tox, toy, color, offset) {
const dx = tox - fromx;
const fromX = fromx + 8
const toX = tox - 8
const dx = toX - fromX;
const dy = toy - fromy;
const headlen = 10; // length of head in pixels
const headlen = 8; // length of head in pixels
const angle = 0
const radius = 5
const radius = 4
context.strokeStyle = color ? color : '#627EEA'
// straight line
if(dy === 0) {
// draw semicircle
// draw circle
context.setLineDash([20,0])
context.beginPath()
context.arc(fromx, fromy, radius, 3*Math.PI/2, Math.PI/2,false)
context.arc(fromX+radius, fromy, radius, 0, 360)
context.stroke()
// draw straightline
context.setLineDash([3])
context.beginPath()
context.moveTo(fromx + radius, fromy)
context.lineTo(tox, toy)
context.moveTo(fromX + 2*radius, fromy)
context.lineTo(toX, toy)
context.stroke()
// draw arrow
context.setLineDash([10,0])
context.setLineDash([20,0])
context.beginPath()
context.moveTo(tox - headlen * Math.cos(angle - Math.PI / 6), toy - headlen * Math.sin(angle - Math.PI / 6))
context.lineTo(tox, toy )
context.lineTo(tox - headlen * Math.cos(angle + Math.PI / 6), toy - headlen * Math.sin(angle + Math.PI / 6))
context.moveTo(toX - headlen * Math.cos(angle - Math.PI / 4), toy - headlen * Math.sin(angle - Math.PI / 4))
context.lineTo(toX, toy)
context.lineTo(toX - headlen * Math.cos(angle + Math.PI / 4), toy - headlen * Math.sin(angle + Math.PI / 4))
context.stroke()
}
// connecting between 2 different y positions
else {
// draw semicircle
// draw circle
context.setLineDash([20,0])
context.beginPath()
context.arc(fromx, fromy, radius, 3*Math.PI/2, Math.PI/2,false)
context.arc(fromX+radius, fromy, radius, 0, 360)
context.stroke()
// draw bent line
context.setLineDash([3])
context.beginPath()
context.moveTo(fromx + radius, fromy)
context.lineTo(fromx + dx / 2 - offset, fromy)
context.lineTo(fromx + dx / 2 - offset, toy + (dy < 0 ? radius : -radius))
context.moveTo(fromX + 2*radius, fromy)
context.lineTo(fromX + dx / 2 - offset, fromy)
context.lineTo(fromX + dx / 2 - offset, toy + (dy < 0 ? radius : -radius))
context.stroke()
// draw connecting circle
context.setLineDash([10,0])
context.setLineDash([20,0])
context.beginPath()
context.moveTo(fromx + dx / 2 + radius - offset, toy)
context.arc(fromx + dx / 2 - offset, toy, radius, 0, 2*Math.PI,false)
context.moveTo(fromX + dx / 2 + radius - offset, toy)
context.arc(fromX + dx / 2 - offset, toy, radius, 0, 2*Math.PI,false)
context.stroke()
// draw straightline
context.setLineDash([3])
context.beginPath()
context.moveTo(fromx + dx / 2 + radius - offset, toy);
context.lineTo(tox, toy)
context.moveTo(fromX + dx / 2 + 2*radius - offset, toy);
context.lineTo(toX, toy)
context.stroke()
// draw arrow
context.setLineDash([10,0])
context.setLineDash([20,0])
context.beginPath()
context.moveTo(tox - headlen * Math.cos(angle - Math.PI / 6), toy - headlen * Math.sin(angle - Math.PI / 6))
context.lineTo(tox, toy )
context.lineTo(tox - headlen * Math.cos(angle + Math.PI / 6), toy - headlen * Math.sin(angle + Math.PI / 6))
context.moveTo(toX - headlen * Math.cos(angle - Math.PI / 6), toy - headlen * Math.sin(angle - Math.PI / 6))
context.lineTo(toX, toy )
context.lineTo(toX - headlen * Math.cos(angle + Math.PI / 6), toy - headlen * Math.sin(angle + Math.PI / 6))
context.stroke()
}
}

View File

@ -13,11 +13,11 @@ import StatusQ.Core.Theme 0.1
ColumnLayout {
id: balancedExceededError
property bool transferPossible: false
property double amountToSend: 0
property bool isLoading: true
property int errorType: Constants.NoError
visible: !balancedExceededError.transferPossible || isLoading
visible: balancedExceededError.errorType !== Constants.NoError || isLoading
StatusIcon {
Layout.preferredHeight: 20
@ -35,12 +35,14 @@ ColumnLayout {
visible: isLoading
}
StatusBaseText {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
font.pixelSize: 13
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
color: Theme.palette.dangerColor1
text: isLoading ? qsTr("Calculating fees") : qsTr("Balance exceeded")
text: isLoading ? qsTr("Calculating fees") : balancedExceededError.errorType === Constants.SendAmountExceedsBalance ?
qsTr("Balance exceeded") : balancedExceededError.errorType === Constants.NoRoute ? qsTr("No route found") : ""
wrapMode: Text.WordWrap
}
}

View File

@ -13,32 +13,37 @@ import "../panels"
Column {
id: root
visible: !isValid || isLoading
spacing: 5
property alias errorMessage: txtValidationError.text
property bool isValid: true
property int errorType: Constants.NoError
property bool isLoading: false
QtObject {
id: d
readonly property bool isValid: root.errorType === Constants.NoError
}
visible: !d.isValid || isLoading
spacing: 5
StatusIcon {
anchors.horizontalCenter: parent.horizontalCenter
height: 20
width: 20
icon: "cancel"
color: Theme.palette.dangerColor1
visible: !isValid && !isLoading
visible: !d.isValid && !isLoading
}
StatusLoadingIndicator {
anchors.horizontalCenter: parent.horizontalCenter
width: 24
height: 24
color: Theme.palette.baseColor1
visible: isLoading && isValid
visible: isLoading && d.isValid
}
StyledText {
id: txtValidationError
anchors.horizontalCenter: parent.horizontalCenter
text: isLoading? qsTr("Calculating fees"): qsTr("Balance exceeded")
text: isLoading ? qsTr("Calculating fees"): errorType === Constants.SendAmountExceedsBalance ?
qsTr("Balance exceeded") : qsTr("No route found")
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 13

View File

@ -67,7 +67,7 @@ StatusDialog {
}
property var recalculateRoutesAndFees: Backpressure.debounce(popup, 600, function() {
if(!!popup.selectedAccount && !!assetSelector.selectedAsset && d.recipientReady) {
if(!!popup.selectedAccount && !!assetSelector.selectedAsset && d.recipientReady && amountToSendInput.input.valid) {
popup.isLoading = true
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,
@ -78,15 +78,13 @@ StatusDialog {
QtObject {
id: d
readonly property int errorType: !amountToSendInput.input.valid ? Constants.SendAmountExceedsBalance :
(networkSelector.bestRoutes && networkSelector.bestRoutes.length <= 0 && !!amountToSendInput.input.text && recipientReady && !popup.isLoading) ?
Constants.NoRoute : Constants.NoError
readonly property double maxFiatBalance: !!assetSelector.selectedAsset ? (amountToSendInput.cryptoFiatFlipped ?
assetSelector.selectedAsset.totalCurrencyBalance :
assetSelector.selectedAsset.totalBalance): 0
onMaxFiatBalanceChanged: {
amountToSendInput.floatValidator.top = maxFiatBalance
amountToSendInput.input.validate()
}
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 errorMode: popup.isLoading || !recipientReady ? false : errorType !== Constants.NoError || 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
@ -110,6 +108,11 @@ StatusDialog {
popup.recalculateRoutesAndFees()
}
}
onErrorTypeChanged: {
if(errorType === Constants.SendAmountExceedsBalance)
bestRoutes = []
}
}
width: 556
@ -230,7 +233,7 @@ StatusDialog {
return ""
}
onSelectedAssetChanged: {
if (!assetSelector.selectedAsset || !!amountToSendInput.input.text || isNaN(amountToSendInput.input.text)) {
if (!assetSelector.selectedAsset || !amountToSendInput.input.text || isNaN(amountToSendInput.input.text)) {
return
}
popup.recalculateRoutesAndFees()
@ -242,8 +245,8 @@ StatusDialog {
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
bgColor: amountToSendInput.input.valid ? Theme.palette.primaryColor3 : Theme.palette.dangerColor2
titleText.color: amountToSendInput.input.valid ? Theme.palette.primaryColor1 : Theme.palette.dangerColor1
}
}
RowLayout {
@ -253,10 +256,15 @@ StatusDialog {
Layout.fillWidth:true
isBridgeTx: popup.isBridgeTx
interactive: popup.interactive
store: popup.store
selectedAsset: assetSelector.selectedAsset
errorMode: d.errorMode
maxFiatBalance: d.maxFiatBalance
currentCurrency: popup.store.currentCurrency
getFiatValue: function(cryptoValue) {
return popup.store.getFiatValue(cryptoValue, selectedAsset.symbol, currentCurrency)
}
getCryptoValue: function(fiatValue) {
return popup.store.getFiatValue(fiatValue, selectedAsset.symbol, currentCurrency)
}
onReCalculateSuggestedRoute: popup.recalculateRoutesAndFees()
}
AmountToReceive {
@ -405,7 +413,7 @@ StatusDialog {
selectedAsset: assetSelector.selectedAsset
onReCalculateSuggestedRoute: popup.recalculateRoutesAndFees()
visible: d.recipientReady && !!assetSelector.selectedAsset
errorType: d.errorType
isLoading: popup.isLoading
bestRoutes: popup.bestRoutes
isBridgeTx: popup.isBridgeTx
@ -424,6 +432,7 @@ StatusDialog {
bestRoutes: popup.bestRoutes
store: popup.store
gasFiatAmount: d.totalFeesInFiat
errorType: d.errorType
}
}
}
@ -435,7 +444,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.cryptoValueToSend) && fees.isValid && !d.errorMode
visible: d.recipientReady && !isNaN(amountToSendInput.cryptoValueToSend) && !d.errorMode
onNextButtonClicked: popup.sendTransaction()
}

View File

@ -13,16 +13,16 @@ 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 double maxFiatBalance: -1
property bool cryptoFiatFlipped: false
property string cryptoValueToSend: !cryptoFiatFlipped ? amountToSendInput.text : txtFiatBalance.text
property string currentCurrency
property var getFiatValue: function(cryptoValue) {}
property var getCryptoValue: function(fiatValue) {}
signal reCalculateSuggestedRoute()
@ -40,13 +40,13 @@ ColumnLayout {
function getFiatValue(value) {
if(!root.selectedAsset || !value)
return zeroString
let cryptoValue = root.store.getFiatValue(value, root.selectedAsset.symbol, root.store.currentCurrency)
let cryptoValue = root.getFiatValue(value)
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)
let cryptoValue = root.getCryptoValue(value)
return formatValue(parseFloat(cryptoValue))
}
}
@ -57,6 +57,11 @@ ColumnLayout {
}
}
onMaxFiatBalanceChanged: {
floatValidator.top = maxFiatBalance
input.validate()
}
StatusBaseText {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
text: root.isBridgeTx ? qsTr("Amount to bridge") : qsTr("Amount to send")
@ -73,7 +78,7 @@ ColumnLayout {
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.color: input.valid ? Theme.palette.directColor1 : Theme.palette.dangerColor1
input.edit.readOnly: !root.interactive
validators: [
StatusFloatValidator {
@ -99,7 +104,7 @@ ColumnLayout {
}
StatusBaseText {
Layout.alignment: Qt.AlignVCenter
text: root.store.currentCurrency.toUpperCase()
text: root.currentCurrency.toUpperCase()
font.pixelSize: amountToSendInput.input.edit.font.pixelSize
color: Theme.palette.baseColor1
visible: cryptoFiatFlipped
@ -123,7 +128,7 @@ ColumnLayout {
anchors.top: parent.top
anchors.left: txtFiatBalance.right
anchors.leftMargin: 4
text: !cryptoFiatFlipped ? root.store.currentCurrency.toUpperCase() : !!root.selectedAsset ? root.selectedAsset.symbol.toUpperCase() : ""
text: !cryptoFiatFlipped ? root.currentCurrency.toUpperCase() : !!root.selectedAsset ? root.selectedAsset.symbol.toUpperCase() : ""
font.pixelSize: 13
color: Theme.palette.directColor5
}

View File

@ -12,13 +12,12 @@ import "../controls"
Rectangle {
id: root
property alias isValid: gasValidator.isValid
property string gasFiatAmount
property bool isLoading: false
property var bestRoutes
property var store
property var selectedTokenSymbol
property int errorType: Constants.NoError
radius: 13
color: Theme.palette.indirectColor1
@ -70,7 +69,7 @@ Rectangle {
getFiatValue: root.store.getFiatValue
currentCurrency: root.store.currencyStore.currentCurrency
currentCurrencySymbol: root.store.currencyStore.currentCurrencySymbol
visible: gasValidator.isValid && !root.isLoading
visible: root.errorType === Constants.NoError && !root.isLoading
bestRoutes: root.bestRoutes
selectedTokenSymbol: root.selectedTokenSymbol
}
@ -78,7 +77,7 @@ Rectangle {
id: gasValidator
width: parent.width
isLoading: root.isLoading
isValid: root.bestRoutes ? root.bestRoutes.length > 0 : true
errorType: root.errorType
}
}
}

View File

@ -10,6 +10,8 @@ import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import "../controls"
Item {
id: root
@ -21,11 +23,13 @@ Item {
property bool customMode: false
property double amountToSend: 0
property double requiredGasInEth: 0
property bool errorMode: !d.thereIsApossibleRoute || d.customAmountToSend > root.amountToSend
property bool errorMode: d.customAmountToSend > root.amountToSend
property bool interactive: true
property bool showPreferredChains: false
property var weiToEth: function(wei) {}
property var reCalculateSuggestedRoute: function() {}
property int errorType: Constants.NoError
property bool isLoading
QtObject {
id: d
@ -38,6 +42,7 @@ Item {
fromNetworksRepeater.itemAt(i).routeOnNetwork = 0
toNetworksRepeater.itemAt(i).amountToReceive = 0
toNetworksRepeater.itemAt(i).routeOnNetwork = 0
toNetworksRepeater.itemAt(i).bentLine = 0
}
}
@ -72,6 +77,8 @@ Item {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
spacing: 12
StatusBaseText {
Layout.maximumWidth: 100
elide: Text.ElideRight
font.pixelSize: 10
color: Theme.palette.baseColor1
text: qsTr("Your Balances").toUpperCase()
@ -90,7 +97,7 @@ Item {
primaryText: model.chainName
secondaryText: (parseFloat(tokenBalanceOnChain) === 0 && root.amountToSend !== 0) ?
qsTr("No Balance") : !hasGas ? qsTr("No Gas") : advancedInputText
tertiaryText: qsTr("BALANCE: ") + LocaleUtils.numberToLocaleString(parseFloat(tokenBalanceOnChain))
tertiaryText: root.errorMode && parseFloat(advancedInputText) !== 0 && advancedInput.valid ? qsTr("EXCEEDS SEND AMOUNT"): qsTr("BALANCE: ") + LocaleUtils.numberToLocaleString(parseFloat(tokenBalanceOnChain))
locked: store.lockedInAmounts.findIndex(lockedItem => lockedItem !== undefined && lockedItem.chainID === model.chainId) !== -1
preCalculatedAdvancedText: {
let index = store.lockedInAmounts.findIndex(lockedItem => lockedItem!== undefined && lockedItem.chainID === model.chainId)
@ -99,9 +106,14 @@ Item {
}
else return LocaleUtils.numberToLocaleString(fromNetwork.amountToSend)
}
state: tokenBalanceOnChain === 0 || !hasGas ? "unavailable" : root.errorMode ? "error" : "default"
maxAdvancedValue: parseFloat(tokenBalanceOnChain)
state: tokenBalanceOnChain === 0 || !hasGas ?
"unavailable" :
(root.errorMode || !advancedInput.valid) && (parseFloat(advancedInputText) !== 0) ? "error" : "default"
cardIcon.source: Style.svg(model.iconUrl)
disabledText: qsTr("Disabled")
disableText: qsTr("Disable")
enableText: qsTr("Enable")
advancedMode: root.customMode
disabled: store.disabledChainIdsFromList.includes(model.chainId)
clickable: root.interactive
@ -116,17 +128,24 @@ Item {
disabled = store.disabledChainIdsFromList.includes(model.chainId)
}
onCardLocked: {
store.addLockedInAmount(model.chainId, advancedInputText, root.selectedAsset.decimals, isLocked)
locked = store.lockedInAmounts.length > 0 && store.lockedInAmounts.findIndex(lockedItem => lockedItem !== undefined && lockedItem.chainID === model.chainId) !== -1
d.calculateCustomAmounts()
if(!locked || d.customAmountToSend <= root.amountToSend)
if(!locked || (d.customAmountToSend <= root.amountToSend && advancedInput.valid))
root.reCalculateSuggestedRoute()
}
}
}
}
BalanceExceeded {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
amountToSend: root.amountToSend
isLoading: root.isLoading
errorType: root.errorType
}
ColumnLayout {
id: toMainNetworksLayout
id: toNetworksLayout
Layout.alignment: Qt.AlignRight | Qt.AlignTop
spacing: 12
StatusBaseText {
@ -137,47 +156,46 @@ Item {
text: StatusQUtils.Utils.elideText(selectedAccount.address, 6, 4).toUpperCase()
elide: Text.ElideMiddle
}
Column {
id: toNetworksLayout
spacing: root.customMode ? 26 : 12
Repeater {
id: toNetworksRepeater
model: root.allNetworks
StatusCard {
id: toCard
objectName: model.chainId
property int routeOnNetwork: 0
property double amountToReceive: 0
property bool preferred: store.preferredChainIds.includes(model.chainId)
primaryText: model.chainName
secondaryText: LocaleUtils.numberToLocaleString(amountToReceive)
tertiaryText: state === "unpreferred" ? qsTr("UNPREFERRED") : ""
state: root.errorMode ? "error" : !preferred ? "unpreferred" : "default"
opacity: preferred || showPreferredChains ? 1 : 0
cardIcon.source: Style.svg(model.iconUrl)
disabledText: qsTr("Disabled")
disabled: store.disabledChainIdsToList.includes(model.chainId)
clickable: root.interactive
onClicked: {
store.addRemoveDisabledToChain(model.chainId, disabled)
// only recalculate if the a best route was disabled
if(root.bestRoutes.length === 0 || routeOnNetwork !== 0 || !disabled)
Repeater {
id: toNetworksRepeater
model: root.allNetworks
StatusCard {
id: toCard
objectName: model.chainId
property int routeOnNetwork: 0
property int bentLine: 0
property double amountToReceive: 0
property bool preferred: store.preferredChainIds.includes(model.chainId)
primaryText: model.chainName
secondaryText: LocaleUtils.numberToLocaleString(amountToReceive)
tertiaryText: state === "unpreferred" ? qsTr("UNPREFERRED") : ""
state: !preferred ? "unpreferred" : "default"
opacity: preferred || showPreferredChains ? 1 : 0
cardIcon.source: Style.svg(model.iconUrl)
disabledText: qsTr("Disabled")
disableText: qsTr("Disable")
enableText: qsTr("Enable")
disabled: store.disabledChainIdsToList.includes(model.chainId)
clickable: root.interactive
onClicked: {
store.addRemoveDisabledToChain(model.chainId, disabled)
// only recalculate if the a best route was disabled
if((root.bestRoutes !== undefined && root.bestRoutes.length === 0) || routeOnNetwork !== 0 || !disabled)
root.reCalculateSuggestedRoute()
}
onVisibleChanged: {
if(visible) {
disabled = store.disabledChainIdsToList.includes(model.chainId)
preferred = store.preferredChainIds.includes(model.chainId)
}
}
onOpacityChanged: {
if(opacity === 1) {
disabled = store.disabledChainIdsToList.includes(model.chainId)
} else {
if(opacity === 0 && routeOnNetwork > 0)
root.reCalculateSuggestedRoute()
}
onVisibleChanged: {
if(visible) {
disabled = store.disabledChainIdsToList.includes(model.chainId)
preferred = store.preferredChainIds.includes(model.chainId)
}
}
onOpacityChanged: {
if(opacity === 1) {
disabled = store.disabledChainIdsToList.includes(model.chainId)
} else {
if(opacity === 0 && routeOnNetwork > 0)
root.reCalculateSuggestedRoute()
}
}
}
}
}
@ -228,20 +246,21 @@ Item {
}
}
if(toN !== null && fromN !== null) {
yOffsetFrom = toN.objectName === fromN.objectName && toN.routeOnNetwork !== 0 ? toN.routeOnNetwork * 10 : 0
yOffsetTo = toN.routeOnNetwork * 10
xOffset = toN.routeOnNetwork * 10
yOffsetFrom = toN.objectName === fromN.objectName && toN.routeOnNetwork !== 0 ? toN.routeOnNetwork * 16 : 0
yOffsetTo = toN.routeOnNetwork * 16
xOffset = (fromN.y - toN.y > 0 ? -1 : 1) * toN.bentLine * 16
let amountToSend = weiToEth(bestRoutes[i].amountIn)
let amountToReceive = weiToEth(bestRoutes[i].amountOut)
fromN.amountToSend = amountToSend
toN.amountToReceive += amountToReceive
fromN.routeOnNetwork += 1
toN.routeOnNetwork += 1
toN.bentLine = toN.objectName !== fromN.objectName
d.thereIsApossibleRoute = true
let routeColor = root.errorMode ? Theme.palette.dangerColor1 : toN.preferred ? '#627EEA' : Theme.palette.pinColor1
StatusQUtils.Utils.drawArrow(ctx, fromN.x + fromN.width,
fromN.y + fromN.cardIconPosition + yOffsetFrom,
toMainNetworksLayout.x + toN.x,
toNetworksLayout.x + toN.x,
toNetworksLayout.y + toN.y + toN.cardIconPosition + yOffsetTo,
routeColor, xOffset)
}

View File

@ -29,6 +29,7 @@ Item {
property bool isBridgeTx: false
property bool showUnpreferredNetworks: advancedNetworkRoutingPage.showUnpreferredNetworks
property var toNetworksList: []
property int errorType: Constants.NoError
signal reCalculateSuggestedRoute()
@ -80,6 +81,7 @@ Item {
selectedAsset: root.selectedAsset
selectedAccount: root.selectedAccount
errorMode: root.errorMode
errorType: root.errorType
toNetworksList: root.toNetworksList
weiToEth: function(wei) {
return "%1 %2".arg(LocaleUtils.numberToLocaleString(parseFloat(store.getWei2Eth(wei, selectedAsset.decimals)))).arg(selectedAsset.symbol)
@ -110,6 +112,7 @@ Item {
isLoading: root.isLoading
interactive: root.interactive
isBridgeTx: root.isBridgeTx
errorType: root.errorType
weiToEth: function(wei) {
return parseFloat(store.getWei2Eth(wei, selectedAsset.decimals))
}

View File

@ -27,6 +27,7 @@ ColumnLayout {
property bool interactive: true
property bool isBridgeTx: false
property bool showUnpreferredNetworks: preferredToggleButton.checked
property int errorType: Constants.NoError
signal reCalculateSuggestedRoute()
@ -74,19 +75,9 @@ ColumnLayout {
text: qsTr("The networks where the receipient will receive tokens. Amounts calculated automatically for the lowest cost.")
wrapMode: Text.WordWrap
}
BalanceExceeded {
id: balanceExceeded
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Style.current.bigPadding
transferPossible: root.store.disabledChainIdsToList.length > 0 || root.store.disabledChainIdsFromList.length > 0 ? true : root.bestRoutes ? root.bestRoutes.length > 0 : false
amountToSend: root.amountToSend
isLoading: root.isLoading
}
Loader {
id: networksLoader
Layout.topMargin: Style.current.padding
active: !balanceExceeded.visible
visible: active
sourceComponent: NetworkCardsComponent {
store: root.store
@ -103,6 +94,8 @@ ColumnLayout {
bestRoutes: root.bestRoutes
weiToEth: root.weiToEth
interactive: root.interactive
errorType: root.errorType
isLoading: root.isLoading
}
}
}

View File

@ -26,6 +26,7 @@ RowLayout {
property var weiToEth: function(wei) {}
property var reCalculateSuggestedRoute: function() {}
property bool errorMode: false
property int errorType: Constants.NoError
spacing: 10
StatusRoundIcon {
@ -62,7 +63,7 @@ RowLayout {
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
clip: true
visible: !root.isLoading ? root.isBridgeTx ? true : root.bestRoutes !== undefined ? root.bestRoutes.length > 0 : true : false
visible: !root.isLoading ? root.isBridgeTx ? true : root.errorType === Constants.NoError : false
Column {
id: row
spacing: Style.current.padding
@ -78,9 +79,9 @@ RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Style.current.bigPadding
transferPossible: root.bestRoutes !== undefined ? root.bestRoutes.length > 0 : true
amountToSend: root.amountToSend
isLoading: root.isLoading
errorType: root.errorType
}
}

View File

@ -725,6 +725,12 @@ QtObject {
Bridge
}
enum ErrorType {
SendAmountExceedsBalance,
NoRoute,
NoError
}
readonly property QtObject walletSection: QtObject {
readonly property string cancelledMessage: "cancelled"
}