feat(Wallet): AssetsViewAdaptor added for preparing data for AssetsView

This commit is contained in:
Michał Cieślak 2024-06-13 14:17:33 +02:00 committed by Michał
parent 4f24ee0422
commit b12caa3f9a
4 changed files with 480 additions and 0 deletions

View File

@ -0,0 +1,275 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Models 0.1
import Storybook 1.0
import utils 1.0
import shared.views 1.0
Item {
id: root
ListModel {
id: listModel
readonly property var data: [
{
tokensKey: "key_ETH",
name: "Ether",
symbol: "ETH",
balances: [
{
chainId: "chain_id_1",
balance: "186316672770338050",
account: "account_1",
},
{
chainId: "chain_id_1",
balance: "386318672772348050",
account: "account_2",
},
{
chainId: "chain_id_2",
balance: "186311232772348990",
account: "account_1",
},
{
chainId: "chain_id_2",
balance: "986317232772348990",
account: "account_1",
}
],
decimals: 18,
communityId: "",
communityName: "",
communityImage: Qt.resolvedUrl(""),
marketDetails: {
changePct24hour: -2.1232,
currencyPrice: {
amount: 3423.23898
}
},
detailsLoading: false,
image: Qt.resolvedUrl("")
},
{
tokensKey: "key_SNT",
name: "Status",
symbol: "SNT",
balances: [
{
chainId: "chain_id_1",
balance: "386316672770338850",
account: "account_1",
},
{
chainId: "chain_id_1",
balance: "377778672772348050",
account: "account_2",
},
{
chainId: "chain_id_2",
balance: "146311232772348990",
account: "account_1",
},
{
chainId: "chain_id_3",
balance: "86317232772348990",
account: "account_1",
}
],
decimals: 18,
communityId: "",
communityName: "",
communityImage: Qt.resolvedUrl(""),
marketDetails: {
changePct24hour: 9.232,
currencyPrice: {
amount: 33.23898
}
},
detailsLoading: false,
image: Qt.resolvedUrl("")
},
{
tokensKey: "key_MYASST",
name: "Community Asset",
symbol: "MYASST",
balances: [
{
chainId: "chain_id_1",
balance: "23234",
account: "account_1",
},
{
chainId: "chain_id_1",
balance: "63234",
account: "account_2",
}
],
decimals: 3,
communityId: "0x033f36ccb",
communityName: "My Community",
communityImage: Constants.tokenIcon("DAI", false),
marketDetails: {
changePct24hour: 0,
currencyPrice: {
amount: 0
}
},
detailsLoading: false,
image: Constants.tokenIcon("ZRX", false)
}
]
Component.onCompleted: {
append(data)
const chains = new Set()
const accounts = new Set()
data.forEach(e => e.balances.forEach(
e => { chains.add(e.chainId);
accounts.add(e.account)}))
chainsSelector.model = [...chains.values()]
chainsDownSelector.model = [...chains.values()]
accountsSelector.model = [...accounts.values()]
}
}
ManageTokensController {
id: manageTokensController
sourceModel: listModel
serializeAsCollectibles: false
onRequestLoadSettings: {
loadingStarted()
const jsonData = [
{
"key": "ETH",
"position": 1,
"visible": true
},
{
"key": "SNT",
"position": 2,
"visible": true
},
{
"key": "MYASST",
"position": 5,
"visible": true
}
]
loadingFinished(JSON.stringify(jsonData))
}
}
AssetsViewAdaptor {
id: adaptor
controller: manageTokensController
chains: chainsSelector.selection
accounts: accountsSelector.selection
marketValueThreshold: minimumBalanceSlider.value
chainsError: chains => {
const chainsDown = chainsDownSelector.selection
const downForToken = chains.filter(value => chainsDown.includes(value))
if (downForToken.length)
return "Chains down: " + JSON.stringify(downForToken)
return ""
}
tokensModel: listModel
}
ColumnLayout {
anchors.fill: parent
Label { text: "CHAINS:" }
CheckBoxFlowSelector {
id: chainsSelector
Layout.fillWidth: true
initialSelection: true
}
Label { text: "CHAINS DOWN:" }
CheckBoxFlowSelector {
id: chainsDownSelector
Layout.fillWidth: true
}
Label { text: "ACCOUNTS:" }
CheckBoxFlowSelector {
id: accountsSelector
Layout.fillWidth: true
initialSelection: true
}
Label { text: "MINIMUM BALANCE:" }
RowLayout {
Slider {
id: minimumBalanceSlider
from: 0.1
to: 100
value: 10
}
Label {
text: minimumBalanceSlider.value
}
}
RowLayout {
GenericListView {
label: "Input model"
model: listModel
Layout.fillWidth: true
Layout.fillHeight: true
skipEmptyRoles: true
}
GenericListView {
label: "Adapter's output model"
model: adaptor.model
Layout.fillWidth: true
Layout.fillHeight: true
roles:
["key", "symbol", "name", "icon", "error", "balance", "balanceText",
"marketDetailsAvailable", "marketDetailsLoading",
"marketPrice", "marketChangePct24hour", "communityId",
"communityName", "communityIcon", "position", "canBeHidden"]
skipEmptyRoles: true
}
}
}
}
// category: Adaptors

