feat(@desktop/wallet): Implement connection error screens

fixes #9835
This commit is contained in:
Khushboo Mehta 2023-03-15 10:17:25 +01:00 committed by Khushboo-dev-cpp
parent df121445ca
commit 072537f61a
34 changed files with 517 additions and 89 deletions

View File

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

View File

@ -50,7 +50,7 @@ method viewDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method networkConnectionStatusUpdate*(self: Module, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int, timeToAutoRetryInSecs: int, withCache: bool) =
self.view.networkConnectionStatusUpdate(website, completelyDown, connectionState, chainIds, lastCheckedAt, timeToAutoRetryInSecs, withCache)
self.view.updateNetworkConnectionStatus(website, completelyDown, connectionState, chainIds, lastCheckedAt, timeToAutoRetryInSecs, withCache)
method refreshBlockchainValues*(self: Module) =
self.controller.refreshBlockchainValues()

View File

@ -0,0 +1,101 @@
import NimQml, strformat
QtObject:
type NetworkConnectionItem* = ref object of QObject
completelyDown: bool
connectionState: int
chainIds: string
lastCheckedAt: int
timeToAutoRetryInSecs: int
withCache: bool
proc delete*(self: NetworkConnectionItem) =
self.QObject.delete
proc newNetworkConnectionItem*(completelyDown = false, connectionState = 0, chainIds = "", lastCheckedAt = 0, timeToAutoRetryInSecs = 0, withCache = false,): NetworkConnectionItem =
new(result, delete)
result.QObject.setup
result.completelyDown = completelyDown
result.connectionState = connectionState
result.chainIds = chainIds
result.lastCheckedAt = lastCheckedAt
result.timeToAutoRetryInSecs = timeToAutoRetryInSecs
result.withCache = withCache
proc `$`*(self: NetworkConnectionItem): string =
result = fmt"""NetworkConnectionItem[
completelyDown: {self.completelyDown},
connectionState: {self.connectionState},
chainIds: {self.chainIds},
lastCheckedAt: {self.lastCheckedAt},
timeToAutoRetryInSecs: {self.timeToAutoRetryInSecs},
withCache: {self.withCache}
]"""
proc completelyDownChanged*(self: NetworkConnectionItem) {.signal.}
proc getCompletelyDown*(self: NetworkConnectionItem): bool {.slot.} =
return self.completelyDown
QtProperty[bool] completelyDown:
read = getCompletelyDown
notify = completelyDownChanged
proc connectionStateChanged*(self: NetworkConnectionItem) {.signal.}
proc getConnectionState*(self: NetworkConnectionItem): int {.slot.} =
return self.connectionState
QtProperty[int] connectionState:
read = getConnectionState
notify = connectionStateChanged
proc chainIdsChanged*(self: NetworkConnectionItem) {.signal.}
proc getChainIds*(self: NetworkConnectionItem): string {.slot.} =
return self.chainIds
QtProperty[string] chainIds:
read = getChainIds
notify = chainIdsChanged
proc lastCheckedAtChanged*(self: NetworkConnectionItem) {.signal.}
proc getLastCheckedAt*(self: NetworkConnectionItem): int {.slot.} =
return self.lastCheckedAt
QtProperty[int] lastCheckedAt:
read = getLastCheckedAt
notify = lastCheckedAtChanged
proc timeToAutoRetryInSecsChanged*(self: NetworkConnectionItem) {.signal.}
proc getTimeToAutoRetryInSecs*(self: NetworkConnectionItem): int {.slot.} =
return self.timeToAutoRetryInSecs
QtProperty[int] timeToAutoRetryInSecs:
read = getTimeToAutoRetryInSecs
notify = timeToAutoRetryInSecsChanged
proc withCacheChanged*(self: NetworkConnectionItem) {.signal.}
proc getWithCache*(self: NetworkConnectionItem): bool {.slot.} =
return self.withCache
QtProperty[bool] withCache:
read = getWithCache
notify = withCacheChanged
proc updateValues*(self: NetworkConnectionItem, completelyDown: bool, connectionState: int,
chainIds: string, lastCheckedAt: int, timeToAutoRetryInSecs: int, withCache: bool) =
if self.completelyDown != completelyDown :
self.completelyDown = completelyDown
self.completelyDownChanged()
if self.connectionState != connectionState :
self.connectionState = connectionState
self.connectionStateChanged()
if self.chainIds != chainIds :
self.chainIds = chainIds
self.chainIdsChanged()
if self.lastCheckedAt != lastCheckedAt :
self.lastCheckedAt = lastCheckedAt
self.lastCheckedAtChanged()
if self.timeToAutoRetryInSecs != timeToAutoRetryInSecs :
self.timeToAutoRetryInSecs = timeToAutoRetryInSecs
self.timeToAutoRetryInSecsChanged()
if self.withCache != withCache :
self.withCache = withCache
self.withCacheChanged()

View File

