feat(@desktop/wallet): add pagination to collectibles view

Fixes #9754
This commit is contained in:
Dario Gabriel Lipicar 2023-03-06 13:46:32 -03:00 committed by dlipicar
parent 6ec562c6b2
commit 30294f97fe
27 changed files with 454 additions and 837 deletions

View File

@ -30,23 +30,14 @@ proc newController*(
proc delete*(self: Controller) = proc delete*(self: Controller) =
discard discard
proc refreshCollections*(self: Controller, chainId: int, address: string) = proc refreshCollectibles*(self: Controller, chainId: int, address: string) =
let collections = self.collectibleService.getOwnedCollections(chainId, address) let collectibles = self.collectibleService.getOwnedCollectibles(chainId, address)
self.delegate.setCollections(collections) self.delegate.refreshCollectibles(chainId, address, collectibles)
proc refreshCollectibles*(self: Controller, chainId: int, address: string, collectionSlug: string) = proc init*(self: Controller) =
let collection = self.collectibleService.getOwnedCollection(chainId, address, collectionSlug) self.events.on(SIGNAL_OWNED_COLLECTIBLES_UPDATE_FINISHED) do(e:Args):
self.delegate.updateCollection(collection)
proc init*(self: Controller) =
self.events.on(SIGNAL_OWNED_COLLECTIONS_UPDATED) do(e:Args):
let args = OwnedCollectionsUpdateArgs(e)
self.refreshCollections(args.chainId, args.address)
self.collectibleService.fetchAllOwnedCollectibles(args.chainId, args.address)
self.events.on(SIGNAL_OWNED_COLLECTIBLES_UPDATED) do(e:Args):
let args = OwnedCollectiblesUpdateArgs(e) let args = OwnedCollectiblesUpdateArgs(e)
self.refreshCollectibles(args.chainId, args.address, args.collectionSlug) self.refreshCollectibles(args.chainId, args.address)
proc getWalletAccount*(self: Controller, accountIndex: int): wallet_account_service.WalletAccountDto = proc getWalletAccount*(self: Controller, accountIndex: int): wallet_account_service.WalletAccountDto =
return self.walletAccountService.getWalletAccount(accountIndex) return self.walletAccountService.getWalletAccount(accountIndex)
@ -54,8 +45,14 @@ proc getWalletAccount*(self: Controller, accountIndex: int): wallet_account_serv
proc getNetwork*(self: Controller): network_service.NetworkDto = proc getNetwork*(self: Controller): network_service.NetworkDto =
return self.networkService.getNetworkForCollectibles() return self.networkService.getNetworkForCollectibles()
proc fetchOwnedCollections*(self: Controller, chainId: int, address: string) = proc resetOwnedCollectibles*(self: Controller, chainId: int, address: string) =
self.collectibleService.fetchOwnedCollections(chainId, address) self.collectibleService.resetOwnedCollectibles(chainId, address)
proc fetchOwnedCollectibles*(self: Controller, chainId: int, address: string, collectionSlug: string) = proc fetchOwnedCollectibles*(self: Controller, chainId: int, address: string, limit: int) =
self.collectibleService.fetchOwnedCollectibles(chainId, address, collectionSlug) self.collectibleService.fetchOwnedCollectibles(chainId, address, limit)
proc getCollectible*(self: Controller, chainId: int, id: UniqueID) : CollectibleDto =
self.collectibleService.getCollectible(chainId, id)
proc getCollection*(self: Controller, chainId: int, slug: string) : CollectionDto =
self.collectibleService.getCollection(chainId, slug)

View File

@ -29,6 +29,7 @@ method setCurrentAddress*(self: Controller, network: network_dto.NetworkDto, add
self.network = network self.network = network
self.address = address self.address = address
proc update*(self: Controller, collectionSlug: string, id: int) = proc update*(self: Controller, id: collectible_service.UniqueID) =
let collection = self.collectibleService.getOwnedCollection(self.network.chainId, self.address, collectionSlug) let collectible = self.collectibleService.getCollectible(self.network.chainId, id)
self.delegate.setData(collection.collection, collection.collectibles[id], self.network) let collection = self.collectibleService.getCollection(self.network.chainId, collectible.collectionSlug)
self.delegate.setData(collection, collectible, self.network)

View File

@ -1,3 +1,4 @@
import stint
import ../../../../../../app_service/service/network/dto as network_dto import ../../../../../../app_service/service/network/dto as network_dto
import ../../../../../../app_service/service/collectible/dto as collectible_dto import ../../../../../../app_service/service/collectible/dto as collectible_dto
@ -17,7 +18,7 @@ method isLoaded*(self: AccessInterface): bool {.base.} =
method setCurrentAddress*(self: AccessInterface, network: network_dto.NetworkDto, address: string) {.base.} = method setCurrentAddress*(self: AccessInterface, network: network_dto.NetworkDto, address: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method update*(self: AccessInterface, collectionSlug: string, id: int) {.base.} = method update*(self: AccessInterface, address: string, tokenId: Uint256) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method setData*(self: AccessInterface, collection: collectible_dto.CollectionDto, collectible: collectible_dto.CollectibleDto, network: network_dto.NetworkDto) {.base.} = method setData*(self: AccessInterface, collection: collectible_dto.CollectionDto, collectible: collectible_dto.CollectibleDto, network: network_dto.NetworkDto) {.base.} =

View File

@ -1,4 +1,4 @@
import NimQml, sequtils, sugar import NimQml, sequtils, sugar, stint
import ../../../../../global/global_singleton import ../../../../../global/global_singleton
@ -8,6 +8,9 @@ import ../../../../../../app_service/service/collectible/service as collectible_
import ../../../../../../app_service/service/collectible/dto as collectible_dto import ../../../../../../app_service/service/collectible/dto as collectible_dto
import ../../../../../../app_service/service/network/dto as network_dto import ../../../../../../app_service/service/network/dto as network_dto
import ../models/collectibles_item as collectibles_item
import ../models/collectibles_utils
export io_interface export io_interface
type type
@ -45,8 +48,13 @@ method viewDidLoad*(self: Module) =
method setCurrentAddress*(self: Module, network: network_dto.NetworkDto, address: string) = method setCurrentAddress*(self: Module, network: network_dto.NetworkDto, address: string) =
self.controller.setCurrentAddress(network, address) self.controller.setCurrentAddress(network, address)
method update*(self: Module, collectionSlug: string, id: int) = method update*(self: Module, address: string, tokenId: Uint256) =
self.controller.update(collectionSlug, id) let id = collectible_dto.UniqueID(
contractAddress: address,
tokenId: tokenId
)
self.controller.update(id)
method setData*(self: Module, collection: collectible_dto.CollectionDto, collectible: collectible_dto.CollectibleDto, network: network_dto.NetworkDto) = method setData*(self: Module, collection: collectible_dto.CollectionDto, collectible: collectible_dto.CollectibleDto, network: network_dto.NetworkDto) =
self.view.setData(collection, collectible, network) let item = collectibleToItem(collectible, collection)
self.view.setData(item, network)

View File

@ -2,7 +2,7 @@ import NimQml, sequtils, sugar, stint
import ./io_interface import ./io_interface
import ../../../../../../app_service/service/network/dto as network_dto import ../../../../../../app_service/service/network/dto as network_dto
import ../../../../../../app_service/service/collectible/dto as collectible_dto import ../models/collectibles_item
import ../models/collectible_trait_item import ../models/collectible_trait_item
import ../models/collectible_trait_model import ../models/collectible_trait_model
@ -15,16 +15,7 @@ QtObject:
networkColor: string networkColor: string
networkIconUrl: string networkIconUrl: string
collectionName: string collectible: Item
collectionImageUrl: string
name: string
id: string
tokenId: string
description: string
backgroundColor: string
imageUrl: string
permalink: string
propertiesModel: TraitModel propertiesModel: TraitModel
rankingsModel: TraitModel rankingsModel: TraitModel
statsModel: TraitModel statsModel: TraitModel
@ -39,8 +30,7 @@ QtObject:
new(result, delete) new(result, delete)
result.setup() result.setup()
result.delegate = delegate result.delegate = delegate
result.description = "Collectibles" result.collectible = initItem()
result.backgroundColor = "transparent"
result.propertiesModel = newTraitModel() result.propertiesModel = newTraitModel()
result.rankingsModel = newTraitModel() result.rankingsModel = newTraitModel()
result.statsModel = newTraitModel() result.statsModel = newTraitModel()
@ -76,7 +66,7 @@ QtObject:
notify = networkIconUrlChanged notify = networkIconUrlChanged
proc getName(self: View): QVariant {.slot.} = proc getName(self: View): QVariant {.slot.} =
return newQVariant(self.name) return newQVariant(self.collectible.getName())
proc nameChanged(self: View) {.signal.} proc nameChanged(self: View) {.signal.}
@ -85,7 +75,7 @@ QtObject:
notify = nameChanged notify = nameChanged
proc getID(self: View): QVariant {.slot.} = proc getID(self: View): QVariant {.slot.} =
return newQVariant(self.id) return newQVariant(self.collectible.getId())
proc idChanged(self: View) {.signal.} proc idChanged(self: View) {.signal.}
@ -94,7 +84,7 @@ QtObject:
notify = idChanged notify = idChanged
proc getTokenID(self: View): QVariant {.slot.} = proc getTokenID(self: View): QVariant {.slot.} =
return newQVariant(self.tokenId) return newQVariant(self.collectible.getTokenId().toString())
proc tokenIdChanged(self: View) {.signal.} proc tokenIdChanged(self: View) {.signal.}
@ -103,7 +93,7 @@ QtObject:
notify = tokenIdChanged notify = tokenIdChanged
proc getDescription(self: View): QVariant {.slot.} = proc getDescription(self: View): QVariant {.slot.} =
return newQVariant(self.description) return newQVariant(self.collectible.getDescription())
proc descriptionChanged(self: View) {.signal.} proc descriptionChanged(self: View) {.signal.}
@ -112,7 +102,7 @@ QtObject:
notify = descriptionChanged notify = descriptionChanged
proc getBackgroundColor(self: View): QVariant {.slot.} = proc getBackgroundColor(self: View): QVariant {.slot.} =
return newQVariant(self.backgroundColor) return newQVariant(self.collectible.getBackgroundColor())
proc backgroundColorChanged(self: View) {.signal.} proc backgroundColorChanged(self: View) {.signal.}
@ -121,7 +111,7 @@ QtObject:
notify = backgroundColorChanged notify = backgroundColorChanged
proc getImageUrl(self: View): QVariant {.slot.} = proc getImageUrl(self: View): QVariant {.slot.} =
return newQVariant(self.imageUrl) return newQVariant(self.collectible.getImageUrl())
proc imageUrlChanged(self: View) {.signal.} proc imageUrlChanged(self: View) {.signal.}
@ -130,7 +120,7 @@ QtObject:
notify = imageUrlChanged notify = imageUrlChanged
proc getCollectionName(self: View): QVariant {.slot.} = proc getCollectionName(self: View): QVariant {.slot.} =
return newQVariant(self.collectionName) return newQVariant(self.collectible.getCollectionName())
proc collectionNameChanged(self: View) {.signal.} proc collectionNameChanged(self: View) {.signal.}
@ -139,7 +129,7 @@ QtObject:
notify = collectionNameChanged notify = collectionNameChanged
proc getCollectionImageUrl(self: View): QVariant {.slot.} = proc getCollectionImageUrl(self: View): QVariant {.slot.} =
return newQVariant(self.collectionImageUrl) return newQVariant(self.collectible.getCollectionImageUrl())
proc collectionImageUrlChanged(self: View) {.signal.} proc collectionImageUrlChanged(self: View) {.signal.}
@ -148,7 +138,7 @@ QtObject:
notify = collectionImageUrlChanged notify = collectionImageUrlChanged
proc getPermalink(self: View): QVariant {.slot.} = proc getPermalink(self: View): QVariant {.slot.} =
return newQVariant(self.permalink) return newQVariant(self.collectible.getPermalink())
proc permalinkChanged(self: View) {.signal.} proc permalinkChanged(self: View) {.signal.}
@ -179,14 +169,14 @@ QtObject:
proc getStats*(self: View): QVariant {.slot.} = proc getStats*(self: View): QVariant {.slot.} =
return newQVariant(self.statsModel) return newQVariant(self.statsModel)
QtProperty[QVariant] rankings: QtProperty[QVariant] stats:
read = getStats read = getStats
notify = statsChanged notify = statsChanged
proc update*(self: View, collectionSlug: string, id: int) {.slot.} = proc update*(self: View, address: string, tokenId: string) {.slot.} =
self.delegate.update(collectionSlug, id) self.delegate.update(address, parse(tokenId, Uint256))
proc setData*(self: View, collection: collectible_dto.CollectionDto, collectible: collectible_dto.CollectibleDto, network: network_dto.NetworkDto) = proc setData*(self: View, collectible: Item, network: network_dto.NetworkDto) =
if (self.networkShortName != network.shortName): if (self.networkShortName != network.shortName):
self.networkShortName = network.shortName self.networkShortName = network.shortName
self.networkShortNameChanged() self.networkShortNameChanged()
@ -199,46 +189,21 @@ QtObject:
self.networkIconUrl = network.iconURL self.networkIconUrl = network.iconURL
self.networkIconUrlChanged() self.networkIconUrlChanged()
if (self.collectionName != collection.name): self.collectible = collectible
self.collectionName = collection.name self.collectionNameChanged()
self.collectionNameChanged() self.collectionImageUrlChanged()
self.nameChanged()
self.idChanged()
self.tokenIdChanged()
self.descriptionChanged()
self.backgroundColorChanged()
self.imageUrlChanged()
if (self.collectionImageUrl != collection.imageUrl): self.propertiesModel.setItems(collectible.getProperties())
self.collectionImageUrl = collection.imageUrl
self.collectionImageUrlChanged()
if (self.name != collectible.name):
self.name = collectible.name
self.nameChanged()
let idString = $collectible.id
if (self.id != idString):
self.id = idString
self.idChanged()
let tokenIdString = collectible.tokenId.toString()
if (self.tokenId != tokenIdString):
self.tokenId = tokenIdString
self.tokenIdChanged()
if (self.description != collectible.description):
self.description = collectible.description
self.descriptionChanged()
let backgroundColor = if (collectible.backgroundColor == ""): "transparent" else: ("#" & collectible.backgroundColor)
if (self.backgroundColor != backgroundColor):
self.backgroundColor = backgroundColor
self.backgroundColorChanged()
if (self.imageUrl != collectible.imageUrl):
self.imageUrl = collectible.imageUrl
self.imageUrlChanged()
self.propertiesModel.setItems(collectible.properties.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue)))
self.propertiesChanged() self.propertiesChanged()
self.rankingsModel.setItems(collectible.rankings.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue))) self.rankingsModel.setItems(collectible.getRankings())
self.rankingsChanged() self.rankingsChanged()
self.statsModel.setItems(collectible.statistics.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue))) self.statsModel.setItems(collectible.getStats())
self.statsChanged() self.statsChanged()

View File

@ -16,16 +16,10 @@ method isLoaded*(self: AccessInterface): bool {.base.} =
method switchAccount*(self: AccessInterface, accountIndex: int) {.base.} = method switchAccount*(self: AccessInterface, accountIndex: int) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method fetchOwnedCollections*(self: AccessInterface) {.base.} = method fetchOwnedCollectibles*(self: AccessInterface, limit: int) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method setCollections*(self: AccessInterface, collections: CollectionsData) {.base.} = method refreshCollectibles*(self: AccessInterface, chainId: int, address: string, collectibles: CollectiblesData) {.base.} =
raise newException(ValueError, "No implementation available")
method fetchOwnedCollectibles*(self: AccessInterface, collectionSlug: string) {.base.} =
raise newException(ValueError, "No implementation available")
method updateCollection*(self: AccessInterface, collection: CollectionData) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method viewDidLoad*(self: AccessInterface) {.base.} = method viewDidLoad*(self: AccessInterface) {.base.} =
@ -35,8 +29,5 @@ method viewDidLoad*(self: AccessInterface) {.base.} =
method collectiblesModuleDidLoad*(self: AccessInterface) {.base.} = method collectiblesModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method collectionsModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method currentCollectibleModuleDidLoad*(self: AccessInterface) {.base.} = method currentCollectibleModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -1,221 +0,0 @@
import NimQml, Tables, strutils
import ./collections_model as collections_model
import ./collectibles_model as collectibles_model
type
ModelRole* {.pure.} = enum
CollectionName = UserRole + 1,
CollectionSlug
CollectionImageUrl
CollectionOwnedAssetCount
CollectionCollectiblesCount
CollectionCollectiblesLoaded
Id
Name
ImageUrl
BackgroundColor
Description
Permalink
Properties
Rankings
Stats
const COLLECTION_ROLE_TO_PROXY_ROLE = {
CollectionRole.Name: ModelRole.CollectionName,
CollectionRole.Slug: ModelRole.CollectionSlug,
CollectionRole.ImageUrl: ModelRole.CollectionImageUrl,
CollectionRole.OwnedAssetCount: ModelRole.CollectionOwnedAssetCount,
CollectionRole.CollectiblesLoaded: ModelRole.CollectionCollectiblesLoaded,
}.toTable()
const COLLECTIBLE_ROLE_TO_PROXY_ROLE = {
CollectibleRole.Id: ModelRole.Id,
CollectibleRole.Name: ModelRole.Name,
CollectibleRole.ImageUrl: ModelRole.ImageUrl,
CollectibleRole.BackgroundColor: ModelRole.BackgroundColor,
CollectibleRole.Description: ModelRole.Description,
CollectibleRole.Permalink: ModelRole.Permalink,
CollectibleRole.Properties: ModelRole.Properties,
CollectibleRole.Rankings: ModelRole.Rankings,
CollectibleRole.Stats: ModelRole.Stats,
}.toTable()
type
Index = tuple
collectionIdx: int
collectibleIdx: int
QtObject:
type
Model* = ref object of QAbstractListModel
collectionsModel: collections_model.Model
sourceIndexToRow: Table[Index, int]
collectionToRows: Table[int, (int, int)]
rowToSourceIndex: Table[int, Index]
proc delete(self: Model) =
self.collectionsModel = nil
self.QAbstractListModel.delete
proc setup(self: Model) =
self.QAbstractListModel.setup
proc countChanged(self: Model) {.signal.}
proc getCount(self: Model): int {.slot.} =
self.sourceIndexToRow.len
QtProperty[int] count:
read = getCount
notify = countChanged
proc collectionsLoadedChanged(self: Model) {.signal.}
proc getCollectionsLoaded*(self: Model): bool {.slot.} =
self.collectionsModel.getCollectionsLoaded()
QtProperty[bool] collectionsLoaded:
read = getCollectionsLoaded
notify = collectionsLoadedChanged
proc collectionCountChanged(self: Model) {.signal.}
proc getCollectionCount*(self: Model): int {.slot.} =
self.collectionsModel.getCount()
QtProperty[int] collectionCount:
read = getCollectionCount
notify = collectionCountChanged
proc rebuildMap(self: Model) =
self.beginResetModel()
self.sourceIndexToRow.clear()
self.collectionToRows.clear()
self.rowToSourceIndex.clear()
var proxy_row = 0
for i in 0 ..< self.collectionsModel.getCount():
let collectiblesModel = self.collectionsModel.getCollectiblesModel(i)
let collectionIndexStart = proxy_row
for j in 0 ..< collectiblesModel.getCount():
let idx = (collectionIdx: i, collectibleIdx: j)
self.sourceIndexToRow[idx] = proxy_row
self.rowToSourceIndex[proxy_row] = idx
proxy_row += 1
self.collectionToRows[i] = (collectionIndexStart, proxy_row - 1)
self.endResetModel()
self.countChanged()
proc newModel*(collectionsModel: collections_model.Model): Model =
new(result, delete)
result.collectionsModel = collectionsModel
result.setup
result.rebuildMap()
signalConnect(result.collectionsModel, "collectionsLoadedChanged()", result, "onCollectionsLoadedChanged()")
signalConnect(result.collectionsModel, "countChanged()", result, "onCollectionCountChanged()")
signalConnect(result.collectionsModel, "signalDataChanged(int, int, int)", result, "onDataChanged(int, int, int)")
proc onCollectionsLoadedChanged(self: Model) {.slot.} =
self.collectionsLoadedChanged()
proc onCollectionCountChanged(self: Model) {.slot.} =
self.collectionCountChanged()
self.rebuildMap()
proc onDataChanged(self: Model,
top: int,
bottom: int,
role: int) {.slot.} =
var topRow = self.collectionToRows[top][0]
var bottomRow = self.collectionToRows[bottom][1]
let topIndex = self.createIndex(topRow, 0, nil)
let bottomIndex = self.createIndex(bottomRow, 0, nil)
if (COLLECTION_ROLE_TO_PROXY_ROLE.hasKey(role.CollectionRole)):
self.dataChanged(topIndex, bottomIndex, @[COLLECTION_ROLE_TO_PROXY_ROLE[role.CollectionRole].int])
elif role == CollectionRole.CollectiblesModel.int:
self.rebuildMap()
method rowCount*(self: Model, index: QModelIndex = nil): int =
return self.getCount()
method roleNames(self: Model): Table[int, string] =
{
ModelRole.CollectionName.int:"collectionName",
ModelRole.CollectionSlug.int:"collectionSlug",
ModelRole.CollectionImageUrl.int:"collectionImageUrl",
ModelRole.CollectionOwnedAssetCount.int:"collectionOwnedAssetCount",
ModelRole.CollectionCollectiblesCount.int:"collectionCollectiblesCount",
ModelRole.CollectionCollectiblesLoaded.int:"collectionCollectiblesLoaded",
ModelRole.Id.int:"id",
ModelRole.Name.int:"name",
ModelRole.ImageUrl.int:"imageUrl",
ModelRole.BackgroundColor.int:"backgroundColor",
ModelRole.Description.int:"description",
ModelRole.Permalink.int:"permalink",
ModelRole.Properties.int:"properties",
ModelRole.Rankings.int:"rankings",
ModelRole.Stats.int:"stats",
}.toTable
proc mapFromSource(self: Model, index: Index): QModelIndex =
if not self.sourceIndexToRow.hasKey(index):
return QModelIndex()
let proxyIndex = self.sourceIndexToRow[index]
return self.createIndex(proxyIndex, 0, nil)
proc mapToSource(self: Model, index: QModelIndex): Index =
if not self.rowToSourceIndex.hasKey(index.row):
return (collectionIdx: -1, collectibleIdx: -1)
return self.rowToSourceIndex[index.row]
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 sourceIndex = self.mapToSource(index)
let collectionIndex = self.collectionsModel.createIndex(sourceIndex.collectionIdx, 0, nil)
let enumRole = role.ModelRole
case enumRole:
of ModelRole.CollectionName:
result = self.collectionsModel.data(collectionIndex, CollectionRole.Name.int)
of ModelRole.CollectionSlug:
result = self.collectionsModel.data(collectionIndex, CollectionRole.Slug.int)
of ModelRole.CollectionImageUrl:
result = self.collectionsModel.data(collectionIndex, CollectionRole.ImageUrl.int)
of ModelRole.CollectionOwnedAssetCount:
result = self.collectionsModel.data(collectionIndex, CollectionRole.OwnedAssetCount.int)
of ModelRole.CollectionCollectiblesLoaded:
result = self.collectionsModel.data(collectionIndex, CollectionRole.CollectiblesLoaded.int)
else:
let collectiblesModel = self.collectionsModel.getCollectiblesModel(sourceIndex.collectionIdx)
let collectibleIndex = collectiblesModel.createIndex(sourceIndex.collectibleIdx, 0, nil)
case enumRole:
of ModelRole.CollectionCollectiblesCount:
result = newQVariant(collectiblesModel.getCount())
of ModelRole.Id:
result = collectiblesModel.data(collectibleIndex, CollectibleRole.Id.int)
of ModelRole.Name:
result = collectiblesModel.data(collectibleIndex, CollectibleRole.Name.int)
of ModelRole.ImageUrl:
result = collectiblesModel.data(collectibleIndex, CollectibleRole.ImageUrl.int)
of ModelRole.BackgroundColor:
result = collectiblesModel.data(collectibleIndex, CollectibleRole.BackgroundColor.int)
of ModelRole.Description:
result = collectiblesModel.data(collectibleIndex, CollectibleRole.Description.int)
of ModelRole.Permalink:
result = collectiblesModel.data(collectibleIndex, CollectibleRole.Permalink.int)
of ModelRole.Properties:
result = collectiblesModel.data(collectibleIndex, CollectibleRole.Properties.int)
of ModelRole.Rankings:
result = collectiblesModel.data(collectibleIndex, CollectibleRole.Rankings.int)
of ModelRole.Stats:
result = collectiblesModel.data(collectibleIndex, CollectibleRole.Stats.int)
else:
return
proc data*(self: Model, row: int, role: ModelRole): QVariant =
return self.data(self.createIndex(row, 0, nil), role.int)

View File

@ -4,6 +4,7 @@ import ./collectible_trait_item
type type
Item* = object Item* = object
id: int id: int
address: string
tokenId: UInt256 tokenId: UInt256
name: string name: string
imageUrl: string imageUrl: string
@ -13,9 +14,14 @@ type
properties: seq[CollectibleTrait] properties: seq[CollectibleTrait]
rankings: seq[CollectibleTrait] rankings: seq[CollectibleTrait]
stats: seq[CollectibleTrait] stats: seq[CollectibleTrait]
collectionName: string
collectionSlug: string
collectionImageUrl: string
isLoading: bool
proc initItem*( proc initItem*(
id: int, id: int,
address: string,
tokenId: UInt256, tokenId: UInt256,
name: string, name: string,
imageUrl: string, imageUrl: string,
@ -24,11 +30,15 @@ proc initItem*(
permalink: string, permalink: string,
properties: seq[CollectibleTrait], properties: seq[CollectibleTrait],
rankings: seq[CollectibleTrait], rankings: seq[CollectibleTrait],
stats: seq[CollectibleTrait] stats: seq[CollectibleTrait],
collectionName: string,
collectionSlug: string,
collectionImageUrl: string
): Item = ): Item =
result.id = id result.id = id
result.address = address
result.tokenId = tokenId result.tokenId = tokenId
result.name = name result.name = if (name != ""): name else: ("#" & tokenId.toString())
result.imageUrl = imageUrl result.imageUrl = imageUrl
result.backgroundColor = if (backgroundColor == ""): "transparent" else: ("#" & backgroundColor) result.backgroundColor = if (backgroundColor == ""): "transparent" else: ("#" & backgroundColor)
result.description = description result.description = description
@ -36,24 +46,39 @@ proc initItem*(
result.properties = properties result.properties = properties
result.rankings = rankings result.rankings = rankings
result.stats = stats result.stats = stats
result.collectionName = collectionName
result.collectionSlug = collectionSlug
result.collectionImageUrl = collectionImageUrl
result.isLoading = false
proc initItem*: Item = proc initItem*: Item =
result = initItem(-1, u256(0), "", "", "transparent", "Collectibles", "", @[], @[], @[]) result = initItem(-1, "", u256(0), "", "", "transparent", "Collectibles", "", @[], @[], @[], "", "", "")
proc initLoadingItem*: Item =
result = initItem()
result.isLoading = true
proc `$`*(self: Item): string = proc `$`*(self: Item): string =
result = fmt"""Collectibles( result = fmt"""Collectibles(
id: {self.id}, id: {self.id},
address: {self.address},
tokenId: {self.tokenId}, tokenId: {self.tokenId},
name: {self.name}, name: {self.name},
imageUrl: {self.imageUrl}, imageUrl: {self.imageUrl},
backgroundColor: {self.backgroundColor}, backgroundColor: {self.backgroundColor},
description: {self.description}, description: {self.description},
permalink: {self.permalink}, permalink: {self.permalink},
collectionName: {self.collectionName},
collectionSlug: {self.collectionSlug},
collectionImageUrl: {self.collectionImageUrl},
]""" ]"""
proc getId*(self: Item): int = proc getId*(self: Item): int =
return self.id return self.id
proc getAddress*(self: Item): string =
return self.address
proc getTokenId*(self: Item): UInt256 = proc getTokenId*(self: Item): UInt256 =
return self.tokenId return self.tokenId
@ -80,3 +105,15 @@ proc getRankings*(self: Item): seq[CollectibleTrait] =
proc getStats*(self: Item): seq[CollectibleTrait] = proc getStats*(self: Item): seq[CollectibleTrait] =
return self.stats return self.stats
proc getCollectionName*(self: Item): string =
return self.collectionName
proc getCollectionSlug*(self: Item): string =
return self.collectionSlug
proc getCollectionImageUrl*(self: Item): string =
return self.collectionImageUrl
proc getIsLoading*(self: Item): bool =
return self.isLoading

View File

@ -5,6 +5,7 @@ import ./collectibles_item, ./collectible_trait_model
type type
CollectibleRole* {.pure.} = enum CollectibleRole* {.pure.} = enum
Id = UserRole + 1, Id = UserRole + 1,
Address
TokenId TokenId
Name Name
ImageUrl ImageUrl
@ -14,11 +15,23 @@ type
Properties Properties
Rankings Rankings
Stats Stats
CollectionName
CollectionSlug
CollectionImageUrl
IsLoading
# Maximum number of owned collectibles to be fetched at a time
const ownedCollectiblesFetchLimit = 200
QtObject: QtObject:
type type
Model* = ref object of QAbstractListModel Model* = ref object of QAbstractListModel
items: seq[Item] items: seq[Item]
allCollectiblesLoaded: bool
isFetching: bool
proc addLoadingItems(self: Model)
proc removeLoadingItems(self: Model)
proc delete(self: Model) = proc delete(self: Model) =
self.items = @[] self.items = @[]
@ -27,13 +40,12 @@ QtObject:
proc setup(self: Model) = proc setup(self: Model) =
self.QAbstractListModel.setup self.QAbstractListModel.setup
proc newModel*(items: seq[Item]): Model = proc newModel*(): Model =
new(result, delete) new(result, delete)
result.setup result.setup
result.items = items result.items = @[]
result.allCollectiblesLoaded = false
proc newModel*(): Model = result.isFetching = true
return newModel(@[])
proc `$`*(self: Model): string = proc `$`*(self: Model): string =
for i in 0 ..< self.items.len: for i in 0 ..< self.items.len:
@ -46,12 +58,50 @@ QtObject:
read = getCount read = getCount
notify = countChanged notify = countChanged
proc isFetchingChanged(self: Model) {.signal.}
proc getIsFetching*(self: Model): bool {.slot.} =
self.isFetching
QtProperty[bool] isFetching:
read = getIsFetching
notify = isFetchingChanged
proc setIsFetching(self: Model, value: bool) =
if value == self.isFetching:
return
if value:
self.addLoadingItems()
else:
self.removeLoadingItems()
self.isFetching = value
self.isFetchingChanged()
proc allCollectiblesLoadedChanged(self: Model) {.signal.}
proc getAllCollectiblesLoaded*(self: Model): bool {.slot.} =
self.allCollectiblesLoaded
QtProperty[bool] allCollectiblesLoaded:
read = getAllCollectiblesLoaded
notify = allCollectiblesLoadedChanged
proc setAllCollectiblesLoaded*(self: Model, value: bool) =
if value == self.allCollectiblesLoaded:
return
self.allCollectiblesLoaded = value
self.allCollectiblesLoadedChanged()
method canFetchMore*(self: Model, parent: QModelIndex): bool =
return not self.allCollectiblesLoaded and not self.isFetching
proc requestFetch(self: Model, limit: int) {.signal.}
method fetchMore*(self: Model, parent: QModelIndex) =
if not self.isFetching:
self.setIsFetching(true)
self.requestFetch(ownedCollectiblesFetchLimit)
method rowCount*(self: Model, index: QModelIndex = nil): int = method rowCount*(self: Model, index: QModelIndex = nil): int =
return self.items.len return self.items.len
method roleNames(self: Model): Table[int, string] = method roleNames(self: Model): Table[int, string] =
{ {
CollectibleRole.Id.int:"id", CollectibleRole.Id.int:"id",
CollectibleRole.Address.int:"address",
CollectibleRole.TokenId.int:"tokenId", CollectibleRole.TokenId.int:"tokenId",
CollectibleRole.Name.int:"name", CollectibleRole.Name.int:"name",
CollectibleRole.ImageUrl.int:"imageUrl", CollectibleRole.ImageUrl.int:"imageUrl",
@ -61,6 +111,10 @@ QtObject:
CollectibleRole.Properties.int:"properties", CollectibleRole.Properties.int:"properties",
CollectibleRole.Rankings.int:"rankings", CollectibleRole.Rankings.int:"rankings",
CollectibleRole.Stats.int:"stats", CollectibleRole.Stats.int:"stats",
CollectibleRole.CollectionName.int:"collectionName",
CollectibleRole.CollectionSlug.int:"collectionSlug",
CollectibleRole.CollectionImageUrl.int:"collectionImageUrl",
CollectibleRole.IsLoading.int:"isLoading",
}.toTable }.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant = method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -76,6 +130,8 @@ QtObject:
case enumRole: case enumRole:
of CollectibleRole.Id: of CollectibleRole.Id:
result = newQVariant(item.getId()) result = newQVariant(item.getId())
of CollectibleRole.Address:
result = newQVariant(item.getAddress())
of CollectibleRole.TokenId: of CollectibleRole.TokenId:
result = newQVariant(item.getTokenId().toString()) result = newQVariant(item.getTokenId().toString())
of CollectibleRole.Name: of CollectibleRole.Name:
@ -100,15 +156,45 @@ QtObject:
let traits = newTraitModel() let traits = newTraitModel()
traits.setItems(item.getStats()) traits.setItems(item.getStats())
result = newQVariant(traits) result = newQVariant(traits)
of CollectibleRole.CollectionName:
result = newQVariant(item.getCollectionName())
of CollectibleRole.CollectionSlug:
result = newQVariant(item.getCollectionSlug())
of CollectibleRole.CollectionImageUrl:
result = newQVariant(item.getCollectionImageUrl())
of CollectibleRole.IsLoading:
result = newQVariant(item.getIsLoading())
proc getItem*(self: Model, index: int): Item = proc addLoadingItems(self: Model) =
return self.items[index] let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
proc setItems*(self: Model, items: seq[Item]) = let loadingItem = initLoadingItem()
self.beginResetModel() self.beginInsertRows(parentModelIndex, self.items.len, self.items.len + ownedCollectiblesFetchLimit - 1)
self.items = items for i in 1..ownedCollectiblesFetchLimit:
self.endResetModel() self.items.add(loadingItem)
self.endInsertRows()
self.countChanged() self.countChanged()
proc appendItems*(self: Model, items: seq[Item]) = proc removeLoadingItems(self: Model) =
self.setItems(concat(self.items, items)) for i in 0 ..< self.items.len:
if self.items[i].getIsLoading():
self.beginRemoveRows(newQModelIndex(), i, self.items.len-1)
self.items.delete(i, self.items.len-1)
self.endRemoveRows()
self.countChanged()
return
proc setItems*(self: Model, items: seq[Item], append: bool) =
self.setIsFetching(false)
if append:
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len + items.len - 1)
self.items = concat(self.items, items)
self.endInsertRows()
else:
self.beginResetModel()
self.items = items
self.endResetModel()
self.countChanged()

View File

@ -2,9 +2,10 @@ import sequtils, sugar
import ../../../../../../app_service/service/collectible/dto import ../../../../../../app_service/service/collectible/dto
import collectibles_item, collectible_trait_item import collectibles_item, collectible_trait_item
proc collectibleToItem*(c: CollectibleDto) : Item = proc collectibleToItem*(c: CollectibleDto, co: CollectionDto) : Item =
return initItem( return initItem(
c.id, c.id,
c.address,
c.tokenId, c.tokenId,
c.name, c.name,
c.imageUrl, c.imageUrl,
@ -13,5 +14,8 @@ proc collectibleToItem*(c: CollectibleDto) : Item =
c.permalink, c.permalink,
c.properties.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue)), c.properties.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue)),
c.rankings.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue)), c.rankings.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue)),
c.statistics.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue)) c.statistics.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue)),
co.name,
co.slug,
co.imageUrl
) )

View File

@ -1,51 +0,0 @@
import strformat, stint
import ./collectibles_model as collectibles_model
import ./collectibles_item as collectibles_item
type
Item* = object
name: string
slug: string
imageUrl: string
ownedAssetCount: Uint256
collectiblesLoaded*: bool
collectiblesModel: collectibles_model.Model
proc initItem*(name, slug, imageUrl: string, ownedAssetCount: Uint256, collectiblesLoaded: bool, collectibles: seq[collectibles_item.Item]): Item =
result.name = name
result.slug = slug
result.imageUrl = imageUrl
result.ownedAssetCount = ownedAssetCount
result.collectiblesLoaded = collectiblesLoaded
result.collectiblesModel = collectibles_model.newModel(collectibles)
proc initItem*(): Item =
result = initItem("", "", "", u256(0), false, @[])
proc `$`*(self: Item): string =
result = fmt"""CollectibleCollection(
name: {self.name},
slug: {self.slug},
imageUrl: {self.imageUrl},
ownedAssetCount: {self.ownedAssetCount},
collectiblesLoaded: {self.collectiblesLoaded},
collectibles: {self.collectiblesModel}
]"""
proc getName*(self: Item): string =
return self.name
proc getSlug*(self: Item): string =
return self.slug
proc getImageUrl*(self: Item): string =
return self.imageUrl
proc getOwnedAssetCount*(self: Item): Uint256 =
return self.ownedAssetCount
proc getCollectiblesLoaded*(self: Item): bool =
return self.collectiblesLoaded
proc getCollectiblesModel*(self: Item): collectibles_model.Model =
return self.collectiblesModel

View File

@ -1,131 +0,0 @@
import NimQml, Tables, strutils, strformat, stint
import ./collections_item as collections_item
import ./collectibles_model as collectibles_model
import ./collectibles_item as collectibles_item
type
CollectionRole* {.pure.} = enum
Name = UserRole + 1,
Slug
ImageUrl
OwnedAssetCount
CollectiblesLoaded
CollectiblesModel
QtObject:
type
Model* = ref object of QAbstractListModel
items: seq[collections_item.Item]
collectionsLoaded: bool
proc delete(self: Model) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: Model) =
self.QAbstractListModel.setup
self.collectionsLoaded = false
proc newModel*(): Model =
new(result, delete)
result.setup
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.} =
self.items.len
QtProperty[int] count:
read = getCount
notify = countChanged
proc collectionsLoadedChanged(self: Model) {.signal.}
proc getCollectionsLoaded*(self: Model): bool {.slot.} =
self.collectionsLoaded
QtProperty[bool] collectionsLoaded:
read = getCollectionsLoaded
notify = collectionsLoadedChanged
method rowCount(self: Model, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: Model): Table[int, string] =
{
CollectionRole.Name.int:"name",
CollectionRole.Slug.int:"slug",
CollectionRole.ImageUrl.int:"imageUrl",
CollectionRole.OwnedAssetCount.int:"ownedAssetCount",
CollectionRole.CollectiblesLoaded.int:"collectiblesLoaded",
CollectionRole.CollectiblesModel.int:"collectiblesModel"
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.items.len):
return
let item = self.items[index.row]
let enumRole = role.CollectionRole
case enumRole:
of CollectionRole.Name:
result = newQVariant(item.getName())
of CollectionRole.Slug:
result = newQVariant(item.getSlug())
of CollectionRole.ImageUrl:
result = newQVariant(item.getImageUrl())
of CollectionRole.OwnedAssetCount:
result = newQVariant(item.getOwnedAssetCount().toString())
of CollectionRole.CollectiblesLoaded:
result = newQVariant(item.getCollectiblesLoaded())
of CollectionRole.CollectiblesModel:
result = newQVariant(item.getCollectiblesModel())
proc setCollections*(self: Model, items: seq[collections_item.Item], collectionsLoaded: bool) =
self.beginResetModel()
self.items = items
self.endResetModel()
self.countChanged()
if self.collectionsLoaded != collectionsLoaded:
self.collectionsLoaded = collectionsLoaded
self.collectionsLoadedChanged()
proc getCollectionItem*(self: Model, index: int) : collections_item.Item =
return self.items[index]
proc getCollectiblesModel*(self: Model, index: int) : collectibles_model.Model =
if index < self.items.len:
return self.items[index].getCollectiblesModel()
echo "getCollectiblesModel: Invalid index ", index, " with len ", self.items.len
return collectibles_model.newModel()
proc findIndexBySlug(self: Model, slug: string): int =
for i in 0 ..< self.items.len:
if self.items[i].getSlug() == slug:
return i
return -1
proc signalDataChanged(self: Model, top: int, bottom: int, roles: int) {.signal.}
proc emitDataChanged(self: Model, top: int, bottom: int, role: int) =
let topIndex = self.createIndex(top, 0, nil)
let bottomIndex = self.createIndex(bottom, 0, nil)
self.dataChanged(topIndex, bottomIndex, @[role])
self.signalDataChanged(top, bottom, role)
proc updateCollectionCollectibles*(self: Model, slug: string, collectibles: seq[collectibles_item.Item], collectiblesLoaded: bool) =
let idx = self.findIndexBySlug(slug)
if idx > -1:
let collectiblesModel = self.items[idx].getCollectiblesModel()
collectiblesModel.setItems(collectibles)
self.emitDataChanged(idx, idx, CollectionRole.CollectiblesModel.int)
if self.items[idx].getCollectiblesLoaded() != collectiblesLoaded:
self.items[idx].collectiblesLoaded = collectiblesLoaded
self.emitDataChanged(idx, idx, CollectionRole.CollectiblesLoaded.int)

View File

@ -1,13 +0,0 @@
import sequtils, sugar, Tables
import ../../../../../../app_service/service/collectible/service
import collections_item, collectibles_utils
proc collectionToItem*(collection: CollectionData) : Item =
return initItem(
collection.collection.name,
collection.collection.slug,
collection.collection.imageUrl,
collection.collection.ownedAssetCount,
collection.collectiblesLoaded,
toSeq(collection.collectibles.values).map(c => collectibleToItem(c))
)

View File

@ -11,8 +11,6 @@ import ../../../../../app_service/service/network/service as network_service
import ./current_collectible/module as current_collectible_module import ./current_collectible/module as current_collectible_module
import ./models/collections_item as collections_item
import ./models/collections_utils
import ./models/collectibles_item as collectibles_item import ./models/collectibles_item as collectibles_item
import ./models/collectible_trait_item as collectible_trait_item import ./models/collectible_trait_item as collectible_trait_item
import ./models/collectibles_utils import ./models/collectibles_utils
@ -45,7 +43,6 @@ proc newModule*(
result.moduleLoaded = false result.moduleLoaded = false
result.currentCollectibleModule = currentCollectibleModule.newModule(result, collectibleService) result.currentCollectibleModule = currentCollectibleModule.newModule(result, collectibleService)
method delete*(self: Module) = method delete*(self: Module) =
self.view.delete self.view.delete
self.currentCollectibleModule.delete self.currentCollectibleModule.delete
@ -76,6 +73,9 @@ method viewDidLoad*(self: Module) =
method currentCollectibleModuleDidLoad*(self: Module) = method currentCollectibleModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad() self.checkIfModuleDidLoad()
method fetchOwnedCollectibles*(self: Module, limit: int) =
self.controller.fetchOwnedCollectibles(self.chainId, self.address, limit)
method switchAccount*(self: Module, accountIndex: int) = method switchAccount*(self: Module, accountIndex: int) =
let network = self.controller.getNetwork() let network = self.controller.getNetwork()
let account = self.controller.getWalletAccount(accountIndex) let account = self.controller.getWalletAccount(accountIndex)
@ -83,40 +83,30 @@ method switchAccount*(self: Module, accountIndex: int) =
self.chainId = network.chainId self.chainId = network.chainId
self.address = account.address self.address = account.address
self.controller.refreshCollections(self.chainId, self.address) # TODO: Implement a way to reduce the number of full re-fetches. It could be only
self.controller.fetchOwnedCollections(self.chainId, self.address) # when NFT activity was detected for the given account, or if a certain amount of
# time has passed. For now, we fetch every time we select the account.
self.controller.resetOwnedCollectibles(self.chainId, self.address)
self.controller.refreshCollectibles(self.chainId, self.address)
self.currentCollectibleModule.setCurrentAddress(network, self.address) self.currentCollectibleModule.setCurrentAddress(network, self.address)
proc collectionToItem(self: Module, collection: CollectionData) : collections_item.Item = method refreshCollectibles*(self: Module, chainId: int, address: string, collectibles: CollectiblesData) =
var item = collectionToItem(collection) if self.chainId == chainId and self.address == address:
#[ Skeleton items are disabled until problem with OpenSea API is researched. var idsToAdd = newSeq[UniqueID]()
OpenSea is telling us the address owns a certain amount of NFTs from a certain collection, but let append = not collectibles.lastLoadWasFromStart
it doesn't give us the NFTs from that collection when trying to fetch them.
# Append skeleton collectibles if not yet fetched
let model = item.getCollectiblesModel()
let unfetchedCollectiblesCount = item.getOwnedAssetCount() - model.getCount()
if unfetchedCollectiblesCount > 0:
echo "unfetchedCollectiblesCount = ", unfetchedCollectiblesCount, " ", item.getSlug()
let skeletonItems = newSeqWith(unfetchedCollectiblesCount, collectibles_item.initItem())
model.appendItems(skeletonItems)
]#
return item
method setCollections*(self: Module, collections: CollectionsData) = var startIdx = 0
self.view.setCollections( if append:
toSeq(collections.collections.values).map(c => self.collectionToItem(c)), for i in collectibles.ids.len - collectibles.lastLoadCount ..< collectibles.ids.len:
collections.collectionsLoaded idsToAdd.add(collectibles.ids[i])
) else:
idsToAdd = collectibles.ids
method updateCollection*(self: Module, collection: CollectionData) = var newCollectibles = idsToAdd.map(id => (block:
self.view.setCollectibles(collection.collection.slug, let c = self.controller.getCollectible(self.chainId, id)
toSeq(collection.collectibles.values).map(c => collectibleToItem(c)), let co = self.controller.getCollection(self.chainId, c.collectionSlug)
collection.collectiblesLoaded return collectibleToItem(c, co)
) ))
self.view.setCollectibles(newCollectibles, append, collectibles.allLoaded)
method fetchOwnedCollections*(self: Module) =
self.controller.fetchOwnedCollections(self.chainId, self.address)
method fetchOwnedCollectibles*(self: Module, collectionSlug: string) =
self.controller.fetchOwnedCollectibles(self.chainId, self.address, collectionSlug)

View File

@ -1,20 +1,16 @@
import NimQml import NimQml
import ./models/collections_model as collections_model import ./models/collectibles_model
import ./models/collectibles_flat_proxy_model as flat_model import ./models/collectibles_item
import ./models/collections_item as collections_item
import ./models/collectibles_item as collectibles_item
import ./io_interface import ./io_interface
QtObject: QtObject:
type type
View* = ref object of QObject View* = ref object of QObject
delegate: io_interface.AccessInterface delegate: io_interface.AccessInterface
model: collections_model.Model model: Model
flatModel: flat_model.Model
proc delete*(self: View) = proc delete*(self: View) =
self.flatModel.delete
self.model.delete self.model.delete
self.QObject.delete self.QObject.delete
@ -23,7 +19,7 @@ QtObject:
result.QObject.setup result.QObject.setup
result.delegate = delegate result.delegate = delegate
result.model = newModel() result.model = newModel()
result.flatModel = flat_model.newModel(result.model) signalConnect(result.model, "requestFetch(int)", result, "fetchMoreOwnedCollectibles(int)")
proc load*(self: View) = proc load*(self: View) =
self.delegate.viewDidLoad() self.delegate.viewDidLoad()
@ -35,21 +31,9 @@ QtObject:
read = getModel read = getModel
notify = modelChanged notify = modelChanged
proc flatModelChanged*(self: View) {.signal.} proc fetchMoreOwnedCollectibles*(self: View, limit: int) {.slot.} =
proc getFlatModel(self: View): QVariant {.slot.} = self.delegate.fetchOwnedCollectibles(limit)
return newQVariant(self.flatModel)
QtProperty[QVariant] flatModel:
read = getFlatModel
notify = flatModelChanged
proc fetchOwnedCollections*(self: View) {.slot.} = proc setCollectibles*(self: View, collectibles: seq[Item], append: bool, allLoaded: bool) =
self.delegate.fetchOwnedCollections() self.model.setItems(collectibles, append)
self.model.setAllCollectiblesLoaded(allLoaded)
proc fetchOwnedCollectibles*(self: View, collectionSlug: string) {.slot.} =
self.delegate.fetchOwnedCollectibles(collectionSlug)
proc setCollections*(self: View, collections: seq[collections_item.Item], collectionsLoaded: bool) =
self.model.setCollections(collections, collectionsLoaded)
proc setCollectibles*(self: View, collectionsSlug: string, collectibles: seq[collectibles_item.Item], collectiblesLoaded: bool) =
self.model.updateCollectionCollectibles(collectionsSlug, collectibles, collectiblesLoaded)

View File

@ -1,28 +1,8 @@
type
FetchOwnedCollectionsTaskArg = ref object of QObjectTaskArg
chainId*: int
address*: string
const fetchOwnedCollectionsTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[FetchOwnedCollectionsTaskArg](argEncoded)
let output = %* {
"chainId": arg.chainId,
"address": arg.address,
"collections": ""
}
try:
let response = collectibles.getOpenseaCollectionsByOwner(arg.chainId, arg.address)
output["collections"] = response.result
except Exception as e:
let errDesription = e.msg
error "error fetchOwnedCollectionsTaskArg: ", errDesription
arg.finish(output)
type type
FetchOwnedCollectiblesTaskArg = ref object of QObjectTaskArg FetchOwnedCollectiblesTaskArg = ref object of QObjectTaskArg
chainId*: int chainId*: int
address*: string address*: string
collectionSlug: string cursor: string
limit: int limit: int
const fetchOwnedCollectiblesTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = const fetchOwnedCollectiblesTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
@ -30,11 +10,11 @@ const fetchOwnedCollectiblesTaskArg: Task = proc(argEncoded: string) {.gcsafe, n
let output = %* { let output = %* {
"chainId": arg.chainId, "chainId": arg.chainId,
"address": arg.address, "address": arg.address,
"collectionSlug": arg.collectionSlug, "cursor": arg.cursor,
"collectibles": "" "collectibles": ""
} }
try: try:
let response = collectibles.getOpenseaAssetsByOwnerAndCollection(arg.chainId, arg.address, arg.collectionSlug, arg.limit) let response = collectibles.getOpenseaAssetsByOwnerWithCursor(arg.chainId, arg.address, arg.cursor, arg.limit)
output["collectibles"] = response.result output["collectibles"] = response.result
except Exception as e: except Exception as e:
let errDesription = e.msg let errDesription = e.msg

View File

@ -1,5 +1,10 @@
import json, Tables, stint, strformat, strutils import json, Tables, stint, strformat, strutils
# Unique identifier for collectible on a specific chain
type
UniqueID* = object
contractAddress*: string
tokenId*: UInt256
type CollectibleTraitType* {.pure.} = enum type CollectibleTraitType* {.pure.} = enum
Properties = 0, Properties = 0,
@ -11,7 +16,6 @@ type CollectionTrait* = ref object
type CollectionDto* = ref object type CollectionDto* = ref object
name*, slug*, imageUrl*: string name*, slug*, imageUrl*: string
ownedAssetCount*: Uint256
trait*: Table[string, CollectionTrait] trait*: Table[string, CollectionTrait]
type CollectibleTrait* = ref object type CollectibleTrait* = ref object
@ -20,7 +24,7 @@ type CollectibleTrait* = ref object
type CollectibleDto* = ref object type CollectibleDto* = ref object
id*: int id*: int
tokenId*: Uint256 tokenId*: Uint256
name*, description*, permalink*, imageThumbnailUrl*, imageUrl*, address*, backgroundColor*: string address*, collectionSlug*, name*, description*, permalink*, imageThumbnailUrl*, imageUrl*, backgroundColor*: string
properties*, rankings*, statistics*: seq[CollectibleTrait] properties*, rankings*, statistics*: seq[CollectibleTrait]
proc newCollectibleDto*: CollectibleDto = proc newCollectibleDto*: CollectibleDto =
@ -31,6 +35,14 @@ proc newCollectibleDto*: CollectibleDto =
proc isValid*(self: CollectibleDto): bool = proc isValid*(self: CollectibleDto): bool =
return self.id >= 0 return self.id >= 0
proc newCollectionDto*: CollectionDto =
return CollectionDto(
slug: ""
)
proc isValid*(self: CollectionDto): bool =
return self.slug != ""
proc isNumeric(s: string): bool = proc isNumeric(s: string): bool =
try: try:
discard s.parseFloat() discard s.parseFloat()
@ -39,14 +51,14 @@ proc isNumeric(s: string): bool =
result = false result = false
proc `$`*(self: CollectionDto): string = proc `$`*(self: CollectionDto): string =
return fmt"CollectionDto(name:{self.name}, slug:{self.slug}, owned asset count:{self.ownedAssetCount})" return fmt"CollectionDto(name:{self.name}, slug:{self.slug})"
proc `$`*(self: CollectibleDto): string = proc `$`*(self: CollectibleDto): string =
return fmt"CollectibleDto(id:{self.id}, token_id:{self.tokenId}, name:{self.name}, description:{self.description}, permalink:{self.permalink}, address:{self.address}, imageUrl: {self.imageUrl}, imageThumbnailUrl: {self.imageThumbnailUrl}, backgroundColor: {self.backgroundColor})" return fmt"CollectibleDto(id:{self.id}, address:{self.address}, tokenId:{self.tokenId}, collectionSlug:{self.collectionSlug}, name:{self.name}, description:{self.description}, permalink:{self.permalink}, imageUrl: {self.imageUrl}, imageThumbnailUrl: {self.imageThumbnailUrl}, backgroundColor: {self.backgroundColor})"
proc getCollectionTraits*(jsonCollection: JsonNode): Table[string, CollectionTrait] = proc getCollectionTraits*(jsonCollection: JsonNode): Table[string, CollectionTrait] =
var traitList: Table[string, CollectionTrait] = initTable[string, CollectionTrait]() var traitList: Table[string, CollectionTrait] = initTable[string, CollectionTrait]()
for key, value in jsonCollection{"traits"}: for key, value in jsonCollection{"traits"}.getFields():
traitList[key] = CollectionTrait(min: value{"min"}.getFloat, max: value{"max"}.getFloat) traitList[key] = CollectionTrait(min: value{"min"}.getFloat, max: value{"max"}.getFloat)
return traitList return traitList
@ -55,7 +67,6 @@ proc toCollectionDto*(jsonCollection: JsonNode): CollectionDto =
name: jsonCollection{"name"}.getStr, name: jsonCollection{"name"}.getStr,
slug: jsonCollection{"slug"}.getStr, slug: jsonCollection{"slug"}.getStr,
imageUrl: jsonCollection{"image_url"}.getStr, imageUrl: jsonCollection{"image_url"}.getStr,
ownedAssetCount: stint.parse(jsonCollection{"owned_asset_count"}.getStr, Uint256),
trait: getCollectionTraits(jsonCollection) trait: getCollectionTraits(jsonCollection)
) )
@ -79,13 +90,14 @@ proc getTrait*(jsonAsset: JsonNode, traitType: CollectibleTraitType): seq[Collec
proc toCollectibleDto*(jsonAsset: JsonNode): CollectibleDto = proc toCollectibleDto*(jsonAsset: JsonNode): CollectibleDto =
return CollectibleDto( return CollectibleDto(
id: jsonAsset{"id"}.getInt, id: jsonAsset{"id"}.getInt,
address: jsonAsset{"asset_contract"}{"address"}.getStr,
tokenId: stint.parse(jsonAsset{"token_id"}.getStr, Uint256), tokenId: stint.parse(jsonAsset{"token_id"}.getStr, Uint256),
collectionSlug: jsonAsset{"collection"}{"slug"}.getStr,
name: jsonAsset{"name"}.getStr, name: jsonAsset{"name"}.getStr,
description: jsonAsset{"description"}.getStr, description: jsonAsset{"description"}.getStr,
permalink: jsonAsset{"permalink"}.getStr, permalink: jsonAsset{"permalink"}.getStr,
imageThumbnailUrl: jsonAsset{"image_thumbnail_url"}.getStr, imageThumbnailUrl: jsonAsset{"image_thumbnail_url"}.getStr,
imageUrl: jsonAsset{"image_url"}.getStr, imageUrl: jsonAsset{"image_url"}.getStr,
address: jsonAsset{"asset_contract"}{"address"}.getStr,
backgroundColor: jsonAsset{"background_color"}.getStr, backgroundColor: jsonAsset{"background_color"}.getStr,
properties: getTrait(jsonAsset, CollectibleTraitType.Properties), properties: getTrait(jsonAsset, CollectibleTraitType.Properties),
rankings: getTrait(jsonAsset, CollectibleTraitType.Rankings), rankings: getTrait(jsonAsset, CollectibleTraitType.Rankings),

View File

@ -1,4 +1,4 @@
import NimQml, Tables, chronicles, sequtils, json, sugar, stint, hashes import NimQml, Tables, chronicles, sequtils, json, sugar, stint, hashes, strformat, times
import ../../../app/core/eventemitter import ../../../app/core/eventemitter
import ../../../app/core/tasks/[qt, threadpool] import ../../../app/core/tasks/[qt, threadpool]
@ -16,29 +16,14 @@ logScope:
topics = "collectible-service" topics = "collectible-service"
# Signals which may be emitted by this service: # Signals which may be emitted by this service:
const SIGNAL_OWNED_COLLECTIONS_UPDATED* = "ownedCollectionsUpdated" const SIGNAL_OWNED_COLLECTIBLES_UPDATE_STARTED* = "ownedCollectiblesUpdateStarted"
const SIGNAL_OWNED_COLLECTIBLES_UPDATED* = "ownedCollectiblesUpdated" const SIGNAL_OWNED_COLLECTIBLES_UPDATE_FINISHED* = "ownedCollectiblesUpdateFinished"
const SIGNAL_COLLECTIBLES_UPDATED* = "collectiblesUpdated" const SIGNAL_COLLECTIBLES_UPDATED* = "collectiblesUpdated"
# Maximum number of collectibles to be fetched at a time
const limit = 200
# Unique identifier for collectible in a specific chain
type
UniqueID* = object
contractAddress*: string
tokenId*: UInt256
type
OwnedCollectionsUpdateArgs* = ref object of Args
chainId*: int
address*: string
type type
OwnedCollectiblesUpdateArgs* = ref object of Args OwnedCollectiblesUpdateArgs* = ref object of Args
chainId*: int chainId*: int
address*: string address*: string
collectionSlug*: string
type type
CollectiblesUpdateArgs* = ref object of Args CollectiblesUpdateArgs* = ref object of Args
@ -46,32 +31,47 @@ type
ids*: seq[UniqueID] ids*: seq[UniqueID]
type type
CollectionData* = ref object CollectiblesData* = ref object
collection*: CollectionDto isFetching*: bool
collectiblesLoaded*: bool allLoaded*: bool
collectibles*: OrderedTableRef[int, CollectibleDto] # [collectibleId, CollectibleDto] lastLoadWasFromStart*: bool
lastLoadFromStartTimestamp*: DateTime
proc newCollectionData*(collection: CollectionDto): CollectionData = lastLoadCount*: int
new(result) previousCursor*: string
result.collection = collection nextCursor*: string
result.collectiblesLoaded = false ids*: seq[UniqueID]
result.collectibles = newOrderedTable[int, CollectibleDto]()
type
CollectionsData* = ref object
collectionsLoaded*: bool
collections*: OrderedTableRef[string, CollectionData] # [collectionSlug, CollectionData]
proc newCollectionsData*(): CollectionsData = proc newCollectiblesData*(): CollectiblesData =
new(result) new(result)
result.collectionsLoaded = false result.isFetching = false
result.collections = newOrderedTable[string, CollectionData]() result.allLoaded = false
result.lastLoadWasFromStart = false
result.lastLoadFromStartTimestamp = now() - initDuration(weeks = 1)
result.lastLoadCount = 0
result.previousCursor = ""
result.nextCursor = ""
result.ids = @[]
proc `$`*(self: CollectiblesData): string =
return fmt"""CollectiblesData(
isFetching:{self.isFetching},
allLoaded:{self.allLoaded},
lastLoadWasFromStart:{self.lastLoadWasFromStart},
lastLoadFromStartTimestamp:{self.lastLoadFromStartTimestamp},
lastLoadCount:{self.lastLoadCount},
previousCursor:{self.previousCursor},
nextCursor:{self.nextCursor},
ids:{self.ids}
)"""
type type
AdressesData* = TableRef[string, CollectionsData] # [address, CollectionsData] AdressesData = TableRef[string, CollectiblesData] # [address, CollectiblesData]
type type
ChainsData* = TableRef[int, AdressesData] # [chainId, AdressesData] ChainsData = TableRef[int, AdressesData] # [chainId, AdressesData]
type
CollectiblesResult = tuple[success: bool, collectibles: seq[CollectibleDto], collections: seq[CollectionDto], previousCursor: string, nextCursor: string]
proc hash(x: UniqueID): Hash = proc hash(x: UniqueID): Hash =
result = x.contractAddress.hash !& x.tokenId.hash result = x.contractAddress.hash !& x.tokenId.hash
@ -84,7 +84,8 @@ QtObject:
threadpool: ThreadPool threadpool: ThreadPool
networkService: network_service.Service networkService: network_service.Service
ownershipData: ChainsData ownershipData: ChainsData
data: TableRef[int, TableRef[UniqueID, CollectibleDto]] # [chainId, [UniqueID, CollectibleDto]] collectibles: TableRef[int, TableRef[UniqueID, CollectibleDto]] # [chainId, [UniqueID, CollectibleDto]]
collections: TableRef[int, TableRef[string, CollectionDto]] # [chainId, [slug, CollectionDto]]
proc delete*(self: Service) = proc delete*(self: Service) =
self.QObject.delete self.QObject.delete
@ -100,115 +101,126 @@ QtObject:
result.threadpool = threadpool result.threadpool = threadpool
result.networkService = networkService result.networkService = networkService
result.ownershipData = newTable[int, AdressesData]() result.ownershipData = newTable[int, AdressesData]()
result.data = newTable[int, TableRef[UniqueID, CollectibleDto]]() result.collectibles = newTable[int, TableRef[UniqueID, CollectibleDto]]()
result.collections = newTable[int, TableRef[string, CollectionDto]]()
proc init*(self: Service) = proc init*(self: Service) =
discard discard
proc insertAddressIfNeeded*(self: Service, chainId: int, address: string) = proc prepareOwnershipData(self: Service, chainId: int, address: string, reset: bool = false) =
if not self.ownershipData.hasKey(chainId): if not self.ownershipData.hasKey(chainId):
self.ownershipData[chainId] = newTable[string, CollectionsData]() self.ownershipData[chainId] = newTable[string, CollectiblesData]()
let chainData = self.ownershipData[chainId] let chainData = self.ownershipData[chainId]
if not chainData.hasKey(address): if reset or not chainData.hasKey(address):
chainData[address] = newCollectionsData() chainData[address] = newCollectiblesData()
proc updateOwnedCollectionsCache*(self: Service, chainId: int, address: string, collections: seq[CollectionDto]) = let addressData = self.ownershipData[chainId][address]
addressData.lastLoadWasFromStart = reset
if reset:
addressData.lastLoadFromStartTimestamp = now()
proc updateOwnedCollectibles(self: Service, chainId: int, address: string, previousCursor: string, nextCursor: string, collectibles: seq[CollectibleDto]) =
try: try:
let oldAddressData = self.ownershipData[chainId][address] let collectiblesData = self.ownershipData[chainId][address]
collectiblesData.previousCursor = previousCursor
# Start with empty object. Only add newly received collections, so removed ones are discarded collectiblesData.nextCursor = nextCursor
let newAddressData = newCollectionsData() collectiblesData.allLoaded = (nextCursor == "")
for collection in collections:
newAddressData.collections[collection.slug] = newCollectionData(collection)
if oldAddressData.collections.hasKey(collection.slug):
let oldCollection = oldAddressData.collections[collection.slug]
let newCollection = newAddressData.collections[collection.slug]
# Take collectibles from old collection
newCollection.collectiblesLoaded = oldCollection.collectiblesLoaded
newCollection.collectibles = oldCollection.collectibles
newAddressData.collectionsLoaded = true
self.ownershipData[chainId][address] = newAddressData
var data = OwnedCollectionsUpdateArgs()
data.chainId = chainId
data.address = address
self.events.emit(SIGNAL_OWNED_COLLECTIONS_UPDATED, data)
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
proc updateOwnedCollectiblesCache*(self: Service, chainId: int, address: string, collectionSlug: string, collectibles: seq[CollectibleDto]) =
try:
let collection = self.ownershipData[chainId][address].collections[collectionSlug]
collection.collectibles.clear()
var count = 0
for collectible in collectibles: for collectible in collectibles:
collection.collectibles[collectible.id] = collectible let newId = UniqueID(
collection.collectiblesLoaded = true contractAddress: collectible.address,
tokenId: collectible.tokenId
var data = OwnedCollectiblesUpdateArgs() )
data.chainId = chainId if not collectiblesData.ids.any(id => newId == id):
data.address = address collectiblesData.ids.add(newId)
data.collectionSlug = collectionSlug count = count + 1
self.events.emit(SIGNAL_OWNED_COLLECTIBLES_UPDATED, data) collectiblesData.lastLoadCount = count
except Exception as e: except Exception as e:
let errDesription = e.msg let errDesription = e.msg
error "error: ", errDesription error "error: ", errDesription
proc updateCollectiblesCache*(self: Service, chainId: int, collectibles: seq[CollectibleDto]) = proc updateCollectiblesCache*(self: Service, chainId: int, collectibles: seq[CollectibleDto], collections: seq[CollectionDto]) =
if not self.data.hasKey(chainId): if not self.collectibles.hasKey(chainId):
self.data[chainId] = newTable[UniqueID, CollectibleDto]() self.collectibles[chainId] = newTable[UniqueID, CollectibleDto]()
if not self.collections.hasKey(chainId):
self.collections[chainId] = newTable[string, CollectionDto]()
var data = CollectiblesUpdateArgs() var data = CollectiblesUpdateArgs()
data.chainId = chainId data.chainId = chainId
for collection in collections:
let slug = collection.slug
self.collections[chainId][slug] = collection
for collectible in collectibles: for collectible in collectibles:
let id = UniqueID( let id = UniqueID(
contractAddress: collectible.address, contractAddress: collectible.address,
tokenId: collectible.tokenId tokenId: collectible.tokenId
) )
self.data[chainId][id] = collectible self.collectibles[chainId][id] = collectible
data.ids.add(id) data.ids.add(id)
self.events.emit(SIGNAL_COLLECTIBLES_UPDATED, data) self.events.emit(SIGNAL_COLLECTIBLES_UPDATED, data)
proc getOwnedCollections*(self: Service, chainId: int, address: string) : CollectionsData = proc getOwnedCollectibles*(self: Service, chainId: int, address: string) : CollectiblesData =
try: try:
return self.ownershipData[chainId][address] return self.ownershipData[chainId][address]
except: except:
discard discard
return newCollectionsData() return newCollectiblesData()
proc getOwnedCollection*(self: Service, chainId: int, address: string, collectionSlug: string) : CollectionData =
try:
return self.ownershipData[chainId][address].collections[collectionSlug]
except:
discard
return newCollectionData(CollectionDto())
proc getCollectible*(self: Service, chainId: int, id: UniqueID) : CollectibleDto = proc getCollectible*(self: Service, chainId: int, id: UniqueID) : CollectibleDto =
try: try:
return self.data[chainId][id] return self.collectibles[chainId][id]
except: except:
discard discard
return newCollectibleDto() return newCollectibleDto()
proc onRxCollectibles*(self: Service, response: string) {.slot.} = proc getCollection*(self: Service, chainId: int, slug: string) : CollectionDto =
try:
return self.collections[chainId][slug]
except:
discard
return newCollectionDto()
proc processCollectiblesResult(responseObj: JsonNode) : CollectiblesResult =
result.success = false
let collectiblesContainerJson = responseObj["collectibles"]
if collectiblesContainerJson.kind == JObject:
let previousCursorJson = collectiblesContainerJson["previous"]
let nextCursorJson = collectiblesContainerJson["next"]
let collectiblesJson = collectiblesContainerJson["assets"]
if previousCursorJson.kind == JString and nextCursorJson.kind == JString:
result.previousCursor = previousCursorJson.getStr()
result.nextCursor = nextCursorJson.getStr()
for collectibleJson in collectiblesJson.getElems():
if collectibleJson.kind == JObject:
result.collectibles.add(collectibleJson.toCollectibleDto())
let collectionJson = collectibleJson["collection"]
if collectionJson.kind == JObject:
result.collections.add(collectionJson.toCollectionDto())
else:
return
else:
return
result.success = true
proc onRxCollectibles(self: Service, response: string) {.slot.} =
try: try:
let responseObj = response.parseJson let responseObj = response.parseJson
if (responseObj.kind == JObject): if (responseObj.kind == JObject):
let chainIdJson = responseObj["chainId"] let chainIdJson = responseObj["chainId"]
let collectiblesJson = responseObj["collectibles"] if chainIdJson.kind == JInt:
if (chainIdJson.kind == JInt and
collectiblesJson.kind == JArray):
let chainId = chainIdJson.getInt() let chainId = chainIdJson.getInt()
let collectibles = map(collectiblesJson.getElems(), proc(x: JsonNode): CollectibleDto = x.toCollectibleDto()) let (success, collectibles, collections, _, _) = processCollectiblesResult(responseObj)
self.updateCollectiblesCache(chainId, collectibles) if success:
self.updateCollectiblesCache(chainId, collectibles, collections)
except Exception as e: except Exception as e:
let errDescription = e.msg let errDescription = e.msg
error "error onRxCollectibles: ", errDescription error "error onRxCollectibles: ", errDescription
@ -223,95 +235,63 @@ QtObject:
contractAddress: id.contractAddress, contractAddress: id.contractAddress,
tokenID: id.tokenId.toString() tokenID: id.tokenId.toString()
)), )),
limit: limit limit: len(ids)
)
self.threadpool.start(arg)
proc onRxOwnedCollections*(self: Service, response: string) {.slot.} =
try:
let responseObj = response.parseJson
if (responseObj.kind == JObject):
let chainIdJson = responseObj["chainId"]
let addressJson = responseObj["address"]
let validAccount = (chainIdJson.kind == JInt and
addressJson.kind == JString)
if (validAccount):
let chainId = chainIdJson.getInt()
let address = addressJson.getStr()
var collections: seq[CollectionDto]
let collectionsJson = responseObj["collections"]
if (collectionsJson.kind == JArray):
collections = map(collectionsJson.getElems(), proc(x: JsonNode): CollectionDto = x.toCollectionDto())
self.updateOwnedCollectionsCache(chainId, address, collections)
except Exception as e:
let errDescription = e.msg
error "error onRxOwnedCollections: ", errDescription
proc fetchOwnedCollections*(self: Service, chainId: int, address: string) =
self.insertAddressIfNeeded(chainId, address)
let arg = FetchOwnedCollectionsTaskArg(
tptr: cast[ByteAddress](fetchOwnedCollectionsTaskArg),
vptr: cast[ByteAddress](self.vptr),
slot: "onRxOwnedCollections",
chainId: chainId,
address: address,
) )
self.threadpool.start(arg) self.threadpool.start(arg)
proc onRxOwnedCollectibles*(self: Service, response: string) {.slot.} = proc onRxOwnedCollectibles(self: Service, response: string) {.slot.} =
var data = OwnedCollectiblesUpdateArgs() var data = OwnedCollectiblesUpdateArgs()
try: try:
let responseObj = response.parseJson let responseObj = response.parseJson
if (responseObj.kind == JObject): if (responseObj.kind == JObject):
let chainIdJson = responseObj["chainId"] let chainIdJson = responseObj["chainId"]
let addressJson = responseObj["address"] let addressJson = responseObj["address"]
let collectionSlugJson = responseObj["collectionSlug"] if (chainIdJson.kind == JInt and
addressJson.kind == JString):
let validCollection = (chainIdJson.kind == JInt and data.chainId = chainIdJson.getInt()
addressJson.kind == JString and data.address = addressJson.getStr()
collectionSlugJson.kind == JString) self.ownershipData[data.chainId][data.address].isFetching = false
if (validCollection): let (success, collectibles, collections, prevCursor, nextCursor) = processCollectiblesResult(responseObj)
let chainId = chainIdJson.getInt() if success:
let address = addressJson.getStr() self.updateCollectiblesCache(data.chainId, collectibles, collections)
let collectionSlug = collectionSlugJson.getStr() self.updateOwnedCollectibles(data.chainId, data.address, prevCursor, nextCursor, collectibles)
var collectibles: seq[CollectibleDto]
let collectiblesJson = responseObj["collectibles"]
if (collectiblesJson.kind == JArray):
collectibles = map(collectiblesJson.getElems(), proc(x: JsonNode): CollectibleDto = x.toCollectibleDto())
self.updateOwnedCollectiblesCache(chainId, address, collectionSlug, collectibles)
self.updateCollectiblesCache(data.chainId, collectibles)
except Exception as e: except Exception as e:
let errDescription = e.msg let errDescription = e.msg
error "error onRxOwnedCollectibles: ", errDescription error "error onRxOwnedCollectibles: ", errDescription
self.events.emit(SIGNAL_OWNED_COLLECTIBLES_UPDATE_FINISHED, data)
proc fetchOwnedCollectibles*(self: Service, chainId: int, address: string, collectionSlug: string) = proc resetOwnedCollectibles*(self: Service, chainId: int, address: string) =
self.insertAddressIfNeeded(chainId, address) self.prepareOwnershipData(chainId, address, true)
let collections = self.ownershipData[chainId][address].collections var data = OwnedCollectiblesUpdateArgs()
data.chainId = chainId
data.address = address
self.events.emit(SIGNAL_OWNED_COLLECTIBLES_UPDATE_FINISHED, data)
if not collections.hasKey(collectionSlug): proc fetchOwnedCollectibles*(self: Service, chainId: int, address: string, limit: int) =
error "error fetchOwnedCollectibles: Attempting to fetch collectibles from unknown collection: ", collectionSlug self.prepareOwnershipData(chainId, address, false)
let collectiblesData = self.ownershipData[chainId][address]
if collectiblesData.isFetching:
return return
if collectiblesData.allLoaded:
return
collectiblesData.isFetching = true
var data = OwnedCollectiblesUpdateArgs()
data.chainId = chainId
data.address = address
self.events.emit(SIGNAL_OWNED_COLLECTIBLES_UPDATE_STARTED, data)
let arg = FetchOwnedCollectiblesTaskArg( let arg = FetchOwnedCollectiblesTaskArg(
tptr: cast[ByteAddress](fetchOwnedCollectiblesTaskArg), tptr: cast[ByteAddress](fetchOwnedCollectiblesTaskArg),
vptr: cast[ByteAddress](self.vptr), vptr: cast[ByteAddress](self.vptr),
slot: "onRxOwnedCollectibles", slot: "onRxOwnedCollectibles",
chainId: chainId, chainId: chainId,
address: address, address: address,
collectionSlug: collectionSlug, cursor: collectiblesData.nextCursor,
limit: limit limit: limit
) )
self.threadpool.start(arg) self.threadpool.start(arg)
proc fetchAllOwnedCollectibles*(self: Service, chainId: int, address: string) =
try:
for collectionSlug, _ in self.ownershipData[chainId][address].collections:
self.fetchOwnedCollectibles(chainId, address, collectionSlug)
except Exception as e:
let errDescription = e.msg
error "error fetchAllOwnedCollectibles: ", errDescription

View File

@ -217,8 +217,12 @@ QtObject:
for tx in historyData["history"].getElems(): for tx in historyData["history"].getElems():
transactions.add(tx.toTransactionDto()) transactions.add(tx.toTransactionDto())
for c in historyData["collectibles"].getElems(): let collectiblesContainerJson = historyData["collectibles"]
collectibles.add(c.toCollectibleDto()) if collectiblesContainerJson.kind == JObject:
let collectiblesJson = collectiblesContainerJson["assets"]
if collectiblesJson.kind == JArray:
for c in collectiblesJson.getElems():
collectibles.add(c.toCollectibleDto())
if self.allTxLoaded.hasKey(address): if self.allTxLoaded.hasKey(address):
self.allTxLoaded[address] = self.allTxLoaded[address] and allTxLoaded self.allTxLoaded[address] = self.allTxLoaded[address] and allTxLoaded

View File

@ -18,14 +18,10 @@ proc `==`*(a, b: NFTUniqueID): bool =
result = a.contractAddress == b.contractAddress and result = a.contractAddress == b.contractAddress and
a.tokenID == b.tokenID a.tokenID == b.tokenID
rpc(getOpenseaCollectionsByOwner, "wallet"): rpc(getOpenseaAssetsByOwnerWithCursor, "wallet"):
chainId: int chainId: int
address: string address: string
cursor: string
rpc(getOpenseaAssetsByOwnerAndCollection, "wallet"):
chainId: int
address: string
collectionSlug: string
limit: int limit: int
rpc(getOpenseaAssetsByNFTUniqueID, "wallet"): rpc(getOpenseaAssetsByNFTUniqueID, "wallet"):

