diff --git a/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml b/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml
index eedbd157a0..0a6f85e9f0 100644
--- a/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml
+++ b/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml
@@ -14,6 +14,7 @@ StatusDropdown {
property var assetsModel
property var collectiblesModel
+ property bool isENSTab: true
property var usedTokens: []
property var usedEnsNames: []
@@ -74,6 +75,8 @@ StatusDropdown {
readonly property var holdingTypes: [
HoldingTypes.Type.Asset, HoldingTypes.Type.Collectible, HoldingTypes.Type.Ens
]
+ readonly property var tabsModel: [qsTr("Assets"), qsTr("Collectibles"), qsTr("ENS")]
+ readonly property var tabsModelNoEns: [qsTr("Assets"), qsTr("Collectibles")]
readonly property bool assetsReady: root.assetAmount > 0 && root.assetKey
readonly property bool collectiblesReady: root.collectibleAmount > 0 && root.collectibleKey
readonly property bool ensReady: d.ensDomainNameValid
@@ -179,13 +182,15 @@ StatusDropdown {
]
onCurrentIndexChanged: {
- d.currentHoldingType = d.holdingTypes[currentIndex]
- d.setInitialFlow()
+ if(currentIndex >= 0) {
+ d.currentHoldingType = d.holdingTypes[currentIndex]
+ d.setInitialFlow()
+ }
}
Repeater {
id: tabLabelsRepeater
- model: [qsTr("Assets"), qsTr("Collectibles"), qsTr("ENS")]
+ model: root.isENSTab ? d.tabsModel : d.tabsModelNoEns
StatusSwitchTabButton {
text: modelData
@@ -204,14 +209,14 @@ StatusDropdown {
State {
name: HoldingsDropdown.FlowType.Selected
PropertyChanges {target: loader; sourceComponent: (d.currentHoldingType === HoldingTypes.Type.Asset) ? assetLayout :
- ((d.currentHoldingType === HoldingTypes.Type.Collectible) ? collectibleLayout : ensLayout) }
+ ((d.currentHoldingType === HoldingTypes.Type.Collectible) ? collectibleLayout : ensLayout) }
PropertyChanges {target: root; height: undefined} // use implicit height
},
State {
name: HoldingsDropdown.FlowType.List_Deep1
PropertyChanges {target: loader; sourceComponent: listLayout}
PropertyChanges {target: root; height: d.extendedContentHeight}
- PropertyChanges {target: d; extendedDeepNavigation: false}
+ PropertyChanges {target: d; extendedDeepNavigation: false}
},
State {
name: HoldingsDropdown.FlowType.List_Deep2
@@ -352,8 +357,8 @@ StatusDropdown {
onRemoveClicked: root.removeClicked()
Component.onCompleted: {
- if (d.collectibleAmountText.length === 0 && root.collectibleAmount)
- collectiblePanel.setAmount(root.collectibleAmount)
+ if (d.collectibleAmountText.length === 0 && root.collectibleAmount)
+ collectiblePanel.setAmount(root.collectibleAmount)
}
Connections {
diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityAirdropsSettingsPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityAirdropsSettingsPanel.qml
new file mode 100644
index 0000000000..433144b67a
--- /dev/null
+++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityAirdropsSettingsPanel.qml
@@ -0,0 +1,103 @@
+import QtQuick 2.14
+import QtQuick.Controls 2.14
+import QtQuick.Layouts 1.14
+
+import StatusQ.Controls 0.1
+import StatusQ.Core.Utils 0.1
+
+import AppLayouts.Chat.layouts 1.0
+import AppLayouts.Chat.views.communities 1.0
+
+import utils 1.0
+
+SettingsPageLayout {
+ id: root
+
+ // Token models:
+ required property var assetsModel
+ required property var collectiblesModel
+
+ property int viewWidth: 560 // by design
+
+ signal airdropClicked(var airdropTokens, string address)
+
+ // TODO: Update with stackmanager when #8736 is integrated
+ function navigateBack() {
+ stackManager.pop(StackView.Immediate)
+ }
+
+ QtObject {
+ id: d
+
+ readonly property string welcomeViewState: "WELCOME"
+ readonly property string newAirdropViewState: "NEW_AIRDROP"
+
+ readonly property string welcomePageTitle: qsTr("Airdrops")
+ readonly property string newAirdropViewPageTitle: qsTr("New airdrop")
+ }
+
+ content: StackView {
+ anchors.fill: parent
+ initialItem: welcomeView
+
+ Component.onCompleted: stackManager.pushInitialState(d.welcomeViewState)
+ }
+
+ state: stackManager.currentState
+ states: [
+ State {
+ name: d.welcomeViewState
+ PropertyChanges {target: root; title: d.welcomePageTitle}
+ PropertyChanges {target: root; previousPageName: ""}
+ PropertyChanges {target: root; headerButtonVisible: true}
+ PropertyChanges {target: root; headerButtonText: qsTr("New Airdrop")}
+ PropertyChanges {target: root; headerWidth: root.viewWidth}
+ },
+ State {
+ name: d.newAirdropViewState
+ PropertyChanges {target: root; title: d.newAirdropViewPageTitle}
+ PropertyChanges {target: root; previousPageName: d.welcomePageTitle}
+ PropertyChanges {target: root; headerButtonVisible: false}
+ PropertyChanges {target: root; headerWidth: 0}
+ }
+ ]
+
+ onHeaderButtonClicked: stackManager.push(d.newAirdropViewState, newAirdropView, null, StackView.Immediate)
+
+ StackViewStates {
+ id: stackManager
+
+ stackView: root.contentItem
+ }
+
+ // Mint tokens possible view contents:
+ Component {
+ id: welcomeView
+
+ CommunityWelcomeSettingsView {
+ viewWidth: root.viewWidth
+ image: Style.png("community/airdrops8_1")
+ title: qsTr("Airdrop community tokens")
+ subtitle: qsTr("You can mint custom tokens and collectibles for your community")
+ checkersModel: [
+ qsTr("Reward individual members with custom tokens for their contribution"),
+ qsTr("Incentivise joining, retention, moderation and desired behaviour"),
+ qsTr("Require holding a token or NFT to obtain exclusive membership rights")
+ ]
+ }
+ }
+
+ Component {
+ id: newAirdropView
+
+ CommunityNewAirdropView {
+ assetsModel: root.assetsModel
+ collectiblesModel: root.collectiblesModel
+
+ onAirdropClicked: {
+ root.airdropClicked(airdropTokens, address)
+ stackManager.clear(d.welcomeViewState, StackView.Immediate)
+ }
+ }
+ }
+}
diff --git a/ui/app/AppLayouts/Chat/panels/communities/qmldir b/ui/app/AppLayouts/Chat/panels/communities/qmldir
index cd1c9cd2a5..33298c261b 100644
--- a/ui/app/AppLayouts/Chat/panels/communities/qmldir
+++ b/ui/app/AppLayouts/Chat/panels/communities/qmldir
@@ -1,3 +1,4 @@
+CommunityAirdropsSettingsPanel 1.0 CommunityAirdropsSettingsPanel.qml
CommunityColumnHeaderPanel 1.0 CommunityColumnHeaderPanel.qml
CommunityMintTokensSettingsPanel 1.0 CommunityMintTokensSettingsPanel.qml
CommunityPermissionsSettingsPanel 1.0 CommunityPermissionsSettingsPanel.qml
diff --git a/ui/app/AppLayouts/Chat/stores/CommunityTokensStore.qml b/ui/app/AppLayouts/Chat/stores/CommunityTokensStore.qml
index 0bf0fa0e3a..10a29d2303 100644
--- a/ui/app/AppLayouts/Chat/stores/CommunityTokensStore.qml
+++ b/ui/app/AppLayouts/Chat/stores/CommunityTokensStore.qml
@@ -55,4 +55,9 @@ QtObject {
}
])
}
+
+ // Airdrop tokens:
+ function airdrop(airdropTokens, address) {
+ console.warn("TODO: Airdrop backend call!")
+ }
}
diff --git a/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml b/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml
index b6b92c8386..f90b191a09 100644
--- a/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml
+++ b/ui/app/AppLayouts/Chat/views/CommunitySettingsView.qml
@@ -36,9 +36,9 @@ StatusSectionLayout {
property var settingsMenuModel: [{name: qsTr("Overview"), icon: "show", enabled: true},
{name: qsTr("Members"), icon: "group-chat", enabled: true},
{name: qsTr("Permissions"), icon: "objects", enabled: root.rootStore.communityPermissionsEnabled},
- {name: qsTr("Mint Tokens"), icon: "token", enabled: root.rootStore.communityTokensEnabled}]
+ {name: qsTr("Mint Tokens"), icon: "token", enabled: root.rootStore.communityTokensEnabled},
+ {name: qsTr("Airdrops"), icon: "airdrop", enabled: root.rootStore.communityTokensEnabled}]
// TODO: Next community settings options:
- // {name: qsTr("Airdrops"), icon: "airdrop"},
// {name: qsTr("Token sales"), icon: "token-sale"},
// {name: qsTr("Subscriptions"), icon: "subscription"},
property var rootStore
@@ -310,6 +310,17 @@ StatusSectionLayout {
}
}
+ CommunityAirdropsSettingsPanel {
+ readonly property CommunityTokensStore communityTokensStore:
+ rootStore.communityTokensStore
+
+ assetsModel: rootStore.assetsModel
+ collectiblesModel: rootStore.collectiblesModel
+
+ onPreviousPageNameChanged: root.backButtonName = previousPageName
+ onAirdropClicked: communityTokensStore.airdrop(airdropTokens, chainId, address)
+ }
+
onCurrentIndexChanged: root.backButtonName = centerPanelContentLoader.item.children[d.currentIndex].previousPageName
}
}
diff --git a/ui/app/AppLayouts/Chat/views/communities/CommunityNewAirdropView.qml b/ui/app/AppLayouts/Chat/views/communities/CommunityNewAirdropView.qml
new file mode 100644
index 0000000000..f4ba6a3f6d
--- /dev/null
+++ b/ui/app/AppLayouts/Chat/views/communities/CommunityNewAirdropView.qml
@@ -0,0 +1,246 @@
+import QtQuick 2.14
+import QtQuick.Controls 2.14
+import QtQuick.Layouts 1.14
+
+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
+
+import utils 1.0
+import shared.panels 1.0
+
+import AppLayouts.Chat.helpers 1.0
+import AppLayouts.Chat.panels.communities 1.0
+import AppLayouts.Chat.controls.community 1.0
+
+// TEMPORAL - BASIC IMPLEMENTATION
+StatusScrollView {
+ id: root
+
+ // Token models:
+ required property var assetsModel
+ required property var collectiblesModel
+
+ property int viewWidth: 560 // by design
+
+ // roles: type, key, name, amount, imageSource
+ property var selectedHoldingsModel: ListModel {}
+
+ readonly property bool isFullyFilled: selectedHoldingsModel.count > 0 &&
+ addressess.itemsModel.count > 0
+
+ signal airdropClicked(var airdropTokens, string address)
+
+ QtObject {
+ id: d
+
+ readonly property int maxAirdropTokens: 5
+ readonly property int dropdownHorizontalOffset: 4
+ readonly property int dropdownVerticalOffset: 1
+ }
+
+ contentWidth: mainLayout.width
+ contentHeight: mainLayout.height
+
+ ColumnLayout {
+ id: mainLayout
+ width: root.viewWidth
+ spacing: 0
+
+ StatusItemSelector {
+ id: tokensSelector
+
+ property int editedIndex: -1
+
+ Layout.fillWidth: true
+ icon: Style.svg("token")
+ title: qsTr("What")
+ defaultItemText: qsTr("Example: 1 SOCK")
+ tagLeftPadding: 2
+ asset.height: 28
+ asset.width: asset.height
+ addButton.visible: itemsModel.count < d.maxAirdropTokens
+
+ itemsModel: HoldingsSelectionModel {
+ sourceModel: root.selectedHoldingsModel
+
+ assetsModel: root.assetsModel
+ collectiblesModel: root.collectiblesModel
+ }
+
+ // TODO: All this code is repeated inside `CommunityNewPermissionView`. Check how to reuse it.
+ HoldingsDropdown {
+ id: dropdown
+
+ assetsModel: root.assetsModel
+ collectiblesModel: root.collectiblesModel
+ isENSTab: false
+
+ function addItem(type, item, amount) {
+ const key = item.key
+
+ root.selectedHoldingsModel.append(
+ { type, key, amount })
+ }
+
+ function getHoldingIndex(key) {
+ return ModelUtils.indexOf(root.selectedHoldingsModel, "key", key)
+ }
+
+ function prepareUpdateIndex(key) {
+ const itemIndex = tokensSelector.editedIndex
+ const existingIndex = getHoldingIndex(key)
+
+ if (itemIndex !== -1 && existingIndex !== -1 && itemIndex !== existingIndex) {
+ const previousKey = root.selectedHoldingsModel.get(itemIndex).key
+ root.selectedHoldingsModel.remove(existingIndex)
+ return getHoldingIndex(previousKey)
+ }
+
+ if (itemIndex === -1) {
+ return existingIndex
+ }
+
+ return itemIndex
+ }
+
+ onAddAsset: {
+ const modelItem = CommunityPermissionsHelpers.getTokenByKey(
+ root.assetsModel, key)
+ addItem(HoldingTypes.Type.Asset, modelItem, amount)
+ dropdown.close()
+ }
+
+ onAddCollectible: {
+ const modelItem = CommunityPermissionsHelpers.getTokenByKey(
+ root.collectiblesModel, key)
+ addItem(HoldingTypes.Type.Collectible, modelItem, amount)
+ dropdown.close()
+ }
+
+ onUpdateAsset: {
+ const itemIndex = prepareUpdateIndex(key)
+ const modelItem = CommunityPermissionsHelpers.getTokenByKey(root.assetsModel, key)
+
+ root.selectedHoldingsModel.set(
+ itemIndex, { type: HoldingTypes.Type.Asset, key, amount })
+ dropdown.close()
+ }
+
+ onUpdateCollectible: {
+ const itemIndex = prepareUpdateIndex(key)
+ const modelItem = CommunityPermissionsHelpers.getTokenByKey(
+ root.collectiblesModel, key)
+
+ root.selectedHoldingsModel.set(
+ itemIndex,
+ { type: HoldingTypes.Type.Collectible, key, amount })
+ dropdown.close()
+ }
+
+ onRemoveClicked: {
+ root.selectedHoldingsModel.remove(tokensSelector.editedIndex)
+ dropdown.close()
+ }
+ }
+
+ addButton.onClicked: {
+ dropdown.parent = tokensSelector.addButton
+ dropdown.x = tokensSelector.addButton.width + d.dropdownHorizontalOffset
+ dropdown.y = 0
+ dropdown.open()
+
+ editedIndex = -1
+ }
+
+ onItemClicked: {
+ if (mouse.button !== Qt.LeftButton)
+ return
+
+ dropdown.parent = item
+ dropdown.x = mouse.x + d.dropdownHorizontalOffset
+ dropdown.y = d.dropdownVerticalOffset
+
+ const modelItem = tokensSelector.itemsModel.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)
+ dropdown.openUpdateFlow()
+
+ editedIndex = index
+ }
+ }
+
+ Rectangle {
+ Layout.leftMargin: 16
+ Layout.preferredWidth: 2
+ Layout.preferredHeight: 24
+ color: Style.current.separator
+ }
+
+ // TEMPORAL
+ StatusInput {
+ id: addressInput
+
+ Layout.fillWidth: true
+ placeholderText: qsTr("Example: 0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7999")
+ }
+
+ Rectangle {
+ Layout.leftMargin: 16
+ Layout.preferredWidth: 2
+ Layout.preferredHeight: 24
+ color: Style.current.separator
+ }
+
+ StatusItemSelector {
+ id: addressess
+
+ Layout.fillWidth: true
+ icon: Style.svg("member")
+ title: qsTr("To")
+ defaultItemText: qsTr("Example: 12 addresses and 3 members")
+ tagLeftPadding: 2
+ asset.height: 28
+ asset.width: asset.height
+
+ addButton.onClicked: {
+ if(addressInput.text.length > 0)
+ itemsModel.append({text: addressInput.text})
+ }
+
+ onItemClicked: addressess.itemsModel.remove(index)
+ }
+
+ StatusButton {
+ Layout.preferredHeight: 44
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ Layout.topMargin: Style.current.bigPadding
+ text: qsTr("Create airdrop")
+ enabled: root.isFullyFilled
+
+ onClicked: {
+ const airdropTokens = ModelUtils.modelToArray(
+ root.selectedHoldingsModel,
+ ["key", "type", "amount"])
+
+ root.airdropClicked(airdropTokens, addressess.itemsModel)
+ }
+ }
+ }
+}
diff --git a/ui/app/AppLayouts/Chat/views/communities/qmldir b/ui/app/AppLayouts/Chat/views/communities/qmldir
index d77ecd52d8..3a1000ba77 100644
--- a/ui/app/AppLayouts/Chat/views/communities/qmldir
+++ b/ui/app/AppLayouts/Chat/views/communities/qmldir
@@ -1,5 +1,6 @@
ChannelsSelectionModel 1.0 ChannelsSelectionModel.qml
CommunityCollectibleView 1.0 CommunityCollectibleView.qml
+CommunityNewAirdropView 1.0 CommunityNewAirdropView.qml
CommunityMintedTokensView 1.0 CommunityMintedTokensView.qml
CommunityNewCollectibleView 1.0 CommunityNewCollectibleView.qml
CommunityNewPermissionView 1.0 CommunityNewPermissionView.qml
diff --git a/ui/imports/assets/icons/member.svg b/ui/imports/assets/icons/member.svg
new file mode 100644
index 0000000000..c418d41ff8
--- /dev/null
+++ b/ui/imports/assets/icons/member.svg
@@ -0,0 +1,4 @@
+
diff --git a/ui/imports/assets/icons/token.svg b/ui/imports/assets/icons/token.svg
new file mode 100644
index 0000000000..4f68221c17
--- /dev/null
+++ b/ui/imports/assets/icons/token.svg
@@ -0,0 +1,4 @@
+
diff --git a/ui/imports/assets/png/community/airdrops8_1.png b/ui/imports/assets/png/community/airdrops8_1.png
new file mode 100644
index 0000000000..baa38a5e01
Binary files /dev/null and b/ui/imports/assets/png/community/airdrops8_1.png differ