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:
parent
6d96745c04
commit
a12a6a4894
|
@ -30,6 +30,7 @@ SplitView {
|
||||||
id: d
|
id: d
|
||||||
|
|
||||||
readonly property SwapInputParamsForm swapInputParamsForm: SwapInputParamsForm {
|
readonly property SwapInputParamsForm swapInputParamsForm: SwapInputParamsForm {
|
||||||
|
selectedNetworkChainId: ctrlSelectedNetworkChainId.currentValue
|
||||||
fromTokensKey: ctrlFromTokensKey.text
|
fromTokensKey: ctrlFromTokensKey.text
|
||||||
fromTokenAmount: ctrlFromTokenAmount.text
|
fromTokenAmount: ctrlFromTokenAmount.text
|
||||||
toTokenKey: ctrlToTokenKey.text
|
toTokenKey: ctrlToTokenKey.text
|
||||||
|
@ -74,7 +75,7 @@ SplitView {
|
||||||
}
|
}
|
||||||
|
|
||||||
currencyStore: d.adaptor.currencyStore
|
currencyStore: d.adaptor.currencyStore
|
||||||
flatNetworksModel: d.adaptor.filteredFlatNetworksModel
|
flatNetworksModel: d.adaptor.swapStore.flatNetworks
|
||||||
processedAssetsModel: d.adaptor.processedAssetsModel
|
processedAssetsModel: d.adaptor.processedAssetsModel
|
||||||
|
|
||||||
tokenKey: d.swapInputParamsForm.fromTokensKey
|
tokenKey: d.swapInputParamsForm.fromTokensKey
|
||||||
|
@ -125,6 +126,22 @@ SplitView {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
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 {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Label {
|
Label {
|
||||||
|
|
|
@ -65,19 +65,9 @@ SplitView {
|
||||||
id: swapInputForm
|
id: swapInputForm
|
||||||
selectedAccountIndex: accountComboBox.currentIndex
|
selectedAccountIndex: accountComboBox.currentIndex
|
||||||
selectedNetworkChainId: d.getNetwork()
|
selectedNetworkChainId: d.getNetwork()
|
||||||
fromTokensKey: {
|
fromTokensKey: fromTokenComboBox.currentValue
|
||||||
if (d.tokenBySymbolModel.count > 0) {
|
|
||||||
return ModelUtils.get(d.tokenBySymbolModel, fromTokenComboBox.currentIndex, "key")
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
fromTokenAmount: swapInput.text
|
fromTokenAmount: swapInput.text
|
||||||
toTokenKey: {
|
toTokenKey: toTokenComboBox.currentValue
|
||||||
if (d.tokenBySymbolModel.count > 0) {
|
|
||||||
return ModelUtils.get(d.tokenBySymbolModel, toTokenComboBox.currentIndex, "key")
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
toTokenAmount: swapOutputAmount.text
|
toTokenAmount: swapOutputAmount.text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +77,7 @@ SplitView {
|
||||||
visible: true
|
visible: true
|
||||||
modal: false
|
modal: false
|
||||||
closePolicy: Popup.CloseOnEscape
|
closePolicy: Popup.CloseOnEscape
|
||||||
|
destroyOnClose: true
|
||||||
swapInputParamsForm: swapInputForm
|
swapInputParamsForm: swapInputForm
|
||||||
swapAdaptor: SwapModalAdaptor {
|
swapAdaptor: SwapModalAdaptor {
|
||||||
swapProposalLoading: loadingCheckBox.checked
|
swapProposalLoading: loadingCheckBox.checked
|
||||||
|
@ -106,7 +97,7 @@ SplitView {
|
||||||
walletAssetsStore: WalletAssetsStore {
|
walletAssetsStore: WalletAssetsStore {
|
||||||
id: thisWalletAssetStore
|
id: thisWalletAssetStore
|
||||||
walletTokensStore: TokensStore {
|
walletTokensStore: TokensStore {
|
||||||
readonly property var plainTokensBySymbolModel: TokensBySymbolModel {}
|
plainTokensBySymbolModel: TokensBySymbolModel {}
|
||||||
}
|
}
|
||||||
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
|
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
|
||||||
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
|
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
|
||||||
|
@ -131,7 +122,7 @@ SplitView {
|
||||||
id: areTestNetworksEnabledCheckbox
|
id: areTestNetworksEnabledCheckbox
|
||||||
text: "areTestNetworksEnabled"
|
text: "areTestNetworksEnabled"
|
||||||
checked: true
|
checked: true
|
||||||
onCheckedChanged: networksComboBox.currentIndex = 0
|
onToggled: networksComboBox.currentIndex = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusBaseText {
|
StatusBaseText {
|
||||||
|
@ -173,6 +164,7 @@ SplitView {
|
||||||
ComboBox {
|
ComboBox {
|
||||||
id: fromTokenComboBox
|
id: fromTokenComboBox
|
||||||
textRole: "name"
|
textRole: "name"
|
||||||
|
valueRole: "key"
|
||||||
model: d.tokenBySymbolModel
|
model: d.tokenBySymbolModel
|
||||||
currentIndex: 0
|
currentIndex: 0
|
||||||
}
|
}
|
||||||
|
@ -190,6 +182,7 @@ SplitView {
|
||||||
ComboBox {
|
ComboBox {
|
||||||
id: toTokenComboBox
|
id: toTokenComboBox
|
||||||
textRole: "name"
|
textRole: "name"
|
||||||
|
valueRole: "key"
|
||||||
model: d.tokenBySymbolModel
|
model: d.tokenBySymbolModel
|
||||||
currentIndex: 1
|
currentIndex: 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,29 +5,33 @@ ListModel {
|
||||||
{
|
{
|
||||||
tokensKey: "DAI",
|
tokensKey: "DAI",
|
||||||
balances: [
|
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: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 11155111, balance: "0" },
|
||||||
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 5, balance: "123456789123456789" },
|
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "123456789123456789" },
|
||||||
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 11155111, balance: "123456789123456789" }
|
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 11155111, balance: "123456789123456789" },
|
||||||
|
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 42161, balance: "45123456789123456789" },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tokensKey: "ETH",
|
tokensKey: "ETH",
|
||||||
balances: [
|
balances: [
|
||||||
|
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 1, balance: "122082928968121891" },
|
||||||
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "1013151281976507736" },
|
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "1013151281976507736" },
|
||||||
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 421613, balance: "473057568699284613" },
|
{ 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: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 11155111, balance: "307400931315122839" },
|
||||||
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "122082928968121891" },
|
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "122082928968121891" },
|
||||||
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 421613, balance: "0" },
|
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 421613, balance: "0" },
|
||||||
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 5, balance: "559133758939097000" }
|
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "559133758939097000" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tokensKey: "STT",
|
tokensKey: "STT",
|
||||||
balances: [
|
balances: [
|
||||||
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 5, balance: "999999999998998500000000000016777216" },
|
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 1, balance: "45123456789123456789" },
|
||||||
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 5, balance: "1077000000000000000000" },
|
{ account: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", chainId: 420, balance: "999999999998998500000000000016777216" },
|
||||||
|
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 10, balance: "1077000000000000000000" },
|
||||||
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "122082928968121891" },
|
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 420, balance: "122082928968121891" },
|
||||||
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 421613, balance: "222000000000000000" },
|
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 421613, balance: "222000000000000000" },
|
||||||
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 11155111, balance: "559133758939097000" }
|
{ account: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881", chainId: 11155111, balance: "559133758939097000" }
|
||||||
|
|
|
@ -56,7 +56,7 @@ QtObject {
|
||||||
Component.onCompleted: append([
|
Component.onCompleted: append([
|
||||||
{
|
{
|
||||||
chainId: 1,
|
chainId: 1,
|
||||||
chainName: "Ethereum Mainnet",
|
chainName: "Mainnet",
|
||||||
blockExplorerUrl: "https://etherscan.io/",
|
blockExplorerUrl: "https://etherscan.io/",
|
||||||
iconUrl: "network/Network=Ethereum",
|
iconUrl: "network/Network=Ethereum",
|
||||||
chainColor: "#627EEA",
|
chainColor: "#627EEA",
|
||||||
|
@ -217,7 +217,6 @@ QtObject {
|
||||||
chainName: "Arbitrum",
|
chainName: "Arbitrum",
|
||||||
iconUrl: ModelsData.networks.arbitrum,
|
iconUrl: ModelsData.networks.arbitrum,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isEnabled: true,
|
|
||||||
shortName: "ARB",
|
shortName: "ARB",
|
||||||
chainColor: "purple",
|
chainColor: "purple",
|
||||||
layer: 2,
|
layer: 2,
|
||||||
|
@ -337,7 +336,6 @@ QtObject {
|
||||||
chainName: "Arbitrum",
|
chainName: "Arbitrum",
|
||||||
iconUrl: ModelsData.networks.arbitrum,
|
iconUrl: ModelsData.networks.arbitrum,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
isEnabled: true,
|
|
||||||
shortName: "ARB",
|
shortName: "ARB",
|
||||||
chainColor: "purple",
|
chainColor: "purple",
|
||||||
layer: 2,
|
layer: 2,
|
||||||
|
|
|
@ -9,7 +9,6 @@ ListModel {
|
||||||
readonly property string nativeSource: "native" //SourceOfTokensModel.custom
|
readonly property string nativeSource: "native" //SourceOfTokensModel.custom
|
||||||
|
|
||||||
readonly property var data: [
|
readonly property var data: [
|
||||||
|
|
||||||
{
|
{
|
||||||
key: "ETH",
|
key: "ETH",
|
||||||
name: "Ether",
|
name: "Ether",
|
||||||
|
@ -117,7 +116,7 @@ ListModel {
|
||||||
changePctDay: 0,
|
changePctDay: 0,
|
||||||
changePct24hour: 0,
|
changePct24hour: 0,
|
||||||
change24hour: 0,
|
change24hour: 0,
|
||||||
currencyPrice: ({amount: 0, symbol: "USD", displayDecimals: 2, stripTrailingZeroes: false})
|
currencyPrice: ({amount: 0.07, symbol: "USD", displayDecimals: 2, stripTrailingZeroes: false})
|
||||||
},
|
},
|
||||||
detailsLoading: false,
|
detailsLoading: false,
|
||||||
marketDetailsLoading: false
|
marketDetailsLoading: false
|
||||||
|
|
|
@ -17,10 +17,10 @@ QtObject {
|
||||||
return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale)
|
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 bigIntBalance = SQUtils.AmountsArithmetic.fromString(balance)
|
||||||
let decimalBalance = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
|
let decimalBalance = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
|
||||||
return formatCurrencyAmount(decimalBalance, symbol)
|
return formatCurrencyAmount(decimalBalance, symbol, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFiatValue(balance, cryptoSymbol) {
|
function getFiatValue(balance, cryptoSymbol) {
|
||||||
|
|
|
@ -182,7 +182,7 @@ QtObject {
|
||||||
let listOfChains = chainIds.split(":")
|
let listOfChains = chainIds.split(":")
|
||||||
let listOfChainIds = []
|
let listOfChainIds = []
|
||||||
for (let k =0;k<listOfChains.length;k++) {
|
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
|
return listOfChainIds
|
||||||
}
|
}
|
||||||
|
@ -211,6 +211,9 @@ QtObject {
|
||||||
root.showUnPreferredChains = !root.showUnPreferredChains
|
root.showUnPreferredChains = !root.showUnPreferredChains
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setRouteEnabledFromChains(chainId) {
|
||||||
|
}
|
||||||
|
|
||||||
function setSelectedTokenIsOwnerToken(isOwnerToken) {
|
function setSelectedTokenIsOwnerToken(isOwnerToken) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,13 +261,11 @@ QtObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNetworkName(chainId) {
|
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) {
|
function formatCurrencyAmountFromBigInt(balance, symbol, decimals, options = null) {
|
||||||
let bigIntBalance = SQUtils.AmountsArithmetic.fromString(balance)
|
return currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals, options)
|
||||||
let decimalBalance = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
|
|
||||||
return currencyStore.formatCurrencyAmount(decimalBalance, symbol)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Property and methods below are used to apply advanced token management settings to the SendModal
|
// Property and methods below are used to apply advanced token management settings to the SendModal
|
||||||
|
@ -318,11 +319,9 @@ QtObject {
|
||||||
},
|
},
|
||||||
FastExpressionFilter {
|
FastExpressionFilter {
|
||||||
expression: {
|
expression: {
|
||||||
if (model.isCommunityAsset)
|
|
||||||
return true
|
|
||||||
return model.currentCurrencyBalance > balanceThresholdAmount
|
return model.currentCurrencyBalance > balanceThresholdAmount
|
||||||
}
|
}
|
||||||
expectedRoles: ["isCommunityAsset", "currentCurrencyBalance"]
|
expectedRoles: ["currentCurrencyBalance"]
|
||||||
enabled: balanceThresholdEnabled
|
enabled: balanceThresholdEnabled
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -122,7 +122,7 @@ private:
|
||||||
m_persistentIndexes.clear();
|
m_persistentIndexes.clear();
|
||||||
m_persistentIndexes.reserve(count);
|
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));
|
m_persistentIndexes.push_back(model->index(i, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,9 +66,8 @@ private:
|
||||||
void updateRoleNames();
|
void updateRoleNames();
|
||||||
void updateIndexes(int from, int to);
|
void updateIndexes(int from, int to);
|
||||||
|
|
||||||
QHash<int, QByteArray> findExpectedRoles(
|
QHash<int, QByteArray> findExpectedRoles(const QHash<int, QByteArray> &roleNames,
|
||||||
const QHash<int, QByteArray>& roleNames,
|
const QStringList &expectedRoles);
|
||||||
const QStringList& expectedRoles);
|
|
||||||
|
|
||||||
QPointer<QQmlComponent> m_delegate;
|
QPointer<QQmlComponent> m_delegate;
|
||||||
QHash<int, QByteArray> m_expectedRoleNames;
|
QHash<int, QByteArray> m_expectedRoleNames;
|
||||||
|
|
|
@ -73,19 +73,25 @@ QtObject {
|
||||||
|
|
||||||
function stripTrailingZeroes(numStr, locale) {
|
function stripTrailingZeroes(numStr, locale) {
|
||||||
locale = locale || Qt.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')
|
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()
|
locale = locale || Qt.locale()
|
||||||
|
|
||||||
if (precision === -1)
|
|
||||||
precision = fractionalPartLength(num)
|
|
||||||
|
|
||||||
return num.toLocaleString(locale, 'f', precision)
|
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) {
|
function numberToLocaleStringInCompactForm(num, locale = null) {
|
||||||
locale = locale || Qt.locale()
|
locale = locale || Qt.locale()
|
||||||
const numberOfDigits = integralPartLength(num)
|
const numberOfDigits = integralPartLength(num)
|
||||||
|
|
|
@ -23,7 +23,7 @@ void LeftJoinModel::initialize(bool reset)
|
||||||
auto rightRoleNames = m_rightModel->roleNames();
|
auto rightRoleNames = m_rightModel->roleNames();
|
||||||
|
|
||||||
auto leftNames = leftRoleNames.values();
|
auto leftNames = leftRoleNames.values();
|
||||||
QList<QByteArray> rightNames;
|
QByteArrayList rightNames;
|
||||||
|
|
||||||
if (m_rolesToJoin.empty()) {
|
if (m_rolesToJoin.empty()) {
|
||||||
rightNames = rightRoleNames.values();
|
rightNames = rightRoleNames.values();
|
||||||
|
@ -41,7 +41,7 @@ void LeftJoinModel::initialize(bool reset)
|
||||||
|
|
||||||
if (roles.empty()) {
|
if (roles.empty()) {
|
||||||
qWarning().noquote()
|
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);
|
.arg(roleName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -264,11 +264,11 @@ QVariant LeftJoinModel::data(const QModelIndex& index, int role) const
|
||||||
m_rightModel->index(0, 0), m_rightModelJoinRole,
|
m_rightModel->index(0, 0), m_rightModelJoinRole,
|
||||||
joinRoleLeftValue, 1, Qt::MatchExactly);
|
joinRoleLeftValue, 1, Qt::MatchExactly);
|
||||||
|
|
||||||
if (match.empty())
|
if (match.isEmpty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
m_lastUsedRightModelIndex = match.first();
|
m_lastUsedRightModelIndex = match.constFirst();
|
||||||
return match.first().data(role - m_rightModelRolesOffset);
|
return m_lastUsedRightModelIndex.data(role - m_rightModelRolesOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LeftJoinModel::classBegin()
|
void LeftJoinModel::classBegin()
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QQmlProperty>
|
#include <QQmlProperty>
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
ObjectProxyModel::ObjectProxyModel(QObject* parent)
|
ObjectProxyModel::ObjectProxyModel(QObject* parent)
|
||||||
: QIdentityProxyModel{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(i.value(), model->data(model->index(index, 0), i.key()));
|
||||||
}
|
}
|
||||||
|
|
||||||
rowData->insert("index", index);
|
rowData->insert(QStringLiteral("index"), index);
|
||||||
context->setContextProperty("model", rowData);
|
context->setContextProperty(QStringLiteral("model"), rowData);
|
||||||
|
|
||||||
QObject* instance = m_delegate->create(context);
|
QObject* instance = m_delegate->create(context);
|
||||||
context->setParent(instance);
|
context->setParent(instance);
|
||||||
|
@ -300,7 +298,7 @@ void ObjectProxyModel::updateIndexes(int from, int to)
|
||||||
auto& entry = m_container[i];
|
auto& entry = m_container[i];
|
||||||
|
|
||||||
if (entry.proxy)
|
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 QHash<int, QByteArray> &roleNames,
|
||||||
const QStringList &expectedRoles)
|
const QStringList &expectedRoles)
|
||||||
{
|
{
|
||||||
if (roleNames.empty() || expectedRoles.isEmpty())
|
if (roleNames.isEmpty() || expectedRoles.isEmpty())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
QHash<int, QByteArray> expected;
|
QHash<int, QByteArray> expected;
|
||||||
|
|
||||||
for (auto& role : expectedRoles) {
|
for (auto &role : expectedRoles) {
|
||||||
auto expectedKeys = roleNames.keys(role.toUtf8());
|
auto expectedKeys = roleNames.keys(role.toUtf8());
|
||||||
auto expectedKeysCount = expectedKeys.size();
|
auto expectedKeysCount = expectedKeys.size();
|
||||||
|
|
||||||
if (expectedKeysCount == 1)
|
if (expectedKeysCount == 1)
|
||||||
expected.insert(expectedKeys.first(), role.toUtf8());
|
expected.insert(expectedKeys.first(), role.toUtf8());
|
||||||
else if (expectedKeysCount == 0) {
|
else if (expectedKeysCount == 0) {
|
||||||
qWarning() << "Expected role not found!";
|
qWarning() << Q_FUNC_INFO;
|
||||||
|
qWarning() << "Expected role" << role << "not found!";
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Malformed source model - multiple roles found for given "
|
qWarning() << Q_FUNC_INFO;
|
||||||
"expected role name!";
|
qWarning()
|
||||||
|
<< "Malformed source model - multiple roles found for given expected role name!";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -263,9 +263,12 @@ private slots:
|
||||||
model.setSourceModel(sourceModel);
|
model.setSourceModel(sourceModel);
|
||||||
model.setDelegate(delegate.get());
|
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);
|
QCOMPARE(model.rowCount(), 3);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
TokenSelectorViewAdaptor 1.0 TokenSelectorViewAdaptor.qml
|
|
@ -231,7 +231,7 @@ Control {
|
||||||
return root.currencyStore.formatCurrencyAmount(balance, root.currencyStore.currentCurrency)
|
return root.currencyStore.formatCurrencyAmount(balance, root.currencyStore.currentCurrency)
|
||||||
}
|
}
|
||||||
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals) {
|
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals) {
|
||||||
return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
|
return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals, {noSymbol: true})
|
||||||
}
|
}
|
||||||
onItemSelected: {
|
onItemSelected: {
|
||||||
d.setSelectedHoldingId(holdingId, holdingType)
|
d.setSelectedHoldingId(holdingId, holdingType)
|
||||||
|
|
|
@ -22,6 +22,8 @@ QObject {
|
||||||
property bool swapProposalReady: false
|
property bool swapProposalReady: false
|
||||||
property bool swapProposalLoading: false
|
property bool swapProposalLoading: false
|
||||||
|
|
||||||
|
property bool showCommunityTokens
|
||||||
|
|
||||||
// To expose the selected from and to Token from the SwapModal
|
// 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 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)
|
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)
|
return root.currencyStore.formatCurrencyAmount(balance, symbol, options, locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
|
function formatCurrencyAmountFromBigInt(balance, symbol, decimals, options = null) {
|
||||||
return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
|
return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllChainIds() {
|
function getAllChainIds() {
|
||||||
|
@ -122,14 +124,13 @@ QObject {
|
||||||
property real displayAssetsBelowBalanceThresholdAmount: root.walletAssetsStore.walletTokensStore.getDisplayAssetsBelowBalanceThresholdDisplayAmount()
|
property real displayAssetsBelowBalanceThresholdAmount: root.walletAssetsStore.walletTokensStore.getDisplayAssetsBelowBalanceThresholdDisplayAmount()
|
||||||
sourceModel: __assetsWithFilteredBalances
|
sourceModel: __assetsWithFilteredBalances
|
||||||
proxyRoles: [
|
proxyRoles: [
|
||||||
FastExpressionRole {
|
|
||||||
name: "isCommunityAsset"
|
|
||||||
expression: !!model.communityId
|
|
||||||
expectedRoles: ["communityId"]
|
|
||||||
},
|
|
||||||
FastExpressionRole {
|
FastExpressionRole {
|
||||||
name: "currentBalance"
|
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"]
|
expectedRoles: ["balances", "decimals"]
|
||||||
},
|
},
|
||||||
FastExpressionRole {
|
FastExpressionRole {
|
||||||
|
@ -150,19 +151,16 @@ QObject {
|
||||||
|
|
||||||
if (!root.walletAssetsStore.assetsController.filterAcceptsSymbol(model.symbol)) // explicitely hidden
|
if (!root.walletAssetsStore.assetsController.filterAcceptsSymbol(model.symbol)) // explicitely hidden
|
||||||
return false
|
return false
|
||||||
if (model.isCommunityAsset) // do not show community assets
|
if (!!model.communityId)
|
||||||
return false
|
return root.showCommunityTokens
|
||||||
if (root.walletAssetsStore.walletTokensStore.displayAssetsBelowBalance)
|
if (root.walletAssetsStore.walletTokensStore.displayAssetsBelowBalance)
|
||||||
return model.currentCurrencyBalance > processedAssetsModel.displayAssetsBelowBalanceThresholdAmount
|
return model.currentCurrencyBalance > processedAssetsModel.displayAssetsBelowBalanceThresholdAmount
|
||||||
return true
|
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
|
// FIXME sort by assetsController instead, to have the sorting/order as in the main wallet view
|
||||||
// sorters: RoleSorter {
|
|
||||||
// roleName: "isCommunityAsset"
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal properties and functions -----------------------------------------------------------------------------------------------------------------------------
|
// Internal properties and functions -----------------------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -203,7 +201,7 @@ QObject {
|
||||||
}
|
}
|
||||||
readonly property LeftJoinModel joinModel: LeftJoinModel {
|
readonly property LeftJoinModel joinModel: LeftJoinModel {
|
||||||
leftModel: submodel
|
leftModel: submodel
|
||||||
rightModel: root.filteredFlatNetworksModel
|
rightModel: root.swapStore.flatNetworks
|
||||||
|
|
||||||
joinRole: "chainId"
|
joinRole: "chainId"
|
||||||
}
|
}
|
||||||
|
@ -227,11 +225,12 @@ QObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Internal function to calculate total balance */
|
/* Internal function to calculate total balance */
|
||||||
function __getTotalBalance(balances, decimals) {
|
function __getTotalBalance(balances, decimals, chainIds = [root.swapFormData.selectedNetworkChainId]) {
|
||||||
let totalBalance = 0
|
let totalBalance = 0
|
||||||
for(let i=0; i<balances.count; i++) {
|
for(let i=0; i<balances.count; i++) {
|
||||||
let balancePerAddressPerChain = ModelUtils.get(balances, 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
|
return totalBalance
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,10 +57,10 @@ QtObject {
|
||||||
sourceModel: root._joinFlatTokensModel
|
sourceModel: root._joinFlatTokensModel
|
||||||
|
|
||||||
proxyRoles: [
|
proxyRoles: [
|
||||||
FastExpressionRole {
|
JoinRole {
|
||||||
name: "explorerUrl"
|
name: "explorerUrl"
|
||||||
expression: model.blockExplorerURL + "/token/" + model.address
|
roleNames: ["blockExplorerURL", "address"]
|
||||||
expectedRoles: ["blockExplorerURL", "address"]
|
separator: "/token/"
|
||||||
},
|
},
|
||||||
FastExpressionRole {
|
FastExpressionRole {
|
||||||
function tokenIcon(symbol) {
|
function tokenIcon(symbol) {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
CollectiblesView 1.0 CollectiblesView.qml
|
|
||||||
AssetsDetailView 1.0 AssetsDetailView.qml
|
AssetsDetailView 1.0 AssetsDetailView.qml
|
||||||
|
CollectiblesView 1.0 CollectiblesView.qml
|
||||||
SavedAddresses 1.0 SavedAddresses.qml
|
SavedAddresses 1.0 SavedAddresses.qml
|
||||||
|
TokenSelectorAssetDelegate 1.0 TokenSelectorAssetDelegate.qml
|
||||||
|
TokenSelectorView 1.0 TokenSelectorView.qml
|
||||||
|
|
|
@ -267,7 +267,7 @@ StatusDialog {
|
||||||
return popup.store.currencyStore.formatCurrencyAmount(balance, popup.store.currencyStore.currentCurrency)
|
return popup.store.currencyStore.formatCurrencyAmount(balance, popup.store.currencyStore.currentCurrency)
|
||||||
}
|
}
|
||||||
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){
|
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)
|
return popup.store.currencyStore.formatCurrencyAmount(balance, popup.store.currencyStore.currentCurrency)
|
||||||
}
|
}
|
||||||
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals) {
|
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals) {
|
||||||
return popup.store.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
|
return popup.store.formatCurrencyAmountFromBigInt(balance, symbol, decimals, {noSymbol: true})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,21 +14,13 @@ QtObject {
|
||||||
property var _profileSectionModuleInst: profileSectionModule
|
property var _profileSectionModuleInst: profileSectionModule
|
||||||
|
|
||||||
function getModelIndexForKey(key) {
|
function getModelIndexForKey(key) {
|
||||||
for (var i=0; i<currenciesModel.count; i++) {
|
const idx = SQUtils.ModelUtils.indexOf(currenciesModel, "key", key)
|
||||||
if (currenciesModel.get(i).key === key) {
|
return idx === -1 ? 0 : idx
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getModelIndexForShortName(shortName) {
|
function getModelIndexForShortName(shortName) {
|
||||||
for (var i=0; i<currenciesModel.count; i++) {
|
const idx = SQUtils.ModelUtils.indexOf(currenciesModel, "shortName", shortName)
|
||||||
if (currenciesModel.get(i).shortName === shortName) {
|
return idx === -1 ? 0 : idx
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property string currentCurrency: Global.appIsReady ? walletSection.currentCurrency : ""
|
readonly property string currentCurrency: Global.appIsReady ? walletSection.currentCurrency : ""
|
||||||
|
@ -989,10 +981,10 @@ QtObject {
|
||||||
return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale)
|
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 bigIntBalance = SQUtils.AmountsArithmetic.fromString(balance)
|
||||||
let decimalBalance = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
|
let decimalBalance = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
|
||||||
return formatCurrencyAmount(decimalBalance, symbol)
|
return formatCurrencyAmount(decimalBalance, symbol, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFiatValue(cryptoAmount, cryptoSymbol) {
|
function getFiatValue(cryptoAmount, cryptoSymbol) {
|
||||||
|
|
|
@ -261,10 +261,8 @@ QtObject {
|
||||||
return walletSectionSendInst.getShortChainIds(chainShortNames)
|
return walletSectionSendInst.getShortChainIds(chainShortNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
|
function formatCurrencyAmountFromBigInt(balance, symbol, decimals, options = null) {
|
||||||
let bigIntBalance = AmountsArithmetic.fromString(balance)
|
return currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals, options)
|
||||||
let decimalBalance = AmountsArithmetic.toNumber(bigIntBalance, decimals)
|
|
||||||
return currencyStore.formatCurrencyAmount(decimalBalance, symbol)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Property set from TokenLIstView and HoldingSelector to search token by name, symbol or contract address
|
// 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
|
if (!root.walletAssetStore.assetsController.filterAcceptsSymbol(model.symbol)) // explicitely hidden
|
||||||
return false
|
return false
|
||||||
if (model.isCommunityAsset)
|
|
||||||
return true
|
|
||||||
if (tokensStore.displayAssetsBelowBalance)
|
if (tokensStore.displayAssetsBelowBalance)
|
||||||
return model.currentCurrencyBalance > processedAssetsModel.displayAssetsBelowBalanceThresholdAmount
|
return model.currentCurrencyBalance > processedAssetsModel.displayAssetsBelowBalanceThresholdAmount
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
expectedRoles: ["symbol", "isCommunityAsset", "currentCurrencyBalance"]
|
expectedRoles: ["symbol", "currentCurrencyBalance"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
sorters: RoleSorter {
|
sorters: RoleSorter {
|
||||||
|
|
Loading…
Reference in New Issue