feat(Community Airdrops): Dynamic fee calculation in the airdrop form
Closes: #10547
This commit is contained in:
parent
d4ba22f7bb
commit
e0dd0b82ce
|
@ -157,6 +157,10 @@ ListModel {
|
|||
title: "SequenceColumnLayout"
|
||||
section: "Panels"
|
||||
}
|
||||
ListElement {
|
||||
title: "FeesPanel"
|
||||
section: "Panels"
|
||||
}
|
||||
ListElement {
|
||||
title: "BurnTokensPopup"
|
||||
section: "Popups"
|
||||
|
|
|
@ -94,11 +94,58 @@ SplitView {
|
|||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: feesCalculationTimer
|
||||
|
||||
interval: 2000
|
||||
|
||||
property var response
|
||||
|
||||
function requestMockedFees(contractKeysAndAmounts) {
|
||||
const fees = []
|
||||
|
||||
function createAmount(amount, symbol, decimals) {
|
||||
return {
|
||||
amount, symbol,
|
||||
displayDecimals: decimals, stripTrailingZeroes: false
|
||||
}
|
||||
}
|
||||
|
||||
contractKeysAndAmounts.forEach(entry => {
|
||||
fees.push({
|
||||
ethFee: createAmount(0.0002120115, "ETH", 4),
|
||||
fiatFee: createAmount(123.15, "USD", 2),
|
||||
errorCode: 0,
|
||||
contractUniqueKey: entry.contractUniqueKey
|
||||
})
|
||||
})
|
||||
|
||||
response = {
|
||||
fees, errorCode: feesErrorsButtonGroup.checkedButton.code,
|
||||
totalEthFee: createAmount(0.0002120115 * fees.length, "ETH", 4),
|
||||
totalFiatFee: createAmount(123.15 * fees.length, "USD", 2)
|
||||
}
|
||||
|
||||
restart()
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (!loader.item)
|
||||
return
|
||||
|
||||
const view = loader.item
|
||||
view.airdropFees = response
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Pane {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
|
||||
anchors.fill: parent
|
||||
active: globalUtilsReady && mainModuleReady
|
||||
|
||||
|
@ -123,6 +170,14 @@ SplitView {
|
|||
name: "infiniteSupply"
|
||||
expression: !(model.index % 4)
|
||||
},
|
||||
ExpressionRole {
|
||||
name: "accountName"
|
||||
expression: "StatusAccount"
|
||||
},
|
||||
ExpressionRole {
|
||||
name: "contractUniqueKey"
|
||||
expression: "contractUniqueKey_" + model.index
|
||||
},
|
||||
ExpressionRole {
|
||||
name: "chainName"
|
||||
expression: model.index ? "Optimism" : "Arbitrum"
|
||||
|
@ -142,7 +197,6 @@ SplitView {
|
|||
value: TokenCategories.Category.Community
|
||||
}
|
||||
|
||||
|
||||
Component.onCompleted: {
|
||||
Qt.callLater(() => communityNewAirdropView.collectiblesModel = this)
|
||||
}
|
||||
|
@ -196,7 +250,17 @@ SplitView {
|
|||
membersModel: members
|
||||
|
||||
onAirdropClicked: {
|
||||
logs.logEvent("CommunityNewAirdropView::airdropClicked", ["airdropTokens", "addresses", "membersPubKeys"], arguments)
|
||||
logs.logEvent("CommunityNewAirdropView::airdropClicked",
|
||||
["airdropTokens", "addresses", "membersPubKeys"],
|
||||
arguments)
|
||||
}
|
||||
|
||||
onAirdropFeesRequested: {
|
||||
logs.logEvent("CommunityNewAirdropView::airdropFeesRequested",
|
||||
["contractKeysAndAmounts", "addresses"],
|
||||
arguments)
|
||||
|
||||
feesCalculationTimer.requestMockedFees(contractKeysAndAmounts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -211,13 +275,48 @@ SplitView {
|
|||
logsView.logText: logs.logText
|
||||
|
||||
ColumnLayout {
|
||||
MenuSeparator {}
|
||||
|
||||
TextEdit {
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
text: "valid address: 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc4"
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
|
||||
ButtonGroup {
|
||||
id: feesErrorsButtonGroup
|
||||
|
||||
buttons: feesErrorsRow.children
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: feesErrorsRow
|
||||
|
||||
Label {
|
||||
text: "Fees calculation errors:"
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
readonly property int code: Constants.ComputeFeeErrorCode.Success
|
||||
text: `Success (${code})`
|
||||
checked: true
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
readonly property int code: Constants.ComputeFeeErrorCode.Infura
|
||||
text: `Infura (${code})`
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
readonly property int code: Constants.ComputeFeeErrorCode.Balance
|
||||
text: `Balance (${code})`
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
readonly property int code: Constants.ComputeFeeErrorCode.Other
|
||||
text: `Other (${code})`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import Storybook 1.0
|
||||
|
||||
import AppLayouts.Chat.panels.communities 1.0
|
||||
|
||||
|
||||
SplitView {
|
||||
Logs { id: logs }
|
||||
|
||||
SplitView {
|
||||
orientation: Qt.Vertical
|
||||
SplitView.fillWidth: true
|
||||
|
||||
Pane {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: feesPanel
|
||||
anchors.margins: -15
|
||||
border.color: "lightgray"
|
||||
}
|
||||
|
||||
FeesPanel {
|
||||
id: feesPanel
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: 500
|
||||
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
account: "My Account 1"
|
||||
network: "Optimism"
|
||||
symbol: "TAT"
|
||||
amount: 2
|
||||
feeText: "0.0015 ($75.43)"
|
||||
}
|
||||
ListElement {
|
||||
account: "My Account 2"
|
||||
network: "Arbitrum"
|
||||
symbol: "SNT"
|
||||
amount: 34
|
||||
feeText: "0.0085 ETH ($175.43)"
|
||||
}
|
||||
}
|
||||
|
||||
errorText: errorTextField.text
|
||||
isFeeLoading: loadingSwitch.checked
|
||||
showSummary: showSummarySwitch.checked
|
||||
showAccounts: showAccountsSwitch.checked
|
||||
|
||||
totalFeeText: "0.01 ETH ($265.43)"
|
||||
}
|
||||
}
|
||||
|
||||
LogsAndControlsPanel {
|
||||
id: logsAndControlsPanel
|
||||
|
||||
SplitView.minimumHeight: 100
|
||||
SplitView.preferredHeight: 150
|
||||
|
||||
logsView.logText: logs.logText
|
||||
}
|
||||
}
|
||||
|
||||
Pane {
|
||||
SplitView.minimumWidth: 300
|
||||
SplitView.preferredWidth: 300
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: "Error text"
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: errorTextField
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: ""
|
||||
}
|
||||
|
||||
Switch {
|
||||
id: loadingSwitch
|
||||
|
||||
text: "Is fee loading"
|
||||
checked: false
|
||||
}
|
||||
|
||||
Switch {
|
||||
id: showSummarySwitch
|
||||
|
||||
text: "Show summary"
|
||||
checked: true
|
||||
}
|
||||
|
||||
Switch {
|
||||
id: showAccountsSwitch
|
||||
|
||||
text: "Show account names"
|
||||
checked: true
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
Control {
|
||||
id: root
|
||||
|
||||
// account, amount, symbol, network, feeText
|
||||
property alias model: repeater.model
|
||||
readonly property alias count: repeater.count
|
||||
|
||||
property alias showSummary: summaryRow.visible
|
||||
property alias errorText: errorTxt.text
|
||||
property alias totalFeeText: totalFeeText.text
|
||||
|
||||
property bool isFeeLoading: false
|
||||
property bool showAccounts: true
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property int delegateHeightWhenAccountsHidden: 28
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: Style.current.padding
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: root.showAccounts
|
||||
? delegateColumn.implicitHeight
|
||||
: Math.max(delegateColumn.implicitHeight,
|
||||
d.delegateHeightWhenAccountsHidden)
|
||||
|
||||
ColumnLayout {
|
||||
id: delegateColumn
|
||||
|
||||
width: parent.width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Airdropping %1 %2 on %3")
|
||||
.arg(model.amount).arg(model.symbol)
|
||||
.arg(model.network)
|
||||
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StatusDotsLoadingIndicator {
|
||||
Layout.rightMargin: Style.current.padding
|
||||
|
||||
visible: root.isFeeLoading
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
text: model.feeText
|
||||
|
||||
visible: !root.isFeeLoading
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
elide: Text.ElideMiddle
|
||||
color: repeater.count === 1 ? Theme.palette.directColor1
|
||||
: Theme.palette.baseColor1
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
|
||||
visible: root.showAccounts
|
||||
|
||||
text: qsTr("via %1").arg(model.account)
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
elide: Text.ElideMiddle
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
|
||||
visible: summaryRow.visible
|
||||
|
||||
color: Theme.palette.baseColor2
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: summaryRow
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.current.halfPadding
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Total")
|
||||
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
StatusDotsLoadingIndicator {
|
||||
visible: root.isFeeLoading
|
||||
|
||||
Layout.rightMargin: Style.current.padding
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: totalFeeText
|
||||
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
visible: !root.isFeeLoading
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: errorTxt
|
||||
|
||||
Layout.topMargin: Style.current.halfPadding
|
||||
Layout.fillWidth: true
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
color: Theme.palette.dangerColor1
|
||||
|
||||
text: root.errorText
|
||||
visible: root.errorText !== ""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ CommunityMintTokensSettingsPanel 1.0 CommunityMintTokensSettingsPanel.qml
|
|||
CommunityPermissionsSettingsPanel 1.0 CommunityPermissionsSettingsPanel.qml
|
||||
CommunityProfilePopupInviteFriendsPanel 1.0 CommunityProfilePopupInviteFriendsPanel.qml
|
||||
CommunityProfilePopupInviteMessagePanel 1.0 CommunityProfilePopupInviteMessagePanel.qml
|
||||
FeesPanel 1.0 FeesPanel.qml
|
||||
HidePermissionPanel 1.0 HidePermissionPanel.qml
|
||||
JoinPermissionsOverlayPanel 1.0 JoinPermissionsOverlayPanel.qml
|
||||
MintTokensFooterPanel 1.0 MintTokensFooterPanel.qml
|
||||
|
|
|
@ -1,25 +1,23 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQml.Models 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Popups.Dialog 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
||||
import AppLayouts.Chat.panels.communities 1.0
|
||||
|
||||
StatusDialog {
|
||||
id: root
|
||||
|
||||
// account, amount, symbol, network, feeText
|
||||
property alias model: repeater.model
|
||||
property alias showSummary: summaryRow.visible
|
||||
property alias errorText: errorTxt.text
|
||||
property alias totalFeeText: totalFeeText.text
|
||||
property alias model: feesPanel.model
|
||||
property alias showSummary: feesPanel.showSummary
|
||||
property alias errorText: feesPanel.errorText
|
||||
property alias totalFeeText: feesPanel.totalFeeText
|
||||
|
||||
property bool isFeeLoading
|
||||
property alias isFeeLoading: feesPanel.isFeeLoading
|
||||
|
||||
signal signTransactionClicked()
|
||||
signal cancelClicked()
|
||||
|
@ -32,126 +30,10 @@ StatusDialog {
|
|||
|
||||
implicitWidth: 600 // by design
|
||||
topPadding: 2 * Style.current.padding // by design
|
||||
bottomPadding: topPadding
|
||||
bottomPadding: Style.current.bigPadding
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: column
|
||||
|
||||
spacing: Style.current.padding
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
|
||||
implicitHeight: delegateColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: delegateColumn
|
||||
|
||||
width: parent.width
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Airdropping %1 %2 on %3")
|
||||
.arg(model.amount).arg(model.symbol)
|
||||
.arg(model.network)
|
||||
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
StatusDotsLoadingIndicator {
|
||||
visible: root.isFeeLoading
|
||||
|
||||
Layout.rightMargin: Style.current.padding
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
text: model.feeText
|
||||
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
elide: Text.ElideMiddle
|
||||
|
||||
color: Theme.palette.baseColor1
|
||||
|
||||
visible: !root.isFeeLoading
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("via %1").arg(model.account)
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
elide: Text.ElideMiddle
|
||||
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
|
||||
color: Theme.palette.baseColor2
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: summaryRow
|
||||
|
||||
Layout.fillHeight: false
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.topMargin: Style.current.halfPadding
|
||||
Layout.bottomMargin: -Style.current.halfPadding
|
||||
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: qsTr("Total")
|
||||
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
StatusDotsLoadingIndicator {
|
||||
visible: root.isFeeLoading
|
||||
|
||||
Layout.rightMargin: Style.current.padding
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: totalFeeText
|
||||
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
visible: !root.isFeeLoading
|
||||
}
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
id: errorTxt
|
||||
|
||||
Layout.topMargin: Style.current.halfPadding
|
||||
Layout.bottomMargin: -Style.current.halfPadding
|
||||
Layout.fillWidth: true
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
color: Theme.palette.dangerColor1
|
||||
|
||||
text: root.errorText
|
||||
visible: root.errorText !== ""
|
||||
}
|
||||
contentItem: FeesPanel {
|
||||
id: feesPanel
|
||||
}
|
||||
|
||||
footer: StatusDialogFooter {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
|
||||
import utils 1.0
|
||||
|
@ -48,6 +50,24 @@ StatusScrollView {
|
|||
|
||||
readonly property var selectedHoldingsModel: ListModel {}
|
||||
|
||||
ModelChangeTracker {
|
||||
id: holdingsModelTracker
|
||||
|
||||
model: selectedHoldingsModel
|
||||
}
|
||||
|
||||
ModelChangeTracker {
|
||||
id: addressesModelTracker
|
||||
|
||||
model: addresses
|
||||
}
|
||||
|
||||
ModelChangeTracker {
|
||||
id: membersModelTracker
|
||||
|
||||
model: selectedMembersModel
|
||||
}
|
||||
|
||||
readonly property bool isFullyFilled: tokensSelector.count > 0 &&
|
||||
airdropRecipientsSelector.count > 0 &&
|
||||
airdropRecipientsSelector.valid
|
||||
|
@ -77,6 +97,51 @@ StatusScrollView {
|
|||
addresses.addAddresses(_addresses)
|
||||
}
|
||||
|
||||
onAirdropFeesChanged: {
|
||||
if (root.airdropFees === null)
|
||||
return
|
||||
|
||||
const fees = root.airdropFees.fees
|
||||
const errorCode = root.airdropFees.errorCode
|
||||
|
||||
function buildFeeString(ethFee, fiatFee) {
|
||||
return `${LocaleUtils.currencyAmountToLocaleString(ethFee)} (${LocaleUtils.currencyAmountToLocaleString(fiatFee)})`
|
||||
}
|
||||
|
||||
d.feesError = ""
|
||||
d.totalFee = ""
|
||||
|
||||
if (errorCode === Constants.ComputeFeeErrorCode.Infura) {
|
||||
d.feesError = qsTr("Infura error")
|
||||
return
|
||||
}
|
||||
|
||||
if (errorCode === Constants.ComputeFeeErrorCode.Success ||
|
||||
errorCode === Constants.ComputeFeeErrorCode.Balance) {
|
||||
fees.forEach(fee => {
|
||||
const idx = ModelUtils.indexOf(
|
||||
feesModel, "contractUniqueKey",
|
||||
fee.contractUniqueKey)
|
||||
|
||||
feesModel.set(idx, {
|
||||
feeText: buildFeeString(fee.ethFee, fee.fiatFee)
|
||||
})
|
||||
})
|
||||
|
||||
d.totalFee = buildFeeString(
|
||||
root.airdropFees.totalEthFee,
|
||||
root.airdropFees.totalFiatFee)
|
||||
|
||||
if (errorCode === Constants.ComputeFeeErrorCode.Balance) {
|
||||
d.feesError = qsTr("Your account does not have enough ETH to pay the gas fee for this airdrop. Try adding some ETH to your account.")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
d.feesError = qsTr("Unknown error")
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
|
@ -84,14 +149,41 @@ StatusScrollView {
|
|||
readonly property int dropdownHorizontalOffset: 4
|
||||
readonly property int dropdownVerticalOffset: 1
|
||||
|
||||
property string feesError
|
||||
property string totalFee
|
||||
|
||||
readonly property bool isFeeLoading:
|
||||
root.airdropFees === null ||
|
||||
(root.airdropFees.errorCode !== Constants.ComputeFeeErrorCode.Success &&
|
||||
root.airdropFees.errorCode !== Constants.ComputeFeeErrorCode.Balance)
|
||||
|
||||
readonly property bool showFees: root.selectedHoldingsModel.count > 0
|
||||
&& airdropRecipientsSelector.valid
|
||||
&& airdropRecipientsSelector.count > 0
|
||||
|
||||
readonly property int totalRevision: holdingsModelTracker.revision
|
||||
+ addressesModelTracker.revision
|
||||
+ membersModelTracker.revision
|
||||
+ (d.showFees ? 1 : 0)
|
||||
|
||||
onTotalRevisionChanged: {
|
||||
Qt.callLater(() => {
|
||||
if (!d.showFees)
|
||||
return
|
||||
|
||||
d.resetFees()
|
||||
})
|
||||
}
|
||||
|
||||
function prepareEntry(key, amount, type) {
|
||||
var tokenModel = null
|
||||
let tokenModel = null
|
||||
if(type === Constants.TokenType.ERC20)
|
||||
tokenModel = root.assetsModel
|
||||
else if (type === Constants.TokenType.ERC721)
|
||||
tokenModel = root.collectiblesModel
|
||||
|
||||
const modelItem = CommunityPermissionsHelpers.getTokenByKey(tokenModel, key)
|
||||
const modelItem = CommunityPermissionsHelpers.getTokenByKey(
|
||||
tokenModel, key)
|
||||
|
||||
return {
|
||||
key, amount, type,
|
||||
|
@ -102,9 +194,59 @@ StatusScrollView {
|
|||
supply: modelItem.supply,
|
||||
infiniteSupply: modelItem.infiniteSupply,
|
||||
contractUniqueKey: modelItem.contractUniqueKey,
|
||||
accountName: modelItem.accountName
|
||||
accountName: modelItem.accountName,
|
||||
symbol: modelItem.symbol
|
||||
}
|
||||
}
|
||||
|
||||
function rebuildFeesModel() {
|
||||
feesModel.clear()
|
||||
|
||||
const airdropTokens = ModelUtils.modelToArray(
|
||||
root.selectedHoldingsModel,
|
||||
["contractUniqueKey", "accountName",
|
||||
"symbol", "amount", "tokenText",
|
||||
"networkText"])
|
||||
|
||||
airdropTokens.forEach(entry => {
|
||||
feesModel.append({
|
||||
contractUniqueKey: entry.contractUniqueKey,
|
||||
amount: entry.amount * addresses.count,
|
||||
account: entry.accountName,
|
||||
symbol: entry.symbol,
|
||||
network: entry.networkText,
|
||||
feeText: ""
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function requestFees() {
|
||||
const airdropTokens = ModelUtils.modelToArray(
|
||||
root.selectedHoldingsModel,
|
||||
["contractUniqueKey", "amount"])
|
||||
|
||||
const contractKeysAndAmounts = airdropTokens.map(item => ({
|
||||
amount: item.amount,
|
||||
contractUniqueKey: item.contractUniqueKey
|
||||
}))
|
||||
const addressesArray = ModelUtils.modelToArray(
|
||||
addresses, ["address"]).map(e => e.address)
|
||||
|
||||
airdropFeesRequested(contractKeysAndAmounts, addressesArray)
|
||||
}
|
||||
|
||||
function resetFees() {
|
||||
root.airdropFees = null
|
||||
d.feesError = ""
|
||||
d.totalFee = ""
|
||||
|
||||
d.rebuildFeesModel()
|
||||
d.requestFees()
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: feesModel
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
|
@ -302,6 +444,8 @@ StatusScrollView {
|
|||
recipientsCountInstantiator.maximumRecipientsCount
|
||||
|
||||
membersModel: SortFilterProxyModel {
|
||||
id: selectedMembersModel
|
||||
|
||||
sourceModel: membersModel
|
||||
|
||||
filters: ExpressionFilter {
|
||||
|
@ -432,7 +576,68 @@ StatusScrollView {
|
|||
}
|
||||
}
|
||||
|
||||
SequenceColumnLayout.Separator {}
|
||||
|
||||
StatusGroupBox {
|
||||
id: feesBox
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: 0
|
||||
|
||||
title: qsTr("Fees")
|
||||
icon: Style.svg("gas")
|
||||
|
||||
Control {
|
||||
id: feesControl
|
||||
|
||||
width: feesBox.availableWidth
|
||||
|
||||
padding: Style.current.padding
|
||||
verticalPadding: 18
|
||||
|
||||
background: Rectangle {
|
||||
radius: Style.current.radius
|
||||
color: Theme.palette.indirectColor1
|
||||
}
|
||||
|
||||
contentItem: Loader {
|
||||
Component {
|
||||
id: feesPanelComponent
|
||||
|
||||
FeesPanel {
|
||||
width: feesControl.availableWidth
|
||||
|
||||
showAccounts: false
|
||||
totalFeeText: d.totalFee
|
||||
showSummary: count > 1
|
||||
isFeeLoading: d.isFeeLoading
|
||||
|
||||
model: feesModel
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: placeholderComponent
|
||||
|
||||
StatusBaseText {
|
||||
width: feesControl.availableWidth
|
||||
|
||||
text: qsTr("Add valid “What” and “To” values to see fees")
|
||||
font.pixelSize: Style.current.primaryTextFontSize
|
||||
elide: Text.ElideRight
|
||||
color: Theme.palette.baseColor1
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: d.showFees ? feesPanelComponent
|
||||
: placeholderComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WarningPanel {
|
||||
id: notEnoughTokensWarning
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.current.padding
|
||||
|
||||
|
@ -442,13 +647,22 @@ StatusScrollView {
|
|||
recipientsCountInstantiator.maximumRecipientsCount < airdropRecipientsSelector.count
|
||||
}
|
||||
|
||||
WarningPanel {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.current.padding
|
||||
|
||||
text: d.feesError
|
||||
|
||||
visible: !notEnoughTokensWarning.visible && d.showFees && d.feesError
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
Layout.preferredHeight: 44
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.current.bigPadding
|
||||
text: qsTr("Create airdrop")
|
||||
enabled: root.isFullyFilled
|
||||
enabled: root.isFullyFilled && !d.isFeeLoading && d.feesError === ""
|
||||
|
||||
onClicked: {
|
||||
feesPopup.open()
|
||||
|
@ -460,13 +674,12 @@ StatusScrollView {
|
|||
|
||||
destroyOnClose: false
|
||||
|
||||
model: ListModel {
|
||||
id: feesModel
|
||||
}
|
||||
model: feesModel
|
||||
|
||||
isFeeLoading: root.airdropFees === null ||
|
||||
(root.airdropFees.errorCode !== Constants.ComputeFeeErrorCode.Success &&
|
||||
root.airdropFees.errorCode !== Constants.ComputeFeeErrorCode.Balance)
|
||||
isFeeLoading: d.isFeeLoading
|
||||
|
||||
totalFeeText: d.totalFee
|
||||
errorText: d.feesError
|
||||
|
||||
onOpened: {
|
||||
const title1 = qsTr("Sign transaction - Airdrop %n token(s)", "",
|
||||
|
@ -476,36 +689,7 @@ StatusScrollView {
|
|||
|
||||
title = `${title1} ${title2}`
|
||||
|
||||
root.airdropFees = null
|
||||
errorText = ""
|
||||
feesModel.clear()
|
||||
|
||||
const airdropTokens = ModelUtils.modelToArray(
|
||||
root.selectedHoldingsModel,
|
||||
["contractUniqueKey", "accountName",
|
||||
"key", "amount", "tokenText",
|
||||
"networkText"])
|
||||
|
||||
airdropTokens.forEach(entry => {
|
||||
feesModel.append({
|
||||
contractUniqueKey: entry.contractUniqueKey,
|
||||
key: entry.key,
|
||||
amount: entry.amount,
|
||||
account: entry.accountName,
|
||||
symbol: entry.key,
|
||||
network: entry.networkText,
|
||||
feeText: ""
|
||||
})
|
||||
})
|
||||
|
||||
const contractKeysAndAmounts = airdropTokens.map(item => ({
|
||||
amount: item.amount,
|
||||
contractUniqueKey: item.contractUniqueKey
|
||||
}))
|
||||
const addresses_ = ModelUtils.modelToArray(
|
||||
addresses, ["address"]).map(e => e.address)
|
||||
|
||||
airdropFeesRequested(contractKeysAndAmounts, addresses_)
|
||||
d.resetFees()
|
||||
}
|
||||
|
||||
onSignTransactionClicked: {
|
||||
|
@ -520,52 +704,6 @@ StatusScrollView {
|
|||
|
||||
root.airdropClicked(airdropTokens, addresses_, pubKeys)
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
|
||||
function onAirdropFeesChanged() {
|
||||
if (root.airdropFees === null)
|
||||
return
|
||||
|
||||
const fees = root.airdropFees.fees
|
||||
const errorCode = root.airdropFees.errorCode
|
||||
|
||||
function buildFeeString(ethFee, fiatFee) {
|
||||
return `${LocaleUtils.currencyAmountToLocaleString(ethFee)} (${LocaleUtils.currencyAmountToLocaleString(fiatFee)})`
|
||||
}
|
||||
|
||||
if (errorCode === Constants.ComputeFeeErrorCode.Infura) {
|
||||
feesPopup.errorText = qsTr("Infura error")
|
||||
return
|
||||
}
|
||||
|
||||
if (errorCode === Constants.ComputeFeeErrorCode.Success ||
|
||||
errorCode === Constants.ComputeFeeErrorCode.Balance) {
|
||||
fees.forEach(fee => {
|
||||
const idx = ModelUtils.indexOf(
|
||||
feesModel, "contractUniqueKey",
|
||||
fee.contractUniqueKey)
|
||||
|
||||
feesPopup.model.set(idx, {
|
||||
feeText: buildFeeString(fee.ethFee, fee.fiatFee)
|
||||
})
|
||||
})
|
||||
|
||||
feesPopup.totalFeeText = buildFeeString(
|
||||
root.airdropFees.totalEthFee,
|
||||
root.airdropFees.totalFiatFee)
|
||||
|
||||
if (errorCode === Constants.ComputeFeeErrorCode.Balance) {
|
||||
feesPopup.errorText = qsTr("Not enough funds to make transaction")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
feesPopup.errorText = qsTr("Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75 3C6.67893 3 5 4.67893 5 6.75V18.75C5 19.7165 5.7835 20.5 6.75 20.5H12.75C13.7165 20.5 14.5 19.7165 14.5 18.75V13H14.75C15.4404 13 16 13.5596 16 14.25V17.75C16 19.2688 17.2312 20.5 18.75 20.5C20.2688 20.5 21.5 19.2688 21.5 17.75V9.1113C21.5 8.02994 21.0332 7.00122 20.2194 6.28914L17.2439 3.68557C16.9322 3.41281 16.4583 3.44439 16.1856 3.75612C15.9128 4.06785 15.9444 4.54167 16.2561 4.81443L17.744 6.11634C16.7328 6.43675 16 7.38279 16 8.5C16 9.88071 17.1193 11 18.5 11C19.0628 11 19.5822 10.814 20 10.5002V17.75C20 18.4404 19.4404 19 18.75 19C18.0596 19 17.5 18.4404 17.5 17.75V14.25C17.5 12.7312 16.2688 11.5 14.75 11.5H14.5V6.75C14.5 4.67893 12.8211 3 10.75 3H8.75ZM6.5 6.75C6.5 5.50736 7.50736 4.5 8.75 4.5H10.75C11.9926 4.5 13 5.50736 13 6.75V9.25L6.5 9.25V6.75ZM6.5 10.75V18.75C6.5 18.8881 6.61193 19 6.75 19H12.75C12.8881 19 13 18.8881 13 18.75V10.75L6.5 10.75ZM18.5 7.5C17.9477 7.5 17.5 7.94772 17.5 8.5C17.5 9.05228 17.9477 9.5 18.5 9.5C19.0523 9.5 19.5 9.05228 19.5 8.5C19.5 7.94772 19.0523 7.5 18.5 7.5Z" fill="#4360DF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
Loading…
Reference in New Issue