feat(AirdropAssets): Enable assets tab in airdrop dropdown

- Added assets model.
- Added no data text for assets tab.
- Added navigation from airdrop to mint specific tab.
- Updated `HoldingsDropdown` component to allow network information for assets.
- Removed `isCollectiblesOnly` option in `HoldingsDropdown`. No longer needed.
- Updated `storybook` to support airdrop assets testing.

Closes #11056
This commit is contained in:
Noelia 2023-06-14 09:19:45 +02:00 committed by Noelia
parent aa8eb07f56
commit d4ba22f7bb
11 changed files with 292 additions and 61 deletions

View File

@ -119,6 +119,49 @@ SplitView {
}
}
AssetsModel {
id: assetsModel
}
SortFilterProxyModel {
id: assetsModelWithSupply
sourceModel: assetsModel
proxyRoles: [
ExpressionRole {
name: "supply"
expression: ((model.index + 1) * 258).toString()
},
ExpressionRole {
name: "infiniteSupply"
expression: !(model.index % 4)
},
ExpressionRole {
name: "chainName"
expression: model.index ? "Ethereum Mainnet" : "Goerli"
},
ExpressionRole {
readonly property string icon1: "network/Network=Ethereum"
readonly property string icon2: "network/Network=Testnet"
name: "chainIcon"
expression: model.index ? icon1 : icon2
}
]
filters: ValueFilter {
roleName: "category"
value: TokenCategories.Category.Community
}
Component.onCompleted: {
Qt.callLater(() => communityAirdropsSettingsPanel.assetsModel = this)
}
}
membersModel: UsersModel {}
onAirdropClicked: logs.logEvent("CommunityAirdropsSettingsPanel::onAirdropClicked")

View File

