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 451a18b167..5436936c23 100644 --- a/src/app/modules/main/wallet_section/all_tokens/module.nim +++ b/src/app/modules/main/wallet_section/all_tokens/module.nim @@ -60,11 +60,11 @@ method getHistoricalDataForToken*(self: Module, symbol: string, currency: string self.controller.getHistoricalDataForToken(symbol, currency, ALL_TIME_RANGE) method tokenHistoricalDataResolved*(self: Module, tokenDetails: string) = - self.view.tokenHistoricalDataReady(tokenDetails) + self.view.setTokenHistoricalDataReady(tokenDetails) method fetchHistoricalBalanceForTokenAsJson*(self: Module, address: string, symbol: string, timeIntervalEnum: int) = self.controller.fetchHistoricalBalanceForTokenAsJson(address, symbol, timeIntervalEnum) method tokenBalanceHistoryDataResolved*(self: Module, balanceHistoryJson: string) = - self.view.tokenBalanceHistoryDataReady(balanceHistoryJson) + self.view.setTokenBalanceHistoryDataReady(balanceHistoryJson) diff --git a/src/app/modules/main/wallet_section/all_tokens/view.nim b/src/app/modules/main/wallet_section/all_tokens/view.nim index abf7262b66..2c77b243c8 100644 --- a/src/app/modules/main/wallet_section/all_tokens/view.nim +++ b/src/app/modules/main/wallet_section/all_tokens/view.nim @@ -6,6 +6,8 @@ QtObject: type View* = ref object of QObject delegate: io_interface.AccessInterface + marketHistoryIsLoading: bool + balanceHistoryIsLoading: bool proc delete*(self: View) = self.QObject.delete @@ -14,19 +16,61 @@ QtObject: new(result, delete) result.QObject.setup result.delegate = delegate + result.marketHistoryIsLoading = false + result.balanceHistoryIsLoading = false proc load*(self: View) = self.delegate.viewDidLoad() + proc marketHistoryIsLoadingChanged*(self: View) {.signal.} + + proc getMarketHistoryIsLoading(self: View): QVariant {.slot.} = + return newQVariant(self.marketHistoryIsLoading) + + proc setMarketHistoryIsLoading(self: View, isLoading: bool) = + if self.marketHistoryIsLoading == isLoading: + return + self.marketHistoryIsLoading = isLoading + self.marketHistoryIsLoadingChanged() + + QtProperty[QVariant] marketHistoryIsLoading: + read = getMarketHistoryIsLoading + notify = marketHistoryIsLoadingChanged + + proc balanceHistoryIsLoadingChanged*(self: View) {.signal.} + + proc getBalanceHistoryIsLoading(self: View): QVariant {.slot.} = + return newQVariant(self.balanceHistoryIsLoading) + + proc setBalanceHistoryIsLoading(self: View, isLoading: bool) = + if self.balanceHistoryIsLoading == isLoading: + return + self.balanceHistoryIsLoading = isLoading + self.balanceHistoryIsLoadingChanged() + + QtProperty[QVariant] balanceHistoryIsLoading: + read = getBalanceHistoryIsLoading + notify = balanceHistoryIsLoadingChanged + proc findTokenSymbolByAddress*(self: View, address: string): string {.slot.} = return self.delegate.findTokenSymbolByAddress(address) proc getHistoricalDataForToken*(self: View, symbol: string, currency: string) {.slot.} = + self.setMarketHistoryIsLoading(true) self.delegate.getHistoricalDataForToken(symbol, currency) proc tokenHistoricalDataReady*(self: View, tokenDetails: string) {.signal.} + proc setTokenHistoricalDataReady*(self: View, tokenDetails: string) = + self.setMarketHistoryIsLoading(false) + self.tokenHistoricalDataReady(tokenDetails) + proc fetchHistoricalBalanceForTokenAsJson*(self: View, address: string, symbol: string, timeIntervalEnum: int) {.slot.} = + self.setBalanceHistoryIsLoading(true) self.delegate.fetchHistoricalBalanceForTokenAsJson(address, symbol, timeIntervalEnum) - proc tokenBalanceHistoryDataReady*(self: View, balanceHistoryJson: string) {.signal.} \ No newline at end of file + proc tokenBalanceHistoryDataReady*(self: View, balanceHistoryJson: string) {.signal.} + + proc setTokenBalanceHistoryDataReady*(self: View, balanceHistoryJson: string) = + self.setBalanceHistoryIsLoading(false) + self.tokenBalanceHistoryDataReady(balanceHistoryJson) diff --git a/src/app/modules/main/wallet_section/module.nim b/src/app/modules/main/wallet_section/module.nim index b073c25d9e..e267d2f51a 100644 --- a/src/app/modules/main/wallet_section/module.nim +++ b/src/app/modules/main/wallet_section/module.nim @@ -120,6 +120,7 @@ method load*(self: Module) = self.setTotalCurrencyBalance() self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args): self.setTotalCurrencyBalance() + self.view.setTokensLoading(false) self.controller.init() self.view.load() diff --git a/src/app/modules/main/wallet_section/view.nim b/src/app/modules/main/wallet_section/view.nim index 854a96689a..f276150d77 100644 --- a/src/app/modules/main/wallet_section/view.nim +++ b/src/app/modules/main/wallet_section/view.nim @@ -11,6 +11,7 @@ QtObject: totalCurrencyBalance: CurrencyAmount signingPhrase: string isMnemonicBackedUp: bool + tokensLoading: bool proc setup(self: View) = self.QObject.setup @@ -21,6 +22,7 @@ QtObject: proc newView*(delegate: io_interface.AccessInterface): View = new(result, delete) result.delegate = delegate + result.tokensLoading = true result.setup() proc load*(self: View) = @@ -49,6 +51,15 @@ QtObject: read = getTotalCurrencyBalance notify = totalCurrencyBalanceChanged + proc tokensLoadingChanged*(self: View) {.signal.} + + proc getTokensLoading(self: View): QVariant {.slot.} = + return newQVariant(self.tokensLoading) + + QtProperty[QVariant] tokensLoading: + read = getTokensLoading + notify = tokensLoadingChanged + proc getSigningPhrase(self: View): QVariant {.slot.} = return newQVariant(self.signingPhrase) @@ -82,6 +93,10 @@ QtObject: proc getCurrencyAmountAsJson*(self: View, amount: float, symbol: string): string {.slot.} = return $self.delegate.getCurrencyAmount(amount, symbol).toJsonNode() + proc setTokensLoading*(self: View, loading: bool) = + self.tokensLoading = loading + self.tokensLoadingChanged() + proc setData*(self: View, currency, signingPhrase: string, mnemonicBackedUp: bool) = self.currentCurrency = currency self.signingPhrase = signingPhrase diff --git a/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml b/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml index 4662816fdf..3906c9a322 100644 --- a/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml +++ b/ui/app/AppLayouts/Wallet/panels/WalletHeader.qml @@ -40,12 +40,13 @@ Item { font.bold: true text: currentAccount.name } - StatusBaseText { + StatusTextWithLoadingState { Layout.alignment: Qt.AlignVCenter font.pixelSize: 28 font.bold: true - color: Theme.palette.baseColor1 + customColor: Theme.palette.baseColor1 text: LocaleUtils.currencyAmountToLocaleString(root.currentAccount.currencyBalance) + loading: root.walletStore.tokensLoading } } diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index 0a95756a78..6f7a8cca5f 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -27,6 +27,7 @@ QtObject { property var currencyStore: SharedStore.RootStore.currencyStore property string currentCurrency: currencyStore.currentCurrency property var totalCurrencyBalance: walletSection.totalCurrencyBalance + property bool tokensLoading: walletSection.tokensLoading property string signingPhrase: walletSection.signingPhrase property string mnemonicBackedUp: walletSection.isMnemonicBackedUp diff --git a/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml b/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml index 5313d09e29..ee214b64d1 100644 --- a/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml +++ b/ui/app/AppLayouts/Wallet/views/AssetsDetailView.qml @@ -145,7 +145,7 @@ Item { chart.chartType: 'line' chart.chartData: { return { - labels: graphDetail.labelsData, + labels: RootStore.marketHistoryIsLoading ? [] : graphDetail.labelsData, datasets: [{ xAxisId: 'x-axis-1', yAxisId: 'y-axis-1', @@ -153,7 +153,7 @@ Item { borderColor: (Theme.palette.name === "dark") ? 'rgba(136, 176, 255, 1)' : 'rgba(67, 96, 223, 1)', borderWidth: 3, pointRadius: 0, - data: graphDetail.dataRange, + data: RootStore.marketHistoryIsLoading ? [] : graphDetail.dataRange, parsing: false, }] } @@ -236,6 +236,11 @@ Item { } } + LoadingGraphView { + anchors.fill: chart + active: RootStore.marketHistoryIsLoading + } + TokenBalanceHistoryStore { id: balanceStore diff --git a/ui/app/AppLayouts/Wallet/views/LeftTabView.qml b/ui/app/AppLayouts/Wallet/views/LeftTabView.qml index 6f8910477d..28a309fd89 100644 --- a/ui/app/AppLayouts/Wallet/views/LeftTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/LeftTabView.qml @@ -103,10 +103,10 @@ Rectangle { Layout.fillWidth: true Layout.leftMargin: Style.current.padding - StyledTextEdit { + StyledTextEditWithLoadingState { id: walletAmountValue objectName: "walletLeftListAmountValue" - color: Style.current.textColor + customColor: Style.current.textColor text: { LocaleUtils.currencyAmountToLocaleString(RootStore.totalCurrencyBalance) } @@ -116,6 +116,7 @@ Rectangle { width: parent.width font.weight: Font.Medium font.pixelSize: 22 + loading: RootStore.tokensLoading } StatusBaseText { @@ -160,6 +161,7 @@ Rectangle { asset.bgColor: Theme.palette.primaryColor3 statusListItemTitle.font.weight: Font.Medium color: sensor.containsMouse || highlighted ? Theme.palette.baseColor3 : "transparent" + statusListItemSubTitle.loading: RootStore.tokensLoading onClicked: { changeSelectedAccount(index) showSavedAddresses = false diff --git a/ui/imports/assets/icons/mask/dummyLineGraph.svg b/ui/imports/assets/icons/mask/dummyLineGraph.svg new file mode 100644 index 0000000000..89c484a3c4 --- /dev/null +++ b/ui/imports/assets/icons/mask/dummyLineGraph.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ui/imports/shared/controls/LoadingTokenDelegate.qml b/ui/imports/shared/controls/LoadingTokenDelegate.qml new file mode 100644 index 0000000000..175f03e103 --- /dev/null +++ b/ui/imports/shared/controls/LoadingTokenDelegate.qml @@ -0,0 +1,28 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 + +import StatusQ.Popups 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 + +import utils 1.0 + +TokenDelegate { + id: root + + title: Constants.dummyText + subTitle: Constants.dummyText + asset.name: Constants.dummyText + change24HourPercentage.text: Constants.dummyText + change24Hour.text: Constants.dummyText + localeCurrencyBalance.text: Constants.dummyText + statusListItemSubTitle.loading: true + statusListItemTitle.loading: true + statusListItemIcon.loading: true + change24HourPercentage.loading: true + change24Hour.loading: true + localeCurrencyBalance.loading: true + textColor: Theme.palette.baseColor1 +} diff --git a/ui/imports/shared/controls/StyledTextEditWithLoadingState.qml b/ui/imports/shared/controls/StyledTextEditWithLoadingState.qml new file mode 100644 index 0000000000..17390cfd7f --- /dev/null +++ b/ui/imports/shared/controls/StyledTextEditWithLoadingState.qml @@ -0,0 +1,30 @@ +import QtQuick 2.13 + +import utils 1.0 + +import StatusQ.Components 0.1 + +StyledTextEdit { + id: root + + property bool loading: false + property color customColor: Style.current.textColor + + color: loading ? "transparent" : customColor + + Loader { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + active: root.loading + sourceComponent: LoadingComponent { + radius: textMetrics.font.pixelSize <= 15 ? 4 : 8 + height: textMetrics.tightBoundingRect.height + width: Math.min(root.width, 140) + } + } + TextMetrics { + id: textMetrics + font: root.font + text: root.text + } +} diff --git a/ui/imports/shared/controls/TokenDelegate.qml b/ui/imports/shared/controls/TokenDelegate.qml index a74773de36..a350867385 100644 --- a/ui/imports/shared/controls/TokenDelegate.qml +++ b/ui/imports/shared/controls/TokenDelegate.qml @@ -5,22 +5,33 @@ import StatusQ.Popups 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Components 0.1 import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 import utils 1.0 StatusListItem { id: root + + property alias localeCurrencyBalance: localeCurrencyBalance + property alias change24Hour: change24HourText + property alias change24HourPercentage: change24HourPercentageText + + property string currentCurrencySymbol + property string textColor: changePct24hour === undefined ? Theme.palette.baseColor1 : + Math.sign(changePct24hour) === 0 ? Theme.palette.baseColor1 : + Math.sign(changePct24hour) === -1 ? Theme.palette.dangerColor1 : + Theme.palette.successColor1 + title: name subTitle: LocaleUtils.currencyAmountToLocaleString(enabledNetworkBalance) asset.name: symbol ? Style.png("tokens/" + symbol) : "" asset.isImage: true + components: [ Column { id: valueColumn - property string textColor: Math.sign(Number(changePct24hour)) === 0 ? Theme.palette.baseColor1 : - Math.sign(Number(changePct24hour)) === -1 ? Theme.palette.dangerColor1 : - Theme.palette.successColor1 - StatusBaseText { + StatusTextWithLoadingState { + id: localeCurrencyBalance anchors.right: parent.right font.pixelSize: 15 font.strikeout: false @@ -29,11 +40,11 @@ StatusListItem { Row { anchors.horizontalCenter: parent.horizontalCenter spacing: 8 - StatusBaseText { + StatusTextWithLoadingState { id: change24HourText font.pixelSize: 15 font.strikeout: false - color: valueColumn.textColor + customColor: root.textColor text: LocaleUtils.currencyAmountToLocaleString(currencyPrice) } Rectangle { @@ -41,10 +52,11 @@ StatusListItem { height: change24HourText.implicitHeight color: Theme.palette.directColor9 } - StatusBaseText { + StatusTextWithLoadingState { + id: change24HourPercentageText font.pixelSize: 15 font.strikeout: false - color: valueColumn.textColor + customColor: root.textColor text: changePct24hour !== "" ? changePct24hour.toFixed(2) + "%" : "---" } } diff --git a/ui/imports/shared/controls/qmldir b/ui/imports/shared/controls/qmldir index 9c2b418241..a0374b35f0 100644 --- a/ui/imports/shared/controls/qmldir +++ b/ui/imports/shared/controls/qmldir @@ -30,3 +30,5 @@ AssetsDetailsHeader 1.0 AssetsDetailsHeader.qml InformationTag 1.0 InformationTag.qml TransactionDetailsHeader.qml 1.0 TransactionDetailsHeader.qml TokenDelegate 1.0 TokenDelegate.qml +StyledTextEditWithLoadingState 1.0 StyledTextEditWithLoadingState.qml +LoadingTokenDelegate 1.0 LoadingTokenDelegate.qml diff --git a/ui/imports/shared/stores/RootStore.qml b/ui/imports/shared/stores/RootStore.qml index da8d08d9df..59b0a79c81 100644 --- a/ui/imports/shared/stores/RootStore.qml +++ b/ui/imports/shared/stores/RootStore.qml @@ -38,7 +38,7 @@ QtObject { property var historyTransactions: walletSectionTransactions.model property bool isNonArchivalNode: history ? history.isNonArchivalNode : false - + property bool tokensLoading: walletSection.tokensLoading property var currentAccount: walletSectionCurrent property var marketValueStore: TokenMarketValuesStore{} @@ -226,8 +226,13 @@ QtObject { walletSectionAllTokens.getHistoricalDataForToken(symbol,currency) } + property bool marketHistoryIsLoading: walletSectionAllTokens.marketHistoryIsLoading + // TODO: range until we optimize to cache the data and abuse the requests function fetchHistoricalBalanceForTokenAsJson(address, symbol, timeIntervalEnum) { walletSectionAllTokens.fetchHistoricalBalanceForTokenAsJson(address, symbol, timeIntervalEnum) } + + property bool balanceHistoryIsLoading: walletSectionAllTokens.balanceHistoryIsLoading + } diff --git a/ui/imports/shared/views/AssetsView.qml b/ui/imports/shared/views/AssetsView.qml index 1fcd78501e..15f626bc5c 100644 --- a/ui/imports/shared/views/AssetsView.qml +++ b/ui/imports/shared/views/AssetsView.qml @@ -32,16 +32,31 @@ Item { id: assetListView objectName: "assetViewStatusListView" anchors.fill: parent - model: SortFilterProxyModel { - sourceModel: account.assets - filters: [ - ExpressionFilter { - expression: visibleForNetworkWithPositiveBalance - } - ] - } + model: RootStore.tokensLoading ? 25 : filteredModel + delegate: RootStore.tokensLoading ? loadingTokenDelegate : tokenDelegate + ScrollBar.vertical: StatusScrollBar {} + } - delegate: TokenDelegate { + SortFilterProxyModel { + id: filteredModel + sourceModel: account.assets + filters: [ + ExpressionFilter { + expression: visibleForNetworkWithPositiveBalance + } + ] + } + + Component { + id: loadingTokenDelegate + LoadingTokenDelegate { + width: ListView.view.width + } + } + + Component { + id: tokenDelegate + TokenDelegate { objectName: "AssetView_TokenListItem_" + symbol readonly property string balance: "%1".arg(enabledNetworkBalance.amount) // Needed for the tests width: ListView.view.width @@ -56,7 +71,5 @@ Item { assetClicked(model) } } - - ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } } } diff --git a/ui/imports/shared/views/LoadingGraphView.qml b/ui/imports/shared/views/LoadingGraphView.qml new file mode 100644 index 0000000000..362e3451ff --- /dev/null +++ b/ui/imports/shared/views/LoadingGraphView.qml @@ -0,0 +1,42 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.13 +import QtQuick.Controls 2.14 +import QtQuick.Window 2.12 +import QtGraphicalEffects 1.12 + +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Core 0.1 +import StatusQ.Controls 0.1 + +import utils 1.0 +import shared.views 1.0 +import shared.controls 1.0 + +import "../stores" + +Loader { + id: root + active: false + sourceComponent: Item { + LoadingComponent { + id: loadingComp + anchors.fill: parent + visible: false + } + + Image { + id: mask + source: Style.svg("mask/dummyLineGraph") + sourceSize: Qt.size(parent.width, parent.height) + smooth: true + visible: false + } + + OpacityMask { + source: loadingComp + anchors.fill: loadingComp + maskSource: mask + } + } +} diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index dabbf8c6d9..781d405e47 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -655,6 +655,8 @@ QtObject { readonly property string seedWalletType: "seed" readonly property string generatedWalletType: "generated" + readonly property string dummyText: "Dummy" + readonly property string windows: "windows" readonly property string linux: "linux" readonly property string mac: "mac"