status-desktop/ui/app/AppLayouts/Chat/controls/community/HoldingsDropdown.qml

477 lines
16 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.Chat.helpers 1.0
import utils 1.0
StatusDropdown {
id: root
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")
if(d.currentHoldingType === HoldingTypes.Type.Collectible)
return qsTr("No collectibles found")
return qsTr("No data found")
}
property var usedTokens: []
property var usedEnsNames: []
property string assetKey: ""
property real assetAmount: 0
property string collectibleKey: ""
property real collectibleAmount: 1
property string ensDomainName: ""
signal addAsset(string key, real amount)
signal addCollectible(string key, real amount)
signal addEns(string domain)
signal updateAsset(string key, real amount)
signal updateCollectible(string key, real amount)
signal updateEns(string domain)
signal removeClicked
signal navigateToMintTokenSettings
enum FlowType {
Selected, List_Deep1, List_Deep1_All, List_Deep2
}
function openUpdateFlow() {
d.initialHoldingMode = HoldingTypes.Mode.UpdateOrRemove
if(d.currentHoldingType !== HoldingTypes.Type.Ens) {
if(statesStack.size === 0)
statesStack.push(HoldingsDropdown.FlowType.List_Deep1)
statesStack.push(HoldingsDropdown.FlowType.Selected)
}
open()
}
function setActiveTab(holdingType) {
d.currentHoldingType = root.isCollectiblesOnly ? HoldingTypes.Type.Collectible : holdingType
}
function reset() {
d.currentHoldingType = root.isCollectiblesOnly ? HoldingTypes.Type.Collectible : HoldingTypes.Type.Asset
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: [
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
property int extendedDropdownType: ExtendedDropdownContent.Type.Assets
property int currentHoldingType: root.isCollectiblesOnly ? HoldingTypes.Type.Collectible : HoldingTypes.Type.Asset
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 extendedContentHeight: 380
readonly property int tabBarHeigh: 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 !== HoldingTypes.Type.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
padding: d.padding
bottomInset: d.bottomInset
bottomPadding: d.padding + d.bottomInset
contentItem: ColumnLayout {
id: content
spacing: d.backButtonToContentSpace
state: statesStack.currentState
StatusIconTextButton {
id: backButton
Layout.preferredWidth: d.backButtonWidth
Layout.preferredHeight: d.backButtonHeight
visible: statesStack.size > 1
spacing: 0
leftPadding: 4
statusIcon: "previous"
icon.width: 12
icon.height: 12
text: qsTr("Back")
}
StatusSwitchTabBar {
id: tabBar
visible: !root.isCollectiblesOnly && !backButton.visible
Layout.preferredHeight: d.tabBarHeigh
Layout.fillWidth: true
currentIndex: d.holdingTypes.indexOf(d.currentHoldingType)
state: d.currentHoldingType
states: [
State {
name: HoldingTypes.Type.Asset
PropertyChanges {target: loader; sourceComponent: listLayout}
PropertyChanges {target: d; extendedDropdownType: ExtendedDropdownContent.Type.Assets}
},
State {
name: HoldingTypes.Type.Collectible
PropertyChanges {target: loader; sourceComponent: listLayout}
PropertyChanges {target: d; extendedDropdownType: ExtendedDropdownContent.Type.Collectibles}
},
State {
name: HoldingTypes.Type.Ens
PropertyChanges {target: loader; sourceComponent: ensLayout}
}
]
onCurrentIndexChanged: {
if(currentIndex >= 0) {
d.currentHoldingType = root.isCollectiblesOnly ? HoldingTypes.Type.Collectible : 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.fillHeight: true
onItemChanged: d.forceLayout()
}
states: [
State {
name: HoldingsDropdown.FlowType.Selected
PropertyChanges {
target: loader
sourceComponent: {
if (d.currentHoldingType === HoldingTypes.Type.Asset)
return assetLayout
if (d.currentHoldingType === HoldingTypes.Type.Collectible)
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()
onIsCollectiblesOnlyChanged: root.reset()
onIsENSTabChanged: root.reset()
Component {
id: listLayout
ExtendedDropdownContent {
id: listPanel
assetsModel: root.assetsModel
collectiblesModel: root.collectiblesModel
noDataText: root.noDataText
checkedKeys: root.usedTokens.map(entry => entry.key)
type: d.extendedDropdownType
showAllTokensMode: d.allTokensMode
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 amount = root.usedTokens.find(entry => entry.key === key).amount
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
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,
CommunityPermissionsHelpers.getTokenNameByKey(root.collectiblesModel, d.currentItemKey),
CommunityPermissionsHelpers.getTokenIconByKey(root.collectiblesModel, d.currentItemKey),
d.currentSubItems)
}
onNavigateToMintTokenSettings: root.navigateToMintTokenSettings()
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 real effectiveAmount: amountValid ? amount : 0
tokenName: CommunityPermissionsHelpers.getTokenNameByKey(root.assetsModel, root.assetKey)
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
onEffectiveAmountChanged: root.assetAmount = effectiveAmount
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: {
if (d.assetAmountText.length === 0 && root.assetAmount)
assetPanel.setAmount(root.assetAmount)
}
}
}
Component {
id: collectibleLayout
TokenPanel {
id: collectiblePanel
readonly property real effectiveAmount: amountValid ? amount : 0
tokenName: CommunityPermissionsHelpers.getTokenNameByKey(root.collectiblesModel, root.collectibleKey)
tokenShortName: ""
tokenImage: CommunityPermissionsHelpers.getTokenIconByKey(root.collectiblesModel, root.collectibleKey)
tokenAmount: CommunityPermissionsHelpers.getTokenAmountByKey(root.collectiblesModel, root.collectibleKey)
amountText: d.collectibleAmountText
tokenCategoryText: qsTr("Collectible")
addOrUpdateButtonEnabled: d.collectiblesReady
allowDecimals: false
mode: d.effectiveHoldingMode
ListModel {
Component.onCompleted: {
const collectible = CommunityPermissionsHelpers.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.supply,
infiniteAmount: collectible.infiniteSupply
})
collectiblePanel.networksModel = this
}
}
onEffectiveAmountChanged: 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: {
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()
}
}
}