feat(CommunityNewAirdropView): design-compliant token airdrop flow for collectibles

This commit is contained in:
Michał Cieślak 2023-04-25 23:24:04 +02:00 committed by Michał
parent f9e2ce97a4
commit 27aac8d83a
16 changed files with 482 additions and 126 deletions

View File

@ -17,6 +17,10 @@ ListModel {
title: "CommunityNewPermissionView" title: "CommunityNewPermissionView"
section: "Views" section: "Views"
} }
ListElement {
title: "CommunityNewAirdropView"
section: "Views"
}
ListElement { ListElement {
title: "ProfileFetchingView" title: "ProfileFetchingView"
section: "Views" section: "Views"

View File

@ -45,6 +45,14 @@
"CommunityMintedTokensView": [ "CommunityMintedTokensView": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2934%3A479136&t=zs22ORYUVDYpqubQ-1" "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2934%3A479136&t=zs22ORYUVDYpqubQ-1"
], ],
"CommunityNewAirdropView": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=22602-495563&t=9dIP8Sji2UlfhsEs-0",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=22628-495258&t=9dIP8Sji2UlfhsEs-0",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=22628-496145&t=9dIP8Sji2UlfhsEs-0",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=22647-497754&t=9dIP8Sji2UlfhsEs-0",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=22647-501014&t=9dIP8Sji2UlfhsEs-0",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=22647-499051&t=kHAcE8WSCyGqhWSH-0"
],
"CommunityNewCollectibleView": [ "CommunityNewCollectibleView": [
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2934%3A480877&t=Qo2FwPRxvSxbluqB-1", "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=2934%3A480877&t=Qo2FwPRxvSxbluqB-1",
"https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=26601%3A518245&t=Qo2FwPRxvSxbluqB-1", "https://www.figma.com/file/17fc13UBFvInrLgNUKJJg5/Kuba%E2%8E%9CDesktop?node-id=26601%3A518245&t=Qo2FwPRxvSxbluqB-1",

View File

@ -110,11 +110,10 @@ SplitView {
showAddressesInputWhenEmpty: showAddressesInputWhenEmpty:
showAddressesInputWhenEmptyCheckBox.checked showAddressesInputWhenEmptyCheckBox.checked
infiniteExpectedNumberOfRecipients: infiniteMaxNumberOfRecipients:
infiniteExpectedNumberOfRecipientsCheckBox.checked infiniteMaxNumberOfRecipientsCheckBox.checked
expectedNumberOfRecipients: maxNumberOfRecipients: maxNumberOfRecipientsSpinBox.value
expectedNumberOfRecipientsSpinBox.value
onAddAddressesRequested: timer.start() onAddAddressesRequested: timer.start()
onRemoveAddressRequested: addresses.remove(index) onRemoveAddressRequested: addresses.remove(index)
@ -163,7 +162,7 @@ SplitView {
} }
CheckBox { CheckBox {
id: infiniteExpectedNumberOfRecipientsCheckBox id: infiniteMaxNumberOfRecipientsCheckBox
text: "Infinite number of expected recipients" text: "Infinite number of expected recipients"
} }
@ -175,7 +174,7 @@ SplitView {
} }
SpinBox { SpinBox {
id: expectedNumberOfRecipientsSpinBox id: maxNumberOfRecipientsSpinBox
value: 2 value: 2
from: 1 from: 1

View File

@ -25,6 +25,7 @@ SplitView {
anchors.topMargin: 50 anchors.topMargin: 50
assetsModel: AssetsModel {} assetsModel: AssetsModel {}
collectiblesModel: CollectiblesModel {} collectiblesModel: CollectiblesModel {}
membersModel: ListModel {}
onAirdropClicked: logs.logEvent("CommunityAirdropsSettingsPanel::onAirdropClicked") onAirdropClicked: logs.logEvent("CommunityAirdropsSettingsPanel::onAirdropClicked")
} }

View File

@ -0,0 +1,180 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import AppLayouts.Chat.views.communities 1.0
import AppLayouts.Chat.controls.community 1.0
import Storybook 1.0
import Models 1.0
import SortFilterProxyModel 0.2
import utils 1.0
SplitView {
orientation: Qt.Vertical
SplitView.fillWidth: true
property bool globalUtilsReady: false
property bool mainModuleReady: false
Logs { id: logs }
QtObject {
function isCompressedPubKey(publicKey) {
return true
}
function getCompressedPk(publicKey) {
return "compressed_" + publicKey
}
function getColorId(publicKey) {
return Math.floor(Math.random() * 10)
}
Component.onCompleted: {
Utils.globalUtilsInst = this
globalUtilsReady = true
}
Component.onDestruction: {
globalUtilsReady = false
Utils.globalUtilsInst = {}
}
}
QtObject {
function getContactDetailsAsJson() {
return JSON.stringify({ ensVerified: true })
}
Component.onCompleted: {
mainModuleReady = true
Utils.mainModuleInst = this
}
Component.onDestruction: {
mainModuleReady = false
Utils.mainModuleInst = {}
}
}
ListModel {
id: members
property int counter: 0
function addMember() {
const i = counter++
const key = `pub_key_${i}`
const firstLetters = ["a", "b", "c", "d"]
const firstLetterIdx = Math.min(Math.floor(i / firstLetters.length),
firstLetters.length - 1)
const firstLetter = firstLetters[firstLetterIdx]
append({
alias: "",
colorId: "1",
displayName: `${firstLetter}contact ${i}`,
ensName: "",
icon: "",
isContact: true,
localNickname: "",
onlineStatus: 1,
pubKey: key,
isVerified: true,
isUntrustworthy: false
})
}
Component.onCompleted: {
for (let i = 0; i < 33; i++)
addMember()
}
}
Pane {
SplitView.fillWidth: true
SplitView.fillHeight: true
Loader {
anchors.fill: parent
active: globalUtilsReady && mainModuleReady
sourceComponent: CommunityNewAirdropView {
id: communityNewPermissionView
CollectiblesModel {
id: collectiblesModel
}
SortFilterProxyModel {
id: collectiblesModelWithSupply
sourceModel: collectiblesModel
proxyRoles: [
ExpressionRole {
name: "supply"
expression: ((model.index + 1) * 115).toString()
},
ExpressionRole {
name: "infiniteSupply"
expression: !(model.index % 4)
},
ExpressionRole {
name: "chainName"
expression: model.index ? "Optimism" : "Arbitrum"
},
ExpressionRole {
readonly property string icon1: "network/Network=Optimism"
readonly property string icon2: "network/Network=Arbitrum"
name: "chainIcon"
expression: model.index ? icon1 : icon2
}
]
filters: ValueFilter {
roleName: "category"
value: TokenCategories.Category.Community
}
Component.onCompleted: {
Qt.callLater(() => communityNewPermissionView.collectiblesModel = this)
}
}
assetsModel: ListModel {}
collectiblesModel: ListModel {}
membersModel: members
onAirdropClicked: {
logs.logEvent("CommunityNewAirdropView::airdropClicked", ["airdropTokens", "addresses", "membersPubKeys"], arguments)
}
}
}
}
LogsAndControlsPanel {
id: logsAndControlsPanel
SplitView.minimumHeight: 100
SplitView.preferredHeight: 160
logsView.logText: logs.logText
ColumnLayout {
MenuSeparator {}
TextEdit {
readOnly: true
selectByMouse: true
text: "valid address: 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc4"
}
}
}
}

View File

@ -25,6 +25,10 @@ SplitView {
return true return true
} }
function getCompressedPk(publicKey) {
return "compressed_" + publicKey
}
function getColorId(publicKey) { function getColorId(publicKey) {
return Math.floor(Math.random() * 10) return Math.floor(Math.random() * 10)
} }

View File

@ -68,6 +68,7 @@ AbstractButton {
} }
StatusBaseText { StatusBaseText {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
text: root.text text: root.text
color: root.textColor color: root.textColor
font.pixelSize: root.font.pixelSize font.pixelSize: root.font.pixelSize

View File

@ -180,10 +180,11 @@ Control {
multiline: true multiline: true
topPadding: bottomPadding + (listView.count ? d.spacing : 0) topPadding: bottomPadding + (listView.count ? d.spacing : 0)
bottomPadding: 5
height: edit.implicitHeight + topPadding + bottomPadding height: edit.implicitHeight + topPadding + bottomPadding
placeholderText: qsTr("Example: 0x39cf...fbd2") placeholderText: root.count ? "" : qsTr("Example: 0x39cf...fbd2")
Keys.onPressed: { Keys.onPressed: {
if ((event.key !== Qt.Key_Return && event.key !== Qt.Key_Enter) if ((event.key !== Qt.Key_Return && event.key !== Qt.Key_Enter)

View File

@ -17,14 +17,15 @@ StatusFlowSelector {
property alias addressesInputText: addressesSelectorPanel.text property alias addressesInputText: addressesSelectorPanel.text
property bool showAddressesInputWhenEmpty: false property bool showAddressesInputWhenEmpty: false
property int expectedNumberOfRecipients: 0 property int maxNumberOfRecipients: 0
property bool infiniteExpectedNumberOfRecipients: false property bool infiniteMaxNumberOfRecipients: false
readonly property int count: addressesSelectorPanel.count + readonly property int count: addressesSelectorPanel.count +
membersSelectorPanel.count membersSelectorPanel.count
readonly property bool valid: readonly property bool valid:
addressesSelectorPanel.invalidAddressesCount === 0 addressesSelectorPanel.invalidAddressesCount === 0 &&
(infiniteMaxNumberOfRecipients || count <= maxNumberOfRecipients)
signal addAddressesRequested(string addresses) signal addAddressesRequested(string addresses)
signal removeAddressRequested(int index) signal removeAddressRequested(int index)
@ -35,7 +36,8 @@ StatusFlowSelector {
title: qsTr("To") title: qsTr("To")
icon: Style.svg("member") icon: Style.svg("member")
flowSpacing: 12 flowSpacing: addressesSelectorPanel.visible || membersSelectorPanel.visible
? 12 : 6
placeholderText: qsTr("Example: 12 addresses and 3 members") placeholderText: qsTr("Example: 12 addresses and 3 members")
@ -61,12 +63,12 @@ StatusFlowSelector {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right
readonly property bool valid: root.infiniteExpectedNumberOfRecipients || readonly property bool valid: root.infiniteMaxNumberOfRecipients ||
root.count <= root.expectedNumberOfRecipients root.count <= root.maxNumberOfRecipients
text: root.count + " / " + (root.infiniteExpectedNumberOfRecipients text: root.count + " / " + (root.infiniteMaxNumberOfRecipients
? qsTr("∞ recipients", "infinite number of recipients") ? qsTr("∞ recipients", "infinite number of recipients")
: qsTr("%n recipient(s)", "", root.expectedNumberOfRecipients)) : qsTr("%n recipient(s)", "", root.maxNumberOfRecipients))
font.pixelSize: Theme.tertiaryTextFontSize + 1 font.pixelSize: Theme.tertiaryTextFontSize + 1
color: valid ? Theme.palette.baseColor1 : Theme.palette.dangerColor1 color: valid ? Theme.palette.baseColor1 : Theme.palette.dangerColor1

View File

@ -12,8 +12,9 @@ StatusFlowSelector {
id: root id: root
property alias model: repeater.model property alias model: repeater.model
readonly property alias count: repeater.count
signal itemClicked(int index, var mouse) signal itemClicked(int index, var mouse, var item)
placeholderText: qsTr("Example: 1 SOCK") placeholderText: qsTr("Example: 1 SOCK")
placeholderItem.visible: repeater.count === 0 placeholderItem.visible: repeater.count === 0
@ -32,9 +33,13 @@ StatusFlowSelector {
id: repeater id: repeater
Control { Control {
id: delegateRoot
component Icon: StatusRoundedImage { component Icon: StatusRoundedImage {
implicitWidth: d.iconSize implicitWidth: d.iconSize
implicitHeight: d.iconSize implicitHeight: d.iconSize
image.mipmap: true
} }
component Text: StatusBaseText { component Text: StatusBaseText {
@ -55,7 +60,7 @@ StatusFlowSelector {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: root.itemClicked(model.index, mouse) onClicked: root.itemClicked(model.index, mouse, delegateRoot)
} }
} }

View File

@ -36,6 +36,8 @@ StatusDropdown {
bottomInset: 10 bottomInset: 10
bottomPadding: padding + bottomInset bottomPadding: padding + bottomInset
onOpened: filterInput.text = ""
QtObject { QtObject {
id: d id: d

View File

@ -68,7 +68,7 @@ Control {
} }
StatusBaseText { StatusBaseText {
visible: !!root.amount visible: !!root.amount && !root.selected
text: root.amount text: root.amount
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
font.pixelSize: 12 font.pixelSize: 12

View File

@ -17,9 +17,11 @@ SettingsPageLayout {
required property var assetsModel required property var assetsModel
required property var collectiblesModel required property var collectiblesModel
required property var membersModel
property int viewWidth: 560 // by design property int viewWidth: 560 // by design
signal airdropClicked(var airdropTokens, var addresses) signal airdropClicked(var airdropTokens, var addresses, var membersPubKeys)
signal navigateToMintTokenSettings signal navigateToMintTokenSettings
@ -102,9 +104,10 @@ SettingsPageLayout {
assetsModel: root.assetsModel assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel collectiblesModel: root.collectiblesModel
membersModel: root.membersModel
onAirdropClicked: { onAirdropClicked: {
root.airdropClicked(airdropTokens, addresses) root.airdropClicked(airdropTokens, addresses, membersPubKeys)
stackManager.clear(d.welcomeViewState, StackView.Immediate) stackManager.clear(d.welcomeViewState, StackView.Immediate)
} }
onNavigateToMintTokenSettings: root.navigateToMintTokenSettings() onNavigateToMintTokenSettings: root.navigateToMintTokenSettings()

View File

@ -19,6 +19,7 @@ import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1 import StatusQ.Controls.Validators 0.1
import AppLayouts.Chat.stores 1.0 import AppLayouts.Chat.stores 1.0
import AppLayouts.Chat.controls.community 1.0
import shared.stores 1.0 import shared.stores 1.0
import shared.views.chat 1.0 import shared.views.chat 1.0
@ -370,8 +371,48 @@ StatusSectionLayout {
readonly property CommunityTokensStore communityTokensStore: readonly property CommunityTokensStore communityTokensStore:
rootStore.communityTokensStore rootStore.communityTokensStore
assetsModel: rootStore.assetsModel assetsModel: ListModel {}
collectiblesModel: rootStore.collectiblesModel
readonly property var communityTokens: root.community.communityTokens
Loader {
id: modelLoader
active: airdropPanel.communityTokens
sourceComponent: SortFilterProxyModel {
sourceModel: airdropPanel.communityTokens
proxyRoles: [
ExpressionRole {
name: "category"
// Singleton cannot be used directly in the epression
readonly property int category: TokenCategories.Category.Own
expression: category
},
ExpressionRole {
name: "iconSource"
expression: model.image
},
ExpressionRole {
name: "key"
expression: model.symbol
}
]
}
}
collectiblesModel: modelLoader.item
membersModel: {
const chatContentModule = root.rootStore.currentChatContentModule()
if (!chatContentModule || !chatContentModule.usersModule) {
// New communities have no chats, so no chatContentModule
return null
}
return chatContentModule.usersModule.model
}
onPreviousPageNameChanged: root.backButtonName = previousPageName onPreviousPageNameChanged: root.backButtonName = previousPageName
onAirdropClicked: communityTokensStore.airdrop(root.community.id, airdropTokens, addresses) onAirdropClicked: communityTokensStore.airdrop(root.community.id, airdropTokens, addresses)

View File

@ -1,10 +1,8 @@
import QtQuick 2.14 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Layouts 1.15
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1 import StatusQ.Core.Utils 0.1
@ -15,7 +13,9 @@ import AppLayouts.Chat.helpers 1.0
import AppLayouts.Chat.panels.communities 1.0 import AppLayouts.Chat.panels.communities 1.0
import AppLayouts.Chat.controls.community 1.0 import AppLayouts.Chat.controls.community 1.0
// TEMPORAL - BASIC IMPLEMENTATION import SortFilterProxyModel 0.2
StatusScrollView { StatusScrollView {
id: root id: root
@ -23,21 +23,27 @@ StatusScrollView {
required property var assetsModel required property var assetsModel
required property var collectiblesModel required property var collectiblesModel
// Community members model:
required property var membersModel
property int viewWidth: 560 // by design property int viewWidth: 560 // by design
// roles: type, key, name, amount, imageSource readonly property var selectedHoldingsModel: ListModel {}
property var selectedHoldingsModel: ListModel {}
readonly property bool isFullyFilled: selectedHoldingsModel.count > 0 && readonly property bool isFullyFilled: tokensSelector.count > 0 &&
addressess.model.count > 0 airdropRecipientsSelector.count > 0 &&
airdropRecipientsSelector.valid
signal airdropClicked(var airdropTokens, var addresses) signal airdropClicked(var airdropTokens, var addresses, var membersPubKeys)
signal navigateToMintTokenSettings signal navigateToMintTokenSettings
function selectCollectible(key, amount) { function selectCollectible(key, amount) {
const modelItem = CommunityPermissionsHelpers.getTokenByKey( const modelItem = CommunityPermissionsHelpers.getTokenByKey(
root.collectiblesModel, key) root.collectiblesModel, key)
d.addItem(HoldingTypes.Type.Collectible, modelItem, amount)
const entry = d.prepareEntry(key, amount)
entry.valid = true
selectedHoldingsModel.append(entry)
} }
QtObject { QtObject {
@ -47,22 +53,68 @@ StatusScrollView {
readonly property int dropdownHorizontalOffset: 4 readonly property int dropdownHorizontalOffset: 4
readonly property int dropdownVerticalOffset: 1 readonly property int dropdownVerticalOffset: 1
function addItem(type, item, amount) { function prepareEntry(key, amount) {
const key = item.key const modelItem = CommunityPermissionsHelpers.getTokenByKey(
root.collectiblesModel, key)
root.selectedHoldingsModel.append({ type, key, amount }) return {
key, amount,
tokenText: amount + " " + modelItem.name,
tokenImage: modelItem.iconSource,
networkText: modelItem.chainName,
networkImage: Style.svg(modelItem.chainIcon),
supply: modelItem.supply,
infiniteSupply: modelItem.infiniteSupply
}
} }
} }
Instantiator {
id: recipientsCountInstantiator
model: selectedHoldingsModel
property bool infinity: true
property int maximumRecipientsCount
function findRecipientsCount() {
let min = Number.MAX_SAFE_INTEGER
for (let i = 0; i < count; i++) {
const item = objectAt(i)
if (!item || item.infiniteSupply)
continue
min = Math.min(item.supply / item.amount, min)
}
infinity = min === Number.MAX_SAFE_INTEGER
maximumRecipientsCount = infinity ? 0 : min
}
delegate: QtObject {
readonly property int supply: model.supply
readonly property real amount: model.amount
readonly property bool infiniteSupply: model.infiniteSupply
onSupplyChanged: recipientsCountInstantiator.findRecipientsCount()
onAmountChanged: recipientsCountInstantiator.findRecipientsCount()
onInfiniteSupplyChanged: recipientsCountInstantiator.findRecipientsCount()
}
onCountChanged: findRecipientsCount()
}
contentWidth: mainLayout.width contentWidth: mainLayout.width
contentHeight: mainLayout.height contentHeight: mainLayout.height
ColumnLayout { SequenceColumnLayout {
id: mainLayout id: mainLayout
width: root.viewWidth width: root.viewWidth
spacing: 0 spacing: 0
StatusItemSelector { AirdropTokensSelector {
id: tokensSelector id: tokensSelector
property int editedIndex: -1 property int editedIndex: -1
@ -71,19 +123,10 @@ StatusScrollView {
icon: Style.svg("token") icon: Style.svg("token")
title: qsTr("What") title: qsTr("What")
placeholderText: qsTr("Example: 1 SOCK") placeholderText: qsTr("Example: 1 SOCK")
tagLeftPadding: 2
asset.height: 28
asset.width: asset.height
addButton.visible: model.count < d.maxAirdropTokens addButton.visible: model.count < d.maxAirdropTokens
model: HoldingsSelectionModel { model: root.selectedHoldingsModel
sourceModel: root.selectedHoldingsModel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
}
// TODO: All this code is repeated inside `CommunityNewPermissionView`. Check how to reuse it.
HoldingsDropdown { HoldingsDropdown {
id: dropdown id: dropdown
@ -114,37 +157,26 @@ StatusScrollView {
return itemIndex return itemIndex
} }
onAddAsset: { onOpened: {
const modelItem = CommunityPermissionsHelpers.getTokenByKey( usedTokens = ModelUtils.modelToArray(
root.assetsModel, key) root.selectedHoldingsModel, ["key", "amount"])
d.addItem(HoldingTypes.Type.Asset, modelItem, amount)
dropdown.close()
} }
onAddCollectible: { onAddCollectible: {
const modelItem = CommunityPermissionsHelpers.getTokenByKey( const entry = d.prepareEntry(key, amount)
root.collectiblesModel, key)
d.addItem(HoldingTypes.Type.Collectible, modelItem, amount)
dropdown.close()
}
onUpdateAsset: { selectedHoldingsModel.append(entry)
const itemIndex = prepareUpdateIndex(key)
const modelItem = CommunityPermissionsHelpers.getTokenByKey(root.assetsModel, key)
root.selectedHoldingsModel.set(
itemIndex, { type: HoldingTypes.Type.Asset, key, amount })
dropdown.close() dropdown.close()
} }
onUpdateCollectible: { onUpdateCollectible: {
const itemIndex = prepareUpdateIndex(key) const itemIndex = prepareUpdateIndex(key)
const entry = d.prepareEntry(key, amount)
const modelItem = CommunityPermissionsHelpers.getTokenByKey( const modelItem = CommunityPermissionsHelpers.getTokenByKey(
root.collectiblesModel, key) root.collectiblesModel, key)
root.selectedHoldingsModel.set( root.selectedHoldingsModel.set(itemIndex, entry)
itemIndex,
{ type: HoldingTypes.Type.Collectible, key, amount })
dropdown.close() dropdown.close()
} }
@ -176,70 +208,146 @@ StatusScrollView {
dropdown.x = mouse.x + d.dropdownHorizontalOffset dropdown.x = mouse.x + d.dropdownHorizontalOffset
dropdown.y = d.dropdownVerticalOffset dropdown.y = d.dropdownVerticalOffset
const modelItem = tokensSelector.model.get(index) const modelItem = selectedHoldingsModel.get(index)
dropdown.collectibleKey = modelItem.key
switch(modelItem.type) { dropdown.collectibleAmount = modelItem.amount
case HoldingTypes.Type.Asset: dropdown.setActiveTab(HoldingTypes.Type.Collectible)
dropdown.assetKey = modelItem.key
dropdown.assetAmount = modelItem.amount
break
case HoldingTypes.Type.Collectible:
dropdown.collectibleKey = modelItem.key
dropdown.collectibleAmount = modelItem.amount
break
default:
console.warn("Unsupported holdings type.")
}
dropdown.setActiveTab(modelItem.type)
dropdown.openUpdateFlow() dropdown.openUpdateFlow()
editedIndex = index editedIndex = index
} }
} }
Rectangle { SequenceColumnLayout.Separator {}
Layout.leftMargin: 16
Layout.preferredWidth: 2
Layout.preferredHeight: 24
color: Style.current.separator
}
// TEMPORAL AirdropRecipientsSelector {
StatusInput { id: airdropRecipientsSelector
id: addressInput
Layout.fillWidth: true addressesModel: addresses
placeholderText: qsTr("Example: 0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7999") infiniteMaxNumberOfRecipients:
} recipientsCountInstantiator.infinity
Rectangle { maxNumberOfRecipients:
Layout.leftMargin: 16 recipientsCountInstantiator.maximumRecipientsCount
Layout.preferredWidth: 2
Layout.preferredHeight: 24
color: Style.current.separator
}
StatusItemSelector { membersModel: SortFilterProxyModel {
id: addressess sourceModel: membersModel
Layout.fillWidth: true filters: ExpressionFilter {
icon: Style.svg("member") id: selectedKeysFilter
title: qsTr("To")
placeholderText: qsTr("Example: 12 addresses and 3 members")
tagLeftPadding: 2
asset.height: 28
asset.width: asset.height
model: ListModel {} property var keys: []
addButton.onClicked: { expression: keys.indexOf(model.pubKey) !== -1
if(addressInput.text.length > 0) }
model.append({text: addressInput.text})
} }
onItemClicked: addressess.model.remove(index) onRemoveMemberRequested: {
const pubKey = ModelUtils.get(membersModel, index, "pubKey")
const keyIndex = selectedKeysFilter.keys.indexOf(pubKey)
selectedKeysFilter.keys.splice(keyIndex, 1)
selectedKeysFilter.keys = selectedKeysFilter.keys
}
onAddAddressesRequested: (addresses_) => {
addresses.addAddressesFromString(addresses_)
airdropRecipientsSelector.clearAddressesInput()
airdropRecipientsSelector.positionAddressesListAtEnd()
}
onRemoveAddressRequested: addresses.remove(index)
ListModel {
id: addresses
function addAddressesFromString(addresses) {
const words = addresses.trim().split(/[\s+,]/)
const existing = new Set()
for (let i = 0; i < count; i++)
existing.add(get(i).address)
words.forEach(word => {
if (word === "" || existing.has(word))
return
const valid = Utils.isValidAddress(word)
append({ valid, address: word })
})
}
}
function openPopup(popup) {
popup.parent = addButton
popup.x = addButton.width + d.dropdownHorizontalOffset
popup.y = 0
popup.open()
}
addButton.onClicked: openPopup(recipientTypeSelectionDropdown)
RecipientTypeSelectionDropdown {
id: recipientTypeSelectionDropdown
onEthAddressesSelected: {
airdropRecipientsSelector.showAddressesInputWhenEmpty = true
airdropRecipientsSelector.forceInputFocus()
recipientTypeSelectionDropdown.close()
}
onCommunityMembersSelected: {
recipientTypeSelectionDropdown.close()
membersDropdown.selectedKeys = selectedKeysFilter.keys
airdropRecipientsSelector.openPopup(membersDropdown)
}
}
MembersDropdown {
id: membersDropdown
model: SortFilterProxyModel {
sourceModel: membersModel
filters: [
ExpressionFilter {
enabled: membersDropdown.searchText !== ""
function matchesAlias(name, filter) {
return name.split(" ").some(p => p.startsWith(filter))
}
expression: {
membersDropdown.selectedKeys
membersDropdown.searchText
if (membersDropdown.selectedKeys.indexOf(model.pubKey) > -1)
return true
const filter = membersDropdown.searchText.toLowerCase()
return matchesAlias(model.alias.toLowerCase(), filter)
|| model.displayName.toLowerCase().includes(filter)
|| model.ensName.toLowerCase().includes(filter)
|| model.localNickname.toLowerCase().includes(filter)
|| model.pubKey.toLowerCase().includes(filter)
}
}
]
}
onBackButtonClicked: {
close()
airdropRecipientsSelector.openPopup(
recipientTypeSelectionDropdown)
}
onAddButtonClicked: {
selectedKeysFilter.keys = selectedKeys
close()
}
}
} }
StatusButton { StatusButton {
@ -253,13 +361,14 @@ StatusScrollView {
onClicked: { onClicked: {
const airdropTokens = ModelUtils.modelToArray( const airdropTokens = ModelUtils.modelToArray(
root.selectedHoldingsModel, root.selectedHoldingsModel,
["key", "type", "amount"]) ["key", "amount"])
const addresses = ModelUtils.modelToArray( const addresses_ = ModelUtils.modelToArray(
addressess.model, addresses, ["address"]).map(e => e.address)
["text"])
root.airdropClicked(airdropTokens, addresses) const pubKeys = selectedKeysFilter.keys
root.airdropClicked(airdropTokens, addresses_, pubKeys)
} }
} }
} }

View File

@ -54,10 +54,6 @@ QtObject {
// Airdrop tokens: // Airdrop tokens:
function airdrop(communityId, airdropTokens, addresses) { function airdrop(communityId, airdropTokens, addresses) {
const addrArray = [] communityTokensModuleInst.airdropCollectibles(communityId, JSON.stringify(airdropTokens), JSON.stringify(addresses))
for(var i = 0; i < addresses.length; i++) {
addrArray.push(addresses[i]["text"])
}
communityTokensModuleInst.airdropCollectibles(communityId, JSON.stringify(airdropTokens), JSON.stringify(addrArray))
} }
} }