feature(wallet): Token preferences in assets model (#15706)

This commit is contained in:
Cuteivist 2024-07-23 14:00:49 +02:00 committed by GitHub
parent 7d49b6bd9c
commit 047f558cd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 124 additions and 83 deletions

View File

@ -1,5 +1,3 @@
import json
import ./io_interface import ./io_interface
import app/core/eventemitter import app/core/eventemitter
@ -111,11 +109,11 @@ proc getCommunityTokenDescription*(self: Controller, chainId: int, address: stri
proc updateTokenPreferences*(self: Controller, tokenPreferencesJson: string) = proc updateTokenPreferences*(self: Controller, tokenPreferencesJson: string) =
self.tokenService.updateTokenPreferences(tokenPreferencesJson) self.tokenService.updateTokenPreferences(tokenPreferencesJson)
proc getTokenPreferences*(self: Controller, symbol: string): TokenPreferencesItem =
return self.tokenService.getTokenPreferences(symbol)
proc getTokenPreferencesJson*(self: Controller): string = proc getTokenPreferencesJson*(self: Controller): string =
let data = self.tokenService.getTokenPreferences() return self.tokenService.getTokenPreferencesJson()
if data.isNil:
return "[]"
return $data
proc getTokenGroupByCommunity*(self: Controller): bool = proc getTokenGroupByCommunity*(self: Controller): bool =
return self.settingsService.tokenGroupByCommunity() return self.settingsService.tokenGroupByCommunity()

View File

@ -31,6 +31,8 @@ type
MarketDetails MarketDetails
DetailsLoading DetailsLoading
MarketDetailsLoading MarketDetailsLoading
Visible
Position
QtObject: QtObject:
type FlatTokensModel* = ref object of QAbstractListModel type FlatTokensModel* = ref object of QAbstractListModel
@ -81,7 +83,9 @@ QtObject:
ModelRole.WebsiteUrl.int:"websiteUrl", ModelRole.WebsiteUrl.int:"websiteUrl",
ModelRole.MarketDetails.int:"marketDetails", ModelRole.MarketDetails.int:"marketDetails",
ModelRole.DetailsLoading.int:"detailsLoading", ModelRole.DetailsLoading.int:"detailsLoading",
ModelRole.MarketDetailsLoading.int:"marketDetailsLoading" ModelRole.MarketDetailsLoading.int:"marketDetailsLoading",
ModelRole.Visible.int:"visible",
ModelRole.Position.int:"position"
}.toTable }.toTable
method data(self: FlatTokensModel, index: QModelIndex, role: int): QVariant = method data(self: FlatTokensModel, index: QModelIndex, role: int): QVariant =
@ -129,6 +133,10 @@ QtObject:
result = newQVariant(self.delegate.getTokensDetailsLoading()) result = newQVariant(self.delegate.getTokensDetailsLoading())
of ModelRole.MarketDetailsLoading: of ModelRole.MarketDetailsLoading:
result = newQVariant(self.delegate.getTokensMarketValuesLoading()) result = newQVariant(self.delegate.getTokensMarketValuesLoading())
of ModelRole.Visible:
result = newQVariant(self.delegate.getTokenPreferences(item.symbol).visible)
of ModelRole.Position:
result = newQVariant(self.delegate.getTokenPreferences(item.symbol).position)
proc modelsUpdated*(self: FlatTokensModel) = proc modelsUpdated*(self: FlatTokensModel) =
self.beginResetModel() self.beginResetModel()
@ -170,3 +178,11 @@ QtObject:
proc currencyFormatsUpdated*(self: FlatTokensModel) = proc currencyFormatsUpdated*(self: FlatTokensModel) =
for marketDetails in self.tokenMarketDetails: for marketDetails in self.tokenMarketDetails:
marketDetails.updateCurrencyFormat() marketDetails.updateCurrencyFormat()
proc tokenPreferencesUpdated*(self: FlatTokensModel) =
if self.delegate.getFlatTokensList().len > 0:
let index = self.createIndex(0, 0, nil)
let lastindex = self.createIndex(self.delegate.getFlatTokensList().len-1, 0, nil)
defer: index.delete
defer: lastindex.delete
self.dataChanged(index, lastindex, @[ModelRole.Visible.int, ModelRole.Position.int])

View File

