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)
This commit is contained in:
emizzle 2020-12-14 16:50:47 +11:00 committed by Iuri Matias
parent b82540fcb7
commit 7e1d7be314
18 changed files with 115 additions and 22 deletions

View File

@ -47,4 +47,16 @@ proc init*(self: ChatController) =
for sticker in recentStickers: for sticker in recentStickers:
self.view.stickers.addRecentStickerToList(sticker) self.view.stickers.addRecentStickerToList(sticker)
self.status.stickers.addStickerToRecent(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()

View File

@ -74,10 +74,10 @@ proc handleChatEvents(self: ChatController) =
var msg = MessageSentArgs(e) var msg = MessageSentArgs(e)
self.view.markMessageAsSent(msg.chatId, msg.id) 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.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.view.setConnected(true)
self.status.events.on(PendingTransactionType.BuyStickerPack.confirmed) do(e: Args): self.status.events.on(PendingTransactionType.BuyStickerPack.confirmed) do(e: Args):

View File

@ -459,7 +459,7 @@ QtObject:
self.ensWasResolved(pubKey) self.ensWasResolved(pubKey)
proc isConnected*(self: ChatsView): bool {.slot.} = proc isConnected*(self: ChatsView): bool {.slot.} =
result = self.connected result = self.status.network.isConnected
proc onlineStatusChanged(self: ChatsView, connected: bool) {.signal.} proc onlineStatusChanged(self: ChatsView, connected: bool) {.signal.}

View File

@ -27,6 +27,11 @@ QtObject:
proc delete(self: StickerPackList) = self.QAbstractListModel.delete proc delete(self: StickerPackList) = self.QAbstractListModel.delete
proc clear*(self: StickerPackList) =
self.beginResetModel()
self.packs = @[]
self.endResetModel()
proc newStickerPackList*(): StickerPackList = proc newStickerPackList*(): StickerPackList =
new(result, delete) new(result, delete)
result.packs = @[] result.packs = @[]

View File

@ -44,11 +44,21 @@ QtObject:
proc transactionCompleted*(self: StickersView, success: bool, txHash: string, revertReason: string = "") {.signal.} proc transactionCompleted*(self: StickersView, success: bool, txHash: string, revertReason: string = "") {.signal.}
proc estimate*(self: StickersView, packId: int, address: string, price: string): int {.slot.} = proc estimate*(self: StickersView, packId: int, address: string, price: string, uuid: string) {.slot.} =
var success: bool let status_stickers = self.status.stickers
result = self.status.stickers.estimateGas(packId, address, price, success) spawnAndSend(self, "setGasEstimate") do:
if not success: var success: bool
result = 325000 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.} = proc buy*(self: StickersView, packId: int, address: string, price: string, gas: string, gasPrice: string, password: string): string {.slot.} =
var success: bool var success: bool
@ -74,6 +84,14 @@ QtObject:
proc recentStickersUpdated*(self: StickersView) {.signal.} 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.} = proc setAvailableStickerPacks*(self: StickersView, availableStickersJSON: string) {.slot.} =
let let
accounts = status_wallet.getWalletAccounts() # TODO: make generic accounts = status_wallet.getWalletAccounts() # TODO: make generic

View File

@ -12,12 +12,14 @@ type NodeController* = ref object
status*: Status status*: Status
view*: NodeView view*: NodeView
variant*: QVariant variant*: QVariant
networkAccessMananger*: QNetworkAccessManager
proc newController*(status: Status): NodeController = proc newController*(status: Status, nam: QNetworkAccessManager): NodeController =
result = NodeController() result = NodeController()
result.status = status result.status = status
result.view = newNodeView(status) result.view = newNodeView(status)
result.variant = newQVariant(result.view) result.variant = newQVariant(result.view)
result.networkAccessMananger = nam
proc delete*(self: NodeController) = proc delete*(self: NodeController) =
delete self.variant delete self.variant

View File

@ -3,6 +3,7 @@ import ../../status/signals/types
import ../../status/[status, node, network] import ../../status/[status, node, network]
import ../../status/libstatus/types as status_types import ../../status/libstatus/types as status_types
import view import view
import ../../eventemitter
logScope: logScope:
topics = "utils" topics = "utils"

View File

@ -67,8 +67,23 @@ proc mainProc() =
i18nPath = joinPath(getAppDir(), "../i18n") i18nPath = joinPath(getAppDir(), "../i18n")
let networkAccessFactory = newQNetworkAccessManagerFactory(TMPDIR & "netcache") let networkAccessFactory = newQNetworkAccessManagerFactory(TMPDIR & "netcache")
let engine = newQQmlApplicationEngine() let engine = newQQmlApplicationEngine()
engine.setNetworkAccessManagerFactory(networkAccessFactory) 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) let signalController = signals.newController(status)
# We need this global variable in order to be able to access the application # 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) var chat = chat.newController(status)
engine.setRootContextProperty("chatsModel", chat.variant) engine.setRootContextProperty("chatsModel", chat.variant)
var node = node.newController(status) var node = node.newController(status, netAccMgr)
engine.setRootContextProperty("nodeModel", node.variant) engine.setRootContextProperty("nodeModel", node.variant)
var utilsController = utilsView.newController(status) var utilsController = utilsView.newController(status)

View File

