From 7e1d7be314567e9887df600c6a0d8c681c5c38da Mon Sep 17 00:00:00 2001 From: emizzle Date: Mon, 14 Dec 2020 16:50:47 +1100 Subject: [PATCH] feat: load installed stickers while offline When the network connection is changed, the sticker packs are cleared and then re-loaded (either loading the offline (installed) sticker packs, or all the sticker packs from the network). Stickers can be sent while offline, though the sticker images do not load once offline (this is likely a side effect of the bug described below). There is a known bug in QNetworkAccessManager (https://bugreports.qt.io/browse/QTBUG-55180) that was re-introduced in 5.14.1 that does not allow us to download resources if we go offline then come back online. The workaround employed in this PR manually sets the NetworkAccessible property of QNetworkAccessManager once we have been connected back online. The NetworkAccessible property is marked as obsolete and will be removed in Qt6, so it is something that we need to be aware of when we upgrade. However the hope is that the bug has been fixed. Close StickersPopup when disconnected from network (so that re-loading of sticker packs doesn't look out of place). fix: set network status correctly at load feat: stickers gas estimate async feat: When network re-connected, any http images that were not properly loaded in an ImageLoader component will automatically be reloaded. fix: Sticker button loading icon chore: Bump nimqml and dotherside NOTE: This PR uses an updated nimqml and dotherside. The respective changes should be merged first, and the commit hash should be bumped in this PR prior to merging. Relevant PRs: [https://github.com/status-im/dotherside/pull/20](https://github.com/status-im/dotherside/pull/20) [https://github.com/status-im/nimqml/pull/17](https://github.com/status-im/nimqml/pull/17) --- src/app/chat/core.nim | 14 +++++++++- src/app/chat/event_handling.nim | 4 +-- src/app/chat/view.nim | 2 +- src/app/chat/views/sticker_pack_list.nim | 5 ++++ src/app/chat/views/stickers.nim | 28 +++++++++++++++---- src/app/node/core.nim | 4 ++- src/app/utilsView/core.nim | 1 + src/nim_status_client.nim | 17 ++++++++++- src/status/network.nim | 14 +++++++--- src/status/stickers.nim | 3 +- src/status/wallet.nim | 6 ++-- ui/shared/GasSelector.qml | 1 + ui/shared/ImageLoader.qml | 16 +++++++++++ ui/shared/status/StatusStickerButton.qml | 2 +- .../status/StatusStickerPackPurchaseModal.qml | 10 ++++++- ui/shared/status/StatusStickersPopup.qml | 6 ++++ vendor/DOtherSide | 2 +- vendor/nimqml | 2 +- 18 files changed, 115 insertions(+), 22 deletions(-) diff --git a/src/app/chat/core.nim b/src/app/chat/core.nim index 2bcc98f101..ee2e904bf9 100644 --- a/src/app/chat/core.nim +++ b/src/app/chat/core.nim @@ -47,4 +47,16 @@ proc init*(self: ChatController) = for sticker in recentStickers: self.view.stickers.addRecentStickerToList(sticker) self.status.stickers.addStickerToRecent(sticker) - self.view.stickers.obtainAvailableStickerPacks() + + if self.status.network.isConnected: + self.view.stickers.obtainAvailableStickerPacks() + else: + self.view.stickers.populateOfflineStickerPacks() + + self.status.events.on("network:disconnected") do(e: Args): + self.view.stickers.clearStickerPacks() + self.view.stickers.populateOfflineStickerPacks() + + self.status.events.on("network:connected") do(e: Args): + self.view.stickers.clearStickerPacks() + self.view.stickers.obtainAvailableStickerPacks() diff --git a/src/app/chat/event_handling.nim b/src/app/chat/event_handling.nim index 040fcc74a5..0ccb76f263 100644 --- a/src/app/chat/event_handling.nim +++ b/src/app/chat/event_handling.nim @@ -74,10 +74,10 @@ proc handleChatEvents(self: ChatController) = var msg = MessageSentArgs(e) self.view.markMessageAsSent(msg.chatId, msg.id) - self.status.events.on("chat:disconnected") do(e: Args): + self.status.events.on("network:disconnected") do(e: Args): self.view.setConnected(false) - self.status.events.on("chat:connected") do(e: Args): + self.status.events.on("network:connected") do(e: Args): self.view.setConnected(true) self.status.events.on(PendingTransactionType.BuyStickerPack.confirmed) do(e: Args): diff --git a/src/app/chat/view.nim b/src/app/chat/view.nim index edc5363533..05424573ad 100644 --- a/src/app/chat/view.nim +++ b/src/app/chat/view.nim @@ -459,7 +459,7 @@ QtObject: self.ensWasResolved(pubKey) proc isConnected*(self: ChatsView): bool {.slot.} = - result = self.connected + result = self.status.network.isConnected proc onlineStatusChanged(self: ChatsView, connected: bool) {.signal.} diff --git a/src/app/chat/views/sticker_pack_list.nim b/src/app/chat/views/sticker_pack_list.nim index 58b652bd22..476c5d655a 100644 --- a/src/app/chat/views/sticker_pack_list.nim +++ b/src/app/chat/views/sticker_pack_list.nim @@ -27,6 +27,11 @@ QtObject: proc delete(self: StickerPackList) = self.QAbstractListModel.delete + proc clear*(self: StickerPackList) = + self.beginResetModel() + self.packs = @[] + self.endResetModel() + proc newStickerPackList*(): StickerPackList = new(result, delete) result.packs = @[] diff --git a/src/app/chat/views/stickers.nim b/src/app/chat/views/stickers.nim index 36956f24f4..b1d56e1995 100644 --- a/src/app/chat/views/stickers.nim +++ b/src/app/chat/views/stickers.nim @@ -44,11 +44,21 @@ QtObject: proc transactionCompleted*(self: StickersView, success: bool, txHash: string, revertReason: string = "") {.signal.} - proc estimate*(self: StickersView, packId: int, address: string, price: string): int {.slot.} = - var success: bool - result = self.status.stickers.estimateGas(packId, address, price, success) - if not success: - result = 325000 + proc estimate*(self: StickersView, packId: int, address: string, price: string, uuid: string) {.slot.} = + let status_stickers = self.status.stickers + spawnAndSend(self, "setGasEstimate") do: + var success: bool + var estimate = status_stickers.estimateGas(packId, address, price, success) + if not success: + estimate = 325000 + let result: tuple[estimate: int, uuid: string] = (estimate, uuid) + Json.encode(result) + + proc gasEstimateReturned*(self: StickersView, estimate: int, uuid: string) {.signal.} + + proc setGasEstimate*(self: StickersView, estimateJson: string) {.slot.} = + let estimateResult = Json.decode(estimateJson, tuple[estimate: int, uuid: string]) + self.gasEstimateReturned(estimateResult.estimate, estimateResult.uuid) proc buy*(self: StickersView, packId: int, address: string, price: string, gas: string, gasPrice: string, password: string): string {.slot.} = var success: bool @@ -74,6 +84,14 @@ QtObject: proc recentStickersUpdated*(self: StickersView) {.signal.} + proc clearStickerPacks*(self: StickersView) = + self.stickerPacks.clear() + + proc populateOfflineStickerPacks*(self: StickersView) = + let installedStickerPacks = self.status.stickers.getInstalledStickerPacks() + for stickerPack in installedStickerPacks.values: + self.addStickerPackToList(stickerPack, isInstalled = true, isBought = true, isPending = false) + proc setAvailableStickerPacks*(self: StickersView, availableStickersJSON: string) {.slot.} = let accounts = status_wallet.getWalletAccounts() # TODO: make generic diff --git a/src/app/node/core.nim b/src/app/node/core.nim index d0c5d157af..505d19e411 100644 --- a/src/app/node/core.nim +++ b/src/app/node/core.nim @@ -12,12 +12,14 @@ type NodeController* = ref object status*: Status view*: NodeView variant*: QVariant + networkAccessMananger*: QNetworkAccessManager -proc newController*(status: Status): NodeController = +proc newController*(status: Status, nam: QNetworkAccessManager): NodeController = result = NodeController() result.status = status result.view = newNodeView(status) result.variant = newQVariant(result.view) + result.networkAccessMananger = nam proc delete*(self: NodeController) = delete self.variant diff --git a/src/app/utilsView/core.nim b/src/app/utilsView/core.nim index c0412a21d1..e9400419ff 100644 --- a/src/app/utilsView/core.nim +++ b/src/app/utilsView/core.nim @@ -3,6 +3,7 @@ import ../../status/signals/types import ../../status/[status, node, network] import ../../status/libstatus/types as status_types import view +import ../../eventemitter logScope: topics = "utils" diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index 9c48bdd250..55142be940 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -67,8 +67,23 @@ proc mainProc() = i18nPath = joinPath(getAppDir(), "../i18n") let networkAccessFactory = newQNetworkAccessManagerFactory(TMPDIR & "netcache") + let engine = newQQmlApplicationEngine() engine.setNetworkAccessManagerFactory(networkAccessFactory) + + let netAccMgr = newQNetworkAccessManager(engine.getNetworkAccessManager()) + + status.events.on("network:connected") do(e: Args): + # This is a workaround for Qt bug https://bugreports.qt.io/browse/QTBUG-55180 + # that was apparently reintroduced in 5.14.1 Unfortunately, the only workaround + # that could be found uses obsolete properties and methods + # (https://doc.qt.io/qt-5/qnetworkaccessmanager-obsolete.html), so this will + # need to be something we keep in mind when upgrading to Qt 6. + # The workaround is to manually set the NetworkAccessible property of the + # QNetworkAccessManager once peers have dropped (network connection is lost). + netAccMgr.clearConnectionCache() + netAccMgr.setNetworkAccessible(NetworkAccessibility.Accessible) + let signalController = signals.newController(status) # We need this global variable in order to be able to access the application @@ -81,7 +96,7 @@ proc mainProc() = var chat = chat.newController(status) engine.setRootContextProperty("chatsModel", chat.variant) - var node = node.newController(status) + var node = node.newController(status, netAccMgr) engine.setRootContextProperty("nodeModel", node.variant) var utilsController = utilsView.newController(status) diff --git a/src/status/network.nim b/src/status/network.nim index 74001649a8..9c4851f7a1 100644 --- a/src/status/network.nim +++ b/src/status/network.nim @@ -8,19 +8,25 @@ type NetworkModel* = ref object peers*: seq[string] events*: EventEmitter + connected*: bool proc newNetworkModel*(events: EventEmitter): NetworkModel = result = NetworkModel() result.events = events result.peers = @[] + result.connected = false proc peerSummaryChange*(self: NetworkModel, peers: seq[string]) = - if peers.len == 0: - self.events.emit("chat:disconnected", Args()) + if peers.len == 0 and self.connected: + self.connected = false + self.events.emit("network:disconnected", Args()) - if peers.len > 0: - self.events.emit("chat:connected", Args()) + if peers.len > 0 and not self.connected: + self.connected = true + self.events.emit("network:connected", Args()) self.peers = peers proc peerCount*(self: NetworkModel): int = self.peers.len + +proc isConnected*(self: NetworkModel): bool = self.connected diff --git a/src/status/stickers.nim b/src/status/stickers.nim index 09db15c034..5cb032f15c 100644 --- a/src/status/stickers.nim +++ b/src/status/stickers.nim @@ -58,10 +58,11 @@ proc estimateGas*(self: StickersModel, packId: int, address: string, price: stri tx = self.buildTransaction( packId.u256, parseAddress(address), - eth2Wei(parseFloat(price), 18), # SNT + eth2Wei(parseFloat(price), sntContract.decimals), approveAndCall, sntContract ) + let response = sntContract.methods["approveAndCall"].estimateGas(tx, approveAndCall, success) if success: result = fromHex[int](response) diff --git a/src/status/wallet.nim b/src/status/wallet.nim index c1c5aa9de0..8381931119 100644 --- a/src/status/wallet.nim +++ b/src/status/wallet.nim @@ -99,8 +99,10 @@ proc confirmTransactionStatus(self: WalletModel, pendingTransactions: JsonNode, self.events.emit(parseEnum[PendingTransactionType](trx["type"].getStr).confirmed, ev) proc checkPendingTransactions*(self: WalletModel) = - let latestBlock = parseInt($fromHex(Stuint[256], getBlockByNumber("latest").parseJson()["result"]["number"].getStr)) - self.confirmTransactionStatus(status_wallet.getPendingTransactions().parseJson["result"], latestBlock) + let response = getBlockByNumber("latest").parseJson() + if response.hasKey("result"): + let latestBlock = parseInt($fromHex(Stuint[256], response["result"]["number"].getStr)) + self.confirmTransactionStatus(status_wallet.getPendingTransactions().parseJson["result"], latestBlock) proc checkPendingTransactions*(self: WalletModel, address: string, blockNumber: int) = self.confirmTransactionStatus(status_wallet.getPendingOutboundTransactionsByAddress(address).parseJson["result"], blockNumber) diff --git a/ui/shared/GasSelector.qml b/ui/shared/GasSelector.qml index 5eb6b80f10..563c267f33 100644 --- a/ui/shared/GasSelector.qml +++ b/ui/shared/GasSelector.qml @@ -27,6 +27,7 @@ Item { //% "Please enter an amount" property string noInputErrorMessage: qsTrId("please-enter-an-amount") property bool isValid: true + readonly property string uuid: Utils.uuid() function defaultGasPrice() { return ((50 * (root.fastestGasPrice - root.slowestGasPrice) / 100) + root.slowestGasPrice) diff --git a/ui/shared/ImageLoader.qml b/ui/shared/ImageLoader.qml index 3f2ec1772b..f5db07b38e 100644 --- a/ui/shared/ImageLoader.qml +++ b/ui/shared/ImageLoader.qml @@ -69,13 +69,29 @@ Rectangle { } ] + Connections { + target: chatsModel + onOnlineStatusChanged: { + if (connected && root.state !== "ready" && + root.visible && + root.source && + root.source.startsWith("http")) { + root.reload() + } + } + } + function reload() { // From the documentation (https://doc.qt.io/qt-5/qml-qtquick-image.html#sourceSize-prop) // Note: Changing this property dynamically causes the image source to // be reloaded, potentially even from the network, if it is not in the // disk cache. + const oldSource = image.source + image.cache = false image.sourceSize.width += 1 image.sourceSize.width -= 1 + image.cache = true + } Component { diff --git a/ui/shared/status/StatusStickerButton.qml b/ui/shared/status/StatusStickerButton.qml index bcdea7d827..80900ecd78 100644 --- a/ui/shared/status/StatusStickerButton.qml +++ b/ui/shared/status/StatusStickerButton.qml @@ -99,7 +99,7 @@ Item { bgColor: root.style === StatusStickerButton.StyleType.Default ? Style.current.darkGrey : Style.current.grey; enabled: false; icon: new Object({ - path: "../../../img/loading.png", + path: "../../app/img/loading.png", rotation: 0, runAnimation: true }) diff --git a/ui/shared/status/StatusStickerPackPurchaseModal.qml b/ui/shared/status/StatusStickerPackPurchaseModal.qml index c235e94938..e878a80a0d 100644 --- a/ui/shared/status/StatusStickerPackPurchaseModal.qml +++ b/ui/shared/status/StatusStickerPackPurchaseModal.qml @@ -99,9 +99,17 @@ ModalPopup { selectedGasLimit = 325000 return } - selectedGasLimit = chatsModel.stickers.estimate(root.stickerPackId, selectFromAccount.selectedAccount.address, root.packPrice) + chatsModel.stickers.estimate(root.stickerPackId, selectFromAccount.selectedAccount.address, root.packPrice, uuid) }) } + Connections { + target: chatsModel.stickers + onGasEstimateReturned: { + if (uuid === gasSelector.uuid) { + gasSelector.selectedGasLimit = estimate + } + } + } GasValidator { id: gasValidator anchors.bottom: parent.bottom diff --git a/ui/shared/status/StatusStickersPopup.qml b/ui/shared/status/StatusStickersPopup.qml index 6fd7c0328b..8327a956d4 100644 --- a/ui/shared/status/StatusStickersPopup.qml +++ b/ui/shared/status/StatusStickersPopup.qml @@ -37,6 +37,12 @@ Popup { footerContent.visible = true stickersContainer.visible = true } + Connections { + target: chatsModel + onOnlineStatusChanged: { + root.close() + } + } contentItem: ColumnLayout { anchors.fill: parent spacing: 0 diff --git a/vendor/DOtherSide b/vendor/DOtherSide index 5012620a2f..1cc16aaa5c 160000 --- a/vendor/DOtherSide +++ b/vendor/DOtherSide @@ -1 +1 @@ -Subproject commit 5012620a2ffbd9ac5c2b7c53671886d54c91a408 +Subproject commit 1cc16aaa5c643d0d33c31f9953fbe4a9f6bde151 diff --git a/vendor/nimqml b/vendor/nimqml index 953e9f4b38..29fca3ce2e 160000 --- a/vendor/nimqml +++ b/vendor/nimqml @@ -1 +1 @@ -Subproject commit 953e9f4b38478c835b9152103192220c49fc4128 +Subproject commit 29fca3ce2ed2c9bc0c99163bc2c199b6ed101dd6