From 2dd51e4584ac39d8e392d6bed3ec786839181dcb Mon Sep 17 00:00:00 2001 From: Stefan Date: Thu, 15 Jun 2023 19:05:56 +0200 Subject: [PATCH] feat(wallet) add recipients API and integrate with filter Bumps status-go to include the new API. Add a new RecipientsModel to the activity controller. Extends the activity Controller with API to access and manage data in the RecipientsModel. Resolving addresses to names remains to be implemented. Updates #10025 --- .../wallet_section/activity/controller.nim | 34 ++++++- .../activity/recipients_model.nim | 95 +++++++++++++++++++ src/backend/activity.nim | 10 ++ ui/imports/shared/views/ActivityView.qml | 31 +++--- 4 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 src/app/modules/main/wallet_section/activity/recipients_model.nim diff --git a/src/app/modules/main/wallet_section/activity/controller.nim b/src/app/modules/main/wallet_section/activity/controller.nim index 8f41b966a0..4433b361dd 100644 --- a/src/app/modules/main/wallet_section/activity/controller.nim +++ b/src/app/modules/main/wallet_section/activity/controller.nim @@ -3,6 +3,7 @@ import tables import model import entry +import recipients_model import ../transactions/item import ../transactions/module as transactions_module @@ -21,11 +22,13 @@ proc toRef*[T](obj: T): ref T = result[] = obj const FETCH_BATCH_COUNT_DEFAULT = 10 +const FETCH_RECIPIENTS_BATCH_COUNT_DEFAULT = 2000 QtObject: type Controller* = ref object of QObject model: Model + recipientsModel: RecipientsModel transactionsModule: transactions_module.AccessInterface currentActivityFilter: backend_activity.ActivityFilter @@ -50,6 +53,12 @@ QtObject: QtProperty[QVariant] model: read = getModel + proc getRecipientsModel*(self: Controller): QVariant {.slot.} = + return newQVariant(self.recipientsModel) + + QtProperty[QVariant] recipientsModel: + read = getRecipientsModel + proc backendToPresentation(self: Controller, backendEntities: seq[backend_activity.ActivityEntry]): seq[entry.ActivityEntry] = var multiTransactionsIds: seq[int] = @[] var transactionIdentities: seq[backend.TransactionIdentity] = @[] @@ -186,6 +195,7 @@ QtObject: proc newController*(transactionsModule: transactions_module.AccessInterface, events: EventEmitter): Controller = new(result, delete) result.model = newModel() + result.recipientsModel = newRecipientsModel() result.transactionsModule = transactionsModule result.currentActivityFilter = backend_activity.getIncludeAllActivityFilter() result.events = events @@ -256,10 +266,10 @@ QtObject: addresses[i] = addressesJson[i].getStr() self.addresses = addresses - + proc setFilterAddresses*(self: Controller, addresses: seq[string]) = self.addresses = addresses - + proc setFilterToAddresses*(self: Controller, addresses: seq[string]) = self.currentActivityFilter.counterpartyAddresses = addresses @@ -291,4 +301,22 @@ QtObject: QtProperty[int] errorCode: read = getErrorCode - notify = errorCodeChanged \ No newline at end of file + notify = errorCodeChanged + + proc updateRecipientsModel*(self: Controller) {.slot.} = + let response = backend_activity.getAllRecipients(0, FETCH_RECIPIENTS_BATCH_COUNT_DEFAULT) + if response.error != nil: + error "error fetching recipients: ", response.error + return + + let result = json.to(response.result, backend_activity.GetAllRecipientsResponse) + self.recipientsModel.addAddresses(result.addresses, 0, result.hasMore) + + proc loadMoreRecipients(self: Controller) {.slot.} = + let response = backend_activity.getAllRecipients(self.recipientsModel.getCount(), FETCH_RECIPIENTS_BATCH_COUNT_DEFAULT) + if response.error != nil: + error "error fetching more recipient entries: ", response.error + return + + let result = json.to(response.result, backend_activity.GetAllRecipientsResponse) + self.recipientsModel.addAddresses(result.addresses, self.recipientsModel.getCount(), result.hasMore) \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/activity/recipients_model.nim b/src/app/modules/main/wallet_section/activity/recipients_model.nim new file mode 100644 index 0000000000..93d56a43d8 --- /dev/null +++ b/src/app/modules/main/wallet_section/activity/recipients_model.nim @@ -0,0 +1,95 @@ +import NimQml, Tables, strutils, strformat, sequtils, logging + +type + ModelRole {.pure.} = enum + AddressRole = UserRole + 1 + HasNameRole + +QtObject: + type + RecipientsModel* = ref object of QAbstractListModel + addresses*: seq[string] + # TODO: store resolved names here along addresses + hasMore: bool + + proc delete(self: RecipientsModel) = + self.QAbstractListModel.delete + + proc setup(self: RecipientsModel) = + self.QAbstractListModel.setup + + proc newRecipientsModel*(): RecipientsModel = + new(result, delete) + + result.addresses = @[] + # TODO: init data storage for the resolved names + + result.setup + + proc countChanged(self: RecipientsModel) {.signal.} + + proc getCount*(self: RecipientsModel): int {.slot.} = + self.addresses.len + + QtProperty[int] count: + read = getCount + notify = countChanged + + method rowCount(self: RecipientsModel, index: QModelIndex = nil): int = + return self.addresses.len + + method roleNames(self: RecipientsModel): Table[int, string] = + { + ModelRole.AddressRole.int:"address", + ModelRole.HasNameRole.int:"hasName" + }.toTable + + method data(self: RecipientsModel, index: QModelIndex, role: int): QVariant = + if (not index.isValid): + return + + if (index.row < 0 or index.row >= self.addresses.len): + return + + let address = self.addresses[index.row] + let enumRole = role.ModelRole + + case enumRole: + of ModelRole.AddressRole: + result = newQVariant(address) + of ModelRole.HasNameRole: + # TODO: check the resolved names storage + result = newQVariant(false) + + proc hasMoreChanged*(self: RecipientsModel) {.signal.} + + proc setHasMore(self: RecipientsModel, hasMore: bool) {.slot.} = + self.hasMore = hasMore + self.hasMoreChanged() + + proc addAddresses*(self: RecipientsModel, newAddresses: seq[string], offset: int, hasMore: bool) = + if offset == 0: + self.beginResetModel() + self.addresses = newAddresses + self.endResetModel() + else: + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + + if offset != self.addresses.len: + error "offset != self.addresses.len" + return + self.beginInsertRows(parentModelIndex, self.addresses.len, self.addresses.len + newAddresses.len - 1) + self.addresses.add(newAddresses) + self.endInsertRows() + + self.countChanged() + self.setHasMore(hasMore) + + proc getHasMore*(self: RecipientsModel): bool {.slot.} = + return self.hasMore + + QtProperty[bool] hasMore: + read = getHasMore + notify = hasMoreChanged + diff --git a/src/backend/activity.nim b/src/backend/activity.nim index 5dc83b4d21..e80f367f6d 100644 --- a/src/backend/activity.nim +++ b/src/backend/activity.nim @@ -155,6 +155,7 @@ type ErrorCodeFilterCanceled, ErrorCodeFilterFailed + # Mirrors services/wallet/activity/service.go FilterResponse FilterResponse* = object activities*: seq[ActivityEntry] offset*: int @@ -209,5 +210,14 @@ rpc(filterActivityAsync, "wallet"): addresses: seq[string] chainIds: seq[int] filter: ActivityFilter + offset: int + limit: int + +# see services/wallet/api.go GetAllRecipientsResponse +type GetAllRecipientsResponse* = object + addresses*: seq[string] + hasMore*: bool + +rpc(getAllRecipients, "wallet"): offset: int limit: int \ No newline at end of file diff --git a/ui/imports/shared/views/ActivityView.qml b/ui/imports/shared/views/ActivityView.qml index 77d0127b3d..3a6cd76e8f 100644 --- a/ui/imports/shared/views/ActivityView.qml +++ b/ui/imports/shared/views/ActivityView.qml @@ -33,6 +33,8 @@ Control { color: "white" } + Component.onCompleted: controller.updateRecipientsModel() + QtObject { id: d @@ -68,17 +70,6 @@ Control { } controller.setFilterStatus(JSON.stringify(statuses)) - // Counterparty addresses - var addresses = toAddressesInput.text.split(',') - if(addresses.length == 1 && addresses[0].trim() == "") { - addresses = [] - } else { - for (var i = 0; i < addresses.length; i++) { - addresses[i] = padHexAddress(addresses[i].trim()); - } - } - controller.setFilterToAddresses(JSON.stringify(addresses)) - // Involved addresses var addresses = addressesInput.text.split(',') if(addresses.length == 1 && addresses[0].trim() == "") { @@ -219,13 +210,17 @@ Control { delegate: ItemOnOffDelegate {} } - Label { text: qsTr("To addresses") } - TextField { - id: toAddressesInput + ComboBox { + id: toAddressesComboBox + model: controller.recipientsModel - Layout.fillWidth: true + displayText: qsTr("Select TO") + (controller.recipientsModel.hasMore ? qsTr(" ...") : "") - placeholderText: qsTr("0x1234, 0x5678, ...") + currentIndex: -1 + + delegate: ItemOnOffDelegate { + textRole: "address" + } } Button { @@ -305,6 +300,8 @@ Control { } component ItemOnOffDelegate: Item { + property string textRole: "text" + width: parent ? parent.width : 0 height: itemLayout.implicitHeight @@ -315,7 +312,7 @@ Control { anchors.fill: parent CheckBox { checked: entry.checked; onCheckedChanged: entry.checked = checked } - Label { text: entry.text } + Label { text: entry[textRole] } RowLayout {} } }