feat(@desktop/wallet): integrate collectibles search backend

Closes #13922
This commit is contained in:
Dario Gabriel Lipicar 2024-06-19 09:43:56 -03:00
parent 0645ed4712
commit 99af24c39e
No known key found for this signature in database
GPG Key ID: 9625E9494309D203
19 changed files with 1702 additions and 12 deletions

View File

@ -0,0 +1,28 @@
import app/modules/shared_modules/collectibles_search/controller as collectibles_search_c
import app/modules/shared_modules/collections_search/controller as collections_search_c
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method getCollectiblesSearchController*(self: AccessInterface): collectibles_search_c.Controller {.base.} =
raise newException(ValueError, "No implementation available")
method getCollectionsSearchController*(self: AccessInterface): collections_search_c.Controller {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim.
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,71 @@
import NimQml
import ./io_interface, ./view
import ../io_interface as delegate_interface
import app/global/global_singleton
import app/core/eventemitter
import app/modules/shared_modules/collectibles_search/controller as collectibles_c
import app/modules/shared_modules/collections_search/controller as collections_c
import app_service/service/network/service as network_service
import backend/collectibles as backend_collectibles
export io_interface
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
events: EventEmitter
view: View
collectiblesController: collectibles_c.Controller
collectionsController: collections_c.Controller
moduleLoaded: bool
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
networkService: network_service.Service
): Module =
result = Module()
result.delegate = delegate
result.events = events
let collectiblesController = collectibles_c.newController(
requestId = int32(backend_collectibles.CollectiblesRequestID.Search),
networkService = networkService,
events = events
)
result.collectiblesController = collectiblesController
let collectionsController = collections_c.newController(
requestId = int32(backend_collectibles.CollectiblesRequestID.Search),
networkService = networkService,
events = events
)
result.collectionsController = collectionsController
result.view = newView(result)
result.moduleLoaded = false
method delete*(self: Module) =
self.view.delete
self.collectionsController.delete
self.collectiblesController.delete
method load*(self: Module) =
singletonInstance.engine.setRootContextProperty("walletSectionCollectiblesSearch", newQVariant(self.view))
self.view.load()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
method viewDidLoad*(self: Module) =
self.moduleLoaded = true
self.delegate.searchCollectiblesModuleDidLoad()
method getCollectiblesSearchController*(self: Module): collectibles_c.Controller =
return self.collectiblesController
method getCollectionsSearchController*(self: Module): collections_c.Controller =
return self.collectionsController

View File

@ -0,0 +1,36 @@
import NimQml, sequtils, strutils
import ./io_interface
import app/modules/shared_modules/collectibles_search/controller as collectibles_search_c
import app/modules/shared_modules/collections_search/controller as collections_search_c
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
collectiblesSearchController: collectibles_search_c.Controller
collectionsSearchController: collections_search_c.Controller
proc delete*(self: View) =
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.QObject.setup
result.delegate = delegate
result.collectiblesSearchController = delegate.getCollectiblesSearchController()
result.collectionsSearchController = delegate.getCollectionsSearchController()
proc load*(self: View) =
self.delegate.viewDidLoad()
proc getCollectiblesSearchController(self: View): QVariant {.slot.} =
return newQVariant(self.collectiblesSearchController)
QtProperty[QVariant] collectiblesSearchController:
read = getCollectiblesSearchController
proc getCollectionsSearchController(self: View): QVariant {.slot.} =
return newQVariant(self.collectionsSearchController)
QtProperty[QVariant] collectionsSearchController:
read = getCollectionsSearchController

View File

@ -49,6 +49,9 @@ method allTokensModuleDidLoad*(self: AccessInterface) {.base.} =
method allCollectiblesModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method searchCollectiblesModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method collectiblesModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -7,6 +7,7 @@ import ../io_interface as delegate_interface
import ./accounts/module as accounts_module
import ./all_tokens/module as all_tokens_module
import ./all_collectibles/module as all_collectibles_module
import ./collectibles_search/module as collectibles_search_module
import ./assets/module as assets_module
import ./saved_addresses/module as saved_addresses_module
import ./buy_sell_crypto/module as buy_sell_crypto_module
@ -65,6 +66,7 @@ type
accountsModule: accounts_module.AccessInterface
allTokensModule: all_tokens_module.AccessInterface
allCollectiblesModule: all_collectibles_module.AccessInterface
collectiblesSearchModule: collectibles_search_module.AccessInterface
assetsModule: assets_module.AccessInterface
sendModule: send_module.AccessInterface
savedAddressesModule: saved_addresses_module.AccessInterface
@ -131,6 +133,7 @@ proc newModule*(
result.allTokensModule = all_tokens_module.newModule(result, events, tokenService, walletAccountService, settingsService, communityTokensService)
let allCollectiblesModule = all_collectibles_module.newModule(result, events, collectibleService, networkService, walletAccountService, settingsService)
result.allCollectiblesModule = allCollectiblesModule
result.collectiblesSearchModule = collectibles_search_module.newModule(result, events, networkService)
result.assetsModule = assets_module.newModule(result, events, walletAccountService, networkService, tokenService,
currencyService)
result.sendModule = send_module.newModule(result, events, walletAccountService, networkService, currencyService,
@ -176,6 +179,7 @@ method delete*(self: Module) =
self.accountsModule.delete
self.allTokensModule.delete
self.allCollectiblesModule.delete
self.collectiblesSearchModule.delete
self.assetsModule.delete
self.savedAddressesModule.delete
self.buySellCryptoModule.delete
@ -335,6 +339,7 @@ method load*(self: Module) =
self.accountsModule.load()
self.allTokensModule.load()
self.allCollectiblesModule.load()
self.collectiblesSearchModule.load()
self.assetsModule.load()
self.savedAddressesModule.load()
self.buySellCryptoModule.load()
@ -356,6 +361,9 @@ proc checkIfModuleDidLoad(self: Module) =
if(not self.allCollectiblesModule.isLoaded()):
return
if(not self.collectiblesSearchModule.isLoaded()):
return
if(not self.assetsModule.isLoaded()):
return
@ -397,6 +405,9 @@ method allTokensModuleDidLoad*(self: Module) =
method allCollectiblesModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method searchCollectiblesModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method collectiblesModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad()

View File

@ -5,6 +5,9 @@ import app_service/service/currency/dto as currency_dto
import ../main/wallet_section/accounts/item as wallet_accounts_item
import ../main/wallet_section/send/account_item as wallet_send_account_item
import backend/collectibles_types as collectibles
import app_service/common/types
proc currencyAmountToItem*(amount: float64, format: CurrencyFormatDto) : CurrencyAmount =
return newCurrencyAmount(
amount,
@ -72,3 +75,11 @@ proc walletAccountToWalletSendAccountItem*(w: WalletAccountDto, chainIds: seq[in
w.testPreferredChainIds,
canSend=w.walletType != "watch" and (w.operable==AccountFullyOperable or w.operable==AccountPartiallyOperable)
)
proc contractTypeToTokenType*(contractType : ContractType): TokenType =
case contractType:
of ContractType.ContractTypeUnknown: return TokenType.Unknown
of ContractType.ContractTypeERC20: return TokenType.ERC20
of ContractType.ContractTypeERC721: return TokenType.ERC721
of ContractType.ContractTypeERC1155: return TokenType.ERC1155
else: return TokenType.Unknown

View File

@ -0,0 +1,216 @@
import NimQml, json, stew/shims/strformat, sequtils, strutils, stint, strutils
import options
import backend/collectibles as backend
import collectible_trait_model
import app_service/common/types
import app/modules/shared/wallet_utils
# It is used to display a detailed collectibles entry in the QML UI
QtObject:
type
CollectiblesDataEntry* = ref object of QObject
id: backend.CollectibleUniqueID
data: backend.Collectible
traits: TraitModel
generatedId: string
generatedCollectionId: string
tokenType: TokenType
proc setup(self: CollectiblesDataEntry) =
self.QObject.setup
proc delete*(self: CollectiblesDataEntry) =
self.QObject.delete
proc setData(self: CollectiblesDataEntry, data: backend.Collectible) =
self.data = data
self.traits = newTraitModel()
if isSome(data.collectibleData) and isSome(data.collectibleData.get().traits):
let traits = data.collectibleData.get().traits.get()
self.traits.setItems(traits)
self.setup()
proc `$`*(self: CollectiblesDataEntry): string =
return fmt"""CollectiblesDataEntry(
id:{self.id},
data:{self.data},
traits:{self.traits},
generatedId:{self.generatedId},
generatedCollectionId:{self.generatedCollectionId},
tokenType:{self.tokenType},
)"""
proc hasCollectibleData(self: CollectiblesDataEntry): bool =
return self.data != nil and isSome(self.data.collectibleData)
proc getCollectibleData(self: CollectiblesDataEntry): backend.CollectibleData =
return self.data.collectibleData.get()
proc getChainID*(self: CollectiblesDataEntry): int {.slot.} =
return self.id.contractID.chainID
QtProperty[int] chainId:
read = getChainID
proc getContractAddress*(self: CollectiblesDataEntry): string {.slot.} =
return self.id.contractID.address
QtProperty[string] contractAddress:
read = getContractAddress
proc getTokenID*(self: CollectiblesDataEntry): UInt256 =
return self.id.tokenID
proc getTokenIDAsString*(self: CollectiblesDataEntry): string {.slot.} =
return self.getTokenID().toString()
QtProperty[string] tokenId:
read = getTokenIDAsString
# Unique ID to identify collectible, generated by us
proc getID*(self: CollectiblesDataEntry): backend.CollectibleUniqueID =
return self.id
proc getIDAsString*(self: CollectiblesDataEntry): string =
return self.generatedId
# Unique ID to identify collection, generated by us
proc getCollectionID*(self: CollectiblesDataEntry): backend.ContractID =
return self.id.contractID
proc getCollectionIDAsString*(self: CollectiblesDataEntry): string =
return self.generatedCollectionId
proc nameChanged*(self: CollectiblesDataEntry) {.signal.}
proc getName*(self: CollectiblesDataEntry): string {.slot.} =
if self.hasCollectibleData():
result = self.data.collectibleData.get().name
if result == "":
result = "#" & self.getTokenIDAsString()
QtProperty[string] name:
read = getName
notify = nameChanged
proc imageURLChanged*(self: CollectiblesDataEntry) {.signal.}
proc getImageURL*(self: CollectiblesDataEntry): string {.slot.} =
if not self.hasCollectibleData() or isNone(self.getCollectibleData().imageUrl):
return ""
return self.getCollectibleData().imageUrl.get()
QtProperty[string] imageUrl:
read = getImageURL
notify = imageURLChanged
proc getOriginalMediaURL(self: CollectiblesDataEntry): string =
if not self.hasCollectibleData() or isNone(self.getCollectibleData().animationUrl):
return ""
return self.getCollectibleData().animationUrl.get()
proc mediaURLChanged*(self: CollectiblesDataEntry) {.signal.}
proc getMediaURL*(self: CollectiblesDataEntry): string {.slot.} =
result = self.getOriginalMediaURL()
if result == "":
result = self.getImageURL()
QtProperty[string] mediaUrl:
read = getMediaURL
notify = mediaURLChanged
proc getOriginalMediaType(self: CollectiblesDataEntry): string =
if not self.hasCollectibleData() or isNone(self.getCollectibleData().animationMediaType):
return ""
return self.getCollectibleData().animationMediaType.get()
proc mediaTypeChanged*(self: CollectiblesDataEntry) {.signal.}
proc getMediaType*(self: CollectiblesDataEntry): string {.slot.} =
result = self.getOriginalMediaType()
if result == "":
result = "image"
QtProperty[string] mediaType:
read = getMediaType
notify = mediaTypeChanged
proc backgroundColorChanged*(self: CollectiblesDataEntry) {.signal.}
proc getBackgroundColor*(self: CollectiblesDataEntry): string {.slot.} =
var color = "transparent"
if self.hasCollectibleData() and isSome(self.getCollectibleData().backgroundColor):
let backgroundColor = self.getCollectibleData().backgroundColor.get()
if backgroundColor != "":
color = "#" & backgroundColor
return color
QtProperty[string] backgroundColor:
read = getBackgroundColor
notify = backgroundColorChanged
proc descriptionChanged*(self: CollectiblesDataEntry) {.signal.}
proc getDescription*(self: CollectiblesDataEntry): string {.slot.} =
if not self.hasCollectibleData() or isNone(self.getCollectibleData().description):
return ""
return self.getCollectibleData().description.get()
QtProperty[string] description:
read = getDescription
notify = descriptionChanged
proc traitsChanged*(self: CollectiblesDataEntry) {.signal.}
proc getTraits*(self: CollectiblesDataEntry): QVariant {.slot.} =
return newQVariant(self.traits)
QtProperty[QVariant] traits:
read = getTraits
notify = traitsChanged
proc tokenTypeChanged*(self: CollectiblesDataEntry) {.signal.}
proc getTokenType*(self: CollectiblesDataEntry): int {.slot.} =
return self.tokenType.int
QtProperty[int] tokenType:
read = getTokenType
notify = tokenTypeChanged
proc updateDataIfSameID*(self: CollectiblesDataEntry, update: backend.Collectible): bool =
if self.id != update.id:
return false
self.setData(update)
# Notify changes for all properties
self.nameChanged()
self.imageUrlChanged()
self.mediaUrlChanged()
self.mediaTypeChanged()
self.backgroundColorChanged()
self.descriptionChanged()
self.traitsChanged()
return true
proc newCollectiblesDataFullEntry*(data: backend.Collectible): CollectiblesDataEntry =
new(result, delete)
result.id = data.id
result.setData(data)
result.generatedId = result.id.toString()
result.generatedCollectionId = result.id.contractID.toString()
result.tokenType = contractTypeToTokenType(data.contractType.get())
result.setup()
proc newCollectiblesDataBasicEntry*(id: backend.CollectibleUniqueID): CollectiblesDataEntry =
new(result, delete)
result.id = id
result.traits = newTraitModel()
result.generatedId = result.id.toString()
result.generatedCollectionId = result.id.contractID.toString()
result.setup()
proc newCollectiblesDataEmptyEntry*(): CollectiblesDataEntry =
let id = backend.CollectibleUniqueID(
contractID: backend.ContractID(
chainID: 0,
address: ""
),
tokenID: stint.u256(0)
)
return newCollectiblesDataBasicEntry(id)

View File

@ -0,0 +1,240 @@
import NimQml, Tables, strutils, stew/shims/strformat, sequtils, stint, json
import logging
import ./collectibles_data_entry
import backend/collectibles as backend_collectibles
type
CollectibleRole* {.pure.} = enum
Uid = UserRole + 1,
ChainId
ContractAddress
TokenId
Name
ImageUrl
MediaUrl
MediaType
BackgroundColor
CollectionUid
QtObject:
type
Model* = ref object of QAbstractListModel
items: seq[CollectiblesDataEntry]
hasMore: bool
proc delete(self: Model) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: Model) =
self.QAbstractListModel.setup
proc newModel*(): Model =
new(result, delete)
result.setup
result.items = @[]
result.hasMore = true
proc `$`*(self: Model): string =
for i in 0 ..< self.items.len:
result &= fmt"""[{i}]:({$self.items[i]})"""
proc countChanged(self: Model) {.signal.}
proc getCount*(self: Model): int {.slot.} =
return self.items.len
QtProperty[int] count:
read = getCount
notify = countChanged
proc hasMoreChanged*(self: Model) {.signal.}
proc getHasMore*(self: Model): bool {.slot.} =
self.hasMore
QtProperty[bool] hasMore:
read = getHasMore
notify = hasMoreChanged
proc setHasMore(self: Model, hasMore: bool) =
if hasMore == self.hasMore:
return
self.hasMore = hasMore
self.hasMoreChanged()
method canFetchMore*(self: Model, parent: QModelIndex): bool =
return self.hasMore
proc loadMoreItems(self: Model) {.signal.}
proc loadMore*(self: Model) {.slot.} =
self.loadMoreItems()
method fetchMore*(self: Model, parent: QModelIndex) =
self.loadMore()
method rowCount*(self: Model, index: QModelIndex = nil): int =
return self.getCount()
method roleNames(self: Model): Table[int, string] =
{
CollectibleRole.Uid.int:"uid",
CollectibleRole.ChainId.int:"chainId",
CollectibleRole.ContractAddress.int:"contractAddress",
CollectibleRole.TokenId.int:"tokenId",
CollectibleRole.Name.int:"name",
CollectibleRole.MediaUrl.int:"mediaUrl",
CollectibleRole.MediaType.int:"mediaType",
CollectibleRole.ImageUrl.int:"imageUrl",
CollectibleRole.BackgroundColor.int:"backgroundColor",
CollectibleRole.CollectionUid.int:"collectionUid"
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.getCount()):
return
let enumRole = role.CollectibleRole
if index.row < self.items.len:
let item = self.items[index.row]
case enumRole:
of CollectibleRole.Uid:
result = newQVariant(item.getIDAsString())
of CollectibleRole.ChainId:
result = newQVariant(item.getChainID())
of CollectibleRole.ContractAddress:
result = newQVariant(item.getContractAddress())
of CollectibleRole.TokenId:
result = newQVariant(item.getTokenIDAsString())
of CollectibleRole.Name:
result = newQVariant(item.getName())
of CollectibleRole.MediaUrl:
result = newQVariant(item.getMediaURL())
of CollectibleRole.MediaType:
result = newQVariant(item.getMediaType())
of CollectibleRole.ImageUrl:
result = newQVariant(item.getImageURL())
of CollectibleRole.BackgroundColor:
result = newQVariant(item.getBackgroundColor())
of CollectibleRole.CollectionUid:
result = newQVariant(item.getCollectionIDAsString())
proc resetCollectibleItems(self: Model, newItems: seq[CollectiblesDataEntry] = @[]) =
self.beginResetModel()
self.items = newItems
self.endResetModel()
self.countChanged()
proc appendCollectibleItems(self: Model, newItems: seq[CollectiblesDataEntry]) =
if len(newItems) == 0:
return
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
# Start after the current last real item
let startIdx = self.items.len
# End at the new last real item
let endIdx = startIdx + newItems.len - 1
self.beginInsertRows(parentModelIndex, startIdx, endIdx)
self.items.insert(newItems, startIdx)
self.endInsertRows()
self.countChanged()
proc removeCollectibleItem(self: Model, idx: int) =
if idx < 0 or idx >= self.items.len:
return
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginRemoveRows(parentModelIndex, idx, idx)
self.items.delete(idx)
self.endRemoveRows()
self.countChanged()
proc updateCollectibleItems(self: Model, newItems: seq[CollectiblesDataEntry]) =
if len(self.items) == 0:
# Current list is empty, just replace with new list
self.resetCollectibleItems(newItems)
return
if len(newItems) == 0:
# New list is empty, just remove all items
self.resetCollectibleItems()
return
var newTable = initTable[string, int](len(newItems))
for i in 0 ..< len(newItems):
newTable[newItems[i].getIDAsString()] = i
# Needs to be built in sequential index order
var oldIndicesToRemove: seq[int] = @[]
for idx in 0 ..< len(self.items):
let uid = self.items[idx].getIDAsString()
if not newTable.hasKey(uid):
# Item in old list but not in new -> Must remove
oldIndicesToRemove.add(idx)
else:
# Item both in old and new lists -> Nothing to do in the current list,
# remove from the new list so it only holds new items.
newTable.del(uid)
if len(oldIndicesToRemove) > 0:
var removedItems = 0
for idx in oldIndicesToRemove:
let updatedIdx = idx - removedItems
self.removeCollectibleItem(updatedIdx)
removedItems += 1
self.countChanged()
var newItemsToAdd: seq[CollectiblesDataEntry] = @[]
for uid, idx in newTable:
newItemsToAdd.add(newItems[idx])
self.appendCollectibleItems(newItemsToAdd)
proc getItems*(self: Model): seq[CollectiblesDataEntry] =
return self.items
proc getItemById*(self: Model, id: string): CollectiblesDataEntry =
for item in self.items:
if(cmpIgnoreCase(item.getIDAsString(), id) == 0):
return item
return nil
proc setItems*(self: Model, newItems: seq[CollectiblesDataEntry], offset: int, hasMore: bool) =
if offset == 0:
self.resetCollectibleItems(newItems)
elif offset != self.getCount():
error "invalid offset"
return
else:
self.appendCollectibleItems(newItems)
self.setHasMore(hasMore)
# Checks the diff between the current list and the new list, appends new items,
# removes missing items.
# We assume the order of the items in the input could change, and we don't care
# about the order of the items in the model.
proc updateItems*(self: Model, newItems: seq[CollectiblesDataEntry]) =
self.updateCollectibleItems(newItems)
self.setHasMore(false)
proc itemsDataUpdated(self: Model) {.signal.}
proc updateItemsData*(self: Model, updates: seq[backend_collectibles.Collectible]) =
var anyUpdated = false
for i in countdown(self.items.high, 0):
let entry = self.items[i]
for j in countdown(updates.high, 0):
let update = updates[j]
if entry.updateDataIfSameID(update):
let index = self.createIndex(i, 0, nil)
defer: index.delete
self.dataChanged(index, index)
anyUpdated = true
break
if anyUpdated:
self.itemsDataUpdated()

View File

@ -6,8 +6,7 @@ import collectible_trait_model
import collectible_ownership_model
import app_service/service/community_tokens/dto/community_token
import app_service/common/types
const invalidTimestamp* = high(int)
import app/modules/shared/wallet_utils
# Additional data needed to build an Entry, which is
# not included in the backend data and needs to be
@ -373,14 +372,6 @@ QtObject:
self.communityImageChanged()
return true
proc contractTypeToTokenType(contractType : ContractType): TokenType =
case contractType:
of ContractType.ContractTypeUnknown: return TokenType.Unknown
of ContractType.ContractTypeERC20: return TokenType.ERC20
of ContractType.ContractTypeERC721: return TokenType.ERC721
of ContractType.ContractTypeERC1155: return TokenType.ERC1155
else: return TokenType.Unknown
proc newCollectibleDetailsFullEntry*(data: backend.Collectible, extradata: ExtraData): CollectiblesEntry =
new(result, delete)
result.id = data.id

View File

@ -0,0 +1,140 @@
import NimQml, json, stew/shims/strformat, sequtils, strutils, strutils
import options
import backend/collectibles_types as backend
import app_service/common/types
import app/modules/shared/wallet_utils
QtObject:
type
CollectionsDataEntry* = ref object of QObject
id: backend.ContractID
data: backend.Collection
generatedId: string
tokenType: TokenType
proc setup(self: CollectionsDataEntry) =
self.QObject.setup
proc delete*(self: CollectionsDataEntry) =
self.QObject.delete
proc setData(self: CollectionsDataEntry, data: backend.Collection) =
self.data = data
self.setup()
proc `$`*(self: CollectionsDataEntry): string =
return fmt"""CollectionsDataEntry(
id:{self.id},
data:{self.data},
generatedId:{self.generatedId},
tokenType:{self.tokenType},
)"""
proc hasCollectionData(self: CollectionsDataEntry): bool =
return self.data != nil and isSome(self.data.collectionData)
proc getCollectionData(self: CollectionsDataEntry): backend.CollectionData =
return self.data.collectionData.get()
proc getChainID*(self: CollectionsDataEntry): int {.slot.} =
return self.id.chainID
QtProperty[int] chainId:
read = getChainID
proc getContractAddress*(self: CollectionsDataEntry): string {.slot.} =
return self.id.address
QtProperty[string] contractAddress:
read = getContractAddress
# Unique ID to identify collection, generated by us
proc getID*(self: CollectionsDataEntry): backend.ContractID =
return self.id
proc getIDAsString*(self: CollectionsDataEntry): string =
return self.generatedId
proc nameChanged*(self: CollectionsDataEntry) {.signal.}
proc getName*(self: CollectionsDataEntry): string {.slot.} =
if self.hasCollectionData():
result = self.getCollectionData().name
if result == "":
result = self.getIDAsString()
QtProperty[string] name:
read = getName
notify = nameChanged
proc imageURLChanged*(self: CollectionsDataEntry) {.signal.}
proc getImageURL*(self: CollectionsDataEntry): string {.slot.} =
if not self.hasCollectionData():
return ""
return self.getCollectionData().imageUrl
QtProperty[string] imageUrl:
read = getImageURL
notify = imageURLChanged
proc slugChanged*(self: CollectionsDataEntry) {.signal.}
proc getSlug*(self: CollectionsDataEntry): string {.slot.} =
if not self.hasCollectionData():
return ""
return self.getCollectionData().slug
QtProperty[string] collectionSlug:
read = getCollectionSlug
notify = collectionSlugChanged
proc tokenTypeChanged*(self: CollectionsDataEntry) {.signal.}
proc getTokenType*(self: CollectionsDataEntry): int {.slot.} =
return self.tokenType.int
QtProperty[int] tokenType:
read = getTokenType
notify = tokenTypeChanged
proc communityIdChanged*(self: CollectionsDataEntry) {.signal.}
proc getCommunityId*(self: CollectionsDataEntry): string {.slot.} =
if not self.hasCollectionData():
return ""
return self.data.communityId
QtProperty[string] communityId:
read = getCommunityId
notify = communityIdChanged
proc updateDataIfSameID*(self: CollectionsDataEntry, update: backend.Collection): bool =
if self.id != update.id:
return false
self.setData(update)
# Notify changes for all properties
self.nameChanged()
self.slugChanged()
self.imageUrlChanged()
return true
proc newCollectionsDataFullEntry*(data: backend.Collection): CollectionsDataEntry =
new(result, delete)
result.id = data.id
result.setData(data)
result.generatedId = result.id.toString()
result.tokenType = contractTypeToTokenType(data.contractType)
result.setup()
proc newCollectionsDataBasicEntry*(id: backend.ContractID): CollectionsDataEntry =
new(result, delete)
result.id = id
result.generatedId = result.id.toString()
result.setup()
proc newCollectionsDataEmptyEntry*(): CollectionsDataEntry =
let id = backend.ContractID(
chainID: 0,
address: ""
)
return newCollectionsDataBasicEntry(id)

View File

@ -0,0 +1,230 @@
import NimQml, Tables, strutils, stew/shims/strformat, sequtils, stint, json
import logging
import ./collections_data_entry
import backend/collectibles_types as backend_collectibles
type
CollectionRole* {.pure.} = enum
# ID roles
Uid = UserRole + 1,
ChainId
ContractAddress
TokenType
# Metadata roles
Name
Slug
ImageUrl
QtObject:
type
Model* = ref object of QAbstractListModel
items: seq[CollectionsDataEntry]
hasMore: bool
proc delete(self: Model) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: Model) =
self.QAbstractListModel.setup
proc newModel*(): Model =
new(result, delete)
result.setup
result.items = @[]
result.hasMore = true
proc `$`*(self: Model): string =
for i in 0 ..< self.items.len:
result &= fmt"""[{i}]:({$self.items[i]})"""
proc countChanged(self: Model) {.signal.}
proc getCount*(self: Model): int {.slot.} =
return self.items.len
QtProperty[int] count:
read = getCount
notify = countChanged
proc hasMoreChanged*(self: Model) {.signal.}
proc getHasMore*(self: Model): bool {.slot.} =
self.hasMore
QtProperty[bool] hasMore:
read = getHasMore
notify = hasMoreChanged
proc setHasMore*(self: Model, hasMore: bool) =
if hasMore == self.hasMore:
return
self.hasMore = hasMore
self.hasMoreChanged()
method canFetchMore*(self: Model, parent: QModelIndex): bool =
return self.hasMore
proc loadMoreItems(self: Model) {.signal.}
proc loadMore*(self: Model) {.slot.} =
self.loadMoreItems()
method fetchMore*(self: Model, parent: QModelIndex) =
self.loadMore()
method rowCount*(self: Model, index: QModelIndex = nil): int =
return self.getCount()
method roleNames(self: Model): Table[int, string] =
{
CollectionRole.Uid.int:"uid",
CollectionRole.ChainId.int:"chainId",
CollectionRole.ContractAddress.int:"contractAddress",
CollectionRole.TokenType.int:"tokenType",
CollectionRole.Name.int:"name",
CollectionRole.Slug.int:"slug",
CollectionRole.ImageUrl.int:"imageUrl"
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.getCount()):
return
let enumRole = role.CollectionRole
if index.row < self.items.len:
let item = self.items[index.row]
case enumRole:
of CollectionRole.Uid:
result = newQVariant(item.getIDAsString())
of CollectionRole.ChainId:
result = newQVariant(item.getChainID())
of CollectionRole.ContractAddress:
result = newQVariant(item.getContractAddress())
of CollectionRole.Name:
result = newQVariant(item.getName())
of CollectionRole.ImageUrl:
result = newQVariant(item.getImageURL())
of CollectionRole.Slug:
result = newQVariant(item.getSlug())
of CollectionRole.TokenType:
result = newQVariant(item.getTokenType())
proc resetItems(self: Model, newItems: seq[CollectionsDataEntry] = @[]) =
self.beginResetModel()
self.items = newItems
self.endResetModel()
self.countChanged()
proc appendItems(self: Model, newItems: seq[CollectionsDataEntry]) =
if len(newItems) == 0:
return
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
# Start after the current last real item
let startIdx = self.items.len
# End at the new last real item
let endIdx = startIdx + newItems.len - 1
self.beginInsertRows(parentModelIndex, startIdx, endIdx)
self.items.insert(newItems, startIdx)
self.endInsertRows()
self.countChanged()
proc removeItem(self: Model, idx: int) =
if idx < 0 or idx >= self.items.len:
return
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginRemoveRows(parentModelIndex, idx, idx)
self.items.delete(idx)
self.endRemoveRows()
self.countChanged()
proc updateCollectionItems(self: Model, newItems: seq[CollectionsDataEntry]) =
if len(self.items) == 0:
# Current list is empty, just replace with new list
self.resetItems(newItems)
return
if len(newItems) == 0:
# New list is empty, just remove all items
self.resetItems()
return
var newTable = initTable[string, int](len(newItems))
for i in 0 ..< len(newItems):
newTable[newItems[i].getIDAsString()] = i
# Needs to be built in sequential index order
var oldIndicesToRemove: seq[int] = @[]
for idx in 0 ..< len(self.items):
let uid = self.items[idx].getIDAsString()
if not newTable.hasKey(uid):
# Item in old list but not in new -> Must remove
oldIndicesToRemove.add(idx)
else:
# Item both in old and new lists -> Nothing to do in the current list,
# remove from the new list so it only holds new items.
newTable.del(uid)
if len(oldIndicesToRemove) > 0:
var removedItems = 0
for idx in oldIndicesToRemove:
let updatedIdx = idx - removedItems
self.removeItem(updatedIdx)
removedItems += 1
self.countChanged()
var newItemsToAdd: seq[CollectionsDataEntry] = @[]
for uid, idx in newTable:
newItemsToAdd.add(newItems[idx])
self.appendItems(newItemsToAdd)
proc getItems*(self: Model): seq[CollectionsDataEntry] =
return self.items
proc getItemById*(self: Model, id: string): CollectionsDataEntry =
for item in self.items:
if(cmpIgnoreCase(item.getIDAsString(), id) == 0):
return item
return nil
proc setItems*(self: Model, newItems: seq[CollectionsDataEntry], offset: int, hasMore: bool) =
if offset == 0:
self.resetItems(newItems)
elif offset != self.getCount():
error "invalid offset"
return
else:
self.appendItems(newItems)
self.setHasMore(hasMore)
# Checks the diff between the current list and the new list, appends new items,
# removes missing items.
# We assume the order of the items in the input could change, and we don't care
# about the order of the items in the model.
proc updateItems*(self: Model, newItems: seq[CollectionsDataEntry]) =
self.updateCollectionItems(newItems)
self.setHasMore(false)
proc itemsDataUpdated(self: Model) {.signal.}
proc updateItemsData*(self: Model, updates: seq[backend_collectibles.Collection]) =
var anyUpdated = false
for i in countdown(self.items.high, 0):
let entry = self.items[i]
for j in countdown(updates.high, 0):
let update = updates[j]
if entry.updateDataIfSameID(update):
let index = self.createIndex(i, 0, nil)
defer: index.delete
self.dataChanged(index, index)
anyUpdated = true
break
if anyUpdated:
self.itemsDataUpdated()

View File

@ -0,0 +1,218 @@
import NimQml, std/json, sequtils, sugar, strutils
import logging
import app/modules/shared_models/collectibles_data_entry
import app/modules/shared_models/collectibles_data_model
import events_handler
import app/core/eventemitter
import backend/collectibles as backend_collectibles
import app_service/service/network/service as network_service
const FETCH_BATCH_COUNT_DEFAULT = 50
QtObject:
type
Controller* = ref object of QObject
networkService: network_service.Service
model: Model
fetchFromStart: bool
eventsHandler: EventsHandler
requestId: int32
chainId: int
contractAddress: string
text: string
isFetching: bool
isError: bool
previousCursor: string
nextCursor: string
provider: string
dataType: backend_collectibles.CollectibleDataType
proc setup(self: Controller) =
self.QObject.setup
proc delete*(self: Controller) =
self.QObject.delete
proc mustFetchFromStart(self: Controller): bool =
return self.previousCursor == ""
proc hasMore(self: Controller): bool =
return self.mustFetchFromStart() or self.nextCursor != ""
proc getModel*(self: Controller): Model =
return self.model
proc getModelAsVariant*(self: Controller): QVariant {.slot.} =
return newQVariant(self.model)
QtProperty[QVariant] model:
read = getModelAsVariant
proc isFetchingChanged(self: Controller) {.signal.}
proc getIsFetching*(self: Controller): bool {.slot.} =
self.isFetching
QtProperty[bool] isFetching:
read = getIsFetching
notify = isFetchingChanged
proc setIsFetching*(self: Controller, value: bool) =
if value == self.isFetching:
return
self.isFetching = value
self.isFetchingChanged()
proc isErrorChanged(self: Controller) {.signal.}
proc getIsError*(self: Controller): bool {.slot.} =
self.isError
QtProperty[bool] isError:
read = getIsError
notify = isErrorChanged
proc setIsError*(self: Controller, value: bool) =
if value == self.isError:
return
self.isError = value
self.isErrorChanged()
proc loadMoreItems(self: Controller) =
if self.getIsFetching():
return
if not self.hasMore():
return
self.setIsFetching(true)
self.setIsError(false)
let params = backend_collectibles.SearchCollectiblesParams(
chainID: self.chainId,
contractAddress: self.contractAddress,
text: self.text,
cursor: self.nextCursor,
limit: FETCH_BATCH_COUNT_DEFAULT,
providerID: self.provider
)
let response = backend_collectibles.searchCollectiblesAsync(self.requestId, params, self.dataType)
if response.error != nil:
self.setIsFetching(false)
self.setIsError(true)
error "error searching collections: ", response.error
proc resetModel(self: Controller) {.slot.} =
self.model.setItems(@[], 0, true)
self.previousCursor = ""
self.nextCursor = ""
self.provider = ""
self.loadMoreItems()
proc onModelLoadMoreItems(self: Controller) {.slot.} =
self.loadMoreItems()
proc textChanged(self: Controller) {.signal.}
proc getText*(self: Controller): string {.slot.} =
self.text
QtProperty[string] text:
read = getText
notify = textChanged
proc chainIdChanged(self: Controller) {.signal.}
proc getChainId*(self: Controller): int {.slot.} =
self.chainId
QtProperty[int] chainId:
read = getChainId
notify = chainIdChanged
proc contractAddressChanged(self: Controller) {.signal.}
proc getContractAddress*(self: Controller): string {.slot.} =
self.contractAddress
QtProperty[string] contractAddress:
read = getContractAddress
notify = contractAddressChanged
proc search*(self: Controller, chainId: int, contractAddress: string, text: string) {.slot.} =
if chainId == self.chainId and contractAddress == self.contractAddress and text == self.text:
return
self.chainId = chainId
self.contractAddress = contractAddress
self.text = text
self.resetModel()
proc processSearchCollectiblesResponse(self: Controller, response: JsonNode) =
let res = fromJson(response, backend_collectibles.SearchCollectiblesResponse)
let isError = res.errorCode != backend_collectibles.ErrorCodeSuccess
if isError:
error "error fetching collectibles entries: ", res.errorCode
self.setIsError(true)
self.setIsFetching(false)
return
if self.nextCursor != res.previousCursor:
error "nextCursor mismatch"
self.setIsError(true)
self.setIsFetching(false)
return
self.previousCursor = res.previousCursor
self.nextCursor = res.nextCursor
self.provider = res.provider
let items = res.collectibles.map(header => (block:
newCollectiblesDataFullEntry(header)
))
self.model.setItems(items, self.model.getCount(), self.hasMore())
self.setIsFetching(false)
proc setupEventHandlers(self: Controller) =
self.eventsHandler.onSearchCollectiblesDone(proc (jsonObj: JsonNode) =
self.processSearchCollectiblesResponse(jsonObj)
)
proc newController*(
requestId: int32,
networkService: network_service.Service,
events: EventEmitter,
dataType: backend_collectibles.CollectibleDataType = backend_collectibles.CollectibleDataType.Details,
): Controller =
new(result, delete)
result.requestId = requestId
result.dataType = dataType
result.networkService = networkService
result.model = newModel()
result.isFetching = false
result.isError = false
result.chainId = 0
result.contractAddress = ""
result.text = ""
result.previousCursor = ""
result.nextCursor = ""
result.provider = ""
result.eventsHandler = newEventsHandler(result.requestId, events)
result.setup()
result.setupEventHandlers()
signalConnect(result.model, "loadMoreItems()", result, "onModelLoadMoreItems()")

View File

@ -0,0 +1,54 @@
import NimQml, std/json, sequtils, strutils, options
import tables
import app/core/eventemitter
import app/core/signals/types
import backend/collectibles as backend_collectibles
type EventCallbackProc = proc (eventObject: JsonNode)
# EventsHandler responsible for catching collectibles related backend events and reporting them
QtObject:
type
EventsHandler* = ref object of QObject
events: EventEmitter
eventHandlers: Table[string, EventCallbackProc]
requestId: int32
proc setup(self: EventsHandler) =
self.QObject.setup
proc delete*(self: EventsHandler) =
self.QObject.delete
proc onSearchCollectiblesDone*(self: EventsHandler, handler: EventCallbackProc) =
self.eventHandlers[backend_collectibles.eventSearchCollectiblesDone] = handler
proc handleApiEvents(self: EventsHandler, e: Args) =
var data = WalletSignal(e)
if data.requestId.isSome and data.requestId.get() != self.requestId:
return
if self.eventHandlers.hasKey(data.eventType):
let callback = self.eventHandlers[data.eventType]
let responseJson = parseJson(data.message)
callback(responseJson)
proc newEventsHandler*(requestId: int32, events: EventEmitter): EventsHandler =
new(result, delete)
result.requestId = requestId
result.events = events
result.eventHandlers = initTable[string, EventCallbackProc]()
result.setup()
# Register for wallet events
let eventsHandler = result
result.events.on(SignalType.Wallet.event, proc(e: Args) =
eventsHandler.handleApiEvents(e)
)

View File

@ -0,0 +1,206 @@
import NimQml, std/json, sequtils, sugar, strutils
import logging
import app/modules/shared_models/collections_data_entry
import app/modules/shared_models/collections_data_model
import events_handler
import app/core/eventemitter
import backend/collectibles as backend_collectibles
import app_service/service/network/service as network_service
const FETCH_BATCH_COUNT_DEFAULT = 50
QtObject:
type
Controller* = ref object of QObject
networkService: network_service.Service
model: Model
fetchFromStart: bool
eventsHandler: EventsHandler
requestId: int32
chainId: int
text: string
isFetching: bool
isError: bool
previousCursor: string
nextCursor: string
provider: string
dataType: backend_collectibles.CollectionDataType
proc setup(self: Controller) =
self.QObject.setup
proc delete*(self: Controller) =
self.QObject.delete
proc mustFetchFromStart(self: Controller): bool =
return self.previousCursor == ""
proc hasMore(self: Controller): bool =
return self.mustFetchFromStart() or self.nextCursor != ""
proc getModel*(self: Controller): Model =
return self.model
proc getModelAsVariant*(self: Controller): QVariant {.slot.} =
return newQVariant(self.model)
QtProperty[QVariant] model:
read = getModelAsVariant
proc isFetchingChanged(self: Controller) {.signal.}
proc getIsFetching*(self: Controller): bool {.slot.} =
self.isFetching
QtProperty[bool] isFetching:
read = getIsFetching
notify = isFetchingChanged
proc setIsFetching*(self: Controller, value: bool) =
if value == self.isFetching:
return
self.isFetching = value
self.isFetchingChanged()
proc isErrorChanged(self: Controller) {.signal.}
proc getIsError*(self: Controller): bool {.slot.} =
self.isError
QtProperty[bool] isError:
read = getIsError
notify = isErrorChanged
proc setIsError*(self: Controller, value: bool) =
if value == self.isError:
return
self.isError = value
self.isErrorChanged()
proc loadMoreItems(self: Controller) =
if self.getIsFetching():
return
if not self.hasMore():
return
self.setIsFetching(true)
self.setIsError(false)
let params = backend_collectibles.SearchCollectionsParams(
chainID: self.chainId,
text: self.text,
cursor: self.nextCursor,
limit: FETCH_BATCH_COUNT_DEFAULT,
providerID: self.provider
)
let response = backend_collectibles.searchCollectionsAsync(self.requestId, params, self.dataType)
if response.error != nil:
self.setIsFetching(false)
self.setIsError(true)
error "error searching collections: ", response.error
proc resetModel(self: Controller) {.slot.} =
self.model.setItems(@[], 0, true)
self.previousCursor = ""
self.nextCursor = ""
self.provider = ""
self.loadMoreItems()
proc textChanged(self: Controller) {.signal.}
proc getText*(self: Controller): string {.slot.} =
self.text
QtProperty[string] text:
read = getText
notify = textChanged
proc chainIdChanged(self: Controller) {.signal.}
proc getChainId*(self: Controller): int {.slot.} =
self.chainId
QtProperty[int] chainId:
read = getChainId
notify = chainIdChanged
proc search*(self: Controller, chainId: int, text: string) {.slot.} =
if chainId == self.chainId and text == self.text:
return
self.chainId = chainId
self.text = text
self.resetModel()
proc onModelLoadMoreItems(self: Controller) {.slot.} =
self.loadMoreItems()
proc processSearchCollectionsResponse(self: Controller, response: JsonNode) =
let res = fromJson(response, backend_collectibles.SearchCollectionsResponse)
let isError = res.errorCode != backend_collectibles.ErrorCodeSuccess
if isError:
error "error fetching collections entries: ", res.errorCode
self.setIsError(true)
self.setIsFetching(false)
return
if self.nextCursor != res.previousCursor:
error "nextCursor mismatch"
self.setIsError(true)
self.setIsFetching(false)
return
self.previousCursor = res.previousCursor
self.nextCursor = res.nextCursor
self.provider = res.provider
let items = res.collections.map(data => (block:
newCollectionsDataFullEntry(data)
))
self.model.setItems(items, self.model.getCount(), self.hasMore())
self.setIsFetching(false)
proc setupEventHandlers(self: Controller) =
self.eventsHandler.onSearchCollectionsDone(proc (jsonObj: JsonNode) =
self.processSearchCollectionsResponse(jsonObj)
)
proc newController*(
requestId: int32,
networkService: network_service.Service,
events: EventEmitter,
dataType: backend_collectibles.CollectionDataType = backend_collectibles.CollectionDataType.Details,
): Controller =
new(result, delete)
result.requestId = requestId
result.dataType = dataType
result.networkService = networkService
result.model = newModel()
result.isFetching = false
result.isError = false
result.chainId = 0
result.text = ""
result.previousCursor = ""
result.nextCursor = ""
result.provider = ""
result.eventsHandler = newEventsHandler(result.requestId, events)
result.setup()
result.setupEventHandlers()
signalConnect(result.model, "loadMoreItems()", result, "onModelLoadMoreItems()")

View File

@ -0,0 +1,54 @@
import NimQml, std/json, sequtils, strutils, options
import tables
import app/core/eventemitter
import app/core/signals/types
import backend/collectibles as backend_collectibles
type EventCallbackProc = proc (eventObject: JsonNode)
# EventsHandler responsible for catching collectibles related backend events and reporting them
QtObject:
type
EventsHandler* = ref object of QObject
events: EventEmitter
eventHandlers: Table[string, EventCallbackProc]
requestId: int32
proc setup(self: EventsHandler) =
self.QObject.setup
proc delete*(self: EventsHandler) =
self.QObject.delete
proc onSearchCollectionsDone*(self: EventsHandler, handler: EventCallbackProc) =
self.eventHandlers[backend_collectibles.eventSearchCollectionsDone] = handler
proc handleApiEvents(self: EventsHandler, e: Args) =
var data = WalletSignal(e)
if data.requestId.isSome and data.requestId.get() != self.requestId:
return
if self.eventHandlers.hasKey(data.eventType):
let callback = self.eventHandlers[data.eventType]
let responseJson = parseJson(data.message)
callback(responseJson)
proc newEventsHandler*(requestId: int32, events: EventEmitter): EventsHandler =
new(result, delete)
result.requestId = requestId
result.events = events
result.eventHandlers = initTable[string, EventCallbackProc]()
result.setup()
# Register for wallet events
let eventsHandler = result
result.events.on(SignalType.Wallet.event, proc(e: Args) =
eventsHandler.handleApiEvents(e)
)

View File

@ -14,6 +14,7 @@ type
ProfileShowcase
WalletSend
AllCollectibles
Search
# Declared in services/wallet/collectibles/service.go
const eventCollectiblesOwnershipUpdateStarted*: string = "wallet-collectibles-ownership-update-started"
@ -26,6 +27,8 @@ const eventCollectiblesDataUpdated*: string = "wallet-collectibles-data-updated"
const eventOwnedCollectiblesFilteringDone*: string = "wallet-owned-collectibles-filtering-done"
const eventGetCollectiblesDetailsDone*: string = "wallet-get-collectibles-details-done"
const eventGetCollectionSocialsDone*: string ="wallet-get-collection-socials-done"
const eventSearchCollectiblesDone*: string ="wallet-search-collectibles-done"
const eventSearchCollectionsDone*: string ="wallet-search-collections-done"
const invalidTimestamp*: int = -1
@ -67,6 +70,22 @@ type
collectibles*: seq[Collectible]
errorCode*: ErrorCode
# Mirrors services/wallet/collectibles/service.go SearchCollectiblesResponse
SearchCollectiblesResponse* = object
collectibles*: seq[Collectible]
nextCursor*: string
previousCursor*: string
provider*: string
errorCode*: ErrorCode
# Mirrors services/wallet/collectibles/service.go SearchCollectionsResponse
SearchCollectionsResponse* = object
collections*: seq[Collection]
nextCursor*: string
previousCursor*: string
provider*: string
errorCode*: ErrorCode
CommunityCollectiblesReceivedPayload* = object
collectibles*: seq[Collectible]
@ -90,6 +109,23 @@ type
FetchCriteria* = object
fetchType*: FetchType
maxCacheAgeSeconds*: int
# see status-go/services/wallet/collectibles/manager.go SearchCollectionsParams
SearchCollectionsParams* = object
chainID*: int
text*: string
cursor*: string
limit*: int
providerID*: string
# see status-go/services/wallet/collectibles/manager.go SearchCollectiblesParams
SearchCollectiblesParams* = object
chainID*: int
contractAddress*: string
text*: string
cursor*: string
limit*: int
providerID*: string
# CollectibleOwnershipState
proc `$`*(self: OwnershipStatus): string =
@ -176,6 +212,13 @@ proc `%`*(t: CollectibleDataType): JsonNode {.inline.} =
proc `%`*(t: ref CollectibleDataType): JsonNode {.inline.} =
return %(t[])
# CollectionDataType
proc `%`*(t: CollectionDataType): JsonNode {.inline.} =
result = %(t.int)
proc `%`*(t: ref CollectionDataType): JsonNode {.inline.} =
return %(t[])
# FetchCriteria
proc `$`*(self: FetchCriteria): string =
return fmt"""FetchCriteria(
@ -191,6 +234,47 @@ proc `%`*(t: FetchCriteria): JsonNode {.inline.} =
proc `%`*(t: ref FetchCriteria): JsonNode {.inline.} =
return %(t[])
#SearchCollectionsParams
proc `$`*(self: SearchCollectionsParams): string =
return fmt"""SearchCollectionsParams(
chainID:{self.chainID},
text:{self.text},
cursor:{self.cursor},
limit:{self.limit},
providerID:{self.providerID}
"""
proc `%`*(t: SearchCollectionsParams): JsonNode {.inline.} =
result = newJObject()
result["chain_id"] = %t.chainID
result["text"] = %t.text
result["cursor"] = %t.cursor
result["limit"] = %t.limit
result["provider_id"] = %t.providerID
proc `%`*(t: ref SearchCollectionsParams): JsonNode {.inline.} =
return %(t[])
#SearchCollectiblesParams
proc `$`*(self: SearchCollectiblesParams): string =
return fmt"""SearchCollectiblesParams(
chainID:{self.chainID},
contractAddress:{self.contractAddress},
text:{self.text},
cursor:{self.cursor},
limit:{self.limit},
providerID:{self.providerID}
"""
proc `%`*(t: SearchCollectiblesParams): JsonNode {.inline.} =
result = newJObject()
result["chain_id"] = %t.chainID
result["contract_address"] = %t.contractAddress
result["text"] = %t.text
result["cursor"] = %t.cursor
result["limit"] = %t.limit
result["provider_id"] = %t.providerID
# Responses
proc fromJson*(e: JsonNode, T: typedesc[GetOwnedCollectiblesResponse]): GetOwnedCollectiblesResponse {.inline.} =
var collectibles: seq[Collectible]
@ -228,6 +312,32 @@ proc fromJson*(e: JsonNode, T: typedesc[GetCollectiblesByUniqueIDResponse]): Get
errorCode: ErrorCode(e["errorCode"].getInt())
)
proc fromJson*(e: JsonNode, T: typedesc[SearchCollectiblesResponse]): SearchCollectiblesResponse {.inline.} =
var collectibles: seq[Collectible] = @[]
for item in e["collectibles"].getElems():
collectibles.add(fromJson(item, Collectible))
result = T(
collectibles: collectibles,
nextCursor: e["nextCursor"].getStr(),
previousCursor: e["previousCursor"].getStr(),
provider: e["provider"].getStr(),
errorCode: ErrorCode(e["errorCode"].getInt())
)
proc fromJson*(e: JsonNode, T: typedesc[SearchCollectionsResponse]): SearchCollectionsResponse {.inline.} =
var collections: seq[Collection] = @[]
for item in e["collections"].getElems():
collections.add(fromJson(item, Collection))
result = T(
collections: collections,
nextCursor: e["nextCursor"].getStr(),
previousCursor: e["previousCursor"].getStr(),
provider: e["provider"].getStr(),
errorCode: ErrorCode(e["errorCode"].getInt())
)
proc fromJson*(e: JsonNode, T: typedesc[CommunityCollectiblesReceivedPayload]): CommunityCollectiblesReceivedPayload {.inline.} =
var collectibles: seq[Collectible] = @[]
for item in e.getElems():
@ -278,6 +388,16 @@ rpc(fetchCollectionSocialsAsync, "wallet"):
rpc(refetchOwnedCollectibles, "wallet"):
discard
rpc(searchCollectionsAsync, "wallet"):
requestId: int32
params: SearchCollectionsParams
dataType: CollectionDataType
rpc(searchCollectiblesAsync, "wallet"):
requestId: int32
params: SearchCollectiblesParams
dataType: CollectibleDataType
rpc(updateCollectiblePreferences, "accounts"):
preferences: seq[CollectiblePreferences]

View File

@ -22,10 +22,14 @@ type
contractID*: ContractID
tokenID*: UInt256
# see status-go/services/wallet/collectibles/service.go CollectibleDataType
# see status-go/services/wallet/collectibles/types.go CollectibleDataType
CollectibleDataType* {.pure.} = enum
UniqueID, Header, Details, CommunityHeader
# see status-go/services/wallet/collectibles/types.go CollectionDataType
CollectionDataType* {.pure.} = enum
ContractID, Details
# Mirrors services/wallet/thirdparty/collectible_types.go CollectibleTrait
CollectibleTrait* = ref object of RootObj
trait_type*: string
@ -73,6 +77,13 @@ type
latestTxHash*: Option[string]
receivedAmount*: Option[float64]
contractType*: Option[ContractType]
Collection* = ref object of RootObj
dataType*: CollectionDataType
id* : ContractID
communityId*: string
contractType*: ContractType
collectionData*: Option[CollectionData]
CollectionSocials* = ref object of RootObj
website*: string
@ -409,6 +420,33 @@ proc toIds(self: seq[Collectible]): seq[CollectibleUniqueID] =
for c in self:
result.add(c.id)
# Collection
proc `$`*(self: Collection): string =
return fmt"""Collection(
dataType:{self.dataType},
id:{self.id},
contractType:{self.contractType},
collectionData:{self.collectionData},
communityId:{self.communityId}
)"""
proc fromJson*(t: JsonNode, T: typedesc[Collection]): Collection {.inline.} =
result = Collection()
result.dataType = t["data_type"].getInt().CollectionDataType
result.id = fromJson(t["id"], ContractID)
let collectionDataNode = t{"collection_data"}
if collectionDataNode != nil and collectionDataNode.kind != JNull:
result.collectionData = some(fromJson(collectionDataNode, CollectionData))
else:
result.collectionData = none(CollectionData)
result.communityId = t["community_id"].getStr
result.contractType = ContractType(t["contract_type"].getInt())
proc toIds(self: seq[Collection]): seq[ContractID] =
result = @[]
for c in self:
result.add(c.id)
# CollectibleBalance
proc `$`*(self: CollectibleBalance): string =
return fmt"""CollectibleBalance(

View File

@ -12,6 +12,7 @@ QtObject {
/* PRIVATE: Modules used to get data from backend */
readonly property var _allCollectiblesModule: !!walletSectionAllCollectibles ? walletSectionAllCollectibles : null
readonly property var _collectiblesSearchModule : !!walletSectionCollectiblesSearch ? walletSectionCollectiblesSearch : null
/* This list contains the complete list of collectibles with separate
entry per collectible which has a unique [network + contractAddress + tokenID] */
@ -103,4 +104,26 @@ QtObject {
function getDetailedCollectible(chainId, contractAddress, tokenId) {
walletSection.collectibleDetailsController.getDetailedCollectible(chainId, contractAddress, tokenId)
}
/* The following are used to search for collections */
readonly property var _collectionsSearchController: !!root._collectiblesSearchModule ? root._collectiblesSearchModule.collectionsSearchController : null
function searchCollections(chainId, text) {
root._collectionsSearchController.search(chainId, text)
}
/* The following are used to display the collection search results */
readonly property var collectionsSearchResults: !!root._collectionsSearchController ? root._collectionsSearchController.model : null
readonly property bool areCollectionsSearchResultsFetching: !!root._collectionsSearchController ? root._collectionsSearchController.isFetching : true
readonly property bool areCollectionsSearchResultsError: !!root._collectionsSearchController ? root._collectionsSearchController.isError : false
/* The following are used to search for collectibles */
readonly property var _collectiblesSearchController: !!root._collectiblesSearchModule ? root._collectiblesSearchModule.collectiblesSearchController : null
function searchCollectibles(chainId, contractAddress, text) {
root._collectiblesSearchController.search(chainId, contractAddress, text)
}
/* The following are used to display the collection search results */
readonly property var collectiblesSearchResults: !!root._collectiblesSearchController ? root._collectiblesSearchController.model : null
readonly property bool areCollectiblesSearchResultsFetching: !!root._collectiblesSearchController ? root._collectiblesSearchController.isFetching : true
readonly property bool areCollectiblesSearchResultsError: !!root._collectiblesSearchController ? root._collectiblesSearchController.isError : false
}

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 10cf2862086630625434f8b2239227dee7440858
Subproject commit 7d7af6249176ea32bd458c67bca4010444df6389