2024-06-12 20:43:08 +00:00
|
|
|
import QtQuick 2.15
|
|
|
|
|
|
|
|
import StatusQ 0.1
|
|
|
|
import StatusQ.Core 0.1
|
|
|
|
import StatusQ.Core.Utils 0.1
|
|
|
|
|
|
|
|
import SortFilterProxyModel 0.2
|
|
|
|
|
2024-06-25 13:37:42 +00:00
|
|
|
import utils 1.0
|
|
|
|
|
2024-06-12 20:43:08 +00:00
|
|
|
QObject {
|
|
|
|
id: root
|
|
|
|
|
|
|
|
/**
|
|
|
|
Transforms and prepares input data (assets) for TokenSelectorView needs. The assets model is internally
|
|
|
|
joined with `flatNetworksModel` for the `balances` submodel
|
|
|
|
|
|
|
|
Expected assets model structure:
|
|
|
|
- tokensKey: string -> unique string ID of the token (asset); e.g. "ETH" or contract address
|
|
|
|
- name: string -> user visible token name (e.g. "Ethereum")
|
|
|
|
- symbol: string -> user visible token symbol (e.g. "ETH")
|
|
|
|
- decimals: int -> number of decimal places
|
|
|
|
- communityId: string -> optional; ID of the community this token belongs to, if any
|
|
|
|
- marketDetails: var -> object containing props like `currencyPrice` for the computed values below
|
|
|
|
- balances: submodel -> [ chainId:int, account:string, balance:BigIntString, iconUrl:string ]
|
|
|
|
|
|
|
|
Computed values:
|
2024-06-18 22:51:49 +00:00
|
|
|
- currentBalance: double (amount of tokens)
|
2024-06-12 20:43:08 +00:00
|
|
|
- currencyBalance: double (e.g. `1000.42` in user's fiat currency)
|
|
|
|
- currencyBalanceAsString: string (e.g. "1 000,42 CZK" formatted as a string according to the user's locale)
|
|
|
|
- balanceAsString: string (`1.42` formatted as e.g. "1,42" in user's locale)
|
2024-06-25 13:37:42 +00:00
|
|
|
- iconSource: string
|
2024-06-12 20:43:08 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
// input API
|
|
|
|
required property var assetsModel
|
2024-06-25 13:37:42 +00:00
|
|
|
|
|
|
|
// expected roles: key, name, symbol, image, communityId
|
|
|
|
property var plainTokensBySymbolModel // optional all tokens model, no balances
|
|
|
|
|
|
|
|
// expected roles: chainId, chainName, iconUrl
|
2024-06-12 20:43:08 +00:00
|
|
|
required property var flatNetworksModel
|
2024-06-25 13:37:42 +00:00
|
|
|
|
|
|
|
// CurrenciesStore.currentCurrency, e.g. "USD"
|
|
|
|
required property string currentCurrency
|
2024-06-12 20:43:08 +00:00
|
|
|
|
|
|
|
// optional filter properties; empty/default values means no filtering
|
2024-06-25 13:37:42 +00:00
|
|
|
property bool showAllTokens // whether to show all tokens, or just the ones we own
|
2024-06-12 20:43:08 +00:00
|
|
|
property var enabledChainIds: []
|
|
|
|
property string accountAddress
|
|
|
|
property bool showCommunityAssets
|
|
|
|
property string searchString
|
|
|
|
|
|
|
|
// output model
|
|
|
|
readonly property SortFilterProxyModel outputAssetsModel: SortFilterProxyModel {
|
2024-07-09 22:10:13 +00:00
|
|
|
|
|
|
|
objectName: "TokenSelectorViewAdaptor_outputAssetsModel"
|
|
|
|
|
2024-06-25 13:37:42 +00:00
|
|
|
sourceModel: showAllTokens && !!plainTokensBySymbolModel ? concatModel : assetsObjectProxyModel
|
|
|
|
|
|
|
|
proxyRoles: [
|
|
|
|
FastExpressionRole {
|
|
|
|
name: "sectionId"
|
|
|
|
expression: {
|
|
|
|
if (!model.currentBalance)
|
2024-07-01 23:44:53 +00:00
|
|
|
return d.favoritesSectionId
|
2024-06-25 13:37:42 +00:00
|
|
|
|
|
|
|
if (root.enabledChainIds.length === 1)
|
|
|
|
return "section_%1".arg(root.enabledChainIds[0])
|
|
|
|
}
|
|
|
|
expectedRoles: ["currentBalance"]
|
|
|
|
},
|
|
|
|
FastExpressionRole {
|
|
|
|
name: "sectionName"
|
|
|
|
function getSectionName(sectionId, hasBalance) {
|
2024-07-01 23:44:53 +00:00
|
|
|
if (sectionId === d.favoritesSectionId)
|
2024-06-25 13:37:42 +00:00
|
|
|
return qsTr("Popular assets")
|
|
|
|
|
|
|
|
if (root.enabledChainIds.length === 1 && hasBalance)
|
|
|
|
return qsTr("Your assets on %1").arg(ModelUtils.getByKey(root.flatNetworksModel, "chainId", root.enabledChainIds[0], "chainName"))
|
|
|
|
}
|
|
|
|
expression: getSectionName(model.sectionId, !!model.currentBalance)
|
|
|
|
expectedRoles: ["sectionId", "currentBalance"]
|
|
|
|
},
|
|
|
|
FastExpressionRole {
|
|
|
|
function tokenIcon(symbol) {
|
|
|
|
return Constants.tokenIcon(symbol)
|
|
|
|
}
|
|
|
|
name: "iconSource"
|
|
|
|
expression: model.image || tokenIcon(model.symbol)
|
|
|
|
expectedRoles: ["image", "symbol"]
|
|
|
|
}
|
|
|
|
]
|
2024-06-12 20:43:08 +00:00
|
|
|
|
|
|
|
filters: [
|
|
|
|
AnyOf {
|
|
|
|
RegExpFilter {
|
|
|
|
roleName: "name"
|
|
|
|
pattern: root.searchString
|
|
|
|
caseSensitivity: Qt.CaseInsensitive
|
|
|
|
}
|
|
|
|
RegExpFilter {
|
|
|
|
roleName: "symbol"
|
|
|
|
pattern: root.searchString
|
|
|
|
caseSensitivity: Qt.CaseInsensitive
|
|
|
|
}
|
|
|
|
},
|
|
|
|
ValueFilter {
|
|
|
|
roleName: "communityId"
|
|
|
|
value: ""
|
|
|
|
enabled: !root.showCommunityAssets
|
2024-07-01 23:45:44 +00:00
|
|
|
},
|
|
|
|
// duplicate tokens filter
|
|
|
|
FastExpressionFilter {
|
|
|
|
function hasDuplicateKey(tokensKey) {
|
|
|
|
return ModelUtils.indexOf(assetsObjectProxyModel, "tokensKey", tokensKey) > -1
|
|
|
|
}
|
|
|
|
|
|
|
|
expression: {
|
|
|
|
if (model.which_model === "plain_tokens_model") {
|
|
|
|
return !hasDuplicateKey(model.tokensKey)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
expectedRoles: ["which_model", "tokensKey"]
|
|
|
|
enabled: root.showAllTokens
|
2024-07-03 16:29:30 +00:00
|
|
|
},
|
|
|
|
// remove tokens not available on selected network(s)
|
|
|
|
FastExpressionFilter {
|
|
|
|
function isPresentOnEnabledNetworks(addressPerChain) {
|
|
|
|
if(!addressPerChain)
|
2024-07-10 18:35:24 +00:00
|
|
|
return true
|
2024-07-03 16:29:30 +00:00
|
|
|
return !!ModelUtils.getFirstModelEntryIf(
|
|
|
|
addressPerChain,
|
|
|
|
(addPerChain) => {
|
|
|
|
return root.enabledChainIds.includes(addPerChain.chainId)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
expression: isPresentOnEnabledNetworks(model.addressPerChain)
|
|
|
|
expectedRoles: ["addressPerChain"]
|
2024-07-09 22:10:13 +00:00
|
|
|
enabled: root.enabledChainIds.length
|
2024-06-12 20:43:08 +00:00
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
sorters: [
|
2024-06-18 22:51:49 +00:00
|
|
|
RoleSorter {
|
|
|
|
roleName: "sectionId"
|
|
|
|
},
|
2024-06-25 13:37:42 +00:00
|
|
|
FastExpressionSorter {
|
|
|
|
expression: {
|
2024-07-01 23:44:53 +00:00
|
|
|
if (modelLeft.sectionId === d.favoritesSectionId && modelRight.sectionId === d.favoritesSectionId)
|
2024-06-25 13:37:42 +00:00
|
|
|
return 0
|
|
|
|
|
|
|
|
const lhs = modelLeft.currencyBalance
|
|
|
|
const rhs = modelRight.currencyBalance
|
|
|
|
if (lhs < rhs)
|
|
|
|
return 1
|
|
|
|
else if (lhs > rhs)
|
|
|
|
return -1
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
expectedRoles: ["currencyBalance", "sectionId"]
|
2024-06-18 22:51:49 +00:00
|
|
|
},
|
|
|
|
RoleSorter {
|
|
|
|
roleName: "name"
|
2024-06-12 20:43:08 +00:00
|
|
|
}
|
2024-06-25 13:37:42 +00:00
|
|
|
// FIXME #15277 sort by assetsController instead, to have the sorting/order as in the main wallet view
|
2024-06-12 20:43:08 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
// internals
|
2024-07-01 23:44:53 +00:00
|
|
|
QtObject {
|
|
|
|
id: d
|
|
|
|
|
|
|
|
readonly property string favoritesSectionId: "section_zzz"
|
|
|
|
}
|
|
|
|
|
2024-06-25 13:37:42 +00:00
|
|
|
RolesRenamingModel {
|
|
|
|
id: renamedTokensBySymbolModel
|
2024-07-03 11:47:05 +00:00
|
|
|
sourceModel: root.plainTokensBySymbolModel || null
|
2024-06-25 13:37:42 +00:00
|
|
|
mapping: [
|
|
|
|
RoleRename {
|
|
|
|
from: "key"
|
|
|
|
to: "tokensKey"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
ConcatModel {
|
|
|
|
id: concatModel
|
|
|
|
sources: [
|
|
|
|
SourceModel {
|
|
|
|
model: renamedTokensBySymbolModel
|
|
|
|
markerRoleValue: "plain_tokens_model"
|
|
|
|
},
|
|
|
|
SourceModel {
|
|
|
|
model: assetsObjectProxyModel
|
|
|
|
markerRoleValue: "wallet_assets_model"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
markerRoleName: "which_model"
|
|
|
|
expectedRoles: ["tokensKey", "name", "symbol", "balances", "currentBalance", "currencyBalance", "currencyBalanceAsString", "communityId", "marketDetails"]
|
|
|
|
}
|
|
|
|
|
2024-06-12 20:43:08 +00:00
|
|
|
ObjectProxyModel {
|
|
|
|
id: assetsObjectProxyModel
|
|
|
|
sourceModel: root.assetsModel
|
|
|
|
|
2024-07-09 22:10:13 +00:00
|
|
|
objectName: "TokenSelectorViewAdaptor_assetsObjectProxyModel"
|
|
|
|
|
2024-06-12 20:43:08 +00:00
|
|
|
delegate: SortFilterProxyModel {
|
|
|
|
id: delegateRoot
|
|
|
|
|
|
|
|
// properties exposed as roles to the top-level model
|
|
|
|
readonly property int decimals: model.decimals
|
|
|
|
readonly property double currentBalance: aggregator.value
|
|
|
|
readonly property double currencyBalance: {
|
|
|
|
if (!!model.marketDetails) {
|
|
|
|
return currentBalance * model.marketDetails.currencyPrice.amount
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
readonly property int displayDecimals: !!model.marketDetails ? model.marketDetails.currencyPrice.displayDecimals : 0
|
|
|
|
readonly property string currencyBalanceAsString:
|
|
|
|
currencyBalance ? LocaleUtils.currencyAmountToLocaleString({amount: currencyBalance, symbol: root.currentCurrency, displayDecimals})
|
|
|
|
: ""
|
|
|
|
|
|
|
|
readonly property var balances: this
|
|
|
|
|
|
|
|
sourceModel: joinModel
|
|
|
|
|
|
|
|
proxyRoles: [
|
|
|
|
FastExpressionRole {
|
|
|
|
name: "balanceAsDouble"
|
|
|
|
function balanceToDouble(balance: string, decimals: int) {
|
|
|
|
if (typeof balance !== 'string')
|
|
|
|
return 0
|
|
|
|
let bigIntBalance = AmountsArithmetic.fromString(balance)
|
|
|
|
return AmountsArithmetic.toNumber(bigIntBalance, decimals)
|
|
|
|
}
|
|
|
|
expression: balanceToDouble(model.balance, delegateRoot.decimals)
|
|
|
|
expectedRoles: ["balance"]
|
|
|
|
},
|
|
|
|
FastExpressionRole {
|
|
|
|
name: "balanceAsString"
|
|
|
|
function convert(amount: double) {
|
|
|
|
return LocaleUtils.currencyAmountToLocaleString({amount, displayDecimals: 2}, {noSymbol: true})
|
|
|
|
}
|
|
|
|
|
|
|
|
expression: convert(model.balanceAsDouble)
|
|
|
|
expectedRoles: ["balanceAsDouble"]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
filters: [
|
|
|
|
ValueFilter {
|
|
|
|
roleName: "balance"
|
|
|
|
value: "0"
|
|
|
|
inverted: true
|
|
|
|
},
|
|
|
|
FastExpressionFilter {
|
|
|
|
expression: root.enabledChainIds.includes(model.chainId)
|
|
|
|
expectedRoles: ["chainId"]
|
|
|
|
enabled: root.enabledChainIds.length
|
|
|
|
},
|
|
|
|
RegExpFilter {
|
|
|
|
roleName: "account"
|
|
|
|
pattern: root.accountAddress
|
|
|
|
caseSensitivity: Qt.CaseInsensitive
|
|
|
|
enabled: root.accountAddress !== ""
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
sorters: [
|
|
|
|
// sort by biggest (sub)balance first
|
|
|
|
RoleSorter {
|
|
|
|
roleName: "balanceAsDouble"
|
|
|
|
sortOrder: Qt.DescendingOrder
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
readonly property LeftJoinModel joinModel: LeftJoinModel {
|
|
|
|
leftModel: model.balances
|
|
|
|
rightModel: root.flatNetworksModel
|
|
|
|
joinRole: "chainId"
|
|
|
|
}
|
|
|
|
|
|
|
|
readonly property SumAggregator aggregator: SumAggregator {
|
|
|
|
model: delegateRoot
|
|
|
|
roleName: "balanceAsDouble"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-25 13:37:42 +00:00
|
|
|
exposedRoles: ["balances", "currentBalance", "currencyBalance", "currencyBalanceAsString", "balanceAsString"]
|
2024-06-12 20:43:08 +00:00
|
|
|
expectedRoles: ["communityId", "balances", "decimals", "marketDetails"]
|
|
|
|
}
|
|
|
|
}
|