@ -10,6 +10,7 @@ type
FlatTokenModelDataSource* = tuple[ FlatTokenModelDataSource* = tuple[
getFlatTokensList: proc(): var seq[TokenItem], getFlatTokensList: proc(): var seq[TokenItem],
getTokenDetails: proc(symbol: string): TokenDetailsItem, getTokenDetails: proc(symbol: string): TokenDetailsItem,
getTokenPreferences: proc(symbol: string): TokenPreferencesItem,
getCommunityTokenDescription: proc(chainId: int, address: string): string, getCommunityTokenDescription: proc(chainId: int, address: string): string,
getTokensDetailsLoading: proc(): bool, getTokensDetailsLoading: proc(): bool,
getTokensMarketValuesLoading: proc(): bool, getTokensMarketValuesLoading: proc(): bool,
@ -18,6 +19,7 @@ type
TokenBySymbolModelDataSource* = tuple[ TokenBySymbolModelDataSource* = tuple[
getTokenBySymbolList: proc(): var seq[TokenBySymbolItem], getTokenBySymbolList: proc(): var seq[TokenBySymbolItem],
getTokenDetails: proc(symbol: string): TokenDetailsItem, getTokenDetails: proc(symbol: string): TokenDetailsItem,
getTokenPreferences: proc(symbol: string): TokenPreferencesItem,
getCommunityTokenDescription: proc(addressPerChain: seq[AddressPerChain]): string, getCommunityTokenDescription: proc(addressPerChain: seq[AddressPerChain]): string,
getTokensDetailsLoading: proc(): bool, getTokensDetailsLoading: proc(): bool,
getTokensMarketValuesLoading: proc(): bool, getTokensMarketValuesLoading: proc(): bool,

View File

@ -70,8 +70,7 @@ method load*(self: Module) =
self.events.on(SIGNAL_TOKENS_PRICES_UPDATED) do(e: Args): self.events.on(SIGNAL_TOKENS_PRICES_UPDATED) do(e: Args):
self.view.tokensMarketValuesUpdated() self.view.tokensMarketValuesUpdated()
self.events.on(SIGNAL_TOKEN_PREFERENCES_UPDATED) do(e: Args): self.events.on(SIGNAL_TOKEN_PREFERENCES_UPDATED) do(e: Args):
let args = ResultArgs(e) self.view.tokenPreferencesUpdated()
self.view.tokenPreferencesUpdated(args.success)
self.events.on(SIGNAL_COMMUNITY_TOKENS_DETAILS_LOADED) do(e: Args): self.events.on(SIGNAL_COMMUNITY_TOKENS_DETAILS_LOADED) do(e: Args):
self.view.tokensDetailsUpdated() self.view.tokensDetailsUpdated()
@ -116,6 +115,7 @@ method getFlatTokenModelDataSource*(self: Module): FlatTokenModelDataSource =
return ( return (
getFlatTokensList: proc(): var seq[TokenItem] = self.controller.getFlatTokensList(), getFlatTokensList: proc(): var seq[TokenItem] = self.controller.getFlatTokensList(),
getTokenDetails: proc(symbol: string): TokenDetailsItem = self.controller.getTokenDetails(symbol), getTokenDetails: proc(symbol: string): TokenDetailsItem = self.controller.getTokenDetails(symbol),
getTokenPreferences: proc(symbol: string): TokenPreferencesItem = self.controller.getTokenPreferences(symbol),
getCommunityTokenDescription: proc(chainId: int, address: string): string = self.controller.getCommunityTokenDescription(chainId, address), getCommunityTokenDescription: proc(chainId: int, address: string): string = self.controller.getCommunityTokenDescription(chainId, address),
getTokensDetailsLoading: proc(): bool = self.controller.getTokensDetailsLoading(), getTokensDetailsLoading: proc(): bool = self.controller.getTokensDetailsLoading(),
getTokensMarketValuesLoading: proc(): bool = self.controller.getTokensMarketValuesLoading() getTokensMarketValuesLoading: proc(): bool = self.controller.getTokensMarketValuesLoading()
@ -125,6 +125,7 @@ method getTokenBySymbolModelDataSource*(self: Module): TokenBySymbolModelDataSou
return ( return (
getTokenBySymbolList: proc(): var seq[TokenBySymbolItem] = self.controller.getTokenBySymbolList(), getTokenBySymbolList: proc(): var seq[TokenBySymbolItem] = self.controller.getTokenBySymbolList(),
getTokenDetails: proc(symbol: string): TokenDetailsItem = self.controller.getTokenDetails(symbol), getTokenDetails: proc(symbol: string): TokenDetailsItem = self.controller.getTokenDetails(symbol),
getTokenPreferences: proc(symbol: string): TokenPreferencesItem = self.controller.getTokenPreferences(symbol),
getCommunityTokenDescription: proc(addressPerChain: seq[AddressPerChain]): string = self.controller.getCommunityTokenDescription(addressPerChain), getCommunityTokenDescription: proc(addressPerChain: seq[AddressPerChain]): string = self.controller.getCommunityTokenDescription(addressPerChain),
getTokensDetailsLoading: proc(): bool = self.controller.getTokensDetailsLoading(), getTokensDetailsLoading: proc(): bool = self.controller.getTokensDetailsLoading(),
getTokensMarketValuesLoading: proc(): bool = self.controller.getTokensMarketValuesLoading() getTokensMarketValuesLoading: proc(): bool = self.controller.getTokensMarketValuesLoading()

View File

@ -30,6 +30,8 @@ type
MarketDetails MarketDetails
DetailsLoading DetailsLoading
MarketDetailsLoading MarketDetailsLoading
Visible
Position
QtObject: QtObject:
type TokensBySymbolModel* = ref object of QAbstractListModel type TokensBySymbolModel* = ref object of QAbstractListModel
@ -84,6 +86,8 @@ QtObject:
ModelRole.MarketDetails.int:"marketDetails", ModelRole.MarketDetails.int:"marketDetails",
ModelRole.DetailsLoading.int:"detailsLoading", ModelRole.DetailsLoading.int:"detailsLoading",
ModelRole.MarketDetailsLoading.int:"marketDetailsLoading", ModelRole.MarketDetailsLoading.int:"marketDetailsLoading",
ModelRole.Visible.int:"visible",
ModelRole.Position.int:"position"
}.toTable }.toTable
method data(self: TokensBySymbolModel, index: QModelIndex, role: int): QVariant = method data(self: TokensBySymbolModel, index: QModelIndex, role: int): QVariant =
@ -130,6 +134,10 @@ QtObject:
result = newQVariant(self.delegate.getTokensDetailsLoading()) result = newQVariant(self.delegate.getTokensDetailsLoading())
of ModelRole.MarketDetailsLoading: of ModelRole.MarketDetailsLoading:
result = newQVariant(self.delegate.getTokensMarketValuesLoading()) result = newQVariant(self.delegate.getTokensMarketValuesLoading())
of ModelRole.Visible:
result = newQVariant(self.delegate.getTokenPreferences(item.symbol).visible)
of ModelRole.Position:
result = newQVariant(self.delegate.getTokenPreferences(item.symbol).position)
proc modelsUpdated*(self: TokensBySymbolModel) = proc modelsUpdated*(self: TokensBySymbolModel) =
self.beginResetModel() self.beginResetModel()
@ -179,3 +187,11 @@ QtObject:
proc currencyFormatsUpdated*(self: TokensBySymbolModel) = proc currencyFormatsUpdated*(self: TokensBySymbolModel) =
for marketDetails in self.tokenMarketDetails: for marketDetails in self.tokenMarketDetails:
marketDetails.updateCurrencyFormat() marketDetails.updateCurrencyFormat()
proc tokenPreferencesUpdated*(self: TokensBySymbolModel) =
if self.delegate.getTokenBySymbolList().len > 0:
let index = self.createIndex(0, 0, nil)
let lastindex = self.createIndex(self.delegate.getTokenBySymbolList().len-1, 0, nil)
defer: index.delete
defer: lastindex.delete
self.dataChanged(index, lastindex, @[ModelRole.Visible.int, ModelRole.Position.int])

View File

@ -141,7 +141,9 @@ QtObject:
self.flatTokensModel.currencyFormatsUpdated() self.flatTokensModel.currencyFormatsUpdated()
self.tokensBySymbolModel.currencyFormatsUpdated() self.tokensBySymbolModel.currencyFormatsUpdated()
proc tokenPreferencesUpdated*(self: View, result: bool) {.signal.} proc tokenPreferencesUpdated*(self: View) =
self.flatTokensModel.tokenPreferencesUpdated()
self.tokensBySymbolModel.tokenPreferencesUpdated()
proc updateTokenPreferences*(self: View, tokenPreferencesJson: string) {.slot.} = proc updateTokenPreferences*(self: View, tokenPreferencesJson: string) {.slot.} =
self.delegate.updateTokenPreferences(tokenPreferencesJson) self.delegate.updateTokenPreferences(tokenPreferencesJson)

View File

@ -60,6 +60,8 @@ QtObject:
tokenDetailsTable: Table[string, TokenDetailsItem] tokenDetailsTable: Table[string, TokenDetailsItem]
tokenMarketValuesTable: Table[string, TokenMarketValuesItem] tokenMarketValuesTable: Table[string, TokenMarketValuesItem]
tokenPriceTable: Table[string, float64] tokenPriceTable: Table[string, float64]
tokenPreferencesTable: Table[string, TokenPreferencesItem]
tokenPreferencesJson: string
tokensDetailsLoading: bool tokensDetailsLoading: bool
tokensPricesLoading: bool tokensPricesLoading: bool
tokensMarketDetailsLoading: bool tokensMarketDetailsLoading: bool
@ -69,6 +71,7 @@ QtObject:
proc getCurrency*(self: Service): string proc getCurrency*(self: Service): string
proc rebuildMarketData*(self: Service) proc rebuildMarketData*(self: Service)
proc fetchTokenPreferences(self: Service)
proc delete*(self: Service) = proc delete*(self: Service) =
self.QObject.delete self.QObject.delete
@ -92,6 +95,7 @@ QtObject:
result.tokenDetailsTable = initTable[string, TokenDetailsItem]() result.tokenDetailsTable = initTable[string, TokenDetailsItem]()
result.tokenMarketValuesTable = initTable[string, TokenMarketValuesItem]() result.tokenMarketValuesTable = initTable[string, TokenMarketValuesItem]()
result.tokenPriceTable = initTable[string, float64]() result.tokenPriceTable = initTable[string, float64]()
result.tokenPreferencesTable = initTable[string, TokenPreferencesItem]()
result.tokensDetailsLoading = true result.tokensDetailsLoading = true
result.tokensPricesLoading = true result.tokensPricesLoading = true
result.tokensMarketDetailsLoading = true result.tokensMarketDetailsLoading = true
@ -261,7 +265,9 @@ QtObject:
# Callback to process the response of getSupportedTokensList call # Callback to process the response of getSupportedTokensList call
proc supportedTokensListRetrieved(self: Service, response: string) {.slot.} = proc supportedTokensListRetrieved(self: Service, response: string) {.slot.} =
# this is emited so that the models can know that the seq it depends on has been updated # this is emited so that the models can know that the seq it depends on has been updated
defer: self.events.emit(SIGNAL_TOKENS_LIST_UPDATED, Args()) defer:
self.fetchTokenPreferences()
self.events.emit(SIGNAL_TOKENS_LIST_UPDATED, Args())
try: try:
let parsedJson = response.parseJson let parsedJson = response.parseJson
var errorString: string var errorString: string
@ -506,30 +512,57 @@ QtObject:
self.threadpool.start(arg) self.threadpool.start(arg)
# Token Management # Token Management
proc getTokenPreferences*(self: Service): JsonNode = proc fetchTokenPreferences(self: Service) =
# this is emited so that the models can notify about token preferences being available
defer: self.events.emit(SIGNAL_TOKEN_PREFERENCES_UPDATED, Args())
self.tokenPreferencesJson = "[]"
try: try:
let response = backend.getTokenPreferences() let response = backend.getTokenPreferences()
if not response.error.isNil: if not response.error.isNil:
error "status-go error", procName="getTokenPreferences", errCode=response.error.code, errDesription=response.error.message error "status-go error", procName="fetchTokenPreferences", errCode=response.error.code, errDesription=response.error.message
return return
return response.result
if response.result.isNil or response.result.kind != JArray:
return
self.tokenPreferencesJson = $response.result
for preferences in response.result:
let dto = Json.decode($preferences, TokenPreferencesDto, allowUnknownFields = true)
self.tokenPreferencesTable[dto.key] = TokenPreferencesItem(
key: dto.key,
position: dto.position,
groupPosition: dto.groupPosition,
visible: dto.visible,
communityId: dto.communityId)
except Exception as e: except Exception as e:
error "error: ", procName="getTokenPreferences", errName=e.name, errDesription=e.msg error "error: ", procName="fetchTokenPreferences", errName=e.name, errDesription=e.msg
proc getTokenPreferences*(self: Service, symbol: string): TokenPreferencesItem =
if not self.tokenPreferencesTable.hasKey(symbol):
return TokenPreferencesItem(
key: symbol,
position: high(int),
groupPosition: high(int),
visible: true,
communityId: ""
)
return self.tokenPreferencesTable[symbol]
proc getTokenPreferencesJson*(self: Service): string =
if len(self.tokenPreferencesJson) == 0:
self.fetchTokenPreferences()
return self.tokenPreferencesJson
proc updateTokenPreferences*(self: Service, tokenPreferencesJson: string) = proc updateTokenPreferences*(self: Service, tokenPreferencesJson: string) =
var updated = false
try: try:
let preferencesJson = parseJson(tokenPreferencesJson) let preferencesJson = parseJson(tokenPreferencesJson)
var tokenPreferences: seq[TokenPreferences] var tokenPreferences: seq[TokenPreferencesDto]
if preferencesJson.kind == JArray: if preferencesJson.kind == JArray:
for preferences in preferencesJson: for preferences in preferencesJson:
add(tokenPreferences, fromJson(preferences, TokenPreferences)) add(tokenPreferences, Json.decode($preferences, TokenPreferencesDto, allowUnknownFields = false))
let response = backend.updateTokenPreferences(tokenPreferences) let response = backend.updateTokenPreferences(tokenPreferences)
if not response.error.isNil: if not response.error.isNil:
raise newException(CatchableError, response.error.message) raise newException(CatchableError, response.error.message)
updated = true self.fetchTokenPreferences()
except Exception as e: except Exception as e:
error "error: ", procName="updateTokenPreferences", errName=e.name, errDesription=e.msg error "error: ", procName="updateTokenPreferences", errName=e.name, errDesription=e.msg
self.events.emit(SIGNAL_TOKEN_PREFERENCES_UPDATED, ResultArgs(success: updated))

View File

@ -89,6 +89,23 @@ proc `$`*(self: TokenDetailsItem): string =
assetWebsiteUrl: {self.assetWebsiteUrl} assetWebsiteUrl: {self.assetWebsiteUrl}
]""" ]"""
type
TokenPreferencesItem* = ref object of RootObj
key*: string
position*: int
groupPosition*: int
visible*: bool
communityId*: string
proc `$`*(self: TokenPreferencesItem): string =
result = fmt"""TokenPreferencesItem[
key: {self.key},
position: {self.position},
groupPosition: {self.groupPosition},
visible: {self.visible},
communityId: {self.communityId}
]"""
type type
TokenMarketValuesItem* = object TokenMarketValuesItem* = object
marketCap*: float64 marketCap*: float64

View File

@ -56,21 +56,13 @@ type
activityTypes* {.serializedFieldName("activityTypes").}: seq[int] activityTypes* {.serializedFieldName("activityTypes").}: seq[int]
readType* {.serializedFieldName("readType").}: int readType* {.serializedFieldName("readType").}: int
TokenPreferences* = ref object of RootObj TokenPreferencesDto* = ref object of RootObj
key* {.serializedFieldName("key").}: string key* {.serializedFieldName("key").}: string
position* {.serializedFieldName("position").}: int position* {.serializedFieldName("position").}: int
groupPosition* {.serializedFieldName("groupPosition").}: int groupPosition* {.serializedFieldName("groupPosition").}: int
visible* {.serializedFieldName("visible").}: bool visible* {.serializedFieldName("visible").}: bool
communityId* {.serializedFieldName("communityId").}: string communityId* {.serializedFieldName("communityId").}: string
proc fromJson*(t: JsonNode, T: typedesc[TokenPreferences]): TokenPreferences {.inline.} =
result = TokenPreferences()
discard t.getProp("key", result.key)
discard t.getProp("position", result.position)
discard t.getProp("groupPosition", result.groupPosition)
discard t.getProp("visible", result.visible)
discard t.getProp("communityId", result.communityId)
rpc(clientVersion, "web3"): rpc(clientVersion, "web3"):
discard discard
@ -285,7 +277,7 @@ rpc(moveWalletAccount, "accounts"):
toPosition: int toPosition: int
rpc(updateTokenPreferences, "accounts"): rpc(updateTokenPreferences, "accounts"):
preferences: seq[TokenPreferences] preferences: seq[TokenPreferencesDto]
rpc(getTokenPreferences, "accounts"): rpc(getTokenPreferences, "accounts"):
discard discard

View File

@ -53,7 +53,9 @@ Item {
} }
}, },
detailsLoading: false, detailsLoading: false,
image: Qt.resolvedUrl("") image: Qt.resolvedUrl(""),
position: 1,
visible: true
}, },
{ {
tokensKey: "key_SNT", tokensKey: "key_SNT",
@ -92,7 +94,9 @@ Item {
} }
}, },
detailsLoading: false, detailsLoading: false,
image: Qt.resolvedUrl("") image: Qt.resolvedUrl(""),
position: 2,
visible: true
}, },
{ {
tokensKey: "key_MYASST", tokensKey: "key_MYASST",
@ -121,7 +125,9 @@ Item {
} }
}, },
detailsLoading: false, detailsLoading: false,
image: Constants.tokenIcon("ZRX", false) image: Constants.tokenIcon("ZRX", false),
position: 5,
visible: true
} }
] ]
@ -141,42 +147,9 @@ Item {
} }
} }
ManageTokensController {
id: manageTokensController
sourceModel: listModel
serializeAsCollectibles: false
onRequestLoadSettings: {
loadingStarted()
const jsonData = [
{
"key": "ETH",
"position": 1,
"visible": true
},
{
"key": "SNT",
"position": 2,
"visible": true
},
{
"key": "MYASST",
"position": 5,
"visible": true
}
]
loadingFinished(JSON.stringify(jsonData))
}
}
AssetsViewAdaptor { AssetsViewAdaptor {
id: adaptor id: adaptor
controller: manageTokensController
chains: chainsSelector.selection chains: chainsSelector.selection
accounts: accountsSelector.selection accounts: accountsSelector.selection

View File

@ -174,7 +174,6 @@ RightTabBaseView {
} }
tokensModel: RootStore.walletAssetsStore.groupedAccountAssetsModel tokensModel: RootStore.walletAssetsStore.groupedAccountAssetsModel
controller: RootStore.walletAssetsStore.assetsController
formatBalance: (balance, symbol) => { formatBalance: (balance, symbol) => {
return LocaleUtils.currencyAmountToLocaleString( return LocaleUtils.currencyAmountToLocaleString(

View File

@ -12,10 +12,6 @@ import SortFilterProxyModel 0.2
QObject { QObject {
id: root id: root
// Controller providing information about visibility and order defined
// by a user (token management)
required property ManageTokensController controller
/** /**
Expected model structure: Expected model structure:
@ -35,6 +31,8 @@ QObject {
currencyPrice [object] - object holding fiat price details currencyPrice [object] - object holding fiat price details
amount [double] - fiat prace of 1 logical unit of cryptocurrency amount [double] - fiat prace of 1 logical unit of cryptocurrency
detailsLoading [bool] - indicatator if market details are ready to use detailsLoading [bool] - indicatator if market details are ready to use
position [int] - custom order position
visible [bool] - token management visiblity flag
Community related part (relevant for community minted assets, empty otherwise): Community related part (relevant for community minted assets, empty otherwise):
@ -115,15 +113,8 @@ QObject {
readonly property real marketPrice: marketDetails.currencyPrice.amount ?? 0 readonly property real marketPrice: marketDetails.currencyPrice.amount ?? 0
readonly property real marketChangePct24hour: marketDetails.changePct24hour ?? 0 readonly property real marketChangePct24hour: marketDetails.changePct24hour ?? 0
readonly property int position: {
controller.revision
return controller.order(model.symbol)
}
readonly property bool visible: { readonly property bool visible: {
root.controller.revision if (!model.visible)
if (!root.controller.filterAcceptsSymbol(model.symbol))
return false return false
if (hasCommunityId) if (hasCommunityId)
@ -184,9 +175,10 @@ QObject {
expectedRoles: expectedRoles:
["tokensKey", "symbol", "image", "balances", "decimals", ["tokensKey", "symbol", "image", "balances", "decimals",
"detailsLoading", "marketDetails", "communityId", "communityImage"] "detailsLoading", "marketDetails", "communityId", "communityImage",
"position", "visible"]
exposedRoles: exposedRoles:
["key", "error", "balance", "balanceText", "position", "icon", ["key", "error", "balance", "balanceText", "icon",
"visible", "canBeHidden", "marketDetailsAvailable", "marketDetailsLoading", "visible", "canBeHidden", "marketDetailsAvailable", "marketDetailsLoading",
"marketPrice", "marketChangePct24hour", "communityIcon"] "marketPrice", "marketChangePct24hour", "communityIcon"]
} }