571 lines
20 KiB
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
|
|
fontPixelSize: 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
|
|
? Style.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
|
|
? Style.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()
|
|
}
|
|
}
|
|
}
|