feat[UI - Wallet Stability] Create generic/reusable assets listview component

TLDR: later this should form a basic building block for a new
TokenSelector picker component, potentially replacing the current
HoldingSelector* and TokenListView components (support for collectibles
TBD as part of https://github.com/status-im/status-desktop/issues/15121)

- create reusable `TokenSelectorAssetDelegate` and `TokenSelectorView`
- add corresponding SB page, showcasing the flow/integration and the
separation of concerns between the view, adaptor and delegate layers
- add QML testcase for TokenSelectorView
- don't display crypto symbol for token balances per chain tags
- update the stores and SB pages
- add some missing formatter functions to LocaleUtils and CurrenciesStore

Fixes #14716
This commit is contained in:
Lukáš Tinkl 2024-06-12 22:43:08 +02:00 committed by Lukáš Tinkl
parent 6d96745c04
commit a12a6a4894
28 changed files with 962 additions and 134 deletions

View File

@ -30,6 +30,7 @@ SplitView {
id: d
readonly property SwapInputParamsForm swapInputParamsForm: SwapInputParamsForm {
selectedNetworkChainId: ctrlSelectedNetworkChainId.currentValue
fromTokensKey: ctrlFromTokensKey.text
fromTokenAmount: ctrlFromTokenAmount.text
toTokenKey: ctrlToTokenKey.text
@ -74,7 +75,7 @@ SplitView {
}
currencyStore: d.adaptor.currencyStore
flatNetworksModel: d.adaptor.filteredFlatNetworksModel
flatNetworksModel: d.adaptor.swapStore.flatNetworks
processedAssetsModel: d.adaptor.processedAssetsModel
tokenKey: d.swapInputParamsForm.fromTokensKey
@ -125,6 +126,22 @@ SplitView {
ColumnLayout {
anchors.fill: parent
RowLayout {
Layout.fillWidth: true
Label {
text: "Chain:"
}
ComboBox {
Layout.fillWidth: true
id: ctrlSelectedNetworkChainId
model: d.adaptor.swapStore.flatNetworks
textRole: "chainName"
valueRole: "chainId"
displayText: currentIndex === -1 ? "All chains" : currentText
currentIndex: -1 // all chains
}
}
RowLayout {
Layout.fillWidth: true
Label {

View File

@ -65,19 +65,9 @@ SplitView {
id: swapInputForm
selectedAccountIndex: accountComboBox.currentIndex
selectedNetworkChainId: d.getNetwork()
fromTokensKey: {
if (d.tokenBySymbolModel.count > 0) {
return ModelUtils.get(d.tokenBySymbolModel, fromTokenComboBox.currentIndex, "key")
}
return ""
}
fromTokensKey: fromTokenComboBox.currentValue
fromTokenAmount: swapInput.text
toTokenKey: {
if (d.tokenBySymbolModel.count > 0) {
return ModelUtils.get(d.tokenBySymbolModel, toTokenComboBox.currentIndex, "key")
}
return ""
}
toTokenKey: toTokenComboBox.currentValue
toTokenAmount: swapOutputAmount.text
}
@ -87,33 +77,34 @@ SplitView {
visible: true
modal: false
closePolicy: Popup.CloseOnEscape
destroyOnClose: true
swapInputParamsForm: swapInputForm
swapAdaptor: SwapModalAdaptor {
swapProposalLoading: loadingCheckBox.checked
swapProposalReady: swapProposalReadyCheckBox.checked
swapStore: SwapStore {
readonly property var accounts: d.accountsModel
readonly property var flatNetworks: d.flatNetworksModel
readonly property bool areTestNetworksEnabled: areTestNetworksEnabledCheckbox.checked
swapAdaptor: SwapModalAdaptor {
swapProposalLoading: loadingCheckBox.checked
swapProposalReady: swapProposalReadyCheckBox.checked
swapStore: SwapStore {
readonly property var accounts: d.accountsModel
readonly property var flatNetworks: d.flatNetworksModel
readonly property bool areTestNetworksEnabled: areTestNetworksEnabledCheckbox.checked
signal suggestedRoutesReady(var txRoutes)
signal suggestedRoutesReady(var txRoutes)
function fetchSuggestedRoutes(accountFrom, accountTo, amount, tokenFrom, tokenTo,
disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) {}
function authenticateAndTransfer(uuid, accountFrom, accountTo,
tokenFrom, tokenTo, sendType, tokenName, tokenIsOwnerToken, paths) {}
}
walletAssetsStore: WalletAssetsStore {
id: thisWalletAssetStore
walletTokensStore: TokensStore {
readonly property var plainTokensBySymbolModel: TokensBySymbolModel {}
}
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
currencyStore: CurrenciesStore {}
swapFormData: swapInputForm
function fetchSuggestedRoutes(accountFrom, accountTo, amount, tokenFrom, tokenTo,
disabledFromChainIDs, disabledToChainIDs, preferredChainIDs, sendType, lockedInAmounts) {}
function authenticateAndTransfer(uuid, accountFrom, accountTo,
tokenFrom, tokenTo, sendType, tokenName, tokenIsOwnerToken, paths) {}
}
walletAssetsStore: WalletAssetsStore {
id: thisWalletAssetStore
walletTokensStore: TokensStore {
plainTokensBySymbolModel: TokensBySymbolModel {}
}
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
currencyStore: CurrenciesStore {}
swapFormData: swapInputForm
}
}
}
}
@ -131,7 +122,7 @@ SplitView {
id: areTestNetworksEnabledCheckbox
text: "areTestNetworksEnabled"
checked: true
onCheckedChanged: networksComboBox.currentIndex = 0
onToggled: networksComboBox.currentIndex = 0
}
StatusBaseText {
@ -173,6 +164,7 @@ SplitView {
ComboBox {
id: fromTokenComboBox
textRole: "name"
valueRole: "key"
model: d.tokenBySymbolModel
currentIndex: 0
}
@ -190,6 +182,7 @@ SplitView {
ComboBox {
id: toTokenComboBox
textRole: "name"
valueRole: "key"
model: d.tokenBySymbolModel
currentIndex: 1
}
@ -197,7 +190,7 @@ SplitView {
StatusInput {
id: swapOutputAmount
Layout.preferredWidth: 100
label: "Token amount to receive"
label: "Token amount to receive"
text: "100"
}

View File

@ -0,0 +1,97 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import Storybook 1.0
import Models 1.0
import SortFilterProxyModel 0.2
import AppLayouts.Wallet.views 1.0
SplitView {
id: root
orientation: Qt.Vertical
Logs { id: logs }
Pane {
SplitView.fillWidth: true
SplitView.fillHeight: true
background: Rectangle {
color: Theme.palette.baseColor3
}
Rectangle {
width: 380
height: 200
color: Theme.palette.statusListItem.backgroundColor
border.color: Theme.palette.primaryColor1
border.width: 1
anchors.centerIn: parent
TokenSelectorAssetDelegate {
implicitWidth: 333
anchors.centerIn: parent
tokensKey: "ETH"
name: "Ethereum"
symbol: "ETH"
currencyBalanceAsString: "14,456.42 USD"
balancesModel: ListModel {
readonly property var data: [
{ chainId: 1, balanceAsString: "1234.50", iconUrl: "network/Network=Ethereum" },
{ chainId: 42161, balanceAsString: "55.91", iconUrl: "network/Network=Arbitrum" },
{ chainId: 10, balanceAsString: "45.12", iconUrl: "network/Network=Optimism" },
{ chainId: 420, balanceAsString: "1.23", iconUrl: "network/Network=Testnet" }
]
Component.onCompleted: append(data)
}
interactive: ctrlInteractive.checked
highlighted: ctrlHighlighted.checked
onAssetSelected: (tokensKey) => {
console.warn("!!! TOKEN SELECTED:", tokensKey)
logs.logEvent("TokenSelectorAssetDelegate::onTokenSelected", ["tokensKey"], arguments)
}
}
}
}
LogsAndControlsPanel {
SplitView.minimumHeight: 300
SplitView.preferredHeight: 300
logsView.logText: logs.logText
RowLayout {
anchors.fill: parent
ColumnLayout {
Switch {
id: ctrlInteractive
text: "Interactive"
checked: true
}
Switch {
id: ctrlHighlighted
text: "Highlighted"
checked: false
}
Item { Layout.fillHeight: true }
}
}
}
}
// category: Delegates

View File

@ -0,0 +1,206 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import Storybook 1.0
import Models 1.0
import SortFilterProxyModel 0.2
import AppLayouts.Wallet.views 1.0
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.adaptors 1.0
import shared.stores 1.0
import utils 1.0
SplitView {
id: root
orientation: Qt.Vertical
Logs { id: logs }
QtObject {
id: d
property var enabledChainIds: []
function addFilter(chainId) {
if (d.enabledChainIds.includes(chainId))
return
const newFilters = d.enabledChainIds.concat(chainId)
d.enabledChainIds = newFilters
}
function removeFilter(chainId) {
const newFilters = d.enabledChainIds.filter((filter) => filter !== chainId)
d.enabledChainIds = newFilters
}
function rebuildFilter() {
let newFilters = []
for (let i = 0; i < chainIdsRepeater.count; i++) {
const item = chainIdsRepeater.itemAt(i)
if (!!item && item.checked) {
newFilters.push(item.chainId)
}
}
d.enabledChainIds = newFilters
}
readonly property string enabledChainIdsString: enabledChainIds.join(":")
readonly property var flatNetworks: NetworksModel.flatNetworks
readonly property var currencyStore: CurrenciesStore {}
readonly property var assetsStore: WalletAssetsStore {
id: thisWalletAssetStore
walletTokensStore: TokensStore {
plainTokensBySymbolModel: TokensBySymbolModel {}
}
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
readonly property var walletAccountsModel: WalletAccountsModel {}
readonly property var adaptor: TokenSelectorViewAdaptor {
assetsModel: d.assetsStore.groupedAccountAssetsModel
flatNetworksModel: d.flatNetworks
enabledChainIds: d.enabledChainIds
currentCurrency: d.currencyStore.currentCurrency
accountAddress: ctrlAccount.currentValue ?? ""
showCommunityAssets: ctrlShowCommunityAssets.checked
searchString: ctrlSearch.text
}
}
Component.onCompleted: d.rebuildFilter()
Pane {
SplitView.fillWidth: true
SplitView.fillHeight: true
background: Rectangle {
color: Theme.palette.baseColor3
}
Rectangle {
width: 380
height: 200
color: Theme.palette.statusListItem.backgroundColor
border.color: Theme.palette.primaryColor1
border.width: 1
anchors.centerIn: parent
// tokensKey, name, symbol, decimals, currentCurrencyBalance (computed), marketDetails, balances -> [ chainId, address, balance, iconUrl ]
TokenSelectorView {
anchors.fill: parent
model: d.adaptor.outputAssetsModel
onTokenSelected: (tokensKey) => {
console.warn("!!! TOKEN SELECTED:", tokensKey)
logs.logEvent("TokenSelectorView::onTokenSelected", ["tokensKey"], arguments)
}
}
}
}
LogsAndControlsPanel {
SplitView.minimumHeight: 400
SplitView.preferredHeight: 400
logsView.logText: logs.logText
RowLayout {
anchors.fill: parent
ColumnLayout {
CheckBox {
id: ctrlTestNetworks
text: "Test networks enabled"
tristate: true
checkState: Qt.PartiallyChecked
onClicked: d.rebuildFilter()
}
Repeater {
id: chainIdsRepeater
model: SortFilterProxyModel {
sourceModel: d.flatNetworks
filters: ValueFilter {
roleName: "isTest"
value: ctrlTestNetworks.checked
enabled: ctrlTestNetworks.checkState !== Qt.PartiallyChecked
}
}
delegate: CheckBox {
required property int chainId
required property string chainName
required property string shortName
required property bool isEnabled
checked: isEnabled
opacity: enabled ? 1 : 0.3
text: "%1 (%2) - %3".arg(chainName).arg(shortName).arg(chainId)
onToggled: {
if (checked)
d.addFilter(chainId)
else
d.removeFilter(chainId)
}
}
}
Label {
Layout.fillWidth: true
text: "Enabled chain ids: %1".arg(d.enabledChainIdsString)
}
}
ColumnLayout {
RowLayout {
Layout.fillWidth: true
Label { text: "Search:" }
TextField {
Layout.fillWidth: true
id: ctrlSearch
placeholderText: "Token name or symbol"
}
}
Switch {
id: ctrlShowCommunityAssets
text: "Show community assets"
}
RowLayout {
Layout.fillWidth: true
Label { text: "Account:" }
ComboBox {
Layout.fillWidth: true
id: ctrlAccount
textRole: "name"
valueRole: "address"
displayText: currentIndex === -1 ? "All accounts" : currentText
model: SortFilterProxyModel {
sourceModel: d.walletAccountsModel
sorters: RoleSorter { roleName: "position" }
}
currentIndex: -1
}
}
Label {
Layout.alignment: Qt.AlignRight
text: "Selected: %1".arg(ctrlAccount.currentValue ?? "all")
}
Item { Layout.fillHeight: true }
}
}
}
}
// category: Views

View File

@ -0,0 +1,82 @@
import QtQuick 2.15
import QtTest 1.15
import Models 1.0
import AppLayouts.Wallet.views 1.0
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.adaptors 1.0
Item {
id: root
width: 600
height: 400
QtObject {
id: d
readonly property var flatNetworks: NetworksModel.flatNetworks
readonly property var assetsStore: WalletAssetsStore {
id: thisWalletAssetStore
walletTokensStore: TokensStore {
plainTokensBySymbolModel: TokensBySymbolModel {}
}
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
readonly property var adaptor: TokenSelectorViewAdaptor {
assetsModel: d.assetsStore.groupedAccountAssetsModel
flatNetworksModel: d.flatNetworks
currentCurrency: "USD"
}
}
Component {
id: componentUnderTest
TokenSelectorView {
anchors.fill: parent
model: d.adaptor.outputAssetsModel
}
}
SignalSpy {
id: signalSpy
target: controlUnderTest
signalName: "tokenSelected"
}
property TokenSelectorView controlUnderTest: null
TestCase {
name: "TokenSelectorView"
when: windowShown
function init() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
signalSpy.clear()
}
function test_basicGeometry() {
verify(!!controlUnderTest)
verify(controlUnderTest.width > 0)
verify(controlUnderTest.height > 0)
}
function test_clickEthToken() {
verify(!!controlUnderTest)
const tokensKey = "ETH"
const delegate = findChild(controlUnderTest, "tokenSelectorAssetDelegate_%1".arg(tokensKey))
verify(!!delegate)
tryCompare(delegate, "tokensKey", tokensKey)
// click the delegate, verify the signal has been fired and has the correct "tokensKey" as argument
mouseClick(delegate)
tryCompare(signalSpy, "count", 1)
compare(signalSpy.signalArguments[0][0], tokensKey)
}
}
}

View File

@ -0,0 +1,111 @@
import QtQuick 2.15
import QtTest 1.15
import Models 1.0
import StatusQ.Core.Utils 0.1
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.adaptors 1.0
Item {
id: root
width: 600
height: 400
QtObject {
id: d
readonly property var flatNetworks: NetworksModel.flatNetworks
readonly property var assetsStore: WalletAssetsStore {
id: thisWalletAssetStore
walletTokensStore: TokensStore {
plainTokensBySymbolModel: TokensBySymbolModel {}
}
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
}
Component {
id: componentUnderTest
TokenSelectorViewAdaptor {
assetsModel: d.assetsStore.groupedAccountAssetsModel
flatNetworksModel: d.flatNetworks
currentCurrency: "USD"
}
}
property TokenSelectorViewAdaptor controlUnderTest: null
TestCase {
name: "TokenSelectorViewAdaptor"
when: windowShown
function init() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
}
function test_search() {
verify(!!controlUnderTest)
const searchText = "dAi"
const originalCount = controlUnderTest.outputAssetsModel.count
controlUnderTest.searchString = searchText
// search yields 1 result
tryCompare(controlUnderTest.outputAssetsModel, "count", 1)
// resetting search string resets the view back to original count
controlUnderTest.searchString = ""
tryCompare(controlUnderTest.outputAssetsModel, "count", originalCount)
}
function test_showCommunityAssets() {
verify(!!controlUnderTest)
const originalCount = controlUnderTest.outputAssetsModel.count
// turn on showing the community assets, verify we now have more items
controlUnderTest.showCommunityAssets = true
tryVerify(() => controlUnderTest.outputAssetsModel.count > originalCount)
// turning them back off, verify we are back to the original number of items
controlUnderTest.showCommunityAssets = false
tryCompare(controlUnderTest.outputAssetsModel, "count", originalCount)
}
function test_enabledChainIds() {
verify(!!controlUnderTest)
// enable just "1" (Eth Mainnet) chain
controlUnderTest.enabledChainIds = [1]
// grab the "DAI" entry
const delegate = ModelUtils.getByKey(controlUnderTest.outputAssetsModel, "tokensKey", "DAI")
verify(!!delegate)
const origBalance = delegate.currencyBalance
// should have 0 balance
tryCompare(delegate, "currencyBalance", 0)
// re-enable all chains, DAI should again have the original balance
controlUnderTest.enabledChainIds = []
tryCompare(delegate, "currencyBalance", origBalance)
}
function test_accountAddress() {
verify(!!controlUnderTest)
// enable the "Hot wallet" account address filter (0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881)
controlUnderTest.accountAddress = "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881"
// grab the "STT" entry
const delegate = ModelUtils.getByKey(controlUnderTest.outputAssetsModel, "tokensKey", "STT")
verify(!!delegate)
// should have ~45.90 balance
fuzzyCompare(delegate.currencyBalance, 45.90, 0.01)
}
}
}

View File

@ -5,29 +5,33 @@ ListModel {
{
tokensKey: "DAI",
balances: [
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 5, balance: "0" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 10, balance: "559133758939097000" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "0" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 11155111, balance: "0" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 5, balance: "123456789123456789" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 11155111, balance: "123456789123456789" }
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "123456789123456789" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 11155111, balance: "123456789123456789" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 42161, balance: "45123456789123456789" },
]
},
{
tokensKey: "ETH",
balances: [
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 1, balance: "122082928968121891" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "1013151281976507736" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 421613, balance: "473057568699284613" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 5, balance: "307400931315122839" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "307400931315122839" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 11155111, balance: "307400931315122839" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "122082928968121891" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 421613, balance: "0" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 5, balance: "559133758939097000" }
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "559133758939097000" }
]
},
{
tokensKey: "STT",
balances: [
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 5, balance: "999999999998998500000000000016777216" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 5, balance: "1077000000000000000000" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 1, balance: "45123456789123456789" },
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "999999999998998500000000000016777216" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 10, balance: "1077000000000000000000" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "122082928968121891" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 421613, balance: "222000000000000000" },
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 11155111, balance: "559133758939097000" }

View File

@ -56,7 +56,7 @@ QtObject {
Component.onCompleted: append([
{
chainId: 1,
chainName: "Ethereum Mainnet",
chainName: "Mainnet",
blockExplorerUrl: "https://etherscan.io/",
iconUrl: "network/Network=Ethereum",
chainColor: "#627EEA",
@ -217,7 +217,6 @@ QtObject {
chainName: "Arbitrum",
iconUrl: ModelsData.networks.arbitrum,
isActive: false,
isEnabled: true,
shortName: "ARB",
chainColor: "purple",
layer: 2,
@ -337,7 +336,6 @@ QtObject {
chainName: "Arbitrum",
iconUrl: ModelsData.networks.arbitrum,
isActive: false,
isEnabled: true,
shortName: "ARB",
chainColor: "purple",
layer: 2,

View File

@ -9,7 +9,6 @@ ListModel {
readonly property string nativeSource: "native" //SourceOfTokensModel.custom
readonly property var data: [
{
key: "ETH",
name: "Ether",
@ -117,7 +116,7 @@ ListModel {
changePctDay: 0,
changePct24hour: 0,
change24hour: 0,
currencyPrice: ({amount: 0, symbol: "USD", displayDecimals: 2, stripTrailingZeroes: false})
currencyPrice: ({amount: 0.07, symbol: "USD", displayDecimals: 2, stripTrailingZeroes: false})
},
detailsLoading: false,
marketDetailsLoading: false

View File

@ -17,10 +17,10 @@ QtObject {
return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale)
}
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
function formatCurrencyAmountFromBigInt(balance, symbol, decimals, options = null) {
let bigIntBalance = SQUtils.AmountsArithmetic.fromString(balance)
let decimalBalance = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
return formatCurrencyAmount(decimalBalance, symbol)
return formatCurrencyAmount(decimalBalance, symbol, options)
}
function getFiatValue(balance, cryptoSymbol) {

View File

@ -182,7 +182,7 @@ QtObject {
let listOfChains = chainIds.split(":")
let listOfChainIds = []
for (let k =0;k<listOfChains.length;k++) {
listOfChainIds.push(SQUtils.ModelUtils.getByKey(NetworksModel.flatNetworks, "shortName", listOfChains[k], "chainId"))
listOfChainIds.push(SQUtils.ModelUtils.getByKey(flatNetworksModel, "shortName", listOfChains[k], "chainId"))
}
return listOfChainIds
}
@ -211,6 +211,9 @@ QtObject {
root.showUnPreferredChains = !root.showUnPreferredChains
}
function setRouteEnabledFromChains(chainId) {
}
function setSelectedTokenIsOwnerToken(isOwnerToken) {
}
@ -258,13 +261,11 @@ QtObject {
}
function getNetworkName(chainId) {
return SQUtils.ModelUtils.getByKey(NetworksModel.flatNetworks, "chainId", chainId, "chainName")
return SQUtils.ModelUtils.getByKey(flatNetworksModel, "chainId", chainId, "chainName")
}
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
let bigIntBalance = SQUtils.AmountsArithmetic.fromString(balance)
let decimalBalance = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
return currencyStore.formatCurrencyAmount(decimalBalance, symbol)
function formatCurrencyAmountFromBigInt(balance, symbol, decimals, options = null) {
return currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals, options)
}
// Property and methods below are used to apply advanced token management settings to the SendModal
@ -318,11 +319,9 @@ QtObject {
},
FastExpressionFilter {
expression: {
if (model.isCommunityAsset)
return true
return model.currentCurrencyBalance > balanceThresholdAmount
}
expectedRoles: ["isCommunityAsset", "currentCurrencyBalance"]
expectedRoles: ["currentCurrencyBalance"]
enabled: balanceThresholdEnabled
}
]

View File

@ -122,7 +122,7 @@ private:
m_persistentIndexes.clear();
m_persistentIndexes.reserve(count);
for (decltype(count) i = 0; i < count; i++)
for (auto i = 0; i < count; i++)
m_persistentIndexes.push_back(model->index(i, 0));
}

View File

@ -66,9 +66,8 @@ private:
void updateRoleNames();
void updateIndexes(int from, int to);
QHash<int, QByteArray> findExpectedRoles(
const QHash<int, QByteArray>& roleNames,
const QStringList& expectedRoles);
QHash<int, QByteArray> findExpectedRoles(const QHash<int, QByteArray> &roleNames,
const QStringList &expectedRoles);
QPointer<QQmlComponent> m_delegate;
QHash<int, QByteArray> m_expectedRoleNames;

View File

@ -73,19 +73,25 @@ QtObject {
function stripTrailingZeroes(numStr, locale) {
locale = locale || Qt.locale()
let regEx = locale.decimalPoint == "." ? /(\.[0-9]*[1-9])0+$|\.0*$/ : /(\,[0-9]*[1-9])0+$|\,0*$/
let regEx = locale.decimalPoint === "." ? /(\.[0-9]*[1-9])0+$|\.0*$/ : /(\,[0-9]*[1-9])0+$|\,0*$/
return numStr.replace(regEx, '$1')
}
function numberToLocaleString(num, precision = -1, locale = null) {
function numberToLocaleString(num, precision = -128 /* QLocale::FloatingPointShortest */, locale = null) {
locale = locale || Qt.locale()
if (precision === -1)
precision = fractionalPartLength(num)
return num.toLocaleString(locale, 'f', precision)
}
function currencyNumberToLocaleString(num, symbol = "", locale = null) {
locale = locale || Qt.locale()
if (typeof num === "string")
num = Number(num)
return num.toLocaleCurrencyString(locale, symbol)
}
function numberToLocaleStringInCompactForm(num, locale = null) {
locale = locale || Qt.locale()
const numberOfDigits = integralPartLength(num)

View File

@ -23,7 +23,7 @@ void LeftJoinModel::initialize(bool reset)
auto rightRoleNames = m_rightModel->roleNames();
auto leftNames = leftRoleNames.values();
QList<QByteArray> rightNames;
QByteArrayList rightNames;
if (m_rolesToJoin.empty()) {
rightNames = rightRoleNames.values();
@ -41,7 +41,7 @@ void LeftJoinModel::initialize(bool reset)
if (roles.empty()) {
qWarning().noquote()
<< QString("Role to join %1 not found in the right model!")
<< QStringLiteral("Role to join %1 not found in the right model!")
.arg(roleName);
return;
}
@ -264,11 +264,11 @@ QVariant LeftJoinModel::data(const QModelIndex& index, int role) const
m_rightModel->index(0, 0), m_rightModelJoinRole,
joinRoleLeftValue, 1, Qt::MatchExactly);
if (match.empty())
if (match.isEmpty())
return {};
m_lastUsedRightModelIndex = match.first();
return match.first().data(role - m_rightModelRolesOffset);
m_lastUsedRightModelIndex = match.constFirst();
return m_lastUsedRightModelIndex.data(role - m_rightModelRolesOffset);
}
void LeftJoinModel::classBegin()

View File

@ -6,8 +6,6 @@
#include <QQmlEngine>
#include <QQmlProperty>
#include <memory>
ObjectProxyModel::ObjectProxyModel(QObject* parent)
: QIdentityProxyModel{parent}
{
@ -184,8 +182,8 @@ QObject* ObjectProxyModel::proxyObject(int index)
rowData->insert(i.value(), model->data(model->index(index, 0), i.key()));
}
rowData->insert("index", index);
context->setContextProperty("model", rowData);
rowData->insert(QStringLiteral("index"), index);
context->setContextProperty(QStringLiteral("model"), rowData);
QObject* instance = m_delegate->create(context);
context->setParent(instance);
@ -300,7 +298,7 @@ void ObjectProxyModel::updateIndexes(int from, int to)
auto& entry = m_container[i];
if (entry.proxy)
entry.rowData->insert("index", i);
entry.rowData->insert(QStringLiteral("index"), i);
}
}
@ -308,22 +306,24 @@ QHash<int, QByteArray> ObjectProxyModel::findExpectedRoles(
const QHash<int, QByteArray> &roleNames,
const QStringList &expectedRoles)
{
if (roleNames.empty() || expectedRoles.isEmpty())
if (roleNames.isEmpty() || expectedRoles.isEmpty())
return {};
QHash<int, QByteArray> expected;
for (auto& role : expectedRoles) {
for (auto &role : expectedRoles) {
auto expectedKeys = roleNames.keys(role.toUtf8());
auto expectedKeysCount = expectedKeys.size();
if (expectedKeysCount == 1)
expected.insert(expectedKeys.first(), role.toUtf8());
else if (expectedKeysCount == 0) {
qWarning() << "Expected role not found!";
qWarning() << Q_FUNC_INFO;
qWarning() << "Expected role" << role << "not found!";
} else {
qWarning() << "Malformed source model - multiple roles found for given "
"expected role name!";
qWarning() << Q_FUNC_INFO;
qWarning()
<< "Malformed source model - multiple roles found for given expected role name!";
return {};
}
}

View File

@ -263,9 +263,12 @@ private slots:
model.setSourceModel(sourceModel);
model.setDelegate(delegate.get());
QTest::ignoreMessage(QtWarningMsg, "Expected role not found!");
const auto expRoleName = QStringLiteral("undefined");
QTest::ignoreMessage(QtWarningMsg,
QRegularExpression(QStringLiteral(".*findExpectedRoles*.")));
QTest::ignoreMessage(QtWarningMsg, QStringLiteral("Expected role \"%1\" not found!").arg(expRoleName).toLatin1());
model.setExpectedRoles({ QStringLiteral("undefined") });
model.setExpectedRoles({ expRoleName });
QCOMPARE(model.rowCount(), 3);
}

View File

@ -0,0 +1,166 @@
import QtQuick 2.15
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1
import SortFilterProxyModel 0.2
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:
- 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)
*/
// input API
required property var assetsModel
required property var flatNetworksModel
required property string currentCurrency // CurrenciesStore.currentCurrency, e.g. "USD"
// optional filter properties; empty/default values means no filtering
property var enabledChainIds: []
property string accountAddress
property bool showCommunityAssets
property string searchString
// output model
readonly property SortFilterProxyModel outputAssetsModel: SortFilterProxyModel {
sourceModel: assetsObjectProxyModel
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
}
]
// FIXME optionally sort/filter by wallet controller as well
sorters: [
RoleSorter {
roleName: "currencyBalance"
sortOrder: Qt.DescendingOrder
}
]
}
// internals
ObjectProxyModel {
id: assetsObjectProxyModel
sourceModel: root.assetsModel
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"
}
}
exposedRoles: ["balances", "currencyBalance", "currencyBalanceAsString", "balanceAsString"]
expectedRoles: ["communityId", "balances", "decimals", "marketDetails"]
}
}

View File

@ -0,0 +1 @@
TokenSelectorViewAdaptor 1.0 TokenSelectorViewAdaptor.qml

View File

@ -231,7 +231,7 @@ Control {
return root.currencyStore.formatCurrencyAmount(balance, root.currencyStore.currentCurrency)
}
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals) {
return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals, {noSymbol: true})
}
onItemSelected: {
d.setSelectedHoldingId(holdingId, holdingType)

View File

@ -22,6 +22,8 @@ QObject {
property bool swapProposalReady: false
property bool swapProposalLoading: false
property bool showCommunityTokens
// To expose the selected from and to Token from the SwapModal
readonly property var fromToken: ModelUtils.getByKey(root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel, "key", root.swapFormData.fromTokensKey)
readonly property var toToken: ModelUtils.getByKey(root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel, "key", root.swapFormData.toTokenKey)
@ -90,8 +92,8 @@ QObject {
return root.currencyStore.formatCurrencyAmount(balance, symbol, options, locale)
}
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
function formatCurrencyAmountFromBigInt(balance, symbol, decimals, options = null) {
return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals, options)
}
function getAllChainIds() {
@ -122,14 +124,13 @@ QObject {
property real displayAssetsBelowBalanceThresholdAmount: root.walletAssetsStore.walletTokensStore.getDisplayAssetsBelowBalanceThresholdDisplayAmount()
sourceModel: __assetsWithFilteredBalances
proxyRoles: [
FastExpressionRole {
name: "isCommunityAsset"
expression: !!model.communityId
expectedRoles: ["communityId"]
},
FastExpressionRole {
name: "currentBalance"
expression: __getTotalBalance(model.balances, model.decimals)
expression: {
// FIXME recalc when selectedNetworkChainId changes
root.swapFormData.selectedNetworkChainId
return __getTotalBalance(model.balances, model.decimals)
}
expectedRoles: ["balances", "decimals"]
},
FastExpressionRole {
@ -150,19 +151,16 @@ QObject {
if (!root.walletAssetsStore.assetsController.filterAcceptsSymbol(model.symbol)) // explicitely hidden
return false
if (model.isCommunityAsset) // do not show community assets
return false
if (!!model.communityId)
return root.showCommunityTokens
if (root.walletAssetsStore.walletTokensStore.displayAssetsBelowBalance)
return model.currentCurrencyBalance > processedAssetsModel.displayAssetsBelowBalanceThresholdAmount
return true
}
expectedRoles: ["symbol", "isCommunityAsset", "currentCurrencyBalance"]
expectedRoles: ["symbol", "communityId", "currentCurrencyBalance"]
}
]
// FIXME sort by assetsController instead, to have the sorting/order as in the main wallet view
// sorters: RoleSorter {
// roleName: "isCommunityAsset"
// }
}
// Internal properties and functions -----------------------------------------------------------------------------------------------------------------------------
@ -203,7 +201,7 @@ QObject {
}
readonly property LeftJoinModel joinModel: LeftJoinModel {
leftModel: submodel
rightModel: root.filteredFlatNetworksModel
rightModel: root.swapStore.flatNetworks
joinRole: "chainId"
}
@ -227,11 +225,12 @@ QObject {
}
/* Internal function to calculate total balance */
function __getTotalBalance(balances, decimals) {
function __getTotalBalance(balances, decimals, chainIds = [root.swapFormData.selectedNetworkChainId]) {
let totalBalance = 0
for(let i=0; i<balances.count; i++) {
let balancePerAddressPerChain = ModelUtils.get(balances, i)
totalBalance+=AmountsArithmetic.toNumber(balancePerAddressPerChain.balance, decimals)
if (chainIds.includes(-1) || chainIds.includes(balancePerAddressPerChain.chainId))
totalBalance += AmountsArithmetic.toNumber(balancePerAddressPerChain.balance, decimals)
}
return totalBalance
}

View File

@ -57,10 +57,10 @@ QtObject {
sourceModel: root._joinFlatTokensModel
proxyRoles: [
FastExpressionRole {
JoinRole {
name: "explorerUrl"
expression: model.blockExplorerURL + "/token/" + model.address
expectedRoles: ["blockExplorerURL", "address"]
roleNames: ["blockExplorerURL", "address"]
separator: "/token/"
},
FastExpressionRole {
function tokenIcon(symbol) {

View File

@ -0,0 +1,130 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
ItemDelegate {
id: root
objectName: "tokenSelectorAssetDelegate_" + tokensKey
required property string tokensKey
required property string name
required property string symbol
required property string currencyBalanceAsString
// expected structure: balancesModel -> model.balances submodel [chainId: int, balance: BigIntString] + flatNetworks [account:string, iconUrl: string]
required property var balancesModel
property bool interactive: true
signal assetSelected(string tokensKey)
spacing: Style.current.halfPadding
horizontalPadding: Style.current.padding
verticalPadding: 4
opacity: interactive ? 1 : 0.3
implicitWidth: ListView.view.width
implicitHeight: 60
icon.width: 32
icon.height: 32
icon.source: Constants.tokenIcon(symbol)
enabled: interactive
background: Rectangle {
radius: Style.current.radius
color: (root.interactive && root.hovered) || root.highlighted ? Theme.palette.statusListItem.highlightColor
: "transparent"
HoverHandler {
cursorShape: root.interactive ? Qt.PointingHandCursor : undefined
}
}
contentItem: RowLayout {
spacing: root.spacing
// asset icon
StatusRoundedImage {
Layout.preferredWidth: root.icon.width
Layout.preferredHeight: root.icon.height
image.source: root.icon.source
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
// name, symbol, total balance
RowLayout {
Layout.fillWidth: true
spacing: root.spacing
Item {
id: nameRow
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
StatusBaseText {
id: nameText
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: Math.min(implicitWidth, nameRow.width - symbolText.width - symbolText.anchors.leftMargin)
text: root.name
font.weight: Font.Medium
elide: Text.ElideRight
}
StatusBaseText {
id: symbolText
anchors.left: nameText.right
anchors.leftMargin: 6
anchors.verticalCenter: parent.verticalCenter
text: root.symbol
color: Theme.palette.baseColor1
}
}
StatusBaseText {
font.weight: Font.Medium
text: root.currencyBalanceAsString
}
}
// balances per network chain
StatusListView {
Layout.maximumWidth: parent.width
Layout.preferredWidth: contentWidth
Layout.preferredHeight: 22
orientation: ListView.Horizontal
spacing: root.spacing
visible: count
interactive: !root.ListView.view.moving
ScrollBar.horizontal: null
model: root.balancesModel
delegate: RowLayout {
height: ListView.view.height
spacing: 4
StatusRoundedImage {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
image.source: Style.svg("tiny/%1".arg(model.iconUrl))
}
StatusBaseText {
font.pixelSize: Theme.tertiaryTextFontSize
text: model.balanceAsString
}
}
// let the root handle the click
TapHandler {
onTapped: root.clicked()
}
}
}
}
onClicked: root.assetSelected(root.tokensKey)
}

View File

@ -0,0 +1,28 @@
import QtQuick 2.15
import StatusQ.Core 0.1
StatusListView {
id: root
// expected model structure:
// tokensKey, name, symbol, decimals, currencyBalanceAsString (computed), marketDetails, balances -> [ chainId, address, balance, iconUrl ]
// output API
signal tokenSelected(string tokensKey)
currentIndex: -1
delegate: TokenSelectorAssetDelegate {
required property var model
required property int index
tokensKey: model.tokensKey
name: model.name
symbol: model.symbol
currencyBalanceAsString: model.currencyBalanceAsString
balancesModel: model.balances
onAssetSelected: (tokensKey) => root.tokenSelected(tokensKey)
}
}

View File

@ -1,3 +1,5 @@
CollectiblesView 1.0 CollectiblesView.qml
AssetsDetailView 1.0 AssetsDetailView.qml
CollectiblesView 1.0 CollectiblesView.qml
SavedAddresses 1.0 SavedAddresses.qml
TokenSelectorAssetDelegate 1.0 TokenSelectorAssetDelegate.qml
TokenSelectorView 1.0 TokenSelectorView.qml

View File

@ -267,7 +267,7 @@ StatusDialog {
return popup.store.currencyStore.formatCurrencyAmount(balance, popup.store.currencyStore.currentCurrency)
}
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){
return popup.store.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
return popup.store.formatCurrencyAmountFromBigInt(balance, symbol, decimals, {noSymbol: true})
}
}
@ -391,7 +391,7 @@ StatusDialog {
return popup.store.currencyStore.formatCurrencyAmount(balance, popup.store.currencyStore.currentCurrency)
}
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals) {
return popup.store.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
return popup.store.formatCurrencyAmountFromBigInt(balance, symbol, decimals, {noSymbol: true})
}
}

View File

@ -14,21 +14,13 @@ QtObject {
property var _profileSectionModuleInst: profileSectionModule
function getModelIndexForKey(key) {
for (var i=0; i<currenciesModel.count; i++) {
if (currenciesModel.get(i).key === key) {
return i;
}
}
return 0;
const idx = SQUtils.ModelUtils.indexOf(currenciesModel, "key", key)
return idx === -1 ? 0 : idx
}
function getModelIndexForShortName(shortName) {
for (var i=0; i<currenciesModel.count; i++) {
if (currenciesModel.get(i).shortName === shortName) {
return i;
}
}
return 0;
const idx = SQUtils.ModelUtils.indexOf(currenciesModel, "shortName", shortName)
return idx === -1 ? 0 : idx
}
readonly property string currentCurrency: Global.appIsReady ? walletSection.currentCurrency : ""
@ -989,10 +981,10 @@ QtObject {
return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale)
}
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
function formatCurrencyAmountFromBigInt(balance, symbol, decimals, options = null) {
let bigIntBalance = SQUtils.AmountsArithmetic.fromString(balance)
let decimalBalance = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
return formatCurrencyAmount(decimalBalance, symbol)
return formatCurrencyAmount(decimalBalance, symbol, options)
}
function getFiatValue(cryptoAmount, cryptoSymbol) {

View File

@ -225,11 +225,11 @@ QtObject {
}
function getNetworkName(chainId) {
return fromNetworksModel.getNetworkName(chainId)
return fromNetworksModel.getNetworkName(chainId)
}
function updateRoutePreferredChains(chainIds) {
walletSectionSendInst.updateRoutePreferredChains(chainIds)
walletSectionSendInst.updateRoutePreferredChains(chainIds)
}
function toggleShowUnPreferredChains() {
@ -261,10 +261,8 @@ QtObject {
return walletSectionSendInst.getShortChainIds(chainShortNames)
}
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
let bigIntBalance = AmountsArithmetic.fromString(balance)
let decimalBalance = AmountsArithmetic.toNumber(bigIntBalance, decimals)
return currencyStore.formatCurrencyAmount(decimalBalance, symbol)
function formatCurrencyAmountFromBigInt(balance, symbol, decimals, options = null) {
return currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals, options)
}
// Property set from TokenLIstView and HoldingSelector to search token by name, symbol or contract address
@ -340,13 +338,11 @@ QtObject {
if (!root.walletAssetStore.assetsController.filterAcceptsSymbol(model.symbol)) // explicitely hidden
return false
if (model.isCommunityAsset)
return true
if (tokensStore.displayAssetsBelowBalance)
return model.currentCurrencyBalance > processedAssetsModel.displayAssetsBelowBalanceThresholdAmount
return true
}
expectedRoles: ["symbol", "isCommunityAsset", "currentCurrencyBalance"]
expectedRoles: ["symbol", "currentCurrencyBalance"]
}
]
sorters: RoleSorter {