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:
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()

View File

@ -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):

View File

@ -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.}

View File

@ -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 = @[]

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

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() {
// 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 {

View File

@ -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
})

View File

@ -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

View File

@ -37,6 +37,12 @@ Popup {
footerContent.visible = true
stickersContainer.visible = true
}
Connections {
target: chatsModel
onOnlineStatusChanged: {
root.close()
}
}
contentItem: ColumnLayout {
anchors.fill: parent
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