From 3daed67ea7b45b978ddd221676e0d19e520ee0e5 Mon Sep 17 00:00:00 2001 From: Stefan Date: Thu, 8 Feb 2024 09:49:12 -0300 Subject: [PATCH] feat(activity): add incremental updates to current activity filter Switch the activity filter to use the new session-based API that deliver incremental updates to the current filter. Drop the old quick win listening for individual change events and use the unified API instead. The new transactions (on-top) trigger the old "new transactions" buttons that trigger reset of the current filter and the top new transacitons highlighted. Highlight mixed changes (not new on top) as they come in Highlight new changes on filter reset Closes #12120 --- .../wallet_section/activity/controller.nim | 118 +++++++++------ .../main/wallet_section/activity/entry.nim | 81 ++++++++--- .../activity/events_handler.nim | 80 ++-------- .../main/wallet_section/activity/model.nim | 13 ++ .../main/wallet_section/activity/status.nim | 6 +- .../modules/main/wallet_section/module.nim | 14 +- src/backend/activity.nim | 137 ++++++++++++++++-- src/backend/backend.nim | 7 + src/backend/transactions.nim | 2 - storybook/pages/TransactionDelegatePage.qml | 14 ++ test/nim/activity_tests.nim | 54 +++++++ test/status-go/integration/helpers/helpers.go | 1 + .../wallet/activityfiltering_test.go | 33 ++--- .../shared/controls/TransactionDelegate.qml | 36 ++++- ui/imports/shared/stores/RootStore.qml | 6 +- ui/imports/shared/views/HistoryView.qml | 47 +----- vendor/status-go | 2 +- 17 files changed, 429 insertions(+), 222 deletions(-) create mode 100644 test/nim/activity_tests.nim diff --git a/src/app/modules/main/wallet_section/activity/controller.nim b/src/app/modules/main/wallet_section/activity/controller.nim index 76e586f072d..4890d756cc7 100644 --- a/src/app/modules/main/wallet_section/activity/controller.nim +++ b/src/app/modules/main/wallet_section/activity/controller.nim @@ -61,8 +61,6 @@ QtObject: # call updateAssetsIdentities after updating chainIds chainIds: seq[int] - requestId: int32 - proc setup(self: Controller) = self.QObject.setup @@ -87,31 +85,9 @@ QtObject: QtProperty[QVariant] collectiblesModel: read = getCollectiblesModel - proc buildMultiTransactionExtraData(self: Controller, metadata: backend_activity.ActivityEntry): ExtraData = - if metadata.symbolIn.isSome(): - result.inAmount = self.currencyService.parseCurrencyValue(metadata.symbolIn.get(), metadata.amountIn) - if metadata.symbolOut.isSome(): - result.outAmount = self.currencyService.parseCurrencyValue(metadata.symbolOut.get(), metadata.amountOut) - - proc buildTransactionExtraData(self: Controller, metadata: backend_activity.ActivityEntry): ExtraData = - if metadata.symbolIn.isSome() or metadata.amountIn > 0: - result.inAmount = self.currencyService.parseCurrencyValue(metadata.symbolIn.get(""), metadata.amountIn) - if metadata.symbolOut.isSome() or metadata.amountOut > 0: - result.outAmount = self.currencyService.parseCurrencyValue(metadata.symbolOut.get(""), metadata.amountOut) - proc backendToPresentation(self: Controller, backendEntities: seq[backend_activity.ActivityEntry]): seq[entry.ActivityEntry] = - let amountToCurrencyConvertor = proc(amount: UInt256, symbol: string): CurrencyAmount = - return currencyAmountToItem(self.currencyService.parseCurrencyValue(symbol, amount), - self.currencyService.getCurrencyFormat(symbol)) for backendEntry in backendEntities: - var ae: entry.ActivityEntry - case backendEntry.getPayloadType(): - of MultiTransaction: - let extraData = self.buildMultiTransactionExtraData(backendEntry) - ae = entry.newMultiTransactionActivityEntry(backendEntry, extraData, amountToCurrencyConvertor) - of SimpleTransaction, PendingTransaction: - let extraData = self.buildTransactionExtraData(backendEntry) - ae = entry.newTransactionActivityEntry(backendEntry, self.addresses, extraData, amountToCurrencyConvertor) + let ae = entry.newActivityEntry(backendEntry, self.addresses, self.currencyService) result.add(ae) proc fetchTxDetails*(self: Controller, entryIndex: int) {.slot.} = @@ -154,37 +130,69 @@ QtObject: self.model.setEntries(entries, res.offset, res.hasMore) - if len(entries) > 0: - self.eventsHandler.updateRelevantTimestamp(entries[len(entries) - 1].getTimestamp()) + if res.offset == 0: + self.status.setNewDataAvailable(false) - proc updateFilter*(self: Controller) {.slot.} = + proc sessionId(self: Controller): int32 = + return self.eventsHandler.getSessionId() + + proc invalidateData(self: Controller) = self.status.setLoadingData(true) self.status.setIsFilterDirty(false) self.model.resetModel(@[]) - self.eventsHandler.updateSubscribedAddresses(self.addresses) - self.eventsHandler.updateSubscribedChainIDs(self.chainIds) self.status.setNewDataAvailable(false) - let response = backend_activity.filterActivityAsync(self.requestId, self.addresses, self.allAddressesSelected, seq[backend_activity.ChainId](self.chainIds), self.currentActivityFilter, 0, FETCH_BATCH_COUNT_DEFAULT) + + # Stops the old session and starts a new one. All the incremental changes are lost + proc newFilterSession*(self: Controller) {.slot.} = + self.invalidateData() + + # stop the previous filter session + if self.eventsHandler.hasSessionId(): + let res = backend_activity.stopActivityFilterSession(self.sessionId()) + if res.error != nil: + error "error stopping the previous session of activity fitlering: ", res.error + self.eventsHandler.clearSessionId() + + # start a new filter session + let (sessionId, ok) = backend_activity.newActivityFilterSession(self.addresses, self.allAddressesSelected, seq[backend_activity.ChainId](self.chainIds), self.currentActivityFilter, FETCH_BATCH_COUNT_DEFAULT) + if not ok: + self.status.setLoadingData(false) + return + + self.eventsHandler.setSessionId(sessionId) + + proc updateFilter*(self: Controller) {.slot.} = + self.invalidateData() + + if not backend_activity.updateFilterForSession(self.sessionId(), self.currentActivityFilter, FETCH_BATCH_COUNT_DEFAULT): + self.status.setLoadingData(false) + error "error updating activity filter" + return + + proc resetActivityData*(self: Controller) {.slot.} = + self.invalidateData() + + let response = backend_activity.resetActivityFilterSession(self.sessionId(), FETCH_BATCH_COUNT_DEFAULT) if response.error != nil: - error "error fetching activity entries: ", response.error self.status.setLoadingData(false) + error "error fetching activity entries from start: ", response.error return proc loadMoreItems(self: Controller) {.slot.} = self.status.setLoadingData(true) - let response = backend_activity.filterActivityAsync(self.requestId, self.addresses, self.allAddressesSelected, seq[backend_activity.ChainId](self.chainIds), self.currentActivityFilter, self.model.getCount(), FETCH_BATCH_COUNT_DEFAULT) + let response = backend_activity.getMoreForActivityFilterSession(self.sessionId(), FETCH_BATCH_COUNT_DEFAULT) if response.error != nil: self.status.setLoadingData(false) - error "error fetching activity entries: ", response.error + error "error fetching more activity entries: ", response.error return proc updateCollectiblesModel*(self: Controller) {.slot.} = self.status.setLoadingCollectibles(true) - let res = backend_activity.getActivityCollectiblesAsync(self.requestId, self.chainIds, self.addresses, 0, FETCH_COLLECTIBLES_BATCH_COUNT_DEFAULT) + let res = backend_activity.getActivityCollectiblesAsync(self.sessionId(), self.chainIds, self.addresses, 0, FETCH_COLLECTIBLES_BATCH_COUNT_DEFAULT) if res.error != nil: self.status.setLoadingCollectibles(false) error "error fetching collectibles: ", res.error @@ -192,7 +200,7 @@ QtObject: proc loadMoreCollectibles*(self: Controller) {.slot.} = self.status.setLoadingCollectibles(true) - let res = backend_activity.getActivityCollectiblesAsync(self.requestId, self.chainIds, self.addresses, self.collectiblesModel.getCount(), FETCH_COLLECTIBLES_BATCH_COUNT_DEFAULT) + let res = backend_activity.getActivityCollectiblesAsync(self.sessionId(), self.chainIds, self.addresses, self.collectiblesModel.getCount(), FETCH_COLLECTIBLES_BATCH_COUNT_DEFAULT) if res.error != nil: self.status.setLoadingCollectibles(false) error "error fetching collectibles: ", res.error @@ -221,7 +229,7 @@ QtObject: proc updateStartTimestamp*(self: Controller) {.slot.} = self.status.setLoadingStartTimestamp(true) - let resJson = backend_activity.getOldestActivityTimestampAsync(self.requestId, self.addresses) + let resJson = backend_activity.getOldestActivityTimestampAsync(self.sessionId(), self.addresses) if resJson.error != nil: self.status.setLoadingStartTimestamp(false) error "error requesting oldest activity timestamp: ", resJson.error @@ -265,6 +273,24 @@ QtObject: self.model.updateEntries(entries) ) + self.eventsHandler.onFilteringSessionUpdated(proc (jn: JsonNode) = + if jn.kind != JObject: + error "expected an object" + + let res = fromJson(jn, backend_activity.SessionUpdate) + + var updated = newSeq[backend_activity.ActivityEntry](len(res.`new`)) + var indices = newSeq[int](len(res.`new`)) + for i in 0 ..< len(res.`new`): + updated[i] = res.`new`[i].entry + indices[i] = res.`new`[i].pos + + self.status.setNewDataAvailable(res.hasNewOnTop) + if len(res.`new`) > 0: + let entries = self.backendToPresentation(updated) + self.model.addNewEntries(entries, indices) + ) + self.eventsHandler.onGetRecipientsDone(proc (jsonObj: JsonNode) = defer: self.status.setLoadingRecipients(false) let res = fromJson(jsonObj, backend_activity.GetRecipientsResponse) @@ -302,18 +328,12 @@ QtObject: error "Error converting activity entries: ", e.msg ) - self.eventsHandler.onNewDataAvailable(proc () = - self.status.setNewDataAvailable(true) - ) - - proc newController*(requestId: int32, - currencyService: currency_service.Service, + proc newController*(currencyService: currency_service.Service, tokenService: token_service.Service, savedAddressService: saved_address_service.Service, events: EventEmitter): Controller = new(result, delete) - result.requestId = requestId result.model = newModel() result.recipientsModel = newRecipientsModel() result.collectiblesModel = newCollectiblesModel() @@ -321,7 +341,7 @@ QtObject: result.savedAddressService = savedAddressService result.currentActivityFilter = backend_activity.getIncludeAllActivityFilter() - result.eventsHandler = newEventsHandler(result.requestId, events) + result.eventsHandler = newEventsHandler(events) result.status = newStatus() result.currencyService = currencyService @@ -461,7 +481,7 @@ QtObject: proc updateRecipientsModel*(self: Controller) {.slot.} = self.status.setLoadingRecipients(true) - let res = backend_activity.getRecipientsAsync(self.requestId, self.chainIds, self.addresses, 0, FETCH_RECIPIENTS_BATCH_COUNT_DEFAULT) + let res = backend_activity.getRecipientsAsync(self.sessionId(), self.chainIds, self.addresses, 0, FETCH_RECIPIENTS_BATCH_COUNT_DEFAULT) if res.error != nil or res.result.kind != JBool: self.status.setLoadingRecipients(false) error "error fetching recipients: ", res.error, "; kind ", res.result.kind @@ -473,7 +493,7 @@ QtObject: proc loadMoreRecipients(self: Controller) {.slot.} = self.status.setLoadingRecipients(true) - let res = backend_activity.getRecipientsAsync(self.requestId, self.chainIds, self.addresses, self.recipientsModel.getCount(), FETCH_RECIPIENTS_BATCH_COUNT_DEFAULT) + let res = backend_activity.getRecipientsAsync(self.sessionId(), self.chainIds, self.addresses, self.recipientsModel.getCount(), FETCH_RECIPIENTS_BATCH_COUNT_DEFAULT) if res.error != nil: self.status.setLoadingRecipients(false) error "error fetching more recipient entries: ", res.error @@ -492,8 +512,12 @@ QtObject: proc globalFilterChanged*(self: Controller, addresses: seq[string], allAddressesSelected: bool, chainIds: seq[int], allChainsEnabled: bool) = if (self.addresses == addresses and self.allAddressesSelected == allAddressesSelected and self.chainIds == chainIds): return + self.setFilterAddresses(addresses, allAddressesSelected) self.setFilterChains(chainIds, allChainsEnabled) + # Every change of chains and addresses have to start a new session to get incremental updates when filter is cleared + self.newFilterSession() + proc noLimitTimestamp*(self: Controller): int {.slot.} = return backend_activity.noLimitTimestampForPeriod diff --git a/src/app/modules/main/wallet_section/activity/entry.nim b/src/app/modules/main/wallet_section/activity/entry.nim index 421290753ea..94954d54984 100644 --- a/src/app/modules/main/wallet_section/activity/entry.nim +++ b/src/app/modules/main/wallet_section/activity/entry.nim @@ -7,6 +7,8 @@ import app/global/global_singleton import app_service/service/currency/service +import app/modules/shared/wallet_utils + import web3/ethtypes as eth # Additional data needed to build an Entry, which is @@ -17,13 +19,10 @@ type inAmount*: float64 outAmount*: float64 - AmountToCurrencyConvertor* = proc (amount: UInt256, symbol: string): CurrencyAmount - # Used to display an activity history header entry in the QML UI QtObject: type ActivityEntry* = ref object of QObject - valueConvertor: AmountToCurrencyConvertor metadata: backend.ActivityEntry extradata: ExtraData @@ -33,6 +32,9 @@ QtObject: nftName: string nftImageURL: string + # true for entries that were changed/added in the current session + highlight: bool + proc setup(self: ActivityEntry) = self.QObject.setup @@ -42,32 +44,61 @@ QtObject: proc isInTransactionType(self: ActivityEntry): bool = return self.metadata.activityType == backend.ActivityType.Receive or self.metadata.activityType == backend.ActivityType.Mint - proc newMultiTransactionActivityEntry*(metadata: backend.ActivityEntry, extradata: ExtraData, valueConvertor: AmountToCurrencyConvertor): ActivityEntry = + proc extractCurrencyAmount(self: ActivityEntry, currencyService: Service): CurrencyAmount = + let amount = if self.isInTransactionType(): self.metadata.amountIn else: self.metadata.amountOut + let symbol = if self.isInTransactionType(): self.metadata.symbolIn.get("") else: self.metadata.symbolOut.get("") + return currencyAmountToItem( + currencyService.parseCurrencyValue(symbol, amount), + currencyService.getCurrencyFormat(symbol), + ) + + proc newMultiTransactionActivityEntry*(metadata: backend.ActivityEntry, extradata: ExtraData, currencyService: Service): ActivityEntry = new(result, delete) - result.valueConvertor = valueConvertor result.metadata = metadata result.extradata = extradata result.noAmount = newCurrencyAmount() - result.amountCurrency = valueConvertor( - if result.isInTransactionType(): metadata.amountIn else: metadata.amountOut, - if result.isInTransactionType(): metadata.symbolIn.get("") else: metadata.symbolOut.get(""), - ) + + result.amountCurrency = result.extractCurrencyAmount(currencyService) + + result.highlight = metadata.isNew + result.setup() - proc newTransactionActivityEntry*(metadata: backend.ActivityEntry, fromAddresses: seq[string], extradata: ExtraData, valueConvertor: AmountToCurrencyConvertor): ActivityEntry = + proc newTransactionActivityEntry*(metadata: backend.ActivityEntry, fromAddresses: seq[string], extradata: ExtraData, currencyService: Service): ActivityEntry = new(result, delete) - result.valueConvertor = valueConvertor result.metadata = metadata result.extradata = extradata - - result.amountCurrency = valueConvertor( - if result.isInTransactionType(): metadata.amountIn else: metadata.amountOut, - if result.isInTransactionType(): metadata.symbolIn.get("") else: metadata.symbolOut.get(""), - ) result.noAmount = newCurrencyAmount() + result.amountCurrency = result.extractCurrencyAmount(currencyService) + + result.highlight = metadata.isNew + result.setup() + proc buildMultiTransactionExtraData(metadata: backend.ActivityEntry, currencyService: Service): ExtraData = + if metadata.symbolIn.isSome(): + result.inAmount = currencyService.parseCurrencyValue(metadata.symbolIn.get(), metadata.amountIn) + if metadata.symbolOut.isSome(): + result.outAmount = currencyService.parseCurrencyValue(metadata.symbolOut.get(), metadata.amountOut) + + proc buildTransactionExtraData(metadata: backend.ActivityEntry, currencyService: Service): ExtraData = + if metadata.symbolIn.isSome() or metadata.amountIn > 0: + result.inAmount = currencyService.parseCurrencyValue(metadata.symbolIn.get(""), metadata.amountIn) + if metadata.symbolOut.isSome() or metadata.amountOut > 0: + result.outAmount = currencyService.parseCurrencyValue(metadata.symbolOut.get(""), metadata.amountOut) + + proc newActivityEntry*(backendEntry: backend.ActivityEntry, addresses: seq[string], currencyService: Service): ActivityEntry = + var ae: entry.ActivityEntry + case backendEntry.getPayloadType(): + of MultiTransaction: + let extraData = buildMultiTransactionExtraData(backendEntry, currencyService) + ae = newMultiTransactionActivityEntry(backendEntry, extraData, currencyService) + of SimpleTransaction, PendingTransaction: + let extraData = buildTransactionExtraData(backendEntry, currencyService) + ae = newTransactionActivityEntry(backendEntry, addresses, extraData, currencyService) + return ae + proc isMultiTransaction*(self: ActivityEntry): bool {.slot.} = return self.metadata.getPayloadType() == backend.PayloadType.MultiTransaction @@ -278,6 +309,20 @@ QtObject: if self.metadata.communityId.isSome(): return self.metadata.communityId.unsafeGet() return "" - + QtProperty[string] communityId: - read = getCommunityId \ No newline at end of file + read = getCommunityId + + proc highlightChanged*(self: ActivityEntry) {.signal.} + + proc getHighlight*(self: ActivityEntry): bool {.slot.} = + return self.highlight + + proc doneHighlighting*(self: ActivityEntry) {.slot.} = + if self.highlight: + self.highlight = false + self.highlightChanged() + + QtProperty[bool] highlight: + read = getHighlight + notify = highlightChanged diff --git a/src/app/modules/main/wallet_section/activity/events_handler.nim b/src/app/modules/main/wallet_section/activity/events_handler.nim index 9d54f50d9b3..1fd6ebcc607 100644 --- a/src/app/modules/main/wallet_section/activity/events_handler.nim +++ b/src/app/modules/main/wallet_section/activity/events_handler.nim @@ -20,13 +20,7 @@ QtObject: eventHandlers: Table[string, EventCallbackProc] walletEventHandlers: Table[string, WalletEventCallbackProc] - # Ignore events older than this relevantTimestamp - relevantTimestamp: int - subscribedAddresses: HashSet[string] - subscribedChainIDs: HashSet[int] - newDataAvailableFn: proc() - - requestId: int + sessionId: Option[int32] proc setup(self: EventsHandler) = self.QObject.setup @@ -40,6 +34,9 @@ QtObject: proc onFilteringUpdateDone*(self: EventsHandler, handler: EventCallbackProc) = self.eventHandlers[backend_activity.eventActivityFilteringUpdate] = handler + proc onFilteringSessionUpdated*(self: EventsHandler, handler: EventCallbackProc) = + self.eventHandlers[backend_activity.eventActivitySessionUpdated] = handler + proc onGetRecipientsDone*(self: EventsHandler, handler: EventCallbackProc) = self.eventHandlers[backend_activity.eventActivityGetRecipientsDone] = handler @@ -49,13 +46,10 @@ QtObject: proc onGetCollectiblesDone*(self: EventsHandler, handler: EventCallbackProc) = self.eventHandlers[backend_activity.eventActivityGetCollectiblesDone] = handler - proc onNewDataAvailable*(self: EventsHandler, handler: proc()) = - self.newDataAvailableFn = handler - proc handleApiEvents(self: EventsHandler, e: Args) = var data = WalletSignal(e) - if data.requestId.isSome and data.requestId.get() != self.requestId: + if not data.requestId.isSome() or not self.sessionId.isSome() or data.requestId.get() != self.sessionId.get(): return if self.walletEventHandlers.hasKey(data.eventType): @@ -66,71 +60,29 @@ QtObject: let responseJson = parseJson(data.message) callback(responseJson) - proc setupWalletEventHandlers(self: EventsHandler) = - let newDataAvailableCallback = proc (data: WalletSignal) = - if self.newDataAvailableFn == nil: - return - - if data.at > 0 and self.relevantTimestamp > 0 and data.at < self.relevantTimestamp: - return - - # Check chain, if any was reported - if len(self.subscribedChainIDs) > 0 and data.chainID > 0: - var contains = false - for chainID in self.subscribedChainIDs: - if data.chainID == chainID: - contains = true - break - if not contains: - return - - var contains = data.accounts.len == 0 - # Check addresses if any was reported - for address in data.accounts: - if address in self.subscribedAddresses: - contains = true - break - - if not contains: - return - - self.newDataAvailableFn() - - # TODO #12120: Replace these specific events with incremental updates events - self.walletEventHandlers[EventNewTransfers] = newDataAvailableCallback - self.walletEventHandlers[EventPendingTransactionUpdate] = newDataAvailableCallback - self.walletEventHandlers[EventMTTransactionUpdate] = newDataAvailableCallback - - proc newEventsHandler*(requestId: int, events: EventEmitter): EventsHandler = + proc newEventsHandler*(events: EventEmitter): EventsHandler = new(result, delete) result.events = events result.eventHandlers = initTable[string, EventCallbackProc]() - result.subscribedAddresses = initHashSet[string]() - result.subscribedChainIDs = initHashSet[int]() - - result.requestId = requestId - result.setup() - result.setupWalletEventHandlers() - # Register for wallet events let eventsHandler = result result.events.on(SignalType.Wallet.event, proc(e: Args) = eventsHandler.handleApiEvents(e) ) - proc updateRelevantTimestamp*(self: EventsHandler, timestamp: int) = - self.relevantTimestamp = timestamp + proc getSessionId*(self: EventsHandler): int32 = + self.sessionId.get(-1) + + proc setSessionId*(self: EventsHandler, sessionId: int32) = + self.sessionId = some(sessionId) + + proc hasSessionId*(self: EventsHandler): bool = + self.sessionId.isSome() - proc updateSubscribedAddresses*(self: EventsHandler, addresses: seq[string]) = - self.subscribedAddresses.clear() - for address in addresses: - self.subscribedAddresses.incl(address) + proc clearSessionId*(self: EventsHandler) = + self.sessionId = none(int32) - proc updateSubscribedChainIDs*(self: EventsHandler, chainIDs: seq[int]) = - self.subscribedChainIDs.clear() - for chainID in chainIDs: - self.subscribedChainIDs.incl(chainID) diff --git a/src/app/modules/main/wallet_section/activity/model.nim b/src/app/modules/main/wallet_section/activity/model.nim index ee2a912f840..b3977ae8a3b 100644 --- a/src/app/modules/main/wallet_section/activity/model.nim +++ b/src/app/modules/main/wallet_section/activity/model.nim @@ -111,6 +111,19 @@ QtObject: return m.getTransactionIdentity().isSome() and d.transaction.isSome() and m.getTransactionIdentity().get() == d.transaction.get() + proc addNewEntries*(self: Model, newEntries: seq[entry.ActivityEntry], insertPositions: seq[int]) = + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + + for j in countdown(newEntries.high, 0): + let ae = newEntries[j] + let pos = insertPositions[j] + self.beginInsertRows(parentModelIndex, pos, pos) + self.entries.insert(ae, pos) + self.endInsertRows() + + self.countChanged() + proc updateEntries*(self: Model, updates: seq[backend.Data]) = for i in countdown(self.entries.high, 0): for j in countdown(updates.high, 0): diff --git a/src/app/modules/main/wallet_section/activity/status.nim b/src/app/modules/main/wallet_section/activity/status.nim index 6566bb184da..3bba726d180 100644 --- a/src/app/modules/main/wallet_section/activity/status.nim +++ b/src/app/modules/main/wallet_section/activity/status.nim @@ -9,7 +9,7 @@ import backend/activity as backend_activity QtObject: type Status* = ref object of QObject - loadingData: Atomic[int] + loadingData: bool errorCode: backend_activity.ErrorCode loadingRecipients: Atomic[int] @@ -36,7 +36,7 @@ QtObject: proc loadingDataChanged*(self: Status) {.signal.} proc setLoadingData*(self: Status, loadingData: bool) = - discard fetchAdd(self.loadingData, if loadingData: 1 else: -1) + self.loadingData = loadingData self.loadingDataChanged() proc loadingRecipientsChanged*(self: Status) {.signal.} @@ -72,7 +72,7 @@ QtObject: result.setup() proc getLoadingData*(self: Status): bool {.slot.} = - return load(self.loadingData) > 0 + return self.loadingData QtProperty[bool] loadingData: read = getLoadingData diff --git a/src/app/modules/main/wallet_section/module.nim b/src/app/modules/main/wallet_section/module.nim index d3a4599c689..848a090cd06 100644 --- a/src/app/modules/main/wallet_section/module.nim +++ b/src/app/modules/main/wallet_section/module.nim @@ -49,11 +49,6 @@ logScope: export io_interface type - ActivityID = enum - History - Temporary0 - Temporary1 - Module* = ref object of io_interface.AccessInterface delegate: delegate_interface.AccessInterface events: EventEmitter @@ -141,14 +136,13 @@ proc newModule*( result.networksService = networkService result.transactionService = transactionService - result.activityController = activityc.newController(int32(ActivityID.History), currencyService, tokenService, + result.activityController = activityc.newController(currencyService, tokenService, savedAddressService, events) result.tmpActivityControllers = [ - activityc.newController(int32(ActivityID.Temporary0), currencyService, tokenService, - savedAddressService, events), - activityc.newController(int32(ActivityID.Temporary1), currencyService, tokenService, - savedAddressService, events) + activityc.newController(currencyService, tokenService, savedAddressService, events), + activityc.newController(currencyService, tokenService, savedAddressService, events) ] + result.collectibleDetailsController = collectible_detailsc.newController(int32(backend_collectibles.CollectiblesRequestID.WalletAccount), networkService, events) result.filter = initFilter(result.controller) diff --git a/src/backend/activity.nim b/src/backend/activity.nim index fed22bdd82a..2611829f6e1 100644 --- a/src/backend/activity.nim +++ b/src/backend/activity.nim @@ -23,6 +23,8 @@ const eventActivityGetOldestTimestampDone*: string = "wallet-activity-get-oldest const eventActivityFetchTransactionDetails*: string = "wallet-activity-fetch-transaction-details-result" const eventActivityGetCollectiblesDone*: string = "wallet-activity-get-collectibles" +const eventActivitySessionUpdated*: string = "wallet-activity-session-updated" + type Period* = object startTimestamp*: int @@ -62,6 +64,27 @@ type filterOutAssets*: bool filterOutCollectibles*: bool +proc `$`*(p: Period): string = + if p.startTimestamp == noLimitTimestampForPeriod and p.endTimestamp == noLimitTimestampForPeriod: + return "Period(UNLIMITED)" + + return fmt"""Period( + startTimestamp: {p.startTimestamp}, + endTimestamp: {p.endTimestamp} + )""" + +proc `$`*(t: ActivityFilter): string = + return fmt"""ActivityFilter( + period: {t.period}, + types: {t.types}, + statuses: {t.statuses}, + counterpartyAddresses: {t.counterpartyAddresses}, + assets: {t.assets}, + collectibles: {t.collectibles}, + filterOutAssets: {t.filterOutAssets}, + filterOutCollectibles: {t.filterOutCollectibles} + )""" + proc toJson[T](obj: Option[T]): JsonNode = if obj.isSome: toJson(obj.get()) @@ -74,6 +97,17 @@ proc fromJson[T](jsonObj: JsonNode, TT: typedesc[Option[T]]): Option[T] = else: return none(T) +proc fromJson[T](jsonObj: JsonNode, TT: typedesc[seq[T]]): seq[T] = + if jsonObj.kind != JArray: + error "Expected array, got: ", jsonObj.kind + return @[] + + result = newSeq[T](jsonObj.len) + for i, elem in jsonObj.getElems(): + result[i] = fromJson(elem, T) + + return result + proc `%`*(at: ActivityType): JsonNode {.inline.} = return newJInt(ord(at)) @@ -241,8 +275,6 @@ proc `$`*(pt: ProtocolType): string {.inline.} = return "Hop" of Uniswap: return "Uniswap" - else: - return "" # Mirrors status-go/services/wallet/activity/activity.go TransferType type @@ -286,6 +318,7 @@ type transferType*: Option[TransferType] communityId*: Option[string] + isNew*: bool # Mirrors status-go/services/wallet/activity/activity.go EntryData Data* = object @@ -312,6 +345,8 @@ type chainIdIn*: Option[ChainId] transferType*: Option[TransferType] + isNew*: bool + nftName*: Option[string] nftUrl*: Option[string] @@ -330,6 +365,18 @@ type hasMore*: bool errorCode*: ErrorCode + + # Mirrors services/wallet/activity/session.go EntryUpdate + EntryUpdate* = object + pos*: int + entry*: ActivityEntry + + # Mirrors services/wallet/activity/session.go SessionUpdate + SessionUpdate* = object + hasNewOnTop*: bool + `new`*: seq[EntryUpdate] + removed*: seq[TransactionIdentity] + proc getPayloadType*(ae: ActivityEntry): PayloadType = return ae.payloadType @@ -366,6 +413,7 @@ proc fromJson*(e: JsonNode, T: typedesc[Data]): Data {.inline.} = const nftNameField = "nftName" const nftUrlField = "nftUrl" const communityIdField = "communityId" + const isNewField = "isNew" result = T( payloadType: fromJson(e["payloadType"], PayloadType), transaction: if e.hasKey(transactionField): @@ -419,9 +467,11 @@ proc fromJson*(e: JsonNode, T: typedesc[Data]): Data {.inline.} = result.chainIdIn = some(fromJson(e[chainIdInField], ChainId)) if e.hasKey(transferTypeField) and e[transferTypeField].kind != JNull: result.transferType = some(fromJson(e[transferTypeField], TransferType)) + result.isNew = e.hasKey(isNewField) and e[isNewField].getBool() proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline.} = let data = fromJson(e, Data) + let zeroValue: UInt256 = "0x0".parse(UInt256, 16) result = T( payloadType: data.payloadType, transaction: data.transaction, @@ -429,8 +479,8 @@ proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline. activityType: data.activityType.get(), activityStatus: data.activityStatus.get(), timestamp: data.timestamp.get(), - amountOut: data.amountOut.get(), - amountIn: data.amountIn.get(), + amountOut: if data.amountOut.isSome: data.amountOut.get() else: zeroValue, + amountIn: if data.amountIn.isSome: data.amountIn.get() else: zeroValue, tokenOut: data.tokenOut, tokenIn: data.tokenIn, symbolOut: data.symbolOut, @@ -440,7 +490,8 @@ proc fromJson*(e: JsonNode, T: typedesc[ActivityEntry]): ActivityEntry {.inline. chainIdOut: data.chainIdOut, chainIdIn: data.chainIdIn, transferType: data.transferType, - communityId: data.communityId + communityId: data.communityId, + isNew: data.isNew ) proc `$`*(self: ActivityEntry): string = @@ -464,7 +515,8 @@ proc `$`*(self: ActivityEntry): string = chainIdOut* {$self.chainIdOut}, chainIdIn* {$self.chainIdIn}, transferType* {$self.transferType}, - communityId* {$self.communityId} + communityId* {$self.communityId}, + isNew* {$self.isNew}, )""" proc fromJson*(e: JsonNode, T: typedesc[FilterResponse]): FilterResponse {.inline.} = @@ -486,14 +538,77 @@ proc fromJson*(e: JsonNode, T: typedesc[FilterResponse]): FilterResponse {.inlin errorCode: ErrorCode(e["errorCode"].getInt()) ) -rpc(filterActivityAsync, "wallet"): - requestId: int32 +proc fromJson*(e: JsonNode, T: typedesc[EntryUpdate]): T {.inline.} = + const posField = "pos" + const entryField = "entry" + result = T( + pos: if e.hasKey(posField): e[posField].getInt() else: -1, + entry: if e.hasKey(entryField): fromJson(e[entryField], ActivityEntry) else: ActivityEntry() + ) + +proc fromJson*(e: JsonNode, T: typedesc[SessionUpdate]): T {.inline.} = + const hasNewOnTopField = "hasNewOnTop" + const newField = "new" + const removedField = "removed" + let hasNewOnTop = e.hasKey(hasNewOnTopField) and e[hasNewOnTopField].getBool() + let newEntries = if e.hasKey(newField): fromJson(e[newField], seq[EntryUpdate]) else: @[] + let removed = if e.hasKey(removedField): fromJson(e[removedField], seq[TransactionIdentity]) else: @[] + result = T( + hasNewOnTop: hasNewOnTop, + `new`: newEntries, + removed: removed + ) + +rpc(startActivityFilterSession, "wallet"): addresses: seq[string] allAddresses: bool chainIds: seq[ChainId] filter: ActivityFilter - offset: int - limit: int + count: int + +rpc(updateActivityFilterForSession, "wallet"): + sessionId: int32 + filter: ActivityFilter + count: int + +rpc(resetActivityFilterSession, "wallet"): + sessionId: int32 + count: int + +rpc(getMoreForActivityFilterSession, "wallet"): + sessionId: int32 + count: int + +rpc(stopActivityFilterSession, "wallet"): + sessionId: int32 + +# returns (sessionId, success) +proc newActivityFilterSession*( + addresses: seq[string], + allAddresses: bool, + chainIds: seq[ChainId], + filter: ActivityFilter, + count: int, +): (int32, bool) {.inline.} = + try: + let res = startActivityFilterSession(addresses, allAddresses, chainIds, filter, count) + if res.error != nil: + error "error starting a new session of activity fitlering: ", res.error + return (int32(-1), false) + return (int32(res.result.getInt()), true) + except: + return (int32(-1), false) + +proc updateFilterForSession*(sessionId: int32, filter: ActivityFilter, count: int): bool {.inline.} = + try: + let res = updateActivityFilterForSession(sessionId, filter, count) + if res.error != nil: + error "error updating fitler for session: ", res.error + return false + except: + return false + + return true # see services/wallet/activity/service.go GetRecipientsResponse type GetRecipientsResponse* = object @@ -540,7 +655,7 @@ rpc(getOldestActivityTimestampAsync, "wallet"): requestId: int32 addresses: seq[string] -type +type # Mirrors services/wallet/thirdparty/collectible_types.go ContractID ContractID* = ref object of RootObj chainID*: int diff --git a/src/backend/backend.nim b/src/backend/backend.nim index c5a10e36b8e..b7371749c44 100644 --- a/src/backend/backend.nim +++ b/src/backend/backend.nim @@ -111,6 +111,13 @@ type hash*: string address*: string +proc fromJson*(t: JsonNode, T: typedesc[TransactionIdentity]): T {.inline.} = + result = TransactionIdentity( + chainId: if t.hasKey("chainId"): t["chainId"].getInt() else: 0, + hash: if t.hasKey("hash"): t["hash"].getStr() else: "", + address: if t.hasKey("address"): t["address"].getStr() else: "", + ) + proc hash*(ti: TransactionIdentity): Hash = var h: Hash = 0 h = h !& hash(ti.chainId) diff --git a/src/backend/transactions.nim b/src/backend/transactions.nim index 1d54b71c6fd..b8ad07e40df 100644 --- a/src/backend/transactions.nim +++ b/src/backend/transactions.nim @@ -33,7 +33,6 @@ type multiTxType* {.serializedFieldName("type").}: MultiTransactionType # Mirrors the transfer events from status-go, services/wallet/transfer/commands.go -const EventNewTransfers*: string = "new-transfers" const EventFetchingRecentHistory*: string = "recent-history-fetching" const EventRecentHistoryReady*: string = "recent-history-ready" const EventFetchingHistoryError*: string = "fetching-history-error" @@ -41,7 +40,6 @@ const EventNonArchivalNodeDetected*: string = "non-archival-node-detected" # Mirrors the pending transfer event from status-go, status-go/services/wallet/transfer/transaction.go const EventPendingTransactionUpdate*: string = "pending-transaction-update" -const EventMTTransactionUpdate*: string = "multi-transaction-update" proc `$`*(self: MultiTransactionDto): string = return fmt"""MultiTransactionDto( diff --git a/storybook/pages/TransactionDelegatePage.qml b/storybook/pages/TransactionDelegatePage.qml index 1d60354a3b4..513fc65aaef 100644 --- a/storybook/pages/TransactionDelegatePage.qml +++ b/storybook/pages/TransactionDelegatePage.qml @@ -11,6 +11,7 @@ import shared.controls 1.0 SplitView { id: root + // mirrors ActivityEntry defined in src/app/modules/main/wallet_section/activity/entry.nim readonly property QtObject mockupModelData: QtObject { readonly property int timestamp: Date.now() / 1000 readonly property int status: ctrlStatus.currentValue @@ -38,6 +39,12 @@ SplitView { readonly property string chainId: "NETWORKID" readonly property string chainIdIn: "NETWORKID-IN" readonly property string chainIdOut: "NETWORKID-OUT" + + readonly property bool highlight: _highlight + function doneHighlighting() { + _highlight = false + } + property bool _highlight: false } SplitView { @@ -175,6 +182,13 @@ SplitView { id: ctrlMultiTrans text: "Multi transaction" } + + Button { + text: "New transaction" + onClicked: { + mockupModelData._highlight = true + } + } } } } diff --git a/test/nim/activity_tests.nim b/test/nim/activity_tests.nim new file mode 100644 index 00000000000..7dad273a00b --- /dev/null +++ b/test/nim/activity_tests.nim @@ -0,0 +1,54 @@ +import unittest, json, options + +import backend/activity + +const testOneNewJsonData = "{\"new\": [{\"pos\": 3, \"entry\": {\"payloadType\": 1, \"id\": 12, \"activityType\": 1, \"activityStatus\": 2, \"timestamp\": 1234567890, \"isNew\": true}}]}" +const testOneNewJsonDataMissingIsNew = "{\"new\": [{\"pos\": 3, \"entry\": {\"payloadType\": 1, \"id\": 12, \"activityType\": 1, \"activityStatus\": 2, \"timestamp\": 1234567890}}]}" +const oneRemovedJsonTestData = "{\"removed\":[{\"chainId\": 7, \"hash\": \"0x5\", \"address\": \"0x6\"}]}" +const testAllSetJsonData = "{\"hasNewOnTop\": true, \"new\": [{\"pos\": 3, \"entry\": {\"payloadType\": 1, \"id\": 12, \"activityType\": 1, \"activityStatus\": 2, \"timestamp\": 1234567890, \"isNew\": true}}], \"removed\":[{\"chainId\": 7, \"hash\": \"0x5\", \"address\": \"0x6\"}]}" + +suite "activity filter API json parsing": + + test "just hasNewOnTop": + const jsonData = "{\"hasNewOnTop\": true}" + let jsonNode = json.parseJson(jsonData) + + let parsed = fromJson(jsonNode, activity.SessionUpdate) + check(parsed.hasNewOnTop == true) + check(len(parsed.new) == 0) + + test "just new": + let jsonNode = json.parseJson(testOneNewJsonData) + + let parsed = fromJson(jsonNode, activity.SessionUpdate) + check(len(parsed.new) == 1) + let update = parsed.new[0] + check(update.pos == 3) + check(update.entry.isNew == true) + check(update.entry.getMultiTransactionId().get(-1) == 12) + check(update.entry.timestamp == 1234567890) + + test "just isNew optional": + let jsonNode = json.parseJson(testOneNewJsonDataMissingIsNew) + + let parsed = fromJson(jsonNode, activity.SessionUpdate) + check(len(parsed.new) == 1) + check(parsed.new[0].entry.isNew == false) + + test "just removed": + let jsonNode = json.parseJson(oneRemovedJsonTestData) + + let parsed = fromJson(jsonNode, activity.SessionUpdate) + check(len(parsed.removed) == 1) + let removed = parsed.removed[0] + check(removed.chainId == 7) + check(removed.hash == "0x5") + check(removed.address == "0x6") + + test "all set": + let jsonNode = json.parseJson(testAllSetJsonData) + + let parsed = fromJson(jsonNode, activity.SessionUpdate) + check(parsed.hasNewOnTop == true) + check(len(parsed.new) == 1) + check(len(parsed.removed) == 1) diff --git a/test/status-go/integration/helpers/helpers.go b/test/status-go/integration/helpers/helpers.go index 96cd465f000..b2c36ea09ff 100644 --- a/test/status-go/integration/helpers/helpers.go +++ b/test/status-go/integration/helpers/helpers.go @@ -114,6 +114,7 @@ func WaitForWalletEvents(eventQueue chan GoEvent, eventNames []walletevent.Event // WaitForWalletEvents waits for the given events to be received on the eventQueue. // It returns the wallet events in the order they are received. +// If a condition is provided, only returning true on the respective call will discard that event. func WaitForWalletEventsWithOptionals(eventQueue chan GoEvent, eventNames []walletevent.EventType, timeout time.Duration, condition func(walletEvent *walletevent.Event) bool, optionalEventNames []walletevent.EventType) (walletEvents []*walletevent.Event, err error) { if len(eventNames) == 0 { return nil, errors.New("no event names provided") diff --git a/test/status-go/integration/wallet/activityfiltering_test.go b/test/status-go/integration/wallet/activityfiltering_test.go index 6ad138683f3..6a549e963be 100644 --- a/test/status-go/integration/wallet/activityfiltering_test.go +++ b/test/status-go/integration/wallet/activityfiltering_test.go @@ -4,7 +4,6 @@ package wallet import ( - "encoding/json" "testing" "time" @@ -42,13 +41,14 @@ func TestActivityIncrementalUpdates_NoFilterNewPendingTransactions(t *testing.T) // Wait for EventActivitySessionUpdated signal triggered by the first EventPendingTransactionUpdate update, err := helpers.WaitForWalletEventGetPayload[activity.SessionUpdate](td.eventQueue, activity.EventActivitySessionUpdated, 60*time.Second) require.NoError(t, err) - require.NotNil(t, update.HasNewEntries) - require.True(t, *update.HasNewEntries) + require.NotNil(t, update.HasNewOnTop) + require.True(t, *update.HasNewOnTop) - // TODO #12120 check EventActivitySessionUpdated due to transactions.EventPendingTransactionStatusChanged - // statusPayload, err := helpers.WaitForWalletEventGetPayload[transactions.StatusChangedPayload](td.eventQueue, , 60*time.Second) + // TODO #12120 check EventActivitySessionUpdated due to EventPendingTransactionStatusChanged + // statusPayload, err := helpers.WaitForWalletEventGetPayload[transactions.StatusChangedPayload](td.eventQueue, activity.EventActivitySessionUpdated, 60*time.Second) // require.NoError(t, err) - // require.Equal(t, transactions.Success, statusPayload.Status) + // require.NotNil(t, update.HasNewOnTop) + // require.True(t, *update.HasNewOnTop) // Start history download to cleanup pending transactions _, err = helpers.CallPrivateMethod("wallet_checkRecentHistoryForChainIDs", []interface{}{[]uint64{5}, []types.Address{td.sender.Address, td.recipient.Address}}) @@ -65,16 +65,13 @@ func TestActivityIncrementalUpdates_NoFilterNewPendingTransactions(t *testing.T) 120*time.Second, func(e *walletevent.Event) bool { if e.Type == activity.EventActivitySessionUpdated { - var parsedPayload activity.SessionUpdate - err := json.Unmarshal(([]byte)(e.Message), &parsedPayload) + update, err = walletevent.GetPayload[activity.SessionUpdate](*e) require.NoError(t, err) - update = &parsedPayload - // TODO #12120 enable after implementing remove and update - // require.NotNil(t, update.HasNewEntries) - // require.True(t, *update.HasNewEntries) - // require.NotNil(t, update.Removed) - // require.True(t, *update.Removed) + require.NotNil(t, update.HasNewOnTop) + require.True(t, *update.HasNewOnTop) + //require.NotNil(t, update.Removed) + //require.True(t, *update.Removed) return false } else if e.Type == transfer.EventFetchingHistoryError { require.Fail(t, "History download failed") @@ -87,12 +84,12 @@ func TestActivityIncrementalUpdates_NoFilterNewPendingTransactions(t *testing.T) []walletevent.EventType{activity.EventActivitySessionUpdated, transfer.EventFetchingHistoryError}, ) require.NoError(t, err) - require.NotNil(t, update, "EventActivitySessionUpdated signal WASN'T triggered by the second EventPendingTransactionUpdate during history download") - require.NotNil(t, update.HasNewEntries) - require.True(t, *update.HasNewEntries) + require.NotNil(t, update, "EventActivitySessionUpdated signal was triggered by the second EventPendingTransactionUpdate during history download") + require.NotNil(t, update.HasNewOnTop) + require.True(t, *update.HasNewOnTop) // Start history download to cleanup pending transactions - _, err = helpers.CallPrivateMethodAndGetT[interface{}]("wallet_resetFilterSession", []interface{}{sessionID, 3}) + _, err = helpers.CallPrivateMethodAndGetT[interface{}]("wallet_resetActivityFilterSession", []interface{}{sessionID, 3}) require.NoError(t, err) updatedRes, err := helpers.WaitForWalletEventsGetMap(td.eventQueue, []walletevent.EventType{activity.EventActivityFilteringDone}, 1*time.Second) diff --git a/ui/imports/shared/controls/TransactionDelegate.qml b/ui/imports/shared/controls/TransactionDelegate.qml index 80829e1afe9..bd29619b4ed 100644 --- a/ui/imports/shared/controls/TransactionDelegate.qml +++ b/ui/imports/shared/controls/TransactionDelegate.qml @@ -157,7 +157,7 @@ StatusListItem { bgWidth: width + 2 bgHeight: height + 2 bgRadius: bgWidth / 2 - bgColor: Style.current.name === Constants.lightThemeName && Constants.isDefaultTokenIcon(root.tokenImage) ? + bgColor: d.lightTheme && Constants.isDefaultTokenIcon(root.tokenImage) ? Theme.palette.white : "transparent" color: "transparent" isImage: !loading @@ -173,6 +173,9 @@ StatusListItem { property int titlePixelSize: 15 property int subtitlePixelSize: 13 property bool showRetryButton: false + + readonly property bool isLightTheme: Style.current.name === Constants.lightThemeName + property color animatedBgColor } function getDetailsString(detailsObj) { @@ -534,6 +537,12 @@ StatusListItem { rightPadding: 16 enabled: !loading loading: !isModelDataValid + color: { + if (bgColorAnimation.running) { + return d.animatedBgColor + } + return sensor.containsMouse ? Theme.palette.baseColor5 : Style.current.transparent + } statusListItemIcon.active: (loading || root.asset.name) asset { @@ -867,4 +876,29 @@ StatusListItem { } } ] + + ColorAnimation { + id: bgColorAnimation + + target: d + property: "animatedBgColor" + from: d.isLightTheme ? "#33869eff" : "#1a4360df" + to: "transparent" + duration: 1000 + alwaysRunToEnd: true + + onStopped: { + modelData.doneHighlighting() + } + } + // Add a delay before the animation to make it easier to notice when scrolling + Timer { + id: delayAnimation + interval: 250 + running: root.visible && isModelDataValid && modelData.highlight + repeat: false + onTriggered: { + bgColorAnimation.start() + } + } } diff --git a/ui/imports/shared/stores/RootStore.qml b/ui/imports/shared/stores/RootStore.qml index 97e651da7c2..b7f28862754 100644 --- a/ui/imports/shared/stores/RootStore.qml +++ b/ui/imports/shared/stores/RootStore.qml @@ -32,8 +32,8 @@ QtObject { property var marketValueStore: TokenMarketValuesStore{} property var allNetworks: networksModule.all - function resetFilter() { - walletSectionInst.activityController.updateFilter() + function resetActivityData() { + walletSectionInst.activityController.resetActivityData() } // TODO remove all these by linking chainId for networks and activity using LeftJoinModel @@ -160,7 +160,7 @@ QtObject { walletSectionInst.activityController.loadMoreItems() } - function updateTransactionFilter() { + function updateTransactionFilterIfDirty() { if (transactionActivityStatus.isFilterDirty) walletSectionInst.activityController.updateFilter() } diff --git a/ui/imports/shared/views/HistoryView.qml b/ui/imports/shared/views/HistoryView.qml index 55589fe7af0..bb665dea60b 100644 --- a/ui/imports/shared/views/HistoryView.qml +++ b/ui/imports/shared/views/HistoryView.qml @@ -66,7 +66,7 @@ ColumnLayout { target: RootStore.transactionActivityStatus enabled: root.visible function onIsFilterDirtyChanged() { - RootStore.updateTransactionFilter() + RootStore.updateTransactionFilterIfDirty() } function onFilterChainsChanged() { WalletStores.RootStore.currentActivityFiltersStore.updateCollectiblesModel() @@ -92,13 +92,7 @@ ColumnLayout { property bool firstSectionHeaderLoaded: false - property double lastRefreshTime readonly property int maxSecondsBetweenRefresh: 3 - function refreshData() { - RootStore.resetFilter() - d.lastRefreshTime = Date.now() - newTransactions.visible = false - } property string openTxDetailsHash @@ -279,8 +273,8 @@ ColumnLayout { text: qsTr("New transactions") - visible: false - onClicked: d.refreshData() + visible: RootStore.newDataAvailable && !RootStore.loadingHistoryTransactions + onClicked: RootStore.resetActivityData() icon.name: "arrow-up" @@ -288,41 +282,6 @@ ColumnLayout { type: StatusButton.Primary size: StatusBaseButton.Size.Tiny } - - Connections { - target: RootStore - - function onNewDataAvailableChanged() { - if (!d.lastRefreshTime || ((Date.now() - d.lastRefreshTime) > (1000 * d.maxSecondsBetweenRefresh))) { - // Show `New transactions` button only when filter is applied - if (!WalletStores.RootStore.currentActivityFiltersStore.filtersSet) { - d.refreshData() - return - } - - newTransactions.visible = RootStore.newDataAvailable - return - } - - if (showRefreshButtonTimer.running) { - if (!RootStore.newDataAvailable) { - showRefreshButtonTimer.stop() - newTransactions.visible = false - } - } else if(RootStore.newDataAvailable) { - showRefreshButtonTimer.start() - } - } - } - - Timer { - id: showRefreshButtonTimer - - interval: 2000 - running: false - repeat: false - onTriggered: newTransactions.visible = RootStore.newDataAvailable - } } StatusMenu { diff --git a/vendor/status-go b/vendor/status-go index 577db512c69..a63d33e04a7 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 577db512c6953bec5a82b961cd901c58851362dd +Subproject commit a63d33e04a79b632cf6b0034fb251e419eeccd82