status-desktop/ui/app/AppLayouts/Communities/popups/HoldingsDropdown.qml

571 lines
20 KiB
QML

import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQml 2.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
import AppLayouts.Communities.controls 1.0
import AppLayouts.Communities.helpers 1.0
import utils 1.0
StatusDropdown {
id: root
property string communityId
property var assetsModel
property var collectiblesModel
property bool isENSTab: true
property bool ensCommunityPermissionsEnabled
property string noDataText: {
if(d.currentHoldingType === Constants.TokenType.ERC20)
return noDataTextForAssets
if(d.currentHoldingType === Constants.TokenType.ERC721)
return noDataTextForCollectibles
return qsTr("No data found")
}
property string noDataTextForAssets: qsTr("No assets found")
property string noDataTextForCollectibles: qsTr("No collectibles found")
property alias allTokensMode: d.allTokensMode
property var usedTokens: []
property var usedEnsNames: []
property string assetKey: ""
property string assetAmount: "0"
property int assetMultiplierIndex: 0
property string collectibleKey: ""
property string collectibleAmount: "1"
property string ensDomainName: ""
property bool showTokenAmount: true
signal addAsset(string key, string amount)
signal addCollectible(string key, string amount)
signal addEns(string domain)
signal updateAsset(string key, string amount)
signal updateCollectible(string key, string amount)
signal updateEns(string domain)
signal removeClicked
signal navigateToMintTokenSettings(bool isAssetType)
enum FlowType {
Selected, List_Deep1, List_Deep1_All, List_Deep2
}
function openUpdateFlow() {
d.initialHoldingMode = HoldingTypes.Mode.UpdateOrRemove
if(d.currentHoldingType !== Constants.TokenType.ENS) {
if(statesStack.size === 0)
statesStack.push(HoldingsDropdown.FlowType.List_Deep1)
statesStack.push(HoldingsDropdown.FlowType.Selected)
}
open()
}
function setActiveTab(holdingType) {
d.currentHoldingType = holdingType
}
function reset() {
d.currentHoldingType = Constants.TokenType.ERC20
d.initialHoldingMode = HoldingTypes.Mode.Add
root.assetKey = ""
root.collectibleKey = ""
root.ensDomainName = ""
d.setDefaultAmounts()
d.setInitialFlow()
}
QtObject {
id: d
// Internal management properties and signals:
readonly property var holdingTypes: {
let types = [
Constants.TokenType.ERC20, Constants.TokenType.ERC721
]
if (root.ensCommunityPermissionsEnabled) {
types.push(Constants.TokenType.ENS)
}
return types
}
readonly property var tabsModel: {
let tabs = [qsTr("Assets"), qsTr("Collectibles")]
if (root.ensCommunityPermissionsEnabled) {
tabs.push(qsTr("ENS"))
}
return tabs
}
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
property int extendedDropdownType: ExtendedDropdownContent.Type.Assets
property int currentHoldingType: Constants.TokenType.ERC20
property bool updateSelected: false
property int initialHoldingMode: HoldingTypes.Mode.Add
property int effectiveHoldingMode: initialHoldingMode === HoldingTypes.Mode.UpdateOrRemove
? HoldingTypes.Mode.UpdateOrRemove
: (updateSelected ? HoldingTypes.Mode.Update : HoldingTypes.Mode.Add)
property bool extendedDeepNavigation: false
property bool allTokensMode: false
property var currentSubItems
property string currentItemKey: ""
property string assetAmountText: ""
property string collectibleAmountText: "1"
property bool ensDomainNameValid: false
// By design values:
readonly property int padding: 8
readonly property int defaultWidth: 289
readonly property int tabBarHeight: 36
readonly property int tabBarTextSize: 13
readonly property int backButtonWidth: 56
readonly property int backButtonHeight: 24
readonly property int backButtonToContentSpace: 8
readonly property int bottomInset: 20
function setInitialFlow() {
statesStack.clear()
if(d.currentHoldingType !== Constants.TokenType.ENS)
statesStack.push(HoldingsDropdown.FlowType.List_Deep1)
else
statesStack.push(HoldingsDropdown.FlowType.Selected)
}
function setDefaultAmounts() {
d.assetAmountText = ""
d.collectibleAmountText = ""
root.assetAmount = "0"
root.collectibleAmount = "1"
}
function forceLayout() {
root.height = 0 //setting height to 0 before because Popup cannot properly resize if the current contentHeight exceeds the available height
root.height = undefined //use implicit height
}
}
StatesStack {
id: statesStack
}
width: d.defaultWidth
leftPadding: 0
rightPadding: 0
topPadding: d.padding
bottomInset: d.bottomInset
bottomPadding: d.bottomInset + (loader.sourceComponent == listLayout ? 0 : d.padding)
contentItem: ColumnLayout {
id: content
spacing: d.backButtonToContentSpace
state: statesStack.currentState
StatusIconTextButton {
id: backButton
Layout.preferredWidth: d.backButtonWidth
Layout.preferredHeight: d.backButtonHeight
Layout.leftMargin: d.padding
Layout.rightMargin: d.padding
visible: statesStack.size > 1
spacing: 0
leftPadding: 4
statusIcon: "previous"
icon.width: 12
icon.height: 12
text: qsTr("Back")
}
StatusSwitchTabBar {
id: tabBar
visible: !backButton.visible
Layout.preferredHeight: d.tabBarHeight
Layout.fillWidth: true
Layout.leftMargin: d.padding
Layout.rightMargin: d.padding
currentIndex: d.holdingTypes.indexOf(d.currentHoldingType)
state: d.currentHoldingType
states: [
State {
name: Constants.TokenType.ERC20
PropertyChanges {target: loader; sourceComponent: listLayout}
PropertyChanges {target: d; extendedDropdownType: ExtendedDropdownContent.Type.Assets}
},
State {
name: Constants.TokenType.ERC721
PropertyChanges {target: loader; sourceComponent: listLayout}
PropertyChanges {target: d; extendedDropdownType: ExtendedDropdownContent.Type.Collectibles}
},
State {
name: Constants.TokenType.ENS
PropertyChanges {target: loader; sourceComponent: ensLayout}
}
]
onCurrentIndexChanged: {
if(currentIndex >= 0) {
d.currentHoldingType = d.holdingTypes[currentIndex]
d.setInitialFlow()
}
}
Repeater {
id: tabLabelsRepeater
model: root.isENSTab ? d.tabsModel : d.tabsModelNoEns
StatusSwitchTabButton {
text: modelData
font.pixelSize: d.tabBarTextSize
}
}
}
Loader {
id: loader
Layout.fillWidth: true
Layout.leftMargin: loader.sourceComponent == listLayout ? 0 : d.padding
Layout.rightMargin: loader.sourceComponent == listLayout ? 0 : d.padding
Layout.fillHeight: true
onItemChanged: d.forceLayout()
}
states: [
State {
name: HoldingsDropdown.FlowType.Selected
PropertyChanges {
target: loader
sourceComponent: {
if (d.currentHoldingType === Constants.TokenType.ERC20)
return assetLayout
if (d.currentHoldingType === Constants.TokenType.ERC721)
return collectibleLayout
return ensLayout
}
}
},
State {
name: HoldingsDropdown.FlowType.List_Deep1
PropertyChanges {target: loader; sourceComponent: listLayout}
PropertyChanges {target: d; extendedDeepNavigation: false}
},
State {
name: HoldingsDropdown.FlowType.List_Deep1_All
extend: HoldingsDropdown.FlowType.List_Deep1
PropertyChanges {target: d; extendedDeepNavigation: false; allTokensMode: true }
},
State {
name: HoldingsDropdown.FlowType.List_Deep2
extend: HoldingsDropdown.FlowType.List_Deep1
PropertyChanges {target: d; extendedDeepNavigation: true}
}
]
}
onClosed: root.reset()
onIsENSTabChanged: root.reset()
Component {
id: listLayout
ExtendedDropdownContent {
id: listPanel
communityId: root.communityId
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
noDataText: root.noDataText
checkedKeys: root.usedTokens.map(entry => entry.key)
type: d.extendedDropdownType
showAllTokensMode: d.allTokensMode
showTokenAmount: root.showTokenAmount
Binding on showAllTokensMode {
value: true
when: d.extendedDropdownType === ExtendedDropdownContent.Type.Assets
&& root.assetsModel.rowCount() > 0
&& ModelUtils.get(root.assetsModel, 0, "category") === TokenCategories.Category.General
}
Binding on showAllTokensMode {
value: true
when: d.extendedDropdownType === ExtendedDropdownContent.Type.Collectibles
&& root.collectiblesModel.rowCount() > 0
&& ModelUtils.get(root.collectiblesModel, 0, "category") === TokenCategories.Category.General
}
onTypeChanged: forceActiveFocus()
onItemClicked: {
d.assetAmountText = ""
d.collectibleAmountText = ""
if (checkedKeys.includes(key)) {
const amountBasicUnit = root.usedTokens.find(entry => entry.key === key).amount
const decimals = PermissionsHelpers.getTokenByKey(root.assetsModel, key).decimals
const amount = AmountsArithmetic.toNumber(amountBasicUnit, decimals)
if(d.extendedDropdownType === ExtendedDropdownContent.Type.Assets)
root.assetAmount = amount
else
root.collectibleAmount = amount
d.updateSelected = true
} else {
d.setDefaultAmounts()
d.updateSelected = false
}
if(d.extendedDropdownType === ExtendedDropdownContent.Type.Assets)
root.assetKey = key
else
{
root.collectibleKey = key
const item = PermissionsHelpers.getTokenByKey(root.collectiblesModel, root.collectibleKey)
//When the collectible is unique, there is no need for the user to select amount
//Just send the add/update events
if((!item.infiniteSupply && (item.supply && item.supply.toString() === "1")
|| (item.remainingSupply && item.remainingSupply.toString() === "1"))) {
root.collectibleAmount = "1"
d.updateSelected ? root.updateCollectible(root.collectibleKey, "1")
: root.addCollectible(root.collectibleKey, "1")
return
}
}
statesStack.push(HoldingsDropdown.FlowType.Selected)
}
onNavigateDeep: {
d.currentSubItems = subItems
d.currentItemKey = key
statesStack.push(HoldingsDropdown.FlowType.List_Deep2)
}
onFooterButtonClicked: statesStack.push(
HoldingsDropdown.FlowType.List_Deep1_All)
onLayoutChanged: d.forceLayout()
Component.onCompleted: {
if(d.extendedDeepNavigation)
listPanel.goForward(d.currentItemKey,
PermissionsHelpers.getTokenNameByKey(root.collectiblesModel, d.currentItemKey),
PermissionsHelpers.getTokenIconByKey(root.collectiblesModel, d.currentItemKey),
d.currentSubItems)
}
onNavigateToMintTokenSettings: root.navigateToMintTokenSettings(type === ExtendedDropdownContent.Type.Assets)
Connections {
target: backButton
function onClicked() {
if (listPanel.canGoBack)
listPanel.goBack()
statesStack.pop()
}
}
Connections {
target: root
function onClosed() { listPanel.goBack() }
}
}
}
Component {
id: assetLayout
TokenPanel {
id: assetPanel
readonly property string effectiveAmount: amountValid ? amount : "0"
property bool completed: false
tokenName: PermissionsHelpers.getTokenNameByKey(root.assetsModel, root.assetKey)
tokenShortName: PermissionsHelpers.getTokenShortNameByKey(root.assetsModel, root.assetKey)
tokenImage: PermissionsHelpers.getTokenIconByKey(root.assetsModel, root.assetKey)
tokenDecimals: PermissionsHelpers.getTokenDecimalsByKey(root.assetsModel, root.assetKey)
tokenAmount: PermissionsHelpers.getTokenRemainingSupplyByKey(root.assetsModel, root.assetKey)
amountText: d.assetAmountText
tokenCategoryText: qsTr("Asset")
addOrUpdateButtonEnabled: d.assetsReady
mode: d.effectiveHoldingMode
ListModel {
Component.onCompleted: {
const asset = PermissionsHelpers.getTokenByKey(
root.assetsModel,
root.assetKey)
if (!asset)
return
const chainName = asset.chainName ?? ""
const chainIcon = asset.chainIcon
? Theme.svg(asset.chainIcon) : ""
if (!chainName)
return
append({
name: chainName,
icon: chainIcon,
amount: asset.remainingSupply,
decimals: asset.decimals,
multiplierIndex: asset.multiplierIndex,
infiniteAmount: asset.infiniteSupply
})
assetPanel.networksModel = this
}
}
onEffectiveAmountChanged: {
if (completed)
root.assetAmount = effectiveAmount
}
onMultiplierIndexChanged: root.assetMultiplierIndex = multiplierIndex
onAmountTextChanged: d.assetAmountText = amountText
onAddClicked: root.addAsset(root.assetKey, root.assetAmount)
onUpdateClicked: root.updateAsset(root.assetKey, root.assetAmount)
onRemoveClicked: root.removeClicked()
Connections {
target: backButton
function onClicked() { statesStack.pop() }
}
Component.onCompleted: {
completed = true
if (d.assetAmountText.length === 0 && root.assetAmount !== "0")
assetPanel.setAmount(root.assetAmount,
root.assetMultiplierIndex)
}
}
}
Component {
id: collectibleLayout
TokenPanel {
id: collectiblePanel
readonly property string effectiveAmount: amountValid ? amount : "0"
property bool completed: false
tokenName: PermissionsHelpers.getTokenNameByKey(root.collectiblesModel, root.collectibleKey)
tokenShortName: ""
tokenImage: PermissionsHelpers.getTokenIconByKey(root.collectiblesModel, root.collectibleKey)
tokenAmount: PermissionsHelpers.getTokenRemainingSupplyByKey(root.collectiblesModel, root.collectibleKey)
tokenDecimals: PermissionsHelpers.getTokenDecimalsByKey(root.collectiblesModel, root.assetKey)
amountText: d.collectibleAmountText
tokenCategoryText: qsTr("Collectible")
addOrUpdateButtonEnabled: d.collectiblesReady
allowDecimals: false
mode: d.effectiveHoldingMode
ListModel {
Component.onCompleted: {
const collectible = PermissionsHelpers.getTokenByKey(
root.collectiblesModel,
root.collectibleKey)
if (!collectible)
return
const chainName = collectible.chainName ?? ""
const chainIcon = collectible.chainIcon
? Theme.svg(collectible.chainIcon) : ""
if (!chainName)
return
append({
name:chainName,
icon: chainIcon,
amount: collectible.remainingSupply,
multiplierIndex: collectible.multiplierIndex,
infiniteAmount: collectible.infiniteSupply
})
collectiblePanel.networksModel = this
}
}
onEffectiveAmountChanged: {
if (completed)
root.collectibleAmount = effectiveAmount
}
onAmountTextChanged: d.collectibleAmountText = amountText
onAddClicked: root.addCollectible(root.collectibleKey, root.collectibleAmount)
onUpdateClicked: root.updateCollectible(root.collectibleKey, root.collectibleAmount)
onRemoveClicked: root.removeClicked()
Component.onCompleted: {
completed = true
if (d.collectibleAmountText.length === 0 && root.collectibleAmount)
collectiblePanel.setAmount(root.collectibleAmount)
}
Connections {
target: backButton
function onClicked() { statesStack.pop() }
}
}
}
Component {
id: ensLayout
EnsPanel {
addButtonEnabled: d.ensReady
domainName: root.ensDomainName
mode: d.initialHoldingMode
reservedNames: root.usedEnsNames
onDomainNameChanged: root.ensDomainName = domainName
onDomainNameValidChanged: d.ensDomainNameValid = domainNameValid
onAddClicked: root.addEns(root.ensDomainName)
onUpdateClicked: root.updateEns(root.ensDomainName)
onRemoveClicked: root.removeClicked()
}
}
}