feat(@desktop/wallet): Update token details view

closes #12373
This commit is contained in:
Khushboo Mehta 2024-02-20 10:04:39 +01:00 committed by Khushboo-dev-cpp
parent aed61b68b1
commit 085bf762a5
23 changed files with 641 additions and 474 deletions

View File

@ -209,7 +209,8 @@ proc newModule*[T](
result, events, tokenService, collectibleService, currencyService,
transactionService, walletAccountService,
settingsService, savedAddressService, networkService, accountsService,
keycardService, nodeService, networkConnectionService, devicesService
keycardService, nodeService, networkConnectionService, devicesService,
communityTokensService
)
result.browserSectionModule = browser_section_module.newModule(
result, events, bookmarkService, settingsService, networkService,

View File

@ -8,6 +8,7 @@ import app_service/service/wallet_account/service as wallet_account_service
import app/modules/shared_models/currency_amount
import app_service/service/currency/dto
import app_service/service/settings/service as settings_service
import app_service/service/community_tokens/service as community_tokens_service
type
Controller* = ref object of RootObj
@ -16,6 +17,7 @@ type
tokenService: token_service.Service
walletAccountService: wallet_account_service.Service
settingsService: settings_service.Service
communityTokensService: community_tokens_service.Service
displayAssetsBelowBalanceThreshold: CurrencyAmount
proc newController*(
@ -23,7 +25,8 @@ proc newController*(
events: EventEmitter,
tokenService: token_service.Service,
walletAccountService: wallet_account_service.Service,
settingsService: settings_service.Service
settingsService: settings_service.Service,
communityTokensService: community_tokens_service.Service
): Controller =
result = Controller()
result.events = events
@ -31,6 +34,7 @@ proc newController*(
result.tokenService = tokenService
result.walletAccountService = walletAccountService
result.settingsService = settingsService
result.communityTokensService = communityTokensService
proc delete*(self: Controller) =
discard
@ -85,6 +89,12 @@ proc getTokensDetailsLoading*(self: Controller): bool =
proc getTokensMarketValuesLoading*(self: Controller): bool =
self.tokenService.getTokensMarketValuesLoading()
proc getCommunityTokenDescription*(self: Controller, addressPerChain: seq[AddressPerChain]): string =
self.communityTokensService.getCommunityTokenDescription(addressPerChain)
proc getCommunityTokenDescription*(self: Controller, chainId: int, address: string): string =
self.communityTokensService.getCommunityTokenDescription(chainId, address)
proc updateTokenPreferences*(self: Controller, tokenPreferencesJson: string) =
self.tokenService.updateTokenPreferences(tokenPreferencesJson)

View File

@ -114,9 +114,11 @@ QtObject:
of ModelRole.CommunityId:
result = newQVariant(item.communityId)
of ModelRole.Description:
let tokenDetails = self.delegate.getTokenDetails(item.symbol)
result = if not tokenDetails.isNil: newQVariant(tokenDetails.description)
else: newQVariant("")
result = if not item.communityId.isEmptyOrWhitespace:
newQVariant(self.delegate.getCommunityTokenDescription(item.chainId, item.address))
else:
if self.delegate.getTokensDetailsLoading(): newQVariant("")
else: newQVariant(self.delegate.getTokenDetails(item.symbol).description)
of ModelRole.WebsiteUrl:
let tokenDetails = self.delegate.getTokenDetails(item.symbol)
result = if not tokenDetails.isNil: newQVariant(tokenDetails.assetWebsiteUrl)

View File

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

View File

@ -11,6 +11,7 @@ import app_service/service/wallet_account/service as wallet_account_service
import app_service/service/token/dto
import app_service/service/currency/service
import app_service/service/settings/service as settings_service
import app_service/service/community_tokens/service as community_tokens_service
export io_interface
@ -28,13 +29,14 @@ proc newModule*(
events: EventEmitter,
tokenService: token_service.Service,
walletAccountService: wallet_account_service.Service,
settingsService: settings_service.Service
settingsService: settings_service.Service,
communityTokensService: community_tokens_service.Service
): Module =
result = Module()
result.delegate = delegate
result.events = events
result.view = newView(result)
result.controller = controller.newController(result, events, tokenService, walletAccountService, settingsService)
result.controller = controller.newController(result, events, tokenService, walletAccountService, settingsService, communityTokensService)
result.moduleLoaded = false
result.addresses = @[]
@ -67,6 +69,8 @@ method load*(self: Module) =
self.events.on(SIGNAL_TOKEN_PREFERENCES_UPDATED) do(e: Args):
let args = ResultArgs(e)
self.view.tokenPreferencesUpdated(args.success)
self.events.on(SIGNAL_COMMUNITY_TOKENS_DETAILS_LOADED) do(e: Args):
self.view.tokensDetailsUpdated()
self.events.on(SIGNAL_CURRENCY_FORMATS_UPDATED) do(e:Args):
self.view.currencyFormatsUpdated()
@ -109,6 +113,7 @@ method getFlatTokenModelDataSource*(self: Module): FlatTokenModelDataSource =
return (
getFlatTokensList: proc(): var seq[TokenItem] = self.controller.getFlatTokensList(),
getTokenDetails: proc(symbol: string): TokenDetailsItem = self.controller.getTokenDetails(symbol),
getCommunityTokenDescription: proc(chainId: int, address: string): string = self.controller.getCommunityTokenDescription(chainId, address),
getTokensDetailsLoading: proc(): bool = self.controller.getTokensDetailsLoading(),
getTokensMarketValuesLoading: proc(): bool = self.controller.getTokensMarketValuesLoading()
)
@ -117,6 +122,7 @@ method getTokenBySymbolModelDataSource*(self: Module): TokenBySymbolModelDataSou
return (
getTokenBySymbolList: proc(): var seq[TokenBySymbolItem] = self.controller.getTokenBySymbolList(),
getTokenDetails: proc(symbol: string): TokenDetailsItem = self.controller.getTokenDetails(symbol),
getCommunityTokenDescription: proc(addressPerChain: seq[AddressPerChain]): string = self.controller.getCommunityTokenDescription(addressPerChain),
getTokensDetailsLoading: proc(): bool = self.controller.getTokensDetailsLoading(),
getTokensMarketValuesLoading: proc(): bool = self.controller.getTokensMarketValuesLoading()
)

View File

@ -116,8 +116,11 @@ QtObject:
of ModelRole.CommunityId:
result = newQVariant(item.communityId)
of ModelRole.Description:
result = if not item.communityId.isEmptyOrWhitespace or self.delegate.getTokensDetailsLoading() : newQVariant("")
else: newQVariant(self.delegate.getTokenDetails(item.symbol).description)
result = if not item.communityId.isEmptyOrWhitespace:
newQVariant(self.delegate.getCommunityTokenDescription(item.addressPerChainId))
else:
if self.delegate.getTokensDetailsLoading() : newQVariant("")
else: newQVariant(self.delegate.getTokenDetails(item.symbol).description)
of ModelRole.WebsiteUrl:
result = if not item.communityId.isEmptyOrWhitespace or self.delegate.getTokensDetailsLoading() : newQVariant("")
else: newQVariant(self.delegate.getTokenDetails(item.symbol).assetWebsiteUrl)

View File

@ -37,6 +37,7 @@ import app_service/service/accounts/service as accounts_service
import app_service/service/node/service as node_service
import app_service/service/network_connection/service as network_connection_service
import app_service/service/devices/service as devices_service
import app_service/service/community_tokens/service as community_tokens_service
import backend/collectibles as backend_collectibles
import backend/activity as backend_activity
@ -107,7 +108,8 @@ proc newModule*(
keycardService: keycard_service.Service,
nodeService: node_service.Service,
networkConnectionService: network_connection_service.Service,
devicesService: devices_service.Service
devicesService: devices_service.Service,
communityTokensService: community_tokens_service.Service
): Module =
result = Module()
result.delegate = delegate
@ -121,7 +123,7 @@ proc newModule*(
result.controller = newController(result, settingsService, walletAccountService, currencyService, networkService)
result.accountsModule = accounts_module.newModule(result, events, walletAccountService, networkService, currencyService)
result.allTokensModule = all_tokens_module.newModule(result, events, tokenService, walletAccountService, settingsService)
result.allTokensModule = all_tokens_module.newModule(result, events, tokenService, walletAccountService, settingsService, communityTokensService)
let allCollectiblesModule = all_collectibles_module.newModule(result, events, collectibleService, networkService, walletAccountService, settingsService)
result.allCollectiblesModule = allCollectiblesModule
result.assetsModule = assets_module.newModule(result, events, walletAccountService, networkService, tokenService,

View File

@ -843,6 +843,20 @@ QtObject:
if token.chainId == chainId and token.address == address:
return token
proc getCommunityTokenDescription*(self: Service, chainId: int, address: string): string =
let communityTokens = self.getAllCommunityTokens()
for token in communityTokens:
if token.chainId == chainId and cmpIgnoreCase(token.address, address) == 0:
return token.description
return ""
proc getCommunityTokenDescription*(self: Service, addressPerChain: seq[AddressPerChain]): string =
for apC in addressPerChain:
let description = self.getCommunityTokenDescription(apC.chainId, apC.address)
if not description.isEmptyOrWhitespace:
return description
return ""
proc getCommunityTokenBurnState*(self: Service, chainId: int, contractAddress: string): ContractTransactionStatus =
let burnTransactions = self.transactionService.getPendingTransactionsForType(PendingTransactionTypeDto.BurnCommunityToken)
for transaction in burnTransactions:

View File

@ -131,24 +131,26 @@ SplitView {
onManageTokensRequested: logs.logEvent("onManageTokensRequested")
}
AssetsDetailView {
id: detailsView
ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
currencyStore: d.currencyStore
allNetworksModel: NetworksModel.allNetworks
networkFilters: d.networksChainsCurrentlySelected
Button {
anchors.top: parent.top
text: "go back"
onClicked: stack.currentIndex = 0
}
AssetsDetailView {
id: detailsView
Layout.fillHeight: true
Layout.fillWidth: true
currencyStore: d.currencyStore
allNetworksModel: NetworksModel.allNetworks
networkFilters: d.networksChainsCurrentlySelected
}
}
}
Pane {
SplitView.minimumWidth: 300
SplitView.preferredWidth: 300
SplitView.preferredWidth: 250
ColumnLayout {
spacing: 12

View File

@ -107,7 +107,7 @@ ListModel {
decimals: 0,
type: 1,
communityId: "ddls",
description: "",
description: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout." ,
websiteUrl: "",
marketDetails: {
marketCap: ({amount: 0, symbol: "USD", displayDecimals: 2, stripTrailingZeroes: false}),
@ -159,7 +159,7 @@ ListModel {
decimals: 0,
type: 1,
communityId: "ddls",
description: "",
description: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. ",
websiteUrl: "",
marketDetails: {
marketCap: ({amount: 0, symbol: "USD", displayDecimals: 2, stripTrailingZeroes: false}),

View File

@ -122,7 +122,7 @@ Page {
property bool isTimeRange: false
leftPadding: 0
width: implicitWidth
width: visible ? implicitWidth: 0
onClicked: {
root.headerTabClicked(privateIdentifier, isTimeRange);
}
@ -144,6 +144,7 @@ Page {
for (var j = 0; j < graphsModel.length; j++) {
var graphTab = tabButton.createObject(root, { text: graphsModel[j].text,
enabled: graphsModel[j].enabled,
visible: graphsModel[j].visible,
isTimeRange: false,
privateIdentifier: typeof graphsModel[j].id !== "undefined" ? graphsModel[j].id : null});
graphsTabBar.addItem(graphTab);

View File

@ -284,6 +284,10 @@ QtObject {
}
return ""
}
function stripHttpsAndwwwFromUrl(text) {
return text.replace(/http(s)?(:)?(\/\/)?|(\/\/)?(www\.)?(\/)/gim, '')
}
}

View File

@ -171,7 +171,7 @@ StatusScrollView {
regexValidator.regularExpression: Constants.regularExpressions.ascii
regexValidator.errorMessage: qsTr("Only A-Z, 0-9 and standard punctuation allowed")
onTextChanged: root.token.description
onTextChanged: root.token.description = text
}
CustomStatusInput {

View File

@ -13,7 +13,7 @@ InformationTag {
tagPrimaryLabel.color: Theme.palette.directColor1
tagSecondaryLabel.color: Theme.palette.directColor1
middleLabel.color: Theme.palette.baseColor1
iconAsset.color: Theme.palette.primaryColor1
asset.color: Theme.palette.primaryColor1
secondarylabelMaxWidth: 1000
height: 32
customBackground: Component {

View File

@ -88,7 +88,8 @@ ColumnLayout {
InformationTag {
id: networkTag
readonly property bool isNetworkValid: networkShortName !== ""
image.source: isNetworkValid && networkIconURL !== "" ? Style.svg("tiny/" + networkIconURL) : ""
asset.name: isNetworkValid && networkIconURL !== "" ? Style.svg("tiny/" + networkIconURL) : ""
asset.isImage: true
tagPrimaryLabel.text: isNetworkValid ? networkShortName : "---"
tagPrimaryLabel.color: isNetworkValid ? networkColor : "black"
visible: isNetworkValid

View File

@ -0,0 +1,42 @@
import QtQuick 2.13
import QtQuick.Layouts 1.13
import QtQuick.Controls 2.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
Control {
id: root
property alias primaryText: primaryText.text
property alias primaryLabel: primaryText
property alias content: content.sourceComponent
padding: 12
background: Rectangle {
radius: Style.current.radius
border.width: 1
border.color: Theme.palette.baseColor2
color: Style.current.transparent
}
contentItem: ColumnLayout {
spacing: 4
StatusBaseText {
id: primaryText
Layout.fillWidth: true
font.pixelSize: 13
lineHeight: 18
lineHeightMode: Text.FixedHeight
color: Theme.palette.directColor5
visible: text
elide: Text.ElideRight
}
Loader {
id: content
}
}
}

View File

@ -10,3 +10,4 @@ ManageTokenMenuButton 1.0 ManageTokenMenuButton.qml
ManageTokensCommunityTag 1.0 ManageTokensCommunityTag.qml
ManageTokensDelegate 1.0 ManageTokensDelegate.qml
ManageTokensGroupDelegate 1.0 ManageTokensGroupDelegate.qml
InformationTileAssetDetails 1.0 InformationTileAssetDetails.qml

View File

@ -62,7 +62,7 @@ Column {
return qsTr("to")
}
}
iconAsset.icon: "history"
asset.name: "history"
visible: activityFilterMenu.selectedTime !== Constants.TransactionTimePeriod.All
onClosed: activityFilterStore.setSelectedTimestamp(Constants.TransactionTimePeriod.All)
}
@ -88,7 +88,7 @@ Column {
console.warn("Unhandled type :: ",activityFilterStore.typeFilters[index])
return ""
}
iconAsset.icon: switch(activityFilterStore.typeFilters[index]) {
asset.name: switch(activityFilterStore.typeFilters[index]) {
case Constants.TransactionType.Send:
return "send"
case Constants.TransactionType.Receive:
@ -126,7 +126,7 @@ Column {
console.warn("Unhandled status :: ",activityFilterStore.statusFilters[index])
return ""
}
iconAsset.icon: switch(activityFilterStore.statusFilters[index]) {
asset.name: switch(activityFilterStore.statusFilters[index]) {
case Constants.TransactionStatus.Failed:
return Style.svg("transaction/failed")
case Constants.TransactionStatus.Pending:
@ -139,7 +139,7 @@ Column {
console.warn("Unhandled status :: ",activityFilterStore.statusFilters[index])
return ""
}
iconAsset.color: "transparent"
asset.color: "transparent"
onClosed: activityFilterStore.toggleStatus(status, activityFilterMenu.allStatusChecked)
}
}
@ -148,8 +148,8 @@ Column {
model: activityFilterStore.tokensFilter
delegate: ActivityFilterTagItem {
tagPrimaryLabel.text: modelData
iconAsset.icon: Constants.tokenIcon(modelData)
iconAsset.color: "transparent"
asset.name: Constants.tokenIcon(modelData)
asset.color: "transparent"
onClosed: activityFilterStore.toggleToken(modelData)
}
}
@ -170,8 +170,8 @@ Column {
return "#" + data[2]
return ""
}
iconAsset.icon: activityFilterStore.collectiblesList.getImageUrl(uid)
iconAsset.color: "transparent"
asset.name: activityFilterStore.collectiblesList.getImageUrl(uid)
asset.color: "transparent"
onClosed: activityFilterStore.toggleCollectibles(uid)
Connections {

View File

@ -244,7 +244,8 @@ StatusModal {
delegate: InformationTag {
tagPrimaryLabel.text: model.shortName
tagPrimaryLabel.color: model.chainColor
image.source: Style.svg("tiny/" + model.iconUrl)
asset.name: Style.svg("tiny/" + model.iconUrl)
asset.isImage: true
visible: d.preferredChainIdsArray.includes(model.chainId.toString())
}
}

View File

@ -17,6 +17,8 @@ import shared.stores 1.0
import SortFilterProxyModel 0.2
import "../controls"
/// \beware: heavy shortcuts here, refactor to match the requirements when touching this again
/// \todo split into token history and balance views; they have different requirements that introduce unnecessary complexity
/// \todo take a declarative approach, move logic into the typed backend and remove multiple source of truth (e.g. time ranges)
@ -38,6 +40,7 @@ Item {
readonly property string symbol: !!root.token? root.token.symbol?? "" : ""
property bool marketDetailsLoading: !!root.token? root.token.marketDetailsLoading?? false : false
property bool tokenDetailsLoading: !!root.token? root.token.detailsLoading?? false: false
property bool isCommunityAsset: !!root.token && token.isCommunityAsset !== undefined ? token.isCommunityAsset : false
readonly property LeftJoinModel addressPerChainModel: LeftJoinModel {
leftModel: token && token.addressPerChain ? token.addressPerChain: null
@ -66,23 +69,38 @@ Item {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
width: parent.width
asset.name: token && token.symbol ? Style.png("tokens/%1".arg(token.symbol)) : ""
asset.name: {
if (!token)
return ""
if (token.image)
return token.image
if (token.symbol)
return Style.png("tokens/%1".arg(token.symbol))
return ""
}
asset.isImage: true
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.symbol ? token.currentCurrencyBalance : 0
return currencyStore.formatCurrencyAmount(totalCurrencyBalance, token.symbol)
if (!d.isCommunityAsset) {
let totalCurrencyBalance = token && token.currentCurrencyBalance && token.symbol ? token.currentCurrencyBalance : 0
return currencyStore.formatCurrencyAmount(totalCurrencyBalance, currencyStore.currentCurrency)
}
return ""
}
decimals: token && token.decimals ? token.decimals : 4
balances: token && token.balances ? token.balances: null
allNetworksModel: root.allNetworksModel
isLoading: d.marketDetailsLoading
address: root.address
errorTooltipText: token && token.balances ? networkConnectionStore.getBlockchainNetworkDownTextForToken(token.balances): ""
formatBalance: function(balance){
return LocaleUtils.currencyAmountToLocaleString(currencyStore.getCurrencyAmount(balance, token.symbol))
}
communityTag.visible: d.isCommunityAsset
communityTag.tagPrimaryLabel.text: d.isCommunityAsset ? token.communityName: ""
communityTag.asset.name: d.isCommunityAsset ? token && !!token.communityImage ? token.communityImage : "" : ""
communityTag.asset.isImage: true
}
enum GraphType {
@ -90,345 +108,341 @@ Item {
Balance
}
Loader {
id: graphDetailLoader
width: parent.width
height: 290
StatusScrollView {
id: scrollView
anchors.top: tokenDetailsHeader.bottom
anchors.topMargin: 24
active: root.visible
sourceComponent: StatusChartPanel {
id: graphDetail
property int selectedGraphType: AssetsDetailView.GraphType.Price
property var selectedStore: d.marketValueStore
function dataReady() {
return typeof selectedStore != "undefined"
}
function timeRangeSelected() {
return dataReady() && graphDetail.timeRangeTabBarIndex >= 0 && graphDetail.selectedTimeRange.length > 0
}
readonly property var labelsData: {
return timeRangeSelected()
? selectedStore.timeRange[graphDetail.timeRangeTabBarIndex][graphDetail.selectedTimeRange]
: []
}
readonly property var dataRange: {
return timeRangeSelected()
? selectedStore.dataRange[graphDetail.timeRangeTabBarIndex][graphDetail.selectedTimeRange]
: []
}
readonly property var maxTicksLimit: {
return timeRangeSelected() && typeof selectedStore.maxTicks != "undefined"
? selectedStore.maxTicks[graphDetail.timeRangeTabBarIndex][graphDetail.selectedTimeRange]
: 0
}
graphsModel: [
{text: qsTr("Price"), enabled: true, id: AssetsDetailView.GraphType.Price},
{text: qsTr("Balance"), enabled: true, id: AssetsDetailView.GraphType.Balance},
]
defaultTimeRangeIndexShown: ChartStoreBase.TimeRange.All
timeRangeModel: dataReady() && selectedStore.timeRangeTabsModel
onHeaderTabClicked: (privateIdentifier, isTimeRange) => {
if(!isTimeRange && graphDetail.selectedGraphType !== privateIdentifier) {
graphDetail.selectedGraphType = privateIdentifier
}
if(graphDetail.selectedGraphType === AssetsDetailView.GraphType.Balance) {
graphDetail.updateBalanceStore()
}
if(!isTimeRange) {
graphDetail.selectedStore = graphDetail.selectedGraphType === AssetsDetailView.GraphType.Price ? d.marketValueStore : balanceStore
}
chart.animateToNewData()
}
readonly property var dateToShortLabel: function (value) {
const range = balanceStore.timeRangeStrToEnum(graphDetail.selectedTimeRange)
return range === ChartStoreBase.TimeRange.Weekly || range === ChartStoreBase.TimeRange.Monthly ?
LocaleUtils.getDayMonth(value) :
LocaleUtils.getMonthYear(value)
}
chart.chartType: 'line'
chart.chartData: {
return {
labels: RootStore.marketHistoryIsLoading ? [] : graphDetail.labelsData,
datasets: [{
xAxisId: 'x-axis-1',
yAxisId: 'y-axis-1',
backgroundColor: (Theme.palette.name === "dark") ? 'rgba(136, 176, 255, 0.2)' : 'rgba(67, 96, 223, 0.2)',
borderColor: (Theme.palette.name === "dark") ? 'rgba(136, 176, 255, 1)' : 'rgba(67, 96, 223, 1)',
borderWidth: graphDetail.selectedGraphType === AssetsDetailView.GraphType.Price ? 3 : 2,
pointRadius: 0,
data: RootStore.marketHistoryIsLoading ? [] : graphDetail.dataRange,
parsing: false,
}]
}
}
chart.chartOptions: {
return {
maintainAspectRatio: false,
responsive: true,
legend: {
display: false
},
elements: {
line: {
cubicInterpolationMode: 'monotone' // without it interpolation makes the line too curvy that can extend horizontally farther then data points
}
},
//TODO enable zoom
//zoom: {
// enabled: true,
// drag: true,
// speed: 0.1,
// threshold: 2
//},
//pan:{enabled:true,mode:'x'},
tooltips: {
format: {
enabled: graphDetail.selectedGraphType === AssetsDetailView.GraphType.Balance,
callback: function (value) {
return graphDetail.dateToShortLabel(value)
},
valueCallback: function(value) {
return LocaleUtils.currencyAmountToLocaleString({ amount: value, symbol: RootStore.currencyStore.currentCurrencySymbol, displayDecimals: 2 })
}
},
intersect: false,
displayColors: false,
callbacks: {
label: function(tooltipItem, data) {
let label = data.datasets[tooltipItem.datasetIndex].label || '';
if (label) {
label += ': ';
}
if (graphDetail.selectedGraphType === AssetsDetailView.GraphType.Balance)
return label + tooltipItem.yLabel // already formatted in tooltips.value.callback
const value = LocaleUtils.currencyAmountToLocaleString({ amount: tooltipItem.yLabel, symbol: RootStore.currencyStore.currentCurrencySymbol, displayDecimals: 2 })
return label + value
}
}
},
scales: {
labelFormat: {
callback: function (value) {
return graphDetail.dateToShortLabel(value)
},
enabled: graphDetail.selectedGraphType === AssetsDetailView.GraphType.Balance,
},
xAxes: [{
id: 'x-axis-1',
position: 'bottom',
type: graphDetail.selectedGraphType === AssetsDetailView.GraphType.Price ? 'category' : 'time',
gridLines: {
drawOnChartArea: false,
drawBorder: false,
drawTicks: false,
},
ticks: {
fontSize: 10,
fontColor: (Theme.palette.name === "dark") ? '#909090' : '#939BA1',
padding: 16,
maxRotation: 0,
minRotation: 0,
maxTicksLimit: graphDetail.maxTicksLimit,
},
time: {
minUnit: 'day' // for '7days' timeframe, otherwise labels are '10PM', '10AM', '10PM', etc
}
}],
yAxes: [{
position: 'left',
id: 'y-axis-1',
gridLines: {
borderDash: [8, 4],
drawBorder: false,
drawTicks: false,
color: (Theme.palette.name === "dark") ? '#909090' : '#939BA1'
},
beforeDataLimits: (axis) => {
axis.paddingTop = 25;
axis.paddingBottom = 0;
},
afterDataLimits: (axis) => {
if(axis.min < 0)
axis.min = 0;
},
ticks: {
fontSize: 10,
fontColor: (Theme.palette.name === "dark") ? '#909090' : '#939BA1',
padding: 8,
callback: function(value, index, ticks) {
return LocaleUtils.numberToLocaleString(value)
},
}
}]
}
}
}
LoadingGraphView {
anchors.fill: chart
active: RootStore.marketHistoryIsLoading
}
function updateBalanceStore() {
let selectedTimeRangeEnum = balanceStore.timeRangeStrToEnum(graphDetail.selectedTimeRange)
let currencySymbol = RootStore.currencyStore.currentCurrency
if(!balanceStore.hasData(root.address, root.showAllAccounts, token.symbol, currencySymbol, selectedTimeRangeEnum)) {
RootStore.fetchHistoricalBalanceForTokenAsJson(root.address, root.showAllAccounts, token.symbol, currencySymbol, selectedTimeRangeEnum)
}
}
Connections {
target: balanceStore
function onNewDataReady(address, tokenSymbol, currencySymbol, timeRange) {
if (timeRange === timeRangeStrToEnum(graphDetail.selectedTimeRange)) {
chart.updateToNewData()
}
}
}
Connections {
target: root
function onAddressChanged() { graphDetail.updateBalanceStore() }
}
Connections {
target: d
function onSymbolChanged() { if (d.symbol) graphDetail.updateBalanceStore() }
}
}
}
ColumnLayout {
anchors.top: graphDetailLoader.bottom
anchors.topMargin: 24
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 47
width: parent.width
contentWidth: availableWidth
padding: 0
spacing: Style.current.padding
ColumnLayout {
width: scrollView.availableWidth
spacing: 40
RowLayout {
Layout.fillWidth: true
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Market Cap")
secondaryText: token && token.marketDetails && token.marketDetails.marketCap ? LocaleUtils.currencyAmountToLocaleString(token.marketDetails.marketCap) : Constants.dummyText
isLoading: d.marketDetailsLoading
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Day Low")
secondaryText: token && token.marketDetails && token.marketDetails.lowDay ? LocaleUtils.currencyAmountToLocaleString(token.marketDetails.lowDay) : Constants.dummyText
isLoading: d.marketDetailsLoading
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Day High")
secondaryText: token && token.marketDetails && token.marketDetails.highDay ? LocaleUtils.currencyAmountToLocaleString(token.marketDetails.highDay) : Constants.dummyText
isLoading: d.marketDetailsLoading
}
Item {
Loader {
id: graphDetailLoader
Layout.fillWidth: true
}
InformationTile {
readonly property double changePctHour: token && token.marketDetails ? token.marketDetails.changePctHour : 0
maxWidth: parent.width
primaryText: qsTr("Hour")
secondaryText: "%1%".arg(LocaleUtils.numberToLocaleString(changePctHour, 2))
secondaryLabel.customColor: changePctHour === 0 ? Theme.palette.directColor1 :
changePctHour < 0 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
isLoading: d.marketDetailsLoading
}
InformationTile {
readonly property double changePctDay: token && token.marketDetails ? token.marketDetails.changePctDay : 0
maxWidth: parent.width
primaryText: qsTr("Day")
secondaryText: "%1%".arg(LocaleUtils.numberToLocaleString(changePctDay, 2))
secondaryLabel.customColor: changePctDay === 0 ? Theme.palette.directColor1 :
changePctDay < 0 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
isLoading: d.marketDetailsLoading
}
InformationTile {
readonly property double changePct24hour: token && token.marketDetails ? token.marketDetails.changePct24hour : 0
maxWidth: parent.width
primaryText: qsTr("24 Hours")
secondaryText: "%1%".arg(LocaleUtils.numberToLocaleString(changePct24hour, 2))
secondaryLabel.customColor: changePct24hour === 0 ? Theme.palette.directColor1 :
changePct24hour < 0 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
isLoading: d.marketDetailsLoading
}
}
Layout.preferredHeight: 290
active: root.visible
sourceComponent: StatusChartPanel {
id: graphDetail
StatusTabBar {
Layout.fillWidth: true
Layout.topMargin: Style.current.xlPadding
property int selectedGraphType: AssetsDetailView.GraphType.Price
property var selectedStore: d.marketValueStore
StatusTabButton {
leftPadding: 0
width: implicitWidth
text: qsTr("Overview")
}
}
StackLayout {
id: stack
Layout.fillWidth: true
Layout.fillHeight: true
StatusScrollView {
id: scrollView
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height
topPadding: 8
bottomPadding: 8
contentWidth: availableWidth
Flow {
id: detailsFlow
readonly property bool isOverflowing: detailsFlow.width - tagsLayout.width - tokenDescriptionText.width < 24
spacing: 24
width: scrollView.availableWidth
StatusTextWithLoadingState {
id: tokenDescriptionText
width: Math.max(536 , scrollView.availableWidth - tagsLayout.width - 24)
font.pixelSize: 15
lineHeight: 22
lineHeightMode: Text.FixedHeight
text: token && token.description ? token.description : Constants.dummyText
customColor: Theme.palette.directColor1
elide: Text.ElideRight
wrapMode: Text.Wrap
textFormat: Qt.RichText
loading: d.tokenDetailsLoading
function dataReady() {
return typeof selectedStore != "undefined"
}
ColumnLayout {
id: tagsLayout
spacing: 10
InformationTag {
id: website
Layout.alignment: detailsFlow.isOverflowing ? Qt.AlignLeft : Qt.AlignRight
iconAsset.icon: "browser"
tagPrimaryLabel.text: qsTr("Website")
function timeRangeSelected() {
return dataReady() && graphDetail.timeRangeTabBarIndex >= 0 && graphDetail.selectedTimeRange.length > 0
}
readonly property var labelsData: {
return timeRangeSelected()
? selectedStore.timeRange[graphDetail.timeRangeTabBarIndex][graphDetail.selectedTimeRange]
: []
}
readonly property var dataRange: {
return timeRangeSelected()
? selectedStore.dataRange[graphDetail.timeRangeTabBarIndex][graphDetail.selectedTimeRange]
: []
}
readonly property var maxTicksLimit: {
return timeRangeSelected() && typeof selectedStore.maxTicks != "undefined"
? selectedStore.maxTicks[graphDetail.timeRangeTabBarIndex][graphDetail.selectedTimeRange]
: 0
}
graphsModel: [
{text: qsTr("Price"), enabled: true, id: AssetsDetailView.GraphType.Price, visible: !d.isCommunityAsset},
{text: qsTr("Balance"), enabled: true, id: AssetsDetailView.GraphType.Balance, visible: true},
]
defaultTimeRangeIndexShown: ChartStoreBase.TimeRange.All
timeRangeModel: dataReady() && selectedStore.timeRangeTabsModel
onHeaderTabClicked: (privateIdentifier, isTimeRange) => {
if(!isTimeRange && graphDetail.selectedGraphType !== privateIdentifier) {
graphDetail.selectedGraphType = privateIdentifier
}
if(graphDetail.selectedGraphType === AssetsDetailView.GraphType.Balance) {
graphDetail.updateBalanceStore()
}
if(!isTimeRange) {
graphDetail.selectedStore = graphDetail.selectedGraphType === AssetsDetailView.GraphType.Price ? d.marketValueStore : balanceStore
}
chart.animateToNewData()
}
readonly property var dateToShortLabel: function (value) {
const range = balanceStore.timeRangeStrToEnum(graphDetail.selectedTimeRange)
return range === ChartStoreBase.TimeRange.Weekly || range === ChartStoreBase.TimeRange.Monthly ?
LocaleUtils.getDayMonth(value) :
LocaleUtils.getMonthYear(value)
}
chart.chartType: 'line'
chart.chartData: {
return {
labels: RootStore.marketHistoryIsLoading ? [] : graphDetail.labelsData,
datasets: [{
xAxisId: 'x-axis-1',
yAxisId: 'y-axis-1',
backgroundColor: (Theme.palette.name === "dark") ? 'rgba(136, 176, 255, 0.2)' : 'rgba(67, 96, 223, 0.2)',
borderColor: (Theme.palette.name === "dark") ? 'rgba(136, 176, 255, 1)' : 'rgba(67, 96, 223, 1)',
borderWidth: graphDetail.selectedGraphType === AssetsDetailView.GraphType.Price ? 3 : 2,
pointRadius: 0,
data: RootStore.marketHistoryIsLoading ? [] : graphDetail.dataRange,
parsing: false,
}]
}
}
chart.chartOptions: {
return {
maintainAspectRatio: false,
responsive: true,
legend: {
display: false
},
elements: {
line: {
cubicInterpolationMode: 'monotone' // without it interpolation makes the line too curvy that can extend horizontally farther then data points
}
},
//TODO enable zoom
//zoom: {
// enabled: true,
// drag: true,
// speed: 0.1,
// threshold: 2
//},
//pan:{enabled:true,mode:'x'},
tooltips: {
format: {
enabled: graphDetail.selectedGraphType === AssetsDetailView.GraphType.Balance,
callback: function (value) {
return graphDetail.dateToShortLabel(value)
},
valueCallback: function(value) {
return LocaleUtils.currencyAmountToLocaleString({ amount: value, symbol: RootStore.currencyStore.currentCurrencySymbol, displayDecimals: 2 })
}
},
intersect: false,
displayColors: false,
callbacks: {
label: function(tooltipItem, data) {
let label = data.datasets[tooltipItem.datasetIndex].label || '';
if (label) {
label += ': ';
}
if (graphDetail.selectedGraphType === AssetsDetailView.GraphType.Balance)
return label + tooltipItem.yLabel // already formatted in tooltips.value.callback
const value = LocaleUtils.currencyAmountToLocaleString({ amount: tooltipItem.yLabel, symbol: RootStore.currencyStore.currentCurrencySymbol, displayDecimals: 2 })
return label + value
}
}
},
scales: {
labelFormat: {
callback: function (value) {
return graphDetail.dateToShortLabel(value)
},
enabled: graphDetail.selectedGraphType === AssetsDetailView.GraphType.Balance,
},
xAxes: [{
id: 'x-axis-1',
position: 'bottom',
type: graphDetail.selectedGraphType === AssetsDetailView.GraphType.Price ? 'category' : 'time',
gridLines: {
drawOnChartArea: false,
drawBorder: false,
drawTicks: false,
},
ticks: {
fontSize: 10,
fontColor: (Theme.palette.name === "dark") ? '#909090' : '#939BA1',
padding: 16,
maxRotation: 0,
minRotation: 0,
maxTicksLimit: graphDetail.maxTicksLimit,
},
time: {
minUnit: 'day' // for '7days' timeframe, otherwise labels are '10PM', '10AM', '10PM', etc
}
}],
yAxes: [{
position: 'left',
id: 'y-axis-1',
gridLines: {
borderDash: [8, 4],
drawBorder: false,
drawTicks: false,
color: (Theme.palette.name === "dark") ? '#909090' : '#939BA1'
},
beforeDataLimits: (axis) => {
axis.paddingTop = 25;
axis.paddingBottom = 0;
},
afterDataLimits: (axis) => {
if(axis.min < 0)
axis.min = 0;
},
ticks: {
fontSize: 10,
fontColor: (Theme.palette.name === "dark") ? '#909090' : '#939BA1',
padding: 8,
callback: function(value, index, ticks) {
return LocaleUtils.numberToLocaleString(value)
},
}
}]
}
}
}
LoadingGraphView {
anchors.fill: chart
active: RootStore.marketHistoryIsLoading
}
function updateBalanceStore() {
let selectedTimeRangeEnum = balanceStore.timeRangeStrToEnum(graphDetail.selectedTimeRange)
let currencySymbol = RootStore.currencyStore.currentCurrency
if(!balanceStore.hasData(root.address, root.showAllAccounts, token.symbol, currencySymbol, selectedTimeRangeEnum)) {
RootStore.fetchHistoricalBalanceForTokenAsJson(root.address, root.showAllAccounts, token.symbol, currencySymbol, selectedTimeRangeEnum)
}
}
Connections {
target: balanceStore
function onNewDataReady(address, tokenSymbol, currencySymbol, timeRange) {
if (timeRange === balanceStore.timeRangeStrToEnum(graphDetail.selectedTimeRange)) {
chart.updateToNewData()
}
}
}
Connections {
target: root
function onAddressChanged() { graphDetail.updateBalanceStore() }
}
Connections {
target: d
function onSymbolChanged() { if (d.symbol) graphDetail.updateBalanceStore() }
}
}
}
RowLayout {
Layout.fillWidth: true
visible: !d.isCommunityAsset
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Market Cap")
secondaryText: token && token.marketDetails && token.marketDetails.marketCap ? LocaleUtils.currencyAmountToLocaleString(token.marketDetails.marketCap) : Constants.dummyText
isLoading: d.marketDetailsLoading
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Day Low")
secondaryText: token && token.marketDetails && token.marketDetails.lowDay ? LocaleUtils.currencyAmountToLocaleString(token.marketDetails.lowDay) : Constants.dummyText
isLoading: d.marketDetailsLoading
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Day High")
secondaryText: token && token.marketDetails && token.marketDetails.highDay ? LocaleUtils.currencyAmountToLocaleString(token.marketDetails.highDay) : Constants.dummyText
isLoading: d.marketDetailsLoading
}
Item {
Layout.fillWidth: true
}
InformationTile {
readonly property double changePctHour: token && token.marketDetails ? token.marketDetails.changePctHour : 0
maxWidth: parent.width
primaryText: qsTr("Hour")
secondaryText: "%1%".arg(LocaleUtils.numberToLocaleString(changePctHour, 2))
secondaryLabel.customColor: changePctHour === 0 ? Theme.palette.directColor1 :
changePctHour < 0 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
isLoading: d.marketDetailsLoading
}
InformationTile {
readonly property double changePctDay: token && token.marketDetails ? token.marketDetails.changePctDay : 0
maxWidth: parent.width
primaryText: qsTr("Day")
secondaryText: "%1%".arg(LocaleUtils.numberToLocaleString(changePctDay, 2))
secondaryLabel.customColor: changePctDay === 0 ? Theme.palette.directColor1 :
changePctDay < 0 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
isLoading: d.marketDetailsLoading
}
InformationTile {
readonly property double changePct24hour: token && token.marketDetails ? token.marketDetails.changePct24hour : 0
maxWidth: parent.width
primaryText: qsTr("24 Hours")
secondaryText: "%1%".arg(LocaleUtils.numberToLocaleString(changePct24hour, 2))
secondaryLabel.customColor: changePct24hour === 0 ? Theme.palette.directColor1 :
changePct24hour < 0 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
isLoading: d.marketDetailsLoading
}
}
Flow {
id: detailsFlow
readonly property bool isOverflowing: detailsFlow.width - websiteBlock.width - tokenDescriptionText.width < 24
Layout.fillWidth: true
spacing: 24
StatusTabBar {
width: parent.width
StatusTabButton {
leftPadding: 0
width: implicitWidth
text: qsTr("Overview")
}
visible: tokenDescriptionText.visible
}
StatusTextWithLoadingState {
id: tokenDescriptionText
width: Math.max(536 , scrollView.availableWidth - websiteBlock.width - 24)
font.pixelSize: 15
lineHeight: 22
lineHeightMode: Text.FixedHeight
text: token && token.description ? token.description : d.tokenDetailsLoading ? Constants.dummyText: ""
customColor: Theme.palette.directColor1
elide: Text.ElideRight
wrapMode: Text.Wrap
textFormat: Qt.RichText
loading: d.tokenDetailsLoading
visible: !!text
}
GridLayout{
columnSpacing: 10
rowSpacing: 10
flow: detailsFlow.isOverflowing ? GridLayout.LeftToRight: GridLayout.TopToBottom
InformationTileAssetDetails {
id: websiteBlock
Layout.preferredWidth: 272
visible: !d.isCommunityAsset
primaryText: qsTr("Website")
content: InformationTag {
asset.name : "browser"
tagPrimaryLabel.text: SQUtils.Utils.stripHttpsAndwwwFromUrl(token.websiteUrl)
visible: typeof token != "undefined" && token && token.websiteUrl !== ""
customBackground: Component {
Rectangle {
@ -444,27 +458,59 @@ Item {
onClicked: Global.openLink(token.websiteUrl)
}
}
}
/* TODO :: Issue with not being able to see correct balances after switching assets will be fixed under
https://github.com/status-im/status-desktop/issues/12912 */
Repeater {
Layout.alignment: detailsFlow.isOverflowing ? Qt.AlignLeft : Qt.AlignRight
model: SortFilterProxyModel {
sourceModel: d.addressPerChainModel
filters: ExpressionFilter {
expression: root.networkFilters.split(":").includes(model.chainId+"")
InformationTileAssetDetails {
Layout.preferredWidth: 272
visible: d.isCommunityAsset
primaryText: qsTr("Minted by")
content: InformationTag {
tagPrimaryLabel.text: token && token.communityName ? token.communityName : ""
asset.name: token && token.communityImage ? token.communityImage : ""
asset.isImage: true
customBackground: Component {
Rectangle {
color: Theme.palette.baseColor2
border.width: 1
border.color: "transparent"
radius: 36
}
}
InformationTag {
image.source: Style.svg("tiny/" + model.iconUrl)
tagPrimaryLabel.text: model.chainName
tagSecondaryLabel.text: model.address
customBackground: Component {
Rectangle {
color: Theme.palette.baseColor2
border.width: 1
border.color: "transparent"
radius: 36
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: Global.switchToCommunity(token.communityId)
}
}
}
InformationTileAssetDetails {
Layout.minimumWidth: 272
Layout.preferredWidth: implicitWidth
primaryText: qsTr("Contract")
content: GridLayout {
columnSpacing: 10
rowSpacing: 10
flow: detailsFlow.isOverflowing ? GridLayout.LeftToRight : GridLayout.TopToBottom
Repeater {
model: SortFilterProxyModel {
sourceModel: d.addressPerChainModel
filters: ExpressionFilter {
expression: root.networkFilters.split(":").includes(model.chainId+"")
}
}
delegate: InformationTag {
asset.name: Style.svg("tiny/" + model.iconUrl)
asset.isImage: true
tagPrimaryLabel.text: model.chainName
tagSecondaryLabel.text: SQUtils.Utils.elideText(model.address, 2,4)
customBackground: Component {
Rectangle {
color: Theme.palette.baseColor2
border.width: 1
border.color: "transparent"
radius: 36
}
}
}
}

View File

@ -23,10 +23,6 @@ RightTabBaseView {
signal launchShareAddressModal()
headerButton.onClicked: {
root.launchShareAddressModal()
}
function resetView() {
stack.currentIndex = 0
root.currentTabIndex = 0
@ -38,6 +34,11 @@ RightTabBaseView {
stack.currentIndex = 0;
}
headerButton.onClicked: {
root.launchShareAddressModal()
}
header.visible: stack.currentIndex === 0
StackLayout {
id: stack
anchors.fill: parent

View File

@ -1,5 +1,6 @@
import QtQuick 2.13
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import utils 1.0
import shared.controls 1.0
@ -19,104 +20,135 @@ Control {
property alias primaryText: tokenName.text
property alias secondaryText: cryptoBalance.text
property alias tertiaryText: fiatBalance.text
property alias communityTag: communityTag
property var balances
property int decimals
property var allNetworksModel
property bool isLoading: false
property string errorTooltipText
property string address
property StatusAssetSettings asset: StatusAssetSettings {
width: 40
height: 40
width: 25
height: 25
}
property var formatBalance: function(balance){}
topPadding: Style.current.padding
contentItem: Column {
contentItem: ColumnLayout {
id: mainLayout
spacing: 4
Row {
readonly property bool isOverflowing: parent.width - tokenNameAndIcon.width - communityAndBalances.width < 24
RowLayout {
id: tokenNameAndIcon
Layout.fillWidth: true
spacing: 8
StatusSmartIdenticon {
id: identiconLoader
anchors.verticalCenter: parent.verticalCenter
asset: root.asset
loading: root.isLoading
}
StatusTextWithLoadingState {
id: tokenName
width: Math.min(root.width - identiconLoader.width - cryptoBalance.width - fiatBalance.width - 24, implicitWidth)
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 22
lineHeight: 30
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: root.width-root.asset.width-8
font.pixelSize: 28
font.bold: true
lineHeight: 38
lineHeightMode: Text.FixedHeight
elide: Text.ElideRight
customColor: Theme.palette.directColor1
loading: root.isLoading
}
StatusTextWithLoadingState {
id: cryptoBalance
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 22
lineHeight: 30
lineHeightMode: Text.FixedHeight
customColor: Theme.palette.baseColor1
loading: root.isLoading
}
StatusBaseText {
id: dotSeparator
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -15
font.pixelSize: 50
color: Theme.palette.baseColor1
text: "."
}
StatusTextWithLoadingState {
id: fiatBalance
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 22
lineHeight: 30
lineHeightMode: Text.FixedHeight
customColor: Theme.palette.baseColor1
StatusSmartIdenticon {
Layout.preferredWidth: root.asset.width
Layout.alignment: Qt.AlignHCenter
asset: root.asset
loading: root.isLoading
}
}
Row {
spacing: Style.current.smallPadding
anchors.left: parent.left
anchors.leftMargin: identiconLoader.width
Repeater {
id: chainRepeater
model: root.allNetworksModel
delegate: InformationTag {
readonly property double aggregatedbalance: balancesAggregator.value/(10 ** root.decimals)
SortFilterProxyModel {
id: filteredBalances
sourceModel: root.balances
filters: ValueFilter {
roleName: "chainId"
value: model.chainId
}
}
SumAggregator {
id: balancesAggregator
model: filteredBalances
roleName: "balance"
}
tagPrimaryLabel.text: root.formatBalance(aggregatedbalance)
tagPrimaryLabel.color: model.chainColor
image.source: Style.svg("tiny/%1".arg(model.iconUrl))
GridLayout {
Layout.fillWidth: true
rowSpacing: Style.current.halfPadding
columnSpacing: Style.current.halfPadding
flow: mainLayout.isOverflowing ? GridLayout.TopToBottom: GridLayout.LeftToRight
RowLayout {
Layout.fillWidth: true
spacing: Style.current.halfPadding
StatusTextWithLoadingState {
id: cryptoBalance
Layout.alignment: Qt.AlignHCenter
font.pixelSize: 28
font.bold: true
lineHeight: 38
lineHeightMode: Text.FixedHeight
customColor: Theme.palette.baseColor1
loading: root.isLoading
visible: balancesAggregator.value > 0
rightComponent: StatusFlatRoundButton {
width: 14
height: visible ? 14 : 0
icon.width: 14
icon.height: 14
icon.name: "tiny/warning"
icon.color: Theme.palette.dangerColor1
tooltip.text: root.errorTooltipText
visible: !!root.errorTooltipText
}
StatusTextWithLoadingState {
id: fiatBalance
Layout.alignment: Qt.AlignBottom
Layout.bottomMargin: 2
font.pixelSize: 15
lineHeight: 22
lineHeightMode: Text.FixedHeight
customColor: Theme.palette.baseColor1
loading: root.isLoading
}
}
Item {
id: filler
Layout.fillWidth: true
}
RowLayout {
id: communityAndBalances
Layout.fillWidth: true
spacing: Style.current.halfPadding
InformationTag {
id: communityTag
}
Repeater {
id: chainRepeater
Layout.alignment: Qt.AlignRight
model: root.allNetworksModel
delegate: InformationTag {
readonly property double aggregatedbalance: balancesAggregator.value/(10 ** root.decimals)
SortFilterProxyModel {
id: filteredBalances
sourceModel: root.balances
filters: [
ValueFilter {
roleName: "chainId"
value: model.chainId
},
ValueFilter {
roleName: "account"
value: root.address.toLowerCase()
enabled: !!root.address
}
]
}
SumAggregator {
id: balancesAggregator
model: filteredBalances
roleName: "balance"
}
tagPrimaryLabel.text: root.formatBalance(aggregatedbalance)
tagPrimaryLabel.color: model.chainColor
asset.name: Style.svg("tiny/%1".arg(model.iconUrl))
asset.isImage: true
loading: root.isLoading
visible: balancesAggregator.value > 0
rightComponent: StatusFlatRoundButton {
width: visible ? 14 : 0
height: visible ? 14 : 0
icon.width: 14
icon.height: 14
icon.name: "tiny/warning"
icon.color: Theme.palette.dangerColor1
tooltip.text: root.errorTooltipText
visible: !!root.errorTooltipText
}
}
}
}

View File

@ -11,11 +11,10 @@ import utils 1.0
Control {
id: root
property alias image: image
property alias iconAsset: iconAsset
property alias tagPrimaryLabel: tagPrimaryLabel
property alias tagSecondaryLabel: tagSecondaryLabel
property alias middleLabel: middleLabel
property alias asset: smartIdenticon.asset
property alias rightComponent: rightComponent.sourceComponent
property bool loading: false
property int secondarylabelMaxWidth: 100
@ -45,18 +44,15 @@ Control {
contentItem: RowLayout {
spacing: root.spacing
visible: !root.loading
// FIXME this could be StatusIcon but it can't load images from an arbitrary URL
Image {
id: image
StatusSmartIdenticon {
id: smartIdenticon
Layout.maximumWidth: visible ? 16 : 0
Layout.maximumHeight: visible ? 16 : 0
visible: !!source
}
StatusIcon {
id: iconAsset
Layout.maximumWidth: visible ? 16 : 0
Layout.maximumHeight: visible ? 16 : 0
visible: !!icon
asset.width: visible ? 16 : 0
asset.height: visible ? 16 : 0
asset.bgHeight: visible ? 16 : 0
asset.bgWidth: visible ? 16 : 0
visible: !!asset.name
}
StatusBaseText {
id: tagPrimaryLabel