import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 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 StatusScrollView { id: root // expected roles: name, symbol, enabledNetworkBalance, enabledNetworkCurrencyBalance, currencyPrice, changePct24hour, communityId, communityName, communityImage required property var assets property var networkConnectionStore property var overview property bool assetDetailsLaunched: false signal assetClicked(var token) signal sendRequested(string symbol) signal receiveRequested(string symbol) signal switchToCommunityRequested(string communityId) signal manageTokensRequested() contentWidth: availableWidth QtObject { id: d property int selectedAssetIndex: -1 readonly property bool isCustomView: d.controller.hasSettings // TODO add respect other predefined orders (#12517) function symbolIsVisible(symbol, balance) { if (symbol === "ETH") // always visible return true if (!d.controller.filterAcceptsSymbol(symbol)) // explicitely hidden return false if (symbol === "SNT" || symbol === "DAI") // visible by default return true return !!balance && !!balance.amount // visible with non-zero balance } readonly property var regularAssetsModel: SortFilterProxyModel { sourceModel: root.assets filters: [ ExpressionFilter { expression: { d.controller.settingsDirty return d.symbolIsVisible(model.symbol, model.enabledNetworkBalance) && !model.communityId } } // TODO add other sort/filter using ManageTokensController (#12517) ] sorters: ExpressionSorter { expression: { d.controller.settingsDirty return d.controller.lessThan(modelLeft.symbol, modelRight.symbol) } enabled: d.isCustomView } } readonly property var communityAssetsModel: SortFilterProxyModel { sourceModel: root.assets filters: [ ExpressionFilter { expression: { d.controller.settingsDirty return d.symbolIsVisible(model.symbol, model.enabledNetworkBalance) && !!model.communityId } } // TODO add other sort/filter using ManageTokensController (#12517) ] sorters: ExpressionSorter { expression: { d.controller.settingsDirty return d.controller.lessThan(modelLeft.symbol, modelRight.symbol) } enabled: d.isCustomView } } readonly property bool hasCommunityAssets: d.communityAssetsModel.count readonly property var controller: ManageTokensController { settingsKey: "WalletAssets" } function hideAllCommunityTokens(communityId) { const tokenSymbols = ModelUtils.getAll(communityAssetsModel, "symbol", "communityId", communityId) d.controller.settingsHideCommunityTokens(communityId, tokenSymbols) } } ColumnLayout { width: root.availableWidth spacing: 0 StatusListView { Layout.fillWidth: true Layout.preferredHeight: contentHeight interactive: false objectName: "assetViewStatusListView" model: d.regularAssetsModel delegate: delegateLoader } StatusDialogDivider { Layout.fillWidth: true Layout.topMargin: Style.current.padding Layout.bottomMargin: Style.current.halfPadding visible: d.hasCommunityAssets } RowLayout { Layout.fillWidth: true Layout.leftMargin: Style.current.padding Layout.rightMargin: Style.current.smallPadding Layout.bottomMargin: 4 visible: d.hasCommunityAssets StatusBaseText { text: qsTr("Community assets") color: Theme.palette.baseColor1 } Item { Layout.fillWidth: true } StatusFlatButton { Layout.preferredWidth: 32 Layout.preferredHeight: 32 icon.name: "info" textColor: Theme.palette.baseColor1 horizontalPadding: 0 verticalPadding: 0 onClicked: Global.openPopup(communityInfoPopupCmp) } } StatusListView { Layout.fillWidth: true Layout.preferredHeight: contentHeight interactive: false objectName: "communityAssetViewStatusListView" model: d.communityAssetsModel delegate: delegateLoader } Component { id: delegateLoader Loader { property var modelData: model property int index: index width: ListView.view.width sourceComponent: model.loading ? loadingTokenDelegate: tokenDelegate } } Component { id: loadingTokenDelegate LoadingTokenDelegate { objectName: "AssetView_LoadingTokenDelegate_" + index } } Component { id: tokenDelegate TokenDelegate { objectName: "AssetView_TokenListItem_" + (!!modelData ? modelData.symbol : "") readonly property string balance: !!modelData ? "%1".arg(modelData.enabledNetworkBalance.amount) : "" // Needed for the tests errorTooltipText_1: !!modelData && !!networkConnectionStore ? networkConnectionStore.getBlockchainNetworkDownTextForToken(modelData.balances) : "" errorTooltipText_2: !!networkConnectionStore ? networkConnectionStore.getMarketNetworkDownText() : "" subTitle: { if (!modelData) { return "" } if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) { return "" } return LocaleUtils.currencyAmountToLocaleString(modelData.enabledNetworkBalance) } 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, RootStore.currencyStore.currentCurrency) d.selectedAssetIndex = index assetClicked(modelData) } 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 && index === d.selectedAssetIndex) assetClicked(modelData) } } } 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 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.openPopup(confirmHideAssetPopup, {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 StatusDialog { destroyOnClose: true title: qsTr("What are community assets?") standardButtons: Dialog.Ok width: 520 contentItem: StatusBaseText { wrapMode: Text.Wrap text: qsTr("Community assets are assets that have been minted by a community. As these assets cannot be verified, always double check their origin and validity before interacting with them. If in doubt, ask a trusted member or admin of the relevant community.") } } } Component { id: confirmHideAssetPopup ConfirmationDialog { property string symbol property string assetName property string assetImage property string communityId readonly property string formattedName: assetName + (communityId ? " (" + qsTr("community asset") + ")" : "") width: 520 destroyOnClose: true confirmButtonLabel: qsTr("Hide %1").arg(assetName) cancelBtnType: "" showCancelButton: true headerSettings.title: qsTr("Hide %1").arg(formattedName) headerSettings.asset.name: assetImage confirmationText: qsTr("Are you sure you want to hide %1? You will no longer see or be able to interact with this asset anywhere inside Status.").arg(formattedName) onCancelButtonClicked: close() onConfirmButtonClicked: { d.controller.settingsHideToken(symbol) close() Global.displayToastMessage( qsTr("%1 was successfully hidden. You can toggle asset visibility via %2.").arg(formattedName) .arg(`` + qsTr("Settings", "Go to Settings") + ""), "", "checkmark-circle", false, Constants.ephemeralNotificationType.success, "" ) } } } Component { id: confirmHideCommunityAssetsPopup ConfirmationDialog { property string communityId property string communityName property string communityImage width: 520 destroyOnClose: true confirmButtonLabel: qsTr("Hide all assets minted by this community") 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: { d.hideAllCommunityTokens(communityId) 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, "" ) } } } } }