From 085bf762a57b8137d781bc2caaf28753afb9d06f Mon Sep 17 00:00:00 2001 From: Khushboo Mehta Date: Tue, 20 Feb 2024 10:04:39 +0100 Subject: [PATCH] feat(@desktop/wallet): Update token details view closes #12373 --- src/app/modules/main/module.nim | 3 +- .../wallet_section/all_tokens/controller.nim | 12 +- .../all_tokens/flat_tokens_model.nim | 8 +- .../all_tokens/io_interface.nim | 2 + .../main/wallet_section/all_tokens/module.nim | 10 +- .../all_tokens/token_by_symbol_model.nim | 7 +- .../modules/main/wallet_section/module.nim | 6 +- .../service/community_tokens/service.nim | 14 + storybook/pages/AssetsViewPage.qml | 18 +- storybook/src/Models/TokensBySymbolModel.qml | 4 +- .../StatusQ/Components/StatusChartPanel.qml | 3 +- ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml | 4 + .../views/EditCommunityTokenView.qml | 2 +- .../Wallet/controls/ActivityFilterTagItem.qml | 2 +- .../controls/CollectibleDetailsHeader.qml | 3 +- .../controls/InformationTileAssetDetails.qml | 42 + ui/app/AppLayouts/Wallet/controls/qmldir | 1 + .../Wallet/panels/ActivityFilterPanel.qml | 16 +- .../AppLayouts/Wallet/popups/ReceiveModal.qml | 3 +- .../Wallet/views/AssetsDetailView.qml | 748 ++++++++++-------- .../AppLayouts/Wallet/views/RightTabView.qml | 9 +- .../shared/controls/AssetsDetailsHeader.qml | 178 +++-- ui/imports/shared/controls/InformationTag.qml | 20 +- 23 files changed, 641 insertions(+), 474 deletions(-) create mode 100644 ui/app/AppLayouts/Wallet/controls/InformationTileAssetDetails.qml diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index ce2b0df9f1..ad6ec88ec1 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -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, diff --git a/src/app/modules/main/wallet_section/all_tokens/controller.nim b/src/app/modules/main/wallet_section/all_tokens/controller.nim index 3571f9bc54..e30e8330c3 100644 --- a/src/app/modules/main/wallet_section/all_tokens/controller.nim +++ b/src/app/modules/main/wallet_section/all_tokens/controller.nim @@ -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) diff --git a/src/app/modules/main/wallet_section/all_tokens/flat_tokens_model.nim b/src/app/modules/main/wallet_section/all_tokens/flat_tokens_model.nim index 0074446564..76d6b2e8bf 100644 --- a/src/app/modules/main/wallet_section/all_tokens/flat_tokens_model.nim +++ b/src/app/modules/main/wallet_section/all_tokens/flat_tokens_model.nim @@ -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) diff --git a/src/app/modules/main/wallet_section/all_tokens/io_interface.nim b/src/app/modules/main/wallet_section/all_tokens/io_interface.nim index 0158100f73..b7d64bfc92 100644 --- a/src/app/modules/main/wallet_section/all_tokens/io_interface.nim +++ b/src/app/modules/main/wallet_section/all_tokens/io_interface.nim @@ -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, ] diff --git a/src/app/modules/main/wallet_section/all_tokens/module.nim b/src/app/modules/main/wallet_section/all_tokens/module.nim index df7c547fca..99c7c98ed8 100644 --- a/src/app/modules/main/wallet_section/all_tokens/module.nim +++ b/src/app/modules/main/wallet_section/all_tokens/module.nim @@ -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() ) diff --git a/src/app/modules/main/wallet_section/all_tokens/token_by_symbol_model.nim b/src/app/modules/main/wallet_section/all_tokens/token_by_symbol_model.nim index b379462daa..4de1a6860a 100644 --- a/src/app/modules/main/wallet_section/all_tokens/token_by_symbol_model.nim +++ b/src/app/modules/main/wallet_section/all_tokens/token_by_symbol_model.nim @@ -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) diff --git a/src/app/modules/main/wallet_section/module.nim b/src/app/modules/main/wallet_section/module.nim index 2a8ee0a666..95a00ff6b0 100644 --- a/src/app/modules/main/wallet_section/module.nim +++ b/src/app/modules/main/wallet_section/module.nim @@ -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, diff --git a/src/app_service/service/community_tokens/service.nim b/src/app_service/service/community_tokens/service.nim index bd11c9931e..797662b66d 100644 --- a/src/app_service/service/community_tokens/service.nim +++ b/src/app_service/service/community_tokens/service.nim @@ -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: diff --git a/storybook/pages/AssetsViewPage.qml b/storybook/pages/AssetsViewPage.qml index ee205d3572..32f6246b9a 100644 --- a/storybook/pages/AssetsViewPage.qml +++ b/storybook/pages/AssetsViewPage.qml @@ -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 diff --git a/storybook/src/Models/TokensBySymbolModel.qml b/storybook/src/Models/TokensBySymbolModel.qml index b5d2bc4725..0c34e87b17 100644 --- a/storybook/src/Models/TokensBySymbolModel.qml +++ b/storybook/src/Models/TokensBySymbolModel.qml @@ -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}), diff --git a/ui/StatusQ/src/StatusQ/Components/StatusChartPanel.qml b/ui/StatusQ/src/StatusQ/Components/StatusChartPanel.qml index c5e69997ff..59f1ce351a 100644 --- a/ui/StatusQ/src/StatusQ/Components/StatusChartPanel.qml +++ b/ui/StatusQ/src/StatusQ/Components/StatusChartPanel.qml @@ -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); diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml b/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml index 57297be7f4..161fee4b77 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/Utils.qml @@ -284,6 +284,10 @@ QtObject { } return "" } + + function stripHttpsAndwwwFromUrl(text) { + return text.replace(/http(s)?(:)?(\/\/)?|(\/\/)?(www\.)?(\/)/gim, '') + } } diff --git a/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml b/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml index f80ff6be9a..3e2141f808 100644 --- a/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml +++ b/ui/app/AppLayouts/Communities/views/EditCommunityTokenView.qml @@ -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 { diff --git a/ui/app/AppLayouts/Wallet/controls/ActivityFilterTagItem.qml b/ui/app/AppLayouts/Wallet/controls/ActivityFilterTagItem.qml index b289139aa7..22fdfaae54 100644 --- a/ui/app/AppLayouts/Wallet/controls/ActivityFilterTagItem.qml +++ b/ui/app/AppLayouts/Wallet/controls/ActivityFilterTagItem.qml @@ -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 { diff --git a/ui/app/AppLayouts/Wallet/controls/CollectibleDetailsHeader.qml b/ui/app/AppLayouts/Wallet/controls/CollectibleDetailsHeader.qml index 77b263a1cc..31de47f490 100644 --- a/ui/app/AppLayouts/Wallet/controls/CollectibleDetailsHeader.qml +++ b/ui/app/AppLayouts/Wallet/controls/CollectibleDetailsHeader.qml @@ -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 diff --git a/ui/app/AppLayouts/Wallet/controls/InformationTileAssetDetails.qml b/ui/app/AppLayouts/Wallet/controls/InformationTileAssetDetails.qml new file mode 100644 index 0000000000..3c00e2df94 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/controls/InformationTileAssetDetails.qml @@ -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 + } + } +} diff --git a/ui/app/AppLayouts/Wallet/controls/qmldir b/ui/app/AppLayouts/Wallet/controls/qmldir index 61e60f4e29..4f7f38ae48 100644 --- a/ui/app/AppLayouts/Wallet/controls/qmldir +++ b/ui/app/AppLayouts/Wallet/controls/qmldir @@ -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 diff --git a/ui/app/AppLayouts/Wallet/panels/ActivityFilterPanel.qml b/ui/app/AppLayouts/Wallet/panels/ActivityFilterPanel.qml index c9bf8f0a5f..240e2f67f0 100644 --- a/ui/app/AppLayouts/Wallet/panels/ActivityFilterPanel.qml +++ b/ui/app/AppLayouts/Wallet/panels/ActivityFilterPanel.qml @@ -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 { diff --git a/ui/app/AppLayouts/Wallet/popups/ReceiveModal.qml b/ui/app/AppLayouts/Wallet/popups/ReceiveModal.qml index 241050651f..3819bf138b 100644 --- a/ui/app/AppLayouts/Wallet/popups/ReceiveModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/ReceiveModal.qml @@ -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()) } } diff --git a/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml b/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml index a6c9727203..3694ed00e3 100644 --- a/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml +++ b/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml @@ -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 + } } } } diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index 4b614d3a83..cbf9b7d432 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -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 diff --git a/ui/imports/shared/controls/AssetsDetailsHeader.qml b/ui/imports/shared/controls/AssetsDetailsHeader.qml index e3cce05835..6a3b8df73a 100644 --- a/ui/imports/shared/controls/AssetsDetailsHeader.qml +++ b/ui/imports/shared/controls/AssetsDetailsHeader.qml @@ -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 + } } } } diff --git a/ui/imports/shared/controls/InformationTag.qml b/ui/imports/shared/controls/InformationTag.qml index cfa2816244..b081634801 100644 --- a/ui/imports/shared/controls/InformationTag.qml +++ b/ui/imports/shared/controls/InformationTag.qml @@ -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