diff --git a/src/app/modules/main/wallet_section/activity/controller.nim b/src/app/modules/main/wallet_section/activity/controller.nim index 07678adb15..bc696c8037 100644 --- a/src/app/modules/main/wallet_section/activity/controller.nim +++ b/src/app/modules/main/wallet_section/activity/controller.nim @@ -1,10 +1,12 @@ -import NimQml, logging, std/json, sequtils, sugar, options -import tables, stint +import NimQml, logging, std/json, sequtils, sugar, options, strutils +import tables, stint, sets import model import entry import recipients_model +import web3/conversions + import ../transactions/item 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/transaction/service as transaction_service +import app_service/service/token/service as token_service + proc toRef*[T](obj: T): ref T = new(result) @@ -25,6 +29,7 @@ proc toRef*[T](obj: T): ref T = const FETCH_BATCH_COUNT_DEFAULT = 10 const FETCH_RECIPIENTS_BATCH_COUNT_DEFAULT = 2000 +# TODO: implement passing of collectibles QtObject: type Controller* = ref object of QObject @@ -33,14 +38,18 @@ QtObject: transactionsModule: transactions_module.AccessInterface currentActivityFilter: backend_activity.ActivityFilter currencyService: currency_service.Service + tokenService: token_service.Service events: EventEmitter loadingData: bool errorCode: backend_activity.ErrorCode - # TODO remove chains and addresses after using ground truth + # call updateAssetsIdentities after updating filterTokenCodes + filterTokenCodes: HashSet[string] + addresses: seq[string] + # call updateAssetsIdentities after updating chainIds chainIds: seq[int] proc setup(self: Controller) = @@ -185,20 +194,21 @@ QtObject: proc updateFilter*(self: Controller) {.slot.} = 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: error "error fetching activity entries: ", response.error self.setLoadingData(false) return 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: error "error fetching activity entries: ", response.error return - self.setLoadingData(true) - proc setFilterTime*(self: Controller, startTimestamp: int, endTimestamp: int) {.slot.} = self.currentActivityFilter.period = backend_activity.newPeriod(startTimestamp, endTimestamp) @@ -214,18 +224,31 @@ QtObject: 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) result.model = newModel() result.recipientsModel = newRecipientsModel() result.transactionsModule = transactionsModule + result.tokenService = tokenService result.currentActivityFilter = backend_activity.getIncludeAllActivityFilter() result.events = events result.currencyService = currencyService + + result.loadingData = false + result.errorCode = backend_activity.ErrorCode.ErrorCodeSuccess + + result.filterTokenCodes = initHashSet[string]() + + result.addresses = @[] + result.chainIds = @[] + result.setup() + # Register and process events let controller = result - proc handleEvent(e: Args) = var data = WalletSignal(e) case data.eventType: @@ -265,30 +288,37 @@ QtObject: 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) 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()) + let tokenCode = assetsJson[i].getStr() + self.filterTokenCodes.incl(tokenCode) - 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 + self.updateAssetsIdentities() proc setFilterAddresses*(self: Controller, addresses: seq[string]) = self.addresses = addresses @@ -299,18 +329,7 @@ QtObject: proc setFilterChains*(self: Controller, chainIds: seq[int]) = self.chainIds = chainIds - # 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 + self.updateAssetsIdentities() proc getLoadingData*(self: Controller): bool {.slot.} = return self.loadingData diff --git a/src/app/modules/main/wallet_section/activity/entry.nim b/src/app/modules/main/wallet_section/activity/entry.nim index 06662dd10d..3220a0ca84 100644 --- a/src/app/modules/main/wallet_section/activity/entry.nim +++ b/src/app/modules/main/wallet_section/activity/entry.nim @@ -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/item @@ -7,7 +7,7 @@ import backend/activity as backend import ../../../shared_models/currency_amount # 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. type ExtraData* = object @@ -269,7 +269,7 @@ QtObject: if self.transaction == nil: error "getSymbol: ActivityEntry is not an transaction.Item" return "" - + if self.activityType == backend.ActivityType.Receive: return self.getInSymbol() diff --git a/src/app/modules/main/wallet_section/module.nim b/src/app/modules/main/wallet_section/module.nim index b8c484a202..d612779550 100644 --- a/src/app/modules/main/wallet_section/module.nim +++ b/src/app/modules/main/wallet_section/module.nim @@ -101,7 +101,7 @@ proc newModule*( result.overviewModule = overview_module.newModule(result, events, walletAccountService, currencyService) result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService) 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.view = newView(result, result.activityController) diff --git a/src/app/modules/main/wallet_section/transactions/multi_transaction_item.nim b/src/app/modules/main/wallet_section/transactions/multi_transaction_item.nim index 7baeee3cbe..95175577d1 100644 --- a/src/app/modules/main/wallet_section/transactions/multi_transaction_item.nim +++ b/src/app/modules/main/wallet_section/transactions/multi_transaction_item.nim @@ -4,7 +4,6 @@ import ./backend/transactions const MultiTransactionMissingID* = 0 -# TODO: make it a Qt object to be referenced in QML via ActivityView type MultiTransactionItem* = object id: int diff --git a/src/app_service/service/ens/service.nim b/src/app_service/service/ens/service.nim index 906b1b721a..18780a07b5 100644 --- a/src/app_service/service/ens/service.nim +++ b/src/app_service/service/ens/service.nim @@ -370,7 +370,7 @@ QtObject: proc getStatusToken*(self: Service): TokenDto = let networkDto = self.networkService.getNetworkForEns() - return self.tokenService.findTokenBySymbol(networkDto, networkDto.sntSymbol()) + return self.tokenService.findTokenBySymbol(networkDto.chainId, networkDto.sntSymbol()) proc registerEns*( self: Service, diff --git a/src/app_service/service/stickers/service.nim b/src/app_service/service/stickers/service.nim index 68dcd66632..7066915bb4 100644 --- a/src/app_service/service/stickers/service.nim +++ b/src/app_service/service/stickers/service.nim @@ -202,7 +202,7 @@ QtObject: proc getStatusToken*(self: Service): TokenDto = 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] = let diff --git a/src/app_service/service/token/service.nim b/src/app_service/service/token/service.nim index 9a6b286cac..6ab0a9bfe8 100644 --- a/src/app_service/service/token/service.nim +++ b/src/app_service/service/token/service.nim @@ -153,10 +153,10 @@ QtObject: if self.hasContractAddressesForToken(symbol): return self.tokensToAddressesMap[symbol].addresses - proc findTokenBySymbol*(self: Service, network: NetworkDto, symbol: string): TokenDto = - if not self.tokens.hasKey(network.chainId): + proc findTokenBySymbol*(self: Service, chainId: int, symbol: string): TokenDto = + if not self.tokens.hasKey(chainId): return - for token in self.tokens[network.chainId]: + for token in self.tokens[chainId]: if token.symbol == symbol: return token diff --git a/src/app_service/service/transaction/service.nim b/src/app_service/service/transaction/service.nim index 6be98c273b..b26d83885c 100644 --- a/src/app_service/service/transaction/service.nim +++ b/src/app_service/service/transaction/service.nim @@ -439,7 +439,7 @@ QtObject: 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 toAddress = token.address let transfer = Transfer( diff --git a/src/backend/activity.nim b/src/backend/activity.nim index 5d3f6c0e5e..a985767aa4 100644 --- a/src/backend/activity.nim +++ b/src/backend/activity.nim @@ -2,9 +2,12 @@ import times, strformat, options import json, json_serialization import core, response_type import stint + +import web3/ethtypes as eth +import web3/conversions + from gen import rpc import backend -import transactions export response_type @@ -14,11 +17,9 @@ const noLimitTimestampForPeriod = 0 # Declared in services/wallet/activity/service.go 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 Period* = object - startTimestamp* : int + startTimestamp*: int endTimestamp*: int # see status-go/services/wallet/activity/filter.go Type @@ -31,19 +32,19 @@ type # see status-go/services/wallet/activity/filter.go TokenType TokenType* {.pure.} = enum - Asset, Collectibles + Native, Erc20, Erc721, Erc1155 - # 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 TokenID + TokenId* = 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] + ChainId* = distinct int + + # see status-go/services/wallet/activity/filter.go Token + Token* = object + tokenType*: TokenType + chainId*: ChainId + address*: Option[eth.Address] + tokenId*: Option[TokenId] # see status-go/services/wallet/activity/filter.go Filter # All empty sequences mean include all @@ -51,9 +52,14 @@ type period*: Period types*: seq[ActivityType] statuses*: seq[ActivityStatus] - tokens*: Tokens counterpartyAddresses*: seq[string] + # Tokens + assets*: seq[Token] + collectibles*: seq[Token] + filterOutAssets*: bool + filterOutCollectibles*: bool + proc toJson[T](obj: Option[T]): JsonNode = if obj.isSome: toJson(obj.get()) @@ -61,7 +67,7 @@ proc toJson[T](obj: Option[T]): JsonNode = newJNull() 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)) else: return none(T) @@ -69,30 +75,84 @@ proc fromJson[T](jsonObj: JsonNode, TT: typedesc[Option[T]]): Option[T] = proc `%`*(at: ActivityType): JsonNode {.inline.} = return newJInt(ord(at)) +proc fromJson*(jn: JsonNode, T: typedesc[ActivityType]): ActivityType {.inline.} = + return cast[ActivityType](jn.getInt()) + proc `%`*(aSt: ActivityStatus): JsonNode {.inline.} = return newJInt(ord(aSt)) -proc fromJson*(x: JsonNode, T: typedesc[ActivityStatus]): ActivityStatus {.inline.} = - return cast[ActivityStatus](x.getInt()) +proc fromJson*(jn: JsonNode, T: typedesc[ActivityStatus]): ActivityStatus {.inline.} = + return cast[ActivityStatus](jn.getInt()) -proc `$`*(tc: TokenCode): string = $(string(tc)) -proc `$`*(ta: TokenAddress): string = $(string(ta)) +proc `%`*(tt: TokenType): JsonNode {.inline.} = + 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)) -proc `%`*(ta: TokenAddress): JsonNode {.inline.} = - return %(string(ta)) +proc fromJson*(jn: JsonNode, T: typedesc[TokenId]): TokenId {.inline.} = + return cast[TokenId](jn.getStr()) -proc parseJson*(tc: var TokenCode, node: JsonNode) = - tc = TokenCode(node.getStr) +proc `%`*(cid: ChainId): JsonNode {.inline.} = + return %(int(cid)) -proc parseJson*(ta: var TokenAddress, node: JsonNode) = - ta = TokenAddress(node.getStr) +proc fromJson*(jn: JsonNode, T: typedesc[ChainId]): ChainId {.inline.} = + return cast[ChainId](jn.getInt()) -proc newAllTokens(): Tokens = - result.assets = none(seq[TokenCode]) - result.collectibles = none(seq[TokenAddress]) +proc `$`*(cid: ChainId): string = $(int(cid)) + +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 = if startTime.isSome: @@ -109,17 +169,24 @@ proc newPeriod*(startTimestamp: int, endTimestamp: int): Period = result.endTimestamp = endTimestamp proc getIncludeAllActivityFilter*(): ActivityFilter = - result = ActivityFilter(period: newPeriod(none(DateTime), none(DateTime)), types: @[], statuses: @[], - tokens: newAllTokens(), counterpartyAddresses: @[]) + result = ActivityFilter(period: newPeriod(none(DateTime), none(DateTime)), + types: @[], statuses: @[], counterpartyAddresses: @[], + assets: newAllTokens(), collectibles: newAllTokens(), + filterOutAssets: false, filterOutCollectibles: false) # Empty sequence for paramters means include all 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.types = activityType result.statuses = activityStatus - result.tokens = tokens result.counterpartyAddresses = counterpartyAddress + result.assets = assets + result.collectibles = collectibles + result.filterOutAssets = filterOutAssets + result.filterOutCollectibles = filterOutCollectibles # Mirrors status-go/services/wallet/activity/activity.go PayloadType type @@ -129,12 +196,12 @@ type PendingTransaction # Define toJson proc for PayloadType -proc `%`*(x: PayloadType): JsonNode {.inline.} = - return newJInt(ord(x)) +proc `%`*(pt: PayloadType): JsonNode {.inline.} = + return newJInt(ord(pt)) # Define fromJson proc for PayloadType -proc fromJson*(x: JsonNode, T: typedesc[PayloadType]): PayloadType {.inline.} = - return cast[PayloadType](x.getInt()) +proc fromJson*(jn: JsonNode, T: typedesc[PayloadType]): PayloadType {.inline.} = + return cast[PayloadType](jn.getInt()) # TODO: hide internals behind safe interface type @@ -145,13 +212,16 @@ type id*: int timestamp*: int - # TODO: change it into ActivityType - activityType*: MultiTransactionType + + activityType*: ActivityType activityStatus*: ActivityStatus - tokenType*: TokenType + amountOut*: UInt256 amountIn*: UInt256 + tokenOut*: Option[Token] + tokenIn*: Option[Token] + # Mirrors services/wallet/activity/service.go ErrorCode ErrorCode* = enum ErrorCodeSuccess = 1, @@ -171,15 +241,30 @@ proc toJson*(ae: ActivityEntry): JsonNode {.inline.} = # Define fromJson proc for PayloadType proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.} = + const tokenOutField = "tokenOut" + const tokenInField = "tokenIn" result = T( 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(), + activityType: fromJson(e["activityType"], ActivityType), activityStatus: fromJson(e["activityStatus"], ActivityStatus), timestamp: e["timestamp"].getInt(), + amountOut: stint.fromHex(UInt256, e["amountOut"].getStr()), - amountIn: stint.fromHex(UInt256, e["amountIn"].getStr()) + 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 = @@ -192,9 +277,10 @@ proc `$`*(self: ActivityEntry): string = timestamp:{self.timestamp}, activityType* {$self.activityType}, activityStatus* {$self.activityStatus}, - tokenType* {$self.tokenType}, amountOut* {$self.amountOut}, amountIn* {$self.amountIn}, + tokenOut* {$self.tokenOut}, + tokenIn* {$self.tokenIn} )""" 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"): addresses: seq[string] - chainIds: seq[int] + chainIds: seq[ChainId] filter: ActivityFilter offset: int limit: int diff --git a/ui/app/AppLayouts/Wallet/stores/ActivityFiltersStore.qml b/ui/app/AppLayouts/Wallet/stores/ActivityFiltersStore.qml index b8af6d73e0..9d5bf4edbb 100644 --- a/ui/app/AppLayouts/Wallet/stores/ActivityFiltersStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/ActivityFiltersStore.qml @@ -100,7 +100,7 @@ QtObject { // update filters tokensFilter = toggleFilterState(tokensFilter, symbol, tokensList.count) // Set backend values - activityController.setFilterAssets(JSON.stringify(tokensFilter)) + activityController.setFilterAssets(JSON.stringify(tokensFilter), false) activityController.updateFilter() } diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index 0c9a2b2708..5baf030e08 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -88,14 +88,6 @@ Item { width: implicitWidth text: qsTr("Activity") } - // TODO - DEV: remove me - // Enable for debugging activity filter - // currentIndex: 3 - // StatusTabButton { - // rightPadding: 0 - // width: implicitWidth - // text: qsTr("DEV activity") - // } } StackLayout { Layout.fillWidth: true @@ -127,17 +119,6 @@ Item { 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 { diff --git a/ui/imports/shared/views/ActivityView.qml b/ui/imports/shared/views/ActivityView.qml deleted file mode 100644 index ea7c5fd6a3..0000000000 --- a/ui/imports/shared/views/ActivityView.qml +++ /dev/null @@ -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"); - } -} diff --git a/ui/imports/shared/views/qmldir b/ui/imports/shared/views/qmldir index 2311441b5c..2f929c0d4c 100644 --- a/ui/imports/shared/views/qmldir +++ b/ui/imports/shared/views/qmldir @@ -13,4 +13,3 @@ TokenListView 1.0 TokenListView.qml TransactionPreview 1.0 TransactionPreview.qml TransactionSigner 1.0 TransactionSigner.qml TransactionStackView 1.0 TransactionStackView.qml -ActivityView 1.0 ActivityView.qml diff --git a/vendor/status-go b/vendor/status-go index bf64f97d5a..8e63f44735 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit bf64f97d5a2bcb1b5fb6b134dda69994e0ddd2bb +Subproject commit 8e63f447352fef5fb4eb1dbd0a87a296b2b96d78