View File

@ -95,6 +95,8 @@ QtObject {
/* This model joins the "Tokens by symbol model combined with Community details" /* This model joins the "Tokens by symbol model combined with Community details"
and "Grouped Account Assets Model" by tokenskey */ and "Grouped Account Assets Model" by tokenskey */
property LeftJoinModel groupedAccountAssetsModel: LeftJoinModel { property LeftJoinModel groupedAccountAssetsModel: LeftJoinModel {
objectName: "groupedAccountAssetsModel"
leftModel: root.baseGroupedAccountAssetModel leftModel: root.baseGroupedAccountAssetModel
rightModel: _jointTokensBySymbolModel rightModel: _jointTokensBySymbolModel
joinRole: "tokensKey" joinRole: "tokensKey"

View File

@ -0,0 +1,202 @@
import QtQml 2.15
import StatusQ 0.1
import StatusQ.Models 0.1
import StatusQ.Core.Utils 0.1
import utils 1.0
import SortFilterProxyModel 0.2
QObject {
id: root
// Controller providing information about visibility and order defined
// by a user (token management)
required property ManageTokensController controller
/**
Expected model structure:
Tokens related part:
tokensKey [string] - unique identifier of a token, e.g "0x3234235"
symbol [string] - token's symbol e.g. "ETH" or "SNT"
name [string] - token's name e.g. "Ether" or "Dai"
image [url] - token's icon for custom tokens
decimals [int] - number of decimal places, e.g. 18 for ETH
balances [model] - submodel of balances per chain/account
chainId [string] - unique identifier of a chain
account [string] - unique identifier of an account
balance [string] - balance in basic unit as big integer string
marketDetails [object] - object holding market details
changePct24hour [double] - percentage change of fiat price in last day
currencyPrice [object] - object holding fiat price details
amount [double] - fiat prace of 1 logical unit of cryptocurrency
detailsLoading [bool] - indicatator if market details are ready to use
Community related part (relevant for community minted assets, empty otherwise):
communityId [string] - unique identifier of a community, e.g. "0x6734235"
communityName [string] - name of a community e.g. "Crypto Kitties"
communityImage [url] - community's icon url
**/
property var tokensModel
// function formatting tokens balance expressed in a commonly used units,
// e.g. 1.2 for 1.2 ETH, according to rules specific for given symbol
property var formatBalance:
(balance, symbol) => `${balance.toLocaleString(Qt.locale())} ${symbol}`
// function providing error message per token depending on used chains,
// should return empty string if no error found
property var chainsError: chains => ""
// list of chain identifiers used for balance calculation
property var chains: []
// list of accounts used for balance calculation
property var accounts: []
// threshold below which the token is omitted from the output model
property double marketValueThreshold
/**
Model structure:
All roles from the source model are passed directly to the output model,
additionally:
key [string] - renamed from tokensKey
icon [url] - from image or fetched by symbol for well-known tokens
balance [double] - tokens balance is the commonly used unit, e.g. 1.2 for 1.2 ETH,
computed from balances according to provided criteria
balanceText [string] - formatted and localized balance
error [string] - error message related to balance
marketDetailsAvailable [bool] - specifies if market datails are available for given token
marketDetailsLoading [bool] - specifies if market datails are available for given token
marketPrice [double] - specifies market price in currently used currency
marketChangePct24hour [double] - percentage price change in last 24 hours, e.g. 0.5 for 0.5% of price change
position [int] - if custom order available, display position fetched from ManageTokensController
canBeHidden [bool] - specifies if given token can be hidden (e.g. ETH should be always visible)
communityIcon [url] - renamed from communityImage
**/
readonly property alias model: sfpm
ObjectProxyModel {
id: proxyModel
sourceModel: root.tokensModel ?? null
delegate: QObject {
readonly property var rootModel: model
readonly property bool hasCommunityId: !!model.communityId
readonly property var marketDetails: model.marketDetails
// Read-only roles exposed to the model:
readonly property string key: model.tokensKey
readonly property string error:
root.chainsError(chainsAggregator.uniqueChains)
readonly property double balance:
AmountsArithmetic.toNumber(totalBalanceAggregator.value, model.decimals)
readonly property string balanceText: root.formatBalance(balance, model.symbol)
readonly property bool marketDetailsAvailable: !hasCommunityId
readonly property bool marketDetailsLoading: model.detailsLoading
readonly property real marketPrice: marketDetails.currencyPrice.amount ?? 0
readonly property real marketChangePct24hour: marketDetails.changePct24hour ?? 0
readonly property int position: {
controller.revision
return controller.order(model.symbol)
}
readonly property bool visible: {
root.controller.revision
if (!root.controller.filterAcceptsSymbol(model.symbol))
return false
if (hasCommunityId)
return true
return balance * marketPrice >= root.marketValueThreshold
}
readonly property url icon:
!!model.image ? model.image
: Constants.tokenIcon(model.symbol, false)
readonly property url communityIcon: model.communityImage ?? ""
readonly property bool canBeHidden: model.symbol !== Constants.ethToken
SortFilterProxyModel {
id: filteredBalances
sourceModel: rootModel.balances
filters: [
FastExpressionFilter {
expression: root.chains.includes(model.chainId)
expectedRoles: ["chainId"]
},
FastExpressionFilter {
expression: root.accounts.includes(model.account)
expectedRoles: ["account"]
}
]
}
FunctionAggregator {
id: totalBalanceAggregator
model: filteredBalances
initialValue: "0"
roleName: "balance"
aggregateFunction: (aggr, value) => AmountsArithmetic.sum(
AmountsArithmetic.fromString(aggr),
AmountsArithmetic.fromString(value)).toString()
}
FunctionAggregator {
id: chainsAggregator
readonly property var uniqueChains: [...new Set(value).values()]
model: filteredBalances
initialValue: []
roleName: "chainId"
aggregateFunction: (aggr, value) => [...aggr, value]
}
}
expectedRoles:
["tokensKey", "symbol", "image", "balances", "decimals",
"detailsLoading", "marketDetails", "communityId", "communityImage"]
exposedRoles:
["key", "error", "balance", "balanceText", "position", "icon",
"visible", "canBeHidden", "marketDetailsAvailable", "marketDetailsLoading",
"marketPrice", "marketChangePct24hour", "communityIcon"]
}
SortFilterProxyModel {
id: sfpm
sourceModel: proxyModel
filters: ValueFilter {
roleName: "visible"
value: true
}
}
}

View File

@ -1,5 +1,6 @@
AssetContextMenu 1.0 AssetContextMenu.qml AssetContextMenu 1.0 AssetContextMenu.qml
AssetsView 1.0 AssetsView.qml AssetsView 1.0 AssetsView.qml
AssetsViewAdaptor 1.0 AssetsViewAdaptor.qml
AssetsViewNew 1.0 AssetsViewNew.qml AssetsViewNew 1.0 AssetsViewNew.qml
ConfirmHideAssetPopup 1.0 ConfirmHideAssetPopup.qml ConfirmHideAssetPopup 1.0 ConfirmHideAssetPopup.qml
ConfirmHideCommunityAssetsPopup 1.0 ConfirmHideCommunityAssetsPopup.qml ConfirmHideCommunityAssetsPopup 1.0 ConfirmHideCommunityAssetsPopup.qml