fix(@desktop/wallet): Prepare send modal to display community assets

fixes #12378
This commit is contained in:
Khushboo Mehta 2024-01-30 14:15:58 +01:00 committed by Khushboo-dev-cpp
parent 6f25499380
commit 7dcc704d29
20 changed files with 420 additions and 235 deletions

View File

@ -31,8 +31,9 @@ SplitView {
submodelRoleName: "balances"
delegateModel: SortFilterProxyModel {
sourceModel: submodel
filters: ExpressionFilter {
expression: txStore.selectedSenderAccount.address === account
filters: FastExpressionFilter {
expression: txStore.selectedSenderAccount.address === model.account
expectedRoles: ["account"]
}
}
}
@ -40,6 +41,9 @@ SplitView {
TransactionStore {
id: txStore
walletAssetStore: root.walletAssetStore
showCommunityAssetsInSend: showCommunityAssetsCheckBox.checked
balanceThresholdEnabled: balanceThresholdCheckbox.checked
balanceThresholdAmount: Number(balanceThresholdValue.text)
}
QtObject {
@ -274,6 +278,32 @@ SplitView {
loader.item.open()
}
}
CheckBox {
id: showCommunityAssetsCheckBox
text: "Show community assets when sending tokens"
checked: true
}
CheckBox {
id: balanceThresholdCheckbox
text: "Turn on balance threshold"
checked: false
}
Rectangle {
border.width: 1
Layout.preferredWidth: 100
Layout.preferredHeight: 50
color: "lightgrey"
TextInput {
id: balanceThresholdValue
anchors.fill: parent
enabled: balanceThresholdCheckbox.checked
text: "0.10"
inputMethodHints: Qt.ImhFormattedNumbersOnly
}
}
}
}
}

View File

