import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import Qt.labs.settings 1.1 import StatusQ 0.1 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Core.Utils 0.1 as SQUtils import StatusQ.Controls 0.1 import StatusQ.Components 0.1 import StatusQ.Popups 0.1 import StatusQ.Popups.Dialog 0.1 import StatusQ.Models 0.1 import StatusQ.Internal 0.1 import SortFilterProxyModel 0.2 import utils 1.0 import shared.stores 1.0 import shared.controls 1.0 import shared.popups 1.0 import AppLayouts.Wallet.controls 1.0 ColumnLayout { id: root // expected roles: name, symbol, balances, currencyPrice, changePct24hour, communityId, communityName, communityImage required property var controller property var currencyStore property var networkConnectionStore property var overview property bool assetDetailsLaunched: false property bool filterVisible property bool areAssetsLoading: false property string addressFilters property string networkFilters signal assetClicked(var token) signal sendRequested(string symbol) signal receiveRequested(string symbol) signal switchToCommunityRequested(string communityId) signal manageTokensRequested() spacing: 0 QtObject { id: d property int selectedAssetIndex: -1 readonly property int loadingItemsCount: 25 readonly property bool isCustomView: cmbTokenOrder.currentValue === SortOrderComboBox.TokenOrderCustom function tokenIsVisible(symbol) { // NOTE Backend returns ETH, SNT, STT and DAI by default if (!root.controller.filterAcceptsSymbol(symbol)) // explicitely hidden return false // Received tokens can have 0 balance, which indicate previosuly owned token return true // TODO handle UI threshold (#12611) } function getTotalBalance(balances, decimals, key) { let totalBalance = 0 let nwFilters = root.networkFilters.split(":") let addrFilters = root.addressFilters.split(":") for(let i=0; i 0 Behavior on Layout.preferredHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } } Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } } StatusDialogDivider { Layout.fillWidth: true } RowLayout { Layout.fillWidth: true spacing: Style.current.halfPadding StatusBaseText { color: Theme.palette.baseColor1 font.pixelSize: Style.current.additionalTextSize text: qsTr("Sort by:") } SortOrderComboBox { id: cmbTokenOrder objectName: "cmbTokenOrder" hasCustomOrderDefined: root.controller.hasSettings model: [ { value: SortOrderComboBox.TokenOrderCurrencyBalance, text: qsTr("Asset balance value"), icon: "token-sale", sortRoleName: "currentCurrencyBalance" }, // custom SFPM ExpressionRole on "enabledNetworkCurrencyBalance" amount { value: SortOrderComboBox.TokenOrderBalance, text: qsTr("Asset balance"), icon: "channel", sortRoleName: "currentBalance" }, // custom SFPM ExpressionRole on "enabledNetworkBalance" amount { value: SortOrderComboBox.TokenOrderCurrencyPrice, text: qsTr("Asset value"), icon: "token", sortRoleName: "tokenPrice" }, // custom SFPM ExpressionRole on "currencyPrice" amount { value: SortOrderComboBox.TokenOrder1DChange, text: qsTr("1d change: balance value"), icon: "history", sortRoleName: "change1DayFiat" }, // custom SFPM ExpressionRole { value: SortOrderComboBox.TokenOrderAlpha, text: qsTr("Asset name"), icon: "bold", sortRoleName: "name" }, { value: SortOrderComboBox.TokenOrderCustom, text: qsTr("Custom order"), icon: "exchange", sortRoleName: "" }, { value: SortOrderComboBox.TokenOrderNone, text: "---", icon: "", sortRoleName: "" }, // separator { value: SortOrderComboBox.TokenOrderCreateCustom, text: hasCustomOrderDefined ? qsTr("Edit custom order →") : qsTr("Create custom order →"), icon: "", sortRoleName: "" } ] onCreateOrEditRequested: { root.manageTokensRequested() } } } StatusDialogDivider { Layout.fillWidth: true } } StatusListView { id: assetsListView Layout.fillWidth: true Layout.topMargin: Style.current.padding Layout.preferredHeight: contentHeight Layout.fillHeight: true objectName: "assetViewStatusListView" model: root.areAssetsLoading ? d.loadingItemsCount : d.customSFPM delegate: delegateLoader section { property: "isCommunityAsset" delegate: Loader { width: ListView.view.width required property string section sourceComponent: section === "true" ? sectionDelegate : null } } } Component { id: sectionDelegate AssetsSectionDelegate { width: parent.width text: qsTr("Community minted") onOpenInfoPopup: Global.openPopup(communityInfoPopupCmp) } } Component { id: delegateLoader Loader { property var modelData: model property int delegateIndex: index width: ListView.view.width sourceComponent: root.areAssetsLoading ? loadingTokenDelegate : tokenDelegate } } Component { id: loadingTokenDelegate LoadingTokenDelegate { objectName: "AssetView_LoadingTokenDelegate_" + delegateIndex } } Component { id: tokenDelegate TokenDelegate { objectName: "AssetView_TokenListItem_" + (!!modelData ? modelData.symbol : "") readonly property string balance: !!modelData && !!modelData.currentBalance ? "%1".arg(modelData.currentBalance) : "" // Needed for the tests errorTooltipText_1: !!modelData && !!networkConnectionStore ? networkConnectionStore.getBlockchainNetworkDownTextForToken(modelData.balances) : "" errorTooltipText_2: !!networkConnectionStore ? networkConnectionStore.getMarketNetworkDownText() : "" subTitle: { if (!modelData || !modelData.symbol) { return "" } if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) { return "" } return LocaleUtils.currencyAmountToLocaleString(root.currencyStore.getCurrencyAmount(modelData.currentBalance, modelData.symbol)) } currencyBalance.text: { let totalCurrencyBalance = modelData && modelData.currentCurrencyBalance ? modelData.currentCurrencyBalance : 0 return currencyStore.formatCurrencyAmount(totalCurrencyBalance, currencyStore.currentCurrency) } errorMode: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCache && !networkConnectionStore.noMarketConnectionAndNoCache : false errorIcon.tooltip.text: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : "" onClicked: (itemId, mouse) => { if (mouse.button === Qt.LeftButton) { RootStore.getHistoricalDataForToken(modelData.symbol, root.currencyStore.currentCurrency) d.selectedAssetIndex = delegateIndex assetClicked(assetsListView.model.get(delegateIndex)) } else if (mouse.button === Qt.RightButton) { Global.openMenu(tokenContextMenu, this, {symbol: modelData.symbol, assetName: modelData.name, assetImage: symbolUrl, communityId: modelData.communityId, communityName: modelData.communityName, communityImage: modelData.communityImage}) } } onSwitchToCommunityRequested: root.switchToCommunityRequested(communityId) Component.onCompleted: { // on Model reset if the detail view is shown, update the data in background. if(root.assetDetailsLaunched && delegateIndex === d.selectedAssetIndex) { assetClicked(assetsListView.model.get(delegateIndex)) } } } } Component { id: tokenContextMenu StatusMenu { onClosed: destroy() property string symbol property string assetName property string assetImage property string communityId property string communityName property string communityImage StatusAction { enabled: root.networkConnectionStore.sendBuyBridgeEnabled && !root.overview.isWatchOnlyAccount && root.overview.canSend visibleOnDisabled: true icon.name: "send" text: qsTr("Send") onTriggered: root.sendRequested(symbol) } StatusAction { icon.name: "receive" text: qsTr("Receive") onTriggered: root.receiveRequested(symbol) } StatusMenuSeparator {} StatusAction { icon.name: "settings" text: qsTr("Manage tokens") onTriggered: root.manageTokensRequested() } StatusAction { enabled: symbol !== "ETH" type: StatusAction.Type.Danger icon.name: "hide" text: qsTr("Hide asset") onTriggered: Global.openConfirmHideAssetPopup(symbol, assetName, assetImage, !!communityId) } StatusAction { enabled: !!communityId type: StatusAction.Type.Danger icon.name: "hide" text: qsTr("Hide all assets from this community") onTriggered: Global.openPopup(confirmHideCommunityAssetsPopup, {communityId, communityName, communityImage}) } } } Component { id: communityInfoPopupCmp CommunityAssetsInfoPopup {} } Component { id: confirmHideCommunityAssetsPopup ConfirmationDialog { property string communityId property string communityName property string communityImage width: 520 destroyOnClose: true confirmButtonLabel: qsTr("Hide '%1' assets").arg(communityName) cancelBtnType: "" showCancelButton: true headerSettings.title: qsTr("Hide %1 community assets").arg(communityName) headerSettings.asset.name: communityImage confirmationText: qsTr("Are you sure you want to hide all community assets minted by %1? You will no longer see or be able to interact with these assets anywhere inside Status.").arg(communityName) onCancelButtonClicked: close() onConfirmButtonClicked: { root.controller.showHideGroup(communityId, false) close() Global.displayToastMessage( qsTr("%1 community assets were successfully hidden. You can toggle asset visibility via %2.").arg(communityName) .arg(`` + qsTr("Settings", "Go to Settings") + ""), "", "checkmark-circle", false, Constants.ephemeralNotificationType.success, "" ) } } } }