mirror of
https://github.com/status-im/status-desktop.git
synced 2025-03-02 15:31:04 +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"
|
title: "CommunityNewPermissionView"
|
||||||
section: "Views"
|
section: "Views"
|
||||||
}
|
}
|
||||||
|
ListElement {
|
||||||
|
title: "CommunityNewAirdropView"
|
||||||
|
section: "Views"
|
||||||
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
title: "ProfileFetchingView"
|
title: "ProfileFetchingView"
|
||||||
section: "Views"
|
section: "Views"
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
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
|
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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,8 @@ StatusDropdown {
|
|||||||
bottomInset: 10
|
bottomInset: 10
|
||||||
bottomPadding: padding + bottomInset
|
bottomPadding: padding + bottomInset
|
||||||
|
|
||||||
|
onOpened: filterInput.text = ""
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user