diff --git a/src/app/modules/main/wallet_section/activity/controller.nim b/src/app/modules/main/wallet_section/activity/controller.nim index 70ae6c70e77..02a2fc3acda 100644 --- a/src/app/modules/main/wallet_section/activity/controller.nim +++ b/src/app/modules/main/wallet_section/activity/controller.nim @@ -63,8 +63,6 @@ QtObject: # call updateAssetsIdentities after updating chainIds chainIds: seq[int] - requestId: int32 - proc setup(self: Controller) = self.QObject.setup @@ -89,31 +87,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, txID: string) {.slot.} = @@ -143,37 +119,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 @@ -181,7 +189,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 @@ -210,7 +218,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 @@ -254,6 +262,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) @@ -291,19 +317,13 @@ QtObject: error "Error converting activity entries: ", e.msg ) - self.eventsHandler.onNewDataAvailable(proc () = - self.status.setNewDataAvailable(true) - ) - - proc newController*(requestId: int32, - detailsController: details_controller.Controller, + proc newController*(detailsController: details_controller.Controller, 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() @@ -311,7 +331,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 @@ -452,7 +472,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 @@ -464,7 +484,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 @@ -483,8 +503,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 9a9a97b57e8..66bbe9e7ea2 100644 --- a/src/app/modules/main/wallet_section/module.nim +++ b/src/app/modules/main/wallet_section/module.nim @@ -50,11 +50,6 @@ logScope: export io_interface type - ActivityID = enum - History - Temporary0 - Temporary1 - Module* = ref object of io_interface.AccessInterface delegate: delegate_interface.AccessInterface events: EventEmitter @@ -145,28 +140,26 @@ proc newModule*( result.transactionService = transactionService result.activityDetailsController = activity_detailsc.newController(currencyService) result.activityController = activityc.newController( - int32(ActivityID.History), - result.activityDetailsController, + result.activityDetailsController, currencyService, tokenService, - savedAddressService, + savedAddressService, events) result.tmpActivityControllers = [ activityc.newController( - int32(ActivityID.Temporary0), result.activityDetailsController, currencyService, tokenService, savedAddressService, events), activityc.newController( - int32(ActivityID.Temporary1), result.activityDetailsController, 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 95e0b97a4d1..b9f89c511e5 100644 --- a/ui/imports/shared/controls/TransactionDelegate.qml +++ b/ui/imports/shared/controls/TransactionDelegate.qml @@ -158,7 +158,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 @@ -174,6 +174,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) { @@ -530,6 +533,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 { @@ -863,4 +872,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 9b5158ef42b..998636f6f63 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 5e25598ac6d..63da428ef28 100644 --- a/ui/imports/shared/views/HistoryView.qml +++ b/ui/imports/shared/views/HistoryView.qml @@ -64,7 +64,7 @@ ColumnLayout { target: RootStore.transactionActivityStatus enabled: root.visible function onIsFilterDirtyChanged() { - RootStore.updateTransactionFilter() + RootStore.updateTransactionFilterIfDirty() } function onFilterChainsChanged() { WalletStores.RootStore.currentActivityFiltersStore.updateCollectiblesModel() @@ -90,13 +90,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 @@ -272,8 +266,8 @@ ColumnLayout { text: qsTr("New transactions") - visible: false - onClicked: d.refreshData() + visible: RootStore.newDataAvailable && !RootStore.loadingHistoryTransactions + onClicked: RootStore.resetActivityData() icon.name: "arrow-up" @@ -281,41 +275,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 6522d52016e..e1c7c715aa3 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 6522d52016eae3d848c228a82d79dde902d69688 +Subproject commit e1c7c715aa3701791c5280e5c7869af7675bc1ee