mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-19 18:18:38 +00:00
feat(wallet): filter activity by ERC20
Refactor code to use the token identity instead of token code Removed the debugging activity view as now we have the API integrated in the history view Fixed the activity type in the activity entry Closes: #11025
This commit is contained in:
parent
ecc1b5316f
commit
2ba9680316
@ -1,10 +1,12 @@
|
|||||||
import NimQml, logging, std/json, sequtils, sugar, options
|
import NimQml, logging, std/json, sequtils, sugar, options, strutils
|
||||||
import tables, stint
|
import tables, stint, sets
|
||||||
|
|
||||||
import model
|
import model
|
||||||
import entry
|
import entry
|
||||||
import recipients_model
|
import recipients_model
|
||||||
|
|
||||||
|
import web3/conversions
|
||||||
|
|
||||||
import ../transactions/item
|
import ../transactions/item
|
||||||
import ../transactions/module as transactions_module
|
import ../transactions/module as transactions_module
|
||||||
|
|
||||||
@ -17,6 +19,8 @@ import backend/transactions
|
|||||||
|
|
||||||
import app_service/service/currency/service as currency_service
|
import app_service/service/currency/service as currency_service
|
||||||
import app_service/service/transaction/service as transaction_service
|
import app_service/service/transaction/service as transaction_service
|
||||||
|
import app_service/service/token/service as token_service
|
||||||
|
|
||||||
|
|
||||||
proc toRef*[T](obj: T): ref T =
|
proc toRef*[T](obj: T): ref T =
|
||||||
new(result)
|
new(result)
|
||||||
@ -25,6 +29,7 @@ proc toRef*[T](obj: T): ref T =
|
|||||||
const FETCH_BATCH_COUNT_DEFAULT = 10
|
const FETCH_BATCH_COUNT_DEFAULT = 10
|
||||||
const FETCH_RECIPIENTS_BATCH_COUNT_DEFAULT = 2000
|
const FETCH_RECIPIENTS_BATCH_COUNT_DEFAULT = 2000
|
||||||
|
|
||||||
|
# TODO: implement passing of collectibles
|
||||||
QtObject:
|
QtObject:
|
||||||
type
|
type
|
||||||
Controller* = ref object of QObject
|
Controller* = ref object of QObject
|
||||||
@ -33,14 +38,18 @@ QtObject:
|
|||||||
transactionsModule: transactions_module.AccessInterface
|
transactionsModule: transactions_module.AccessInterface
|
||||||
currentActivityFilter: backend_activity.ActivityFilter
|
currentActivityFilter: backend_activity.ActivityFilter
|
||||||
currencyService: currency_service.Service
|
currencyService: currency_service.Service
|
||||||
|
tokenService: token_service.Service
|
||||||
|
|
||||||
events: EventEmitter
|
events: EventEmitter
|
||||||
|
|
||||||
loadingData: bool
|
loadingData: bool
|
||||||
errorCode: backend_activity.ErrorCode
|
errorCode: backend_activity.ErrorCode
|
||||||
|
|
||||||
# TODO remove chains and addresses after using ground truth
|
# call updateAssetsIdentities after updating filterTokenCodes
|
||||||
|
filterTokenCodes: HashSet[string]
|
||||||
|
|
||||||
addresses: seq[string]
|
addresses: seq[string]
|
||||||
|
# call updateAssetsIdentities after updating chainIds
|
||||||
chainIds: seq[int]
|
chainIds: seq[int]
|
||||||
|
|
||||||
proc setup(self: Controller) =
|
proc setup(self: Controller) =
|
||||||
@ -185,20 +194,21 @@ QtObject:
|
|||||||
|
|
||||||
proc updateFilter*(self: Controller) {.slot.} =
|
proc updateFilter*(self: Controller) {.slot.} =
|
||||||
self.setLoadingData(true)
|
self.setLoadingData(true)
|
||||||
let response = backend_activity.filterActivityAsync(self.addresses, self.chainIds, self.currentActivityFilter, 0, FETCH_BATCH_COUNT_DEFAULT)
|
|
||||||
|
let response = backend_activity.filterActivityAsync(self.addresses, seq[backend_activity.ChainId](self.chainIds), self.currentActivityFilter, 0, FETCH_BATCH_COUNT_DEFAULT)
|
||||||
if response.error != nil:
|
if response.error != nil:
|
||||||
error "error fetching activity entries: ", response.error
|
error "error fetching activity entries: ", response.error
|
||||||
self.setLoadingData(false)
|
self.setLoadingData(false)
|
||||||
return
|
return
|
||||||
|
|
||||||
proc loadMoreItems(self: Controller) {.slot.} =
|
proc loadMoreItems(self: Controller) {.slot.} =
|
||||||
let response = backend_activity.filterActivityAsync(self.addresses, self.chainIds, self.currentActivityFilter, self.model.getCount(), FETCH_BATCH_COUNT_DEFAULT)
|
self.setLoadingData(true)
|
||||||
|
|
||||||
|
let response = backend_activity.filterActivityAsync(self.addresses, seq[backend_activity.ChainId](self.chainIds), self.currentActivityFilter, self.model.getCount(), FETCH_BATCH_COUNT_DEFAULT)
|
||||||
if response.error != nil:
|
if response.error != nil:
|
||||||
error "error fetching activity entries: ", response.error
|
error "error fetching activity entries: ", response.error
|
||||||
return
|
return
|
||||||
|
|
||||||
self.setLoadingData(true)
|
|
||||||
|
|
||||||
proc setFilterTime*(self: Controller, startTimestamp: int, endTimestamp: int) {.slot.} =
|
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)
|
||||||
|
|
||||||
@ -214,18 +224,31 @@ QtObject:
|
|||||||
|
|
||||||
self.currentActivityFilter.types = types
|
self.currentActivityFilter.types = types
|
||||||
|
|
||||||
proc newController*(transactionsModule: transactions_module.AccessInterface, events: EventEmitter, currencyService: currency_service.Service): Controller =
|
proc newController*(transactionsModule: transactions_module.AccessInterface,
|
||||||
|
currencyService: currency_service.Service,
|
||||||
|
tokenService: token_service.Service,
|
||||||
|
events: EventEmitter): Controller =
|
||||||
new(result, delete)
|
new(result, delete)
|
||||||
result.model = newModel()
|
result.model = newModel()
|
||||||
result.recipientsModel = newRecipientsModel()
|
result.recipientsModel = newRecipientsModel()
|
||||||
result.transactionsModule = transactionsModule
|
result.transactionsModule = transactionsModule
|
||||||
|
result.tokenService = tokenService
|
||||||
result.currentActivityFilter = backend_activity.getIncludeAllActivityFilter()
|
result.currentActivityFilter = backend_activity.getIncludeAllActivityFilter()
|
||||||
result.events = events
|
result.events = events
|
||||||
result.currencyService = currencyService
|
result.currencyService = currencyService
|
||||||
|
|
||||||
|
result.loadingData = false
|
||||||
|
result.errorCode = backend_activity.ErrorCode.ErrorCodeSuccess
|
||||||
|
|
||||||
|
result.filterTokenCodes = initHashSet[string]()
|
||||||
|
|
||||||
|
result.addresses = @[]
|
||||||
|
result.chainIds = @[]
|
||||||
|
|
||||||
result.setup()
|
result.setup()
|
||||||
|
|
||||||
|
# Register and process events
|
||||||
let controller = result
|
let controller = result
|
||||||
|
|
||||||
proc handleEvent(e: Args) =
|
proc handleEvent(e: Args) =
|
||||||
var data = WalletSignal(e)
|
var data = WalletSignal(e)
|
||||||
case data.eventType:
|
case data.eventType:
|
||||||
@ -265,30 +288,37 @@ QtObject:
|
|||||||
|
|
||||||
self.currentActivityFilter.counterpartyAddresses = addresses
|
self.currentActivityFilter.counterpartyAddresses = addresses
|
||||||
|
|
||||||
proc setFilterAssets*(self: Controller, assetsArrayJsonString: string) {.slot.} =
|
# Depends on self.filterTokenCodes and self.chainIds, so should be called after updating them
|
||||||
|
proc updateAssetsIdentities(self: Controller) =
|
||||||
|
var assets = newSeq[backend_activity.Token]()
|
||||||
|
for tokenCode in self.filterTokenCodes:
|
||||||
|
for chainId in self.chainIds:
|
||||||
|
let token = self.tokenService.findTokenBySymbol(chainId, tokenCode)
|
||||||
|
if token != nil:
|
||||||
|
let tokenType = if token.symbol == "ETH": backend_activity.TokenType.Native else: backend_activity.TokenType.Erc20
|
||||||
|
assets.add(backend_activity.Token(
|
||||||
|
tokenType: tokenType,
|
||||||
|
chainId: backend_activity.ChainId(token.chainId),
|
||||||
|
address: some(token.address)
|
||||||
|
))
|
||||||
|
|
||||||
|
self.currentActivityFilter.assets = assets
|
||||||
|
|
||||||
|
proc setFilterAssets*(self: Controller, assetsArrayJsonString: string, excludeAssets: bool) {.slot.} =
|
||||||
|
self.filterTokenCodes.clear()
|
||||||
|
if excludeAssets:
|
||||||
|
return
|
||||||
|
|
||||||
let assetsJson = parseJson(assetsArrayJsonString)
|
let assetsJson = parseJson(assetsArrayJsonString)
|
||||||
if assetsJson.kind != JArray:
|
if assetsJson.kind != JArray:
|
||||||
error "invalid array of json strings"
|
error "invalid array of json strings"
|
||||||
return
|
return
|
||||||
|
|
||||||
var assets = newSeq[TokenCode](assetsJson.len)
|
|
||||||
for i in 0 ..< assetsJson.len:
|
for i in 0 ..< assetsJson.len:
|
||||||
assets[i] = TokenCode(assetsJson[i].getStr())
|
let tokenCode = assetsJson[i].getStr()
|
||||||
|
self.filterTokenCodes.incl(tokenCode)
|
||||||
|
|
||||||
self.currentActivityFilter.tokens.assets = option(assets)
|
self.updateAssetsIdentities()
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
proc setFilterAddresses*(self: Controller, addresses: seq[string]) =
|
proc setFilterAddresses*(self: Controller, addresses: seq[string]) =
|
||||||
self.addresses = addresses
|
self.addresses = addresses
|
||||||
@ -299,18 +329,7 @@ QtObject:
|
|||||||
proc setFilterChains*(self: Controller, chainIds: seq[int]) =
|
proc setFilterChains*(self: Controller, chainIds: seq[int]) =
|
||||||
self.chainIds = chainIds
|
self.chainIds = chainIds
|
||||||
|
|
||||||
# TODO: remove me and use ground truth
|
self.updateAssetsIdentities()
|
||||||
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
|
|
||||||
|
|
||||||
proc getLoadingData*(self: Controller): bool {.slot.} =
|
proc getLoadingData*(self: Controller): bool {.slot.} =
|
||||||
return self.loadingData
|
return self.loadingData
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import NimQml, tables, json, strformat, sequtils, strutils, logging, stint, strutils
|
import NimQml, json, strformat, sequtils, strutils, logging, stint, strutils
|
||||||
|
|
||||||
import ../transactions/view
|
import ../transactions/view
|
||||||
import ../transactions/item
|
import ../transactions/item
|
||||||
@ -7,7 +7,7 @@ import backend/activity as backend
|
|||||||
import ../../../shared_models/currency_amount
|
import ../../../shared_models/currency_amount
|
||||||
|
|
||||||
# Additional data needed to build an Entry, which is
|
# Additional data needed to build an Entry, which is
|
||||||
# not included in the metadata and needs to be
|
# not included in the metadata and needs to be
|
||||||
# fetched from a different source.
|
# fetched from a different source.
|
||||||
type
|
type
|
||||||
ExtraData* = object
|
ExtraData* = object
|
||||||
@ -269,7 +269,7 @@ QtObject:
|
|||||||
if self.transaction == nil:
|
if self.transaction == nil:
|
||||||
error "getSymbol: ActivityEntry is not an transaction.Item"
|
error "getSymbol: ActivityEntry is not an transaction.Item"
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
if self.activityType == backend.ActivityType.Receive:
|
if self.activityType == backend.ActivityType.Receive:
|
||||||
return self.getInSymbol()
|
return self.getInSymbol()
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ proc newModule*(
|
|||||||
result.overviewModule = overview_module.newModule(result, events, walletAccountService, currencyService)
|
result.overviewModule = overview_module.newModule(result, events, walletAccountService, currencyService)
|
||||||
result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService)
|
result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService)
|
||||||
result.networksService = networkService
|
result.networksService = networkService
|
||||||
result.activityController = activityc.newController(result.transactionsModule, events, currencyService)
|
result.activityController = activityc.newController(result.transactionsModule, currencyService, tokenService, events)
|
||||||
result.filter = initFilter(result.controller, result.activityController)
|
result.filter = initFilter(result.controller, result.activityController)
|
||||||
|
|
||||||
result.view = newView(result, result.activityController)
|
result.view = newView(result, result.activityController)
|
||||||
|
@ -4,7 +4,6 @@ import ./backend/transactions
|
|||||||
|
|
||||||
const MultiTransactionMissingID* = 0
|
const MultiTransactionMissingID* = 0
|
||||||
|
|
||||||
# TODO: make it a Qt object to be referenced in QML via ActivityView
|
|
||||||
type
|
type
|
||||||
MultiTransactionItem* = object
|
MultiTransactionItem* = object
|
||||||
id: int
|
id: int
|
||||||
|
@ -370,7 +370,7 @@ QtObject:
|
|||||||
|
|
||||||
proc getStatusToken*(self: Service): TokenDto =
|
proc getStatusToken*(self: Service): TokenDto =
|
||||||
let networkDto = self.networkService.getNetworkForEns()
|
let networkDto = self.networkService.getNetworkForEns()
|
||||||
return self.tokenService.findTokenBySymbol(networkDto, networkDto.sntSymbol())
|
return self.tokenService.findTokenBySymbol(networkDto.chainId, networkDto.sntSymbol())
|
||||||
|
|
||||||
proc registerEns*(
|
proc registerEns*(
|
||||||
self: Service,
|
self: Service,
|
||||||
|
@ -202,7 +202,7 @@ QtObject:
|
|||||||
proc getStatusToken*(self: Service): TokenDto =
|
proc getStatusToken*(self: Service): TokenDto =
|
||||||
let networkDto = self.networkService.getNetworkForStickers()
|
let networkDto = self.networkService.getNetworkForStickers()
|
||||||
|
|
||||||
return self.tokenService.findTokenBySymbol(networkDto, networkDto.sntSymbol())
|
return self.tokenService.findTokenBySymbol(networkDto.chainId, networkDto.sntSymbol())
|
||||||
|
|
||||||
proc buyPack*(self: Service, packId: string, address, gas, gasPrice: string, eip1559Enabled: bool, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, success: var bool): tuple[txHash: string, error: string] =
|
proc buyPack*(self: Service, packId: string, address, gas, gasPrice: string, eip1559Enabled: bool, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, success: var bool): tuple[txHash: string, error: string] =
|
||||||
let
|
let
|
||||||
|
@ -153,10 +153,10 @@ QtObject:
|
|||||||
if self.hasContractAddressesForToken(symbol):
|
if self.hasContractAddressesForToken(symbol):
|
||||||
return self.tokensToAddressesMap[symbol].addresses
|
return self.tokensToAddressesMap[symbol].addresses
|
||||||
|
|
||||||
proc findTokenBySymbol*(self: Service, network: NetworkDto, symbol: string): TokenDto =
|
proc findTokenBySymbol*(self: Service, chainId: int, symbol: string): TokenDto =
|
||||||
if not self.tokens.hasKey(network.chainId):
|
if not self.tokens.hasKey(chainId):
|
||||||
return
|
return
|
||||||
for token in self.tokens[network.chainId]:
|
for token in self.tokens[chainId]:
|
||||||
if token.symbol == symbol:
|
if token.symbol == symbol:
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
@ -439,7 +439,7 @@ QtObject:
|
|||||||
|
|
||||||
let network = self.networkService.getNetwork(chainID)
|
let network = self.networkService.getNetwork(chainID)
|
||||||
|
|
||||||
let token = self.tokenService.findTokenBySymbol(network, tokenSymbol)
|
let token = self.tokenService.findTokenBySymbol(network.chainId, tokenSymbol)
|
||||||
let amountToSend = conversion.eth2Wei(parseFloat(value), token.decimals)
|
let amountToSend = conversion.eth2Wei(parseFloat(value), token.decimals)
|
||||||
let toAddress = token.address
|
let toAddress = token.address
|
||||||
let transfer = Transfer(
|
let transfer = Transfer(
|
||||||
|
@ -2,9 +2,12 @@ import times, strformat, options
|
|||||||
import json, json_serialization
|
import json, json_serialization
|
||||||
import core, response_type
|
import core, response_type
|
||||||
import stint
|
import stint
|
||||||
|
|
||||||
|
import web3/ethtypes as eth
|
||||||
|
import web3/conversions
|
||||||
|
|
||||||
from gen import rpc
|
from gen import rpc
|
||||||
import backend
|
import backend
|
||||||
import transactions
|
|
||||||
|
|
||||||
export response_type
|
export response_type
|
||||||
|
|
||||||
@ -14,11 +17,9 @@ const noLimitTimestampForPeriod = 0
|
|||||||
# Declared in services/wallet/activity/service.go
|
# Declared in services/wallet/activity/service.go
|
||||||
const eventActivityFilteringDone*: string = "wallet-activity-filtering-done"
|
const eventActivityFilteringDone*: string = "wallet-activity-filtering-done"
|
||||||
|
|
||||||
# TODO: consider using common status-go types via protobuf
|
|
||||||
# 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
|
||||||
@ -31,19 +32,19 @@ type
|
|||||||
|
|
||||||
# see status-go/services/wallet/activity/filter.go TokenType
|
# see status-go/services/wallet/activity/filter.go TokenType
|
||||||
TokenType* {.pure.} = enum
|
TokenType* {.pure.} = enum
|
||||||
Asset, Collectibles
|
Native, Erc20, Erc721, Erc1155
|
||||||
|
|
||||||
# see status-go/services/wallet/activity/filter.go TokenCode, TokenAddress
|
# see status-go/services/wallet/activity/filter.go TokenID
|
||||||
TokenCode* = distinct string
|
TokenId* = 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
|
ChainId* = distinct int
|
||||||
# All empty sequences or none Options mean include all
|
|
||||||
Tokens* = object
|
# see status-go/services/wallet/activity/filter.go Token
|
||||||
assets*: Option[seq[TokenCode]]
|
Token* = object
|
||||||
collectibles*: Option[seq[TokenAddress]]
|
tokenType*: TokenType
|
||||||
enabledTypes*: seq[TokenType]
|
chainId*: ChainId
|
||||||
|
address*: Option[eth.Address]
|
||||||
|
tokenId*: Option[TokenId]
|
||||||
|
|
||||||
# see status-go/services/wallet/activity/filter.go Filter
|
# see status-go/services/wallet/activity/filter.go Filter
|
||||||
# All empty sequences mean include all
|
# All empty sequences mean include all
|
||||||
@ -51,9 +52,14 @@ type
|
|||||||
period*: Period
|
period*: Period
|
||||||
types*: seq[ActivityType]
|
types*: seq[ActivityType]
|
||||||
statuses*: seq[ActivityStatus]
|
statuses*: seq[ActivityStatus]
|
||||||
tokens*: Tokens
|
|
||||||
counterpartyAddresses*: seq[string]
|
counterpartyAddresses*: seq[string]
|
||||||
|
|
||||||
|
# Tokens
|
||||||
|
assets*: seq[Token]
|
||||||
|
collectibles*: seq[Token]
|
||||||
|
filterOutAssets*: bool
|
||||||
|
filterOutCollectibles*: bool
|
||||||
|
|
||||||
proc toJson[T](obj: Option[T]): JsonNode =
|
proc toJson[T](obj: Option[T]): JsonNode =
|
||||||
if obj.isSome:
|
if obj.isSome:
|
||||||
toJson(obj.get())
|
toJson(obj.get())
|
||||||
@ -61,7 +67,7 @@ proc toJson[T](obj: Option[T]): JsonNode =
|
|||||||
newJNull()
|
newJNull()
|
||||||
|
|
||||||
proc fromJson[T](jsonObj: JsonNode, TT: typedesc[Option[T]]): Option[T] =
|
proc fromJson[T](jsonObj: JsonNode, TT: typedesc[Option[T]]): Option[T] =
|
||||||
if jsonObj.kind != JNull:
|
if jsonObj != nil and jsonObj.kind != JNull:
|
||||||
return some(to(jsonObj, T))
|
return some(to(jsonObj, T))
|
||||||
else:
|
else:
|
||||||
return none(T)
|
return none(T)
|
||||||
@ -69,30 +75,84 @@ proc fromJson[T](jsonObj: JsonNode, TT: typedesc[Option[T]]): Option[T] =
|
|||||||
proc `%`*(at: ActivityType): JsonNode {.inline.} =
|
proc `%`*(at: ActivityType): JsonNode {.inline.} =
|
||||||
return newJInt(ord(at))
|
return newJInt(ord(at))
|
||||||
|
|
||||||
|
proc fromJson*(jn: JsonNode, T: typedesc[ActivityType]): ActivityType {.inline.} =
|
||||||
|
return cast[ActivityType](jn.getInt())
|
||||||
|
|
||||||
proc `%`*(aSt: ActivityStatus): JsonNode {.inline.} =
|
proc `%`*(aSt: ActivityStatus): JsonNode {.inline.} =
|
||||||
return newJInt(ord(aSt))
|
return newJInt(ord(aSt))
|
||||||
|
|
||||||
proc fromJson*(x: JsonNode, T: typedesc[ActivityStatus]): ActivityStatus {.inline.} =
|
proc fromJson*(jn: JsonNode, T: typedesc[ActivityStatus]): ActivityStatus {.inline.} =
|
||||||
return cast[ActivityStatus](x.getInt())
|
return cast[ActivityStatus](jn.getInt())
|
||||||
|
|
||||||
proc `$`*(tc: TokenCode): string = $(string(tc))
|
proc `%`*(tt: TokenType): JsonNode {.inline.} =
|
||||||
proc `$`*(ta: TokenAddress): string = $(string(ta))
|
return newJInt(ord(tt))
|
||||||
|
|
||||||
proc `%`*(tc: TokenCode): JsonNode {.inline.} =
|
proc fromJson*(jn: JsonNode, T: typedesc[TokenType]): TokenType {.inline.} =
|
||||||
|
return cast[TokenType](jn.getInt())
|
||||||
|
|
||||||
|
proc `$`*(tc: TokenId): string = $(string(tc))
|
||||||
|
|
||||||
|
proc `%`*(tc: TokenId): JsonNode {.inline.} =
|
||||||
return %(string(tc))
|
return %(string(tc))
|
||||||
|
|
||||||
proc `%`*(ta: TokenAddress): JsonNode {.inline.} =
|
proc fromJson*(jn: JsonNode, T: typedesc[TokenId]): TokenId {.inline.} =
|
||||||
return %(string(ta))
|
return cast[TokenId](jn.getStr())
|
||||||
|
|
||||||
proc parseJson*(tc: var TokenCode, node: JsonNode) =
|
proc `%`*(cid: ChainId): JsonNode {.inline.} =
|
||||||
tc = TokenCode(node.getStr)
|
return %(int(cid))
|
||||||
|
|
||||||
proc parseJson*(ta: var TokenAddress, node: JsonNode) =
|
proc fromJson*(jn: JsonNode, T: typedesc[ChainId]): ChainId {.inline.} =
|
||||||
ta = TokenAddress(node.getStr)
|
return cast[ChainId](jn.getInt())
|
||||||
|
|
||||||
proc newAllTokens(): Tokens =
|
proc `$`*(cid: ChainId): string = $(int(cid))
|
||||||
result.assets = none(seq[TokenCode])
|
|
||||||
result.collectibles = none(seq[TokenAddress])
|
const addressField = "address"
|
||||||
|
const tokenIdField = "tokenId"
|
||||||
|
|
||||||
|
proc `%`*(t: Token): JsonNode {.inline.} =
|
||||||
|
result = newJObject()
|
||||||
|
result["tokenType"] = %(t.tokenType)
|
||||||
|
result["chainId"] = %(t.chainId)
|
||||||
|
|
||||||
|
if t.address.isSome:
|
||||||
|
result[addressField] = %(t.address.get)
|
||||||
|
|
||||||
|
if t.tokenId.isSome:
|
||||||
|
result[tokenIdField] = %(t.tokenId.get)
|
||||||
|
|
||||||
|
proc `%`*(t: ref Token): JsonNode {.inline.} =
|
||||||
|
return %(t[])
|
||||||
|
|
||||||
|
proc fromJson*(t: JsonNode, T: typedesc[Token]): Token {.inline.} =
|
||||||
|
result = Token()
|
||||||
|
result.tokenType = fromJson(t["tokenType"], TokenType)
|
||||||
|
result.chainId = fromJson(t["chainId"], ChainId)
|
||||||
|
|
||||||
|
if t.contains(addressField) and t[addressField].kind != JNull:
|
||||||
|
var address: eth.Address
|
||||||
|
fromJson(t[addressField], addressField, address)
|
||||||
|
result.address = some(address)
|
||||||
|
|
||||||
|
if t.contains(tokenIdField) and t[tokenIdField].kind != JNull:
|
||||||
|
result.tokenId = fromJson(t[tokenIdField], Option[TokenId])
|
||||||
|
|
||||||
|
proc fromJson*(t: JsonNode, T: typedesc[ref Token]): ref Token {.inline.} =
|
||||||
|
result = new(Token)
|
||||||
|
result[] = fromJson(t, Token)
|
||||||
|
|
||||||
|
proc `$`*(t: Token): string =
|
||||||
|
return fmt"""Token(
|
||||||
|
tokenType: {t.tokenType},
|
||||||
|
chainId*: {t.chainId},
|
||||||
|
address*: {t.address},
|
||||||
|
tokenId*: {t.tokenId}
|
||||||
|
)"""
|
||||||
|
|
||||||
|
proc `$`*(t: ref Token): string =
|
||||||
|
return $(t[])
|
||||||
|
|
||||||
|
proc newAllTokens(): seq[Token] =
|
||||||
|
return @[]
|
||||||
|
|
||||||
proc newPeriod*(startTime: Option[DateTime], endTime: Option[DateTime]): Period =
|
proc newPeriod*(startTime: Option[DateTime], endTime: Option[DateTime]): Period =
|
||||||
if startTime.isSome:
|
if startTime.isSome:
|
||||||
@ -109,17 +169,24 @@ proc newPeriod*(startTimestamp: int, endTimestamp: int): Period =
|
|||||||
result.endTimestamp = endTimestamp
|
result.endTimestamp = endTimestamp
|
||||||
|
|
||||||
proc getIncludeAllActivityFilter*(): ActivityFilter =
|
proc getIncludeAllActivityFilter*(): ActivityFilter =
|
||||||
result = ActivityFilter(period: newPeriod(none(DateTime), none(DateTime)), types: @[], statuses: @[],
|
result = ActivityFilter(period: newPeriod(none(DateTime), none(DateTime)),
|
||||||
tokens: newAllTokens(), counterpartyAddresses: @[])
|
types: @[], statuses: @[], counterpartyAddresses: @[],
|
||||||
|
assets: newAllTokens(), collectibles: newAllTokens(),
|
||||||
|
filterOutAssets: false, filterOutCollectibles: false)
|
||||||
|
|
||||||
# Empty sequence for paramters means include all
|
# Empty sequence for paramters means include all
|
||||||
proc newActivityFilter*(period: Period, activityType: seq[ActivityType], activityStatus: seq[ActivityStatus],
|
proc newActivityFilter*(period: Period, activityType: seq[ActivityType], activityStatus: seq[ActivityStatus],
|
||||||
tokens: Tokens, counterpartyAddress: seq[string]): ActivityFilter =
|
counterpartyAddress: seq[string],
|
||||||
|
assets: seq[Token], collectibles: seq[Token],
|
||||||
|
filterOutAssets: bool, filterOutCollectibles: bool): ActivityFilter =
|
||||||
result.period = period
|
result.period = period
|
||||||
result.types = activityType
|
result.types = activityType
|
||||||
result.statuses = activityStatus
|
result.statuses = activityStatus
|
||||||
result.tokens = tokens
|
|
||||||
result.counterpartyAddresses = counterpartyAddress
|
result.counterpartyAddresses = counterpartyAddress
|
||||||
|
result.assets = assets
|
||||||
|
result.collectibles = collectibles
|
||||||
|
result.filterOutAssets = filterOutAssets
|
||||||
|
result.filterOutCollectibles = filterOutCollectibles
|
||||||
|
|
||||||
# Mirrors status-go/services/wallet/activity/activity.go PayloadType
|
# Mirrors status-go/services/wallet/activity/activity.go PayloadType
|
||||||
type
|
type
|
||||||
@ -129,12 +196,12 @@ type
|
|||||||
PendingTransaction
|
PendingTransaction
|
||||||
|
|
||||||
# Define toJson proc for PayloadType
|
# Define toJson proc for PayloadType
|
||||||
proc `%`*(x: PayloadType): JsonNode {.inline.} =
|
proc `%`*(pt: PayloadType): JsonNode {.inline.} =
|
||||||
return newJInt(ord(x))
|
return newJInt(ord(pt))
|
||||||
|
|
||||||
# Define fromJson proc for PayloadType
|
# Define fromJson proc for PayloadType
|
||||||
proc fromJson*(x: JsonNode, T: typedesc[PayloadType]): PayloadType {.inline.} =
|
proc fromJson*(jn: JsonNode, T: typedesc[PayloadType]): PayloadType {.inline.} =
|
||||||
return cast[PayloadType](x.getInt())
|
return cast[PayloadType](jn.getInt())
|
||||||
|
|
||||||
# TODO: hide internals behind safe interface
|
# TODO: hide internals behind safe interface
|
||||||
type
|
type
|
||||||
@ -145,13 +212,16 @@ type
|
|||||||
id*: int
|
id*: int
|
||||||
|
|
||||||
timestamp*: int
|
timestamp*: int
|
||||||
# TODO: change it into ActivityType
|
|
||||||
activityType*: MultiTransactionType
|
activityType*: ActivityType
|
||||||
activityStatus*: ActivityStatus
|
activityStatus*: ActivityStatus
|
||||||
tokenType*: TokenType
|
|
||||||
amountOut*: UInt256
|
amountOut*: UInt256
|
||||||
amountIn*: UInt256
|
amountIn*: UInt256
|
||||||
|
|
||||||
|
tokenOut*: Option[Token]
|
||||||
|
tokenIn*: Option[Token]
|
||||||
|
|
||||||
# Mirrors services/wallet/activity/service.go ErrorCode
|
# Mirrors services/wallet/activity/service.go ErrorCode
|
||||||
ErrorCode* = enum
|
ErrorCode* = enum
|
||||||
ErrorCodeSuccess = 1,
|
ErrorCodeSuccess = 1,
|
||||||
@ -171,15 +241,30 @@ 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.} =
|
||||||
|
const tokenOutField = "tokenOut"
|
||||||
|
const tokenInField = "tokenIn"
|
||||||
result = T(
|
result = T(
|
||||||
payloadType: fromJson(e["payloadType"], PayloadType),
|
payloadType: fromJson(e["payloadType"], PayloadType),
|
||||||
transaction: if e.hasKey("transaction"): fromJson(e["transaction"], Option[TransactionIdentity])
|
transaction: if e.hasKey("transaction"):
|
||||||
else: none(TransactionIdentity),
|
fromJson(e["transaction"], Option[TransactionIdentity])
|
||||||
|
else:
|
||||||
|
none(TransactionIdentity),
|
||||||
id: e["id"].getInt(),
|
id: e["id"].getInt(),
|
||||||
|
activityType: fromJson(e["activityType"], ActivityType),
|
||||||
activityStatus: fromJson(e["activityStatus"], ActivityStatus),
|
activityStatus: fromJson(e["activityStatus"], ActivityStatus),
|
||||||
timestamp: e["timestamp"].getInt(),
|
timestamp: e["timestamp"].getInt(),
|
||||||
|
|
||||||
amountOut: stint.fromHex(UInt256, e["amountOut"].getStr()),
|
amountOut: stint.fromHex(UInt256, e["amountOut"].getStr()),
|
||||||
amountIn: stint.fromHex(UInt256, e["amountIn"].getStr())
|
amountIn: stint.fromHex(UInt256, e["amountIn"].getStr()),
|
||||||
|
|
||||||
|
tokenOut: if e.contains(tokenOutField):
|
||||||
|
some(fromJson(e[tokenOutField], Token))
|
||||||
|
else:
|
||||||
|
none(Token),
|
||||||
|
tokenIn: if e.contains(tokenInField):
|
||||||
|
some(fromJson(e[tokenInField], Token))
|
||||||
|
else:
|
||||||
|
none(Token)
|
||||||
)
|
)
|
||||||
|
|
||||||
proc `$`*(self: ActivityEntry): string =
|
proc `$`*(self: ActivityEntry): string =
|
||||||
@ -192,9 +277,10 @@ proc `$`*(self: ActivityEntry): string =
|
|||||||
timestamp:{self.timestamp},
|
timestamp:{self.timestamp},
|
||||||
activityType* {$self.activityType},
|
activityType* {$self.activityType},
|
||||||
activityStatus* {$self.activityStatus},
|
activityStatus* {$self.activityStatus},
|
||||||
tokenType* {$self.tokenType},
|
|
||||||
amountOut* {$self.amountOut},
|
amountOut* {$self.amountOut},
|
||||||
amountIn* {$self.amountIn},
|
amountIn* {$self.amountIn},
|
||||||
|
tokenOut* {$self.tokenOut},
|
||||||
|
tokenIn* {$self.tokenIn}
|
||||||
)"""
|
)"""
|
||||||
|
|
||||||
proc fromJson*(e: JsonNode, T: typedesc[FilterResponse]): FilterResponse {.inline.} =
|
proc fromJson*(e: JsonNode, T: typedesc[FilterResponse]): FilterResponse {.inline.} =
|
||||||
@ -215,7 +301,7 @@ proc fromJson*(e: JsonNode, T: typedesc[FilterResponse]): FilterResponse {.inlin
|
|||||||
|
|
||||||
rpc(filterActivityAsync, "wallet"):
|
rpc(filterActivityAsync, "wallet"):
|
||||||
addresses: seq[string]
|
addresses: seq[string]
|
||||||
chainIds: seq[int]
|
chainIds: seq[ChainId]
|
||||||
filter: ActivityFilter
|
filter: ActivityFilter
|
||||||
offset: int
|
offset: int
|
||||||
limit: int
|
limit: int
|
||||||
|
@ -100,7 +100,7 @@ QtObject {
|
|||||||
// update filters
|
// update filters
|
||||||
tokensFilter = toggleFilterState(tokensFilter, symbol, tokensList.count)
|
tokensFilter = toggleFilterState(tokensFilter, symbol, tokensList.count)
|
||||||
// Set backend values
|
// Set backend values
|
||||||
activityController.setFilterAssets(JSON.stringify(tokensFilter))
|
activityController.setFilterAssets(JSON.stringify(tokensFilter), false)
|
||||||
activityController.updateFilter()
|
activityController.updateFilter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,14 +88,6 @@ Item {
|
|||||||
width: implicitWidth
|
width: implicitWidth
|
||||||
text: qsTr("Activity")
|
text: qsTr("Activity")
|
||||||
}
|
}
|
||||||
// TODO - DEV: remove me
|
|
||||||
// Enable for debugging activity filter
|
|
||||||
// currentIndex: 3
|
|
||||||
// StatusTabButton {
|
|
||||||
// rightPadding: 0
|
|
||||||
// width: implicitWidth
|
|
||||||
// text: qsTr("DEV activity")
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
StackLayout {
|
StackLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -127,17 +119,6 @@ Item {
|
|||||||
stack.currentIndex = 3
|
stack.currentIndex = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: replace with the real activity view
|
|
||||||
// Enable for debugging activity filter
|
|
||||||
// ActivityView {
|
|
||||||
// Layout.fillWidth: true
|
|
||||||
// Layout.fillHeight: true
|
|
||||||
|
|
||||||
// controller: RootStore.activityController
|
|
||||||
// networksModel: RootStore.allNetworks
|
|
||||||
// assetsModel: RootStore.assets
|
|
||||||
// assetsLoading: RootStore.assetsLoading
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CollectibleDetailView {
|
CollectibleDetailView {
|
||||||
|
@ -1,440 +0,0 @@
|
|||||||
import QtQuick 2.15
|
|
||||||
import QtQml 2.15
|
|
||||||
import QtQuick.Controls 2.15
|
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
|
|
||||||
import StatusQ.Core 0.1
|
|
||||||
import StatusQ.Components 0.1
|
|
||||||
import StatusQ.Controls 0.1
|
|
||||||
import StatusQ.Core.Theme 0.1
|
|
||||||
|
|
||||||
import AppLayouts.stores 1.0
|
|
||||||
|
|
||||||
import SortFilterProxyModel 0.2
|
|
||||||
|
|
||||||
import utils 1.0
|
|
||||||
|
|
||||||
import "../panels"
|
|
||||||
import "../popups"
|
|
||||||
import "../stores"
|
|
||||||
import "../controls"
|
|
||||||
|
|
||||||
// Temporary developer view to test the filter APIs
|
|
||||||
Control {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property var controller: null
|
|
||||||
property var networksModel: null
|
|
||||||
property var assetsModel: null
|
|
||||||
property bool assetsLoading: true
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: controller.updateRecipientsModel()
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: filterLayout
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: timeFilterLayout
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Label { text: qsTr("Past Days Span: 100") }
|
|
||||||
Slider {
|
|
||||||
id: fromSlider
|
|
||||||
|
|
||||||
Layout.preferredWidth: 200
|
|
||||||
Layout.preferredHeight: 50
|
|
||||||
|
|
||||||
from: 100
|
|
||||||
to: 0
|
|
||||||
|
|
||||||
stepSize: 1
|
|
||||||
value: 0
|
|
||||||
}
|
|
||||||
Label { text: `${fromSlider.value}d - ${toSlider.value}d` }
|
|
||||||
Slider {
|
|
||||||
id: toSlider
|
|
||||||
|
|
||||||
Layout.preferredWidth: 200
|
|
||||||
Layout.preferredHeight: 50
|
|
||||||
|
|
||||||
enabled: fromSlider.value > 1
|
|
||||||
|
|
||||||
from: fromSlider.value - 1
|
|
||||||
to: 0
|
|
||||||
|
|
||||||
stepSize: 1
|
|
||||||
value: 0
|
|
||||||
}
|
|
||||||
Label { text: qsTr("0") }
|
|
||||||
}
|
|
||||||
Label { text: `Interval: ${d.start > 0 ? root.epochToDateStr(d.start) : qsTr("all time")} - ${d.end > 0 ? root.epochToDateStr(d.end) : qsTr("now")}` }
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
Label { text: qsTr("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: qsTr("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 {}
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
id: toAddressesComboBox
|
|
||||||
model: controller.recipientsModel
|
|
||||||
|
|
||||||
displayText: qsTr("Select TO") + (controller.recipientsModel.hasMore ? qsTr(" ...") : "")
|
|
||||||
|
|
||||||
currentIndex: -1
|
|
||||||
|
|
||||||
delegate: ItemOnOffDelegate {
|
|
||||||
textRole: "address"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: qsTr("Update")
|
|
||||||
onClicked: d.updateFilter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
|
|
||||||
Label { text: qsTr("Addresses") }
|
|
||||||
TextField {
|
|
||||||
id: addressesInput
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
placeholderText: qsTr("0x1234, 0x5678, ...")
|
|
||||||
}
|
|
||||||
|
|
||||||
Label { text: qsTr("Chains") }
|
|
||||||
ComboBox {
|
|
||||||
displayText: qsTr("Select chains")
|
|
||||||
|
|
||||||
Layout.preferredWidth: 300
|
|
||||||
|
|
||||||
model: clonedNetworksModel
|
|
||||||
currentIndex: -1
|
|
||||||
|
|
||||||
delegate: ItemOnOffDelegate {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label { text: qsTr("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 {
|
|
||||||
property string textRole: "text"
|
|
||||||
|
|
||||||
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[textRole] }
|
|
||||||
RowLayout {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
id: listView
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
if(controller.model.hasMore) {
|
|
||||||
controller.loadMoreItems();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
model: controller.model
|
|
||||||
|
|
||||||
delegate: Item {
|
|
||||||
width: parent ? parent.width : 0
|
|
||||||
height: itemLayout.implicitHeight
|
|
||||||
|
|
||||||
readonly property var entry: model.activityEntry
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: itemLayout
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: 5
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Label { text: qsTr("in"); Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
|
||||||
Label { text: entry.inAmount }
|
|
||||||
Label { text: qsTr("out"); Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
|
||||||
Label { text: entry.outAmount }
|
|
||||||
Label { text: qsTr("from"); Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
|
||||||
Label { text: entry.sender; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
|
||||||
Label { text: qsTr("to"); Layout.leftMargin: 5; Layout.rightMargin: 5 }
|
|
||||||
Label { text: entry.recipient; Layout.maximumWidth: 200; elide: Text.ElideMiddle }
|
|
||||||
RowLayout {} // Spacer
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
Label { text: entry.isMultiTransaction ? qsTr("MT") : entry.isPendingTransaction ? qsTr("PT") : qsTr(" T") }
|
|
||||||
Label { text: `[${root.epochToDateStr(entry.timestamp)}] ` }
|
|
||||||
Label {
|
|
||||||
text: `{${
|
|
||||||
function() {
|
|
||||||
switch (entry.status) {
|
|
||||||
case Constants.TransactionStatus.Failed: return qsTr("Failed");
|
|
||||||
case Constants.TransactionStatus.Pending: return qsTr("Pending");
|
|
||||||
case Constants.TransactionStatus.Complete: return qsTr("Complete");
|
|
||||||
case Constants.TransactionStatus.Finalized: return qsTr("Finalized");
|
|
||||||
}
|
|
||||||
return qsTr("-")
|
|
||||||
}()}}`
|
|
||||||
Layout.leftMargin: 5;
|
|
||||||
}
|
|
||||||
RowLayout {} // Spacer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onContentYChanged: checkIfFooterVisible()
|
|
||||||
onHeightChanged: checkIfFooterVisible()
|
|
||||||
onContentHeightChanged: checkIfFooterVisible()
|
|
||||||
Connections {
|
|
||||||
target: listView.footerItem
|
|
||||||
function onHeightChanged() {
|
|
||||||
listView.checkIfFooterVisible()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkIfFooterVisible() {
|
|
||||||
if((contentY + height) > (contentHeight - footerItem.height) && controller.model.hasMore && !controller.loadingData) {
|
|
||||||
controller.loadMoreItems();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
footer: Column {
|
|
||||||
id: loadingItems
|
|
||||||
|
|
||||||
width: listView.width
|
|
||||||
visible: controller.model.hasMore
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: controller.model.hasMore ? 10 : 0
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: loadingItems.loadingPattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property string loadingPattern: ""
|
|
||||||
property int glanceOffset: 0
|
|
||||||
Timer {
|
|
||||||
interval: 25; repeat: true; running: true
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
let offset = loadingItems.glanceOffset
|
|
||||||
let length = 100
|
|
||||||
let slashCount = 3;
|
|
||||||
|
|
||||||
let pattern = new Array(length).fill(' ');
|
|
||||||
|
|
||||||
for (let i = 0; i < slashCount; i++) {
|
|
||||||
let position = (offset + i) % length;
|
|
||||||
pattern[position] = '/';
|
|
||||||
}
|
|
||||||
pattern = '[' + pattern.join('') + ']';
|
|
||||||
|
|
||||||
loadingItems.loadingPattern = pattern;
|
|
||||||
loadingItems.glanceOffset = (offset + 1) % length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function epochToDateStr(epochTimestamp) {
|
|
||||||
var date = new Date(epochTimestamp * 1000);
|
|
||||||
return date.toLocaleString(Qt.locale(), "dd-MM-yyyy hh:mm");
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,4 +13,3 @@ TokenListView 1.0 TokenListView.qml
|
|||||||
TransactionPreview 1.0 TransactionPreview.qml
|
TransactionPreview 1.0 TransactionPreview.qml
|
||||||
TransactionSigner 1.0 TransactionSigner.qml
|
TransactionSigner 1.0 TransactionSigner.qml
|
||||||
TransactionStackView 1.0 TransactionStackView.qml
|
TransactionStackView 1.0 TransactionStackView.qml
|
||||||
ActivityView 1.0 ActivityView.qml
|
|
||||||
|
2
vendor/status-go
vendored
2
vendor/status-go
vendored
@ -1 +1 @@
|
|||||||
Subproject commit bf64f97d5a2bcb1b5fb6b134dda69994e0ddd2bb
|
Subproject commit 8e63f447352fef5fb4eb1dbd0a87a296b2b96d78
|
Loading…
x
Reference in New Issue
Block a user