mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-26 13:36:04 +00:00
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:
parent
b82540fcb7
commit
7e1d7be314
@ -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)
|
||||||
|
|
||||||
|
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()
|
self.view.stickers.obtainAvailableStickerPacks()
|
||||||
|
@ -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):
|
||||||
|
@ -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.}
|
||||||
|
|
||||||
|
@ -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 = @[]
|
||||||
|
@ -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.} =
|
||||||
|
let status_stickers = self.status.stickers
|
||||||
|
spawnAndSend(self, "setGasEstimate") do:
|
||||||
var success: bool
|
var success: bool
|
||||||
result = self.status.stickers.estimateGas(packId, address, price, success)
|
var estimate = status_stickers.estimateGas(packId, address, price, success)
|
||||||
if not success:
|
if not success:
|
||||||
result = 325000
|
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
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -99,7 +99,9 @@ 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()
|
||||||
|
if response.hasKey("result"):
|
||||||
|
let latestBlock = parseInt($fromHex(Stuint[256], response["result"]["number"].getStr))
|
||||||
self.confirmTransactionStatus(status_wallet.getPendingTransactions().parseJson["result"], latestBlock)
|
self.confirmTransactionStatus(status_wallet.getPendingTransactions().parseJson["result"], latestBlock)
|
||||||
|
|
||||||
proc checkPendingTransactions*(self: WalletModel, address: string, blockNumber: int) =
|
proc checkPendingTransactions*(self: WalletModel, address: string, blockNumber: int) =
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
|
@ -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
2
vendor/DOtherSide
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 5012620a2ffbd9ac5c2b7c53671886d54c91a408
|
Subproject commit 1cc16aaa5c643d0d33c31f9953fbe4a9f6bde151
|
2
vendor/nimqml
vendored
2
vendor/nimqml
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 953e9f4b38478c835b9152103192220c49fc4128
|
Subproject commit 29fca3ce2ed2c9bc0c99163bc2c199b6ed101dd6
|
Loading…
x
Reference in New Issue
Block a user