feat(wallet) complete the filter API
Bumps status-go HEAD to include required changes Updates Nim filter components and APIs to follow API changes in status-go Complete the debugging code Add TODO placeholders to be completed in follow up PRs: collectibles ... General improvements and refactoring Closes #10634
This commit is contained in:
parent
1a07b73354
commit
9262943176
|
@ -1,4 +1,5 @@
|
||||||
import NimQml, logging, std/json, sequtils, sugar, options
|
import NimQml, logging, std/json, sequtils, sugar, options
|
||||||
|
import tables
|
||||||
|
|
||||||
import model
|
import model
|
||||||
import entry
|
import entry
|
||||||
|
@ -22,6 +23,9 @@ QtObject:
|
||||||
model: Model
|
model: Model
|
||||||
transactionsModule: transactions_module.AccessInterface
|
transactionsModule: transactions_module.AccessInterface
|
||||||
currentActivityFilter: backend_activity.ActivityFilter
|
currentActivityFilter: backend_activity.ActivityFilter
|
||||||
|
# TODO remove chains and addresses to use the app one
|
||||||
|
addresses: seq[string]
|
||||||
|
chainIds: seq[int]
|
||||||
|
|
||||||
proc setup(self: Controller) =
|
proc setup(self: Controller) =
|
||||||
self.QObject.setup
|
self.QObject.setup
|
||||||
|
@ -43,15 +47,15 @@ QtObject:
|
||||||
read = getModel
|
read = getModel
|
||||||
|
|
||||||
# TODO: move it to service, make it async and lazy load details for transactions
|
# TODO: move it to service, make it async and lazy load details for transactions
|
||||||
proc backendToPresentation(self: Controller, backendEnties: seq[backend_activity.ActivityEntry]): seq[entry.ActivityEntry] =
|
proc backendToPresentation(self: Controller, backendEntities: seq[backend_activity.ActivityEntry]): seq[entry.ActivityEntry] =
|
||||||
var multiTransactionsIds: seq[int] = @[]
|
var multiTransactionsIds: seq[int] = @[]
|
||||||
var transactionIdentities: seq[backend.TransactionIdentity] = @[]
|
var transactionIdentities: seq[backend.TransactionIdentity] = @[]
|
||||||
var pendingTransactionIdentities: seq[backend.TransactionIdentity] = @[]
|
var pendingTransactionIdentities: seq[backend.TransactionIdentity] = @[]
|
||||||
|
|
||||||
# Extract metadata required to fetch details
|
# Extract metadata required to fetch details
|
||||||
# TODO: temporary here to show the working API. Will be done as required on a detail request from UI
|
# TODO: temporary here to show the working API. Will be done as required on a detail request from UI
|
||||||
for backendEntry in backendEnties:
|
for backendEntry in backendEntities:
|
||||||
case backendEntry.transactionType:
|
case backendEntry.payloadType:
|
||||||
of MultiTransaction:
|
of MultiTransaction:
|
||||||
multiTransactionsIds.add(backendEntry.id)
|
multiTransactionsIds.add(backendEntry.id)
|
||||||
of SimpleTransaction:
|
of SimpleTransaction:
|
||||||
|
@ -59,11 +63,13 @@ QtObject:
|
||||||
of PendingTransaction:
|
of PendingTransaction:
|
||||||
pendingTransactionIdentities.add(backendEntry.transaction.get())
|
pendingTransactionIdentities.add(backendEntry.transaction.get())
|
||||||
|
|
||||||
var multiTransactions: seq[MultiTransactionDto] = @[]
|
var multiTransactions = initTable[int, MultiTransactionDto]()
|
||||||
if len(multiTransactionsIds) > 0:
|
if len(multiTransactionsIds) > 0:
|
||||||
multiTransactions = transaction_service.getMultiTransactions(multiTransactionsIds)
|
let mts = transaction_service.getMultiTransactions(multiTransactionsIds)
|
||||||
|
for mt in mts:
|
||||||
|
multiTransactions[mt.id] = mt
|
||||||
|
|
||||||
var transactions: seq[Item] = @[]
|
var transactions = initTable[TransactionIdentity, ref Item]()
|
||||||
if len(transactionIdentities) > 0:
|
if len(transactionIdentities) > 0:
|
||||||
let response = backend.getTransfersForIdentities(transactionIdentities)
|
let response = backend.getTransfersForIdentities(transactionIdentities)
|
||||||
let res = response.result
|
let res = response.result
|
||||||
|
@ -71,9 +77,11 @@ QtObject:
|
||||||
raise newException(Defect, "failed fetching transaction details")
|
raise newException(Defect, "failed fetching transaction details")
|
||||||
|
|
||||||
let transactionsDtos = res.getElems().map(x => x.toTransactionDto())
|
let transactionsDtos = res.getElems().map(x => x.toTransactionDto())
|
||||||
transactions = self.transactionsModule.transactionsToItems(transactionsDtos, @[])
|
let trItems = self.transactionsModule.transactionsToItems(transactionsDtos, @[])
|
||||||
|
for item in trItems:
|
||||||
|
transactions[TransactionIdentity(chainId: item.getChainId(), hash: item.getId(), address: item.getAddress())] = toRef(item)
|
||||||
|
|
||||||
var pendingTransactions: seq[Item] = @[]
|
var pendingTransactions = initTable[TransactionIdentity, ref Item]()
|
||||||
if len(pendingTransactionIdentities) > 0:
|
if len(pendingTransactionIdentities) > 0:
|
||||||
let response = backend.getPendingTransactionsForIdentities(pendingTransactionIdentities)
|
let response = backend.getPendingTransactionsForIdentities(pendingTransactionIdentities)
|
||||||
let res = response.result
|
let res = response.result
|
||||||
|
@ -81,33 +89,43 @@ QtObject:
|
||||||
raise newException(Defect, "failed fetching pending transactions details")
|
raise newException(Defect, "failed fetching pending transactions details")
|
||||||
|
|
||||||
let pendingTransactionsDtos = res.getElems().map(x => x.toPendingTransactionDto())
|
let pendingTransactionsDtos = res.getElems().map(x => x.toPendingTransactionDto())
|
||||||
pendingTransactions = self.transactionsModule.transactionsToItems(pendingTransactionsDtos, @[])
|
let trItems = self.transactionsModule.transactionsToItems(pendingTransactionsDtos, @[])
|
||||||
|
for item in trItems:
|
||||||
|
pendingTransactions[TransactionIdentity(chainId: item.getChainId(), hash: item.getId(), address: item.getAddress())] = toRef(item)
|
||||||
|
|
||||||
# Merge detailed transaction info in order
|
# Merge detailed transaction info in order
|
||||||
result = newSeq[entry.ActivityEntry](multiTransactions.len + transactions.len + pendingTransactions.len)
|
result = newSeqOfCap[entry.ActivityEntry](multiTransactions.len + transactions.len + pendingTransactions.len)
|
||||||
var mtIndex = 0
|
var mtIndex = 0
|
||||||
var tIndex = 0
|
var tIndex = 0
|
||||||
var ptIndex = 0
|
var ptIndex = 0
|
||||||
for i in low(backendEnties) .. high(backendEnties):
|
for backendEntry in backendEntities:
|
||||||
let backendEntry = backendEnties[i]
|
case backendEntry.payloadType:
|
||||||
case backendEntry.transactionType:
|
|
||||||
of MultiTransaction:
|
of MultiTransaction:
|
||||||
result[i] = entry.newMultiTransactionActivityEntry(multiTransactions[mtIndex])
|
let id = multiTransactionsIds[mtIndex]
|
||||||
|
if multiTransactions.hasKey(id):
|
||||||
|
result.add(entry.newMultiTransactionActivityEntry(multiTransactions[id], backendEntry))
|
||||||
|
else:
|
||||||
|
error "failed to find multi transaction with id: ", id
|
||||||
mtIndex += 1
|
mtIndex += 1
|
||||||
of SimpleTransaction:
|
of SimpleTransaction:
|
||||||
let refInstance = new(Item)
|
let identity = transactionIdentities[tIndex]
|
||||||
refInstance[] = transactions[tIndex]
|
if transactions.hasKey(identity):
|
||||||
result[i] = entry.newTransactionActivityEntry(refInstance, false)
|
result.add(entry.newTransactionActivityEntry(transactions[identity], backendEntry))
|
||||||
|
else:
|
||||||
|
error "failed to find transaction with identity: ", identity
|
||||||
tIndex += 1
|
tIndex += 1
|
||||||
of PendingTransaction:
|
of PendingTransaction:
|
||||||
let refInstance = new(Item)
|
let identity = pendingTransactionIdentities[ptIndex]
|
||||||
refInstance[] = pendingTransactions[ptIndex]
|
if pendingTransactions.hasKey(identity):
|
||||||
result[i] = entry.newTransactionActivityEntry(refInstance, true)
|
result.add(entry.newTransactionActivityEntry(pendingTransactions[identity], backendEntry))
|
||||||
|
else:
|
||||||
|
error "failed to find pending transaction with identity: ", identity
|
||||||
ptIndex += 1
|
ptIndex += 1
|
||||||
|
|
||||||
proc refreshData*(self: Controller) {.slot.} =
|
proc refreshData(self: Controller) =
|
||||||
|
|
||||||
# result type is RpcResponse
|
# result type is RpcResponse
|
||||||
let response = backend_activity.getActivityEntries(@["0x0000000000000000000000000000000000000001"], @[1], self.currentActivityFilter, 0, 10)
|
let response = backend_activity.getActivityEntries(self.addresses, self.chainIds, self.currentActivityFilter, 0, 10)
|
||||||
# RPC returns null for result in case of empty array
|
# RPC returns null for result in case of empty array
|
||||||
if response.error != nil or (response.result.kind != JArray and response.result.kind != JNull):
|
if response.error != nil or (response.result.kind != JArray and response.result.kind != JNull):
|
||||||
error "error fetching activity entries: ", response.error
|
error "error fetching activity entries: ", response.error
|
||||||
|
@ -117,15 +135,88 @@ QtObject:
|
||||||
self.model.setEntries(@[])
|
self.model.setEntries(@[])
|
||||||
return
|
return
|
||||||
|
|
||||||
var backendEnties = newSeq[backend_activity.ActivityEntry](response.result.len)
|
var backendEntities = newSeq[backend_activity.ActivityEntry](response.result.len)
|
||||||
for i in 0 ..< response.result.len:
|
for i in 0 ..< response.result.len:
|
||||||
backendEnties[i] = fromJson(response.result[i], backend_activity.ActivityEntry)
|
backendEntities[i] = fromJson(response.result[i], backend_activity.ActivityEntry)
|
||||||
let entries = self.backendToPresentation(backendEnties)
|
let entries = self.backendToPresentation(backendEntities)
|
||||||
self.model.setEntries(entries)
|
self.model.setEntries(entries)
|
||||||
|
|
||||||
# TODO: add all parameters and separate in different methods
|
proc updateFilter*(self: Controller) {.slot.} =
|
||||||
proc updateFilter*(self: Controller, startTimestamp: int, endTimestamp: int) {.slot.} =
|
self.refreshData()
|
||||||
# Update filter
|
|
||||||
|
proc setFilterTime*(self: Controller, startTimestamp: int, endTimestamp: int) {.slot.} =
|
||||||
self.currentActivityFilter.period = backend_activity.newPeriod(startTimestamp, endTimestamp)
|
self.currentActivityFilter.period = backend_activity.newPeriod(startTimestamp, endTimestamp)
|
||||||
|
|
||||||
self.refreshData()
|
proc setFilterType*(self: Controller, typesArrayJsonString: string) {.slot.} =
|
||||||
|
let typesJson = parseJson(typesArrayJsonString)
|
||||||
|
if typesJson.kind != JArray:
|
||||||
|
error "invalid array of json ints"
|
||||||
|
return
|
||||||
|
|
||||||
|
var types = newSeq[backend_activity.ActivityType](typesJson.len)
|
||||||
|
for i in 0 ..< typesJson.len:
|
||||||
|
types[i] = backend_activity.ActivityType(typesJson[i].getInt())
|
||||||
|
|
||||||
|
self.currentActivityFilter.types = types
|
||||||
|
|
||||||
|
proc setFilterStatus*(self: Controller, statusesArrayJsonString: string) {.slot.} =
|
||||||
|
let statusesJson = parseJson(statusesArrayJsonString)
|
||||||
|
if statusesJson.kind != JArray:
|
||||||
|
error "invalid array of json ints"
|
||||||
|
return
|
||||||
|
|
||||||
|
var statuses = newSeq[backend_activity.ActivityStatus](statusesJson.len)
|
||||||
|
for i in 0 ..< statusesJson.len:
|
||||||
|
statuses[i] = backend_activity.ActivityStatus(statusesJson[i].getInt())
|
||||||
|
|
||||||
|
self.currentActivityFilter.statuses = statuses
|
||||||
|
|
||||||
|
proc setFilterToAddresses*(self: Controller, addressesArrayJsonString: string) {.slot.} =
|
||||||
|
let addressesJson = parseJson(addressesArrayJsonString)
|
||||||
|
if addressesJson.kind != JArray:
|
||||||
|
error "invalid array of json strings"
|
||||||
|
return
|
||||||
|
|
||||||
|
var addresses = newSeq[string](addressesJson.len)
|
||||||
|
for i in 0 ..< addressesJson.len:
|
||||||
|
addresses[i] = addressesJson[i].getStr()
|
||||||
|
|
||||||
|
self.currentActivityFilter.counterpartyAddresses = addresses
|
||||||
|
|
||||||
|
proc setFilterAssets*(self: Controller, assetsArrayJsonString: string) {.slot.} =
|
||||||
|
let assetsJson = parseJson(assetsArrayJsonString)
|
||||||
|
if assetsJson.kind != JArray:
|
||||||
|
error "invalid array of json strings"
|
||||||
|
return
|
||||||
|
|
||||||
|
var assets = newSeq[TokenCode](assetsJson.len)
|
||||||
|
for i in 0 ..< assetsJson.len:
|
||||||
|
assets[i] = TokenCode(assetsJson[i].getStr())
|
||||||
|
|
||||||
|
self.currentActivityFilter.tokens.assets = option(assets)
|
||||||
|
|
||||||
|
# TODO: remove me and use ground truth
|
||||||
|
proc setFilterAddresses*(self: Controller, addressesArrayJsonString: string) {.slot.} =
|
||||||
|
let addressesJson = parseJson(addressesArrayJsonString)
|
||||||
|
if addressesJson.kind != JArray:
|
||||||
|
error "invalid array of json strings"
|
||||||
|
return
|
||||||
|
|
||||||
|
var addresses = newSeq[string](addressesJson.len)
|
||||||
|
for i in 0 ..< addressesJson.len:
|
||||||
|
addresses[i] = addressesJson[i].getStr()
|
||||||
|
|
||||||
|
self.addresses = addresses
|
||||||
|
|
||||||
|
# TODO: remove me and use ground truth
|
||||||
|
proc setFilterChains*(self: Controller, chainIdsArrayJsonString: string) {.slot.} =
|
||||||
|
let chainIdsJson = parseJson(chainIdsArrayJsonString)
|
||||||
|
if chainIdsJson.kind != JArray:
|
||||||
|
error "invalid array of json ints"
|
||||||
|
return
|
||||||
|
|
||||||
|
var chainIds = newSeq[int](chainIdsJson.len)
|
||||||
|
for i in 0 ..< chainIdsJson.len:
|
||||||
|
chainIds[i] = chainIdsJson[i].getInt()
|
||||||
|
|
||||||
|
self.chainIds = chainIds
|
||||||
|
|
|
@ -3,34 +3,44 @@ import NimQml, tables, json, strformat, sequtils, strutils, logging
|
||||||
import ../transactions/view
|
import ../transactions/view
|
||||||
import ../transactions/item
|
import ../transactions/item
|
||||||
import ./backend/transactions
|
import ./backend/transactions
|
||||||
|
import backend/activity as backend
|
||||||
|
|
||||||
# The ActivityEntry contains one of the following instances transaction, pensing transaction or multi-transaction
|
|
||||||
# It is used to display an activity history entry in the QML UI
|
# It is used to display an activity history entry in the QML UI
|
||||||
|
#
|
||||||
|
# TODO add all required metadata from filtering
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# Outdated: The ActivityEntry contains one of the following instances transaction, pending transaction or multi-transaction
|
||||||
QtObject:
|
QtObject:
|
||||||
type
|
type
|
||||||
ActivityEntry* = ref object of QObject
|
ActivityEntry* = ref object of QObject
|
||||||
|
# TODO: these should be removed
|
||||||
multi_transaction: MultiTransactionDto
|
multi_transaction: MultiTransactionDto
|
||||||
transaction: ref Item
|
transaction: ref Item
|
||||||
isPending: bool
|
isPending: bool
|
||||||
|
|
||||||
|
metadata: backend.ActivityEntry
|
||||||
|
|
||||||
proc setup(self: ActivityEntry) =
|
proc setup(self: ActivityEntry) =
|
||||||
self.QObject.setup
|
self.QObject.setup
|
||||||
|
|
||||||
proc delete*(self: ActivityEntry) =
|
proc delete*(self: ActivityEntry) =
|
||||||
self.QObject.delete
|
self.QObject.delete
|
||||||
|
|
||||||
proc newMultiTransactionActivityEntry*(mt: MultiTransactionDto): ActivityEntry =
|
proc newMultiTransactionActivityEntry*(mt: MultiTransactionDto, metadata: backend.ActivityEntry): ActivityEntry =
|
||||||
new(result, delete)
|
new(result, delete)
|
||||||
result.multi_transaction = mt
|
result.multi_transaction = mt
|
||||||
result.transaction = nil
|
result.transaction = nil
|
||||||
result.isPending = false
|
result.isPending = false
|
||||||
result.setup()
|
result.setup()
|
||||||
|
|
||||||
proc newTransactionActivityEntry*(tr: ref Item, isPending: bool): ActivityEntry =
|
proc newTransactionActivityEntry*(tr: ref Item, metadata: backend.ActivityEntry): ActivityEntry =
|
||||||
new(result, delete)
|
new(result, delete)
|
||||||
result.multi_transaction = nil
|
result.multi_transaction = nil
|
||||||
result.transaction = tr
|
result.transaction = tr
|
||||||
result.isPending = isPending
|
result.isPending = metadata.payloadType == backend.PayloadType.PendingTransaction
|
||||||
result.setup()
|
result.setup()
|
||||||
|
|
||||||
proc isMultiTransaction*(self: ActivityEntry): bool {.slot.} =
|
proc isMultiTransaction*(self: ActivityEntry): bool {.slot.} =
|
||||||
|
@ -125,3 +135,9 @@ QtObject:
|
||||||
read = getTimestamp
|
read = getTimestamp
|
||||||
|
|
||||||
# TODO: properties - type, fromChains, toChains, fromAsset, toAsset, assetName
|
# TODO: properties - type, fromChains, toChains, fromAsset, toAsset, assetName
|
||||||
|
|
||||||
|
# proc getType*(self: ActivityEntry): int {.slot.} =
|
||||||
|
# return self.metadata.activityType.int
|
||||||
|
|
||||||
|
# QtProperty[int] type:
|
||||||
|
# read = getType
|
|
@ -14,7 +14,7 @@ type
|
||||||
fromAsset: string
|
fromAsset: string
|
||||||
toAsset: string
|
toAsset: string
|
||||||
fromAmount: string
|
fromAmount: string
|
||||||
multiTxtype: MultiTransactionType
|
multiTxType: MultiTransactionType
|
||||||
|
|
||||||
proc initMultiTransactionItem*(
|
proc initMultiTransactionItem*(
|
||||||
id: int,
|
id: int,
|
||||||
|
@ -24,7 +24,7 @@ proc initMultiTransactionItem*(
|
||||||
fromAsset: string,
|
fromAsset: string,
|
||||||
toAsset: string,
|
toAsset: string,
|
||||||
fromAmount: string,
|
fromAmount: string,
|
||||||
multiTxtype: MultiTransactionType,
|
multiTxType: MultiTransactionType,
|
||||||
): MultiTransactionItem =
|
): MultiTransactionItem =
|
||||||
result.id = id
|
result.id = id
|
||||||
result.timestamp = timestamp
|
result.timestamp = timestamp
|
||||||
|
@ -33,7 +33,7 @@ proc initMultiTransactionItem*(
|
||||||
result.fromAsset = fromAsset
|
result.fromAsset = fromAsset
|
||||||
result.toAsset = toAsset
|
result.toAsset = toAsset
|
||||||
result.fromAmount = fromAmount
|
result.fromAmount = fromAmount
|
||||||
result.multiTxtype = multiTxtype
|
result.multiTxType = multiTxType
|
||||||
|
|
||||||
proc `$`*(self: MultiTransactionItem): string =
|
proc `$`*(self: MultiTransactionItem): string =
|
||||||
result = fmt"""MultiTransactionItem(
|
result = fmt"""MultiTransactionItem(
|
||||||
|
@ -44,7 +44,7 @@ proc `$`*(self: MultiTransactionItem): string =
|
||||||
fromAsset: {self.fromAsset},
|
fromAsset: {self.fromAsset},
|
||||||
toAsset: {self.toAsset},
|
toAsset: {self.toAsset},
|
||||||
fromAmount: {self.fromAmount},
|
fromAmount: {self.fromAmount},
|
||||||
multiTxtype: {self.multiTxtype},
|
multiTxType: {self.multiTxType},
|
||||||
]"""
|
]"""
|
||||||
|
|
||||||
proc getId*(self: MultiTransactionItem): int =
|
proc getId*(self: MultiTransactionItem): int =
|
||||||
|
@ -68,5 +68,5 @@ proc getToAsset*(self: MultiTransactionItem): string =
|
||||||
proc getFromAmount*(self: MultiTransactionItem): string =
|
proc getFromAmount*(self: MultiTransactionItem): string =
|
||||||
return self.fromAmount
|
return self.fromAmount
|
||||||
|
|
||||||
proc getMultiTxtype*(self: MultiTransactionItem): MultiTransactionType =
|
proc getMultiTxType*(self: MultiTransactionItem): MultiTransactionType =
|
||||||
return self.multiTxtype
|
return self.multiTxType
|
|
@ -93,5 +93,5 @@ proc multiTransactionToItem*(t: MultiTransactionDto): MultiTransactionItem =
|
||||||
t.fromAsset,
|
t.fromAsset,
|
||||||
t.toAsset,
|
t.toAsset,
|
||||||
t.fromAmount,
|
t.fromAmount,
|
||||||
t.multiTxtype
|
t.multiTxType
|
||||||
)
|
)
|
|
@ -151,7 +151,7 @@ proc toMultiTransactionDto*(jsonObj: JsonNode): MultiTransactionDto =
|
||||||
discard jsonObj.getProp("fromAmount", result.fromAmount)
|
discard jsonObj.getProp("fromAmount", result.fromAmount)
|
||||||
var multiTxType: int
|
var multiTxType: int
|
||||||
discard jsonObj.getProp("type", multiTxType)
|
discard jsonObj.getProp("type", multiTxType)
|
||||||
result.multiTxtype = cast[MultiTransactionType](multiTxType)
|
result.multiTxType = cast[MultiTransactionType](multiTxType)
|
||||||
|
|
||||||
proc cmpTransactions*(x, y: TransactionDto): int =
|
proc cmpTransactions*(x, y: TransactionDto): int =
|
||||||
# Sort proc to compare transactions from a single account.
|
# Sort proc to compare transactions from a single account.
|
||||||
|
|
|
@ -365,7 +365,7 @@ QtObject:
|
||||||
fromAsset: tokenSymbol,
|
fromAsset: tokenSymbol,
|
||||||
toAsset: tokenSymbol,
|
toAsset: tokenSymbol,
|
||||||
fromAmount: "0x" & amountToSend.toHex,
|
fromAmount: "0x" & amountToSend.toHex,
|
||||||
multiTxtype: transactions.MultiTransactionType.MultiTransactionSend,
|
multiTxType: transactions.MultiTransactionType.MultiTransactionSend,
|
||||||
),
|
),
|
||||||
paths,
|
paths,
|
||||||
password,
|
password,
|
||||||
|
@ -432,7 +432,7 @@ QtObject:
|
||||||
fromAsset: tokenSymbol,
|
fromAsset: tokenSymbol,
|
||||||
toAsset: tokenSymbol,
|
toAsset: tokenSymbol,
|
||||||
fromAmount: "0x" & amountToSend.toHex,
|
fromAmount: "0x" & amountToSend.toHex,
|
||||||
multiTxtype: transactions.MultiTransactionType.MultiTransactionSend,
|
multiTxType: transactions.MultiTransactionType.MultiTransactionSend,
|
||||||
),
|
),
|
||||||
paths,
|
paths,
|
||||||
password,
|
password,
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import times, strformat
|
import times, strformat, options
|
||||||
import json, json_serialization
|
import json, json_serialization
|
||||||
import options
|
import core, response_type
|
||||||
import ./core, ./response_type
|
from gen import rpc
|
||||||
from ./gen import rpc
|
import backend
|
||||||
import ./backend
|
|
||||||
import transactions
|
import transactions
|
||||||
|
|
||||||
export response_type
|
export response_type
|
||||||
|
|
||||||
|
# see status-go/services/wallet/activity/filter.go NoLimitTimestampForPeriod
|
||||||
|
const noLimitTimestampForPeriod = 0
|
||||||
|
|
||||||
# TODO: consider using common status-go types via protobuf
|
# TODO: consider using common status-go types via protobuf
|
||||||
# TODO: consider using flags instead of list of enums
|
# TODO: consider using flags instead of list of enums
|
||||||
type
|
type
|
||||||
Period* = object
|
Period* = object
|
||||||
startTimestamp*: int
|
startTimestamp* : int
|
||||||
endTimestamp*: int
|
endTimestamp*: int
|
||||||
|
|
||||||
# see status-go/services/wallet/activity/filter.go Type
|
# see status-go/services/wallet/activity/filter.go Type
|
||||||
|
@ -27,37 +29,89 @@ type
|
||||||
TokenType* {.pure.} = enum
|
TokenType* {.pure.} = enum
|
||||||
Asset, Collectibles
|
Asset, Collectibles
|
||||||
|
|
||||||
|
# see status-go/services/wallet/activity/filter.go TokenCode, TokenAddress
|
||||||
|
TokenCode* = distinct string
|
||||||
|
# Not used for now until collectibles are supported in the backend. TODO: extend this with chain ID and token ID
|
||||||
|
TokenAddress* = distinct string
|
||||||
|
|
||||||
|
# see status-go/services/wallet/activity/filter.go Tokens
|
||||||
|
# All empty sequences or none Options mean include all
|
||||||
|
Tokens* = object
|
||||||
|
assets*: Option[seq[TokenCode]]
|
||||||
|
collectibles*: Option[seq[TokenAddress]]
|
||||||
|
enabledTypes*: seq[TokenType]
|
||||||
|
|
||||||
# see status-go/services/wallet/activity/filter.go Filter
|
# see status-go/services/wallet/activity/filter.go Filter
|
||||||
|
# All empty sequences mean include all
|
||||||
ActivityFilter* = object
|
ActivityFilter* = object
|
||||||
period* {.serializedFieldName("period").}: Period
|
period*: Period
|
||||||
types* {.serializedFieldName("types").}: seq[ActivityType]
|
types*: seq[ActivityType]
|
||||||
statuses* {.serializedFieldName("statuses").}: seq[ActivityStatus]
|
statuses*: seq[ActivityStatus]
|
||||||
tokenTypes* {.serializedFieldName("tokenTypes").}: seq[TokenType]
|
tokens*: Tokens
|
||||||
counterpartyAddresses* {.serializedFieldName("counterpartyAddresses").}: seq[string]
|
counterpartyAddresses*: seq[string]
|
||||||
|
|
||||||
|
proc toJson[T](obj: Option[T]): JsonNode =
|
||||||
|
if obj.isSome:
|
||||||
|
toJson(obj.get())
|
||||||
|
else:
|
||||||
|
newJNull()
|
||||||
|
|
||||||
|
proc fromJson[T](jsonObj: JsonNode, TT: typedesc[Option[T]]): Option[T] =
|
||||||
|
if jsonObj.kind != JNull:
|
||||||
|
return some(to(jsonObj, T))
|
||||||
|
else:
|
||||||
|
return none(T)
|
||||||
|
|
||||||
|
proc `%`*(at: ActivityType): JsonNode {.inline.} =
|
||||||
|
return newJInt(ord(at))
|
||||||
|
|
||||||
|
proc `%`*(aSt: ActivityStatus): JsonNode {.inline.} =
|
||||||
|
return newJInt(ord(aSt))
|
||||||
|
|
||||||
|
proc `$`*(tc: TokenCode): string = $(string(tc))
|
||||||
|
proc `$`*(ta: TokenAddress): string = $(string(ta))
|
||||||
|
|
||||||
|
proc `%`*(tc: TokenCode): JsonNode {.inline.} =
|
||||||
|
return %(string(tc))
|
||||||
|
|
||||||
|
proc `%`*(ta: TokenAddress): JsonNode {.inline.} =
|
||||||
|
return %(string(ta))
|
||||||
|
|
||||||
|
proc parseJson*(tc: var TokenCode, node: JsonNode) =
|
||||||
|
tc = TokenCode(node.getStr)
|
||||||
|
|
||||||
|
proc parseJson*(ta: var TokenAddress, node: JsonNode) =
|
||||||
|
ta = TokenAddress(node.getStr)
|
||||||
|
|
||||||
|
proc newAllTokens(): Tokens =
|
||||||
|
result.assets = none(seq[TokenCode])
|
||||||
|
result.collectibles = none(seq[TokenAddress])
|
||||||
|
|
||||||
proc newPeriod*(startTime: Option[DateTime], endTime: Option[DateTime]): Period =
|
proc newPeriod*(startTime: Option[DateTime], endTime: Option[DateTime]): Period =
|
||||||
if startTime.isSome:
|
if startTime.isSome:
|
||||||
result.startTimestamp = startTime.get().toTime().toUnix().int
|
result.startTimestamp = startTime.get().toTime().toUnix().int
|
||||||
else:
|
else:
|
||||||
result.startTimestamp = 0
|
result.startTimestamp = noLimitTimestampForPeriod
|
||||||
if endTime.isSome:
|
if endTime.isSome:
|
||||||
result.endTimestamp = endTime.get().toTime().toUnix().int
|
result.endTimestamp = endTime.get().toTime().toUnix().int
|
||||||
else:
|
else:
|
||||||
result.endTimestamp = 0
|
result.endTimestamp = noLimitTimestampForPeriod
|
||||||
|
|
||||||
proc newPeriod*(startTimestamp: int, endTimestamp: int): Period =
|
proc newPeriod*(startTimestamp: int, endTimestamp: int): Period =
|
||||||
result.startTimestamp = startTimestamp
|
result.startTimestamp = startTimestamp
|
||||||
result.endTimestamp = endTimestamp
|
result.endTimestamp = endTimestamp
|
||||||
|
|
||||||
proc getIncludeAllActivityFilter*(): ActivityFilter =
|
proc getIncludeAllActivityFilter*(): ActivityFilter =
|
||||||
result = ActivityFilter(period: newPeriod(none(DateTime), none(DateTime)), types: @[], statuses: @[], tokenTypes: @[], counterpartyAddresses: @[])
|
result = ActivityFilter(period: newPeriod(none(DateTime), none(DateTime)), types: @[], statuses: @[],
|
||||||
|
tokens: newAllTokens(), counterpartyAddresses: @[])
|
||||||
|
|
||||||
# Empty sequence for paramters means include all
|
# Empty sequence for paramters means include all
|
||||||
proc newActivityFilter*(period: Period, activityType: seq[ActivityType], activityStatus: seq[ActivityStatus], tokenType: seq[TokenType], counterpartyAddress: seq[string]): ActivityFilter =
|
proc newActivityFilter*(period: Period, activityType: seq[ActivityType], activityStatus: seq[ActivityStatus],
|
||||||
|
tokens: Tokens, counterpartyAddress: seq[string]): ActivityFilter =
|
||||||
result.period = period
|
result.period = period
|
||||||
result.types = activityType
|
result.types = activityType
|
||||||
result.statuses = activityStatus
|
result.statuses = activityStatus
|
||||||
result.tokenTypes = tokenType
|
result.tokens = tokens
|
||||||
result.counterpartyAddresses = counterpartyAddress
|
result.counterpartyAddresses = counterpartyAddress
|
||||||
|
|
||||||
# Mirrors status-go/services/wallet/activity/activity.go PayloadType
|
# Mirrors status-go/services/wallet/activity/activity.go PayloadType
|
||||||
|
@ -68,8 +122,8 @@ type
|
||||||
PendingTransaction
|
PendingTransaction
|
||||||
|
|
||||||
# Define toJson proc for PayloadType
|
# Define toJson proc for PayloadType
|
||||||
proc toJson*(x: PayloadType): JsonNode {.inline.} =
|
proc `%`*(x: PayloadType): JsonNode {.inline.} =
|
||||||
return %*(ord(x))
|
return newJInt(ord(x))
|
||||||
|
|
||||||
# Define fromJson proc for PayloadType
|
# Define fromJson proc for PayloadType
|
||||||
proc fromJson*(x: JsonNode, T: typedesc[PayloadType]): PayloadType {.inline.} =
|
proc fromJson*(x: JsonNode, T: typedesc[PayloadType]): PayloadType {.inline.} =
|
||||||
|
@ -78,23 +132,16 @@ proc fromJson*(x: JsonNode, T: typedesc[PayloadType]): PayloadType {.inline.} =
|
||||||
# TODO: hide internals behind safe interface
|
# TODO: hide internals behind safe interface
|
||||||
type
|
type
|
||||||
ActivityEntry* = object
|
ActivityEntry* = object
|
||||||
transactionType* {.serializedFieldName("transactionType").}: PayloadType
|
# Identification
|
||||||
transaction* {.serializedFieldName("transaction").}: Option[TransactionIdentity]
|
payloadType*: PayloadType
|
||||||
id* {.serializedFieldName("id").}: int
|
transaction*: Option[TransactionIdentity]
|
||||||
timestamp* {.serializedFieldName("timestamp").}: int
|
id*: int
|
||||||
activityType* {.serializedFieldName("activityType").}: MultiTransactionType
|
|
||||||
|
|
||||||
proc fromJson[T](jsonObj: JsonNode, TT: typedesc[Option[T]]): Option[T] =
|
timestamp*: int
|
||||||
if jsonObj.kind != JNull:
|
# TODO: change it into ActivityType
|
||||||
return some(to(jsonObj, T))
|
activityType*: MultiTransactionType
|
||||||
else:
|
activityStatus*: ActivityStatus
|
||||||
return none(T)
|
tokenType*: TokenType
|
||||||
|
|
||||||
proc toJson[T](obj: Option[T]): JsonNode =
|
|
||||||
if obj.isSome:
|
|
||||||
toJson(obj.get())
|
|
||||||
else:
|
|
||||||
newJNull()
|
|
||||||
|
|
||||||
# Define toJson proc for PayloadType
|
# Define toJson proc for PayloadType
|
||||||
proc toJson*(ae: ActivityEntry): JsonNode {.inline.} =
|
proc toJson*(ae: ActivityEntry): JsonNode {.inline.} =
|
||||||
|
@ -103,19 +150,24 @@ proc toJson*(ae: ActivityEntry): JsonNode {.inline.} =
|
||||||
# Define fromJson proc for PayloadType
|
# Define fromJson proc for PayloadType
|
||||||
proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.} =
|
proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.} =
|
||||||
result = T(
|
result = T(
|
||||||
transactionType: fromJson(e["transactionType"], PayloadType),
|
payloadType: fromJson(e["payloadType"], PayloadType),
|
||||||
transaction: if e.hasKey("transaction"): fromJson(e["transaction"], Option[TransactionIdentity]) else: none(TransactionIdentity),
|
transaction: if e.hasKey("transaction"): fromJson(e["transaction"], Option[TransactionIdentity])
|
||||||
|
else: none(TransactionIdentity),
|
||||||
id: e["id"].getInt(),
|
id: e["id"].getInt(),
|
||||||
timestamp: e["timestamp"].getInt()
|
timestamp: e["timestamp"].getInt()
|
||||||
)
|
)
|
||||||
|
|
||||||
proc `$`*(self: ActivityEntry): string =
|
proc `$`*(self: ActivityEntry): string =
|
||||||
let transactionStr = if self.transaction.isSome: $self.transaction.get() else: "none(TransactionIdentity)"
|
let transactionStr = if self.transaction.isSome: $self.transaction.get()
|
||||||
|
else: "none(TransactionIdentity)"
|
||||||
return fmt"""ActivityEntry(
|
return fmt"""ActivityEntry(
|
||||||
transactionType:{self.transactionType.int},
|
payloadType:{$self.payloadType},
|
||||||
transaction:{transactionStr},
|
transaction:{transactionStr},
|
||||||
id:{self.id},
|
id:{self.id},
|
||||||
timestamp:{self.timestamp},
|
timestamp:{self.timestamp},
|
||||||
|
activityType* {$self.activityType},
|
||||||
|
activityStatus* {$self.activityStatus},
|
||||||
|
tokenType* {$self.tokenType},
|
||||||
)"""
|
)"""
|
||||||
|
|
||||||
rpc(getActivityEntries, "wallet"):
|
rpc(getActivityEntries, "wallet"):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import json, json_serialization, strformat
|
import json, json_serialization, strformat
|
||||||
|
import hashes
|
||||||
import ./core, ./response_type
|
import ./core, ./response_type
|
||||||
from ./gen import rpc
|
from ./gen import rpc
|
||||||
|
|
||||||
|
@ -98,9 +99,19 @@ rpc(getPendingTransactionsByChainIDs, "wallet"):
|
||||||
|
|
||||||
type
|
type
|
||||||
TransactionIdentity* = ref object
|
TransactionIdentity* = ref object
|
||||||
chainId* {.serializedFieldName("chainId").}: int
|
chainId*: int
|
||||||
hash* {.serializedFieldName("hash").}: string
|
hash*: string
|
||||||
address* {.serializedFieldName("address").}: string
|
address*: string
|
||||||
|
|
||||||
|
proc hash*(ti: TransactionIdentity): Hash =
|
||||||
|
var h: Hash = 0
|
||||||
|
h = h !& hash(ti.chainId)
|
||||||
|
h = h !& hash(ti.hash)
|
||||||
|
h = h !& hash(ti.address)
|
||||||
|
result = !$h
|
||||||
|
|
||||||
|
proc `==`*(a, b: TransactionIdentity): bool =
|
||||||
|
result = (a.chainId == b.chainId) and (a.hash == b.hash) and (a.address == b.address)
|
||||||
|
|
||||||
proc `$`*(self: TransactionIdentity): string =
|
proc `$`*(self: TransactionIdentity): string =
|
||||||
return fmt"""TransactionIdentity(
|
return fmt"""TransactionIdentity(
|
||||||
|
|
|
@ -17,7 +17,7 @@ type
|
||||||
fromAsset* {.serializedFieldName("fromAsset").}: string
|
fromAsset* {.serializedFieldName("fromAsset").}: string
|
||||||
toAsset* {.serializedFieldName("toAsset").}: string
|
toAsset* {.serializedFieldName("toAsset").}: string
|
||||||
fromAmount* {.serializedFieldName("fromAmount").}: string
|
fromAmount* {.serializedFieldName("fromAmount").}: string
|
||||||
multiTxtype* {.serializedFieldName("type").}: MultiTransactionType
|
multiTxType* {.serializedFieldName("type").}: MultiTransactionType
|
||||||
|
|
||||||
proc getTransactionByHash*(chainId: int, hash: string): RpcResponse[JsonNode] {.raises: [Exception].} =
|
proc getTransactionByHash*(chainId: int, hash: string): RpcResponse[JsonNode] {.raises: [Exception].} =
|
||||||
core.callPrivateRPCWithChainId("eth_getTransactionByHash", chainId, %* [hash])
|
core.callPrivateRPCWithChainId("eth_getTransactionByHash", chainId, %* [hash])
|
||||||
|
|
|
@ -84,6 +84,7 @@ Item {
|
||||||
text: qsTr("Activity")
|
text: qsTr("Activity")
|
||||||
}
|
}
|
||||||
// TODO - DEV: remove me
|
// TODO - DEV: remove me
|
||||||
|
// Enable for debugging activity filter
|
||||||
// currentIndex: 3
|
// currentIndex: 3
|
||||||
// StatusTabButton {
|
// StatusTabButton {
|
||||||
// rightPadding: 0
|
// rightPadding: 0
|
||||||
|
@ -128,6 +129,9 @@ Item {
|
||||||
// Layout.fillHeight: true
|
// Layout.fillHeight: true
|
||||||
|
|
||||||
// controller: RootStore.activityController
|
// controller: RootStore.activityController
|
||||||
|
// networksModel: RootStore.allNetworks
|
||||||
|
// assetsModel: RootStore.assets
|
||||||
|
// assetsLoading: RootStore.assetsLoading
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,7 +148,6 @@ Item {
|
||||||
|
|
||||||
assetsLoading: RootStore.assetsLoading
|
assetsLoading: RootStore.assetsLoading
|
||||||
address: RootStore.overview.mixedcaseAddress
|
address: RootStore.overview.mixedcaseAddress
|
||||||
|
|
||||||
networkConnectionStore: root.networkConnectionStore
|
networkConnectionStore: root.networkConnectionStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ import StatusQ.Components 0.1
|
||||||
import StatusQ.Controls 0.1
|
import StatusQ.Controls 0.1
|
||||||
import StatusQ.Core.Theme 0.1
|
import StatusQ.Core.Theme 0.1
|
||||||
|
|
||||||
|
import AppLayouts.stores 1.0
|
||||||
|
|
||||||
import SortFilterProxyModel 0.2
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
import utils 1.0
|
import utils 1.0
|
||||||
|
@ -18,10 +20,134 @@ import "../stores"
|
||||||
import "../controls"
|
import "../controls"
|
||||||
|
|
||||||
// Temporary developer view to test the filter APIs
|
// Temporary developer view to test the filter APIs
|
||||||
Item {
|
Control {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var controller
|
property var controller: null
|
||||||
|
property var networksModel: null
|
||||||
|
property var assetsModel: null
|
||||||
|
property bool assetsLoading: true
|
||||||
|
|
||||||
|
// Mirrors src/backend/activity.nim ActivityType
|
||||||
|
enum ActivityType {
|
||||||
|
Send,
|
||||||
|
Receive,
|
||||||
|
Buy,
|
||||||
|
Swap,
|
||||||
|
Bridge
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors src/backend/activity.nim ActivityStatus
|
||||||
|
enum ActivityStatus {
|
||||||
|
Failed,
|
||||||
|
Pending,
|
||||||
|
Complete,
|
||||||
|
Finalized
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
|
||||||
|
readonly property int millisInADay: 24 * 60 * 60 * 1000
|
||||||
|
property int start: fromSlider.value > 0
|
||||||
|
? Math.floor(new Date(new Date() - (fromSlider.value * millisInADay)).getTime() / 1000)
|
||||||
|
: 0
|
||||||
|
property int end: toSlider.value > 0
|
||||||
|
? Math.floor(new Date(new Date() - (toSlider.value * millisInADay)).getTime() / 1000)
|
||||||
|
: 0
|
||||||
|
|
||||||
|
function updateFilter() {
|
||||||
|
// Time
|
||||||
|
controller.setFilterTime(d.start, d.end)
|
||||||
|
|
||||||
|
// Activity types
|
||||||
|
var types = []
|
||||||
|
for(var i = 0; i < typeModel.count; i++) {
|
||||||
|
let item = typeModel.get(i)
|
||||||
|
if(item.checked) {
|
||||||
|
types.push(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.setFilterType(JSON.stringify(types))
|
||||||
|
|
||||||
|
// Activity status
|
||||||
|
var statuses = []
|
||||||
|
for(var i = 0; i < statusModel.count; i++) {
|
||||||
|
let item = statusModel.get(i)
|
||||||
|
if(item.checked) {
|
||||||
|
statuses.push(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.setFilterStatus(JSON.stringify(statuses))
|
||||||
|
|
||||||
|
// Counterparty addresses
|
||||||
|
var addresses = toAddressesInput.text.split(',')
|
||||||
|
if(addresses.length == 1 && addresses[0].trim() == "") {
|
||||||
|
addresses = []
|
||||||
|
} else {
|
||||||
|
for (var i = 0; i < addresses.length; i++) {
|
||||||
|
addresses[i] = padHexAddress(addresses[i].trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.setFilterToAddresses(JSON.stringify(addresses))
|
||||||
|
|
||||||
|
// Involved addresses
|
||||||
|
var addresses = addressesInput.text.split(',')
|
||||||
|
if(addresses.length == 1 && addresses[0].trim() == "") {
|
||||||
|
addresses = []
|
||||||
|
} else {
|
||||||
|
for (var i = 0; i < addresses.length; i++) {
|
||||||
|
addresses[i] = padHexAddress(addresses[i].trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.setFilterAddresses(JSON.stringify(addresses))
|
||||||
|
|
||||||
|
// Chains
|
||||||
|
var chains = []
|
||||||
|
for(var i = 0; i < clonedNetworksModel.count; i++) {
|
||||||
|
let item = clonedNetworksModel.get(i)
|
||||||
|
if(item.checked) {
|
||||||
|
chains.push(parseInt(item.chainId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.setFilterChains(JSON.stringify(chains))
|
||||||
|
|
||||||
|
// Assets
|
||||||
|
var assets = []
|
||||||
|
if(assetsLoader.status == Loader.Ready) {
|
||||||
|
for(var i = 0; i < assetsLoader.item.count; i++) {
|
||||||
|
let item = assetsLoader.item.get(i)
|
||||||
|
if(item.checked) {
|
||||||
|
assets.push(item.symbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.setFilterAssets(JSON.stringify(assets))
|
||||||
|
|
||||||
|
// Update the model
|
||||||
|
controller.updateFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
function padHexAddress(input) {
|
||||||
|
var addressLength = 40;
|
||||||
|
var strippedInput = input.startsWith("0x") ? input.slice(2) : input;
|
||||||
|
|
||||||
|
if (strippedInput.length > addressLength) {
|
||||||
|
console.error("Input is longer than expected address");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var paddingLength = addressLength - strippedInput.length;
|
||||||
|
var padding = Array(paddingLength + 1).join("0");
|
||||||
|
|
||||||
|
return "0x" + padding + strippedInput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
@ -29,11 +155,8 @@ Item {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: filterLayout
|
id: filterLayout
|
||||||
|
|
||||||
readonly property int millisInADay: 24 * 60 * 60 * 1000
|
ColumnLayout {
|
||||||
property int start: fromSlider.value > 0 ? Math.floor(new Date(new Date() - (fromSlider.value * millisInADay)).getTime() / 1000) : 0
|
id: timeFilterLayout
|
||||||
property int end: toSlider.value > 0 ? Math.floor(new Date(new Date() - (toSlider.value * millisInADay)).getTime() / 1000) : 0
|
|
||||||
|
|
||||||
function updateFilter() { controller.updateFilter(start, end) }
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Label { text: "Past Days Span: 100" }
|
Label { text: "Past Days Span: 100" }
|
||||||
|
@ -48,8 +171,6 @@ Item {
|
||||||
|
|
||||||
stepSize: 1
|
stepSize: 1
|
||||||
value: 0
|
value: 0
|
||||||
|
|
||||||
onPressedChanged: { if (!pressed) filterLayout.updateFilter() }
|
|
||||||
}
|
}
|
||||||
Label { text: `${fromSlider.value}d - ${toSlider.value}d` }
|
Label { text: `${fromSlider.value}d - ${toSlider.value}d` }
|
||||||
Slider {
|
Slider {
|
||||||
|
@ -65,12 +186,156 @@ Item {
|
||||||
|
|
||||||
stepSize: 1
|
stepSize: 1
|
||||||
value: 0
|
value: 0
|
||||||
|
|
||||||
onPressedChanged: { if (!pressed) filterLayout.updateFilter() }
|
|
||||||
}
|
}
|
||||||
Label { text: "0" }
|
Label { text: "0" }
|
||||||
}
|
}
|
||||||
Label { text: `Interval: ${filterLayout.start > 0 ? root.epochToDateStr(filterLayout.start) : "all time"} - ${filterLayout.end > 0 ? root.epochToDateStr(filterLayout.end) : "now"}` }
|
Label { text: `Interval: ${d.start > 0 ? root.epochToDateStr(d.start) : "all time"} - ${d.end > 0 ? root.epochToDateStr(d.end) : "now"}` }
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Label { text: "Type" }
|
||||||
|
// Models the ActivityType
|
||||||
|
ListModel {
|
||||||
|
id: typeModel
|
||||||
|
|
||||||
|
ListElement { text: qsTr("Send"); checked: false }
|
||||||
|
ListElement { text: qsTr("Receive"); checked: false }
|
||||||
|
ListElement { text: qsTr("Buy"); checked: false }
|
||||||
|
ListElement { text: qsTr("Swap"); checked: false }
|
||||||
|
ListElement { text: qsTr("Bridge"); checked: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
model: typeModel
|
||||||
|
|
||||||
|
displayText: qsTr("Select types")
|
||||||
|
|
||||||
|
currentIndex: -1
|
||||||
|
textRole: "text"
|
||||||
|
|
||||||
|
delegate: ItemOnOffDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label { text: "Status" }
|
||||||
|
// ActivityStatus
|
||||||
|
ListModel {
|
||||||
|
id: statusModel
|
||||||
|
ListElement { text: qsTr("Failed"); checked: false }
|
||||||
|
ListElement { text: qsTr("Pending"); checked: false }
|
||||||
|
ListElement { text: qsTr("Complete"); checked: false }
|
||||||
|
ListElement { text: qsTr("Finalized"); checked: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
displayText: qsTr("Select statuses")
|
||||||
|
|
||||||
|
model: statusModel
|
||||||
|
|
||||||
|
currentIndex: -1
|
||||||
|
textRole: "text"
|
||||||
|
|
||||||
|
delegate: ItemOnOffDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label { text: "To addresses" }
|
||||||
|
TextField {
|
||||||
|
id: toAddressesInput
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
placeholderText: qsTr("0x1234, 0x5678, ...")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: qsTr("Update")
|
||||||
|
onClicked: d.updateFilter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
|
||||||
|
Label { text: "Addresses" }
|
||||||
|
TextField {
|
||||||
|
id: addressesInput
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
placeholderText: qsTr("0x1234, 0x5678, ...")
|
||||||
|
}
|
||||||
|
|
||||||
|
Label { text: "Chains" }
|
||||||
|
ComboBox {
|
||||||
|
displayText: qsTr("Select chains")
|
||||||
|
|
||||||
|
Layout.preferredWidth: 300
|
||||||
|
|
||||||
|
model: clonedNetworksModel
|
||||||
|
currentIndex: -1
|
||||||
|
|
||||||
|
delegate: ItemOnOffDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label { text: "Assets" }
|
||||||
|
ComboBox {
|
||||||
|
displayText: assetsLoader.status != Loader.Ready ? qsTr("Loading...") : qsTr("Select an asset")
|
||||||
|
|
||||||
|
enabled: assetsLoader.status == Loader.Ready
|
||||||
|
|
||||||
|
Layout.preferredWidth: 300
|
||||||
|
|
||||||
|
model: assetsLoader.item
|
||||||
|
|
||||||
|
currentIndex: -1
|
||||||
|
|
||||||
|
delegate: ItemOnOffDelegate {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloneModel {
|
||||||
|
id: clonedNetworksModel
|
||||||
|
|
||||||
|
sourceModel: root.networksModel
|
||||||
|
roles: ["layer", "chainId", "chainName"]
|
||||||
|
rolesOverride: [{ role: "text", transform: (md) => `${md.chainName} [${md.chainId}] ${md.layer}` },
|
||||||
|
{ role: "checked", transform: (md) => false }]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found out the hard way that the assets are not loaded immediately after root.assetLoading is enabled so there is no data set yet
|
||||||
|
Timer {
|
||||||
|
id: delayAssetLoading
|
||||||
|
|
||||||
|
property bool loadingEnabled: false
|
||||||
|
|
||||||
|
interval: 1000; repeat: false
|
||||||
|
running: !root.assetsLoading
|
||||||
|
onTriggered: loadingEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: assetsLoader
|
||||||
|
|
||||||
|
sourceComponent: CloneModel {
|
||||||
|
sourceModel: root.assetsModel
|
||||||
|
roles: ["name", "symbol", "address"]
|
||||||
|
rolesOverride: [{ role: "text", transform: (md) => `[${md.symbol}] ${md.name}`},
|
||||||
|
{ role: "checked", transform: (md) => false }]
|
||||||
|
}
|
||||||
|
active: delayAssetLoading.loadingEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
component ItemOnOffDelegate: Item {
|
||||||
|
width: parent ? parent.width : 0
|
||||||
|
height: itemLayout.implicitHeight
|
||||||
|
|
||||||
|
readonly property var entry: model
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: itemLayout
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
CheckBox { checked: entry.checked; onCheckedChanged: entry.checked = checked }
|
||||||
|
Label { text: entry.text }
|
||||||
|
RowLayout {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
|
|
Loading…
Reference in New Issue