@ -1,27 +1,57 @@
import NimQml
import ./io_interface
import ./network_connection_item
import ../../../../app_service/service/network_connection/service as network_connection_service
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
blockchainNetworkConnection: NetworkConnectionItem
collectiblesNetworkConnection: NetworkConnectionItem
marketValuesNetworkConnection: NetworkConnectionItem
proc setup(self: View) =
self.QObject.setup
proc delete*(self: View) =
self.QObject.delete
self.blockchainNetworkConnection.delete
self.collectiblesNetworkConnection.delete
self.marketValuesNetworkConnection.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.delegate = delegate
result.blockchainNetworkConnection = newNetworkConnectionItem()
result.collectiblesNetworkConnection = newNetworkConnectionItem()
result.marketValuesNetworkConnection = newNetworkConnectionItem()
result.setup()
proc load*(self: View) =
self.delegate.viewDidLoad()
proc networkConnectionStatusUpdate*(self: View, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int, timeToAutoRetryInSecs: int, withCache: bool) {.signal.}
proc blockchainNetworkConnectionChanged*(self:View) {.signal.}
proc getBlockchainNetworkConnection(self: View): QVariant {.slot.} =
return newQVariant(self.blockchainNetworkConnection)
QtProperty[QVariant] blockchainNetworkConnection:
read = getBlockchainNetworkConnection
notify = blockchainNetworkConnectionChanged
proc collectiblesNetworkConnectionChanged*(self:View) {.signal.}
proc getCollectiblesNetworkConnection(self: View): QVariant {.slot.} =
return newQVariant(self.collectiblesNetworkConnection)
QtProperty[QVariant] collectiblesNetworkConnection:
read = getCollectiblesNetworkConnection
notify = collectiblesNetworkConnectionChanged
proc marketValuesNetworkConnectionChanged*(self:View) {.signal.}
proc getMarketValuesNetworkConnection(self: View): QVariant {.slot.} =
return newQVariant(self.marketValuesNetworkConnection)
QtProperty[QVariant] marketValuesNetworkConnection:
read = getMarketValuesNetworkConnection
notify = marketValuesNetworkConnectionChanged
proc refreshBlockchainValues*(self: View) {.slot.} =
self.delegate.refreshBlockchainValues()
@ -32,3 +62,19 @@ QtObject:
proc refreshCollectiblesValues*(self: View) {.slot.} =
self.delegate.refreshCollectiblesValues()
proc networkConnectionStatusUpdate*(self: View, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int, timeToAutoRetryInSecs: int, withCache: bool) {.signal.}
proc updateNetworkConnectionStatus*(self: View, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int, timeToAutoRetryInSecs: int, withCache: bool) =
case website:
of BLOCKCHAINS:
self.blockchainNetworkConnection.updateValues(completelyDown, connectionState, chainIds, lastCheckedAt, timeToAutoRetryInSecs, withCache)
self.blockchainNetworkConnectionChanged()
of COLLECTIBLES:
self.collectiblesNetworkConnection.updateValues(completelyDown, connectionState, chainIds, lastCheckedAt, timeToAutoRetryInSecs, withCache)
self.collectiblesNetworkConnectionChanged()
of MARKET:
self.marketValuesNetworkConnection.updateValues(completelyDown, connectionState, chainIds, lastCheckedAt, timeToAutoRetryInSecs, withCache)
self.marketValuesNetworkConnectionChanged()
self.networkConnectionStatusUpdate(website, completelyDown, connectionState, chainIds, lastCheckedAt, timeToAutoRetryInSecs, withCache)

View File

