feat(CommunityAirdrops): Fees popup with multiple entries for airdrops
Closes: #10484
This commit is contained in:
parent
38d3b32cb9
commit
8b44e686f4
|
@ -193,6 +193,10 @@ ListModel {
|
|||
title: "SignTokenTransactionsPopup"
|
||||
section: "Popups"
|
||||
}
|
||||
ListElement {
|
||||
title: "SignMultiTokenTransactionsPopup"
|
||||
section: "Popups"
|
||||
}
|
||||
ListElement {
|
||||
title: "RemotelyDestructPopup"
|
||||
section: "Popups"
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import Storybook 1.0
|
||||
|
||||
import AppLayouts.Chat.popups.community 1.0
|
||||
|
||||
|
||||
SplitView {
|
||||
Logs { id: logs }
|
||||
|
||||
SplitView {
|
||||
orientation: Qt.Vertical
|
||||
SplitView.fillWidth: true
|
||||
|
||||
Pane {
|
||||
id: pane
|
||||
|
||||
SplitView.fillWidth: true
|
||||
SplitView.fillHeight: true
|
||||
|
||||
PopupBackground {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.centerIn: parent
|
||||
text: "Reopen"
|
||||
|
||||
onClicked: dialog.open()
|
||||
}
|
||||
|
||||
SignMultiTokenTransactionsPopup {
|
||||
id: dialog
|
||||
|
||||
model: ListModel {
|
||||
id: feesModel
|
||||
|
||||
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)"
|
||||
}
|
||||
}
|
||||
|
||||
closePolicy: Popup.NoAutoClose
|
||||
visible: true
|
||||
modal: false
|
||||
destroyOnClose: false
|
||||
|
||||
title: `Sign transaction - Airdrop ${model.count} token(s) to 32 recipients`
|
||||
|
||||
isFeeLoading: loadingSwitch.checked
|
||||
showSummary: showSummarySwitch.checked
|
||||
|
||||
errorText: errorTextField.text
|
||||
totalFeeText: "0.01 ETH ($265.43)"
|
||||
|
||||
onSignTransactionClicked: logs.logEvent("SignTokenTransactionsPopup::onSignTransactionClicked")
|
||||
onCancelClicked: logs.logEvent("SignTokenTransactionsPopup::onCancelClicked")
|
||||
}
|
||||
}
|
||||
|
||||
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: ""
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: recipientsCountSpinBox
|
||||
|
||||
from: 1
|
||||
to: 1000
|
||||
}
|
||||
|
||||
Switch {
|
||||
id: loadingSwitch
|
||||
|
||||
text: "Is fee loading"
|
||||
checked: false
|
||||
}
|
||||
|
||||
Switch {
|
||||
id: showSummarySwitch
|
||||
|
||||
text: "Is summary visible"
|
||||
checked: true
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -117,4 +117,8 @@ QtObject {
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
function roleNames(model) {
|
||||
return Internal.ModelUtils.roleNames(model)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,17 @@ SettingsPageLayout {
|
|||
|
||||
required property var membersModel
|
||||
|
||||
// JS object specifing fees for the airdrop operation, should be set to
|
||||
// provide response to airdropFeesRequested signal.
|
||||
// Refer CommunityNewAirdropView::airdropFees for details.
|
||||
property var airdropFees: null
|
||||
|
||||
property int viewWidth: 560 // by design
|
||||
|
||||
signal airdropClicked(var airdropTokens, var addresses, var membersPubKeys)
|
||||
signal navigateToMintTokenSettings
|
||||
signal airdropFeesRequested(var contractKeysAndAmounts, var addresses)
|
||||
|
||||
signal navigateToMintTokenSettings
|
||||
|
||||
function navigateBack() {
|
||||
stackManager.pop(StackView.Immediate)
|
||||
|
@ -109,6 +115,10 @@ SettingsPageLayout {
|
|||
collectiblesModel: root.collectiblesModel
|
||||
membersModel: root.membersModel
|
||||
|
||||
Binding on airdropFees {
|
||||
value: root.airdropFees
|
||||
}
|
||||
|
||||
onAirdropClicked: {
|
||||
root.airdropClicked(airdropTokens, addresses, membersPubKeys)
|
||||
stackManager.clear(d.welcomeViewState, StackView.Immediate)
|
||||
|
@ -118,6 +128,7 @@ SettingsPageLayout {
|
|||
Component.onCompleted: {
|
||||
d.selectToken.connect(view.selectToken)
|
||||
d.addAddresses.connect(view.addAddresses)
|
||||
airdropFeesRequested.connect(root.airdropFeesRequested)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
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
|
||||
|
||||
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 bool isFeeLoading
|
||||
|
||||
signal signTransactionClicked()
|
||||
signal cancelClicked()
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property int minTextWidth: 50
|
||||
}
|
||||
|
||||
implicitWidth: 600 // by design
|
||||
topPadding: 2 * Style.current.padding // by design
|
||||
bottomPadding: topPadding
|
||||
|
||||
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 !== ""
|
||||
}
|
||||
}
|
||||
|
||||
footer: StatusDialogFooter {
|
||||
spacing: Style.current.padding
|
||||
rightButtons: ObjectModel {
|
||||
StatusButton {
|
||||
text: qsTr("Cancel")
|
||||
type: StatusBaseButton.Type.Danger
|
||||
onClicked: {
|
||||
root.cancelClicked()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
StatusButton {
|
||||
enabled: root.errorText === "" && !root.isFeeLoading
|
||||
icon.name: "password"
|
||||
text: qsTr("Sign transaction")
|
||||
onClicked: {
|
||||
root.signTransactionClicked()
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
AlertPopup 1.0 AlertPopup.qml
|
||||
BurnTokensPopup 1.0 BurnTokensPopup.qml
|
||||
CreateChannelPopup 1.0 CreateChannelPopup.qml
|
||||
CommunityTokenPermissionsPopup 1.0 CommunityTokenPermissionsPopup.qml
|
||||
CreateChannelPopup 1.0 CreateChannelPopup.qml
|
||||
RemotelyDestructPopup 1.0 RemotelyDestructPopup.qml
|
||||
SignMultiTokenTransactionsPopup 1.0 SignMultiTokenTransactionsPopup.qml
|
||||
SignTokenTransactionsPopup 1.0 SignTokenTransactionsPopup.qml
|
||||
|
|
|
@ -447,6 +447,10 @@ StatusSectionLayout {
|
|||
onAirdropClicked: communityTokensStore.airdrop(root.community.id, airdropTokens, addresses)
|
||||
onNavigateToMintTokenSettings: root.goTo(Constants.CommunitySettingsSections.MintTokens)
|
||||
|
||||
onAirdropFeesRequested:
|
||||
communityTokensStore.computeAirdropFee(
|
||||
root.community.id, contractKeysAndAmounts, addresses)
|
||||
|
||||
Connections {
|
||||
target: mintPanel
|
||||
|
||||
|
@ -461,6 +465,14 @@ StatusSectionLayout {
|
|||
airdropPanel.addAddresses(addresses)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: rootStore.communityTokensStore
|
||||
|
||||
function onAirdropFeeUpdated(airdropFees) {
|
||||
airdropPanel.airdropFees = airdropFees
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: root.backButtonName = centerPanelContentLoader.item.children[d.currentIndex].previousPageName
|
||||
|
|
|
@ -9,9 +9,10 @@ import StatusQ.Core.Utils 0.1
|
|||
import utils 1.0
|
||||
import shared.panels 1.0
|
||||
|
||||
import AppLayouts.Chat.controls.community 1.0
|
||||
import AppLayouts.Chat.helpers 1.0
|
||||
import AppLayouts.Chat.panels.communities 1.0
|
||||
import AppLayouts.Chat.controls.community 1.0
|
||||
import AppLayouts.Chat.popups.community 1.0
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
|
@ -26,6 +27,23 @@ StatusScrollView {
|
|||
// Community members model:
|
||||
required property var membersModel
|
||||
|
||||
// JS object specifing fees for the airdrop operation, should be set to
|
||||
// provide response to airdropFeesRequested signal.
|
||||
//
|
||||
// The expected structure is as follows:
|
||||
// {
|
||||
// fees: [{
|
||||
// ethFee: {CurrencyAmount JSON},
|
||||
// fiatFee: {CurrencyAmount JSON},
|
||||
// contractUniqueKey: string,
|
||||
// errorCode: ComputeFeeErrorCode (int)
|
||||
// }],
|
||||
// totalEthFee: {CurrencyAmount JSON},
|
||||
// totalFiatFee: {CurrencyAmount JSON},
|
||||
// errorCode: ComputeFeeErrorCode (int)
|
||||
// }
|
||||
property var airdropFees: null
|
||||
|
||||
property int viewWidth: 560 // by design
|
||||
|
||||
readonly property var selectedHoldingsModel: ListModel {}
|
||||
|
@ -35,6 +53,9 @@ StatusScrollView {
|
|||
airdropRecipientsSelector.valid
|
||||
|
||||
signal airdropClicked(var airdropTokens, var addresses, var membersPubKeys)
|
||||
|
||||
signal airdropFeesRequested(var contractKeysAndAmounts, var addresses)
|
||||
|
||||
signal navigateToMintTokenSettings
|
||||
|
||||
function selectToken(key, amount, type) {
|
||||
|
@ -81,6 +102,7 @@ StatusScrollView {
|
|||
supply: modelItem.supply,
|
||||
infiniteSupply: modelItem.infiniteSupply,
|
||||
contractUniqueKey: modelItem.contractUniqueKey,
|
||||
accountName: modelItem.accountName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -401,6 +423,64 @@ StatusScrollView {
|
|||
enabled: root.isFullyFilled
|
||||
|
||||
onClicked: {
|
||||
feesPopup.open()
|
||||
}
|
||||
}
|
||||
|
||||
SignMultiTokenTransactionsPopup {
|
||||
id: feesPopup
|
||||
|
||||
destroyOnClose: false
|
||||
|
||||
model: ListModel {
|
||||
id: feesModel
|
||||
}
|
||||
|
||||
isFeeLoading: root.airdropFees === null ||
|
||||
(root.airdropFees.errorCode !== Constants.ComputeFeeErrorCode.Success &&
|
||||
root.airdropFees.errorCode !== Constants.ComputeFeeErrorCode.Balance)
|
||||
|
||||
onOpened: {
|
||||
const title1 = qsTr("Sign transaction - Airdrop %n token(s)", "",
|
||||
selectedHoldingsModel.rowCount())
|
||||
const title2 = qsTr("to %n recipient(s)", "",
|
||||
addresses.count + airdropRecipientsSelector.membersModel.count)
|
||||
|
||||
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_)
|
||||
}
|
||||
|
||||
onSignTransactionClicked: {
|
||||
const airdropTokens = ModelUtils.modelToArray(
|
||||
root.selectedHoldingsModel,
|
||||
["contractUniqueKey", "amount"])
|
||||
|
@ -412,6 +492,52 @@ 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ QtObject {
|
|||
|
||||
signal deployFeeUpdated(var ethCurrency, var fiatCurrency, int error)
|
||||
signal selfDestructFeeUpdated(var ethCurrency, var fiatCurrency, int error)
|
||||
signal airdropFeeUpdated(var airdropFees)
|
||||
|
||||
signal deploymentStateChanged(string communityId, int status, string url)
|
||||
|
||||
|
@ -52,19 +53,23 @@ QtObject {
|
|||
|
||||
readonly property Connections connections: Connections {
|
||||
target: communityTokensModuleInst
|
||||
|
||||
function onDeployFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
|
||||
root.deployFeeUpdated(ethCurrency, fiatCurrency, errorCode)
|
||||
}
|
||||
|
||||
function onSelfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode) {
|
||||
root.selfDestructFeeUpdated(ethCurrency, fiatCurrency, errorCode)
|
||||
}
|
||||
|
||||
function onAirdropFeesUpdated(jsonFees) {
|
||||
console.log("Fees:", jsonFees)
|
||||
root.airdropFeeUpdated(JSON.parse(jsonFees))
|
||||
}
|
||||
|
||||
function onDeploymentStateChanged(communityId, status, url) {
|
||||
root.deploymentStateChanged(communityId, status, url)
|
||||
}
|
||||
|
||||
function onRemoteDestructStateChanged(communityId, tokenName, status, url) {
|
||||
root.remoteDestructStateChanged(communityId, tokenName, status, url)
|
||||
}
|
||||
|
@ -99,7 +104,9 @@ QtObject {
|
|||
communityTokensModuleInst.airdropCollectibles(communityId, JSON.stringify(airdropTokens), JSON.stringify(addresses))
|
||||
}
|
||||
|
||||
function computeAirdropFee(communityId, airdropTokens, addresses) {
|
||||
communityTokensModuleInst.computeAirdropCollectiblesFee(communityId, JSON.stringify(airdropTokens), JSON.stringify(addresses))
|
||||
function computeAirdropFee(communityId, contractKeysAndAmounts, addresses) {
|
||||
communityTokensModuleInst.computeAirdropCollectiblesFee(
|
||||
communityId, JSON.stringify(contractKeysAndAmounts),
|
||||
JSON.stringify(addresses))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue