feat(@desktop/wallet): integrate collectibles search backend
Closes #13922
This commit is contained in:
parent
0645ed4712
commit
99af24c39e
|
@ -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")
|
|
@ -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
|
|
@ -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
|
|
@ -49,6 +49,9 @@ method allTokensModuleDidLoad*(self: AccessInterface) {.base.} =
|
||||||
method allCollectiblesModuleDidLoad*(self: AccessInterface) {.base.} =
|
method allCollectiblesModuleDidLoad*(self: AccessInterface) {.base.} =
|
||||||
raise newException(ValueError, "No implementation available")
|
raise newException(ValueError, "No implementation available")
|
||||||
|
|
||||||
|
method searchCollectiblesModuleDidLoad*(self: AccessInterface) {.base.} =
|
||||||
|
raise newException(ValueError, "No implementation available")
|
||||||
|
|
||||||
method collectiblesModuleDidLoad*(self: AccessInterface) {.base.} =
|
method collectiblesModuleDidLoad*(self: AccessInterface) {.base.} =
|
||||||
raise newException(ValueError, "No implementation available")
|
raise newException(ValueError, "No implementation available")
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import ../io_interface as delegate_interface
|
||||||
import ./accounts/module as accounts_module
|
import ./accounts/module as accounts_module
|
||||||
import ./all_tokens/module as all_tokens_module
|
import ./all_tokens/module as all_tokens_module
|
||||||
import ./all_collectibles/module as all_collectibles_module
|
import ./all_collectibles/module as all_collectibles_module
|
||||||
|
import ./collectibles_search/module as collectibles_search_module
|
||||||
import ./assets/module as assets_module
|
import ./assets/module as assets_module
|
||||||
import ./saved_addresses/module as saved_addresses_module
|
import ./saved_addresses/module as saved_addresses_module
|
||||||
import ./buy_sell_crypto/module as buy_sell_crypto_module
|
import ./buy_sell_crypto/module as buy_sell_crypto_module
|
||||||
|
@ -65,6 +66,7 @@ type
|
||||||
accountsModule: accounts_module.AccessInterface
|
accountsModule: accounts_module.AccessInterface
|
||||||
allTokensModule: all_tokens_module.AccessInterface
|
allTokensModule: all_tokens_module.AccessInterface
|
||||||
allCollectiblesModule: all_collectibles_module.AccessInterface
|
allCollectiblesModule: all_collectibles_module.AccessInterface
|
||||||
|
collectiblesSearchModule: collectibles_search_module.AccessInterface
|
||||||
assetsModule: assets_module.AccessInterface
|
assetsModule: assets_module.AccessInterface
|
||||||
sendModule: send_module.AccessInterface
|
sendModule: send_module.AccessInterface
|
||||||
savedAddressesModule: saved_addresses_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)
|
result.allTokensModule = all_tokens_module.newModule(result, events, tokenService, walletAccountService, settingsService, communityTokensService)
|
||||||
let allCollectiblesModule = all_collectibles_module.newModule(result, events, collectibleService, networkService, walletAccountService, settingsService)
|
let allCollectiblesModule = all_collectibles_module.newModule(result, events, collectibleService, networkService, walletAccountService, settingsService)
|
||||||
result.allCollectiblesModule = allCollectiblesModule
|
result.allCollectiblesModule = allCollectiblesModule
|
||||||
|
result.collectiblesSearchModule = collectibles_search_module.newModule(result, events, networkService)
|
||||||
result.assetsModule = assets_module.newModule(result, events, walletAccountService, networkService, tokenService,
|
result.assetsModule = assets_module.newModule(result, events, walletAccountService, networkService, tokenService,
|
||||||
currencyService)
|
currencyService)
|
||||||
result.sendModule = send_module.newModule(result, events, walletAccountService, networkService, currencyService,
|
result.sendModule = send_module.newModule(result, events, walletAccountService, networkService, currencyService,
|
||||||
|
@ -176,6 +179,7 @@ method delete*(self: Module) =
|
||||||
self.accountsModule.delete
|
self.accountsModule.delete
|
||||||
self.allTokensModule.delete
|
self.allTokensModule.delete
|
||||||
self.allCollectiblesModule.delete
|
self.allCollectiblesModule.delete
|
||||||
|
self.collectiblesSearchModule.delete
|
||||||
self.assetsModule.delete
|
self.assetsModule.delete
|
||||||
self.savedAddressesModule.delete
|
self.savedAddressesModule.delete
|
||||||
self.buySellCryptoModule.delete
|
self.buySellCryptoModule.delete
|
||||||
|
@ -335,6 +339,7 @@ method load*(self: Module) =
|
||||||
self.accountsModule.load()
|
self.accountsModule.load()
|
||||||
self.allTokensModule.load()
|
self.allTokensModule.load()
|
||||||
self.allCollectiblesModule.load()
|
self.allCollectiblesModule.load()
|
||||||
|
self.collectiblesSearchModule.load()
|
||||||
self.assetsModule.load()
|
self.assetsModule.load()
|
||||||
self.savedAddressesModule.load()
|
self.savedAddressesModule.load()
|
||||||
self.buySellCryptoModule.load()
|
self.buySellCryptoModule.load()
|
||||||
|
@ -356,6 +361,9 @@ proc checkIfModuleDidLoad(self: Module) =
|
||||||
if(not self.allCollectiblesModule.isLoaded()):
|
if(not self.allCollectiblesModule.isLoaded()):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if(not self.collectiblesSearchModule.isLoaded()):
|
||||||
|
return
|
||||||
|
|
||||||
if(not self.assetsModule.isLoaded()):
|
if(not self.assetsModule.isLoaded()):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -397,6 +405,9 @@ method allTokensModuleDidLoad*(self: Module) =
|
||||||
method allCollectiblesModuleDidLoad*(self: Module) =
|
method allCollectiblesModuleDidLoad*(self: Module) =
|
||||||
self.checkIfModuleDidLoad()
|
self.checkIfModuleDidLoad()
|
||||||
|
|
||||||
|
method searchCollectiblesModuleDidLoad*(self: Module) =
|
||||||
|
self.checkIfModuleDidLoad()
|
||||||
|
|
||||||
method collectiblesModuleDidLoad*(self: Module) =
|
method collectiblesModuleDidLoad*(self: Module) =
|
||||||
self.checkIfModuleDidLoad()
|
self.checkIfModuleDidLoad()
|
||||||
|
|
||||||
|
|
|
@ -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/accounts/item as wallet_accounts_item
|
||||||
import ../main/wallet_section/send/account_item as wallet_send_account_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 =
|
proc currencyAmountToItem*(amount: float64, format: CurrencyFormatDto) : CurrencyAmount =
|
||||||
return newCurrencyAmount(
|
return newCurrencyAmount(
|
||||||
amount,
|
amount,
|
||||||
|
@ -72,3 +75,11 @@ proc walletAccountToWalletSendAccountItem*(w: WalletAccountDto, chainIds: seq[in
|
||||||
w.testPreferredChainIds,
|
w.testPreferredChainIds,
|
||||||
canSend=w.walletType != "watch" and (w.operable==AccountFullyOperable or w.operable==AccountPartiallyOperable)
|
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
|
|
@ -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)
|
|
@ -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()
|
|
@ -6,8 +6,7 @@ import collectible_trait_model
|
||||||
import collectible_ownership_model
|
import collectible_ownership_model
|
||||||
import app_service/service/community_tokens/dto/community_token
|
import app_service/service/community_tokens/dto/community_token
|
||||||
import app_service/common/types
|
import app_service/common/types
|
||||||
|
import app/modules/shared/wallet_utils
|
||||||
const invalidTimestamp* = high(int)
|
|
||||||
|
|
||||||
# Additional data needed to build an Entry, which is
|
# Additional data needed to build an Entry, which is
|
||||||
# not included in the backend data and needs to be
|
# not included in the backend data and needs to be
|
||||||
|
@ -373,14 +372,6 @@ QtObject:
|
||||||
self.communityImageChanged()
|
self.communityImageChanged()
|
||||||
return true
|
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 =
|
proc newCollectibleDetailsFullEntry*(data: backend.Collectible, extradata: ExtraData): CollectiblesEntry =
|
||||||
new(result, delete)
|
new(result, delete)
|
||||||
result.id = data.id
|
result.id = data.id
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
|
@ -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()")
|
|
@ -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)
|
||||||
|
)
|
|
@ -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()")
|
|
@ -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)
|
||||||
|
)
|
|
@ -14,6 +14,7 @@ type
|
||||||
ProfileShowcase
|
ProfileShowcase
|
||||||
WalletSend
|
WalletSend
|
||||||
AllCollectibles
|
AllCollectibles
|
||||||
|
Search
|
||||||
|
|
||||||
# Declared in services/wallet/collectibles/service.go
|
# Declared in services/wallet/collectibles/service.go
|
||||||
const eventCollectiblesOwnershipUpdateStarted*: string = "wallet-collectibles-ownership-update-started"
|
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 eventOwnedCollectiblesFilteringDone*: string = "wallet-owned-collectibles-filtering-done"
|
||||||
const eventGetCollectiblesDetailsDone*: string = "wallet-get-collectibles-details-done"
|
const eventGetCollectiblesDetailsDone*: string = "wallet-get-collectibles-details-done"
|
||||||
const eventGetCollectionSocialsDone*: string ="wallet-get-collection-socials-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
|
const invalidTimestamp*: int = -1
|
||||||
|
|
||||||
|
@ -67,6 +70,22 @@ type
|
||||||
collectibles*: seq[Collectible]
|
collectibles*: seq[Collectible]
|
||||||
errorCode*: ErrorCode
|
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
|
CommunityCollectiblesReceivedPayload* = object
|
||||||
collectibles*: seq[Collectible]
|
collectibles*: seq[Collectible]
|
||||||
|
|
||||||
|
@ -90,6 +109,23 @@ type
|
||||||
FetchCriteria* = object
|
FetchCriteria* = object
|
||||||
fetchType*: FetchType
|
fetchType*: FetchType
|
||||||
maxCacheAgeSeconds*: int
|
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
|
# CollectibleOwnershipState
|
||||||
proc `$`*(self: OwnershipStatus): string =
|
proc `$`*(self: OwnershipStatus): string =
|
||||||
|
@ -176,6 +212,13 @@ proc `%`*(t: CollectibleDataType): JsonNode {.inline.} =
|
||||||
proc `%`*(t: ref CollectibleDataType): JsonNode {.inline.} =
|
proc `%`*(t: ref CollectibleDataType): JsonNode {.inline.} =
|
||||||
return %(t[])
|
return %(t[])
|
||||||
|
|
||||||
|
# CollectionDataType
|
||||||
|
proc `%`*(t: CollectionDataType): JsonNode {.inline.} =
|
||||||
|
result = %(t.int)
|
||||||
|
|
||||||
|
proc `%`*(t: ref CollectionDataType): JsonNode {.inline.} =
|
||||||
|
return %(t[])
|
||||||
|
|
||||||
# FetchCriteria
|
# FetchCriteria
|
||||||
proc `$`*(self: FetchCriteria): string =
|
proc `$`*(self: FetchCriteria): string =
|
||||||
return fmt"""FetchCriteria(
|
return fmt"""FetchCriteria(
|
||||||
|
@ -191,6 +234,47 @@ proc `%`*(t: FetchCriteria): JsonNode {.inline.} =
|
||||||
proc `%`*(t: ref FetchCriteria): JsonNode {.inline.} =
|
proc `%`*(t: ref FetchCriteria): JsonNode {.inline.} =
|
||||||
return %(t[])
|
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
|
# Responses
|
||||||
proc fromJson*(e: JsonNode, T: typedesc[GetOwnedCollectiblesResponse]): GetOwnedCollectiblesResponse {.inline.} =
|
proc fromJson*(e: JsonNode, T: typedesc[GetOwnedCollectiblesResponse]): GetOwnedCollectiblesResponse {.inline.} =
|
||||||
var collectibles: seq[Collectible]
|
var collectibles: seq[Collectible]
|
||||||
|
@ -228,6 +312,32 @@ proc fromJson*(e: JsonNode, T: typedesc[GetCollectiblesByUniqueIDResponse]): Get
|
||||||
errorCode: ErrorCode(e["errorCode"].getInt())
|
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.} =
|
proc fromJson*(e: JsonNode, T: typedesc[CommunityCollectiblesReceivedPayload]): CommunityCollectiblesReceivedPayload {.inline.} =
|
||||||
var collectibles: seq[Collectible] = @[]
|
var collectibles: seq[Collectible] = @[]
|
||||||
for item in e.getElems():
|
for item in e.getElems():
|
||||||
|
@ -278,6 +388,16 @@ rpc(fetchCollectionSocialsAsync, "wallet"):
|
||||||
rpc(refetchOwnedCollectibles, "wallet"):
|
rpc(refetchOwnedCollectibles, "wallet"):
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
rpc(searchCollectionsAsync, "wallet"):
|
||||||
|
requestId: int32
|
||||||
|
params: SearchCollectionsParams
|
||||||
|
dataType: CollectionDataType
|
||||||
|
|
||||||
|
rpc(searchCollectiblesAsync, "wallet"):
|
||||||
|
requestId: int32
|
||||||
|
params: SearchCollectiblesParams
|
||||||
|
dataType: CollectibleDataType
|
||||||
|
|
||||||
rpc(updateCollectiblePreferences, "accounts"):
|
rpc(updateCollectiblePreferences, "accounts"):
|
||||||
preferences: seq[CollectiblePreferences]
|
preferences: seq[CollectiblePreferences]
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,14 @@ type
|
||||||
contractID*: ContractID
|
contractID*: ContractID
|
||||||
tokenID*: UInt256
|
tokenID*: UInt256
|
||||||
|
|
||||||
# see status-go/services/wallet/collectibles/service.go CollectibleDataType
|
# see status-go/services/wallet/collectibles/types.go CollectibleDataType
|
||||||
CollectibleDataType* {.pure.} = enum
|
CollectibleDataType* {.pure.} = enum
|
||||||
UniqueID, Header, Details, CommunityHeader
|
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
|
# Mirrors services/wallet/thirdparty/collectible_types.go CollectibleTrait
|
||||||
CollectibleTrait* = ref object of RootObj
|
CollectibleTrait* = ref object of RootObj
|
||||||
trait_type*: string
|
trait_type*: string
|
||||||
|
@ -73,6 +77,13 @@ type
|
||||||
latestTxHash*: Option[string]
|
latestTxHash*: Option[string]
|
||||||
receivedAmount*: Option[float64]
|
receivedAmount*: Option[float64]
|
||||||
contractType*: Option[ContractType]
|
contractType*: Option[ContractType]
|
||||||
|
|
||||||
|
Collection* = ref object of RootObj
|
||||||
|
dataType*: CollectionDataType
|
||||||
|
id* : ContractID
|
||||||
|
communityId*: string
|
||||||
|
contractType*: ContractType
|
||||||
|
collectionData*: Option[CollectionData]
|
||||||
|
|
||||||
CollectionSocials* = ref object of RootObj
|
CollectionSocials* = ref object of RootObj
|
||||||
website*: string
|
website*: string
|
||||||
|
@ -409,6 +420,33 @@ proc toIds(self: seq[Collectible]): seq[CollectibleUniqueID] =
|
||||||
for c in self:
|
for c in self:
|
||||||
result.add(c.id)
|
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
|
# CollectibleBalance
|
||||||
proc `$`*(self: CollectibleBalance): string =
|
proc `$`*(self: CollectibleBalance): string =
|
||||||
return fmt"""CollectibleBalance(
|
return fmt"""CollectibleBalance(
|
||||||
|
|
|
@ -12,6 +12,7 @@ QtObject {
|
||||||
|
|
||||||
/* PRIVATE: Modules used to get data from backend */
|
/* PRIVATE: Modules used to get data from backend */
|
||||||
readonly property var _allCollectiblesModule: !!walletSectionAllCollectibles ? walletSectionAllCollectibles : null
|
readonly property var _allCollectiblesModule: !!walletSectionAllCollectibles ? walletSectionAllCollectibles : null
|
||||||
|
readonly property var _collectiblesSearchModule : !!walletSectionCollectiblesSearch ? walletSectionCollectiblesSearch : null
|
||||||
|
|
||||||
/* This list contains the complete list of collectibles with separate
|
/* This list contains the complete list of collectibles with separate
|
||||||
entry per collectible which has a unique [network + contractAddress + tokenID] */
|
entry per collectible which has a unique [network + contractAddress + tokenID] */
|
||||||
|
@ -103,4 +104,26 @@ QtObject {
|
||||||
function getDetailedCollectible(chainId, contractAddress, tokenId) {
|
function getDetailedCollectible(chainId, contractAddress, tokenId) {
|
||||||
walletSection.collectibleDetailsController.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
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 10cf2862086630625434f8b2239227dee7440858
|
Subproject commit 7d7af6249176ea32bd458c67bca4010444df6389
|
Loading…
Reference in New Issue