feat(wallet) lazy load activity NFT information
Bump status-go to include required changes Implement processing of activity updated message Updates: #11600
This commit is contained in:
parent
722a9022e2
commit
2c769602f4
|
@ -1,4 +1,4 @@
|
|||
import NimQml, logging, std/json, sequtils, sugar, options, strutils
|
||||
import NimQml, logging, std/json, sequtils, sugar, options, strutils, locks
|
||||
import tables, stint, sets
|
||||
|
||||
import model
|
||||
|
@ -38,6 +38,7 @@ QtObject:
|
|||
type
|
||||
Controller* = ref object of QObject
|
||||
model: Model
|
||||
|
||||
recipientsModel: RecipientsModel
|
||||
currentActivityFilter: backend_activity.ActivityFilter
|
||||
currencyService: currency_service.Service
|
||||
|
@ -229,6 +230,7 @@ QtObject:
|
|||
return
|
||||
|
||||
let entries = self.backendToPresentation(res.activities)
|
||||
|
||||
self.model.setEntries(entries, res.offset, res.hasMore)
|
||||
|
||||
if len(entries) > 0:
|
||||
|
@ -237,7 +239,9 @@ QtObject:
|
|||
proc updateFilter*(self: Controller) {.slot.} =
|
||||
self.status.setLoadingData(true)
|
||||
self.status.setIsFilterDirty(false)
|
||||
|
||||
self.model.resetModel(@[])
|
||||
|
||||
self.eventsHandler.updateSubscribedAddresses(self.addresses)
|
||||
self.eventsHandler.updateSubscribedChainIDs(self.chainIds)
|
||||
self.status.setNewDataAvailable(false)
|
||||
|
@ -250,7 +254,6 @@ QtObject:
|
|||
|
||||
proc loadMoreItems(self: Controller) {.slot.} =
|
||||
self.status.setLoadingData(true)
|
||||
|
||||
let response = backend_activity.filterActivityAsync(self.requestId, self.addresses, seq[backend_activity.ChainId](self.chainIds), self.currentActivityFilter, self.model.getCount(), FETCH_BATCH_COUNT_DEFAULT)
|
||||
if response.error != nil:
|
||||
self.status.setLoadingData(false)
|
||||
|
@ -288,6 +291,17 @@ QtObject:
|
|||
self.processResponse(jsonObj)
|
||||
)
|
||||
|
||||
self.eventsHandler.onFilteringUpdateDone(proc (jn: JsonNode) =
|
||||
if jn.kind != JArray:
|
||||
error "expected an array"
|
||||
|
||||
var entries = newSeq[backend_activity.Data](jn.len)
|
||||
for i in 0 ..< jn.len:
|
||||
entries[i] = fromJson(jn[i], backend_activity.Data)
|
||||
|
||||
self.model.updateEntries(entries)
|
||||
)
|
||||
|
||||
self.eventsHandler.onGetRecipientsDone(proc (jsonObj: JsonNode) =
|
||||
defer: self.status.setLoadingRecipients(false)
|
||||
let res = fromJson(jsonObj, backend_activity.GetRecipientsResponse)
|
||||
|
|
|
@ -28,7 +28,6 @@ type
|
|||
# It is used to display an activity history entry in the QML UI
|
||||
#
|
||||
# TODO remove this legacy after the NFT is served async; see #11598
|
||||
# TODO add all required metadata from filtering; see #11597
|
||||
#
|
||||
# Looking into going away from carying the whole detailed data and just keep the required data for the UI
|
||||
# and request the detailed data on demand
|
||||
|
@ -50,6 +49,9 @@ QtObject:
|
|||
amountCurrency: CurrencyAmount
|
||||
noAmount: CurrencyAmount
|
||||
|
||||
nftName: string
|
||||
nftImageURL: string
|
||||
|
||||
proc setup(self: ActivityEntry) =
|
||||
self.QObject.setup
|
||||
|
||||
|
@ -129,6 +131,9 @@ QtObject:
|
|||
QtProperty[string] id:
|
||||
read = getId
|
||||
|
||||
proc getMetadata*(self: ActivityEntry): backend.ActivityEntry =
|
||||
return self.metadata
|
||||
|
||||
proc getSender*(self: ActivityEntry): string {.slot.} =
|
||||
return if self.metadata.sender.isSome(): "0x" & self.metadata.sender.unsafeGet().toHex() else: ""
|
||||
|
||||
|
@ -199,21 +204,33 @@ QtObject:
|
|||
QtProperty[bool] isNFT:
|
||||
read = getIsNFT
|
||||
|
||||
proc getNFTName*(self: ActivityEntry): string {.slot.} =
|
||||
# TODO: complete this async #11597
|
||||
return ""
|
||||
proc nftNameChanged*(self: ActivityEntry) {.signal.}
|
||||
|
||||
proc getNftName*(self: ActivityEntry): string {.slot.} =
|
||||
return self.nftName
|
||||
|
||||
proc setNftName*(self: ActivityEntry, nftName: string) =
|
||||
self.nftName = nftName
|
||||
self.nftNameChanged()
|
||||
|
||||
# TODO: lazy load this in activity history service. See #11597
|
||||
QtProperty[string] nftName:
|
||||
read = getNFTName
|
||||
read = getNftName
|
||||
write = setNftName
|
||||
notify = nftNameChanged
|
||||
|
||||
proc getNFTImageURL*(self: ActivityEntry): string {.slot.} =
|
||||
# TODO: complete this async #11597
|
||||
return ""
|
||||
proc nftImageUrlChanged*(self: ActivityEntry) {.signal.}
|
||||
|
||||
# TODO: lazy load this in activity history service. See #11597
|
||||
QtProperty[string] nftImageURL:
|
||||
read = getNFTImageURL
|
||||
proc getNftImageUrl*(self: ActivityEntry): string {.slot.} =
|
||||
return self.nftImageUrl
|
||||
|
||||
proc setNftImageUrl*(self: ActivityEntry, nftImageUrl: string) =
|
||||
self.nftImageUrl = nftImageUrl
|
||||
self.nftImageUrlChanged()
|
||||
|
||||
QtProperty[string] nftImageUrl:
|
||||
read = getNftImageUrl
|
||||
write = setNftImageUrl
|
||||
notify = nftImageUrlChanged
|
||||
|
||||
proc getTotalFees*(self: ActivityEntry): QVariant {.slot.} =
|
||||
if self.transaction == nil:
|
||||
|
|
|
@ -37,6 +37,9 @@ QtObject:
|
|||
proc onFilteringDone*(self: EventsHandler, handler: EventCallbackProc) =
|
||||
self.eventHandlers[backend_activity.eventActivityFilteringDone] = handler
|
||||
|
||||
proc onFilteringUpdateDone*(self: EventsHandler, handler: EventCallbackProc) =
|
||||
self.eventHandlers[backend_activity.eventActivityFilteringUpdate] = handler
|
||||
|
||||
proc onGetRecipientsDone*(self: EventsHandler, handler: EventCallbackProc) =
|
||||
self.eventHandlers[backend_activity.eventActivityGetRecipientsDone] = handler
|
||||
|
||||
|
@ -56,13 +59,8 @@ QtObject:
|
|||
let callback = self.walletEventHandlers[data.eventType]
|
||||
callback(data)
|
||||
elif self.eventHandlers.hasKey(data.eventType):
|
||||
var responseJson: JsonNode
|
||||
responseJson = parseJson(data.message)
|
||||
|
||||
if responseJson.kind != JObject:
|
||||
error "unexpected json type", responseJson.kind
|
||||
return
|
||||
let callback = self.eventHandlers[data.eventType]
|
||||
let responseJson = parseJson(data.message)
|
||||
callback(responseJson)
|
||||
|
||||
proc setupWalletEventHandlers(self: EventsHandler) =
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import NimQml, Tables, strutils, strformat, sequtils, logging
|
||||
import NimQml, Tables, strutils, strformat, sequtils, logging, options
|
||||
|
||||
import ./entry
|
||||
|
||||
import app/modules/shared_models/currency_amount
|
||||
import backend/activity as backend
|
||||
import backend/backend as importing_transactionidentity_comp
|
||||
|
||||
type
|
||||
ModelRole {.pure.} = enum
|
||||
|
@ -11,7 +13,7 @@ type
|
|||
QtObject:
|
||||
type
|
||||
Model* = ref object of QAbstractListModel
|
||||
entries: seq[ActivityEntry]
|
||||
entries: seq[entry.ActivityEntry]
|
||||
hasMore: bool
|
||||
|
||||
proc delete(self: Model) =
|
||||
|
@ -23,10 +25,12 @@ QtObject:
|
|||
|
||||
proc newModel*(): Model =
|
||||
new(result, delete)
|
||||
|
||||
result.entries = @[]
|
||||
result.setup
|
||||
result.hasMore = true
|
||||
|
||||
result.setup
|
||||
|
||||
proc `$`*(self: Model): string =
|
||||
for i in 0 ..< self.entries.len:
|
||||
result &= fmt"""[{i}]:({$self.entries[i]})"""
|
||||
|
@ -68,12 +72,12 @@ QtObject:
|
|||
self.hasMore = hasMore
|
||||
self.hasMoreChanged()
|
||||
|
||||
proc resetModel*(self: Model, newEntries: seq[ActivityEntry]) =
|
||||
proc resetModel*(self: Model, newEntries: seq[entry.ActivityEntry]) =
|
||||
self.beginResetModel()
|
||||
self.entries = newEntries
|
||||
self.endResetModel()
|
||||
|
||||
proc setEntries*(self: Model, newEntries: seq[ActivityEntry], offset: int, hasMore: bool) =
|
||||
proc setEntries*(self: Model, newEntries: seq[entry.ActivityEntry], offset: int, hasMore: bool) =
|
||||
if offset == 0:
|
||||
self.resetModel(newEntries)
|
||||
else:
|
||||
|
@ -83,12 +87,34 @@ QtObject:
|
|||
if offset != self.entries.len:
|
||||
error "offset != self.entries.len"
|
||||
return
|
||||
|
||||
self.beginInsertRows(parentModelIndex, self.entries.len, self.entries.len + newEntries.len - 1)
|
||||
self.entries.add(newEntries)
|
||||
self.endInsertRows()
|
||||
|
||||
self.countChanged()
|
||||
self.setHasMore(hasMore)
|
||||
|
||||
proc sameIdentity(e: entry.ActivityEntry, d: backend.Data): bool =
|
||||
let m = e.getMetadata()
|
||||
if m.payloadType != d.payloadType:
|
||||
return false
|
||||
|
||||
if m.payloadType == MultiTransaction:
|
||||
return m.id == d.id.get()
|
||||
|
||||
return m.transaction.isSome() and d.transaction.isSome() and m.transaction.get() == d.transaction.get()
|
||||
|
||||
proc updateEntries*(self: Model, updates: seq[backend.Data]) =
|
||||
for i in countdown(self.entries.high, 0):
|
||||
for j in countdown(updates.high, 0):
|
||||
if sameIdentity(self.entries[i], updates[j]):
|
||||
if updates[j].nftName.isSome():
|
||||
self.entries[i].setNftName(updates[j].nftName.get())
|
||||
if updates[j].nftUrl.isSome():
|
||||
self.entries[i].setNftImageUrl(updates[j].nftUrl.get())
|
||||
break
|
||||
|
||||
proc getHasMore*(self: Model): bool {.slot.} =
|
||||
return self.hasMore
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ const noLimitTimestampForPeriod* = 0
|
|||
|
||||
# Declared in services/wallet/activity/service.go
|
||||
const eventActivityFilteringDone*: string = "wallet-activity-filtering-done"
|
||||
const eventActivityFilteringUpdate*: string = "wallet-activity-filtering-entries-updated"
|
||||
const eventActivityGetRecipientsDone*: string = "wallet-activity-get-recipients-result"
|
||||
const eventActivityGetOldestTimestampDone*: string = "wallet-activity-get-oldest-timestamp-result"
|
||||
const eventActivityFetchTransactionDetails*: string = "wallet-activity-fetch-transaction-details-result"
|
||||
|
@ -100,8 +101,6 @@ proc `$`*(tt: TokenType): string {.inline.} =
|
|||
return "ERC-721"
|
||||
of Erc1155:
|
||||
return "ERC-1155"
|
||||
else:
|
||||
return ""
|
||||
|
||||
proc fromJson*(jn: JsonNode, T: typedesc[TokenType]): TokenType {.inline.} =
|
||||
return cast[TokenType](jn.getInt())
|
||||
|
@ -211,11 +210,9 @@ type
|
|||
SimpleTransaction
|
||||
PendingTransaction
|
||||
|
||||
# Define toJson proc for PayloadType
|
||||
proc `%`*(pt: PayloadType): JsonNode {.inline.} =
|
||||
return newJInt(ord(pt))
|
||||
|
||||
# Define fromJson proc for PayloadType
|
||||
proc fromJson*(jn: JsonNode, T: typedesc[PayloadType]): PayloadType {.inline.} =
|
||||
return cast[PayloadType](jn.getInt())
|
||||
|
||||
|
@ -250,11 +247,9 @@ type
|
|||
Erc721
|
||||
Erc1155
|
||||
|
||||
# Define toJson proc for TransferType
|
||||
proc `%`*(pt: TransferType): JsonNode {.inline.} =
|
||||
return newJInt(ord(pt))
|
||||
|
||||
# Define fromJson proc for TransferType
|
||||
proc fromJson*(jn: JsonNode, T: typedesc[TransferType]): TransferType {.inline.} =
|
||||
return cast[TransferType](jn.getInt())
|
||||
|
||||
|
@ -284,6 +279,32 @@ type
|
|||
chainIdIn*: Option[ChainId]
|
||||
transferType*: Option[TransferType]
|
||||
|
||||
# Mirrors status-go/services/wallet/activity/activity.go EntryData
|
||||
Data* = object
|
||||
payloadType*: PayloadType
|
||||
transaction*: Option[TransactionIdentity]
|
||||
id*: Option[int]
|
||||
|
||||
timestamp*: Option[int]
|
||||
|
||||
activityType*: Option[ActivityType]
|
||||
activityStatus*: Option[ActivityStatus]
|
||||
|
||||
amountOut*: Option[UInt256]
|
||||
amountIn*: Option[UInt256]
|
||||
|
||||
tokenOut*: Option[Token]
|
||||
tokenIn*: Option[Token]
|
||||
|
||||
sender*: Option[eth.Address]
|
||||
recipient*: Option[eth.Address]
|
||||
chainIdOut*: Option[ChainId]
|
||||
chainIdIn*: Option[ChainId]
|
||||
transferType*: Option[TransferType]
|
||||
|
||||
nftName*: Option[string]
|
||||
nftUrl*: Option[string]
|
||||
|
||||
# Mirrors services/wallet/activity/service.go ErrorCode
|
||||
ErrorCode* = enum
|
||||
ErrorCodeSuccess = 1,
|
||||
|
@ -297,12 +318,17 @@ type
|
|||
hasMore*: bool
|
||||
errorCode*: ErrorCode
|
||||
|
||||
# Define toJson proc for PayloadType
|
||||
proc toJson*(ae: ActivityEntry): JsonNode {.inline.} =
|
||||
return %*(ae)
|
||||
|
||||
# Define fromJson proc for PayloadType
|
||||
proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.} =
|
||||
proc fromJson*(e: JsonNode, T: typedesc[Data]): Data {.inline.} =
|
||||
const transactionField = "transaction"
|
||||
const idField = "id"
|
||||
const activityTypeField = "activityType"
|
||||
const activityStatusField = "activityStatus"
|
||||
const timestampField = "timestamp"
|
||||
const amountOutField = "amountOut"
|
||||
const amountInField = "amountIn"
|
||||
const tokenOutField = "tokenOut"
|
||||
const tokenInField = "tokenIn"
|
||||
const senderField = "sender"
|
||||
|
@ -310,20 +336,26 @@ proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.
|
|||
const chainIdOutField = "chainIdOut"
|
||||
const chainIdInField = "chainIdIn"
|
||||
const transferTypeField = "transferType"
|
||||
const nftNameField = "nftName"
|
||||
const nftUrlField = "nftUrl"
|
||||
result = T(
|
||||
payloadType: fromJson(e["payloadType"], PayloadType),
|
||||
transaction: if e.hasKey("transaction"):
|
||||
fromJson(e["transaction"], Option[TransactionIdentity])
|
||||
transaction: if e.hasKey(transactionField):
|
||||
fromJson(e[transactionField], Option[TransactionIdentity])
|
||||
else:
|
||||
none(TransactionIdentity),
|
||||
id: e["id"].getInt(),
|
||||
activityType: fromJson(e["activityType"], ActivityType),
|
||||
activityStatus: fromJson(e["activityStatus"], ActivityStatus),
|
||||
timestamp: e["timestamp"].getInt(),
|
||||
|
||||
amountOut: stint.fromHex(UInt256, e["amountOut"].getStr()),
|
||||
amountIn: stint.fromHex(UInt256, e["amountIn"].getStr()),
|
||||
|
||||
id: if e.hasKey(idField): some(e[idField].getInt()) else: none(int),
|
||||
activityType: if e.hasKey(activityTypeField):
|
||||
some(fromJson(e[activityTypeField], ActivityType))
|
||||
else:
|
||||
none(ActivityType),
|
||||
activityStatus: if e.hasKey(activityStatusField):
|
||||
some(fromJson(e[activityStatusField], ActivityStatus))
|
||||
else:
|
||||
none(ActivityStatus),
|
||||
timestamp: if e.hasKey(timestampField): some(e[timestampField].getInt()) else: none(int),
|
||||
amountOut: if e.hasKey(amountOutField): some(stint.fromHex(UInt256, e[amountOutField].getStr())) else: none(UInt256),
|
||||
amountIn: if e.hasKey(amountInField): some(stint.fromHex(UInt256, e[amountInField].getStr())) else: none(UInt256),
|
||||
tokenOut: if e.contains(tokenOutField):
|
||||
some(fromJson(e[tokenOutField], Token))
|
||||
else:
|
||||
|
@ -332,6 +364,9 @@ proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.
|
|||
some(fromJson(e[tokenInField], Token))
|
||||
else:
|
||||
none(Token),
|
||||
|
||||
nftName: if e.contains(nftNameField): some(e[nftNameField].getStr()) else: none(string),
|
||||
nftUrl: if e.contains(nftUrlField): some(e[nftUrlField].getStr()) else: none(string),
|
||||
)
|
||||
if e.hasKey(senderField) and e[senderField].kind != JNull:
|
||||
var address: eth.Address
|
||||
|
@ -348,6 +383,26 @@ proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.
|
|||
if e.hasKey(transferTypeField) and e[transferTypeField].kind != JNull:
|
||||
result.transferType = some(fromJson(e[transferTypeField], TransferType))
|
||||
|
||||
proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.} =
|
||||
let data = fromJson(e, Data)
|
||||
result = T(
|
||||
payloadType: data.payloadType,
|
||||
transaction: data.transaction,
|
||||
id: if data.id.isSome: data.id.get() else: 0,
|
||||
activityType: data.activityType.get(),
|
||||
activityStatus: data.activityStatus.get(),
|
||||
timestamp: data.timestamp.get(),
|
||||
amountOut: data.amountOut.get(),
|
||||
amountIn: data.amountIn.get(),
|
||||
tokenOut: data.tokenOut,
|
||||
tokenIn: data.tokenIn,
|
||||
sender: data.sender,
|
||||
recipient: data.recipient,
|
||||
chainIdOut: data.chainIdOut,
|
||||
chainIdIn: data.chainIdIn,
|
||||
transferType: data.transferType,
|
||||
)
|
||||
|
||||
proc `$`*(self: ActivityEntry): string =
|
||||
let transactionStr = if self.transaction.isSome: $self.transaction.get()
|
||||
else: "none(TransactionIdentity)"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import json, stint, json_serialization
|
||||
import json, stint, json_serialization, strformat
|
||||
|
||||
import ../app_service/service/eth/dto/transaction
|
||||
import ./core as core
|
||||
|
@ -41,6 +41,19 @@ const EventNonArchivalNodeDetected*: string = "non-archival-node-detected"
|
|||
const EventPendingTransactionUpdate*: string = "pending-transaction-update"
|
||||
const EventMTTransactionUpdate*: string = "multi-transaction-update"
|
||||
|
||||
proc `$`*(self: MultiTransactionDto): string =
|
||||
return fmt"""MultiTransactionDto(
|
||||
id:{self.id},
|
||||
timestamp:{self.timestamp},
|
||||
fromAddress:{self.fromAddress},
|
||||
toAddress:{self.toAddress},
|
||||
fromAsset:{self.fromAsset},
|
||||
toAsset:{self.toAsset},
|
||||
fromAmount:{self.fromAmount},
|
||||
toAmount:{self.toAmount},
|
||||
multiTxType:{self.multiTxType}
|
||||
)"""
|
||||
|
||||
proc getTransactionByHash*(chainId: int, hash: string): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||
core.callPrivateRPCWithChainId("eth_getTransactionByHash", chainId, %* [hash])
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 71800a19f1ee636652a208e77c550f72e9b8863d
|
||||
Subproject commit c0f32748b4927b5e3272e1e8e9cb6b226f002720
|
Loading…
Reference in New Issue