@ -4,6 +4,7 @@ import ../../../../../app_service/service/collectible/service as collectible_ser
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../app_service/service/network/service as network_service
import ../../../../../app_service/service/network_connection/service as network_connection_service
import ../../../../../app_service/service/node/service as node_service
import ../../../../core/eventemitter
type
@ -13,13 +14,20 @@ type
collectibleService: collectible_service.Service
walletAccountService: wallet_account_service.Service
networkService: network_service.Service
nodeService: node_service.Service
networkConnectionService: network_connection_service.Service
# Forward declaration
proc resetOwnedCollectibles*(self: Controller, chainId: int, address: string)
proc newController*(
delegate: io_interface.AccessInterface,
events: EventEmitter,
collectibleService: collectible_service.Service,
walletAccountService: wallet_account_service.Service,
networkService: network_service.Service
networkService: network_service.Service,
nodeService: node_service.Service,
networkConnectionService: network_connection_service.Service
): Controller =
result = Controller()
result.delegate = delegate
@ -27,6 +35,8 @@ proc newController*(
result.collectibleService = collectibleService
result.walletAccountService = walletAccountService
result.networkService = networkService
result.nodeService = nodeService
result.networkConnectionService = networkConnectionService
proc delete*(self: Controller) =
discard
@ -37,6 +47,8 @@ proc refreshCollectibles(self: Controller, chainId: int, address: string) =
self.delegate.setCollectibles(chainId, address, data)
else:
self.delegate.appendCollectibles(chainId, address, data)
if not self.nodeService.isConnected() or not self.networkConnectionService.checkIfConnected(COLLECTIBLES):
self.delegate.noConnectionToOpenSea()
proc init*(self: Controller) =
self.events.on(SIGNAL_OWNED_COLLECTIBLES_RESET) do(e:Args):
@ -55,7 +67,15 @@ proc init*(self: Controller) =
let args = RetryCollectibleArgs(e)
let chainId = self.networkService.getNetworkForCollectibles().chainId
for address in args.addresses:
self.refreshCollectibles(chainId, address)
self.resetOwnedCollectibles(chainId, address)
self.events.on(SIGNAL_NETWORK_DISCONNECTED) do(e: Args):
self.delegate.noConnectionToOpenSea()
self.events.on(SIGNAL_CONNECTION_UPDATE) do(e:Args):
let args = NetworkConnectionsArgs(e)
if args.website == COLLECTIBLES and args.completelyDown:
self.delegate.noConnectionToOpenSea()
proc getWalletAccount*(self: Controller, accountIndex: int): wallet_account_service.WalletAccountDto =
return self.walletAccountService.getWalletAccount(accountIndex)

View File

@ -37,3 +37,6 @@ method collectiblesModuleDidLoad*(self: AccessInterface) {.base.} =
method currentCollectibleModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method noConnectionToOpenSea*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -203,3 +203,8 @@ QtObject:
self.items = concat(self.items, items)
self.endInsertRows()
self.countChanged()
# in case loading is still going on and no items are present, show loading items when there is no connection to opensea possible
proc noConnectionToOpenSea*(self: Model) =
if self.items.len == 0:
self.setIsFetching(true)

View File

@ -8,6 +8,8 @@ import ../io_interface as delegate_interface
import ../../../../../app_service/service/collectible/service as collectible_service
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../app_service/service/network/service as network_service
import ../../../../../app_service/service/node/service as node_service
import ../../../../../app_service/service/network_connection/service as network_connection_service
import ./current_collectible/module as current_collectible_module
@ -34,12 +36,14 @@ proc newModule*(
events: EventEmitter,
collectibleService: collectible_service.Service,
walletAccountService: wallet_account_service.Service,
networkService: network_service.Service
networkService: network_service.Service,
nodeService: node_service.Service,
networkConnectionService: network_connection_service.Service
): Module =
result = Module()
result.delegate = delegate
result.view = newView(result)
result.controller = newController(result, events, collectibleService, walletAccountService, networkService)
result.controller = newController(result, events, collectibleService, walletAccountService, networkService, nodeService, networkConnectionService)
result.moduleLoaded = false
result.currentCollectibleModule = currentCollectibleModule.newModule(result, collectibleService)
@ -109,7 +113,6 @@ method setCollectibles*(self: Module, chainId: int, address: string, data: Colle
self.view.setCollectibles(newCollectibles)
self.view.setAllLoaded(data.allLoaded)
method appendCollectibles*(self: Module, chainId: int, address: string, data: CollectiblesData) =
if self.chainId == chainId and self.address == address:
self.view.setIsFetching(data.isFetching)
@ -122,3 +125,6 @@ method appendCollectibles*(self: Module, chainId: int, address: string, data: Co
self.view.appendCollectibles(newCollectibles)
self.view.setAllLoaded(data.allLoaded)
method noConnectionToOpenSea*(self: Module) =
self.view.noConnectionToOpenSea()

View File

@ -45,3 +45,6 @@ QtObject:
proc appendCollectibles*(self: View, collectibles: seq[Item]) =
self.model.appendItems(collectibles)
proc noConnectionToOpenSea*(self: View) =
self.model.noConnectionToOpenSea()

View File

@ -24,6 +24,8 @@ import ../../../../app_service/service/settings/service as settings_service
import ../../../../app_service/service/saved_address/service as saved_address_service
import ../../../../app_service/service/network/service as network_service
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 io_interface
export io_interface
@ -57,7 +59,9 @@ proc newModule*(
savedAddressService: saved_address_service.Service,
networkService: network_service.Service,
accountsService: accounts_service.Service,
keycardService: keycard_service.Service
keycardService: keycard_service.Service,
nodeService: node_service.Service,
networkConnectionService: network_connection_service.Service
): Module =
result = Module()
result.delegate = delegate
@ -68,7 +72,7 @@ proc newModule*(
result.accountsModule = accounts_module.newModule(result, events, keycardService, walletAccountService, accountsService, networkService, tokenService, currencyService)
result.allTokensModule = all_tokens_module.newModule(result, events, tokenService, walletAccountService)
result.collectiblesModule = collectibles_module.newModule(result, events, collectibleService, walletAccountService, networkService)
result.collectiblesModule = collectibles_module.newModule(result, events, collectibleService, walletAccountService, networkService, nodeService, networkConnectionService)
result.currentAccountModule = current_account_module.newModule(result, events, walletAccountService, networkService, tokenService, currencyService)
result.transactionsModule = transactions_module.newModule(result, events, transactionService, walletAccountService, networkService, currencyService)
result.savedAddressesModule = saved_addresses_module.newModule(result, events, savedAddressService)
@ -121,6 +125,8 @@ method load*(self: Module) =
self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args):
self.setTotalCurrencyBalance()
self.view.setTokensLoading(false)
self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_BEING_FETCHED) do(e:Args):
self.view.setTokensLoading(true)
self.events.on(SIGNAL_CURRENCY_FORMATS_UPDATED) do(e:Args):
self.setTotalCurrencyBalance()

View File

@ -11,7 +11,7 @@ const fetchOwnedCollectiblesTaskArg: Task = proc(argEncoded: string) {.gcsafe, n
"chainId": arg.chainId,
"address": arg.address,
"cursor": arg.cursor,
"collectibles": ""
"collectibles": {"assets":nil,"next":"","previous":""}
}
try:
let response = collectibles.getOpenseaAssetsByOwnerWithCursor(arg.chainId, arg.address, arg.cursor, arg.limit)

View File

@ -157,9 +157,9 @@ QtObject:
# needs to be re-written once cache for colletibles works
proc areCollectionsLoaded*(self: Service): bool =
for chainId, adressesData in self.ownershipData:
for address, collectionsData in adressesData:
if collectionsData.allLoaded:
for chainId, adressesData in self.accountsOwnershipData:
for address, ownershipData in adressesData:
if ownershipData.data.anyLoaded:
return true
return false

View File

@ -150,7 +150,7 @@ QtObject:
of BLOCKCHAINS:
return self.walletService.hasCache()
of MARKET:
return self.walletService.hasCache()
return self.walletService.hasMarketCache()
of COLLECTIBLES:
return self.collectibleService.areCollectionsLoaded()
@ -246,9 +246,6 @@ QtObject:
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website]))
proc checkConnected(self: Service) =
if(not singletonInstance.localAccountSensitiveSettings.getIsWalletEnabled()):
return
try:
if self.nodeService.isConnected():
let response = backend.checkConnected()
@ -263,3 +260,9 @@ QtObject:
proc networkConnected*(self: Service) =
self.walletService.reloadAccountTokens()
self.events.emit(SIGNAL_REFRESH_COLLECTIBLES, RetryCollectibleArgs(addresses: self.walletService.getAddresses()))
proc checkIfConnected*(self: Service, website: string): bool =
if self.connectionStatus.hasKey(website) and self.connectionStatus[website].completelyDown:
return false
return true

View File