@ -103,7 +103,7 @@ SplitView {
active: globalUtilsReady && mainModuleReady
sourceComponent: CommunityNewAirdropView {
id: communityNewPermissionView
id: communityNewAirdropView
CollectiblesModel {
id: collectiblesModel
@ -144,7 +144,50 @@ SplitView {
Component.onCompleted: {
Qt.callLater(() => communityNewPermissionView.collectiblesModel = this)
Qt.callLater(() => communityNewAirdropView.collectiblesModel = this)
}
}
AssetsModel {
id: assetsModel
}
SortFilterProxyModel {
id: assetsModelWithSupply
sourceModel: assetsModel
proxyRoles: [
ExpressionRole {
name: "supply"
expression: ((model.index + 1) * 258).toString()
},
ExpressionRole {
name: "infiniteSupply"
expression: !(model.index % 4)
},
ExpressionRole {
name: "chainName"
expression: model.index ? "Ethereum Mainnet" : "Goerli"
},
ExpressionRole {
readonly property string icon1: "network/Network=Ethereum"
readonly property string icon2: "network/Network=Testnet"
name: "chainIcon"
expression: model.index ? icon1 : icon2
}
]
filters: ValueFilter {
roleName: "category"
value: TokenCategories.Category.Community
}
Component.onCompleted: {
Qt.callLater(() => communityNewAirdropView.assetsModel = this)
}
}

View File

@ -89,14 +89,52 @@ SplitView {
}
}
AssetsModel {
id: assetsModel
}
SortFilterProxyModel {
id: assetsModelWithSupply
sourceModel: assetsModel
proxyRoles: [
ExpressionRole {
name: "supply"
expression: (model.index + 1) * 584
},
ExpressionRole {
name: "infiniteSupply"
expression: !(model.index % 4)
},
ExpressionRole {
name: "chainName"
expression: model.index ? "Ethereum Mainnet" : "Goerli"
},
ExpressionRole {
readonly property string icon1: "network/Network=Ethereum"
readonly property string icon2: "network/Network=Testnet"
name: "chainIcon"
expression: model.index ? icon1 : icon2
}
]
filters: ValueFilter {
roleName: "category"
value: TokenCategories.Category.Community
}
}
collectiblesModel: isAirdropMode.checked
? collectiblesModelWithSupply
: collectiblesModel
assetsModel: AssetsModel {}
assetsModel: isAirdropMode.checked
? assetsModelWithSupply
: assetsModel
isENSTab: isEnsTabChecker.checked
isCollectiblesOnly: isCollectiblesOnlyChecker.checked
onOpened: contentItem.parent.parent = container
Component.onCompleted: {
@ -118,12 +156,6 @@ SplitView {
checked: true
}
CheckBox {
id: isCollectiblesOnlyChecker
text: "Collectibles only"
checked: false
}
CheckBox {
id: isAirdropMode
text: "Airdrop mode"

View File

@ -16,7 +16,7 @@ ListModel {
iconSource: ModelsData.assets.zrx,
name: "Ox",
shortName: "ZRX",
category: TokenCategories.Category.Own
category: TokenCategories.Category.Community
},
{
key: "1inch",

View File

@ -439,11 +439,18 @@ Item {
ListDropdownContent {
availableData: d.availableData
noDataText: root.noDataText
areHeaderButtonsVisible: root.state === d.depth1_ListState
&& !root.showAllTokensMode
headerModel: ListModel {
ListElement { key: "MINT"; icon: "add"; iconSize: 16; description: qsTr("Mint asset"); rotation: 0; spacing: 8 }
ListElement { key: "IMPORT"; icon: "invite-users"; iconSize: 16; description: qsTr("Import existing asset"); rotation: 180; spacing: 8 }
ListElement {
key: "MINT"
icon: "add"
iconSize: 16
description: qsTr("Mint asset")
rotation: 0
spacing: 8
}
}
areHeaderButtonsVisible: false // TEMPORARILY hidden. These 2 header options will be implemented after MVP.
checkedKeys: root.checkedKeys
searchMode: d.searchMode
@ -454,12 +461,9 @@ Item {
isFooterButtonVisible: !root.showAllTokensMode && !d.searchMode
&& filteredModel.item && d.currentModel.count > filteredModel.item.count
onHeaderItemClicked: root.navigateToMintTokenSettings()
onFooterButtonClicked: root.footerButtonClicked()
onHeaderItemClicked: {
if(key === "MINT") console.log("TODO: Mint asset")
else if(key === "IMPORT") console.log("TODO: Import existing asset")
}
onItemClicked: root.itemClicked(key, shortName, iconSource)
onImplicitHeightChanged: root.layoutChanged()

View File

@ -17,14 +17,15 @@ StatusDropdown {
property var assetsModel
property var collectiblesModel
property bool isENSTab: true
property bool isCollectiblesOnly: false
property string noDataText: {
if(d.currentHoldingType === HoldingTypes.Type.Asset)
return qsTr("No assets found")
return noDataTextForAssets
if(d.currentHoldingType === HoldingTypes.Type.Collectible)
return qsTr("No collectibles found")
return noDataTextForCollectibles
return qsTr("No data found")
}
property string noDataTextForAssets: qsTr("No assets found")
property string noDataTextForCollectibles: qsTr("No collectibles found")
property var usedTokens: []
property var usedEnsNames: []
@ -46,7 +47,7 @@ StatusDropdown {
signal updateEns(string domain)
signal removeClicked
signal navigateToMintTokenSettings
signal navigateToMintTokenSettings(bool isAssetType)
enum FlowType {
Selected, List_Deep1, List_Deep1_All, List_Deep2
@ -64,11 +65,11 @@ StatusDropdown {
}
function setActiveTab(holdingType) {
d.currentHoldingType = root.isCollectiblesOnly ? HoldingTypes.Type.Collectible : holdingType
d.currentHoldingType = holdingType
}
function reset() {
d.currentHoldingType = root.isCollectiblesOnly ? HoldingTypes.Type.Collectible : HoldingTypes.Type.Asset
d.currentHoldingType = HoldingTypes.Type.Asset
d.initialHoldingMode = HoldingTypes.Mode.Add
root.assetKey = ""
@ -93,7 +94,7 @@ StatusDropdown {
readonly property bool ensReady: d.ensDomainNameValid
property int extendedDropdownType: ExtendedDropdownContent.Type.Assets
property int currentHoldingType: root.isCollectiblesOnly ? HoldingTypes.Type.Collectible : HoldingTypes.Type.Asset
property int currentHoldingType: HoldingTypes.Type.Asset
property bool updateSelected: false
@ -176,7 +177,7 @@ StatusDropdown {
StatusSwitchTabBar {
id: tabBar
visible: !root.isCollectiblesOnly && !backButton.visible
visible: !backButton.visible
Layout.preferredHeight: d.tabBarHeigh
Layout.fillWidth: true
currentIndex: d.holdingTypes.indexOf(d.currentHoldingType)
@ -200,7 +201,7 @@ StatusDropdown {
onCurrentIndexChanged: {
if(currentIndex >= 0) {
d.currentHoldingType = root.isCollectiblesOnly ? HoldingTypes.Type.Collectible : d.holdingTypes[currentIndex]
d.currentHoldingType = d.holdingTypes[currentIndex]
d.setInitialFlow()
}
}
@ -256,7 +257,6 @@ StatusDropdown {
}
onClosed: root.reset()
onIsCollectiblesOnlyChanged: root.reset()
onIsENSTabChanged: root.reset()
Component {
@ -336,7 +336,7 @@ StatusDropdown {
d.currentSubItems)
}
onNavigateToMintTokenSettings: root.navigateToMintTokenSettings()
onNavigateToMintTokenSettings: root.navigateToMintTokenSettings(type === ExtendedDropdownContent.Type.Assets)
Connections {
target: backButton
@ -369,11 +369,37 @@ StatusDropdown {
tokenShortName: CommunityPermissionsHelpers.getTokenShortNameByKey(root.assetsModel, root.assetKey)
tokenImage: CommunityPermissionsHelpers.getTokenIconByKey(root.assetsModel, root.assetKey)
amountText: d.assetAmountText
tokenCategoryText: qsTr("Asset")
addOrUpdateButtonEnabled: d.assetsReady
mode: d.effectiveHoldingMode
ListModel {
Component.onCompleted: {
const asset = CommunityPermissionsHelpers.getTokenByKey(
root.assetsModel,
root.assetKey)
if (!asset)
return
const chainName = asset.chainName ?? ""
const chainIcon = asset.chainIcon
? Style.svg(asset.chainIcon) : ""
if (!chainName)
return
append({
name:chainName,
icon: chainIcon,
amount: asset.supply,
infiniteAmount: asset.infiniteSupply
})
assetPanel.networksModel = this
}
}
onEffectiveAmountChanged: root.assetAmount = effectiveAmount
onAmountTextChanged: d.assetAmountText = amountText
onAddClicked: root.addAsset(root.assetKey, root.assetAmount)

View File

@ -188,7 +188,7 @@ StatusListView {
anchors.leftMargin: Style.current.halfPadding
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: parent.width
width: parent.width - anchors.leftMargin
text: sectionDelegateRoot.section
color: Theme.palette.baseColor1
font.pixelSize: 12

View File

@ -27,9 +27,10 @@ SettingsPageLayout {
property int viewWidth: 560 // by design
signal airdropClicked(var airdropTokens, var addresses, var membersPubKeys)
signal airdropFeesRequested(var contractKeysAndAmounts, var addresses)
signal navigateToMintTokenSettings
signal navigateToMintTokenSettings(bool isAssetType)
function navigateBack() {
stackManager.pop(StackView.Immediate)
@ -123,7 +124,7 @@ SettingsPageLayout {
root.airdropClicked(airdropTokens, addresses, membersPubKeys)
stackManager.clear(d.welcomeViewState, StackView.Immediate)
}
onNavigateToMintTokenSettings: root.navigateToMintTokenSettings()
onNavigateToMintTokenSettings: root.navigateToMintTokenSettings(isAssetType)
Component.onCompleted: {
d.selectToken.connect(view.selectToken)

View File

@ -62,6 +62,11 @@ SettingsPageLayout {
stackManager.pop(StackView.Immediate)
}
function resetNavigation(isAssetView = false) {
d.isAssetView = isAssetView
stackManager.clear(d.initialViewState, StackView.Immediate)
}
QtObject {
id: d
@ -88,7 +93,7 @@ SettingsPageLayout {
property int burnAmount
property int remainingTokens
property url artworkSource
property bool isAssetType
property bool isAssetView: false
property var currentToken // CollectibleObject or AssetObject type
readonly property var initialItem: (root.tokensModel && root.tokensModel.count > 0) ? mintedTokensView : welcomeView
@ -138,7 +143,7 @@ SettingsPageLayout {
},
State {
name: d.newTokenViewState
PropertyChanges {target: root; title: d.isAssetType ? d.newAssetPageTitle : d.newCollectiblePageTitle }
PropertyChanges {target: root; title: d.isAssetView ? d.newAssetPageTitle : d.newCollectiblePageTitle }
PropertyChanges {target: root; subTitle: ""}
PropertyChanges {target: root; previousPageName: d.backButtonText}
PropertyChanges {target: root; primaryHeaderButton.visible: false}
@ -159,12 +164,19 @@ SettingsPageLayout {
]
onPrimaryHeaderButtonClicked: {
if(root.state == d.initialViewState)
stackManager.push(d.newTokenViewState, newTokenView, null, StackView.Immediate)
if(root.state == d.initialViewState) {
// Then move on to the new token view, with the specific tab selected:
stackManager.push(d.newTokenViewState,
newTokenView,
{
isAssetView: d.isAssetView
},
StackView.Immediate)
}
if(root.state == d.tokenViewState) {
if(d.currentToken) {
if(d.isAssetType) {
if(d.isAssetView) {
// Copy current data:
temp_.asset.copyAsset(d.currentToken)
@ -172,13 +184,13 @@ SettingsPageLayout {
d.currentToken = temp_.asset
// Reset the stack:
stackManager.clear(d.initialViewState, StackView.Immediate)
root.resetNavigation(true)
// Then move on to the new token view, but asset pre-filled:
stackManager.push(d.newTokenViewState,
newTokenView,
{
isAssetView: d.isAssetType,
isAssetView: d.isAssetView,
referenceName: d.currentToken.name,
referenceSymbol: d.currentToken.symbol,
validationMode: StatusInput.ValidationMode.Always,
@ -193,13 +205,13 @@ SettingsPageLayout {
d.currentToken = temp_.collectible
// Reset the stack:
stackManager.clear(d.initialViewState, StackView.Immediate)
root.resetNavigation(false)
// Then move on to the new token view, but collectible pre-filled:
stackManager.push(d.newTokenViewState,
newTokenView,
{
isAssetView: d.isAssetType,
isAssetView: d.isAssetView,
referenceName: d.currentToken.name,
referenceSymbol: d.currentToken.symbol,
validationMode: StatusInput.ValidationMode.Always,
@ -347,7 +359,7 @@ SettingsPageLayout {
else
root.mintCollectible(collectible)
stackManager.clear(d.initialViewState, StackView.Immediate)
root.resetNavigation()
}
viewWidth: root.viewWidth
@ -610,7 +622,7 @@ SettingsPageLayout {
Binding {
target: d
property: "isAssetType"
property: "isAssetView"
value: view.tokenType === Constants.TokenType.ERC20
}
@ -718,7 +730,7 @@ SettingsPageLayout {
onAcceptClicked: {
root.deleteToken(d.tokenKey)
stackManager.clear(d.initialViewState, StackView.Immediate)
root.resetNavigation()
}
onCancelClicked: close()
}

View File

@ -396,26 +396,37 @@ StatusSectionLayout {
url)
}
}
Connections {
target: airdropPanel
function onNavigateToMintTokenSettings(isAssetType) {
// Here it is forced a navigation to the new airdrop form, like if it was clicked the header button
mintPanel.resetNavigation(isAssetType)
mintPanel.primaryHeaderButtonClicked()
}
}
}
CommunityAirdropsSettingsPanel {
id: airdropPanel
readonly property CommunityTokensStore communityTokensStore:
rootStore.communityTokensStore
assetsModel: ListModel {}
rootStore.communityTokensStore
readonly property var communityTokens: root.community.communityTokens
Loader {
id: modelLoader
id: assetsModelLoader
active: airdropPanel.communityTokens
sourceComponent: SortFilterProxyModel {
sourceModel: airdropPanel.communityTokens
filters: ValueFilter {
roleName: "tokenType"
value: Constants.TokenType.ERC20
}
proxyRoles: [
ExpressionRole {
name: "category"
@ -436,8 +447,39 @@ StatusSectionLayout {
}
}
collectiblesModel: modelLoader.item
Loader {
id: collectiblesModelLoader
active: airdropPanel.communityTokens
sourceComponent: SortFilterProxyModel {
sourceModel: airdropPanel.communityTokens
filters: ValueFilter {
roleName: "tokenType"
value: Constants.TokenType.ERC721
}
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
}
]
}
}
assetsModel: assetsModelLoader.item
collectiblesModel: collectiblesModelLoader.item
membersModel: {
const chatContentModule = root.rootStore.currentChatContentModule()
if (!chatContentModule || !chatContentModule.usersModule) {

View File

@ -56,7 +56,7 @@ StatusScrollView {
signal airdropFeesRequested(var contractKeysAndAmounts, var addresses)
signal navigateToMintTokenSettings
signal navigateToMintTokenSettings(bool isAssetType)
function selectToken(key, amount, type) {
var tokenModel = null
@ -94,7 +94,7 @@ StatusScrollView {
const modelItem = CommunityPermissionsHelpers.getTokenByKey(tokenModel, key)
return {
key, amount,
key, amount, type,
tokenText: amount + " " + modelItem.name,
tokenImage: modelItem.iconSource,
networkText: modelItem.chainName,
@ -174,8 +174,8 @@ StatusScrollView {
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
isENSTab: false
isCollectiblesOnly: true
noDataText: qsTr("First you need to mint or import a collectible before you can perform an airdrop")
noDataTextForAssets: qsTr("First you need to mint or import an asset before you can perform an airdrop")
noDataTextForCollectibles: qsTr("First you need to mint or import a collectible before you can perform an airdrop")
function getHoldingIndex(key) {
return ModelUtils.indexOf(root.selectedHoldingsModel, "key", key)
@ -203,6 +203,14 @@ StatusScrollView {
root.selectedHoldingsModel, ["key", "amount"])
}
onAddAsset: {
const entry = d.prepareEntry(key, amount, Constants.TokenType.ERC20)
entry.valid = true
selectedHoldingsModel.append(entry)
dropdown.close()
}
onAddCollectible: {
const entry = d.prepareEntry(key, amount, Constants.TokenType.ERC721)
entry.valid = true
@ -211,12 +219,19 @@ StatusScrollView {
dropdown.close()
}
onUpdateAsset: {
const itemIndex = prepareUpdateIndex(key)
const entry = d.prepareEntry(key, amount, Constants.TokenType.ERC20)
root.selectedHoldingsModel.set(itemIndex, entry)
dropdown.close()
}
onUpdateCollectible: {
const itemIndex = prepareUpdateIndex(key)
const entry = d.prepareEntry(key, amount, Constants.TokenType.ERC721)
const modelItem = CommunityPermissionsHelpers.getTokenByKey(
root.collectiblesModel, key)
root.selectedHoldingsModel.set(itemIndex, entry)
dropdown.close()
@ -228,7 +243,7 @@ StatusScrollView {
}
onNavigateToMintTokenSettings: {
root.navigateToMintTokenSettings()
root.navigateToMintTokenSettings(isAssetType)
close()
}
}
@ -251,9 +266,22 @@ StatusScrollView {
dropdown.y = d.dropdownVerticalOffset
const modelItem = selectedHoldingsModel.get(index)
dropdown.collectibleKey = modelItem.key
dropdown.collectibleAmount = modelItem.amount
dropdown.setActiveTab(HoldingTypes.Type.Collectible)
switch(modelItem.type) {
case HoldingTypes.Type.Asset:
dropdown.assetKey = modelItem.key
dropdown.assetAmount = modelItem.amount
dropdown.setActiveTab(HoldingTypes.Type.Asset)
break
case HoldingTypes.Type.Collectible:
dropdown.collectibleKey = modelItem.key
dropdown.collectibleAmount = modelItem.amount
dropdown.setActiveTab(HoldingTypes.Type.Collectible)
break
default:
console.warn("Unsupported token type.")
}
dropdown.openUpdateFlow()
editedIndex = index