@ -8,19 +8,25 @@ type
NetworkModel* = ref object NetworkModel* = ref object
peers*: seq[string] peers*: seq[string]
events*: EventEmitter events*: EventEmitter
connected*: bool
proc newNetworkModel*(events: EventEmitter): NetworkModel = proc newNetworkModel*(events: EventEmitter): NetworkModel =
result = NetworkModel() result = NetworkModel()
result.events = events result.events = events
result.peers = @[] result.peers = @[]
result.connected = false
proc peerSummaryChange*(self: NetworkModel, peers: seq[string]) = proc peerSummaryChange*(self: NetworkModel, peers: seq[string]) =
if peers.len == 0: if peers.len == 0 and self.connected:
self.events.emit("chat:disconnected", Args()) self.connected = false
self.events.emit("network:disconnected", Args())
if peers.len > 0: if peers.len > 0 and not self.connected:
self.events.emit("chat:connected", Args()) self.connected = true
self.events.emit("network:connected", Args())
self.peers = peers self.peers = peers
proc peerCount*(self: NetworkModel): int = self.peers.len proc peerCount*(self: NetworkModel): int = self.peers.len
proc isConnected*(self: NetworkModel): bool = self.connected

View File

@ -58,10 +58,11 @@ proc estimateGas*(self: StickersModel, packId: int, address: string, price: stri
tx = self.buildTransaction( tx = self.buildTransaction(
packId.u256, packId.u256,
parseAddress(address), parseAddress(address),
eth2Wei(parseFloat(price), 18), # SNT eth2Wei(parseFloat(price), sntContract.decimals),
approveAndCall, approveAndCall,
sntContract sntContract
) )
let response = sntContract.methods["approveAndCall"].estimateGas(tx, approveAndCall, success) let response = sntContract.methods["approveAndCall"].estimateGas(tx, approveAndCall, success)
if success: if success:
result = fromHex[int](response) result = fromHex[int](response)

View File

@ -99,8 +99,10 @@ proc confirmTransactionStatus(self: WalletModel, pendingTransactions: JsonNode,
self.events.emit(parseEnum[PendingTransactionType](trx["type"].getStr).confirmed, ev) self.events.emit(parseEnum[PendingTransactionType](trx["type"].getStr).confirmed, ev)
proc checkPendingTransactions*(self: WalletModel) = proc checkPendingTransactions*(self: WalletModel) =
let latestBlock = parseInt($fromHex(Stuint[256], getBlockByNumber("latest").parseJson()["result"]["number"].getStr)) let response = getBlockByNumber("latest").parseJson()
self.confirmTransactionStatus(status_wallet.getPendingTransactions().parseJson["result"], latestBlock) 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) = proc checkPendingTransactions*(self: WalletModel, address: string, blockNumber: int) =
self.confirmTransactionStatus(status_wallet.getPendingOutboundTransactionsByAddress(address).parseJson["result"], blockNumber) self.confirmTransactionStatus(status_wallet.getPendingOutboundTransactionsByAddress(address).parseJson["result"], blockNumber)

View File

@ -27,6 +27,7 @@ Item {
//% "Please enter an amount" //% "Please enter an amount"
property string noInputErrorMessage: qsTrId("please-enter-an-amount") property string noInputErrorMessage: qsTrId("please-enter-an-amount")
property bool isValid: true property bool isValid: true
readonly property string uuid: Utils.uuid()
function defaultGasPrice() { function defaultGasPrice() {
return ((50 * (root.fastestGasPrice - root.slowestGasPrice) / 100) + root.slowestGasPrice) return ((50 * (root.fastestGasPrice - root.slowestGasPrice) / 100) + root.slowestGasPrice)

View File

@ -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() { function reload() {
// From the documentation (https://doc.qt.io/qt-5/qml-qtquick-image.html#sourceSize-prop) // 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 // Note: Changing this property dynamically causes the image source to
// be reloaded, potentially even from the network, if it is not in the // be reloaded, potentially even from the network, if it is not in the
// disk cache. // disk cache.
const oldSource = image.source
image.cache = false
image.sourceSize.width += 1 image.sourceSize.width += 1
image.sourceSize.width -= 1 image.sourceSize.width -= 1
image.cache = true
} }
Component { Component {

View File

@ -99,7 +99,7 @@ Item {
bgColor: root.style === StatusStickerButton.StyleType.Default ? Style.current.darkGrey : Style.current.grey; bgColor: root.style === StatusStickerButton.StyleType.Default ? Style.current.darkGrey : Style.current.grey;
enabled: false; enabled: false;
icon: new Object({ icon: new Object({
path: "../../../img/loading.png", path: "../../app/img/loading.png",
rotation: 0, rotation: 0,
runAnimation: true runAnimation: true
}) })

View File

@ -99,9 +99,17 @@ ModalPopup {
selectedGasLimit = 325000 selectedGasLimit = 325000
return 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 { GasValidator {
id: gasValidator id: gasValidator
anchors.bottom: parent.bottom anchors.bottom: parent.bottom

View File

@ -37,6 +37,12 @@ Popup {
footerContent.visible = true footerContent.visible = true
stickersContainer.visible = true stickersContainer.visible = true
} }
Connections {
target: chatsModel
onOnlineStatusChanged: {
root.close()
}
}
contentItem: ColumnLayout { contentItem: ColumnLayout {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0

2
vendor/DOtherSide vendored

@ -1 +1 @@
Subproject commit 5012620a2ffbd9ac5c2b7c53671886d54c91a408 Subproject commit 1cc16aaa5c643d0d33c31f9953fbe4a9f6bde151

2
vendor/nimqml vendored

@ -1 +1 @@
Subproject commit 953e9f4b38478c835b9152103192220c49fc4128 Subproject commit 29fca3ce2ed2c9bc0c99163bc2c199b6ed101dd6