@ -33,6 +33,7 @@ const SIGNAL_WALLET_ACCOUNT_UPDATED* = "walletAccount/walletAccountUpdated"
const SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED* = "walletAccount/networkEnabledUpdated"
const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY* = "walletAccount/derivedAddressesReady"
const SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT* = "walletAccount/tokensRebuilt"
const SIGNAL_WALLET_ACCOUNT_TOKENS_BEING_FETCHED* = "walletAccount/tokenFetching"
const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_DETAILS_FETCHED* = "walletAccount/derivedAddressDetailsFetched"
const SIGNAL_KEYCARDS_SYNCHRONIZED* = "keycardsSynchronized"
@ -579,6 +580,7 @@ QtObject:
accounts: accounts,
storeResult: store
)
self.events.emit(SIGNAL_WALLET_ACCOUNT_TOKENS_BEING_FETCHED, Args())
self.threadpool.start(arg)
proc getCurrentCurrencyIfEmpty(self: Service, currency = ""): string =
@ -851,3 +853,13 @@ QtObject:
if self.walletAccounts[address].tokens.len > 0:
return true
return false
proc hasMarketCache*(self: Service): bool =
withLock self.walletAccountsLock:
for address, accountDto in self.walletAccounts:
for token in self.walletAccounts[address].tokens:
for currency, marketValues in token.marketValuesPerCurrency:
if marketValues.highDay > 0:
return true
return false

View File

@ -301,6 +301,8 @@ class StatusWalletScreen:
for index in range(list.count):
tokenListItem = list.itemAtIndex(index)
if tokenListItem != None and tokenListItem.objectName == "AssetView_LoadingTokenDelegate_"+str(index):
return (False, )
if tokenListItem != None and tokenListItem.objectName == "AssetView_TokenListItem_" + symbol and tokenListItem.balance != "0":
return (True, tokenListItem)
return (False, )

View File

@ -1,3 +1,3 @@
GANACHE_RPC_PORT=9545
GANACHE_MNEMONIC='pelican chief sudden oval media rare swamp elephant lawsuit wheat knife initial'
GANACHE_DB_FOLDER="./../../../../../test/ui-test/fixtures/ganache-dbs/goerli"
GANACHE_DB_FOLDER=./../../../../../test/ui-test/fixtures/ganache-dbs/goerli

View File

@ -21,7 +21,7 @@ Feature: Status Desktop Transaction
Examples:
| amount | token | chain_name |
| 0.1 | ETH | Ethereum Mainnet |
| 1 | ETH | Ethereum Mainnet |
# | 1 | ETH | Goerli |
# | 1 | STT | Goerli |
# | 100 | STT | Goerli |

View File

