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
This commit is contained in:
Stefan 2023-06-15 19:05:56 +02:00 committed by Stefan Dunca
parent fecf58102c
commit 2dd51e4584
4 changed files with 150 additions and 20 deletions

View File

@ -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
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)

View File

@ -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

View File

@ -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

View File

@ -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 {}
}
}