2023-11-22 19:58:02 +00:00
import QtQuick 2.15
import QtQuick . Controls 2.15
import QtQuick . Layouts 1.15
2023-12-06 10:54:36 +00:00
import Qt . labs . settings 1.1
2021-10-05 20:50:22 +00:00
2023-11-24 12:16:13 +00:00
import StatusQ 0.1
2022-07-13 12:29:38 +00:00
import StatusQ . Core 0.1
2022-08-24 15:47:26 +00:00
import StatusQ . Core . Theme 0.1
2023-11-24 12:16:13 +00:00
import StatusQ . Core . Utils 0.1 as SQUtils
2022-08-08 21:12:12 +00:00
import StatusQ . Controls 0.1
import StatusQ . Components 0.1
2023-11-22 19:58:02 +00:00
import StatusQ . Popups 0.1
import StatusQ . Popups . Dialog 0.1
import StatusQ . Models 0.1
import StatusQ . Internal 0.1
2022-08-08 21:12:12 +00:00
import SortFilterProxyModel 0.2
2022-07-13 12:29:38 +00:00
2021-10-05 20:50:22 +00:00
import utils 1.0
2023-11-22 19:58:02 +00:00
import shared . stores 1.0
2022-11-23 17:58:22 +00:00
import shared . controls 1.0
2023-11-22 19:58:02 +00:00
import shared . popups 1.0
2021-10-05 20:50:22 +00:00
2023-12-06 10:54:36 +00:00
import AppLayouts . Wallet . controls 1.0
ColumnLayout {
2022-08-08 21:12:12 +00:00
id: root
2023-11-24 12:16:13 +00:00
// expected roles: name, symbol, balances, currencyPrice, changePct24hour, communityId, communityName, communityImage
2023-11-22 19:58:02 +00:00
required property var assets
2023-11-24 12:16:13 +00:00
property var currencyStore
2023-03-15 09:17:25 +00:00
property var networkConnectionStore
2023-11-22 19:58:02 +00:00
property var overview
2022-08-08 21:12:12 +00:00
property bool assetDetailsLaunched: false
2023-12-06 10:54:36 +00:00
property bool filterVisible
2023-11-24 12:16:13 +00:00
property bool areAssetsLoading: false
property string addressFilters
property string networkFilters
2022-08-08 21:12:12 +00:00
signal assetClicked ( var token )
2023-11-22 19:58:02 +00:00
signal sendRequested ( string symbol )
signal receiveRequested ( string symbol )
signal switchToCommunityRequested ( string communityId )
signal manageTokensRequested ( )
2023-12-06 10:54:36 +00:00
spacing: 0
2022-08-08 21:12:12 +00:00
QtObject {
id: d
property int selectedAssetIndex: - 1
2023-11-24 12:16:13 +00:00
readonly property int loadingItemsCount: 25
2022-03-25 08:46:47 +00:00
2023-12-06 10:54:36 +00:00
readonly property bool isCustomView: cmbTokenOrder . currentValue === SortOrderComboBox . TokenOrderCustom
2021-10-05 20:50:22 +00:00
2024-01-12 11:22:02 +00:00
function tokenIsVisible ( symbol ) {
2024-01-04 12:22:12 +00:00
// NOTE Backend returns ETH, SNT, STT and DAI by default
2023-11-22 19:58:02 +00:00
if ( ! d . controller . filterAcceptsSymbol ( symbol ) ) // explicitely hidden
return false
2024-01-04 12:22:12 +00:00
// Received tokens can have 0 balance, which indicate previosuly owned token
return true // TODO handle UI threshold (#12611)
2023-12-06 10:54:36 +00:00
}
readonly property var controller: ManageTokensController {
settingsKey: "WalletAssets"
2023-11-22 19:58:02 +00:00
}
2023-12-06 10:54:36 +00:00
function hideAllCommunityTokens ( communityId ) {
2023-11-24 12:16:13 +00:00
const tokenSymbols = ModelUtils . getAll ( assetsListView . model , "symbol" , "communityId" , communityId )
2024-01-19 09:53:32 +00:00
d . controller . settingsHideGroupTokens ( communityId , tokenSymbols )
2023-12-06 10:54:36 +00:00
}
2023-11-24 12:16:13 +00:00
function getTotalBalance ( balances , decimals ) {
let totalBalance = 0
2024-01-12 11:22:02 +00:00
let nwFilters = root . networkFilters . split ( ":" )
let addrFilters = root . addressFilters . split ( ":" )
2023-11-24 12:16:13 +00:00
for ( let i = 0 ; i < balances . count ; i ++ ) {
2024-01-12 11:22:02 +00:00
let balancePerAddressPerChain = ModelUtils . get ( balances , i )
if ( nwFilters . includes ( balancePerAddressPerChain . chainId + "" ) &&
addrFilters . includes ( balancePerAddressPerChain . account ) ) {
totalBalance += SQUtils . AmountsArithmetic . toNumber ( balancePerAddressPerChain . balance , decimals )
}
2023-11-22 19:58:02 +00:00
}
2023-11-24 12:16:13 +00:00
return totalBalance
}
property SortFilterProxyModel customSFPM: SortFilterProxyModel {
2024-01-12 11:22:02 +00:00
sourceModel: root . assets
2023-11-24 12:16:13 +00:00
proxyRoles: [
FastExpressionRole {
id: filter
name: "currentBalance"
expression: d . getTotalBalance ( model . balances , model . decimals , root . addressFilters , root . networkFilters )
expectedRoles: [ "balances" , "decimals" ]
} ,
FastExpressionRole {
name: "currentCurrencyBalance"
expression: {
if ( ! model . communityId ) {
2024-01-05 15:47:58 +00:00
if ( ! ! model . marketDetails ) {
return model . currentBalance * model . marketDetails . currencyPrice . amount
}
return 0
2023-11-24 12:16:13 +00:00
}
else {
2024-01-05 11:01:56 +00:00
return model . currentBalance
2023-11-24 12:16:13 +00:00
}
}
2024-01-05 11:01:56 +00:00
expectedRoles: [ "marketDetails" , "communityId" , "currentBalance" ]
2023-11-24 12:16:13 +00:00
} ,
FastExpressionRole {
name: "tokenPrice"
expression: model . marketDetails . currencyPrice . amount
expectedRoles: [ "marketDetails" ]
} ,
2023-12-18 10:12:57 +00:00
FastExpressionRole {
name: "changePct24hour"
expression: model . marketDetails . changePct24hour
expectedRoles: [ "marketDetails" ]
} ,
2023-11-24 12:16:13 +00:00
FastExpressionRole {
name: "isCommunityAsset"
expression: ! ! model . communityId
expectedRoles: [ "communityId" ]
}
]
filters: [
2024-01-05 11:01:56 +00:00
FastExpressionFilter {
2023-11-24 12:16:13 +00:00
expression: {
d . controller . settingsDirty
2024-01-12 11:22:02 +00:00
return d . tokenIsVisible ( model . symbol )
2023-11-24 12:16:13 +00:00
}
2024-01-12 11:22:02 +00:00
expectedRoles: [ "symbol" ]
2023-11-24 12:16:13 +00:00
}
]
sorters: [
RoleSorter {
roleName: "isCommunityAsset"
} ,
2024-01-05 11:01:56 +00:00
FastExpressionSorter {
2023-11-24 12:16:13 +00:00
expression: {
d . controller . settingsDirty
return d . controller . lessThan ( modelLeft . symbol , modelRight . symbol )
}
enabled: d . isCustomView
2024-01-05 11:01:56 +00:00
expectedRoles: [ "symbol" ]
2023-11-24 12:16:13 +00:00
} ,
RoleSorter {
roleName: cmbTokenOrder . currentSortRoleName
sortOrder: cmbTokenOrder . currentSortOrder
enabled: ! d . isCustomView
}
]
}
2023-12-06 10:54:36 +00:00
}
2023-11-22 19:58:02 +00:00
2023-12-06 10:54:36 +00:00
Settings {
category: "AssetsViewSortSettings"
property alias currentSortField: cmbTokenOrder . currentIndex
property alias currentSortOrder: cmbTokenOrder . currentSortOrder
2023-01-10 13:04:23 +00:00
}
2022-08-24 15:47:26 +00:00
2023-11-22 19:58:02 +00:00
ColumnLayout {
2023-12-06 10:54:36 +00:00
Layout.fillWidth: true
Layout.preferredHeight: root . filterVisible ? implicitHeight : 0
spacing: 20
2023-12-18 10:12:57 +00:00
opacity: root . filterVisible ? 1 : 0
2023-11-22 19:58:02 +00:00
2023-12-06 10:54:36 +00:00
Behavior on Layout . preferredHeight { NumberAnimation { duration: 200 ; easing.type: Easing . InOutQuad } }
Behavior on opacity { NumberAnimation { duration: 200 ; easing.type: Easing . InOutQuad } }
2023-11-22 19:58:02 +00:00
StatusDialogDivider {
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
2023-12-06 10:54:36 +00:00
spacing: Style . current . halfPadding
2023-11-22 19:58:02 +00:00
StatusBaseText {
color: Theme . palette . baseColor1
2023-12-06 10:54:36 +00:00
font.pixelSize: Style . current . additionalTextSize
text: qsTr ( "Sort by:" )
2023-11-22 19:58:02 +00:00
}
2023-12-06 10:54:36 +00:00
SortOrderComboBox {
id: cmbTokenOrder
hasCustomOrderDefined: d . 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 . TokenOrder1WChange , text: qsTr ( "1d change: balance value" ) , icon: "history" , sortRoleName: "changePct24hour" } , // FIXME changePct1week role missing in backend!!!
{ 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 ( )
}
2023-11-22 19:58:02 +00:00
}
}
2023-12-06 10:54:36 +00:00
StatusDialogDivider {
2023-11-22 19:58:02 +00:00
Layout.fillWidth: true
}
2023-12-06 10:54:36 +00:00
}
2023-11-22 19:58:02 +00:00
2023-12-18 10:12:57 +00:00
StatusListView {
id: assetsListView
2023-12-06 10:54:36 +00:00
Layout.fillWidth: true
Layout.topMargin: Style . current . padding
2023-12-18 10:12:57 +00:00
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
2023-11-24 12:16:13 +00:00
}
}
}
Component {
id: sectionDelegate
2023-12-06 10:54:36 +00:00
ColumnLayout {
2023-12-18 10:12:57 +00:00
width: parent . width
2023-12-06 10:54:36 +00:00
spacing: 0
StatusDialogDivider {
Layout.fillWidth: true
Layout.topMargin: Style . current . padding
Layout.bottomMargin: Style . current . halfPadding
2023-11-22 19:58:02 +00:00
}
2023-12-06 10:54:36 +00:00
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style . current . padding
Layout.rightMargin: Style . current . smallPadding
Layout.bottomMargin: 4
StatusBaseText {
text: qsTr ( "Community assets" )
color: Theme . palette . baseColor1
2023-11-22 19:58:02 +00:00
}
2023-12-06 10:54:36 +00:00
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 )
2023-06-06 06:13:22 +00:00
}
}
2023-12-06 10:54:36 +00:00
}
}
Component {
id: delegateLoader
Loader {
property var modelData: model
property int delegateIndex: index
width: ListView . view . width
2023-12-18 10:12:57 +00:00
sourceComponent: root . areAssetsLoading ? loadingTokenDelegate : tokenDelegate
2023-11-22 19:58:02 +00:00
}
2023-12-06 10:54:36 +00:00
}
2023-11-22 19:58:02 +00:00
2023-12-06 10:54:36 +00:00
Component {
id: loadingTokenDelegate
LoadingTokenDelegate {
objectName: "AssetView_LoadingTokenDelegate_" + delegateIndex
}
}
Component {
id: tokenDelegate
TokenDelegate {
objectName: "AssetView_TokenListItem_" + ( ! ! modelData ? modelData.symbol : "" )
2023-12-18 10:12:57 +00:00
readonly property string balance: ! ! modelData && ! ! modelData . currentBalance ? "%1" . arg ( modelData . currentBalance ) : "" // Needed for the tests
2023-12-06 10:54:36 +00:00
errorTooltipText_1: ! ! modelData && ! ! networkConnectionStore ? networkConnectionStore . getBlockchainNetworkDownTextForToken ( modelData . balances ) : ""
errorTooltipText_2: ! ! networkConnectionStore ? networkConnectionStore . getMarketNetworkDownText ( ) : ""
subTitle: {
2023-11-24 12:16:13 +00:00
if ( ! modelData || ! modelData . symbol ) {
2023-12-06 10:54:36 +00:00
return ""
2023-11-22 19:58:02 +00:00
}
2023-12-06 10:54:36 +00:00
if ( networkConnectionStore && networkConnectionStore . noTokenBalanceAvailable ) {
return ""
2023-11-22 19:58:02 +00:00
}
2023-11-24 12:16:13 +00:00
return LocaleUtils . currencyAmountToLocaleString ( root . currencyStore . getCurrencyAmount ( modelData . currentBalance , modelData . symbol ) )
}
currencyBalance.text: {
let totalCurrencyBalance = modelData && modelData . currentCurrencyBalance ? modelData.currentCurrencyBalance : 0
return LocaleUtils . currencyAmountToLocaleString ( root . currencyStore . getCurrentCurrencyAmount ( totalCurrencyBalance ) )
2023-12-06 10:54:36 +00:00
}
errorMode: ! ! networkConnectionStore ? networkConnectionStore . noBlockchainConnectionAndNoCache && ! networkConnectionStore.noMarketConnectionAndNoCache : false
errorIcon.tooltip.text: ! ! networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : ""
onClicked: ( itemId , mouse ) = > {
if ( mouse . button === Qt . LeftButton ) {
2023-11-24 12:16:13 +00:00
RootStore . getHistoricalDataForToken ( modelData . symbol , root . currencyStore . currentCurrency )
2023-12-06 10:54:36 +00:00
d . selectedAssetIndex = delegateIndex
2023-11-24 12:16:13 +00:00
assetClicked ( assetsListView . model . get ( delegateIndex ) )
2023-12-06 10:54:36 +00:00
} 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 ) {
2023-11-24 12:16:13 +00:00
assetClicked ( assetsListView . model . get ( delegateIndex ) )
2023-11-22 19:58:02 +00:00
}
2022-08-08 21:12:12 +00:00
}
2023-11-22 19:58:02 +00:00
}
2023-12-06 10:54:36 +00:00
}
2023-11-22 19:58:02 +00:00
2023-12-06 10:54:36 +00:00
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 } )
2023-11-22 19:58:02 +00:00
}
}
2023-12-06 10:54:36 +00:00
}
2023-11-22 19:58:02 +00:00
2023-12-06 10:54:36 +00:00
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." )
2023-11-22 19:58:02 +00:00
}
}
2023-12-06 10:54:36 +00:00
}
2023-11-22 19:58:02 +00:00
2023-12-06 10:54:36 +00:00
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 ( ` < a style = "text-decoration:none" href = "#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}/${Constants.walletSettingsSubsection.manageAssets}" > ` + qsTr ( "Settings" , "Go to Settings" ) + "</a>" ) ,
"" ,
"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 ( ` < a style = "text-decoration:none" href = "#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}/${Constants.walletSettingsSubsection.manageAssets}" > ` + qsTr ( "Settings" , "Go to Settings" ) + "</a>" ) ,
"" ,
"checkmark-circle" ,
false ,
Constants . ephemeralNotificationType . success ,
""
)
2022-08-08 21:12:12 +00:00
}
2022-07-14 11:03:36 +00:00
}
2021-10-05 20:50:22 +00:00
}
}