@ -10,13 +10,25 @@ import AppLayouts.Wallet.stores 1.0
import StatusQ.Core.Utils 0.1
import shared.stores 1.0
import shared.stores.send 1.0
SplitView {
id: root
orientation: Qt.Vertical
readonly property WalletAssetsStore walletAssetStore: WalletAssetsStore {
assetsWithFilteredBalances: groupedAccountsAssetsModel
}
TransactionStore {
id: txStore
walletAssetStore: root.walletAssetStore
}
readonly property CurrenciesStore currencyStore: CurrenciesStore {}
Item {
SplitView.fillWidth: true
SplitView.fillHeight: true
@ -31,26 +43,16 @@ SplitView {
width: 400
assets: walletAssetStore.groupedAccountAssetsModel
assets: txStore.processedAssetsModel
collectibles: WalletNestedCollectiblesModel {}
networksModel: NetworksModel.allNetworks
getCurrencyAmountFromBigInt: function(balance, symbol, decimals){
let bigIntBalance = AmountsArithmetic.fromString(balance)
let balance123 = AmountsArithmetic.toNumber(bigIntBalance, decimals)
return ({
amount: balance123,
symbol: symbol,
displayDecimals: 2,
stripTrailingZeroes: false
})
formatCurrentCurrencyAmount: function(balance){
return currencyStore.formatCurrencyAmount(balance, "USD")
}
getCurrentCurrencyAmount: function(balance){
return ({
amount: balance,
symbol: "USD",
displayDecimals: 2,
stripTrailingZeroes: false
})
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){
let bigIntBalance = AmountsArithmetic.fromString(balance)
let decimalBalance = AmountsArithmetic.toNumber(bigIntBalance, decimals)
return currencyStore.formatCurrencyAmount(decimalBalance, symbol)
}
}
}

View File

@ -104,7 +104,7 @@ ListModel {
addressPerChain: [
{ chainId: 420, address: "0x6b175474e89094c44da98b954eedeac495271e0f"}
],
decimals: 18,
decimals: 0,
type: 1,
communityId: "ddls",
description: "",
@ -130,7 +130,7 @@ ListModel {
addressPerChain: [
{ chainId: 420, address: "0x6b175474e89094c44da98b954eedeac495271p0f"}
],
decimals: 18,
decimals: 0,
type: 1,
communityId: "sox",
description: "",
@ -156,7 +156,7 @@ ListModel {
addressPerChain: [
{ chainId: 420, address: "0x6b175474e89094c44da98b954eedeac495271d0f"}
],
decimals: 18,
decimals: 0,
type: 1,
communityId: "ddls",
description: "",
@ -182,7 +182,7 @@ ListModel {
addressPerChain: [
{ chainId: 420, address: "0x6b175474e89094c44da98b954eedeac495271a0f"}
],
decimals: 18,
decimals: 0,
type: 1,
communityId: "ast",
description: "",

View File

@ -1,5 +1,7 @@
import QtQuick 2.15
import StatusQ.Core 0.1
QtObject {
id: root
@ -7,7 +9,11 @@ QtObject {
property string currentCurrencySymbol: "$"
function formatCurrencyAmount(amount, symbol, options = null, locale = null) {
return amount
if (isNaN(amount)) {
return "N/A"
}
var currencyAmount = getCurrencyAmount(amount, symbol)
return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale)
}
function getFiatValue(balance, cryptoSymbol, fiatSymbol) {

View File

@ -90,7 +90,7 @@ QtObject {
function getHolding(holdingId, holdingType) {
if (holdingType === Constants.TokenType.ERC20) {
return getAsset(walletAssetStore.groupedAccountAssetsModel, holdingId)
return getAsset(processedAssetsModel, holdingId)
} else if (holdingType === Constants.TokenType.ERC721) {
return getCollectible(holdingId)
} else {
@ -100,7 +100,7 @@ QtObject {
function getSelectorHolding(holdingId, holdingType) {
if (holdingType === Constants.TokenType.ERC20) {
return getAsset(walletAssetStore.groupedAccountAssetsModel, holdingId)
return getAsset(processedAssetsModel, holdingId)
} else if (holdingType === Constants.TokenType.ERC721) {
return getSelectorCollectible(holdingId)
} else {
@ -255,14 +255,96 @@ QtObject {
return SQUtils.ModelUtils.getByKey(NetworksModel.allNetworks, "chainId", chainId, "chainName")
}
function getCurrencyAmountFromBigInt(balance, symbol, decimals) {
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
let bigIntBalance = SQUtils.AmountsArithmetic.fromString(balance)
let balance123 = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
return currencyStore.getCurrencyAmount(balance123, symbol)
let decimalBalance = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
return currencyStore.formatCurrencyAmount(decimalBalance, symbol)
}
function getCurrentCurrencyAmount(balance) {
return currencyStore.getCurrencyAmount(balance, currencyStore.currentCurrency)
// Property and methods below are used to apply advanced token management settings to the SendModal
property bool showCommunityAssetsInSend: true
property bool balanceThresholdEnabled: true
property real balanceThresholdAmount
// Property set from TokenLIstView and HoldingSelector to search token by name, symbol or contract address
property string assetSearchString
// Model prepared to provide filtered and sorted assets as per the advanced Settings in token management
property var processedAssetsModel: SortFilterProxyModel {
sourceModel: walletAssetStore.groupedAccountAssetsModel
proxyRoles: [
FastExpressionRole {
name: "isCommunityAsset"
expression: !!model.communityId
expectedRoles: ["communityId"]
},
FastExpressionRole {
name: "currentBalance"
expression: __getTotalBalance(model.balances, model.decimals, model.symbol, root.selectedSenderAccount)
expectedRoles: ["balances", "decimals", "symbol"]
},
FastExpressionRole {
name: "currentCurrencyBalance"
expression: {
if (!!model.marketDetails) {
return model.currentBalance * model.marketDetails.currencyPrice.amount
}
return 0
}
expectedRoles: ["marketDetails", "currentBalance"]
}
]
filters: [
FastExpressionFilter {
function search(symbol, name, addressPerChain, searchString) {
return (
symbol.startsWith(searchString.toUpperCase()) ||
name.toUpperCase().startsWith(searchString.toUpperCase()) || __searchAddressInList(addressPerChain, searchString)
)
}
expression: search(symbol, name, addressPerChain, root.assetSearchString)
expectedRoles: ["symbol", "name", "addressPerChain"]
},
ValueFilter {
roleName: "isCommunityAsset"
value: false
enabled: !showCommunityAssetsInSend
},
FastExpressionFilter {
expression: {
if (model.isCommunityAsset)
return true
return model.currentCurrencyBalance > balanceThresholdAmount
}
expectedRoles: ["isCommunityAsset", "currentCurrencyBalance"]
enabled: balanceThresholdEnabled
}
]
sorters: RoleSorter {
roleName: "isCommunityAsset"
}
}
/* Internal function to search token address */
function __searchAddressInList(addressPerChain, searchString) {
let addressFound = false
let tokenAddresses = SQUtils.ModelUtils.modelToFlatArray(addressPerChain, "address")
for (let i =0; i< tokenAddresses.length; i++){
if(tokenAddresses[i].toUpperCase().startsWith(searchString.toUpperCase())) {
addressFound = true
break;
}
}
return addressFound
}
/* Internal function to calculate total balance */
function __getTotalBalance(balances, decimals, symbol) {
let totalBalance = 0
for(let i=0; i<balances.count; i++) {
let balancePerAddressPerChain = SQUtils.ModelUtils.get(balances, i)
totalBalance+=SQUtils.AmountsArithmetic.toNumber(balancePerAddressPerChain.balance, decimals)
}
return totalBalance
}
}

View File

@ -15,6 +15,7 @@ Item {
property alias count: comboBox.count
property alias delegate: comboBox.delegate
property alias contentItem: comboBox.contentItem
property alias comboBoxListViewSection: listView.section
property alias currentIndex: comboBox.currentIndex
property alias currentValue: comboBox.currentValue

View File

@ -98,4 +98,9 @@ QtObject {
}
]
}
// Property and methods below are used to apply advanced token management settings to the SendModal
readonly property bool showCommunityAssetsInSend: root._allTokensModule.showCommunityAssetWhenSendingTokens
readonly property bool balanceThresholdEnabled: root._allTokensModule.displayAssetsBelowBalance
readonly property real balanceThresholdAmount: root._allTokensModule.displayAssetsBelowBalanceThreshold
}

View File

@ -72,8 +72,8 @@ Item {
primaryText: token && token.name ? token.name : Constants.dummyText
secondaryText: token ? LocaleUtils.currencyAmountToLocaleString(root.currencyStore.getCurrencyAmount(token.currentBalance, token.symbol)) : Constants.dummyText
tertiaryText: {
let totalCurrencyBalance = token && token.currentCurrencyBalance ? token.currentCurrencyBalance : 0
return LocaleUtils.currencyAmountToLocaleString(root.currencyStore.getCurrentCurrencyAmount(totalCurrencyBalance))
let totalCurrencyBalance = token && token.currentCurrencyBalance && token.symbol ? token.currentCurrencyBalance : 0
return currencyStore.formatCurrencyAmount(totalCurrencyBalance, token.symbol)
}
decimals: token && token.decimals ? token.decimals : 4
balances: token && token.balances ? token.balances: null

View File

@ -75,6 +75,7 @@ Item {
readonly property CurrenciesStore currencyStore: CurrenciesStore{}
readonly property TransactionStore transactionStore: TransactionStore {
walletAssetStore: appMain.walletAssetsStore
tokensStore: appMain.tokensStore
}
// set from main.qml

View File

@ -0,0 +1,42 @@
import QtQuick 2.13
import QtQuick.Layouts 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups.Dialog 0.1
import utils 1.0
ColumnLayout {
signal openInfoPopup()
spacing: 0
StatusDialogDivider {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
Layout.bottomMargin: Style.current.halfPadding
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.smallPadding
Layout.bottomMargin: 4
StatusBaseText {
text: qsTr("Community assets")
color: Theme.palette.baseColor1
}
Item { Layout.fillWidth: true }
StatusFlatButton {
Layout.preferredWidth: 32
Layout.preferredHeight: 32
icon.name: "info"
textColor: Theme.palette.baseColor1
horizontalPadding: 0
verticalPadding: 0
onClicked: openInfoPopup()
}
}
}

View File

@ -45,3 +45,4 @@ TransactionDelegate 1.0 TransactionDelegate.qml
TransactionDetailsHeader.qml 1.0 TransactionDetailsHeader.qml
MockedKeycardReaderStateSelector 1.0 MockedKeycardReaderStateSelector.qml
MockedKeycardStateSelector 1.0 MockedKeycardStateSelector.qml
AssetsSectionDelegate 1.0 AssetsSectionDelegate.qml

View File

@ -0,0 +1,15 @@
import QtQuick 2.14
import StatusQ.Core 0.1
import StatusQ.Popups.Dialog 0.1
StatusDialog {
destroyOnClose: true
title: qsTr("What are community assets?")
standardButtons: Dialog.Ok
width: 520
contentItem: StatusBaseText {
wrapMode: Text.Wrap
text: qsTr("Community assets are assets that have been minted by a community. As these assets cannot be verified, always double check their origin and validity before interacting with them. If in doubt, ask a trusted member or admin of the relevant community.")
}
}

View File

@ -26,3 +26,4 @@ DeleteMessageConfirmationPopup 1.0 DeleteMessageConfirmationPopup.qml
UserAgreementPopup 1.0 UserAgreementPopup.qml
AlertPopup 1.0 AlertPopup.qml
ConfirmExternalLinkPopup 1.0 ConfirmExternalLinkPopup.qml
CommunityAssetsInfoPopup 1.0 CommunityAssetsInfoPopup.qml

View File

@ -77,9 +77,8 @@ StatusDialog {
(popup.bestRoutes && popup.bestRoutes.count === 0 &&
!!amountToSendInput.input.text && recipientLoader.ready && !popup.isLoading) ?
Constants.NoRoute : Constants.NoError
readonly property double totalSelectedHoldingBalance: isSelectedHoldingValidAsset ? !d.selectedHolding.communityId ? selectedHoldingAggregator.value/(10 ** d.selectedHolding.decimals): selectedHoldingAggregator.value: 0
readonly property double maxFiatBalance: isSelectedHoldingValidAsset && selectedHolding.marketDetails ? totalSelectedHoldingBalance * selectedHolding.marketDetails.currencyPrice.amount : 0
readonly property double maxCryptoBalance: isSelectedHoldingValidAsset ? totalSelectedHoldingBalance : 0
readonly property double maxFiatBalance: isSelectedHoldingValidAsset ? selectedHolding.currentCurrencyBalance : 0
readonly property double maxCryptoBalance: isSelectedHoldingValidAsset ? selectedHolding.currentBalance : 0
readonly property double maxInputBalance: amountToSendInput.inputIsFiat ? maxFiatBalance : maxCryptoBalance
readonly property string inputSymbol: amountToSendInput.inputIsFiat ? currencyStore.currentCurrency : store.selectedAssetSymbol
readonly property bool errorMode: popup.isLoading || !recipientLoader.ready ? false : errorType !== Constants.NoError || networkSelector.errorMode || !amountToSendInput.inputNumberValid
@ -149,21 +148,6 @@ StatusDialog {
}
}
SumAggregator {
id: selectedHoldingAggregator
model: SortFilterProxyModel {
sourceModel: d.isSelectedHoldingValidAsset ? d.selectedHolding.balances: d.isHoveredHoldingValidAsset && !!d.hoveredHolding.symbol ? d.hoveredHolding.balances: null
filters: FastExpressionFilter {
expression: {
store.selectedSenderAccount
return store.selectedSenderAccount.address === model.account
}
expectedRoles: ["account"]
}
}
roleName: "balance"
}
width: 556
padding: 0
@ -282,7 +266,7 @@ StatusDialog {
Layout.fillWidth: true
Layout.fillHeight: true
selectedSenderAccount: store.selectedSenderAccount.address
assetsModel: popup.store.walletAssetStore.groupedAccountAssetsModel
assetsModel: popup.store.processedAssetsModel
collectiblesModel: popup.preSelectedAccount ? popup.nestedCollectiblesModel : null
networksModel: popup.store.allNetworksModel
currentCurrencySymbol: d.currencyStore.currentCurrencySymbol
@ -291,11 +275,12 @@ StatusDialog {
onItemSelected: {
d.setSelectedHoldingId(holdingId, holdingType)
}
getCurrencyAmountFromBigInt: function(balance, symbol, decimals){
return store.getCurrencyAmountFromBigInt(balance, symbol, decimals)
onSearchTextChanged: popup.store.assetSearchString = searchText
formatCurrentCurrencyAmount: function(balance){
return popup.store.currencyStore.formatCurrencyAmount(balance, popup.store.currencyStore.currentCurrency)
}
getCurrentCurrencyAmount: function(balance){
return store.getCurrentCurrencyAmount(balance)
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){
return popup.store.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
}
}
@ -306,8 +291,7 @@ StatusDialog {
visible: d.isSelectedHoldingValidAsset || d.isHoveredHoldingValidAsset && !d.isERC721Transfer
title: {
if(d.isHoveredHoldingValidAsset && !!d.hoveredHolding.symbol) {
let totalAmount = !d.hoveredHolding.communityId ? selectedHoldingAggregator.value/(10 ** d.hoveredHolding.decimals): selectedHoldingAggregator.value
const input = amountToSendInput.inputIsFiat ? totalAmount * d.hoveredHolding.marketDetails.currencyPrice.amount : totalAmount
const input = amountToSendInput.inputIsFiat ? d.hoveredHolding.currentCurrencyBalance : d.hoveredHolding.currentBalance
const max = d.prepareForMaxSend(input, d.hoveredHolding.symbol)
if (max <= 0)
return qsTr("No balances active")
@ -424,7 +408,7 @@ StatusDialog {
visible: !d.selectedHolding
selectedSenderAccount: store.selectedSenderAccount.address
assets: popup.store.walletAssetStore.groupedAccountAssetsModel
assets: popup.store.processedAssetsModel
collectibles: popup.preSelectedAccount ? popup.nestedCollectiblesModel : null
networksModel: popup.store.allNetworksModel
onlyAssets: holdingSelector.onlyAssets
@ -438,11 +422,12 @@ StatusDialog {
d.setHoveredHoldingId("", Constants.TokenType.Unknown)
}
}
getCurrencyAmountFromBigInt: function(balance, symbol, decimals){
return store.getCurrencyAmountFromBigInt(balance, symbol, decimals)
onAssetSearchStringChanged: store.assetSearchString = assetSearchString
formatCurrentCurrencyAmount: function(balance){
return popup.store.currencyStore.formatCurrencyAmount(balance, popup.store.currencyStore.currentCurrency)
}
getCurrentCurrencyAmount: function(balance){
return store.getCurrentCurrencyAmount(balance)
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals) {
return popup.store.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
}
}

View File

@ -14,8 +14,8 @@ StatusListItem {
signal tokenSelected(var selectedToken)
signal tokenHovered(var selectedToken, bool hovered)
property var getCurrencyAmountFromBigInt: function(balance, symbol, decimals){}
property var getCurrentCurrencyAmount: function(balance){}
property var formatCurrentCurrencyAmount: function(balance){}
property var formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){}
property var balancesModel
property string selectedSenderAccount
@ -29,25 +29,6 @@ StatusListItem {
function selectToken() {
root.tokenSelected({name, symbol, balances, decimals})
}
readonly property string balanceRoleName: "balance"
property string roleName: balanceRoleName
}
property var filteredBalances : SortFilterProxyModel {
sourceModel: root.balancesModel
filters: FastExpressionFilter {
expression: {
root.selectedSenderAccount
return root.selectedSenderAccount === model.account
}
expectedRoles: ["account"]
}
onCountChanged: {
// Added because the SumAggregator is not evaluated after the filters are applied
d.roleName = ""
d.roleName = d.balanceRoleName
}
}
Connections {
@ -64,11 +45,8 @@ StatusListItem {
statusListItemTitleAside.width: statusListItemTitleArea.width - statusListItemTitle.width
statusListItemTitleAside.elide: Text.ElideRight
label: {
if (!!model && !!model.marketDetails && !!model.marketDetails.currencyPrice) {
let totalCurrencyBalance = aggregator.value/(10 ** decimals) * model.marketDetails.currencyPrice.amount
return LocaleUtils.currencyAmountToLocaleString(root.getCurrentCurrencyAmount(totalCurrencyBalance))
}
return LocaleUtils.currencyAmountToLocaleString(root.getCurrentCurrencyAmount(0))
let balance = !!model && !!model.currentCurrencyBalance ? model.currentCurrencyBalance : 0
return root.formatCurrentCurrencyAmount(balance)
}
asset.name: symbol ? Style.png("tokens/" + symbol) : ""
asset.isImage: true
@ -77,13 +55,13 @@ StatusListItem {
statusListItemLabel.anchors.verticalCenterOffset: -12
statusListItemLabel.color: Theme.palette.directColor1
statusListItemInlineTagsSlot.spacing: 0
tagsModel: filteredBalances
tagsModel: root.balancesModel
tagsDelegate: expandedItem
statusListItemInlineTagsSlot.children: Row {
id: compactRow
spacing: -6
Repeater {
model: filteredBalances
model: root.balancesModel
delegate: compactItem
}
}
@ -93,12 +71,6 @@ StatusListItem {
onClicked: d.selectToken()
SumAggregator {
id: aggregator
model: filteredBalances
roleName: d.roleName
}
Component {
id: compactItem
StatusRoundedImage {
@ -114,7 +86,7 @@ StatusListItem {
StatusListItemTag {
height: 16
leftPadding: 0
title: LocaleUtils.currencyAmountToLocaleString(root.getCurrencyAmountFromBigInt(balance, symbol, decimals))
title: root.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
titleText.font.pixelSize: 12
closeButtonVisible: false
bgColor: "transparent"

View File

@ -34,6 +34,7 @@ Item {
property alias comboBoxControl: comboBox.control
property alias comboBoxDelegate: comboBox.delegate
property alias comboBoxListViewSection: comboBox.comboBoxListViewSection
property var comboBoxPopupHeader
property int contentIconSize: 21

View File

@ -4,6 +4,7 @@ import QtQuick 2.13
import QtQuick.Layouts 1.13
import shared.controls 1.0
import shared.popups 1.0
import utils 1.0
import SortFilterProxyModel 0.2
@ -12,7 +13,6 @@ import StatusQ 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import "../controls"
@ -25,12 +25,13 @@ Item {
property var networksModel
property string currentCurrencySymbol
property bool onlyAssets: true
property string searchText
implicitWidth: holdingItemSelector.implicitWidth
implicitHeight: holdingItemSelector.implicitHeight
property var getCurrencyAmountFromBigInt: function(balance, symbol, decimals){}
property var getCurrentCurrencyAmount: function(balance){}
property var formatCurrentCurrencyAmount: function(balance){}
property var formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){}
signal itemHovered(string holdingId, var holdingType)
signal itemSelected(string holdingId, var holdingType)
@ -78,7 +79,6 @@ Item {
property var currentHoldingType: Constants.TokenType.Unknown
property string searchText
readonly property string uppercaseSearchText: searchText.toUpperCase()
property var assetTextFn: function (asset) {
@ -89,25 +89,6 @@ Item {
return !!asset && asset.symbol ? Style.png("tokens/%1".arg(asset.symbol)) : ""
}
property var assetComboBoxModel: SortFilterProxyModel {
sourceModel: root.assetsModel
filters: [
FastExpressionFilter {
function search(symbol, name, addressPerChain, searchString) {
return (
searchString === "" ||
symbol.startsWith(searchString) ||
name.toUpperCase().startsWith(searchString) ||
d.searchAddressInList(addressPerChain, searchString)
)
}
expression: search(symbol, name, addressPerChain, d.uppercaseSearchText)
expectedRoles: ["symbol", "name", "addressPerChain"]
}
]
}
property var collectibleTextFn: function (item) {
if (!!item) {
return !!item.collectionName ? item.collectionName + ": " + item.name : item.name
@ -168,18 +149,6 @@ Item {
readonly property int collectibleContentIconSize: 28
readonly property int assetContentTextSize: 28
readonly property int collectibleContentTextSize: 15
function searchAddressInList(addressPerChain, searchString) {
let addressFound = false
let tokenAddresses = SQUtils.ModelUtils.modelToFlatArray(addressPerChain, "address")
for (let i =0; i< tokenAddresses.length; i++){
if(tokenAddresses[i].toUpperCase().startsWith(searchString.toUpperCase())) {
addressFound = true
break;
}
}
return addressFound
}
}
HoldingItemSelector {
@ -222,15 +191,39 @@ Item {
comboBoxPopupHeader: headerComponent
itemTextFn: d.isCurrentBrowsingTypeAsset ? d.assetTextFn : d.collectibleTextFn
itemIconSourceFn: d.isCurrentBrowsingTypeAsset ? d.assetIconSourceFn : d.collectibleIconSourceFn
comboBoxModel: d.isCurrentBrowsingTypeAsset ? d.assetComboBoxModel : d.collectibleComboBoxModel
comboBoxModel: d.isCurrentBrowsingTypeAsset ? root.assetsModel : d.collectibleComboBoxModel
contentIconSize: d.isAsset(d.currentHoldingType) ? d.assetContentIconSize : d.collectibleContentIconSize
contentTextSize: d.isAsset(d.currentHoldingType) ? d.assetContentTextSize : d.collectibleContentTextSize
comboBoxListViewSection.property: "isCommunityAsset"
comboBoxListViewSection.delegate: Loader {
width: ListView.view.width
required property string section
sourceComponent: d.isCurrentBrowsingTypeAsset && section === "true" ? sectionDelegate : null
}
comboBoxControl.popup.onClosed: comboBoxControl.popup.contentItem.headerItem.clear()
}
Component {
id: sectionDelegate
AssetsSectionDelegate {
width: parent.width
onOpenInfoPopup: Global.openPopup(communityInfoPopupCmp)
}
}
Component {
id: communityInfoPopupCmp
CommunityAssetsInfoPopup {}
}
Component {
id: headerComponent
ColumnLayout {
function clear() {
searchInput.input.edit.clear()
}
width: holdingItemSelector.comboBoxControl.popup.width
Layout.topMargin: d.headerTopMargin
spacing: -1 // Used to overlap rectangles from row components
@ -318,11 +311,11 @@ Item {
root.itemSelected(selectedToken.symbol, Constants.TokenType.ERC20)
holdingItemSelector.comboBoxControl.popup.close()
}
getCurrencyAmountFromBigInt: function(balance, symbol, decimals){
return root.getCurrencyAmountFromBigInt(balance, symbol, decimals)
formatCurrentCurrencyAmount: function(balance){
return root.formatCurrentCurrencyAmount(balance)
}
getCurrentCurrencyAmount: function(balance){
return root.getCurrentCurrencyAmount(balance)
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){
return root.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
}
}
}

View File

@ -8,10 +8,11 @@ import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import utils 1.0
import shared.controls 1.0
import shared.popups 1.0
import "../controls"
Item {
@ -21,25 +22,28 @@ Item {
property var assets: null
property var collectibles: null
property var networksModel
property string assetSearchString
signal tokenSelected(string symbol, var holdingType)
signal tokenHovered(string symbol, var holdingType, bool hovered)
property bool onlyAssets: false
property int browsingHoldingType: Constants.TokenType.ERC20
property var getCurrencyAmountFromBigInt: function(balance, symbol, decimals){}
property var getCurrentCurrencyAmount: function(balance){}
property var formatCurrentCurrencyAmount: function(balance){}
property var formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){}
onVisibleChanged: {
if(!visible && root.collectibles)
if(!visible) {
if (root.collectibles)
root.collectibles.currentCollectionUid = ""
tokenList.headerItem.input.edit.clear()
}
}
QtObject {
id: d
property string assetSearchString
readonly property var updateAssetSearchText: Backpressure.debounce(root, 1000, function(inputText) {
d.assetSearchString = inputText
assetSearchString = inputText
})
property string collectibleSearchString
@ -71,18 +75,6 @@ Item {
rightModel: d.renamedAllNetworksModel
joinRole: "chainId"
}
function searchAddressInList(addressPerChain, searchString) {
let addressFound = false
let tokenAddresses = SQUtils.ModelUtils.modelToFlatArray(addressPerChain, "address")
for (let i =0; i< tokenAddresses.length; i++){
if(tokenAddresses[i].toUpperCase().startsWith(searchString.toUpperCase())) {
addressFound = true
break;
}
}
return addressFound
}
}
implicitWidth: contentLayout.implicitWidth
@ -144,28 +136,32 @@ Item {
height: tokenList.contentHeight
header: root.browsingHoldingType === Constants.TokenType.ERC20 ? tokenHeader : collectibleHeader
model: root.browsingHoldingType === Constants.TokenType.ERC20 ? tokensModel : collectiblesModel
model: root.browsingHoldingType === Constants.TokenType.ERC20 ? root.assets : collectiblesModel
delegate: root.browsingHoldingType === Constants.TokenType.ERC20 ? tokenDelegate : collectiblesDelegate
}
section {
property: "isCommunityAsset"
delegate: Loader {
width: ListView.view.width
required property string section
sourceComponent: root.browsingHoldingType === Constants.TokenType.ERC20 && section === "true" ? sectionDelegate : null
}
}
}
property var tokensModel: SortFilterProxyModel {
sourceModel: root.assets
filters: [
FastExpressionFilter {
function search(symbol, name, addressPerChain, searchString) {
tokenList.positionViewAtBeginning()
return (
symbol.startsWith(searchString.toUpperCase()) ||
name.toUpperCase().startsWith(searchString.toUpperCase()) || d.searchAddressInList(addressPerChain, searchString)
)
Component {
id: sectionDelegate
AssetsSectionDelegate {
width: parent.width
onOpenInfoPopup: Global.openPopup(communityInfoPopupCmp)
}
}
Component {
id: communityInfoPopupCmp
CommunityAssetsInfoPopup {}
}
}
expression: search(symbol, name, addressPerChain, d.assetSearchString)
expectedRoles: ["symbol", "name", "addressPerChain"]
}
]
}
property var collectiblesModel: SortFilterProxyModel {
@ -195,11 +191,11 @@ Item {
}
onTokenSelected: root.tokenSelected(symbol, Constants.TokenType.ERC20)
onTokenHovered: root.tokenHovered(symbol, Constants.TokenType.ERC20, hovered)
getCurrencyAmountFromBigInt: function(balance, symbol, decimals){
return root.getCurrencyAmountFromBigInt(balance, symbol, decimals)
formatCurrentCurrencyAmount: function(balance){
return root.formatCurrentCurrencyAmount(balance)
}
getCurrentCurrencyAmount: function(balance){
return root.getCurrentCurrencyAmount(balance)
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){
return root.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
}
}
}

View File

@ -6,6 +6,7 @@ import shared.stores 1.0
import utils 1.0
import StatusQ 0.1
import StatusQ.Core.Utils 0.1
import AppLayouts.Wallet.stores 1.0
@ -15,6 +16,7 @@ QtObject {
property CurrenciesStore currencyStore: CurrenciesStore {}
property WalletAssetsStore walletAssetStore
property TokensStore tokensStore
property var mainModuleInst: mainModule
property var walletSectionSendInst: walletSectionSend
@ -105,15 +107,9 @@ QtObject {
function getAsset(assetsList, symbol) {
for(var i=0; i< assetsList.rowCount();i++) {
let asset = ModelUtils.get(assetsList, i)
let asset = assetsList.get(i)
if(symbol === asset.symbol) {
return {
name: asset.name,
symbol: asset.symbol,
totalBalance: asset.totalBalance,
balances: asset.balances,
decimals: asset.decimals
}
return asset
}
}
return {}
@ -137,7 +133,7 @@ QtObject {
function getHolding(holdingId, holdingType) {
if (holdingType === Constants.TokenType.ERC20) {
return getAsset(walletAssetStore.groupedAccountAssetsModel, holdingId)
return getAsset(processedAssetsModel, holdingId)
} else if (holdingType === Constants.TokenType.ERC721) {
return getCollectible(holdingId)
} else {
@ -147,7 +143,7 @@ QtObject {
function getSelectorHolding(holdingId, holdingType) {
if (holdingType === Constants.TokenType.ERC20) {
return getAsset(walletAssetStore.groupedAccountAssetsModel, holdingId)
return getAsset(processedAssetsModel, holdingId)
} else if (holdingType === Constants.TokenType.ERC721) {
return getSelectorCollectible(holdingId)
} else {
@ -238,6 +234,7 @@ QtObject {
}
function resetStoredProperties() {
assetSearchString = ""
walletSectionSendInst.resetStoredProperties()
nestedCollectiblesModel.currentCollectionUid = ""
}
@ -253,13 +250,104 @@ QtObject {
return walletSectionSendInst.getShortChainIds(chainShortNames)
}
function getCurrencyAmountFromBigInt(balance, symbol, decimals) {
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
let bigIntBalance = AmountsArithmetic.fromString(balance)
let decimalBalance = AmountsArithmetic.toNumber(bigIntBalance, decimals)
return currencyStore.getCurrencyAmount(decimalBalance, symbol)
return currencyStore.formatCurrencyAmount(decimalBalance, symbol)
}
function getCurrentCurrencyAmount(balance) {
return currencyStore.getCurrencyAmount(balance, currencyStore.currentCurrency)
// Property set from TokenLIstView and HoldingSelector to search token by name, symbol or contract address
property string assetSearchString
// Internal model filtering balances by the account selected on the SendModalPage
property SubmodelProxyModel __assetsWithFilteredBalances: SubmodelProxyModel {
sourceModel: walletAssetStore.groupedAccountAssetsModel
submodelRoleName: "balances"
delegateModel: SortFilterProxyModel {
sourceModel: submodel
filters: FastExpressionFilter {
expression: root.selectedSenderAccount.address === model.account
expectedRoles: ["account"]
}
}
}
// Model prepared to provide filtered and sorted assets as per the advanced Settings in token management
property var processedAssetsModel: SortFilterProxyModel {
sourceModel: __assetsWithFilteredBalances
proxyRoles: [
FastExpressionRole {
name: "isCommunityAsset"
expression: !!model.communityId
expectedRoles: ["communityId"]
},
FastExpressionRole {
name: "currentBalance"
expression: __getTotalBalance(model.balances, model.decimals, root.selectedSenderAccount)
expectedRoles: ["balances", "decimals"]
},
FastExpressionRole {
name: "currentCurrencyBalance"
expression: {
if (!!model.marketDetails) {
return model.currentBalance * model.marketDetails.currencyPrice.amount
}
return 0
}
expectedRoles: ["marketDetails", "currentBalance", "symbol"]
}
]
filters: [
FastExpressionFilter {
function search(symbol, name, addressPerChain, searchString) {
return (
symbol.toUpperCase().startsWith(searchString.toUpperCase()) ||
name.toUpperCase().startsWith(searchString.toUpperCase()) || __searchAddressInList(addressPerChain, searchString)
)
}
expression: search(symbol, name, addressPerChain, assetSearchString)
expectedRoles: ["symbol", "name", "addressPerChain"]
},
ValueFilter {
roleName: "isCommunityAsset"
value: false
enabled: !tokensStore.showCommunityAssetsInSend
},
FastExpressionFilter {
expression: {
if (model.isCommunityAsset)
return true
return model.currentCurrencyBalance > tokensStore.balanceThresholdAmount
}
expectedRoles: ["isCommunityAsset", "currentCurrencyBalance"]
enabled: tokensStore.balanceThresholdEnabled
}
]
sorters: RoleSorter {
roleName: "isCommunityAsset"
}
}
/* Internal function to search token address */
function __searchAddressInList(addressPerChain, searchString) {
let addressFound = false
let tokenAddresses = ModelUtils.modelToFlatArray(addressPerChain, "address")
for (let i =0; i< tokenAddresses.length; i++){
if(tokenAddresses[i].toUpperCase().startsWith(searchString.toUpperCase())) {
addressFound = true
break;
}
}
return addressFound
}
/* Internal function to calculate total balance */
function __getTotalBalance(balances, decimals) {
let totalBalance = 0
for(let i=0; i<balances.count; i++) {
let balancePerAddressPerChain = ModelUtils.get(balances, i)
totalBalance+=AmountsArithmetic.toNumber(balancePerAddressPerChain.balance, decimals)
}
return totalBalance
}
}

View File

@ -89,7 +89,6 @@ ColumnLayout {
sourceModel: root.assets
proxyRoles: [
FastExpressionRole {
id: filter
name: "currentBalance"
expression: d.getTotalBalance(model.balances, model.decimals, root.addressFilters, root.networkFilters)
expectedRoles: ["balances", "decimals"]
@ -240,35 +239,9 @@ ColumnLayout {
Component {
id: sectionDelegate
ColumnLayout {
AssetsSectionDelegate {
width: parent.width
spacing: 0
StatusDialogDivider {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
Layout.bottomMargin: Style.current.halfPadding
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.smallPadding
Layout.bottomMargin: 4
StatusBaseText {
text: qsTr("Community assets")
color: Theme.palette.baseColor1
}
Item { Layout.fillWidth: true }
StatusFlatButton {
Layout.preferredWidth: 32
Layout.preferredHeight: 32
icon.name: "info"
textColor: Theme.palette.baseColor1
horizontalPadding: 0
verticalPadding: 0
onClicked: Global.openPopup(communityInfoPopupCmp)
}
}
onOpenInfoPopup: Global.openPopup(communityInfoPopupCmp)
}
}
@ -307,7 +280,7 @@ ColumnLayout {
}
currencyBalance.text: {
let totalCurrencyBalance = modelData && modelData.currentCurrencyBalance ? modelData.currentCurrencyBalance : 0
return LocaleUtils.currencyAmountToLocaleString(root.currencyStore.getCurrentCurrencyAmount(totalCurrencyBalance))
return currencyStore.formatCurrencyAmount(totalCurrencyBalance, currencyStore.currentCurrency)
}
errorMode: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCache && !networkConnectionStore.noMarketConnectionAndNoCache : false
errorIcon.tooltip.text: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : ""
@ -380,16 +353,7 @@ ColumnLayout {
Component {
id: communityInfoPopupCmp
StatusDialog {
destroyOnClose: true
title: qsTr("What are community assets?")
standardButtons: Dialog.Ok
width: 520
contentItem: StatusBaseText {
wrapMode: Text.Wrap
text: qsTr("Community assets are assets that have been minted by a community. As these assets cannot be verified, always double check their origin and validity before interacting with them. If in doubt, ask a trusted member or admin of the relevant community.")
}
}
CommunityAssetsInfoPopup {}
}
Component {