From 959cf579977e3184cef8ea3de18882003d3ad870 Mon Sep 17 00:00:00 2001 From: Stefan Date: Fri, 12 Jan 2024 19:40:51 -0600 Subject: [PATCH] feat(activity): WIP Prototype of displaying dynamic activity filter updates --- .../wallet_section/activity/controller.nim | 58 ++++++++++++++--- .../main/wallet_section/activity/entry.nim | 20 +++++- .../activity/events_handler.nim | 15 ++++- .../main/wallet_section/activity/model.nim | 12 ++++ src/backend/activity.nim | 65 +++++++++++++++++-- storybook/pages/TransactionDelegatePage.qml | 14 ++++ test/status-go/integration/helpers/helpers.go | 41 +++++++++--- .../wallet/activityfiltering_test.go | 18 +---- .../wallet/pendingtransactions_test.go | 25 ++++--- .../shared/controls/TransactionDelegate.qml | 28 +++++++- vendor/status-go | 2 +- 11 files changed, 244 insertions(+), 54 deletions(-) diff --git a/src/app/modules/main/wallet_section/activity/controller.nim b/src/app/modules/main/wallet_section/activity/controller.nim index 7df2f847823..6b41c471a98 100644 --- a/src/app/modules/main/wallet_section/activity/controller.nim +++ b/src/app/modules/main/wallet_section/activity/controller.nim @@ -60,6 +60,7 @@ QtObject: # call updateAssetsIdentities after updating chainIds chainIds: seq[int] + # TODO #12120: refactor to sessionId and use the eventsHandler's sessionId requestId: int32 collectiblesToTokenConverter: CollectiblesToTokenConverter @@ -100,7 +101,7 @@ QtObject: 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] = + proc backendToPresentation(self: Controller, backendEntities: seq[backend_activity.ActivityEntry], highlight: bool): seq[entry.ActivityEntry] = let amountToCurrencyConvertor = proc(amount: UInt256, symbol: string): CurrencyAmount = return currencyAmountToItem(self.currencyService.parseCurrencyValue(symbol, amount), self.currencyService.getCurrencyFormat(symbol)) @@ -112,7 +113,7 @@ QtObject: ae = entry.newMultiTransactionActivityEntry(backendEntry, extraData, amountToCurrencyConvertor) of SimpleTransaction, PendingTransaction: let extraData = self.buildTransactionExtraData(backendEntry) - ae = entry.newTransactionActivityEntry(backendEntry, self.addresses, extraData, amountToCurrencyConvertor) + ae = entry.newTransactionActivityEntry(backendEntry, self.addresses, extraData, amountToCurrencyConvertor, highlight) result.add(ae) proc fetchTxDetails*(self: Controller, entryIndex: int) {.slot.} = @@ -151,14 +152,14 @@ QtObject: error "error fetching activity entries: ", res.errorCode return - let entries = self.backendToPresentation(res.activities) + let entries = self.backendToPresentation(res.activities, false) self.model.setEntries(entries, res.offset, res.hasMore) if len(entries) > 0: self.eventsHandler.updateRelevantTimestamp(entries[len(entries) - 1].getTimestamp()) - proc updateFilter*(self: Controller) {.slot.} = + proc startFilterSession*(self: Controller) {.slot.} = self.status.setLoadingData(true) self.status.setIsFilterDirty(false) @@ -168,12 +169,38 @@ QtObject: 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) - if response.error != nil: - error "error fetching activity entries: ", response.error + let (sessionId, ok) = backend_activity.newActivityFilterSession(self.addresses, self.allAddressesSelected, seq[backend_activity.ChainId](self.chainIds), self.currentActivityFilter, FETCH_BATCH_COUNT_DEFAULT) + echo "@dd sessionId: ", sessionId, " ok: ", ok + if not ok: self.status.setLoadingData(false) return + self.eventsHandler.setSessionId(sessionId) + self.requestId = sessionId.int32 + + # TODO #12120: deprecated, replace with [start|stop]FilterSession + proc updateFilter*(self: Controller) {.slot.} = + if self.requestId >= 0: + let res = backend_activity.stopActivityFilterSession(self.requestId) + if res.error != nil: + error "error stopping the previous session of activity fitlering: ", res.error + + self.startFilterSession() + # 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) + # if response.error != nil: + # error "error fetching activity entries: ", response.error + # self.status.setLoadingData(false) + # return + proc loadMoreItems(self: Controller) {.slot.} = self.status.setLoadingData(true) @@ -241,6 +268,20 @@ QtObject: self.model.updateEntries(entries) ) + self.eventsHandler.onFilteringSessionUpdated(proc (jn: JsonNode) = + echo "@dd onFilteringSessionUpdated: ", pretty(jn) + if jn.kind != JObject: + error "expected an object" + + let res = fromJson(jn, backend_activity.SessionUpdate) + + echo "@dd onFilteringSessionUpdated newEntries: ", res.newEntries.len + + let entries = self.backendToPresentation(res.newEntries, true) + + self.model.updateEntries(entries) + ) + self.eventsHandler.onGetRecipientsDone(proc (jsonObj: JsonNode) = defer: self.status.setLoadingRecipients(false) let res = fromJson(jsonObj, backend_activity.GetRecipientsResponse) @@ -282,6 +323,7 @@ QtObject: self.status.setNewDataAvailable(true) ) + # TODO #12120: no need for hardcoded requestId with sessions proc newController*(requestId: int32, currencyService: currency_service.Service, tokenService: token_service.Service, @@ -289,7 +331,7 @@ QtObject: collectiblesConverter: CollectiblesToTokenConverter): Controller = new(result, delete) - result.requestId = requestId + result.requestId = -1 #requestId result.model = newModel() result.recipientsModel = newRecipientsModel() result.collectiblesModel = newCollectiblesModel() diff --git a/src/app/modules/main/wallet_section/activity/entry.nim b/src/app/modules/main/wallet_section/activity/entry.nim index 9a85a6308b2..a967e446513 100644 --- a/src/app/modules/main/wallet_section/activity/entry.nim +++ b/src/app/modules/main/wallet_section/activity/entry.nim @@ -33,6 +33,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 @@ -54,7 +57,7 @@ QtObject: ) 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, valueConvertor: AmountToCurrencyConvertor, highlight: bool): ActivityEntry = new(result, delete) result.valueConvertor = valueConvertor result.metadata = metadata @@ -65,6 +68,7 @@ QtObject: if result.isInTransactionType(): metadata.symbolIn.get("") else: metadata.symbolOut.get(""), ) result.noAmount = newCurrencyAmount() + result.highlight = highlight result.setup() @@ -273,3 +277,17 @@ QtObject: QtProperty[QVariant] amountCurrency: read = getAmountCurrency + + 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 \ No newline at end of file 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..3077e348fb5 100644 --- a/src/app/modules/main/wallet_section/activity/events_handler.nim +++ b/src/app/modules/main/wallet_section/activity/events_handler.nim @@ -21,11 +21,13 @@ QtObject: walletEventHandlers: Table[string, WalletEventCallbackProc] # Ignore events older than this relevantTimestamp + # TODO #12120: remove this after dropping individual events and only using incremental updates events relevantTimestamp: int subscribedAddresses: HashSet[string] subscribedChainIDs: HashSet[int] newDataAvailableFn: proc() + # TODO #12120: Optional[int], getter and setter requestId: int proc setup(self: EventsHandler) = @@ -34,12 +36,17 @@ QtObject: proc delete*(self: EventsHandler) = self.QObject.delete + # TODO #12120: replace with session update event proc onFilteringDone*(self: EventsHandler, handler: EventCallbackProc) = self.eventHandlers[backend_activity.eventActivityFilteringDone] = handler + # TODO #12120: replace with session update event 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 @@ -98,9 +105,10 @@ QtObject: # TODO #12120: Replace these specific events with incremental updates events self.walletEventHandlers[EventNewTransfers] = newDataAvailableCallback - self.walletEventHandlers[EventPendingTransactionUpdate] = newDataAvailableCallback + # self.walletEventHandlers[EventPendingTransactionUpdate] = newDataAvailableCallback self.walletEventHandlers[EventMTTransactionUpdate] = newDataAvailableCallback + # TODO #12120: no need for hardcoded requestId with sessions proc newEventsHandler*(requestId: int, events: EventEmitter): EventsHandler = new(result, delete) @@ -110,7 +118,7 @@ QtObject: result.subscribedAddresses = initHashSet[string]() result.subscribedChainIDs = initHashSet[int]() - result.requestId = requestId + result.requestId = -1 # requestId result.setup() @@ -134,3 +142,6 @@ QtObject: self.subscribedChainIDs.clear() for chainID in chainIDs: self.subscribedChainIDs.incl(chainID) + + proc setSessionId*(self: EventsHandler, sessionId: int) = + self.requestId = sessionId \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/activity/model.nim b/src/app/modules/main/wallet_section/activity/model.nim index 34526d052ab..ddfaf3044e3 100644 --- a/src/app/modules/main/wallet_section/activity/model.nim +++ b/src/app/modules/main/wallet_section/activity/model.nim @@ -101,6 +101,18 @@ QtObject: self.countChanged() self.setHasMore(hasMore) + # TODO #12120: add indexes + proc updateEntries*(self: Model, newEntries: seq[entry.ActivityEntry]) = + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + + self.beginInsertRows(parentModelIndex, 0, newEntries.len - 1) + self.entries = newEntries & self.entries + self.endInsertRows() + + self.countChanged() + # hasMore flag should not be change by updates + proc sameIdentity(e: entry.ActivityEntry, d: backend.Data): bool = let m = e.getMetadata() if m.getPayloadType() != d.payloadType: diff --git a/src/backend/activity.nim b/src/backend/activity.nim index 2819aab87e5..361c1e2676b 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 @@ -74,6 +76,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 +254,6 @@ proc `$`*(pt: ProtocolType): string {.inline.} = return "Hop" of Uniswap: return "Uniswap" - else: - return "" # Mirrors status-go/services/wallet/activity/activity.go TransferType type @@ -319,6 +330,7 @@ type ErrorCodeTaskCanceled, ErrorCodeFailed + # TODO #12120: Replace it with SessionUpdate # Mirrors services/wallet/activity/service.go FilterResponse FilterResponse* = object activities*: seq[ActivityEntry] @@ -326,6 +338,12 @@ type hasMore*: bool errorCode*: ErrorCode + # Mirrors services/wallet/activity/session.go SessionUpdate + SessionUpdate* = object + newEntries*: seq[ActivityEntry] + removed*: seq[Data] + updated*: seq[Data] + proc getPayloadType*(ae: ActivityEntry): PayloadType = return ae.payloadType @@ -423,8 +441,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: 0.u256, + amountIn: if data.amountIn.isSome: data.amountIn.get() else: 0.u256, tokenOut: data.tokenOut, tokenIn: data.tokenIn, symbolOut: data.symbolOut, @@ -478,6 +496,19 @@ proc fromJson*(e: JsonNode, T: typedesc[FilterResponse]): FilterResponse {.inlin errorCode: ErrorCode(e["errorCode"].getInt()) ) +proc fromJson*(e: JsonNode, T: typedesc[SessionUpdate]): SessionUpdate {.inline.} = + var newEntries: seq[ActivityEntry] + if e.hasKey("newEntries"): + let jsonEntries = e["newEntries"] + if jsonEntries.kind == JNull: + newEntries = @[] + elif jsonEntries.kind == JArray: + newEntries = fromJson(jsonEntries, seq[ActivityEntry]) + + result = T( + newEntries: newEntries + ) + rpc(filterActivityAsync, "wallet"): requestId: int32 addresses: seq[string] @@ -487,6 +518,32 @@ rpc(filterActivityAsync, "wallet"): offset: int limit: int +rpc(startActivityFilterSession, "wallet"): + addresses: seq[string] + allAddresses: bool + chainIds: seq[ChainId] + filter: ActivityFilter + count: int + +rpc(stopActivityFilterSession, "wallet"): + sessionId: int + +# returns (sessionId, success) +proc newActivityFilterSession*( + addresses: seq[string], + allAddresses: bool, + chainIds: seq[ChainId], + filter: ActivityFilter, + count: int, +): (int, bool) {.inline.} = + let res = startActivityFilterSession(addresses, allAddresses, chainIds, filter, count) + if res.error != nil: + error "error starting a new session of activity fitlering: ", res.error + return (-1, false) + var test: json.JsonNode + test = res.result + return (test.getInt(), true) + # see services/wallet/activity/service.go GetRecipientsResponse type GetRecipientsResponse* = object addresses*: seq[string] diff --git a/storybook/pages/TransactionDelegatePage.qml b/storybook/pages/TransactionDelegatePage.qml index 77ba4fd2c94..c95c1577d7d 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/status-go/integration/helpers/helpers.go b/test/status-go/integration/helpers/helpers.go index df3cdb1e609..caf8edacd36 100644 --- a/test/status-go/integration/helpers/helpers.go +++ b/test/status-go/integration/helpers/helpers.go @@ -108,10 +108,10 @@ func WaitForEvent(eventQueue chan GoEvent, eventName StatusGoEventName, timeout } // WaitForWalletEvents returns payloads corresponding to the given eventNames in the order they are received for duplicate events -func WaitForWalletEvents[T any](eventQueue chan GoEvent, eventNamesOrig []walletevent.EventType, timeout time.Duration) (payloads []*T, err error) { +func WaitForWalletEvents(eventQueue chan GoEvent, eventNamesOrig []walletevent.EventType, timeout time.Duration) (payloads [][]byte, err error) { var event *GoEvent - payloads = make([]*T, len(eventNamesOrig)) + payloads = make([][]byte, len(eventNamesOrig)) processed := make([]bool, len(eventNamesOrig)) processedCount := 0 @@ -126,7 +126,6 @@ func WaitForWalletEvents[T any](eventQueue chan GoEvent, eventNamesOrig []wallet return nil, errors.New("event payload is not a wallet event") } - var newPayload T foundIndex := -1 for i, eventName := range eventNamesOrig { if walletEvent.Type == eventName && !processed[i] { @@ -139,11 +138,7 @@ func WaitForWalletEvents[T any](eventQueue chan GoEvent, eventNamesOrig []wallet if foundIndex != -1 { if walletEvent.Message != "" { - err = json.Unmarshal([]byte(walletEvent.Message), &newPayload) - if err != nil { - return nil, err - } - payloads[foundIndex] = &newPayload + payloads[foundIndex] = []byte(walletEvent.Message) } else { payloads[foundIndex] = nil } @@ -154,12 +149,38 @@ func WaitForWalletEvents[T any](eventQueue chan GoEvent, eventNamesOrig []wallet } } +// WaitForWalletEvents returns payloads corresponding to the given eventNames in the order they are received for duplicate events +func WaitForWalletEventsGetMap(eventQueue chan GoEvent, eventNamesOrig []walletevent.EventType, timeout time.Duration) (payloads []map[string]interface{}, err error) { + bytePayloads, err := WaitForWalletEvents(eventQueue, eventNamesOrig, timeout) + if err != nil { + return nil, err + } + for _, payload := range bytePayloads { + newPayload := make(map[string]interface{}) + err = json.Unmarshal(payload, &newPayload) + if err != nil { + return nil, err + } + payloads = append(payloads, newPayload) + } + return payloads, nil +} + func WaitForWalletEvent[T any](eventQueue chan GoEvent, eventName walletevent.EventType, timeout time.Duration) (payload *T, err error) { - res, err := WaitForWalletEvents[T](eventQueue, []walletevent.EventType{eventName}, timeout) + res, err := WaitForWalletEvents(eventQueue, []walletevent.EventType{eventName}, timeout) + if err != nil { + return nil, err + } + if res[0] == nil { + return nil, nil + } + + newPayload := new(T) + err = json.Unmarshal(res[0], newPayload) if err != nil { return nil, err } - return res[0], nil + return newPayload, nil } func loginToAccount(hashedPassword, userFolder, nodeConfigJson string) error { diff --git a/test/status-go/integration/wallet/activityfiltering_test.go b/test/status-go/integration/wallet/activityfiltering_test.go index e4f65534928..1fa1e09e7ae 100644 --- a/test/status-go/integration/wallet/activityfiltering_test.go +++ b/test/status-go/integration/wallet/activityfiltering_test.go @@ -14,8 +14,6 @@ import ( "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/services/wallet/activity" "github.com/status-im/status-go/services/wallet/common" - "github.com/status-im/status-go/services/wallet/walletevent" - "github.com/status-im/status-go/transactions" ) // TestActivityIncrementalUpdates_NoFilterNewPendingTransactions tests that a pending transaction is created, then updated and finally deleted. @@ -27,12 +25,8 @@ func TestActivityIncrementalUpdates_NoFilterNewPendingTransactions(t *testing.T) require.NoError(t, err) // Confirm async filtering results - filterRes, err := helpers.WaitForWalletEvents[activity.FilterResponse]( - td.eventQueue, []walletevent.EventType{activity.EventActivityFilteringDone}, - 5*time.Second, - ) + res, err := helpers.WaitForWalletEvent[activity.FilterResponse](td.eventQueue, activity.EventActivityFilteringDone, 5*time.Second) require.NoError(t, err) - res := filterRes[0] require.Equal(t, activity.ErrorCodeSuccess, res.ErrorCode) require.Equal(t, 3, len(res.Activities)) @@ -43,14 +37,4 @@ func TestActivityIncrementalUpdates_NoFilterNewPendingTransactions(t *testing.T) update, err := helpers.WaitForWalletEvent[activity.SessionUpdate](td.eventQueue, activity.EventActivitySessionUpdated, 2*time.Second) require.NoError(t, err) require.Equal(t, 1, len(update.NewEntries)) - - // Step x: Trigger downloading of the new transaction ... - _, err = helpers.CallPrivateMethodWithTimeout("wallet_checkRecentHistoryForChainIDs", []interface{}{[]uint64{5}, []types.Address{td.sender.Address, td.recipient.Address}}, 2*time.Second) - require.NoError(t, err) - - // ... and wait for the new transaction download to trigger deletion from pending_transactions - updatePayload, err := helpers.WaitForWalletEvent[transactions.PendingTxUpdatePayload]( - td.eventQueue, transactions.EventPendingTransactionUpdate, 120*time.Second) - require.NoError(t, err) - require.Equal(t, true, updatePayload.Deleted) } diff --git a/test/status-go/integration/wallet/pendingtransactions_test.go b/test/status-go/integration/wallet/pendingtransactions_test.go index bae892e4630..b2d88c14bfd 100644 --- a/test/status-go/integration/wallet/pendingtransactions_test.go +++ b/test/status-go/integration/wallet/pendingtransactions_test.go @@ -23,21 +23,28 @@ func TestPendingTx_NotificationStatus(t *testing.T) { sendTransaction(t, td) - // Start history download ... - _, err := helpers.CallPrivateMethod("wallet_checkRecentHistoryForChainIDs", []interface{}{[]uint64{5}, []types.Address{td.sender.Address, td.recipient.Address}}) - require.NoError(t, err) - - // ... and wait for the new transaction download to trigger deletion from pending_transactions - updatePayloads, err := helpers.WaitForWalletEvents[transactions.PendingTxUpdatePayload]( + // Wait for transaction to be included in block + confirmationPayloads, err := helpers.WaitForWalletEventsGetMap( td.eventQueue, []walletevent.EventType{ transactions.EventPendingTransactionUpdate, - transactions.EventPendingTransactionUpdate, + transactions.EventPendingTransactionStatusChanged, }, 60*time.Second, ) require.NoError(t, err) + require.False(t, confirmationPayloads[0]["deleted"].(bool)) + require.Equal(t, transactions.Success, confirmationPayloads[1]["status"].(transactions.TxStatus)) + + // Start history download ... + _, err = helpers.CallPrivateMethod("wallet_checkRecentHistoryForChainIDs", []interface{}{[]uint64{5}, []types.Address{td.sender.Address, td.recipient.Address}}) + require.NoError(t, err) + + // ... and wait for the new transaction download to trigger deletion from pending_transactions + updatePayload, err := helpers.WaitForWalletEvent[transactions.PendingTxUpdatePayload]( + td.eventQueue, transactions.EventPendingTransactionUpdate, 60*time.Second, + ) + require.NoError(t, err) // Validate that we received both add and delete event - require.False(t, updatePayloads[0].Deleted) - require.True(t, updatePayloads[1].Deleted) + require.True(t, updatePayload.Deleted) } diff --git a/ui/imports/shared/controls/TransactionDelegate.qml b/ui/imports/shared/controls/TransactionDelegate.qml index c4f157f4a3b..8d052065dcd 100644 --- a/ui/imports/shared/controls/TransactionDelegate.qml +++ b/ui/imports/shared/controls/TransactionDelegate.qml @@ -160,7 +160,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 @@ -176,6 +176,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) { @@ -527,7 +530,12 @@ StatusListItem { rightPadding: 16 enabled: !loading loading: !isModelDataValid - color: sensor.containsMouse ? Theme.palette.baseColor5 : Style.current.transparent + color: { + if (bgColorAnimation.running) { + return d.animatedBgColor + } + return sensor.containsMouse ? Theme.palette.baseColor5 : Style.current.transparent + } statusListItemIcon.active: (loading || root.asset.name) asset { @@ -861,4 +869,20 @@ StatusListItem { } } ] + + ColorAnimation { + id: bgColorAnimation + + target: d + property: "animatedBgColor" + from: d.isLightTheme ? "#33869eff" : "#1a4360df" + to: "transparent" + duration: 1000 + alwaysRunToEnd: true + running: root.visible && modelData.highlight + + onStopped: { + modelData.doneHighlighting() + } + } } diff --git a/vendor/status-go b/vendor/status-go index 812910f087e..cabbbde5bb0 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 812910f087e0451cc702d2590cec3953bb8c3d14 +Subproject commit cabbbde5bb02000cbdb3c4e8e211de40db4b1471