mirror of
https://github.com/status-im/status-desktop.git
synced 2025-03-01 15:01:11 +00:00
feat(CommunityNewAirdropView): design-compliant token airdrop flow for collectibles
This commit is contained in:
parent
f9e2ce97a4
commit
27aac8d83a
@ -17,6 +17,10 @@ ListModel {
|
||||
title: "CommunityNewPermissionView"
|
||||
section: "Views"
|
||||
}
|
||||
ListElement {
|
||||
title: "CommunityNewAirdropView"
|
||||
section: "Views"
|
||||
}
|
||||
ListElement {
|
||||
title: "ProfileFetchingView"
|
||||
section: "Views"
|
||||
|
@ -45,6 +45,14 @@
|
||||
"CommunityMintedTokensView": [
|
||||
"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": [
|
||||
"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",
|
||||
|
@ -110,11 +110,10 @@ SplitView {
|
||||
showAddressesInputWhenEmpty:
|
||||
showAddressesInputWhenEmptyCheckBox.checked
|
||||
|
||||
infiniteExpectedNumberOfRecipients:
|
||||
infiniteExpectedNumberOfRecipientsCheckBox.checked
|
||||
infiniteMaxNumberOfRecipients:
|
||||
infiniteMaxNumberOfRecipientsCheckBox.checked
|
||||
|
||||
expectedNumberOfRecipients:
|
||||
expectedNumberOfRecipientsSpinBox.value
|
||||
maxNumberOfRecipients: maxNumberOfRecipientsSpinBox.value
|
||||
|
||||
onAddAddressesRequested: timer.start()
|
||||
onRemoveAddressRequested: addresses.remove(index)
|
||||
@ -163,7 +162,7 @@ SplitView {
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: infiniteExpectedNumberOfRecipientsCheckBox
|
||||
id: infiniteMaxNumberOfRecipientsCheckBox
|
||||
|
||||
text: "Infinite number of expected recipients"
|
||||
}
|
||||
@ -175,7 +174,7 @@ SplitView {
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: expectedNumberOfRecipientsSpinBox
|
||||
id: maxNumberOfRecipientsSpinBox
|
||||
|
||||
value: 2
|
||||
from: 1
|
||||
|
@ -25,6 +25,7 @@ SplitView {
|
||||
anchors.topMargin: 50
|
||||
assetsModel: AssetsModel {}
|
||||
collectiblesModel: CollectiblesModel {}
|
||||
membersModel: ListModel {}
|
||||
|
||||
onAirdropClicked: logs.logEvent("CommunityAirdropsSettingsPanel::onAirdropClicked")
|
||||
}
|
||||
|
180
storybook/pages/CommunityNewAirdropViewPage.qml
Normal file
180
storybook/pages/CommunityNewAirdropViewPage.qml
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,10 @@ SplitView {
|
||||
return true
|
||||
}
|
||||
|
||||
function getCompressedPk(publicKey) {
|
||||
return "compressed_" + publicKey
|
||||
}
|
||||
|
||||
function getColorId(publicKey) {
|
||||
return Math.floor(Math.random() * 10)
|
||||
}
|
||||
|
@ -68,6 +68,7 @@ AbstractButton {
|
||||
}
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
text: root.text
|
||||
color: root.textColor
|
||||
font.pixelSize: root.font.pixelSize
|
||||
|
@ -180,10 +180,11 @@ Control {
|
||||
multiline: true
|
||||
|
||||
topPadding: bottomPadding + (listView.count ? d.spacing : 0)
|
||||
bottomPadding: 5
|
||||
|
||||
height: edit.implicitHeight + topPadding + bottomPadding
|
||||
|
||||
placeholderText: qsTr("Example: 0x39cf...fbd2")
|
||||
placeholderText: root.count ? "" : qsTr("Example: 0x39cf...fbd2")
|
||||
|
||||
Keys.onPressed: {
|
||||
if ((event.key !== Qt.Key_Return && event.key !== Qt.Key_Enter)
|
||||
|
@ -17,14 +17,15 @@ StatusFlowSelector {
|
||||
property alias addressesInputText: addressesSelectorPanel.text
|
||||
|
||||
property bool showAddressesInputWhenEmpty: false
|
||||
property int expectedNumberOfRecipients: 0
|
||||
property bool infiniteExpectedNumberOfRecipients: false
|
||||
property int maxNumberOfRecipients: 0
|
||||
property bool infiniteMaxNumberOfRecipients: false
|
||||
|
||||
readonly property int count: addressesSelectorPanel.count +
|
||||
membersSelectorPanel.count
|
||||
|
||||
readonly property bool valid:
|
||||
addressesSelectorPanel.invalidAddressesCount === 0
|
||||
addressesSelectorPanel.invalidAddressesCount === 0 &&
|
||||
(infiniteMaxNumberOfRecipients || count <= maxNumberOfRecipients)
|
||||
|
||||
signal addAddressesRequested(string addresses)
|
||||
signal removeAddressRequested(int index)
|
||||
@ -35,7 +36,8 @@ StatusFlowSelector {
|
||||
|
||||
title: qsTr("To")
|
||||
icon: Style.svg("member")
|
||||
flowSpacing: 12
|
||||
flowSpacing: addressesSelectorPanel.visible || membersSelectorPanel.visible
|
||||
? 12 : 6
|
||||
|
||||
placeholderText: qsTr("Example: 12 addresses and 3 members")
|
||||
|
||||
@ -61,12 +63,12 @@ StatusFlowSelector {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
|
||||
readonly property bool valid: root.infiniteExpectedNumberOfRecipients ||
|
||||
root.count <= root.expectedNumberOfRecipients
|
||||
readonly property bool valid: root.infiniteMaxNumberOfRecipients ||
|
||||
root.count <= root.maxNumberOfRecipients
|
||||
|
||||
text: root.count + " / " + (root.infiniteExpectedNumberOfRecipients
|
||||
text: root.count + " / " + (root.infiniteMaxNumberOfRecipients
|
||||
? qsTr("∞ recipients", "infinite number of recipients")
|
||||
: qsTr("%n recipient(s)", "", root.expectedNumberOfRecipients))
|
||||
: qsTr("%n recipient(s)", "", root.maxNumberOfRecipients))
|
||||
|
||||
font.pixelSize: Theme.tertiaryTextFontSize + 1
|
||||
color: valid ? Theme.palette.baseColor1 : Theme.palette.dangerColor1
|
||||
|
@ -12,8 +12,9 @@ StatusFlowSelector {
|
||||
id: root
|
||||
|
||||
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")
|
||||
placeholderItem.visible: repeater.count === 0
|
||||
@ -32,9 +33,13 @@ StatusFlowSelector {
|
||||
id: repeater
|
||||
|
||||
Control {
|
||||
id: delegateRoot
|
||||
|
||||
component Icon: StatusRoundedImage {
|
||||
implicitWidth: d.iconSize
|
||||
implicitHeight: d.iconSize
|
||||
|
||||
image.mipmap: true
|
||||
}
|
||||
|
||||
component Text: StatusBaseText {
|
||||
@ -55,7 +60,7 @@ StatusFlowSelector {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.itemClicked(model.index, mouse)
|
||||
onClicked: root.itemClicked(model.index, mouse, delegateRoot)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,8 @@ StatusDropdown {
|
||||
bottomInset: 10
|
||||
bottomPadding: padding + bottomInset
|
||||
|
||||
onOpened: filterInput.text = ""
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
|
@ -68,7 +68,7 @@ Control {
|
||||
}
|
||||
|
||||
StatusBaseText {
|
||||
visible: !!root.amount
|
||||
visible: !!root.amount && !root.selected
|
||||
text: root.amount
|
||||
color: Theme.palette.baseColor1
|
||||
font.pixelSize: 12
|
||||
|
@ -17,9 +17,11 @@ SettingsPageLayout {
|
||||
required property var assetsModel
|
||||
required property var collectiblesModel
|
||||
|
||||
required property var membersModel
|
||||
|
||||
property int viewWidth: 560 // by design
|
||||
|
||||
signal airdropClicked(var airdropTokens, var addresses)
|
||||
signal airdropClicked(var airdropTokens, var addresses, var membersPubKeys)
|
||||
signal navigateToMintTokenSettings
|
||||
|
||||
|
||||
@ -102,9 +104,10 @@ SettingsPageLayout {
|
||||
|
||||
assetsModel: root.assetsModel
|
||||
collectiblesModel: root.collectiblesModel
|
||||
membersModel: root.membersModel
|
||||
|
||||
onAirdropClicked: {
|
||||
root.airdropClicked(airdropTokens, addresses)
|
||||
root.airdropClicked(airdropTokens, addresses, membersPubKeys)
|
||||
stackManager.clear(d.welcomeViewState, StackView.Immediate)
|
||||
}
|
||||
onNavigateToMintTokenSettings: root.navigateToMintTokenSettings()
|
||||
|
@ -19,6 +19,7 @@ import StatusQ.Controls 0.1
|
||||
import StatusQ.Controls.Validators 0.1
|
||||
|
||||
import AppLayouts.Chat.stores 1.0
|
||||
import AppLayouts.Chat.controls.community 1.0
|
||||
|
||||
import shared.stores 1.0
|
||||
import shared.views.chat 1.0
|
||||
@ -370,8 +371,48 @@ StatusSectionLayout {
|
||||
readonly property CommunityTokensStore communityTokensStore:
|
||||
rootStore.communityTokensStore
|
||||
|
||||
assetsModel: rootStore.assetsModel
|
||||
collectiblesModel: rootStore.collectiblesModel
|
||||
assetsModel: ListModel {}
|
||||
|
||||
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
|
||||
onAirdropClicked: communityTokensStore.airdrop(root.community.id, airdropTokens, addresses)
|
||||
|
@ -1,10 +1,8 @@
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtQuick.Layouts 1.14
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Controls 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.controls.community 1.0
|
||||
|
||||
// TEMPORAL - BASIC IMPLEMENTATION
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
|
||||
StatusScrollView {
|
||||
id: root
|
||||
|
||||
@ -23,21 +23,27 @@ StatusScrollView {
|
||||
required property var assetsModel
|
||||
required property var collectiblesModel
|
||||
|
||||
// Community members model:
|
||||
required property var membersModel
|
||||
|
||||
property int viewWidth: 560 // by design
|
||||
|
||||
// roles: type, key, name, amount, imageSource
|
||||
property var selectedHoldingsModel: ListModel {}
|
||||
readonly property var selectedHoldingsModel: ListModel {}
|
||||
|
||||
readonly property bool isFullyFilled: selectedHoldingsModel.count > 0 &&
|
||||
addressess.model.count > 0
|
||||
readonly property bool isFullyFilled: tokensSelector.count > 0 &&
|
||||
airdropRecipientsSelector.count > 0 &&
|
||||
airdropRecipientsSelector.valid
|
||||
|
||||
signal airdropClicked(var airdropTokens, var addresses)
|
||||
signal airdropClicked(var airdropTokens, var addresses, var membersPubKeys)
|
||||
signal navigateToMintTokenSettings
|
||||
|
||||
function selectCollectible(key, amount) {
|
||||
const modelItem = CommunityPermissionsHelpers.getTokenByKey(
|
||||
root.collectiblesModel, key)
|
||||
d.addItem(HoldingTypes.Type.Collectible, modelItem, amount)
|
||||
|
||||
const entry = d.prepareEntry(key, amount)
|
||||
entry.valid = true
|
||||
selectedHoldingsModel.append(entry)
|
||||
}
|
||||
|
||||
QtObject {
|
||||
@ -47,22 +53,68 @@ StatusScrollView {
|
||||
readonly property int dropdownHorizontalOffset: 4
|
||||
readonly property int dropdownVerticalOffset: 1
|
||||
|
||||
function addItem(type, item, amount) {
|
||||
const key = item.key
|
||||
function prepareEntry(key, amount) {
|
||||
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
|
||||
contentHeight: mainLayout.height
|
||||
|
||||
ColumnLayout {
|
||||
SequenceColumnLayout {
|
||||
id: mainLayout
|
||||
width: root.viewWidth
|
||||
spacing: 0
|
||||
|
||||
StatusItemSelector {
|
||||
AirdropTokensSelector {
|
||||
id: tokensSelector
|
||||
|
||||
property int editedIndex: -1
|
||||
@ -71,19 +123,10 @@ StatusScrollView {
|
||||
icon: Style.svg("token")
|
||||
title: qsTr("What")
|
||||
placeholderText: qsTr("Example: 1 SOCK")
|
||||
tagLeftPadding: 2
|
||||
asset.height: 28
|
||||
asset.width: asset.height
|
||||
addButton.visible: model.count < d.maxAirdropTokens
|
||||
|
||||
model: HoldingsSelectionModel {
|
||||
sourceModel: root.selectedHoldingsModel
|
||||
model: root.selectedHoldingsModel
|
||||
|
||||
assetsModel: root.assetsModel
|
||||
collectiblesModel: root.collectiblesModel
|
||||
}
|
||||
|
||||
// TODO: All this code is repeated inside `CommunityNewPermissionView`. Check how to reuse it.
|
||||
HoldingsDropdown {
|
||||
id: dropdown
|
||||
|
||||
@ -114,37 +157,26 @@ StatusScrollView {
|
||||
return itemIndex
|
||||
}
|
||||
|
||||
onAddAsset: {
|
||||
const modelItem = CommunityPermissionsHelpers.getTokenByKey(
|
||||
root.assetsModel, key)
|
||||
d.addItem(HoldingTypes.Type.Asset, modelItem, amount)
|
||||
dropdown.close()
|
||||
onOpened: {
|
||||
usedTokens = ModelUtils.modelToArray(
|
||||
root.selectedHoldingsModel, ["key", "amount"])
|
||||
}
|
||||
|
||||
onAddCollectible: {
|
||||
const modelItem = CommunityPermissionsHelpers.getTokenByKey(
|
||||
root.collectiblesModel, key)
|
||||
d.addItem(HoldingTypes.Type.Collectible, modelItem, amount)
|
||||
dropdown.close()
|
||||
}
|
||||
const entry = d.prepareEntry(key, amount)
|
||||
|
||||
onUpdateAsset: {
|
||||
const itemIndex = prepareUpdateIndex(key)
|
||||
const modelItem = CommunityPermissionsHelpers.getTokenByKey(root.assetsModel, key)
|
||||
|
||||
root.selectedHoldingsModel.set(
|
||||
itemIndex, { type: HoldingTypes.Type.Asset, key, amount })
|
||||
selectedHoldingsModel.append(entry)
|
||||
dropdown.close()
|
||||
}
|
||||
|
||||
onUpdateCollectible: {
|
||||
const itemIndex = prepareUpdateIndex(key)
|
||||
|
||||
const entry = d.prepareEntry(key, amount)
|
||||
const modelItem = CommunityPermissionsHelpers.getTokenByKey(
|
||||
root.collectiblesModel, key)
|
||||
|
||||
root.selectedHoldingsModel.set(
|
||||
itemIndex,
|
||||
{ type: HoldingTypes.Type.Collectible, key, amount })
|
||||
root.selectedHoldingsModel.set(itemIndex, entry)
|
||||
dropdown.close()
|
||||
}
|
||||
|
||||
@ -176,70 +208,146 @@ StatusScrollView {
|
||||
dropdown.x = mouse.x + d.dropdownHorizontalOffset
|
||||
dropdown.y = d.dropdownVerticalOffset
|
||||
|
||||
const modelItem = tokensSelector.model.get(index)
|
||||
|
||||
switch(modelItem.type) {
|
||||
case HoldingTypes.Type.Asset:
|
||||
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)
|
||||
const modelItem = selectedHoldingsModel.get(index)
|
||||
dropdown.collectibleKey = modelItem.key
|
||||
dropdown.collectibleAmount = modelItem.amount
|
||||
dropdown.setActiveTab(HoldingTypes.Type.Collectible)
|
||||
dropdown.openUpdateFlow()
|
||||
|
||||
editedIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.leftMargin: 16
|
||||
Layout.preferredWidth: 2
|
||||
Layout.preferredHeight: 24
|
||||
color: Style.current.separator
|
||||
}
|
||||
SequenceColumnLayout.Separator {}
|
||||
|
||||
// TEMPORAL
|
||||
StatusInput {
|
||||
id: addressInput
|
||||
AirdropRecipientsSelector {
|
||||
id: airdropRecipientsSelector
|
||||
|
||||
Layout.fillWidth: true
|
||||
addressesModel: addresses
|
||||
|
||||
placeholderText: qsTr("Example: 0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7999")
|
||||
}
|
||||
infiniteMaxNumberOfRecipients:
|
||||
recipientsCountInstantiator.infinity
|
||||
|
||||
Rectangle {
|
||||
Layout.leftMargin: 16
|
||||
Layout.preferredWidth: 2
|
||||
Layout.preferredHeight: 24
|
||||
color: Style.current.separator
|
||||
}
|
||||
maxNumberOfRecipients:
|
||||
recipientsCountInstantiator.maximumRecipientsCount
|
||||
|
||||
StatusItemSelector {
|
||||
id: addressess
|
||||
membersModel: SortFilterProxyModel {
|
||||
sourceModel: membersModel
|
||||
|
||||
Layout.fillWidth: true
|
||||
icon: Style.svg("member")
|
||||
title: qsTr("To")
|
||||
placeholderText: qsTr("Example: 12 addresses and 3 members")
|
||||
tagLeftPadding: 2
|
||||
asset.height: 28
|
||||
asset.width: asset.height
|
||||
filters: ExpressionFilter {
|
||||
id: selectedKeysFilter
|
||||
|
||||
model: ListModel {}
|
||||
property var keys: []
|
||||
|
||||
addButton.onClicked: {
|
||||
if(addressInput.text.length > 0)
|
||||
model.append({text: addressInput.text})
|
||||
expression: keys.indexOf(model.pubKey) !== -1
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -253,13 +361,14 @@ StatusScrollView {
|
||||
onClicked: {
|
||||
const airdropTokens = ModelUtils.modelToArray(
|
||||
root.selectedHoldingsModel,
|
||||
["key", "type", "amount"])
|
||||
["key", "amount"])
|
||||
|
||||
const addresses = ModelUtils.modelToArray(
|
||||
addressess.model,
|
||||
["text"])
|
||||
const addresses_ = ModelUtils.modelToArray(
|
||||
addresses, ["address"]).map(e => e.address)
|
||||
|
||||
root.airdropClicked(airdropTokens, addresses)
|
||||
const pubKeys = selectedKeysFilter.keys
|
||||
|
||||
root.airdropClicked(airdropTokens, addresses_, pubKeys)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,10 +54,6 @@ QtObject {
|
||||
|
||||
// Airdrop tokens:
|
||||
function airdrop(communityId, airdropTokens, addresses) {
|
||||
const addrArray = []
|
||||
for(var i = 0; i < addresses.length; i++) {
|
||||
addrArray.push(addresses[i]["text"])
|
||||
}
|
||||
communityTokensModuleInst.airdropCollectibles(communityId, JSON.stringify(airdropTokens), JSON.stringify(addrArray))
|
||||
communityTokensModuleInst.airdropCollectibles(communityId, JSON.stringify(airdropTokens), JSON.stringify(addresses))
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user