From 20733272f28f28e862f7aa5c04a5dac318d03d63 Mon Sep 17 00:00:00 2001 From: Dario Gabriel Lipicar Date: Thu, 19 Jan 2023 21:44:35 -0300 Subject: [PATCH] feat(@desktop/wallet): implement new collectibles view Fixes #8810 --- .../collectibles/controller.nim | 1 + .../models/collectibles_flat_proxy_model.nim | 221 ++++++++++++++++++ .../models/collectibles_model.nim | 68 +++--- .../models/collectibles_utils.nim | 16 ++ .../collectibles/models/collections_item.nim | 3 +- .../collectibles/models/collections_model.nim | 85 ++++--- .../collectibles/models/collections_utils.nim | 13 ++ .../wallet_section/collectibles/module.nim | 48 ++-- .../main/wallet_section/collectibles/view.nim | 32 +-- .../service/collectible/service.nim | 22 +- ui/app/AppLayouts/Wallet/stores/RootStore.qml | 1 + .../Wallet/views/CollectiblesView.qml | 68 ++---- .../AppLayouts/Wallet/views/RightTabView.qml | 2 + .../CollectibleCollectionView.qml | 121 ---------- .../views/collectibles/CollectibleView.qml | 90 +++++++ 15 files changed, 513 insertions(+), 278 deletions(-) create mode 100644 src/app/modules/main/wallet_section/collectibles/models/collectibles_flat_proxy_model.nim create mode 100644 src/app/modules/main/wallet_section/collectibles/models/collectibles_utils.nim create mode 100644 src/app/modules/main/wallet_section/collectibles/models/collections_utils.nim delete mode 100644 ui/app/AppLayouts/Wallet/views/collectibles/CollectibleCollectionView.qml create mode 100644 ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml diff --git a/src/app/modules/main/wallet_section/collectibles/controller.nim b/src/app/modules/main/wallet_section/collectibles/controller.nim index 6a79129d5c..274002c69e 100644 --- a/src/app/modules/main/wallet_section/collectibles/controller.nim +++ b/src/app/modules/main/wallet_section/collectibles/controller.nim @@ -42,6 +42,7 @@ proc init*(self: Controller) = self.events.on(SIGNAL_COLLECTIONS_UPDATED) do(e:Args): let args = CollectionsUpdateArgs(e) self.refreshCollections(args.chainId, args.address) + self.collectibleService.fetchAllCollectibles(args.chainId, args.address) self.events.on(SIGNAL_COLLECTIBLES_UPDATED) do(e:Args): let args = CollectiblesUpdateArgs(e) diff --git a/src/app/modules/main/wallet_section/collectibles/models/collectibles_flat_proxy_model.nim b/src/app/modules/main/wallet_section/collectibles/models/collectibles_flat_proxy_model.nim new file mode 100644 index 0000000000..f7be1f8d1c --- /dev/null +++ b/src/app/modules/main/wallet_section/collectibles/models/collectibles_flat_proxy_model.nim @@ -0,0 +1,221 @@ +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) \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/collectibles/models/collectibles_model.nim b/src/app/modules/main/wallet_section/collectibles/models/collectibles_model.nim index ee07e2bf54..64a6b7f133 100644 --- a/src/app/modules/main/wallet_section/collectibles/models/collectibles_model.nim +++ b/src/app/modules/main/wallet_section/collectibles/models/collectibles_model.nim @@ -1,9 +1,9 @@ -import NimQml, Tables, strutils, strformat +import NimQml, Tables, strutils, strformat, sequtils import ./collectibles_item, ./collectible_trait_model type - ModelRole {.pure.} = enum + CollectibleRole* {.pure.} = enum Id = UserRole + 1, Name ImageUrl @@ -26,37 +26,39 @@ QtObject: proc setup(self: Model) = self.QAbstractListModel.setup - proc newModel*(): Model = + proc newModel*(items: seq[Item]): Model = new(result, delete) result.setup + result.items = items + + proc newModel*(): Model = + return newModel(@[]) 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.} = + proc getCount*(self: Model): int {.slot.} = self.items.len - QtProperty[int] count: read = getCount notify = countChanged - method rowCount(self: Model, index: QModelIndex = nil): int = + method rowCount*(self: Model, index: QModelIndex = nil): int = return self.items.len method roleNames(self: Model): Table[int, string] = { - 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", + CollectibleRole.Id.int:"id", + CollectibleRole.Name.int:"name", + CollectibleRole.ImageUrl.int:"imageUrl", + CollectibleRole.BackgroundColor.int:"backgroundColor", + CollectibleRole.Description.int:"description", + CollectibleRole.Permalink.int:"permalink", + CollectibleRole.Properties.int:"properties", + CollectibleRole.Rankings.int:"rankings", + CollectibleRole.Stats.int:"stats", }.toTable method data(self: Model, index: QModelIndex, role: int): QVariant = @@ -67,36 +69,42 @@ QtObject: return let item = self.items[index.row] - let enumRole = role.ModelRole + let enumRole = role.CollectibleRole case enumRole: - of ModelRole.Id: + of CollectibleRole.Id: result = newQVariant(item.getId()) - of ModelRole.Name: + of CollectibleRole.Name: result = newQVariant(item.getName()) - of ModelRole.ImageUrl: + of CollectibleRole.ImageUrl: result = newQVariant(item.getImageUrl()) - of ModelRole.BackgroundColor: + of CollectibleRole.BackgroundColor: result = newQVariant(item.getBackgroundColor()) - of ModelRole.Description: + of CollectibleRole.Description: result = newQVariant(item.getDescription()) - of ModelRole.Permalink: + of CollectibleRole.Permalink: result = newQVariant(item.getPermalink()) - of ModelRole.Properties: + of CollectibleRole.Properties: let traits = newTraitModel() traits.setItems(item.getProperties()) result = newQVariant(traits) - of ModelRole.Rankings: + of CollectibleRole.Rankings: let traits = newTraitModel() traits.setItems(item.getRankings()) result = newQVariant(traits) - of ModelRole.Stats: + of CollectibleRole.Stats: let traits = newTraitModel() traits.setItems(item.getStats()) result = newQVariant(traits) + proc getItem*(self: Model, index: int): Item = + return self.items[index] + proc setItems*(self: Model, items: seq[Item]) = - self.beginResetModel() - self.items = items - self.endResetModel() - self.countChanged() + self.beginResetModel() + self.items = items + self.endResetModel() + self.countChanged() + + proc appendItems*(self: Model, items: seq[Item]) = + self.setItems(concat(self.items, items)) \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/collectibles/models/collectibles_utils.nim b/src/app/modules/main/wallet_section/collectibles/models/collectibles_utils.nim new file mode 100644 index 0000000000..2e48bf0897 --- /dev/null +++ b/src/app/modules/main/wallet_section/collectibles/models/collectibles_utils.nim @@ -0,0 +1,16 @@ +import sequtils, sugar +import ../../../../../../app_service/service/collectible/dto +import collectibles_item, collectible_trait_item + +proc collectibleToItem*(c: CollectibleDto) : Item = + return initItem( + c.id, + c.name, + c.imageUrl, + c.backgroundColor, + c.description, + c.permalink, + 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.statistics.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue)) + ) diff --git a/src/app/modules/main/wallet_section/collectibles/models/collections_item.nim b/src/app/modules/main/wallet_section/collectibles/models/collections_item.nim index a042a4ca0b..5eddc5918f 100644 --- a/src/app/modules/main/wallet_section/collectibles/models/collections_item.nim +++ b/src/app/modules/main/wallet_section/collectibles/models/collections_item.nim @@ -17,8 +17,7 @@ proc initItem*(name, slug, imageUrl: string, ownedAssetCount: int, collectiblesL result.imageUrl = imageUrl result.ownedAssetCount = ownedAssetCount result.collectiblesLoaded = collectiblesLoaded - result.collectiblesModel = collectibles_model.newModel() - result.collectiblesModel.setItems(collectibles) + result.collectiblesModel = collectibles_model.newModel(collectibles) proc initItem*(): Item = result = initItem("", "", "", 0, false, @[]) diff --git a/src/app/modules/main/wallet_section/collectibles/models/collections_model.nim b/src/app/modules/main/wallet_section/collectibles/models/collections_model.nim index 3f79307432..3648c3bd02 100644 --- a/src/app/modules/main/wallet_section/collectibles/models/collections_model.nim +++ b/src/app/modules/main/wallet_section/collectibles/models/collections_model.nim @@ -4,8 +4,9 @@ import ./collections_item as collections_item import ./collectibles_model as collectibles_model import ./collectibles_item as collectibles_item + type - ModelRole {.pure.} = enum + CollectionRole* {.pure.} = enum Name = UserRole + 1, Slug ImageUrl @@ -36,34 +37,30 @@ QtObject: result &= fmt"""[{i}]:({$self.items[i]})""" proc countChanged(self: Model) {.signal.} - - proc getCount(self: Model): int {.slot.} = + proc getCount*(self: Model): int {.slot.} = self.items.len - - QtProperty[int] collectionsLoaded: - read = getCollectionsLoaded - notify = collectionsLoadedChanged - - proc collectionsLoadedChanged(self: Model) {.signal.} - - proc getCollectionsLoaded(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] = { - ModelRole.Name.int:"name", - ModelRole.Slug.int:"slug", - ModelRole.ImageUrl.int:"imageUrl", - ModelRole.OwnedAssetCount.int:"ownedAssetCount", - ModelRole.CollectiblesLoaded.int:"collectiblesLoaded", - ModelRole.CollectiblesModel.int:"collectiblesModel" + 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 = @@ -74,29 +71,39 @@ QtObject: return let item = self.items[index.row] - let enumRole = role.ModelRole + let enumRole = role.CollectionRole case enumRole: - of ModelRole.Name: + of CollectionRole.Name: result = newQVariant(item.getName()) - of ModelRole.Slug: + of CollectionRole.Slug: result = newQVariant(item.getSlug()) - of ModelRole.ImageUrl: + of CollectionRole.ImageUrl: result = newQVariant(item.getImageUrl()) - of ModelRole.OwnedAssetCount: + of CollectionRole.OwnedAssetCount: result = newQVariant(item.getOwnedAssetCount()) - of ModelRole.CollectiblesLoaded: + of CollectionRole.CollectiblesLoaded: result = newQVariant(item.getCollectiblesLoaded()) - of ModelRole.CollectiblesModel: + of CollectionRole.CollectiblesModel: result = newQVariant(item.getCollectiblesModel()) - proc setItems*(self: Model, items: seq[collections_item.Item]) = + proc setCollections*(self: Model, items: seq[collections_item.Item], collectionsLoaded: bool) = self.beginResetModel() self.items = items self.endResetModel() self.countChanged() - self.collectionsLoaded = true - self.collectionsLoadedChanged() + 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: @@ -104,15 +111,21 @@ QtObject: return i return -1 - proc updateCollectionCollectibles*(self: Model, slug: string, collectibles: seq[collectibles_item.Item]) = + 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 index = self.createIndex(idx, 0, nil) - let collectiblesModel = self.items[idx].getCollectiblesModel() collectiblesModel.setItems(collectibles) - self.dataChanged(index, index, @[ModelRole.CollectiblesModel.int]) + self.emitDataChanged(idx, idx, CollectionRole.CollectiblesModel.int) - if not self.items[idx].getCollectiblesLoaded(): - self.items[idx].collectiblesLoaded = true - self.dataChanged(index, index, @[ModelRole.CollectiblesLoaded.int]) + if self.items[idx].getCollectiblesLoaded() != collectiblesLoaded: + self.items[idx].collectiblesLoaded = collectiblesLoaded + self.emitDataChanged(idx, idx, CollectionRole.CollectiblesLoaded.int) diff --git a/src/app/modules/main/wallet_section/collectibles/models/collections_utils.nim b/src/app/modules/main/wallet_section/collectibles/models/collections_utils.nim new file mode 100644 index 0000000000..24b982f87a --- /dev/null +++ b/src/app/modules/main/wallet_section/collectibles/models/collections_utils.nim @@ -0,0 +1,13 @@ +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)) + ) \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/collectibles/module.nim b/src/app/modules/main/wallet_section/collectibles/module.nim index 634ab2b89f..35c34092a0 100644 --- a/src/app/modules/main/wallet_section/collectibles/module.nim +++ b/src/app/modules/main/wallet_section/collectibles/module.nim @@ -6,15 +6,17 @@ import ../../../../core/eventemitter import ./io_interface, ./view, ./controller import ../io_interface as delegate_interface import ../../../../../app_service/service/collectible/service as collectible_service -import ../../../../../app_service/service/collectible/service as collectible_dto import ../../../../../app_service/service/wallet_account/service as wallet_account_service import ../../../../../app_service/service/network/service as network_service 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/collectible_trait_item as collectible_trait_item -import ./models/collections_item as collections_item +import ./models/collectibles_utils +import ./models/collectibles_model as collectibles_model export io_interface @@ -86,37 +88,31 @@ method switchAccount*(self: Module, accountIndex: int) = self.currentCollectibleModule.setCurrentAddress(network, self.address) -proc collectibleToItem(c: collectible_dto.CollectibleDto) : collectibles_item.Item = - return collectibles_item.initItem( - c.id, - c.name, - c.imageUrl, - c.backgroundColor, - c.description, - c.permalink, - 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.statistics.map(t => initTrait(t.traitType, t.value, t.displayType, t.maxValue)) - ) - -proc collectionToItem(c: CollectionData) : collections_item.Item = - return collections_item.initItem( - c.collection.name, - c.collection.slug, - c.collection.imageUrl, - c.collection.ownedAssetCount, - c.collectiblesLoaded, - toSeq(c.collectibles.values).map(c => collectibleToItem(c)) - ) +proc collectionToItem(self: Module, collection: CollectionData) : collections_item.Item = + var item = collectionToItem(collection) + #[ Skeleton items are disabled until problem with OpenSea API is researched. + OpenSea is telling us the address owns a certain amount of NFTs from a certain collection, but + 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) = self.view.setCollections( - toSeq(collections.collections.values).map(c => collectionToItem(c)) + toSeq(collections.collections.values).map(c => self.collectionToItem(c)), + collections.collectionsLoaded ) method updateCollection*(self: Module, collection: CollectionData) = self.view.setCollectibles(collection.collection.slug, - toSeq(collection.collectibles.values).map(c => collectibleToItem(c)) + toSeq(collection.collectibles.values).map(c => collectibleToItem(c)), + collection.collectiblesLoaded ) method fetchCollections*(self: Module) = diff --git a/src/app/modules/main/wallet_section/collectibles/view.nim b/src/app/modules/main/wallet_section/collectibles/view.nim index 09bb8faabb..aeb04209c3 100644 --- a/src/app/modules/main/wallet_section/collectibles/view.nim +++ b/src/app/modules/main/wallet_section/collectibles/view.nim @@ -1,6 +1,7 @@ import NimQml -import ./models/collections_model +import ./models/collections_model as collections_model +import ./models/collectibles_flat_proxy_model as flat_model import ./models/collections_item as collections_item import ./models/collectibles_item as collectibles_item import ./io_interface @@ -9,12 +10,12 @@ QtObject: type View* = ref object of QObject delegate: io_interface.AccessInterface - model: Model - modelVariant: QVariant + model: collections_model.Model + flatModel: flat_model.Model proc delete*(self: View) = + self.flatModel.delete self.model.delete - self.modelVariant.delete self.QObject.delete proc newView*(delegate: io_interface.AccessInterface): View = @@ -22,28 +23,33 @@ QtObject: result.QObject.setup result.delegate = delegate result.model = newModel() - result.modelVariant = newQVariant(result.model) + result.flatModel = flat_model.newModel(result.model) proc load*(self: View) = self.delegate.viewDidLoad() proc modelChanged*(self: View) {.signal.} - proc getModel(self: View): QVariant {.slot.} = - return self.modelVariant - + return newQVariant(self.model) QtProperty[QVariant] model: read = getModel notify = modelChanged - proc setCollections*(self: View, collections: seq[collections_item.Item]) = - self.model.setItems(collections) - - proc setCollectibles*(self: View, collectionsSlug: string, collectibles: seq[collectibles_item.Item]) = - self.model.updateCollectionCollectibles(collectionsSlug, collectibles) + proc flatModelChanged*(self: View) {.signal.} + proc getFlatModel(self: View): QVariant {.slot.} = + return newQVariant(self.flatModel) + QtProperty[QVariant] flatModel: + read = getFlatModel + notify = flatModelChanged proc fetchCollections*(self: View) {.slot.} = self.delegate.fetchCollections() proc fetchCollectibles*(self: View, collectionSlug: string) {.slot.} = self.delegate.fetchCollectibles(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) diff --git a/src/app_service/service/collectible/service.nim b/src/app_service/service/collectible/service.nim index ce153b3d5a..e227a8a805 100644 --- a/src/app_service/service/collectible/service.nim +++ b/src/app_service/service/collectible/service.nim @@ -165,11 +165,12 @@ QtObject: data.chainId = chainIdJson.getInt() data.address = addressJson.getStr() + var collections: seq[CollectionDto] let collectionsJson = responseObj["collections"] if (collectionsJson.kind == JArray): - let collections = map(collectionsJson.getElems(), proc(x: JsonNode): CollectionDto = x.toCollectionDto()) - self.setCollections(data.chainId, data.address, collections) - self.events.emit(SIGNAL_COLLECTIONS_UPDATED, data) + collections = map(collectionsJson.getElems(), proc(x: JsonNode): CollectionDto = x.toCollectionDto()) + self.setCollections(data.chainId, data.address, collections) + self.events.emit(SIGNAL_COLLECTIONS_UPDATED, data) except Exception as e: let errDescription = e.msg error "error onRxCollections: ", errDescription @@ -208,11 +209,12 @@ QtObject: data.address = addressJson.getStr() data.collectionSlug = collectionSlugJson.getStr() + var collectibles: seq[CollectibleDto] let collectiblesJson = responseObj["collectibles"] if (collectiblesJson.kind == JArray): - let collectibles = map(collectiblesJson.getElems(), proc(x: JsonNode): CollectibleDto = x.toCollectibleDto()) - self.setCollectibles(data.chainId, data.address, data.collectionSlug, collectibles) - self.events.emit(SIGNAL_COLLECTIBLES_UPDATED, data) + collectibles = map(collectiblesJson.getElems(), proc(x: JsonNode): CollectibleDto = x.toCollectibleDto()) + self.setCollectibles(data.chainId, data.address, data.collectionSlug, collectibles) + self.events.emit(SIGNAL_COLLECTIBLES_UPDATED, data) except Exception as e: let errDescription = e.msg error "error onRxCollectibles: ", errDescription @@ -241,3 +243,11 @@ QtObject: limit: limit ) self.threadpool.start(arg) + + proc fetchAllCollectibles*(self: Service, chainId: int, address: string) = + try: + for collectionSlug, _ in self.data[chainId][address].collections: + self.fetchCollectibles(chainId, address, collectionSlug) + except Exception as e: + let errDescription = e.msg + error "error fetchAllCollectibles: ", errDescription diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index 3199da2943..0a95756a78 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -31,6 +31,7 @@ QtObject { property string mnemonicBackedUp: walletSection.isMnemonicBackedUp property var collections: walletSectionCollectibles.model + property var flatCollectibles: walletSectionCollectibles.flatModel property var currentCollectible: walletSectionCurrentCollectible property var savedAddresses: walletSectionSavedAddresses.model diff --git a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml index 015973ba2a..09700a208d 100644 --- a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml +++ b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml @@ -1,34 +1,35 @@ import QtQuick 2.13 import QtQuick.Controls 2.13 -import QtGraphicalEffects 1.13 import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 import StatusQ.Components 0.1 - -import utils 1.0 -import shared 1.0 import shared.panels 1.0 -import "../stores" -import "../popups" import "collectibles" Item { id: root + property var collectiblesModel width: parent.width + signal collectibleClicked(string collectionSlug, int collectibleId) + readonly property bool areCollectionsLoaded: root.collectiblesModel.collectionsLoaded + Loader { id: contentLoader width: parent.width height: parent.height sourceComponent: { - if (!RootStore.collections.collectionsLoaded) + if (!root.areCollectionsLoaded) { return loading - } else if (RootStore.collections.count === 0) { + } else if (root.collectiblesModel.collectionCount === 0) { return empty; + } else if (root.collectiblesModel.count === 0) { + return loading } return loaded; } @@ -64,42 +65,21 @@ Item { Component { id: loaded - - StatusScrollView { - id: scrollView - - Column { - id: collectiblesSection - width: root.width - - Repeater { - objectName: "collectionsRepeater" - id: collectionsRepeater - model: RootStore.collections - delegate: StatusExpandableItem { - id: collectionDelegate - anchors.left: parent.left - anchors.right: parent.right - primaryText: model.name - asset.name: model.imageUrl - asset.isImage: true - type: StatusExpandableItem.Type.Secondary - expandableComponent: CollectibleCollectionView { - collectionImageUrl: model.imageUrl - collectiblesLoaded: model.collectiblesLoaded - collectiblesModel: model.collectiblesModel - anchors.left: parent.left - anchors.right: parent.right - onCollectibleClicked: { - RootStore.selectCollectible(model.slug, collectibleId) - root.collectibleClicked(model.slug, collectibleId); - } - } - onExpandedChanged: { - if(expanded) { - RootStore.fetchCollectibles(model.slug) - } - } + StatusGridView { + id: gridView + anchors.fill: parent + model: root.collectiblesModel + cellHeight: 229 + cellWidth: 176 + delegate: Item { + height: gridView.cellHeight + width: gridView.cellWidth + CollectibleView { + collectibleModel: model + anchors.fill: parent + anchors.bottomMargin: 4 + onCollectibleClicked: { + root.collectibleClicked(slug, collectibleId); } } } diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index 45ed8d270a..913b7f646a 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -97,7 +97,9 @@ Item { } } CollectiblesView { + collectiblesModel: RootStore.flatCollectibles onCollectibleClicked: { + RootStore.selectCollectible(collectionSlug, collectibleId) stack.currentIndex = 1 } } diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleCollectionView.qml b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleCollectionView.qml deleted file mode 100644 index 4bcd784159..0000000000 --- a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleCollectionView.qml +++ /dev/null @@ -1,121 +0,0 @@ -import QtQuick 2.13 -import QtQuick.Controls 2.13 -import QtGraphicalEffects 1.13 - -import StatusQ.Components 0.1 -import StatusQ.Core.Theme 0.1 -import shared.panels 1.0 - -import "../../stores" -import utils 1.0 - -Item { - id: root - - property string collectionImageUrl: "" - property bool collectiblesLoaded: false - property var collectiblesModel - - width: parent.width - height: contentLoader.height - - signal collectibleClicked(int collectibleId) - - Loader { - id: contentLoader - width: parent.width - anchors.top: parent.top - anchors.topMargin: 16 - anchors.horizontalCenter: parent.horizontalCenter - sourceComponent: { - if (!root.collectiblesLoaded) { - return loading - } else if (root.collectiblesModel.count === 0) { - return empty - } - return loaded - } - } - - Component { - id: loading - - Item { - id: loadingIndicator - height: 164 - StatusLoadingIndicator { - width: 20 - height: 20 - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - } - } - } - - Component { - id: empty - - Item { - id: emptyContainer - height: 164 - StyledText { - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - color: Style.current.secondaryText - text: qsTr("No collectibles available") - font.pixelSize: 15 - } - } - } - - Component { - id: loaded - - Flow { - width: parent.width - - bottomPadding: 16 - spacing: 24 - - Component { - id: collectibleDelegate - - StatusRoundedImage { - id: image - width: 146 - height: 146 - radius: 16 - image.source: model.imageUrl - border.color: Theme.palette.baseColor2 - border.width: 1 - showLoadingIndicator: true - color: model.backgroundColor - Rectangle { - anchors.centerIn: parent - width: image.width - height: image.height - radius: image.radius - border.width: 1 - border.color: Theme.palette.primaryColor1 - color: Theme.palette.indirectColor3 - visible: mouse.containsMouse - } - MouseArea { - id: mouse - anchors.fill: parent - hoverEnabled: true - onClicked: { - root.collectibleClicked(model.id); - } - } - } - } - - Repeater { - objectName: "collectiblesRepeater" - model: root.collectiblesModel - delegate: collectibleDelegate - } - } - } -} diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml new file mode 100644 index 0000000000..923839efc3 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml @@ -0,0 +1,90 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.13 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 + +import shared.panels 1.0 + +Item { + id: root + property var collectibleModel + + implicitHeight: 225 + implicitWidth: 176 + + signal collectibleClicked(string slug, int collectibleId) + + readonly property bool isLoaded: root.collectibleModel.collectionCollectiblesLoaded + + ColumnLayout { + //Layout.fillHeight: true + //Layout.fillWidth: true + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + StatusRoundedImage { + id: image + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.topMargin: 8 + Layout.bottomMargin: 0 + implicitWidth: 160 + implicitHeight: 160 + radius: 12 + image.source: root.collectibleModel.imageUrl + border.color: Theme.palette.baseColor2 + border.width: 1 + showLoadingIndicator: true + color: root.collectibleModel.backgroundColor + } + StatusBaseText { + id: collectibleLabel + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.topMargin: 9 + Layout.preferredWidth: 144 + Layout.preferredHeight: 21 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + font.pixelSize: 15 + color: Theme.palette.directColor1 + font.weight: Font.DemiBold + elide: Text.ElideRight + text: isLoaded ? root.collectibleModel.name : "..." + } + StatusBaseText { + id: collectionLabel + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + Layout.topMargin: 0 + Layout.preferredWidth: 144 + Layout.preferredHeight: 18 + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + font.pixelSize: 13 + color: Theme.palette.baseColor1 + elide: Text.ElideRight + text: root.collectibleModel.collectionName + } + } + + Rectangle { + anchors.fill: parent + radius: 18 + border.width: 1 + border.color: Theme.palette.primaryColor1 + color: Theme.palette.indirectColor3 + visible: root.isLoaded && mouse.containsMouse + } + MouseArea { + id: mouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + if (root.isLoaded) { + root.collectibleClicked(root.collectibleModel.collectionSlug, root.collectibleModel.id); + } + } + } +} \ No newline at end of file