View File

@ -33,6 +33,7 @@ ColumnLayout {
font.pixelSize: isNarrowMode ? 15 : 22 font.pixelSize: isNarrowMode ? 15 : 22
lineHeight: isNarrowMode ? 22 : 30 lineHeight: isNarrowMode ? 22 : 30
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
elide: Text.ElideRight
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }
} }
@ -57,6 +58,7 @@ ColumnLayout {
id: collectibleIdTopRow id: collectibleIdTopRow
sourceComponent: collectibleIdComponent sourceComponent: collectibleIdComponent
visible: !root.isNarrowMode visible: !root.isNarrowMode
Layout.fillWidth: true
Binding { Binding {
target: collectibleIdTopRow.item target: collectibleIdTopRow.item
@ -74,6 +76,7 @@ ColumnLayout {
id: collectibleIdBottomRow id: collectibleIdBottomRow
sourceComponent: collectibleIdComponent sourceComponent: collectibleIdComponent
visible: root.isNarrowMode visible: root.isNarrowMode
Layout.maximumWidth: root.width - parent.spacing - networkTag.width
Binding { Binding {
target: collectibleIdBottomRow.item target: collectibleIdBottomRow.item

View File

@ -28,8 +28,7 @@ QtObject {
property string signingPhrase: walletSection.signingPhrase property string signingPhrase: walletSection.signingPhrase
property string mnemonicBackedUp: walletSection.isMnemonicBackedUp property string mnemonicBackedUp: walletSection.isMnemonicBackedUp
property var collections: walletSectionCollectibles.model property var flatCollectibles: walletSectionCollectibles.model
property var flatCollectibles: walletSectionCollectibles.flatModel
property var currentCollectible: walletSectionCurrentCollectible property var currentCollectible: walletSectionCurrentCollectible
property var savedAddresses: SortFilterProxyModel { property var savedAddresses: SortFilterProxyModel {
@ -185,10 +184,6 @@ QtObject {
return globalUtils.hex2Dec(value) return globalUtils.hex2Dec(value)
} }
function fetchOwnedCollectibles(slug) {
walletSectionCollectibles.fetchOwnedCollectibles(slug)
}
function getCollectionMaxValue(traitType, value, maxValue, collectionIndex) { function getCollectionMaxValue(traitType, value, maxValue, collectionIndex) {
// Not Refactored Yet // Not Refactored Yet
// if(maxValue !== "") // if(maxValue !== "")
@ -198,8 +193,8 @@ QtObject {
// walletModelV2Inst.collectiblesView.collections.getCollectionTraitMaxValue(collectionIndex, traitType).toString(); // walletModelV2Inst.collectiblesView.collections.getCollectionTraitMaxValue(collectionIndex, traitType).toString();
} }
function selectCollectible(slug, id) { function selectCollectible(address, tokenId) {
walletSectionCurrentCollectible.update(slug, id) walletSectionCurrentCollectible.update(address, tokenId)
} }
function getNameForSavedWalletAddress(address) { function getNameForSavedWalletAddress(address) {

View File

@ -4,6 +4,7 @@ import QtQuick.Controls 2.13
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import shared.panels 1.0 import shared.panels 1.0
import utils 1.0 import utils 1.0
@ -15,9 +16,7 @@ Item {
property var collectiblesModel property var collectiblesModel
width: parent.width width: parent.width
signal collectibleClicked(string collectionSlug, int collectibleId) signal collectibleClicked(string address, string tokenId)
readonly property bool areCollectionsLoaded: root.collectiblesModel.collectionsLoaded
Loader { Loader {
id: contentLoader id: contentLoader
@ -25,7 +24,7 @@ Item {
height: parent.height height: parent.height
sourceComponent: { sourceComponent: {
if (root.areCollectionsLoaded && root.collectiblesModel.collectionCount === 0) if (root.collectiblesModel.allCollectiblesLoaded && root.collectiblesModel.count === 0)
return empty; return empty;
return loaded; return loaded;
} }
@ -49,18 +48,19 @@ Item {
StatusGridView { StatusGridView {
id: gridView id: gridView
anchors.fill: parent anchors.fill: parent
model: root.areCollectionsLoaded ? root.collectiblesModel : Constants.dummyModelItems model: root.collectiblesModel
cellHeight: 229 cellHeight: 229
cellWidth: 176 cellWidth: 176
delegate: CollectibleView { delegate: CollectibleView {
height: gridView.cellHeight height: gridView.cellHeight
width: gridView.cellWidth width: gridView.cellWidth
collectibleModel: root.areCollectionsLoaded ? model : undefined collectibleModel: model
isLoadingDelegate: !root.areCollectionsLoaded
onCollectibleClicked: { onCollectibleClicked: {
root.collectibleClicked(slug, collectibleId); root.collectibleClicked(address, tokenId);
} }
} }
ScrollBar.vertical: StatusScrollBar {}
} }
} }
} }

View File

@ -99,7 +99,7 @@ Item {
CollectiblesView { CollectiblesView {
collectiblesModel: RootStore.flatCollectibles collectiblesModel: RootStore.flatCollectibles
onCollectibleClicked: { onCollectibleClicked: {
RootStore.selectCollectible(collectionSlug, collectibleId) RootStore.selectCollectible(address, tokenId)
stack.currentIndex = 1 stack.currentIndex = 1
} }
} }

View File

@ -5,6 +5,7 @@ import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import utils 1.0 import utils 1.0
import shared.controls 1.0 import shared.controls 1.0
@ -26,7 +27,7 @@ Item {
asset.name: currentCollectible.collectionImageUrl asset.name: currentCollectible.collectionImageUrl
asset.isImage: true asset.isImage: true
primaryText: currentCollectible.collectionName primaryText: currentCollectible.collectionName
secondaryText: currentCollectible.id secondaryText: "#" + currentCollectible.tokenId
isNarrowMode: root.isNarrowMode isNarrowMode: root.isNarrowMode
networkShortName: currentCollectible.networkShortName networkShortName: currentCollectible.networkShortName
networkColor: currentCollectible.networkColor networkColor: currentCollectible.networkColor

View File

@ -15,14 +15,12 @@ Item {
id: root id: root
property var collectibleModel property var collectibleModel
property bool isLoadingDelegate
signal collectibleClicked(string slug, int collectibleId) signal collectibleClicked(string address, string tokenId)
QtObject { QtObject {
id: d id: d
readonly property bool modeDataValid: !!root.collectibleModel && root.collectibleModel !== undefined readonly property bool modeDataValid: !!root.collectibleModel && root.collectibleModel !== undefined && root.collectibleModel.id >= 0
readonly property bool isLoaded: modeDataValid ? root.collectibleModel.collectionCollectiblesLoaded : false
} }
implicitHeight: 225 implicitHeight: 225
@ -48,7 +46,7 @@ Item {
color: d.modeDataValid ? root.collectibleModel.backgroundColor : "transparent" color: d.modeDataValid ? root.collectibleModel.backgroundColor : "transparent"
Loader { Loader {
anchors.fill: parent anchors.fill: parent
active: root.isLoadingDelegate active: root.collectibleModel.isLoading
sourceComponent: LoadingComponent {radius: image.radius} sourceComponent: LoadingComponent {radius: image.radius}
} }
} }
@ -57,7 +55,7 @@ Item {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.topMargin: 9 Layout.topMargin: 9
Layout.preferredWidth: isLoadingDelegate ? 134 : 144 Layout.preferredWidth: root.collectibleModel.isLoading ? 134 : 144
Layout.preferredHeight: 21 Layout.preferredHeight: 21
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@ -65,22 +63,22 @@ Item {
customColor: Theme.palette.directColor1 customColor: Theme.palette.directColor1
font.weight: Font.DemiBold font.weight: Font.DemiBold
elide: Text.ElideRight elide: Text.ElideRight
text: isLoadingDelegate ? Constants.dummyText : d.isLoaded && d.modeDataValid ? root.collectibleModel.name : "..." text: root.collectibleModel.isLoading ? Constants.dummyText : d.modeDataValid ? root.collectibleModel.name : "..."
loading: root.isLoadingDelegate loading: root.collectibleModel.isLoading
} }
StatusTextWithLoadingState { StatusTextWithLoadingState {
id: collectionLabel id: collectionLabel
Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.preferredWidth: isLoadingDelegate ? 88 : 144 Layout.preferredWidth: root.collectibleModel.isLoading ? 88 : 144
Layout.preferredHeight: 18 Layout.preferredHeight: 18
horizontalAlignment: Text.AlignLeft horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
font.pixelSize: 13 font.pixelSize: 13
customColor: Theme.palette.baseColor1 customColor: Theme.palette.baseColor1
elide: Text.ElideRight elide: Text.ElideRight
text: isLoadingDelegate ? Constants.dummyText : d.modeDataValid ? root.collectibleModel.collectionName : "" text: root.collectibleModel.isLoading ? Constants.dummyText : d.modeDataValid ? root.collectibleModel.collectionName : "..."
loading: root.isLoadingDelegate loading: root.collectibleModel.isLoading
} }
} }
@ -90,15 +88,15 @@ Item {
border.width: 1 border.width: 1
border.color: Theme.palette.primaryColor1 border.color: Theme.palette.primaryColor1
color: Theme.palette.indirectColor3 color: Theme.palette.indirectColor3
visible: d.isLoaded && mouse.containsMouse visible: !root.collectibleModel.isLoading && mouse.containsMouse
} }
MouseArea { MouseArea {
id: mouse id: mouse
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onClicked: { onClicked: {
if (d.isLoaded) { if (d.modeDataValid && !root.collectibleModel.isLoading) {
root.collectibleClicked(root.collectibleModel.collectionSlug, root.collectibleModel.id); root.collectibleClicked(root.collectibleModel.address, root.collectibleModel.tokenId);
} }
} }
} }

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit f60716412259aece88d899d09fc71de4c4ba5d2e Subproject commit 5ecb7b68eefada3725c360f68fba7ac92b612c82