@ -33,6 +33,7 @@ Rectangle {
property var inlineTagModel: []
property Component inlineTagDelegate
property bool loading: false
property bool errorMode: false
property StatusAssetSettings asset: StatusAssetSettings {
height: isImage ? 40 : 20
@ -78,6 +79,7 @@ Rectangle {
property alias statusListItemInlineTagsSlot: statusListItemTagsSlotInline
property alias statusListItemLabel: statusListItemLabel
property alias subTitleBadgeComponent: subTitleBadgeLoader.sourceComponent
property alias errorIcon: errorIcon
signal clicked(string itemId, var mouse)
signal titleClicked(string titleId)
@ -251,11 +253,24 @@ Rectangle {
anchors.leftMargin: 4
}
StatusFlatRoundButton {
id: errorIcon
anchors.top: statusListItemTitle.bottom
width: 14
height: visible ? 14 : 0
icon.width: 14
icon.height: 14
icon.name: "tiny/warning"
icon.color: Theme.palette.dangerColor1
visible: root.errorMode && !!toolTip.text
}
RowLayout {
id: statusListItemSubtitleTagsRow
anchors.top: statusListItemTitle.bottom
width: parent.width
spacing: 4
visible: !errorMode
Loader {
id: subTitleBadgeLoader

View File

@ -0,0 +1,29 @@
import QtQuick 2.14
import StatusQ.Controls 0.1
Item {
property alias button: button
property alias text: button.text
property alias icon: button.icon.name
property alias tooltipText: tooltip.text
implicitWidth: button.width
implicitHeight: button.height
StatusFlatButton {
id: button
anchors.centerIn: parent
}
MouseArea {
id: mouseArea
anchors.fill: button
hoverEnabled: !button.enabled
enabled: !button.enabled
cursorShape: Qt.PointingHandCursor
}
StatusToolTip {
id: tooltip
visible: mouseArea.containsMouse
}
}

View File

@ -9,12 +9,14 @@ import StatusQ.Core.Theme 0.1
import utils 1.0
import "../popups"
import "../controls"
Rectangle {
id: walletFooter
property var sendModal
property var walletStore
property var networkConnectionStore
height: 61
color: Theme.palette.statusAppLayout.rightPanelBackgroundColor
@ -29,13 +31,15 @@ Rectangle {
height: parent.height
spacing: Style.current.padding
StatusFlatButton {
objectName: "walletFooterSendButton"
icon.name: "send"
text: qsTr("Send")
onClicked: function() {
FooterTooltipButton {
button.objectName: "walletFooterSendButton"
button.icon.name: "send"
button.text: qsTr("Send")
button.enabled: networkConnectionStore.sendBuyBridgeEnabled
button.onClicked: function() {
sendModal.open()
}
tooltipText: networkConnectionStore.sendBuyBridgeToolTipText
}
StatusFlatButton {
@ -46,14 +50,15 @@ Rectangle {
}
}
StatusFlatButton {
id: bridgeBtn
icon.name: "bridge"
text: qsTr("Bridge")
onClicked: function () {
FooterTooltipButton {
button.icon.name: "bridge"
button.text: qsTr("Bridge")
button.enabled: networkConnectionStore.sendBuyBridgeEnabled
button.onClicked: function() {
sendModal.isBridgeTx = true
sendModal.open()
}
tooltipText: networkConnectionStore.sendBuyBridgeToolTipText
}
StatusFlatButton {

View File

@ -18,6 +18,7 @@ import "../stores"
Item {
id: root
property var networkConnectionStore
property string currency: ""
property var currentAccount
property var store
@ -47,6 +48,7 @@ Item {
customColor: Theme.palette.baseColor1
text: LocaleUtils.currencyAmountToLocaleString(root.currentAccount.currencyBalance)
loading: root.walletStore.tokensLoading
visible: !networkConnectionStore.tokenBalanceNotAvailable
}
}

View File

@ -21,6 +21,7 @@ Item {
id: root
property var token: ({})
property var networkConnectionStore
/*required*/ property string address: ""
QtObject {
@ -51,10 +52,12 @@ Item {
width: parent.width
asset.name: token && token.symbol ? Style.png("tokens/%1".arg(token.symbol)) : ""
asset.isImage: true
primaryText: token.name ?? ""
secondaryText: token ? LocaleUtils.currencyAmountToLocaleString(token.enabledNetworkBalance) : ""
tertiaryText: token ? LocaleUtils.currencyAmountToLocaleString(token.enabledNetworkCurrencyBalance) : ""
primaryText: token.name ?? Constants.dummyText
secondaryText: token ? LocaleUtils.currencyAmountToLocaleString(token.enabledNetworkBalance) : Constants.dummyText
tertiaryText: token ? LocaleUtils.currencyAmountToLocaleString(token.enabledNetworkCurrencyBalance) : Constants.dummyText
balances: token && token.balances ? token.balances : null
isLoading: RootStore.tokensLoading
errorTooltipText: token && token.balances ? networkConnectionStore.getNetworkDownTextForToken(token.balances): ""
getNetworkColor: function(chainId){
return RootStore.getNetworkColor(chainId)
}
@ -274,17 +277,20 @@ Item {
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Market Cap")
secondaryText: token && token.marketCap ? LocaleUtils.currencyAmountToLocaleString(token.marketCap) : "---"
secondaryText: token && token.marketCap ? LocaleUtils.currencyAmountToLocaleString(token.marketCap) : Constants.dummyText
isLoading: RootStore.tokensLoading
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Day Low")
secondaryText: token && token.lowDay ? LocaleUtils.currencyAmountToLocaleString(token.lowDay) : "---"
secondaryText: token && token.lowDay ? LocaleUtils.currencyAmountToLocaleString(token.lowDay) : Constants.dummyText
isLoading: RootStore.tokensLoading
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Day High")
secondaryText: token && token.highDay ? LocaleUtils.currencyAmountToLocaleString(token.highDay) : "---"
secondaryText: token && token.highDay ? LocaleUtils.currencyAmountToLocaleString(token.highDay) : Constants.dummyText
isLoading: RootStore.tokensLoading
}
Item {
Layout.fillWidth: true
@ -293,28 +299,31 @@ Item {
readonly property double changePctHour: token.changePctHour ?? 0
maxWidth: parent.width
primaryText: qsTr("Hour")
secondaryText: changePctHour ? "%1%".arg(LocaleUtils.numberToLocaleString(changePctHour, 2)) : "---"
secondaryLabel.color: Math.sign(changePctHour) === 0 ? Theme.palette.directColor1 :
Math.sign(changePctHour) === -1 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
secondaryText: changePctHour ? "%1%".arg(LocaleUtils.numberToLocaleString(changePctHour, 2)) : Constants.dummyText
secondaryLabel.customColor: changePctHour === 0 ? Theme.palette.directColor1 :
changePctHour < 0 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
isLoading: RootStore.tokensLoading
}
InformationTile {
readonly property double changePctDay: token.changePctDay ?? 0
maxWidth: parent.width
primaryText: qsTr("Day")
secondaryText: changePctDay ? "%1%".arg(LocaleUtils.numberToLocaleString(changePctDay, 2)) : "---"
secondaryLabel.color: Math.sign(changePctDay) === 0 ? Theme.palette.directColor1 :
Math.sign(changePctDay) === -1 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
secondaryText: changePctDay ? "%1%".arg(LocaleUtils.numberToLocaleString(changePctDay, 2)) : Constants.dummyText
secondaryLabel.customColor: changePctDay === 0 ? Theme.palette.directColor1 :
changePctDay < 0 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
isLoading: RootStore.tokensLoading
}
InformationTile {
readonly property double changePct24hour: token.changePct24hour ?? 0
maxWidth: parent.width
primaryText: qsTr("24 Hours")
secondaryText: changePct24hour ? "%1%".arg(LocaleUtils.numberToLocaleString(changePct24hour, 2)) : "---"
secondaryLabel.color: Math.sign(changePct24hour) === 0 ? Theme.palette.directColor1 :
Math.sign(changePct24hour) === -1 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
secondaryText: changePct24hour ? "%1%".arg(LocaleUtils.numberToLocaleString(changePct24hour, 2)) : Constants.dummyText
secondaryLabel.customColor: changePct24hour === 0 ? Theme.palette.directColor1 :
changePct24hour < 0 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
isLoading: RootStore.tokensLoading
}
}
@ -348,18 +357,19 @@ Item {
spacing: 24
width: scrollView.availableWidth
StatusBaseText {
StatusTextWithLoadingState {
id: tokenDescriptionText
width: Math.max(536 , scrollView.availableWidth - tagsLayout.width - 24)
font.pixelSize: 15
lineHeight: 22
lineHeightMode: Text.FixedHeight
text: token.description ?? ""
color: Theme.palette.directColor1
text: token.description ?? Constants.dummyText
customColor: Theme.palette.directColor1
elide: Text.ElideRight
wrapMode: Text.Wrap
textFormat: Qt.RichText
loading: RootStore.tokensLoading
}
ColumnLayout {
id: tagsLayout

View File

@ -14,6 +14,7 @@ import shared 1.0
import shared.panels 1.0
import shared.controls 1.0
import shared.popups.keycard 1.0
import shared.stores 1.0
import "../controls"
import "../popups"
@ -22,6 +23,7 @@ import "../stores"
Rectangle {
id: root
readonly property NetworkConnectionStore networkConnectionStore: NetworkConnectionStore {}
property var changeSelectedAccount: function(){}
property bool showSavedAddresses: false
onShowSavedAddressesChanged: {
@ -117,6 +119,20 @@ Rectangle {
font.weight: Font.Medium
font.pixelSize: 22
loading: RootStore.tokensLoading
visible: !networkConnectionStore.tokenBalanceNotAvailable
}
StatusFlatRoundButton {
id: errorIcon
width: 14
height: visible ? 14 : 0
icon.width: 14
icon.height: 14
icon.name: "tiny/warning"
icon.color: Theme.palette.dangerColor1
tooltip.text: networkConnectionStore.tokenBalanceNotAvailableText
tooltip.maxWidth: 200
visible: networkConnectionStore.tokenBalanceNotAvailable
}
StatusBaseText {
@ -162,6 +178,9 @@ Rectangle {
statusListItemTitle.font.weight: Font.Medium
color: sensor.containsMouse || highlighted ? Theme.palette.baseColor3 : "transparent"
statusListItemSubTitle.loading: RootStore.tokensLoading
errorMode: networkConnectionStore.tokenBalanceNotAvailable
errorIcon.tooltip.maxWidth: 300
errorIcon.tooltip.text: networkConnectionStore.tokenBalanceNotAvailableText
onClicked: {
changeSelectedAccount(index)
showSavedAddresses = false

View File

@ -6,6 +6,7 @@ import StatusQ.Controls 0.1
import utils 1.0
import shared.views 1.0
import shared.stores 1.0
import "./"
import "../stores"
@ -19,6 +20,7 @@ Item {
property var store
property var contactsStore
property var sendModal
readonly property NetworkConnectionStore networkConnectionStore: NetworkConnectionStore {}
function resetStack() {
stack.currentIndex = 0;
@ -58,6 +60,7 @@ Item {
currentAccount: RootStore.currentAccount
store: root.store
walletStore: RootStore
networkConnectionStore: root.networkConnectionStore
}
StatusTabBar {
id: walletTabBar
@ -90,6 +93,7 @@ Item {
AssetsView {
account: RootStore.currentAccount
networkConnectionStore: root.networkConnectionStore
assetDetailsLaunched: stack.currentIndex === 2
onAssetClicked: {
assetDetailView.token = token
@ -124,6 +128,7 @@ Item {
visible: (stack.currentIndex === 2)
address: RootStore.currentAccount.mixedcaseAddress
networkConnectionStore: root.networkConnectionStore
}
TransactionDetailView {
id: transactionDetailView
@ -142,6 +147,7 @@ Item {
Layout.rightMargin: !!root.StackView ? -root.StackView.view.anchors.rightMargin : 0
sendModal: root.sendModal
walletStore: RootStore
networkConnectionStore: root.networkConnectionStore
}
}
}

View File

@ -156,29 +156,4 @@ QtObject {
function resolveENS(value) {
mainModuleInst.resolveENS(value, "")
}
property var networkConnectionModuleInst: networkConnectionModule
function getChainIdsJointString(chainIdsDown) {
let jointChainIdString = ""
for (const chain of chainIdsDown) {
jointChainIdString = (!!jointChainIdString) ? jointChainIdString + " & " : jointChainIdString
jointChainIdString += allNetworks.getNetworkFullName(parseInt(chain))
}
return jointChainIdString
}
function retryConnection(websiteDown) {
switch(websiteDown) {
case Constants.walletConnections.blockchains:
networkConnectionModule.refreshBlockchainValues()
break
case Constants.walletConnections.market:
networkConnectionModule.refreshMarketValues()
break
case Constants.walletConnections.collectibles:
networkConnectionModule.refreshCollectiblesValues()
break
}
}
}

View File

@ -680,7 +680,6 @@ Item {
ConnectionWarnings {
id: walletBlockchainConnectionBanner
objectName: "walletBlockchainConnectionBanner"
readonly property string jointChainIdString: appMain.rootStore.getChainIdsJointString(chainIdsDown)
Layout.fillWidth: true
websiteDown: Constants.walletConnections.blockchains
text: {
@ -723,6 +722,7 @@ Item {
StatusToolTip {
id: toolTip
orientation: StatusToolTip.Orientation.Bottom
maxWidth: 300
}
}

View File

@ -16,6 +16,8 @@ Control {
property alias secondaryText: cryptoBalance.text
property alias tertiaryText: fiatBalance.text
property var balances
property bool isLoading: false
property string errorTooltipText
property StatusAssetSettings asset: StatusAssetSettings {
width: 40
height: 40
@ -34,8 +36,9 @@ Control {
id: identiconLoader
anchors.verticalCenter: parent.verticalCenter
asset: root.asset
loading: root.isLoading
}
StatusBaseText {
StatusTextWithLoadingState {
id: tokenName
width: Math.min(root.width - identiconLoader.width - cryptoBalance.width - fiatBalance.width - 24, implicitWidth)
anchors.verticalCenter: parent.verticalCenter
@ -43,15 +46,17 @@ Control {
lineHeight: 30
lineHeightMode: Text.FixedHeight
elide: Text.ElideRight
color: Theme.palette.directColor1
customColor: Theme.palette.directColor1
loading: root.isLoading
}
StatusBaseText {
StatusTextWithLoadingState {
id: cryptoBalance
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 22
lineHeight: 30
lineHeightMode: Text.FixedHeight
color: Theme.palette.baseColor1
customColor: Theme.palette.baseColor1
loading: root.isLoading
}
StatusBaseText {
id: dotSeparator
@ -61,13 +66,14 @@ Control {
color: Theme.palette.baseColor1
text: "."
}
StatusBaseText {
StatusTextWithLoadingState {
id: fiatBalance
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 22
lineHeight: 30
lineHeightMode: Text.FixedHeight
color: Theme.palette.baseColor1
customColor: Theme.palette.baseColor1
loading: root.isLoading
}
}
Row {
@ -81,6 +87,17 @@ Control {
tagPrimaryLabel.text: root.formatBalance(model.balance)
tagPrimaryLabel.color: root.getNetworkColor(model.chainId)
image.source: Style.svg("tiny/%1".arg(root.getNetworkIcon(model.chainId)))
loading: root.isLoading
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
}
}
}
}

View File

@ -19,6 +19,7 @@ Rectangle {
property alias tagsDelegate: tags.delegate
property int maxWidth: 0
property bool copy: false
property bool isLoading: false
signal copyClicked(string textToCopy)
@ -43,13 +44,14 @@ Rectangle {
}
RowLayout {
width: 100
StatusBaseText {
StatusTextWithLoadingState {
id: secondaryText
Layout.maximumWidth: root.maxWidth - Style.current.xlPadding - (root.copy ? 50 : 0)
font.pixelSize: 15
color: Theme.palette.directColor1
customColor: Theme.palette.directColor1
visible: text
elide: Text.ElideRight
loading: root.isLoading
}
CopyToClipBoardButton {
visible: root.copy

View File

@ -21,24 +21,52 @@ StatusListItem {
Math.sign(changePct24hour) === 0 ? Theme.palette.baseColor1 :
Math.sign(changePct24hour) === -1 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
property string errorTooltipText_1
property string errorTooltipText_2
title: name
subTitle: LocaleUtils.currencyAmountToLocaleString(enabledNetworkBalance)
asset.name: symbol ? Style.png("tokens/" + symbol) : ""
asset.isImage: true
statusListItemTitleIcons.sourceComponent: 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_1
tooltip.maxWidth: 300
visible: !!tooltip.text
}
components: [
Column {
id: valueColumn
StatusFlatRoundButton {
id: errorIcon
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_2
tooltip.maxWidth: 200
visible: !!tooltip.text
}
StatusTextWithLoadingState {
id: localeCurrencyBalance
anchors.right: parent.right
font.pixelSize: 15
text: LocaleUtils.currencyAmountToLocaleString(enabledNetworkCurrencyBalance)
visible: !errorIcon.visible
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 8
visible: !errorIcon.visible
StatusTextWithLoadingState {
id: change24HourText
font.pixelSize: 15

View File

@ -3,10 +3,13 @@ import QtQuick 2.14
import StatusQ.Core 0.1
import utils 1.0
import shared.stores 1.0
ModuleWarning {
id: root
readonly property NetworkConnectionStore networkConnectionStore: NetworkConnectionStore {}
readonly property string jointChainIdString: networkConnectionStore.getChainIdsJointString(chainIdsDown)
property string websiteDown
property int connectionState: -1
property int autoTryTimerInSecs: 0
@ -37,11 +40,11 @@ ModuleWarning {
type: connectionState === Constants.ConnectionStatus.Success ? ModuleWarning.Success : ModuleWarning.Danger
buttonText: connectionState === Constants.ConnectionStatus.Failure ? qsTr("Retry now") : ""
onClicked: appMain.rootStore.retryConnection(websiteDown)
onClicked: networkConnectionStore.retryConnection(websiteDown)
onCloseClicked: hide()
Connections {
target: appMain.rootStore.networkConnectionModuleInst
target: networkConnectionStore.networkConnectionModuleInst
function onNetworkConnectionStatusUpdate(website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int, timeToAutoRetryInSecs: int, withCache: bool) {
if (website === websiteDown) {
root.connectionState = connectionState

View File

@ -120,6 +120,7 @@ StatusDialog {
popup.recalculateRoutesAndFees()
}
}
readonly property NetworkConnectionStore networkConnectionStore: NetworkConnectionStore {}
onErrorTypeChanged: {
if(errorType === Constants.SendAmountExceedsBalance)
@ -166,6 +167,13 @@ StatusDialog {
recipientSelector.input.text = popup.selectedAccount.address
d.waitTimer.restart()
}
// add networks that are down to disabled list
if(!!d.networkConnectionStore.blockchainNetworksDown) {
for(let i in d.networkConnectionStore.blockchainNetworksDown) {
store.addRemoveDisabledToChain(parseInt(d.networkConnectionStore.blockchainNetworksDown[i]), true)
}
}
}
onClosed: popup.store.resetTxStoreProperties()

View File

@ -0,0 +1,95 @@
import QtQuick 2.13
import StatusQ.Core 0.1
import utils 1.0
QtObject {
id: root
readonly property var networkConnectionModuleInst: networkConnectionModule
readonly property var blockchainNetworksDown: networkConnectionModule.blockchainNetworkConnection.chainIds.split(";")
readonly property bool atleastOneBlockchainNetworkAvailable: blockchainNetworksDown.length < networksModule.all.count
readonly property bool noBlockchainConnWithoutCache: (!mainModule.isOnline || networkConnectionModule.blockchainNetworkConnection.completelyDown) &&
!walletSection.tokensLoading && walletSectionCurrent.assets.count === 0
readonly property bool sendBuyBridgeEnabled: localAppSettings.testEnvironment || (mainModule.isOnline &&
(!networkConnectionModule.blockchainNetworkConnection.completelyDown && atleastOneBlockchainNetworkAvailable) &&
!networkConnectionModule.marketValuesNetworkConnection.completelyDown)
readonly property string sendBuyBridgeToolTipText: !mainModule.isOnline ?
qsTr("Requires internet connection") :
networkConnectionModule.blockchainNetworkConnection.completelyDown ||
(!networkConnectionModule.blockchainNetworkConnection.completelyDown &&
!atleastOneBlockchainNetworkAvailable) ?
qsTr("Requires Pocket Network(POKT) or Infura, both of which are currently unavailable") :
networkConnectionModule.marketValuesNetworkConnection.completelyDown ?
qsTr("Requires CryptoCompare or CoinGecko, both of which are currently unavailable"):
qsTr("Requires POKT/ Infura and CryptoCompare/CoinGecko, which are all currently unavailable")
readonly property bool tokenBalanceNotAvailable: ((!mainModule.isOnline || networkConnectionModule.blockchainNetworkConnection.completelyDown) &&
!walletSection.tokensLoading && walletSectionCurrent.assets.count === 0) ||
(networkConnectionModule.marketValuesNetworkConnection.completelyDown &&
!networkConnectionModule.marketValuesNetworkConnection.withCache)
readonly property string tokenBalanceNotAvailableText: !mainModule.isOnline ?
qsTr("Internet connection lost. Data could not be retrieved.") :
networkConnectionModule.blockchainNetworkConnection.completelyDown ?
qsTr("Token balances are fetched from Pocket Network (POKT) and Infura which are both curently unavailable") :
networkConnectionModule.marketValuesNetworkConnection.completelyDown ?
qsTr("Market values are fetched from CryptoCompare and CoinGecko which are both currently unavailable") :
qsTr("Market values and token balances use CryptoCompare/CoinGecko and POKT/Infura which are all currently unavailable.")
function getBlockchainNetworkDownTextForToken(balances) {
if(!!balances && !networkConnectionModule.blockchainNetworkConnection.completelyDown) {
let chainIdsDown = []
for (var i =0; i<balances.count; i++) {
let chainId = balances.rowData(i, "chainId")
if(blockchainNetworksDown.includes(chainId))
chainIdsDown.push(chainId)
}
if(chainIdsDown.length > 0) {
return qsTr("Pocket Network (POKT) & Infura are currently both unavailable for %1. %1 balances are as of %2.")
.arg(getChainIdsJointString(chainIdsDown))
.arg(LocaleUtils.formatDateTime(new Date(networkConnectionModule.blockchainNetworkConnection.lastCheckedAt*1000)))
}
}
return ""
}
function getMarketNetworkDownText() {
if(networkConnectionModule.blockchainNetworkConnection.completelyDown &&
!walletSection.tokensLoading && walletSectionCurrent.assets.count === 0 &&
networkConnectionModule.marketValuesNetworkConnection.completelyDown &&
!networkConnectionModule.marketValuesNetworkConnection.withCache)
return qsTr("Market values and token balances use CryptoCompare/CoinGecko and POKT/Infura which are all currently unavailable.")
else if(networkConnectionModule.marketValuesNetworkConnection.completelyDown &&
!networkConnectionModule.marketValuesNetworkConnection.withCache)
return qsTr("Market values are fetched from CryptoCompare and CoinGecko which are both currently unavailable")
else
return ""
}
function getChainIdsJointString(chainIdsDown) {
let jointChainIdString = ""
for (const chain of chainIdsDown) {
jointChainIdString = (!!jointChainIdString) ? jointChainIdString + " & " : jointChainIdString
jointChainIdString += networksModule.all.getNetworkFullName(parseInt(chain))
}
return jointChainIdString
}
function retryConnection(websiteDown) {
switch(websiteDown) {
case Constants.walletConnections.blockchains:
networkConnectionModule.refreshBlockchainValues()
break
case Constants.walletConnections.market:
networkConnectionModule.refreshMarketValues()
break
case Constants.walletConnections.collectibles:
networkConnectionModule.refreshCollectiblesValues()
break
}
}
}

View File

@ -5,3 +5,4 @@ BIP39_en 1.0 BIP39_en.qml
ChartStoreBase 1.0 ChartStoreBase.qml
TokenBalanceHistoryStore 1.0 TokenBalanceHistoryStore.qml
TokenMarketValuesStore 1.0 TokenMarketValuesStore.qml
NetworkConnectionStore 1.0 NetworkConnectionStore.qml

View File

@ -17,6 +17,7 @@ Item {
id: root
property var account
property var networkConnectionStore
property bool assetDetailsLaunched: false
signal assetClicked(var token)
@ -32,8 +33,9 @@ Item {
id: assetListView
objectName: "assetViewStatusListView"
anchors.fill: parent
model: RootStore.tokensLoading ? Constants.dummyModelItems : filteredModel
delegate: RootStore.tokensLoading ? loadingTokenDelegate : tokenDelegate
// To-do: will try to move the loading tokens to the nim side under this task https://github.com/status-im/status-desktop/issues/9648
model: RootStore.tokensLoading || networkConnectionStore.noBlockchainConnWithoutCache ? Constants.dummyModelItems : filteredModel
delegate: RootStore.tokensLoading || networkConnectionStore.noBlockchainConnWithoutCache ? loadingTokenDelegate : tokenDelegate
}
SortFilterProxyModel {
@ -49,6 +51,7 @@ Item {
Component {
id: loadingTokenDelegate
LoadingTokenDelegate {
objectName: "AssetView_LoadingTokenDelegate_" + index
width: ListView.view.width
}
}
@ -58,6 +61,8 @@ Item {
TokenDelegate {
objectName: "AssetView_TokenListItem_" + symbol
readonly property string balance: "%1".arg(enabledNetworkBalance.amount) // Needed for the tests
errorTooltipText_1: networkConnectionStore.getBlockchainNetworkDownTextForToken(balances)
errorTooltipText_2: networkConnectionStore.getMarketNetworkDownText()
width: ListView.view.width
onClicked: {
RootStore.getHistoricalDataForToken(symbol, RootStore.currencyStore.currentCurrency)