From dc75c120df55651214b9f7ce40fcbb5d06afc62b Mon Sep 17 00:00:00 2001 From: Dario Gabriel Lipicar Date: Mon, 17 Jul 2023 20:56:40 -0300 Subject: [PATCH] feat(wallet): use new status-go collectibles backend Fixes #11558 --- .../collectible_details/controller.nim | 116 ++++++++ .../collectible_details/events_handler.nim | 54 ++++ .../collectibles/controller.nim | 159 ++++++----- .../current_collectible/controller.nim | 33 --- .../current_collectible/io_interface.nim | 31 -- .../current_collectible/module.nim | 60 ---- .../collectibles/current_collectible/view.nim | 175 ------------ .../collectibles/events_handler.nim | 72 +++++ .../collectibles/io_interface.nim | 45 --- .../models/collectible_trait_item.nim | 23 -- .../models/collectibles_utils.nim | 30 -- .../wallet_section/collectibles/module.nim | 142 ---------- .../main/wallet_section/collectibles/view.nim | 53 ---- .../modules/main/wallet_section/module.nim | 20 +- src/app/modules/main/wallet_section/view.nim | 18 +- .../collectible_details_entry.nim | 165 +++++++++++ .../collectible_trait_model.nim | 12 +- .../collectibles_item.nim | 77 ++--- .../collectibles_model.nim | 117 +++----- .../shared_models/collectibles_utils.nim | 23 ++ .../service/collectible/async_tasks.nim | 28 -- .../service/collectible/service.nim | 30 -- .../service/transaction/async_tasks.nim | 11 +- src/backend/collectibles.nim | 90 ++++-- src/backend/collectibles_types.nim | 268 ++++++++++++++++++ storybook/pages/ProfileDialogViewPage.qml | 6 +- .../ProfileShowcaseCollectiblesPanelPage.qml | 23 +- .../ProfileShowcaseCollectiblesPanel.qml | 6 +- .../AppLayouts/Profile/stores/WalletStore.qml | 2 +- .../views/profile/MyProfileSettingsView.qml | 2 +- .../Wallet/stores/ActivityFiltersStore.qml | 3 +- .../Wallet/stores/CollectiblesStore.qml | 23 ++ ui/app/AppLayouts/Wallet/stores/RootStore.qml | 16 +- ui/app/AppLayouts/Wallet/stores/qmldir | 1 + .../Wallet/views/CollectiblesView.qml | 8 +- .../AppLayouts/Wallet/views/RightTabView.qml | 6 +- .../collectibles/CollectibleDetailView.qml | 31 +- .../shared/stores/NetworkConnectionStore.qml | 2 +- .../views/profile/ProfileShowcaseView.qml | 3 +- vendor/status-go | 2 +- 40 files changed, 1034 insertions(+), 952 deletions(-) create mode 100644 src/app/modules/main/wallet_section/collectible_details/controller.nim create mode 100644 src/app/modules/main/wallet_section/collectible_details/events_handler.nim delete mode 100644 src/app/modules/main/wallet_section/collectibles/current_collectible/controller.nim delete mode 100644 src/app/modules/main/wallet_section/collectibles/current_collectible/io_interface.nim delete mode 100644 src/app/modules/main/wallet_section/collectibles/current_collectible/module.nim delete mode 100644 src/app/modules/main/wallet_section/collectibles/current_collectible/view.nim create mode 100644 src/app/modules/main/wallet_section/collectibles/events_handler.nim delete mode 100644 src/app/modules/main/wallet_section/collectibles/io_interface.nim delete mode 100644 src/app/modules/main/wallet_section/collectibles/models/collectible_trait_item.nim delete mode 100644 src/app/modules/main/wallet_section/collectibles/models/collectibles_utils.nim delete mode 100644 src/app/modules/main/wallet_section/collectibles/module.nim delete mode 100644 src/app/modules/main/wallet_section/collectibles/view.nim create mode 100644 src/app/modules/shared_models/collectible_details_entry.nim rename src/app/modules/{main/wallet_section/collectibles/models => shared_models}/collectible_trait_model.nim (86%) rename src/app/modules/{main/wallet_section/collectibles/models => shared_models}/collectibles_item.nim (51%) rename src/app/modules/{main/wallet_section/collectibles/models => shared_models}/collectibles_model.nim (64%) create mode 100644 src/app/modules/shared_models/collectibles_utils.nim create mode 100644 src/backend/collectibles_types.nim create mode 100644 ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml diff --git a/src/app/modules/main/wallet_section/collectible_details/controller.nim b/src/app/modules/main/wallet_section/collectible_details/controller.nim new file mode 100644 index 0000000000..64e9e14556 --- /dev/null +++ b/src/app/modules/main/wallet_section/collectible_details/controller.nim @@ -0,0 +1,116 @@ +import NimQml, logging, std/json, sequtils, strutils +import stint + +import app/modules/shared_models/collectible_details_entry +import events_handler + +import app/core/eventemitter + +import backend/collectibles as backend_collectibles +import app_service/service/network/service as network_service + +QtObject: + type + Controller* = ref object of QObject + networkService: network_service.Service + + isDetailedEntryLoading: bool + detailedEntry: CollectibleDetailsEntry + + eventsHandler: EventsHandler + + proc setup(self: Controller) = + self.QObject.setup + + proc delete*(self: Controller) = + self.QObject.delete + + proc getDetailedEntry*(self: Controller): QVariant {.slot.} = + return newQVariant(self.detailedEntry) + + proc detailedEntryChanged(self: Controller) {.signal.} + + QtProperty[QVariant] detailedEntry: + read = getDetailedEntry + notify = detailedEntryChanged + + proc getIsDetailedEntryLoading*(self: Controller): QVariant {.slot.} = + return newQVariant(self.detailedEntry) + + proc isDetailedEntryLoadingChanged(self: Controller) {.signal.} + + proc setIsDetailedEntryLoading(self: Controller, value: bool) = + if self.isDetailedEntryLoading != value: + self.isDetailedEntryLoading = value + self.isDetailedEntryLoadingChanged() + + QtProperty[bool] isDetailedEntryLoading: + read = getIsDetailedEntryLoading + notify = isDetailedEntryLoadingChanged + + proc getExtraData(self: Controller, chainID: int): ExtraData = + let network = self.networkService.getNetwork(chainID) + + return ExtraData( + networkShortName: network.shortName, + networkColor: network.chainColor, + networkIconUrl: network.iconURL + ) + + proc processGetCollectiblesDataResponse(self: Controller, response: JsonNode) = + defer: self.setIsDetailedEntryLoading(false) + + let res = fromJson(response, backend_collectibles.GetCollectiblesDataResponse) + + if res.errorCode != ErrorCodeSuccess: + error "error fetching collectible details: ", res.errorCode + return + + if len(res.collectibles) != 1: + error "unexpected number of items fetching collectible details: ", len(res.collectibles) + return + + let collectible = res.collectibles[0] + let extradata = self.getExtraData(collectible.id.chainID) + + self.detailedEntry = newCollectibleDetailsFullEntry(collectible, extradata) + self.detailedEntryChanged() + + proc getDetailedCollectible*(self: Controller, chainId: int, contractAddress: string, tokenId: string) {.slot.} = + self.setIsDetailedEntryLoading(true) + + let id = backend_collectibles.CollectibleUniqueID( + chainID: chainId, + contractAddress: contractAddress, + tokenID: stint.u256(tokenId) + ) + let extradata = self.getExtraData(chainId) + + self.detailedEntry = newCollectibleDetailsBasicEntry(id, extradata) + self.detailedEntryChanged() + + let response = backend_collectibles.getCollectiblesDataAsync(@[id]) + if response.error != nil: + self.setIsDetailedEntryLoading(false) + error "error fetching collectible details: ", response.error + return + + proc setupEventHandlers(self: Controller) = + self.eventsHandler.onGetCollectiblesDataDone(proc (jsonObj: JsonNode) = + self.processGetCollectiblesDataResponse(jsonObj) + ) + + proc newController*(networkService: network_service.Service, + events: EventEmitter + ): Controller = + new(result, delete) + result.networkService = networkService + + result.detailedEntry = newCollectibleDetailsEmptyEntry() + result.isDetailedEntryLoading = false + + result.eventsHandler = newEventsHandler(events) + + result.setup() + + result.setupEventHandlers() diff --git a/src/app/modules/main/wallet_section/collectible_details/events_handler.nim b/src/app/modules/main/wallet_section/collectible_details/events_handler.nim new file mode 100644 index 0000000000..2375a65487 --- /dev/null +++ b/src/app/modules/main/wallet_section/collectible_details/events_handler.nim @@ -0,0 +1,54 @@ +import NimQml, logging, std/json, sequtils, strutils +import tables, stint + +import app/core/eventemitter +import app/core/signals/types + +import backend/collectibles as backend_collectibles + +type EventCallbackProc = proc (eventObject: JsonNode) + +# EventsHandler responsible for catching collectibles related backend events and reporting them +QtObject: + type + EventsHandler* = ref object of QObject + events: EventEmitter + eventHandlers: Table[string, EventCallbackProc] + + proc setup(self: EventsHandler) = + self.QObject.setup + + proc delete*(self: EventsHandler) = + self.QObject.delete + + proc onGetCollectiblesDataDone*(self: EventsHandler, handler: EventCallbackProc) = + self.eventHandlers[backend_collectibles.eventGetCollectiblesDataDone] = handler + + proc handleApiEvents(self: EventsHandler, e: Args) = + var data = WalletSignal(e) + + if self.eventHandlers.hasKey(data.eventType): + var responseJson: JsonNode + responseJson = parseJson(data.message) + + if responseJson.kind != JObject: + error "unexpected json type", responseJson.kind + return + let callback = self.eventHandlers[data.eventType] + callback(responseJson) + else: + discard + + proc newEventsHandler*(events: EventEmitter): EventsHandler = + new(result, delete) + result.events = events + result.eventHandlers = initTable[string, EventCallbackProc]() + + result.setup() + + # Register for wallet events + let eventsHandler = result + result.events.on(SignalType.Wallet.event, proc(e: Args) = + eventsHandler.handleApiEvents(e) + ) + \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/collectibles/controller.nim b/src/app/modules/main/wallet_section/collectibles/controller.nim index a9c6f449dc..b91d4b3674 100644 --- a/src/app/modules/main/wallet_section/collectibles/controller.nim +++ b/src/app/modules/main/wallet_section/collectibles/controller.nim @@ -1,81 +1,108 @@ -import sequtils, Tables, sugar -import io_interface -import ../../../../../app_service/service/collectible/service as collectible_service -import ../../../../../app_service/service/wallet_account/service as wallet_account_service -import ../../../../../app_service/service/network/service as network_service -import ../../../../../app_service/service/network_connection/service as network_connection_service -import ../../../../../app_service/service/node/service as node_service -import ../../../../core/eventemitter +import NimQml, std/json, sequtils, sugar, strutils +import stint, logging -type - Controller* = ref object of RootObj - delegate: io_interface.AccessInterface - events: EventEmitter - collectibleService: collectible_service.Service - walletAccountService: wallet_account_service.Service - networkService: network_service.Service - nodeService: node_service.Service - networkConnectionService: network_connection_service.Service +import app/modules/shared_models/collectibles_model +import app/modules/shared_models/collectibles_utils +import events_handler -proc newController*( - delegate: io_interface.AccessInterface, - events: EventEmitter, - collectibleService: collectible_service.Service, - walletAccountService: wallet_account_service.Service, - networkService: network_service.Service, - nodeService: node_service.Service, - networkConnectionService: network_connection_service.Service -): Controller = - result = Controller() - result.delegate = delegate - result.events = events - result.collectibleService = collectibleService - result.walletAccountService = walletAccountService - result.networkService = networkService - result.nodeService = nodeService - result.networkConnectionService = networkConnectionService +import app/core/eventemitter -proc delete*(self: Controller) = - discard +import backend/collectibles as backend_collectibles -proc updateCollectibles(self: Controller, chainId: int, address: string) = - let data = self.collectibleService.getOwnedCollectibles(chainId, @[address]) - self.delegate.appendCollectibles(chainId, address, data[0]) +const FETCH_BATCH_COUNT_DEFAULT = 50 -proc init*(self: Controller) = - self.events.on(SIGNAL_OWNED_COLLECTIBLES_REFETCH) do(e:Args): - let args = OwnedCollectiblesUpdateArgs(e) - self.delegate.resetCollectibles() +QtObject: + type + Controller* = ref object of QObject + model: Model - self.events.on(SIGNAL_OWNED_COLLECTIBLES_UPDATE_ERROR) do(e:Args): - let args = OwnedCollectiblesUpdateArgs(e) - self.updateCollectibles(args.chainId, args.address) + eventsHandler: EventsHandler - self.events.on(SIGNAL_OWNED_COLLECTIBLES_UPDATE_STARTED) do(e:Args): - let args = OwnedCollectiblesUpdateArgs(e) - self.delegate.onFetchStarted(args.chainId, args.address) + addresses: seq[string] + chainIds: seq[int] - self.events.on(SIGNAL_OWNED_COLLECTIBLES_UPDATE_FINISHED) do(e:Args): - let args = OwnedCollectiblesUpdateArgs(e) - self.updateCollectibles(args.chainId, args.address) + proc setup(self: Controller) = + self.QObject.setup - self.events.on(SIGNAL_REFRESH_COLLECTIBLES) do(e:Args): - self.collectibleService.refetchAllOwnedCollectibles() + proc delete*(self: Controller) = + self.QObject.delete -proc getNetwork*(self: Controller): network_service.NetworkDto = - return self.networkService.getNetworkForCollectibles() + proc getModel*(self: Controller): QVariant {.slot.} = + return newQVariant(self.model) -proc getOwnedCollectibles*(self: Controller, chainId: int, addresses: seq[string]): seq[CollectiblesData] = - return self.collectibleService.getOwnedCollectibles(chainId, addresses) + QtProperty[QVariant] model: + read = getModel -proc fetchOwnedCollectibles*(self: Controller, chainId: int, addresses: seq[string]) = - self.collectibleService.fetchOwnedCollectibles(chainId, addresses) + proc processFilterOwnedCollectiblesResponse(self: Controller, response: JsonNode) = + defer: self.model.setIsFetching(false) -proc getCollectible*(self: Controller, chainId: int, id: UniqueID) : CollectibleDto = - self.collectibleService.getCollectible(chainId, id) + let res = fromJson(response, backend_collectibles.FilterOwnedCollectiblesResponse) -proc getCollection*(self: Controller, chainId: int, slug: string) : CollectionDto = - self.collectibleService.getCollection(chainId, slug) + let isError = res.errorCode != ErrorCodeSuccess + defer: self.model.setIsError(isError) -proc getHasCollectiblesCache*(self: Controller, address: string): bool = - return self.collectibleService.areCollectionsLoaded(address) \ No newline at end of file + if isError: + error "error fetching collectibles entries: ", res.errorCode + return + + try: + let items = res.collectibles.map(header => collectibleToItem(header)) + self.model.setItems(items, res.offset, res.hasMore) + except Exception as e: + error "Error converting activity entries: ", e.msg + + proc updateFilter*(self: Controller) {.slot.} = + self.model.resetModel(@[]) + + let response = backend_collectibles.filterOwnedCollectiblesAsync(self.chainIds, self.addresses, 0, FETCH_BATCH_COUNT_DEFAULT) + if response.error != nil: + self.model.setIsFetching(false) + self.model.setIsError(true) + error "error fetching collectibles entries: ", response.error + return + self.model.setIsFetching(true) + self.model.setIsError(false) + + proc loadMoreItems(self: Controller) {.slot.} = + if self.model.getIsFetching(): + return + + let response = backend_collectibles.filterOwnedCollectiblesAsync(self.chainIds, self.addresses, self.model.getCount(), FETCH_BATCH_COUNT_DEFAULT) + if response.error != nil: + self.model.setIsError(true) + error "error fetching collectibles entries: ", response.error + return + self.model.setIsFetching(true) + self.model.setIsError(false) + + proc setupEventHandlers(self: Controller) = + self.eventsHandler.onOwnedCollectiblesFilteringDone(proc (jsonObj: JsonNode) = + self.processFilterOwnedCollectiblesResponse(jsonObj) + ) + + self.eventsHandler.onCollectiblesOwnershipUpdateFinished(proc () = + self.updateFilter() + ) + + proc newController*(events: EventEmitter): Controller = + new(result, delete) + + result.model = newModel() + + result.eventsHandler = newEventsHandler(events) + + result.addresses = @[] + result.chainIds = @[] + + result.setup() + + result.setupEventHandlers() + + signalConnect(result.model, "loadMoreItems()", result, "loadMoreItems()") + + proc globalFilterChanged*(self: Controller, addresses: seq[string], chainIds: seq[int]) = + if chainIds == self.chainIds and addresses == self.addresses: + return + self.chainIds = chainIds + self.addresses = addresses + self.updateFilter() \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/collectibles/current_collectible/controller.nim b/src/app/modules/main/wallet_section/collectibles/current_collectible/controller.nim deleted file mode 100644 index a01a976524..0000000000 --- a/src/app/modules/main/wallet_section/collectibles/current_collectible/controller.nim +++ /dev/null @@ -1,33 +0,0 @@ -import sequtils, Tables -import io_interface - -import ../../../../../../app_service/service/collectible/service as collectible_service -import ../../../../../../app_service/service/network/dto as network_dto - -type - Controller* = ref object of RootObj - delegate: io_interface.AccessInterface - collectibleService: collectible_service.Service - network: network_dto.NetworkDto - -proc newController*( - delegate: io_interface.AccessInterface, - collectibleService: collectible_service.Service, -): Controller = - result = Controller() - result.delegate = delegate - result.collectibleService = collectibleService - -proc delete*(self: Controller) = - discard - -proc init*(self: Controller) = - discard - -method setCurrentNetwork*(self: Controller, network: network_dto.NetworkDto) = - self.network = network - -proc update*(self: Controller, id: collectible_service.UniqueID) = - let collectible = self.collectibleService.getCollectible(self.network.chainId, id) - let collection = self.collectibleService.getCollection(self.network.chainId, collectible.collectionSlug) - self.delegate.setData(collection, collectible, self.network) diff --git a/src/app/modules/main/wallet_section/collectibles/current_collectible/io_interface.nim b/src/app/modules/main/wallet_section/collectibles/current_collectible/io_interface.nim deleted file mode 100644 index 9486a7532a..0000000000 --- a/src/app/modules/main/wallet_section/collectibles/current_collectible/io_interface.nim +++ /dev/null @@ -1,31 +0,0 @@ -import stint -import ../../../../../../app_service/service/network/dto as network_dto -import ../../../../../../app_service/service/collectible/dto as collectible_dto - -type - AccessInterface* {.pure inheritable.} = ref object of RootObj - ## Abstract class for any input/interaction with this module. - -method delete*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") - -method load*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") - -method isLoaded*(self: AccessInterface): bool {.base.} = - raise newException(ValueError, "No implementation available") - -method setCurrentNetwork*(self: AccessInterface, network: network_dto.NetworkDto) {.base.} = - raise newException(ValueError, "No implementation available") - -method update*(self: AccessInterface, address: string, tokenId: Uint256) {.base.} = - raise newException(ValueError, "No implementation available") - -method setData*(self: AccessInterface, collection: collectible_dto.CollectionDto, collectible: collectible_dto.CollectibleDto, network: network_dto.NetworkDto) {.base.} = - raise newException(ValueError, "No implementation available") - -# View Delegate Interface -# Delegate for the view must be declared here due to use of QtObject and multi -# inheritance, which is not well supported in Nim. -method viewDidLoad*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/wallet_section/collectibles/current_collectible/module.nim b/src/app/modules/main/wallet_section/collectibles/current_collectible/module.nim deleted file mode 100644 index 2de5b81086..0000000000 --- a/src/app/modules/main/wallet_section/collectibles/current_collectible/module.nim +++ /dev/null @@ -1,60 +0,0 @@ -import NimQml, sequtils, sugar, stint - -import ../../../../../global/global_singleton - -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/dto as collectible_dto -import ../../../../../../app_service/service/network/dto as network_dto - -import ../models/collectibles_item as collectibles_item -import ../models/collectibles_utils - -export io_interface - -type - Module* = ref object of io_interface.AccessInterface - delegate: delegate_interface.AccessInterface - view: View - controller: Controller - moduleLoaded: bool - -proc newModule*( - delegate: delegate_interface.AccessInterface, - collectibleService: collectible_service.Service -): Module = - result = Module() - result.delegate = delegate - result.view = newView(result) - result.controller = newController(result, collectibleService) - result.moduleLoaded = false - -method delete*(self: Module) = - self.view.delete - -method load*(self: Module) = - singletonInstance.engine.setRootContextProperty("walletSectionCurrentCollectible", newQVariant(self.view)) - self.controller.init() - self.view.load() - -method isLoaded*(self: Module): bool = - return self.moduleLoaded - -method viewDidLoad*(self: Module) = - self.moduleLoaded = true - self.delegate.currentCollectibleModuleDidLoad() - -method setCurrentNetwork*(self: Module, network: network_dto.NetworkDto) = - self.controller.setCurrentNetwork(network) - -method update*(self: Module, address: string, tokenId: Uint256) = - 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) = - let item = collectibleToItem(collectible, collection) - self.view.setData(item, network) diff --git a/src/app/modules/main/wallet_section/collectibles/current_collectible/view.nim b/src/app/modules/main/wallet_section/collectibles/current_collectible/view.nim deleted file mode 100644 index 6a9e33f475..0000000000 --- a/src/app/modules/main/wallet_section/collectibles/current_collectible/view.nim +++ /dev/null @@ -1,175 +0,0 @@ -import NimQml, sequtils, sugar, stint - -import ./io_interface -import ../../../../../../app_service/service/network/dto as network_dto -import ../models/collectibles_item -import ../models/collectible_trait_item -import ../models/collectible_trait_model - -QtObject: - type - View* = ref object of QObject - delegate: io_interface.AccessInterface - - networkShortName: string - networkColor: string - networkIconUrl: string - - collectible: Item - propertiesModel: TraitModel - rankingsModel: TraitModel - statsModel: TraitModel - - proc setup(self: View) = - self.QObject.setup - - proc delete*(self: View) = - self.QObject.delete - - proc newView*(delegate: io_interface.AccessInterface): View = - new(result, delete) - result.setup() - result.delegate = delegate - result.collectible = initItem() - result.propertiesModel = newTraitModel() - result.rankingsModel = newTraitModel() - result.statsModel = newTraitModel() - - proc load*(self: View) = - self.delegate.viewDidLoad() - - proc getNetworkShortName(self: View): QVariant {.slot.} = - return newQVariant(self.networkShortName) - - proc networkShortNameChanged(self: View) {.signal.} - - QtProperty[QVariant] networkShortName: - read = getNetworkShortName - notify = networkShortNameChanged - - proc getNetworkColor(self: View): QVariant {.slot.} = - return newQVariant(self.networkColor) - - proc networkColorChanged(self: View) {.signal.} - - QtProperty[QVariant] networkColor: - read = getNetworkColor - notify = networkColorChanged - - proc getNetworkIconUrl(self: View): QVariant {.slot.} = - return newQVariant(self.networkIconUrl) - - proc networkIconUrlChanged(self: View) {.signal.} - - QtProperty[QVariant] networkIconUrl: - read = getNetworkIconUrl - notify = networkIconUrlChanged - - proc currentCollectibleChanged(self: View) {.signal.} - - proc getName(self: View): QVariant {.slot.} = - return newQVariant(self.collectible.getName()) - QtProperty[QVariant] name: - read = getName - notify = currentCollectibleChanged - - proc getID(self: View): QVariant {.slot.} = - return newQVariant(self.collectible.getId()) - QtProperty[QVariant] id: - read = getID - notify = currentCollectibleChanged - - proc getTokenID(self: View): QVariant {.slot.} = - return newQVariant(self.collectible.getTokenId().toString()) - QtProperty[QVariant] tokenId: - read = getTokenID - notify = currentCollectibleChanged - - proc getDescription(self: View): QVariant {.slot.} = - return newQVariant(self.collectible.getDescription()) - QtProperty[QVariant] description: - read = getDescription - notify = currentCollectibleChanged - - proc getBackgroundColor(self: View): QVariant {.slot.} = - return newQVariant(self.collectible.getBackgroundColor()) - QtProperty[QVariant] backgroundColor: - read = getBackgroundColor - notify = currentCollectibleChanged - - proc getMediaUrl(self: View): QVariant {.slot.} = - return newQVariant(self.collectible.getMediaUrl()) - QtProperty[QVariant] mediaUrl: - read = getMediaUrl - notify = currentCollectibleChanged - - proc getMediaType(self: View): QVariant {.slot.} = - return newQVariant(self.collectible.getMediaType()) - QtProperty[QVariant] mediaType: - read = getMediaType - notify = currentCollectibleChanged - - proc getImageUrl(self: View): QVariant {.slot.} = - return newQVariant(self.collectible.getImageUrl()) - QtProperty[QVariant] imageUrl: - read = getImageUrl - notify = currentCollectibleChanged - - proc getCollectionName(self: View): QVariant {.slot.} = - return newQVariant(self.collectible.getCollectionName()) - QtProperty[QVariant] collectionName: - read = getCollectionName - notify = currentCollectibleChanged - - proc getCollectionImageUrl(self: View): QVariant {.slot.} = - return newQVariant(self.collectible.getCollectionImageUrl()) - QtProperty[QVariant] collectionImageUrl: - read = getCollectionImageUrl - notify = currentCollectibleChanged - - proc getPermalink(self: View): QVariant {.slot.} = - return newQVariant(self.collectible.getPermalink()) - QtProperty[QVariant] permalink: - read = getPermalink - notify = currentCollectibleChanged - - proc getProperties*(self: View): QVariant {.slot.} = - return newQVariant(self.propertiesModel) - QtProperty[QVariant] properties: - read = getProperties - notify = currentCollectibleChanged - - proc getRankings*(self: View): QVariant {.slot.} = - return newQVariant(self.rankingsModel) - QtProperty[QVariant] rankings: - read = getRankings - notify = currentCollectibleChanged - - proc getStats*(self: View): QVariant {.slot.} = - return newQVariant(self.statsModel) - QtProperty[QVariant] stats: - read = getStats - notify = currentCollectibleChanged - - proc update*(self: View, address: string, tokenId: string) {.slot.} = - self.delegate.update(address, parse(tokenId, Uint256)) - - proc setData*(self: View, collectible: Item, network: network_dto.NetworkDto) = - if (self.networkShortName != network.shortName): - self.networkShortName = network.shortName - self.networkShortNameChanged() - - if (self.networkColor != network.chainColor): - self.networkColor = network.chainColor - self.networkColorChanged() - - if (self.networkIconUrl != network.iconURL): - self.networkIconUrl = network.iconURL - self.networkIconUrlChanged() - - self.collectible = collectible - self.propertiesModel.setItems(collectible.getProperties()) - self.rankingsModel.setItems(collectible.getRankings()) - self.statsModel.setItems(collectible.getStats()) - - self.currentCollectibleChanged() \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/collectibles/events_handler.nim b/src/app/modules/main/wallet_section/collectibles/events_handler.nim new file mode 100644 index 0000000000..20e4999881 --- /dev/null +++ b/src/app/modules/main/wallet_section/collectibles/events_handler.nim @@ -0,0 +1,72 @@ +import NimQml, logging, std/json, sequtils, strutils +import tables, stint + +import app/core/eventemitter +import app/core/signals/types + +import backend/collectibles as backend_collectibles + +type EventCallbackProc = proc (eventObject: JsonNode) +type WalletEventCallbackProc = proc (data: WalletSignal) + +# EventsHandler responsible for catching collectibles related backend events and reporting them +QtObject: + type + EventsHandler* = ref object of QObject + events: EventEmitter + eventHandlers: Table[string, EventCallbackProc] + walletEventHandlers: Table[string, WalletEventCallbackProc] + collectiblesOwnershipUpdateFinishedFn: proc() + + proc setup(self: EventsHandler) = + self.QObject.setup + + proc delete*(self: EventsHandler) = + self.QObject.delete + + proc onOwnedCollectiblesFilteringDone*(self: EventsHandler, handler: EventCallbackProc) = + self.eventHandlers[backend_collectibles.eventOwnedCollectiblesFilteringDone] = handler + + proc onCollectiblesOwnershipUpdateFinished*(self: EventsHandler, handler: proc()) = + self.collectiblesOwnershipUpdateFinishedFn = handler + + proc handleApiEvents(self: EventsHandler, e: Args) = + var data = WalletSignal(e) + + if self.walletEventHandlers.hasKey(data.eventType): + let callback = self.walletEventHandlers[data.eventType] + callback(data) + elif self.eventHandlers.hasKey(data.eventType): + var responseJson: JsonNode + responseJson = parseJson(data.message) + + if responseJson.kind != JObject: + error "unexpected json type", responseJson.kind + return + let callback = self.eventHandlers[data.eventType] + callback(responseJson) + else: + discard + + proc setupWalletEventHandlers(self: EventsHandler) = + self.walletEventHandlers[backend_collectibles.eventCollectiblesOwnershipUpdateFinished] = proc (data: WalletSignal) = + if self.collectiblesOwnershipUpdateFinishedFn == nil: + return + + self.collectiblesOwnershipUpdateFinishedFn() + + proc newEventsHandler*(events: EventEmitter): EventsHandler = + new(result, delete) + result.events = events + result.eventHandlers = initTable[string, EventCallbackProc]() + + result.setup() + + result.setupWalletEventHandlers() + + # Register for wallet events + let eventsHandler = result + result.events.on(SignalType.Wallet.event, proc(e: Args) = + eventsHandler.handleApiEvents(e) + ) + \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/collectibles/io_interface.nim b/src/app/modules/main/wallet_section/collectibles/io_interface.nim deleted file mode 100644 index 7aee089172..0000000000 --- a/src/app/modules/main/wallet_section/collectibles/io_interface.nim +++ /dev/null @@ -1,45 +0,0 @@ -import ../../../../../app_service/service/collectible/service - -type - AccessInterface* {.pure inheritable.} = ref object of RootObj - ## Abstract class for any input/interaction with this module. - -method delete*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") - -method load*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") - -method isLoaded*(self: AccessInterface): bool {.base.} = - raise newException(ValueError, "No implementation available") - -method filterChanged*(self: AccessInterface, addresses: seq[string], chainIds: seq[int]) {.base.} = - raise newException(ValueError, "No implementation available") - -method fetchOwnedCollectibles*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") - -method onFetchStarted*(self: AccessInterface, chainId: int, address: string) {.base.} = - raise newException(ValueError, "No implementation available") - -method setCollectibles*(self: AccessInterface, data: seq[CollectiblesData]) {.base.} = - raise newException(ValueError, "No implementation available") - -method appendCollectibles*(self: AccessInterface, chainId: int, address: string, data: CollectiblesData) {.base.} = - raise newException(ValueError, "No implementation available") - -method resetCollectibles*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") - -method viewDidLoad*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") - -method getHasCollectiblesCache*(self: AccessInterface): bool {.base.} = - raise newException(ValueError, "No implementation available") - -# Methods called by submodules of this module -method collectiblesModuleDidLoad*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") - -method currentCollectibleModuleDidLoad*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/wallet_section/collectibles/models/collectible_trait_item.nim b/src/app/modules/main/wallet_section/collectibles/models/collectible_trait_item.nim deleted file mode 100644 index c32d21bc8f..0000000000 --- a/src/app/modules/main/wallet_section/collectibles/models/collectible_trait_item.nim +++ /dev/null @@ -1,23 +0,0 @@ -type - CollectibleTrait* = object - traitType, value, displayType, maxValue: string - -proc getTraitType*(self: CollectibleTrait): string = - return self.traitType - -proc getValue*(self: CollectibleTrait): string = - return self.value - -proc getDisplayType*(self: CollectibleTrait): string = - return self.displayType - -proc getMaxValue*(self: CollectibleTrait): string = - return self.maxValue - -proc initTrait*( - traitType, value, displayType, maxValue: string -): CollectibleTrait = - result.traitType = traitType - result.value = value - result.displayType = displayType - result.maxValue = maxValue \ 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 deleted file mode 100644 index 6f1a8bdfc2..0000000000 --- a/src/app/modules/main/wallet_section/collectibles/models/collectibles_utils.nim +++ /dev/null @@ -1,30 +0,0 @@ -import sequtils, sugar, times -import ../../../../../../app_service/service/collectible/dto -import collectibles_item, collectible_trait_item - -proc collectibleToItem*(c: CollectibleDto, co: CollectionDto, isPinned: bool = false) : Item = - var mediaUrl = c.animationUrl - var mediaType = c.animationMediaType - if mediaUrl == "": - mediaUrl = c.imageUrl - mediaType = "image" - - return initItem( - c.id, - c.address, - c.tokenId, - c.name, - mediaUrl, - mediaType, - 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)), - co.name, - co.slug, - co.imageUrl, - isPinned - ) diff --git a/src/app/modules/main/wallet_section/collectibles/module.nim b/src/app/modules/main/wallet_section/collectibles/module.nim deleted file mode 100644 index 6e1277356e..0000000000 --- a/src/app/modules/main/wallet_section/collectibles/module.nim +++ /dev/null @@ -1,142 +0,0 @@ -import NimQml, Tables, sequtils, sugar - -import ../../../../global/global_singleton -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/wallet_account/service as wallet_account_service -import ../../../../../app_service/service/network/service as network_service -import ../../../../../app_service/service/node/service as node_service -import ../../../../../app_service/service/network_connection/service as network_connection_service - -import ./current_collectible/module as current_collectible_module - -import ./models/collectibles_item as collectibles_item -import ./models/collectible_trait_item as collectible_trait_item -import ./models/collectibles_utils -import ./models/collectibles_model as collectibles_model - -export io_interface - -type - Module* = ref object of io_interface.AccessInterface - delegate: delegate_interface.AccessInterface - view: View - controller: Controller - moduleLoaded: bool - - chainId: int - addresses: seq[string] - - currentCollectibleModule: current_collectible_module.AccessInterface - -proc newModule*( - delegate: delegate_interface.AccessInterface, - events: EventEmitter, - collectibleService: collectible_service.Service, - walletAccountService: wallet_account_service.Service, - networkService: network_service.Service, - nodeService: node_service.Service, - networkConnectionService: network_connection_service.Service -): Module = - result = Module() - result.delegate = delegate - result.view = newView(result) - result.controller = newController(result, events, collectibleService, walletAccountService, networkService, nodeService, networkConnectionService) - result.moduleLoaded = false - result.chainId = 0 - result.addresses = @[] - result.currentCollectibleModule = currentCollectibleModule.newModule(result, collectibleService) - -method delete*(self: Module) = - self.view.delete - self.currentCollectibleModule.delete - -method load*(self: Module) = - singletonInstance.engine.setRootContextProperty("walletSectionCollectibles", newQVariant(self.view)) - self.controller.init() - self.view.load() - - self.currentCollectibleModule.load - -method isLoaded*(self: Module): bool = - return self.moduleLoaded - -proc checkIfModuleDidLoad(self: Module) = - if self.moduleLoaded: - return - - if(not self.currentCollectibleModule.isLoaded()): - return - - self.moduleLoaded = true - self.delegate.collectiblesModuleDidLoad() - -method viewDidLoad*(self: Module) = - self.checkIfModuleDidLoad() - -method currentCollectibleModuleDidLoad*(self: Module) = - self.checkIfModuleDidLoad() - -method fetchOwnedCollectibles*(self: Module) = - self.controller.fetchOwnedCollectibles(self.chainId, self.addresses) - -method filterChanged*(self: Module, addresses: seq[string], chainIds: seq[int]) = - let network = self.controller.getNetwork() - self.chainId = network.chainId - self.addresses = addresses - self.currentCollectibleModule.setCurrentNetwork(network) - self.view.setCollectibles(@[]) - let data = self.controller.getOwnedCollectibles(self.chainId, self.addresses) - - for i, addressData in data.pairs: - if not addressData.anyLoaded: - self.controller.fetchOwnedCollectibles(self.chainId, @[self.addresses[i]]) - - self.setCollectibles(data) - -proc ownedCollectibleToItem(self: Module, oc: OwnedCollectible): Item = - let c = self.controller.getCollectible(self.chainId, oc.id) - let col = self.controller.getCollection(self.chainId, c.collectionSlug) - return collectibleToItem(c, col, oc.isFromWatchedContract) - -method onFetchStarted*(self: Module, chainId: int, address: string) = - if self.chainId == chainId and address in self.addresses: - self.view.setIsFetching(true) - -method resetCollectibles*(self: Module) = - let data = self.controller.getOwnedCollectibles(self.chainId, self.addresses) - self.setCollectibles(data) - -method appendCollectibles*(self: Module, chainId: int, address: string, data: CollectiblesData) = - if not (self.chainId == chainId and address in self.addresses): - return - - self.view.setIsError(data.isError) - - if data.isError and not data.anyLoaded: - # If fetching failed before being able to get any collectibles info, - # show loading animation - self.view.setIsFetching(true) - else: - self.view.setIsFetching(data.isFetching) - - if data.lastLoadCount > 0: - var ownedCollectiblesToAdd = newSeq[OwnedCollectible]() - for i in data.collectibles.len - data.lastLoadCount ..< data.collectibles.len: - ownedCollectiblesToAdd.add(data.collectibles[i]) - - let newCollectibles = ownedCollectiblesToAdd.map(oc => self.ownedCollectibleToItem(oc)) - - self.view.appendCollectibles(newCollectibles) - - self.view.setAllLoaded(data.allLoaded) - -method setCollectibles*(self: Module, data: seq[CollectiblesData]) = - for index, address in self.addresses: - self.appendCollectibles(self.chainId, address, data[index]) - -method getHasCollectiblesCache*(self: Module): bool = - return self.controller.getHasCollectiblesCache(self.addresses[0]) \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/collectibles/view.nim b/src/app/modules/main/wallet_section/collectibles/view.nim deleted file mode 100644 index 9e67ba599c..0000000000 --- a/src/app/modules/main/wallet_section/collectibles/view.nim +++ /dev/null @@ -1,53 +0,0 @@ -import NimQml - -import ./models/collectibles_model -import ./models/collectibles_item -import ./io_interface - -QtObject: - type - View* = ref object of QObject - delegate: io_interface.AccessInterface - model: Model - - proc delete*(self: View) = - self.model.delete - self.QObject.delete - - proc newView*(delegate: io_interface.AccessInterface): View = - new(result, delete) - result.QObject.setup - result.delegate = delegate - result.model = newModel() - signalConnect(result.model, "requestFetch()", result, "fetchMoreOwnedCollectibles()") - - proc load*(self: View) = - self.delegate.viewDidLoad() - - proc modelChanged*(self: View) {.signal.} - proc getModel(self: View): QVariant {.slot.} = - return newQVariant(self.model) - QtProperty[QVariant] model: - read = getModel - notify = modelChanged - - proc fetchMoreOwnedCollectibles*(self: View) {.slot.} = - self.delegate.fetchOwnedCollectibles() - - proc setIsError*(self: View, isError: bool) = - self.model.setIsError(isError) - - proc setIsFetching*(self: View, isFetching: bool) = - self.model.setIsFetching(isFetching) - - proc setAllLoaded*(self: View, allLoaded: bool) = - self.model.setAllCollectiblesLoaded(allLoaded) - - proc setCollectibles*(self: View, collectibles: seq[Item]) = - self.model.setItems(collectibles) - - proc appendCollectibles*(self: View, collectibles: seq[Item]) = - self.model.appendItems(collectibles) - - proc getHasCollectiblesCache(self: View): bool {.slot.} = - return self.delegate.getHasCollectiblesCache() \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/module.nim b/src/app/modules/main/wallet_section/module.nim index fa80a3d7a9..81e778a3b5 100644 --- a/src/app/modules/main/wallet_section/module.nim +++ b/src/app/modules/main/wallet_section/module.nim @@ -6,7 +6,6 @@ import ../io_interface as delegate_interface import ./accounts/module as accounts_module import ./all_tokens/module as all_tokens_module -import ./collectibles/module as collectibles_module import ./assets/module as assets_module import ./transactions/module as transactions_module import ./saved_addresses/module as saved_addresses_module @@ -17,6 +16,8 @@ import ./send/module as send_module import ../../shared_modules/add_account/module as add_account_module import ./activity/controller as activityc +import ./collectibles/controller as collectiblesc +import ./collectible_details/controller as collectible_detailsc import ../../../global/global_singleton import ../../../core/eventemitter @@ -49,7 +50,6 @@ type accountsModule: accounts_module.AccessInterface allTokensModule: all_tokens_module.AccessInterface - collectiblesModule: collectibles_module.AccessInterface assetsModule: assets_module.AccessInterface sendModule: send_module.AccessInterface transactionsModule: transactions_module.AccessInterface @@ -64,6 +64,8 @@ type walletAccountService: wallet_account_service.Service activityController: activityc.Controller + collectiblesController: collectiblesc.Controller + collectibleDetailsController: collectible_detailsc.Controller proc newModule*( delegate: delegate_interface.AccessInterface, @@ -92,7 +94,6 @@ proc newModule*( result.accountsModule = accounts_module.newModule(result, events, walletAccountService, networkService, currencyService) result.allTokensModule = all_tokens_module.newModule(result, events, tokenService, walletAccountService) - result.collectiblesModule = collectibles_module.newModule(result, events, collectibleService, walletAccountService, networkService, nodeService, networkConnectionService) result.assetsModule = assets_module.newModule(result, events, walletAccountService, networkService, tokenService, currencyService) result.transactionsModule = transactions_module.newModule(result, events, transactionService, walletAccountService, networkService, currencyService) result.sendModule = send_module.newModule(result, events, walletAccountService, networkService, currencyService, transactionService) @@ -102,15 +103,16 @@ proc newModule*( result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService) result.networksService = networkService result.activityController = activityc.newController(result.transactionsModule, currencyService, tokenService, events) + result.collectiblesController = collectiblesc.newController(events) + result.collectibleDetailsController = collectible_detailsc.newController(networkService, events) result.filter = initFilter(result.controller) - result.view = newView(result, result.activityController) + result.view = newView(result, result.activityController, result.collectiblesController, result.collectibleDetailsController) method delete*(self: Module) = self.accountsModule.delete self.allTokensModule.delete - self.collectiblesModule.delete self.assetsModule.delete self.transactionsModule.delete self.savedAddressesModule.delete @@ -119,6 +121,8 @@ method delete*(self: Module) = self.controller.delete self.view.delete self.activityController.delete + self.collectiblesController.delete + self.collectibleDetailsController.delete if not self.addAccountModule.isNil: self.addAccountModule.delete @@ -143,11 +147,11 @@ method notifyFilterChanged(self: Module) = let includeWatchOnly = self.controller.isIncludeWatchOnlyAccount() self.overviewModule.filterChanged(self.filter.addresses, self.filter.chainIds, includeWatchOnly, self.filter.allAddresses) self.assetsModule.filterChanged(self.filter.addresses, self.filter.chainIds) - self.collectiblesModule.filterChanged(self.filter.addresses, self.filter.chainIds) self.transactionsModule.filterChanged(self.filter.addresses, self.filter.chainIds) self.accountsModule.filterChanged(self.filter.addresses, self.filter.chainIds) self.sendModule.filterChanged(self.filter.addresses, self.filter.chainIds) self.activityController.globalFilterChanged(self.filter.addresses, self.filter.chainIds) + self.collectiblesController.globalFilterChanged(self.filter.addresses, self.filter.chainIds) if self.filter.addresses.len > 0: self.view.filterChanged(self.filter.addresses[0], includeWatchOnly, self.filter.allAddresses) @@ -214,7 +218,6 @@ method load*(self: Module) = self.view.load() self.accountsModule.load() self.allTokensModule.load() - self.collectiblesModule.load() self.assetsModule.load() self.transactionsModule.load() self.savedAddressesModule.load() @@ -233,9 +236,6 @@ proc checkIfModuleDidLoad(self: Module) = if(not self.allTokensModule.isLoaded()): return - if(not self.collectiblesModule.isLoaded()): - return - if(not self.assetsModule.isLoaded()): return diff --git a/src/app/modules/main/wallet_section/view.nim b/src/app/modules/main/wallet_section/view.nim index 3c9ea2b1f8..fb012f90f6 100644 --- a/src/app/modules/main/wallet_section/view.nim +++ b/src/app/modules/main/wallet_section/view.nim @@ -1,6 +1,8 @@ import NimQml, json import ./activity/controller as activityc +import ./collectibles/controller as collectiblesc +import ./collectible_details/controller as collectible_detailsc import ./io_interface import ../../shared_models/currency_amount @@ -14,6 +16,8 @@ QtObject: tmpAmount: float # shouldn't be used anywhere except in prepare*/getPrepared* procs tmpSymbol: string # shouldn't be used anywhere except in prepare*/getPrepared* procs activityController: activityc.Controller + collectiblesController: collectiblesc.Controller + collectibleDetailsController: collectible_detailsc.Controller proc setup(self: View) = self.QObject.setup @@ -21,10 +25,12 @@ QtObject: proc delete*(self: View) = self.QObject.delete - proc newView*(delegate: io_interface.AccessInterface, activityController: activityc.Controller): View = + proc newView*(delegate: io_interface.AccessInterface, activityController: activityc.Controller, collectiblesController: collectiblesc.Controller, collectibleDetailsController: collectible_detailsc.Controller): View = new(result, delete) result.delegate = delegate result.activityController = activityController + result.collectiblesController = collectiblesController + result.collectibleDetailsController = collectibleDetailsController result.setup() proc load*(self: View) = @@ -119,3 +125,13 @@ QtObject: return newQVariant(self.activityController) QtProperty[QVariant] activityController: read = getActivityController + + proc getCollectiblesController(self: View): QVariant {.slot.} = + return newQVariant(self.collectiblesController) + QtProperty[QVariant] collectiblesController: + read = getCollectiblesController + + proc getCollectibleDetailsController(self: View): QVariant {.slot.} = + return newQVariant(self.collectibleDetailsController) + QtProperty[QVariant] collectibleDetailsController: + read = getCollectibleDetailsController diff --git a/src/app/modules/shared_models/collectible_details_entry.nim b/src/app/modules/shared_models/collectible_details_entry.nim new file mode 100644 index 0000000000..466043d7b6 --- /dev/null +++ b/src/app/modules/shared_models/collectible_details_entry.nim @@ -0,0 +1,165 @@ +import NimQml, json, strformat, sequtils, strutils, stint, strutils + +import backend/collectibles as backend +import collectible_trait_model + +# Additional data needed to build an Entry, which is +# not included in the backend data and needs to be +# fetched from a different source. +type + ExtraData* = object + networkShortName*: string + networkColor*: string + networkIconURL*: string + +# It is used to display a detailed collectibles entry in the QML UI +QtObject: + type + CollectibleDetailsEntry* = ref object of QObject + id: backend.CollectibleUniqueID + data: backend.CollectibleData + extradata: ExtraData + traits: TraitModel + + proc setup(self: CollectibleDetailsEntry) = + self.QObject.setup + + proc delete*(self: CollectibleDetailsEntry) = + self.QObject.delete + + proc newCollectibleDetailsFullEntry*(data: backend.CollectibleData, extradata: ExtraData): CollectibleDetailsEntry = + new(result, delete) + result.id = data.id + result.data = data + result.extradata = extradata + result.traits = newTraitModel() + result.traits.setItems(data.traits) + result.setup() + + proc newCollectibleDetailsBasicEntry*(id: backend.CollectibleUniqueID, extradata: ExtraData): CollectibleDetailsEntry = + new(result, delete) + result.id = id + result.extradata = extradata + result.traits = newTraitModel() + result.setup() + + proc newCollectibleDetailsEmptyEntry*(): CollectibleDetailsEntry = + let id = backend.CollectibleUniqueID( + tokenID: stint.u256(0) + ) + let extradata = ExtraData() + return newCollectibleDetailsBasicEntry(id, extradata) + + proc `$`*(self: CollectibleDetailsEntry): string = + return fmt"""CollectibleDetailsEntry( + id:{self.id}, + data:{self.data}, + extradata:{self.extradata}, + traits:{self.traits} + )""" + + proc getChainID*(self: CollectibleDetailsEntry): int {.slot.} = + return self.id.chainID + + QtProperty[int] chainId: + read = getChainID + + proc getContractAddress*(self: CollectibleDetailsEntry): string {.slot.} = + return self.id.contractAddress + + QtProperty[string] contractAddress: + read = getContractAddress + + proc getTokenID*(self: CollectibleDetailsEntry): string {.slot.} = + return self.id.tokenID.toString() + + QtProperty[string] tokenId: + read = getTokenID + + proc getName*(self: CollectibleDetailsEntry): string {.slot.} = + if self.data == nil: + return "" + return self.data.name + + QtProperty[string] name: + read = getName + + proc getImageURL*(self: CollectibleDetailsEntry): string {.slot.} = + if self.data == nil: + return "" + return self.data.imageUrl + + QtProperty[string] imageUrl: + read = getImageURL + + proc getMediaURL*(self: CollectibleDetailsEntry): string {.slot.} = + if self.data == nil: + return "" + return self.data.animationUrl + + QtProperty[string] mediaUrl: + read = getMediaURL + + proc getMediaType*(self: CollectibleDetailsEntry): string {.slot.} = + if self.data == nil: + return "" + return self.data.animationMediaType + + QtProperty[string] mediaType: + read = getMediaType + + proc getBackgroundColor*(self: CollectibleDetailsEntry): string {.slot.} = + if self.data == nil: + return "" + return self.data.backgroundColor + + QtProperty[string] backgroundColor: + read = getBackgroundColor + + proc getCollectionName*(self: CollectibleDetailsEntry): string {.slot.} = + if self.data == nil: + return "" + return self.data.collectionData.name + + QtProperty[string] collectionName: + read = getCollectionName + + proc getDescription*(self: CollectibleDetailsEntry): string {.slot.} = + if self.data == nil: + return "" + return self.data.description + + QtProperty[string] description: + read = getDescription + + proc getCollectionImageURL*(self: CollectibleDetailsEntry): string {.slot.} = + if self.data == nil: + return "" + return self.data.collectionData.imageUrl + + QtProperty[string] collectionImageUrl: + read = getCollectionImageURL + + proc getTraits*(self: CollectibleDetailsEntry): QVariant {.slot.} = + return newQVariant(self.traits) + + QtProperty[QVariant] traits: + read = getTraits + + proc getNetworkShortName*(self: CollectibleDetailsEntry): string {.slot.} = + return self.extradata.networkShortName + + QtProperty[string] networkShortName: + read = getNetworkShortName + + proc getNetworkColor*(self: CollectibleDetailsEntry): string {.slot.} = + return self.extradata.networkColor + + QtProperty[string] networkColor: + read = getNetworkColor + + proc getNetworkIconURL*(self: CollectibleDetailsEntry): string {.slot.} = + return self.extradata.networkIconURL + + QtProperty[string] networkIconUrl: + read = getNetworkIconURL \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/collectibles/models/collectible_trait_model.nim b/src/app/modules/shared_models/collectible_trait_model.nim similarity index 86% rename from src/app/modules/main/wallet_section/collectibles/models/collectible_trait_model.nim rename to src/app/modules/shared_models/collectible_trait_model.nim index fbad796859..6fe7cb5499 100644 --- a/src/app/modules/main/wallet_section/collectibles/models/collectible_trait_model.nim +++ b/src/app/modules/shared_models/collectible_trait_model.nim @@ -1,6 +1,6 @@ import NimQml, Tables, strutils, strformat -import ./collectible_trait_item +import backend/collectibles as backend type ModelRole {.pure.} = enum @@ -12,7 +12,7 @@ type QtObject: type TraitModel* = ref object of QAbstractListModel - items: seq[CollectibleTrait] + items: seq[backend.CollectibleTrait] proc delete(self: TraitModel) = self.items = @[] @@ -61,13 +61,13 @@ QtObject: case enumRole: of ModelRole.TraitType: - result = newQVariant(item.getTraitType()) + result = newQVariant(item.trait_type) of ModelRole.Value: - result = newQVariant(item.getValue()) + result = newQVariant(item.value) of ModelRole.DisplayType: - result = newQVariant(item.getDisplayType()) + result = newQVariant(item.display_type) of ModelRole.MaxValue: - result = newQVariant(item.getMaxValue()) + result = newQVariant(item.max_value) proc setItems*(self: TraitModel, items: seq[CollectibleTrait]) = self.beginResetModel() diff --git a/src/app/modules/main/wallet_section/collectibles/models/collectibles_item.nim b/src/app/modules/shared_models/collectibles_item.nim similarity index 51% rename from src/app/modules/main/wallet_section/collectibles/models/collectibles_item.nim rename to src/app/modules/shared_models/collectibles_item.nim index b61c4c199e..968ca1d1be 100644 --- a/src/app/modules/main/wallet_section/collectibles/models/collectibles_item.nim +++ b/src/app/modules/shared_models/collectibles_item.nim @@ -1,67 +1,45 @@ import strformat, stint -import ./collectible_trait_item type Item* = object - id: int - address: string + chainId: int + contractAddress: string tokenId: UInt256 name: string mediaUrl: string mediaType: string imageUrl: string backgroundColor: string - description: string - permalink: string - properties: seq[CollectibleTrait] - rankings: seq[CollectibleTrait] - stats: seq[CollectibleTrait] collectionName: string - collectionSlug: string - collectionImageUrl: string isLoading: bool isPinned: bool proc initItem*( - id: int, - address: string, + chainId: int, + contractAddress: string, tokenId: UInt256, name: string, mediaUrl: string, mediaType: string, imageUrl: string, backgroundColor: string, - description: string, - permalink: string, - properties: seq[CollectibleTrait], - rankings: seq[CollectibleTrait], - stats: seq[CollectibleTrait], collectionName: string, - collectionSlug: string, - collectionImageUrl: string, isPinned: bool ): Item = - result.id = id - result.address = address + result.chainId = chainId + result.contractAddress = contractAddress result.tokenId = tokenId result.name = if (name != ""): name else: ("#" & tokenId.toString()) result.mediaUrl = mediaUrl result.mediaType = mediaType result.imageUrl = imageUrl result.backgroundColor = if (backgroundColor == ""): "transparent" else: ("#" & backgroundColor) - result.description = description - result.permalink = permalink - result.properties = properties - result.rankings = rankings - result.stats = stats result.collectionName = collectionName - result.collectionSlug = collectionSlug - result.collectionImageUrl = collectionImageUrl result.isLoading = false result.isPinned = isPinned proc initItem*: Item = - result = initItem(-1, "", u256(0), "", "", "", "", "transparent", "Collectibles", "", @[], @[], @[], "", "", "", false) + result = initItem(0, "", u256(0), "", "", "", "", "transparent", "Collectibles", false) proc initLoadingItem*: Item = result = initItem() @@ -69,32 +47,32 @@ proc initLoadingItem*: Item = proc `$`*(self: Item): string = result = fmt"""Collectibles( - id: {self.id}, - address: {self.address}, + chainId: {self.chainId}, + contractAddress: {self.contractAddress}, tokenId: {self.tokenId}, name: {self.name}, mediaUrl: {self.mediaUrl}, mediaType: {self.mediaType}, imageUrl: {self.imageUrl}, backgroundColor: {self.backgroundColor}, - description: {self.description}, - permalink: {self.permalink}, collectionName: {self.collectionName}, - collectionSlug: {self.collectionSlug}, - collectionImageUrl: {self.collectionImageUrl}, isLoading: {self.isLoading}, isPinned: {self.isPinned}, ]""" -proc getId*(self: Item): int = - return self.id +proc getChainId*(self: Item): int = + return self.chainId -proc getAddress*(self: Item): string = - return self.address +proc getContractAddress*(self: Item): string = + return self.contractAddress proc getTokenId*(self: Item): UInt256 = return self.tokenId +# Unique ID to identify collectible, generated by us +proc getId*(self: Item): string = + return fmt"{self.getChainId}+{self.getContractAddress}+{self.getTokenID}" + proc getName*(self: Item): string = return self.name @@ -110,30 +88,9 @@ proc getImageUrl*(self: Item): string = proc getBackgroundColor*(self: Item): string = return self.backgroundColor -proc getDescription*(self: Item): string = - return self.description - -proc getPermalink*(self: Item): string = - return self.permalink - -proc getProperties*(self: Item): seq[CollectibleTrait] = - return self.properties - -proc getRankings*(self: Item): seq[CollectibleTrait] = - return self.rankings - -proc getStats*(self: Item): seq[CollectibleTrait] = - 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 diff --git a/src/app/modules/main/wallet_section/collectibles/models/collectibles_model.nim b/src/app/modules/shared_models/collectibles_model.nim similarity index 64% rename from src/app/modules/main/wallet_section/collectibles/models/collectibles_model.nim rename to src/app/modules/shared_models/collectibles_model.nim index 188e8bbd9f..0c9edf4c0f 100644 --- a/src/app/modules/main/wallet_section/collectibles/models/collectibles_model.nim +++ b/src/app/modules/shared_models/collectibles_model.nim @@ -1,25 +1,20 @@ import NimQml, Tables, strutils, strformat, sequtils, stint +import logging import ./collectibles_item, ./collectible_trait_model type CollectibleRole* {.pure.} = enum - Id = UserRole + 1, - Address + Uid = UserRole + 1, + ChainId + ContractAddress TokenId Name + ImageUrl MediaUrl MediaType - ImageUrl BackgroundColor - Description - Permalink - Properties - Rankings - Stats CollectionName - CollectionSlug - CollectionImageUrl IsLoading IsPinned @@ -29,7 +24,7 @@ QtObject: type Model* = ref object of QAbstractListModel items: seq[Item] - allCollectiblesLoaded: bool + hasMore: bool isFetching: bool isError: bool loadingItemsStartIdx: int @@ -48,7 +43,7 @@ QtObject: new(result, delete) result.setup result.items = @[] - result.allCollectiblesLoaded = false + result.hasMore = true result.isFetching = false result.isError = false result.loadingItemsStartIdx = -1 @@ -92,46 +87,41 @@ QtObject: self.isError = value self.isErrorChanged() - 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: + proc hasMoreChanged*(self: Model) {.signal.} + proc getHasMore*(self: Model): bool {.slot.} = + self.hasMore + QtProperty[bool] hasMore: + read = getHasMore + notify = hasMoreChanged + proc setHasMore(self: Model, hasMore: bool) {.slot.} = + if hasMore == self.hasMore: return - self.allCollectiblesLoaded = value - self.allCollectiblesLoadedChanged() + self.hasMore = hasMore + self.hasMoreChanged() method canFetchMore*(self: Model, parent: QModelIndex): bool = - return not self.allCollectiblesLoaded and not self.isFetching and not self.isError + return self.hasMore + + proc loadMoreItems(self: Model) {.signal.} - proc requestFetch(self: Model) {.signal.} method fetchMore*(self: Model, parent: QModelIndex) = - self.requestFetch() + self.loadMoreItems() method rowCount*(self: Model, index: QModelIndex = nil): int = return self.items.len method roleNames(self: Model): Table[int, string] = { - CollectibleRole.Id.int:"id", - CollectibleRole.Address.int:"address", + CollectibleRole.Uid.int:"uid", + CollectibleRole.ChainId.int:"chainId", + CollectibleRole.ContractAddress.int:"contractAddress", CollectibleRole.TokenId.int:"tokenId", CollectibleRole.Name.int:"name", CollectibleRole.MediaUrl.int:"mediaUrl", CollectibleRole.MediaType.int:"mediaType", CollectibleRole.ImageUrl.int:"imageUrl", CollectibleRole.BackgroundColor.int:"backgroundColor", - CollectibleRole.Description.int:"description", - CollectibleRole.Permalink.int:"permalink", - CollectibleRole.Properties.int:"properties", - CollectibleRole.Rankings.int:"rankings", - CollectibleRole.Stats.int:"stats", CollectibleRole.CollectionName.int:"collectionName", - CollectibleRole.CollectionSlug.int:"collectionSlug", - CollectibleRole.CollectionImageUrl.int:"collectionImageUrl", CollectibleRole.IsLoading.int:"isLoading", CollectibleRole.IsPinned.int:"isPinned", }.toTable @@ -147,10 +137,12 @@ QtObject: let enumRole = role.CollectibleRole case enumRole: - of CollectibleRole.Id: + of CollectibleRole.Uid: result = newQVariant(item.getId()) - of CollectibleRole.Address: - result = newQVariant(item.getAddress()) + of CollectibleRole.ChainId: + result = newQVariant(item.getChainId()) + of CollectibleRole.ContractAddress: + result = newQVariant(item.getContractAddress()) of CollectibleRole.TokenId: result = newQVariant(item.getTokenId().toString()) of CollectibleRole.Name: @@ -163,28 +155,8 @@ QtObject: result = newQVariant(item.getImageUrl()) of CollectibleRole.BackgroundColor: result = newQVariant(item.getBackgroundColor()) - of CollectibleRole.Description: - result = newQVariant(item.getDescription()) - of CollectibleRole.Permalink: - result = newQVariant(item.getPermalink()) - of CollectibleRole.Properties: - let traits = newTraitModel() - traits.setItems(item.getProperties()) - result = newQVariant(traits) - of CollectibleRole.Rankings: - let traits = newTraitModel() - traits.setItems(item.getRankings()) - result = newQVariant(traits) - of CollectibleRole.Stats: - let traits = newTraitModel() - traits.setItems(item.getStats()) - 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()) of CollectibleRole.IsPinned: @@ -218,24 +190,29 @@ QtObject: self.endRemoveRows() self.countChanged() - proc setItems*(self: Model, items: seq[Item]) = - if self.isFetching: - self.removeLoadingItems() + proc resetModel*(self: Model, newItems: seq[Item]) = self.beginResetModel() - self.items = items + self.items = newItems self.endResetModel() - self.countChanged() - if self.isFetching: - self.appendLoadingItems() - proc appendItems*(self: Model, items: seq[Item]) = + proc setItems*(self: Model, newItems: seq[Item], offset: int, hasMore: bool) = if self.isFetching: self.removeLoadingItems() - 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() + + if offset == 0: + self.resetModel(newItems) + else: + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + + if offset != self.items.len: + error "offset != self.items.len" + return + self.beginInsertRows(parentModelIndex, self.items.len, self.items.len + newItems.len - 1) + self.items.add(newItems) + self.endInsertRows() self.countChanged() + self.setHasMore(hasMore) + if self.isFetching: self.appendLoadingItems() diff --git a/src/app/modules/shared_models/collectibles_utils.nim b/src/app/modules/shared_models/collectibles_utils.nim new file mode 100644 index 0000000000..0552c08473 --- /dev/null +++ b/src/app/modules/shared_models/collectibles_utils.nim @@ -0,0 +1,23 @@ +import sequtils, sugar, times +import backend/collectibles as backend +import collectibles_item + +proc collectibleToItem*(c: backend.CollectibleHeader, isPinned: bool = false) : Item = + var mediaUrl = c.animationUrl + var mediaType = c.animationMediaType + if mediaUrl == "": + mediaUrl = c.imageUrl + mediaType = "image" + + return initItem( + c.id.chainID, + c.id.contractAddress, + c.id.tokenID, + c.name, + mediaUrl, + mediaType, + c.imageUrl, + c.backgroundColor, + c.collectionName, + isPinned + ) diff --git a/src/app_service/service/collectible/async_tasks.nim b/src/app_service/service/collectible/async_tasks.nim index b18ca02a76..46705fd1c2 100644 --- a/src/app_service/service/collectible/async_tasks.nim +++ b/src/app_service/service/collectible/async_tasks.nim @@ -64,31 +64,3 @@ const fetchOwnedCollectiblesFromContractAddressesTaskArg: Task = proc(argEncoded "error": e.msg } arg.finish(output) - -type - FetchCollectiblesTaskArg = ref object of QObjectTaskArg - chainId*: int - ids*: seq[collectibles.NFTUniqueID] - limit: int - -const fetchCollectiblesTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[FetchCollectiblesTaskArg](argEncoded) - try: - let response = collectibles.getOpenseaAssetsByNFTUniqueID(arg.chainId, arg.ids, arg.limit) - - if not response.error.isNil: - raise newException(ValueError, "Error getOpenseaAssetsByNFTUniqueID" & response.error.message) - - let output = %* { - "chainId": arg.chainId, - "collectibles": response.result, - "error": "" - } - arg.finish(output) - except Exception as e: - let output = %* { - "chainId": arg.chainId, - "collectibles": "", - "error": e.msg - } - arg.finish(output) diff --git a/src/app_service/service/collectible/service.nim b/src/app_service/service/collectible/service.nim index 05f978319b..05d828ad2e 100644 --- a/src/app_service/service/collectible/service.nim +++ b/src/app_service/service/collectible/service.nim @@ -355,36 +355,6 @@ QtObject: return result.success = true - proc onRxCollectibles(self: Service, response: string) {.slot.} = - let responseObj = response.parseJson - let chainIdJson = responseObj["chainId"] - let chainId = chainIdJson.getInt() - - let errorStr = responseObj["error"].getStr() - if errorStr != "": - error "error onRxCollectibles: ", errorStr - else: - let (success, collectibles, collections, _, _) = processCollectiblesResult(responseObj) - if success: - self.updateCollectiblesCache(chainId, collectibles, collections) - else: - let errDesription = "Could not get data from response" - error "error onRxCollectibles: ", errDesription - - proc fetchCollectibles*(self: Service, chainId: int, ids: seq[UniqueID]) = - let arg = FetchCollectiblesTaskArg( - tptr: cast[ByteAddress](fetchCollectiblesTaskArg), - vptr: cast[ByteAddress](self.vptr), - slot: "onRxCollectibles", - chainId: chainId, - ids: ids.map(id => collectibles.NFTUniqueID( - contractAddress: id.contractAddress, - tokenID: id.tokenId.toString() - )), - limit: len(ids) - ) - self.threadpool.start(arg) - proc onRxOwnedCollectibles(self: Service, response: string) {.slot.} = let responseObj = response.parseJson let chainIdJson = responseObj["chainId"] diff --git a/src/app_service/service/transaction/async_tasks.nim b/src/app_service/service/transaction/async_tasks.nim index 3a4c246593..83d6e7e1b5 100644 --- a/src/app_service/service/transaction/async_tasks.nim +++ b/src/app_service/service/transaction/async_tasks.nim @@ -39,24 +39,25 @@ const loadTransactionsTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} output["allTxLoaded"] = %(response.getElems().len < arg.limit) # Fetch collectibles for transactions - var uniqueIds: seq[collectibles.NFTUniqueID] = @[] + var uniqueIds: seq[collectibles.CollectibleUniqueID] = @[] for txJson in response.getElems(): let tx = txJson.toTransactionDto() if tx.typeValue == ERC721_TRANSACTION_TYPE: - let nftId = collectibles.NFTUniqueID( + let nftId = collectibles.CollectibleUniqueID( + chainID: arg.chainId, contractAddress: tx.contract, - tokenID: tx.tokenId.toString(10) + tokenID: tx.tokenId ) if not uniqueIds.any(x => (x == nftId)): uniqueIds.add(nftId) if len(uniqueIds) > 0: - let collectiblesResponse = collectibles.getOpenseaAssetsByNFTUniqueID(arg.chainId, uniqueIds, arg.collectiblesLimit) + let collectiblesResponse = collectibles.getOpenseaAssetsByCollectibleUniqueID(uniqueIds) if not collectiblesResponse.error.isNil: # We don't want to prevent getting the list of transactions if we cannot get # NFT metadata. Just don't return the metadata. - let errDesription = "Error getOpenseaAssetsByNFTUniqueID" & collectiblesResponse.error.message + let errDesription = "Error getOpenseaAssetsByCollectibleUniqueID" & collectiblesResponse.error.message error "error loadTransactionsTask: ", errDesription else: output["collectibles"] = collectiblesResponse.result diff --git a/src/backend/collectibles.nim b/src/backend/collectibles.nim index c1fe3c9099..3c5b74a40b 100644 --- a/src/backend/collectibles.nim +++ b/src/backend/collectibles.nim @@ -1,41 +1,95 @@ import json, json_serialization, strformat +import stint, Tables +import core +import response_type, collectibles_types -import ./core, ./response_type +#import ./core, ./response_type from ./gen import rpc +export response_type, collectibles_types + +# Declared in services/wallet/collectibles/service.go +const eventCollectiblesOwnershipUpdateStarted*: string = "wallet-collectibles-ownership-update-started" +const eventCollectiblesOwnershipUpdateFinished*: string = "wallet-collectibles-ownership-update-finished" +const eventCollectiblesOwnershipUpdateFinishedWithError*: string = "wallet-collectibles-ownership-update-finished-with-error" + +const eventOwnedCollectiblesFilteringDone*: string = "wallet-owned-collectibles-filtering-done" +const eventGetCollectiblesDataDone*: string = "wallet-get-collectibles-data-done" + type - NFTUniqueID* = ref object of RootObj - contractAddress* {.serializedFieldName("contract_address").}: string - tokenID* {.serializedFieldName("token_id").}: string + # Mirrors services/wallet/collectibles/service.go ErrorCode + ErrorCode* = enum + ErrorCodeSuccess = 1, + ErrorCodeTaskCanceled, + ErrorCodeFailed -proc `$`*(self: NFTUniqueID): string = - return fmt"""NFTUniqueID( - contractAddress:{self.contractAddress}, - tokenID:{self.tokenID} - )""" + # Mirrors services/wallet/collectibles/service.go FilterOwnedCollectiblesResponse + FilterOwnedCollectiblesResponse* = object + collectibles*: seq[CollectibleHeader] + offset*: int + hasMore*: bool + errorCode*: ErrorCode -proc `==`*(a, b: NFTUniqueID): bool = - result = a.contractAddress == b.contractAddress and - a.tokenID == b.tokenID + # Mirrors services/wallet/collectibles/service.go GetCollectiblesDataResponse + GetCollectiblesDataResponse* = object + collectibles*: seq[CollectibleData] + errorCode*: ErrorCode -rpc(getOpenseaAssetsByOwnerWithCursor, "wallet"): + +# Responses +proc fromJson*(e: JsonNode, T: typedesc[FilterOwnedCollectiblesResponse]): FilterOwnedCollectiblesResponse {.inline.} = + var collectibles: seq[CollectibleHeader] + if e.hasKey("collectibles"): + let jsonCollectibles = e["collectibles"] + collectibles = newSeq[CollectibleHeader](jsonCollectibles.len) + for i in 0 ..< jsonCollectibles.len: + collectibles[i] = fromJson(jsonCollectibles[i], CollectibleHeader) + + result = T( + collectibles: collectibles, + offset: e["offset"].getInt(), + hasMore: if e.hasKey("hasMore"): e["hasMore"].getBool() + else: false, + errorCode: ErrorCode(e["errorCode"].getInt()) + ) + +proc fromJson*(e: JsonNode, T: typedesc[GetCollectiblesDataResponse]): GetCollectiblesDataResponse {.inline.} = + var collectibles: seq[CollectibleData] = @[] + if e.hasKey("collectibles"): + let jsonCollectibles = e["collectibles"] + for item in jsonCollectibles.getElems(): + collectibles.add(fromJson(item, CollectibleData)) + + result = T( + collectibles: collectibles, + errorCode: ErrorCode(e["errorCode"].getInt()) + ) + +rpc(getCollectiblesByOwnerWithCursor, "wallet"): chainId: int address: string cursor: string limit: int -rpc(getOpenseaAssetsByOwnerAndContractAddressWithCursor, "wallet"): +rpc(getCollectiblesByOwnerAndContractAddressWithCursor, "wallet"): chainId: int address: string contractAddresses: seq[string] cursor: string limit: int -rpc(getOpenseaAssetsByNFTUniqueID, "wallet"): - chainId: int - uniqueIds: seq[NFTUniqueID] - limit: int +rpc(getCollectiblesByUniqueID, "wallet"): + uniqueIds: seq[CollectibleUniqueID] rpc(getCollectibleOwnersByContractAddress, "wallet"): chainId: int contractAddress: string + +rpc(filterOwnedCollectiblesAsync, "wallet"): + chainIDs: seq[int] + addresses: seq[string] + offset: int + limit: int + +rpc(getCollectiblesDataAsync, "wallet"): + uniqueIds: seq[CollectibleUniqueID] diff --git a/src/backend/collectibles_types.nim b/src/backend/collectibles_types.nim new file mode 100644 index 0000000000..6bf31c9a4a --- /dev/null +++ b/src/backend/collectibles_types.nim @@ -0,0 +1,268 @@ +import json, strformat +import stint, Tables + +type + # Mirrors services/wallet/thirdparty/collectible_types.go CollectibleUniqueID + CollectibleUniqueID* = ref object of RootObj + chainID*: int + contractAddress*: string + tokenID*: UInt256 + + # Mirrors services/wallet/thirdparty/collectible_types.go CollectibleHeader + CollectibleHeader* = ref object of RootObj + id* : CollectibleUniqueID + name*: string + imageUrl*: string + animationUrl*: string + animationMediaType*: string + backgroundColor*: string + collectionName*: string + + # Mirrors services/wallet/thirdparty/collectible_types.go CollectibleTrait + CollectibleTrait* = ref object of RootObj + trait_type*: string + value*: string + display_type*: string + max_value*: string + + # Mirrors services/wallet/thirdparty/collectible_types.go CollectionTrait + CollectionTrait* = ref object of RootObj + min*: float + max*: float + + # Mirrors services/wallet/thirdparty/collectible_types.go CollectionData + CollectionData* = ref object of RootObj + name*: string + slug*: string + imageUrl*: string + traits*: Table[string, CollectionTrait] + + # Mirrors services/wallet/thirdparty/collectible_types.go CollectibleData + CollectibleData* = ref object of RootObj + id* : CollectibleUniqueID + name*: string + description*: string + permalink*: string + imageUrl*: string + animationUrl*: string + animationMediaType*: string + traits*: seq[CollectibleTrait] + backgroundColor*: string + tokenUri*: string + collectionData*: CollectionData + + # Mirrors services/wallet/thirdparty/collectible_types.go TokenBalance + CollectibleBalance* = ref object + tokenId*: UInt256 + balance*: UInt256 + + # Mirrors services/wallet/thirdparty/collectible_types.go CollectibleOwner + CollectibleOwner* = ref object + address*: string + balances*: seq[CollectibleBalance] + + # Mirrors services/wallet/thirdparty/collectible_types.go CollectibleContractOwnership + CollectibleContractOwnership* = ref object + contractAddress*: string + owners*: seq[CollectibleOwner] + +# CollectibleUniqueID +proc `$`*(self: CollectibleUniqueID): string = + return fmt"""CollectibleUniqueID( + chainID:{self.chainID}, + contractAddress:{self.contractAddress}, + tokenID:{self.tokenID} + )""" + +proc `==`*(a, b: CollectibleUniqueID): bool = + result = a.chainID == b.chainID and + a.contractAddress == b.contractAddress and + a.tokenID == b.tokenID + +proc `%`*(t: CollectibleUniqueID): JsonNode {.inline.} = + result = newJObject() + result["chainID"] = %(t.chainID) + result["contractAddress"] = %(t.contractAddress) + result["tokenID"] = %(t.tokenID.toString()) + +proc `%`*(t: ref CollectibleUniqueID): JsonNode {.inline.} = + return %(t[]) + +proc fromJson*(t: JsonNode, T: typedesc[CollectibleUniqueID]): CollectibleUniqueID {.inline.} = + result = CollectibleUniqueID() + result.chainID = t["chainID"].getInt() + result.contractAddress = t["contractAddress"].getStr() + result.tokenID = stint.parse(t["tokenID"].getStr(), UInt256) + +proc fromJson*(t: JsonNode, T: typedesc[ref CollectibleUniqueID]): ref CollectibleUniqueID {.inline.} = + result = new(CollectibleUniqueID) + result[] = fromJson(t, CollectibleUniqueID) + +# CollectibleHeader +proc `$`*(self: CollectibleHeader): string = + return fmt"""CollectibleHeader( + id:{self.id}, + name:{self.name}, + imageUrl:{self.imageUrl}, + animationUrl:{self.animationUrl}, + animationMediaType:{self.animationMediaType}, + backgroundColor:{self.backgroundColor}, + collectionName:{self.collectionName} + )""" + +proc fromJson*(t: JsonNode, T: typedesc[CollectibleHeader]): CollectibleHeader {.inline.} = + result = CollectibleHeader() + result.id = fromJson(t["id"], CollectibleUniqueID) + result.name = t["name"].getStr() + result.imageUrl = t["image_url"].getStr() + result.animationUrl = t["animation_url"].getStr() + result.animationMediaType = t["animation_media_type"].getStr() + result.backgroundColor = t["background_color"].getStr() + result.collectionName = t["collection_name"].getStr() + +# CollectibleTrait +proc `$`*(self: CollectibleTrait): string = + return fmt"""CollectibleTrait( + trait_type:{self.trait_type}, + value:{self.value}, + display_type:{self.display_type}, + max_value:{self.max_value} + )""" + +proc fromJson*(t: JsonNode, T: typedesc[CollectibleTrait]): CollectibleTrait {.inline.} = + result = CollectibleTrait() + result.trait_type = t["trait_type"].getStr() + result.value = t["value"].getStr() + result.display_type = t["display_type"].getStr() + result.max_value = t["max_value"].getStr() + +proc fromJson*(t: JsonNode, T: typedesc[ref CollectibleTrait]): ref CollectibleTrait {.inline.} = + result = new(CollectibleTrait) + result[] = fromJson(t, CollectibleTrait) + +# CollectionTrait +proc `$`*(self: CollectionTrait): string = + return fmt"""CollectionTrait( + min:{self.min}, + max:{self.max} + )""" + +proc fromJson*(t: JsonNode, T: typedesc[CollectionTrait]): CollectionTrait {.inline.} = + result = CollectionTrait() + result.min = t["min"].getFloat() + result.max = t["max"].getFloat() + +proc fromJson*(t: JsonNode, T: typedesc[ref CollectionTrait]): ref CollectionTrait {.inline.} = + result = new(CollectionTrait) + result[] = fromJson(t, CollectionTrait) + +# CollectionData +proc `$`*(self: CollectionData): string = + return fmt"""CollectionData( + name:{self.name}, + slug:{self.slug}, + imageUrl:{self.imageUrl}, + traits:{self.traits} + )""" + +proc getCollectionTraits*(t: JsonNode): Table[string, CollectionTrait] = + var traitList: Table[string, CollectionTrait] = initTable[string, CollectionTrait]() + for key, value in t{"traits"}.getFields(): + traitList[key] = fromJson(value, CollectionTrait) + return traitList + +proc fromJson*(t: JsonNode, T: typedesc[CollectionData]): CollectionData {.inline.} = + result = CollectionData() + result.name = t["name"].getStr() + result.slug = t["slug"].getStr() + result.imageUrl = t["image_url"].getStr() + result.traits = getCollectionTraits(t["traits"]) + +proc fromJson*(t: JsonNode, T: typedesc[ref CollectionData]): ref CollectionData {.inline.} = + result = new(CollectionData) + result[] = fromJson(t, CollectionData) + +# CollectibleData +proc `$`*(self: CollectibleData): string = + return fmt"""CollectibleData( + id:{self.id}, + name:{self.name}, + description:{self.description}, + permalink:{self.permalink}, + imageUrl:{self.imageUrl}, + animationUrl:{self.animationUrl}, + animationMediaType:{self.animationMediaType}, + traits:{self.traits}, + backgroundColor:{self.backgroundColor}, + tokenUri:{self.tokenUri}, + collectionData:{self.collectionData} + )""" + +proc getCollectibleTraits*(t: JsonNode): seq[CollectibleTrait] = + var traitList: seq[CollectibleTrait] = @[] + for item in t.getElems(): + traitList.add(fromJson(item, CollectibleTrait)) + return traitList + +proc fromJson*(t: JsonNode, T: typedesc[CollectibleData]): CollectibleData {.inline.} = + result = CollectibleData() + result.id = fromJson(t["id"], CollectibleUniqueID) + result.name = t["name"].getStr() + result.description = t["description"].getStr() + result.permalink = t["permalink"].getStr() + result.imageUrl = t["image_url"].getStr() + result.animationUrl = t["animation_url"].getStr() + result.animationMediaType = t["animation_media_type"].getStr() + result.traits = getCollectibleTraits(t["traits"]) + result.backgroundColor = t["background_color"].getStr() + result.tokenUri = t["token_uri"].getStr() + result.collectionData = fromJson(t["collection_data"], CollectionData) + +proc fromJson*(t: JsonNode, T: typedesc[ref CollectibleData]): ref CollectibleData {.inline.} = + result = new(CollectibleData) + result[] = fromJson(t, CollectibleData) + +# CollectibleBalance +proc `$`*(self: CollectibleBalance): string = + return fmt"""CollectibleBalance( + tokenId:{self.tokenId}, + balance:{self.balance} + """ + +proc getCollectibleBalances(jsonAsset: JsonNode): seq[CollectibleBalance] = + var balanceList: seq[CollectibleBalance] = @[] + for item in jsonAsset.items: + balanceList.add(CollectibleBalance( + tokenId: stint.parse(item{"tokenId"}.getStr, Uint256), + balance: stint.parse(item{"balance"}.getStr, Uint256) + )) + return balanceList + +# CollectibleOwner +proc `$`*(self: CollectibleOwner): string = + return fmt"""CollectibleOwner( + address:{self.address}, + balances:{self.balances} + """ + +proc getCollectibleOwners(jsonAsset: JsonNode): seq[CollectibleOwner] = + var ownerList: seq[CollectibleOwner] = @[] + for item in jsonAsset.items: + ownerList.add(CollectibleOwner( + address: item{"ownerAddress"}.getStr, + balances: getCollectibleBalances(item{"tokenBalances"}) + )) + return ownerList + +# CollectibleContractOwnership +proc `$`*(self: CollectibleContractOwnership): string = + return fmt"""CollectibleContractOwnership( + contractAddress:{self.contractAddress}, + owners:{self.owners} + """ + +proc fromJson*(t: JsonNode, T: typedesc[CollectibleContractOwnership]): CollectibleContractOwnership {.inline.} = + return CollectibleContractOwnership( + contractAddress: t{"contractAddress"}.getStr, + owners: getCollectibleOwners(t{"owners"}) + ) \ No newline at end of file diff --git a/storybook/pages/ProfileDialogViewPage.qml b/storybook/pages/ProfileDialogViewPage.qml index 13da6bd05d..160a2cdbd9 100644 --- a/storybook/pages/ProfileDialogViewPage.qml +++ b/storybook/pages/ProfileDialogViewPage.qml @@ -228,10 +228,6 @@ SplitView { logs.logEvent("walletStore::setFilterAddress", ["address"], arguments) } - function selectCollectible(slug, id) { - logs.logEvent("walletStore::selectCollectible", ["slug", "id"], arguments) - } - readonly property var accounts: ListModel { ListElement { name: "My Status Account" @@ -338,7 +334,7 @@ SplitView { Component.onCompleted: append(data) } - readonly property var flatCollectibles: ListModel { + readonly property var collectibles: ListModel { readonly property var data: [ { //id: 123, diff --git a/storybook/pages/ProfileShowcaseCollectiblesPanelPage.qml b/storybook/pages/ProfileShowcaseCollectiblesPanelPage.qml index af4345709c..cff43888ea 100644 --- a/storybook/pages/ProfileShowcaseCollectiblesPanelPage.qml +++ b/storybook/pages/ProfileShowcaseCollectiblesPanelPage.qml @@ -28,63 +28,52 @@ SplitView { id: collectiblesModel readonly property var data: [ { - id: 123, + uid: "123", name: "SNT", - description: "", collectionName: "Super Nitro Toluen (with pink bg)", backgroundColor: "pink", imageUrl: ModelsData.collectibles.custom, - permalink: "green", isLoading: false }, { - id: 34545656768, + uid: "34545656768", name: "Kitty 1", - description: "", collectionName: "Kitties", backgroundColor: "", imageUrl: ModelsData.collectibles.kitty1Big, - permalink: "", isLoading: false }, { - id: 123456, + uid: "123456", name: "Kitty 2", - description: "", collectionName: "", backgroundColor: "", imageUrl: ModelsData.collectibles.kitty2Big, - permalink: "", isLoading: false }, { - id: 12345645459537432, + uid: "12345645459537432", name: "", - description: "Kitty 3 description", collectionName: "Super Kitties", backgroundColor: "oink", imageUrl: ModelsData.collectibles.kitty3Big, - permalink: "", isLoading: false }, { - id: 691, + uid: "691", name: "KILLABEAR", - description: "Please note that weapons are not yet reflected in the rarity stats.", collectionName: "KILLABEARS", backgroundColor: "#807c56", imageUrl: "https://assets.killabears.com/content/killabears/img/691-e81f892696a8ae700e0dbc62eb072060679a2046d1ef5eb2671bdb1fad1f68e3.png", - permalink: "https://opensea.io/assets/ethereum/0xc99c679c50033bbc5321eb88752e89a93e9e83c5/691", isLoading: true }, { - id: 8876, + uid: "8876", name: "AIORBIT", description: "", collectionName: "AIORBIT (Animated SVG)", backgroundColor: "", imageUrl: "https://dl.openseauserdata.com/cache/originImage/files/8b14ef530b28853445c27d6693c4e805.svg", - permalink: "https://opensea.io/assets/ethereum/0xba66a7c5e1f89a542e3108e3df155a9bf41ac824/8876", isLoading: false } ] diff --git a/ui/app/AppLayouts/Profile/panels/ProfileShowcaseCollectiblesPanel.qml b/ui/app/AppLayouts/Profile/panels/ProfileShowcaseCollectiblesPanel.qml index 5b3c67c73c..5749560231 100644 --- a/ui/app/AppLayouts/Profile/panels/ProfileShowcaseCollectiblesPanel.qml +++ b/ui/app/AppLayouts/Profile/panels/ProfileShowcaseCollectiblesPanel.qml @@ -8,9 +8,9 @@ ProfileShowcasePanel { id: root settingsKey: "collectibles" - keyRole: "id" - roleNames: ["id", "name", "description", "collectionName", "backgroundColor", "imageUrl"] - filterFunc: (modelData) => !showcaseModel.hasItem(modelData.id) + keyRole: "uid" + roleNames: ["uid", "name", "collectionName", "backgroundColor", "imageUrl"] + filterFunc: (modelData) => !showcaseModel.hasItem(modelData.uid) hiddenPlaceholderBanner: qsTr("Collectibles here will show on your profile") showcasePlaceholderBanner: qsTr("Collectibles here will be hidden from your profile") diff --git a/ui/app/AppLayouts/Profile/stores/WalletStore.qml b/ui/app/AppLayouts/Profile/stores/WalletStore.qml index 8878863d3e..dd8b5ad6c4 100644 --- a/ui/app/AppLayouts/Profile/stores/WalletStore.qml +++ b/ui/app/AppLayouts/Profile/stores/WalletStore.qml @@ -20,8 +20,8 @@ QtObject { // TODO(alaibe): there should be no access to wallet section, create collectible in profile property var overview: walletSectionOverview - property var flatCollectibles: Global.appIsReady ? walletSectionCollectibles.model : null property var assets: walletSectionAssets.assets + property var collectibles: Global.appIsReady ? walletSection.collectiblesController.model : null // To-do: Fetch profile collectibles separately property var accounts: Global.appIsReady? accountsModule.accounts : null property var originModel: accountsModule.keyPairModel property bool includeWatchOnlyAccount: accountsModule.includeWatchOnlyAccount diff --git a/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml b/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml index 1ad0504f2a..bb263589d5 100644 --- a/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml +++ b/ui/app/AppLayouts/Profile/views/profile/MyProfileSettingsView.qml @@ -211,7 +211,7 @@ ColumnLayout { ProfileShowcaseCollectiblesPanel { Layout.minimumHeight: implicitHeight Layout.maximumHeight: implicitHeight - baseModel: root.walletStore.flatCollectibles + baseModel: root.walletStore.collectibles } ProfileShowcaseAssetsPanel { diff --git a/ui/app/AppLayouts/Wallet/stores/ActivityFiltersStore.qml b/ui/app/AppLayouts/Wallet/stores/ActivityFiltersStore.qml index 3033352c1d..5734fcf85a 100644 --- a/ui/app/AppLayouts/Wallet/stores/ActivityFiltersStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/ActivityFiltersStore.qml @@ -156,7 +156,8 @@ QtObject { } // Collectibles Filters - property var collectiblesList: walletSectionCollectibles.model + // To-do: Get list of collectibles with activity from backend + property var collectiblesList: walletSection.collectiblesController.model property var collectiblesFilter: [] function toggleCollectibles(id) { // update filters diff --git a/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml b/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml new file mode 100644 index 0000000000..9c6bf03154 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml @@ -0,0 +1,23 @@ + +import QtQuick 2.12 +import utils 1.0 + +QtObject { + id: root + readonly property var ownedCollectibles: Global.appIsReady ? walletSection.collectiblesController.model : null + + readonly property var detailedCollectible: Global.appIsReady ? walletSection.collectibleDetailsController.detailedEntry : null + readonly property var detailedCollectibleStatus: Global.appIsReady ? walletSection.collectibleDetailsController.status : null + readonly property bool isDetailedCollectibleLoading: Global.appIsReady ? walletSection.collectibleDetailsController.isDetailedEntryLoading : true + + function fetchMoreCollectibles() { + if (!root.ownedCollectibles.hasMore + || root.ownedCollectibes.isFetching) + return + walletSection.collectiblesController.loadMoreItems() + } + + function getDetailedCollectible(chainId, contractAddress, tokenId) { + walletSection.collectibleDetailsController.getDetailedCollectible(chainId, contractAddress, tokenId) + } +} \ No newline at end of file diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index 1751636de5..ea707fd9df 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -29,8 +29,7 @@ QtObject { property string signingPhrase: walletSection.signingPhrase property string mnemonicBackedUp: walletSection.isMnemonicBackedUp - property var flatCollectibles: walletSectionCollectibles.model - property var currentCollectible: walletSectionCurrentCollectible + property CollectiblesStore collectiblesStore: CollectiblesStore {} property var areTestNetworksEnabled: networksModule.areTestNetworksEnabled @@ -175,19 +174,6 @@ QtObject { return globalUtils.hex2Dec(value) } - function getCollectionMaxValue(traitType, value, maxValue, collectionIndex) { - // Not Refactored Yet -// if(maxValue !== "") -// return parseInt(value) + qsTr(" of ") + maxValue; -// else -// return parseInt(value) + qsTr(" of ") + -// walletModelV2Inst.collectiblesView.collections.getCollectionTraitMaxValue(collectionIndex, traitType).toString(); - } - - function selectCollectible(address, tokenId) { - walletSectionCurrentCollectible.update(address, tokenId) - } - function getNameForSavedWalletAddress(address) { return walletSectionSavedAddresses.getNameByAddress(address) } diff --git a/ui/app/AppLayouts/Wallet/stores/qmldir b/ui/app/AppLayouts/Wallet/stores/qmldir index fd12c5a1e8..1e5e73ec8f 100644 --- a/ui/app/AppLayouts/Wallet/stores/qmldir +++ b/ui/app/AppLayouts/Wallet/stores/qmldir @@ -1,2 +1,3 @@ singleton RootStore 1.0 RootStore.qml ActivityFiltersStore 1.0 ActivityFiltersStore.qml +CollectiblesStore 1.0 CollectiblesStore.qml diff --git a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml index fc7439713c..132a4117e5 100644 --- a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml +++ b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml @@ -16,7 +16,7 @@ Item { property var collectiblesModel width: parent.width - signal collectibleClicked(string address, string tokenId) + signal collectibleClicked(int chainId, string contractAddress, string tokenId) Loader { id: contentLoader @@ -24,8 +24,10 @@ Item { height: parent.height sourceComponent: { - if (root.collectiblesModel.allCollectiblesLoaded && root.collectiblesModel.count === 0) + /* TODO: Issue #11635 + if (!root.collectiblesModel.hasMore && root.collectiblesModel.count === 0) return empty; + */ return loaded; } } @@ -62,7 +64,7 @@ Item { backgroundColor: model.backgroundColor ? model.backgroundColor : "transparent" isLoading: model.isLoading - onClicked: root.collectibleClicked(model.address, model.tokenId) + onClicked: root.collectibleClicked(model.chainId, model.contractAddress, model.tokenId) } ScrollBar.vertical: StatusScrollBar {} diff --git a/ui/app/AppLayouts/Wallet/views/RightTabView.qml b/ui/app/AppLayouts/Wallet/views/RightTabView.qml index 3409fe928f..ee98c3f0df 100644 --- a/ui/app/AppLayouts/Wallet/views/RightTabView.qml +++ b/ui/app/AppLayouts/Wallet/views/RightTabView.qml @@ -115,9 +115,9 @@ Item { } } CollectiblesView { - collectiblesModel: RootStore.flatCollectibles + collectiblesModel: RootStore.collectiblesStore.ownedCollectibles onCollectibleClicked: { - RootStore.selectCollectible(address, tokenId) + RootStore.collectiblesStore.getDetailedCollectible(chainId, contractAddress, tokenId) stack.currentIndex = 1 } } @@ -134,6 +134,8 @@ Item { CollectibleDetailView { Layout.fillWidth: true Layout.fillHeight: true + collectible: RootStore.collectiblesStore.detailedCollectible + isCollectibleLoading: RootStore.collectiblesStore.isDetailedCollectibleLoading } AssetsDetailView { id: assetDetailView diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml index 3d579282b0..c6a071ab9d 100644 --- a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml +++ b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml @@ -16,7 +16,8 @@ import "../../controls" Item { id: root - property var currentCollectible: RootStore.currentCollectible + property var collectible + property bool isCollectibleLoading readonly property int isNarrowMode : width < 700 CollectibleDetailsHeader { @@ -24,14 +25,14 @@ Item { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - asset.name: currentCollectible.collectionImageUrl + asset.name: collectible.collectionImageUrl asset.isImage: true - primaryText: currentCollectible.collectionName - secondaryText: "#" + currentCollectible.tokenId + primaryText: collectible.collectionName + secondaryText: "#" + collectible.tokenId isNarrowMode: root.isNarrowMode - networkShortName: currentCollectible.networkShortName - networkColor: currentCollectible.networkColor - networkIconURL: currentCollectible.networkIconUrl + networkShortName: collectible.networkShortName + networkColor: collectible.networkColor + networkIconURL: collectible.networkIconUrl } ColumnLayout { @@ -57,12 +58,12 @@ Item { width: size height: size radius: 2 - color: currentCollectible.backgroundColor + color: collectible.backgroundColor border.color: Theme.palette.directColor8 border.width: 1 - mediaUrl: currentCollectible.mediaUrl - mediaType: currentCollectible.mediaType - fallbackImageUrl: currentCollectible.imageUrl + mediaUrl: collectible.mediaUrl + mediaType: collectible.mediaType + fallbackImageUrl: collectible.imageUrl } Column { @@ -76,7 +77,7 @@ Item { width: parent.width height: 24 - text: currentCollectible.name + text: collectible.name color: Theme.palette.directColor1 font.pixelSize: 17 lineHeight: 24 @@ -97,7 +98,7 @@ Item { id: descriptionText width: descriptionScrollView.availableWidth - text: currentCollectible.description + text: collectible.description textFormat: Text.MarkdownText color: Theme.palette.directColor4 font.pixelSize: 15 @@ -114,7 +115,7 @@ Item { id: collectiblesDetailsTab Layout.fillWidth: true Layout.topMargin: root.isNarrowMode ? 0 : Style.current.xlPadding - visible: currentCollectible.properties.count > 0 + visible: collectible.traits.count > 0 StatusTabButton { leftPadding: 0 @@ -132,7 +133,7 @@ Item { width: scrollView.availableWidth spacing: 10 Repeater { - model: currentCollectible.properties + model: collectible.traits InformationTile { maxWidth: parent.width primaryText: model.traitType diff --git a/ui/imports/shared/stores/NetworkConnectionStore.qml b/ui/imports/shared/stores/NetworkConnectionStore.qml index 4dc27b63ce..7cb672bb7b 100644 --- a/ui/imports/shared/stores/NetworkConnectionStore.qml +++ b/ui/imports/shared/stores/NetworkConnectionStore.qml @@ -13,7 +13,7 @@ QtObject { readonly property bool balanceCache: walletSectionAssets.hasBalanceCache readonly property bool marketValuesCache: walletSectionAssets.hasMarketValuesCache - readonly property bool collectiblesCache: walletSectionCollectibles.getHasCollectiblesCache() + readonly property bool collectiblesCache: false // TODO: Issue #11636 readonly property var blockchainNetworksDown: !!networkConnectionModule.blockchainNetworkConnection.chainIds ? networkConnectionModule.blockchainNetworkConnection.chainIds.split(";") : [] readonly property bool atleastOneBlockchainNetworkAvailable: blockchainNetworksDown.length < networksModule.all.count diff --git a/ui/imports/shared/views/profile/ProfileShowcaseView.qml b/ui/imports/shared/views/profile/ProfileShowcaseView.qml index 6b7bf5b1bd..cf36613c5c 100644 --- a/ui/imports/shared/views/profile/ProfileShowcaseView.qml +++ b/ui/imports/shared/views/profile/ProfileShowcaseView.qml @@ -247,7 +247,8 @@ Control { cellWidth: (width-rightMargin)/4 cellHeight: cellWidth visible: count - model: root.isCurrentUser ? root.walletStore.flatCollectibles : null // TODO show other users too + // TODO Issue #11637: Dedicated controller for user's list of collectibles (no watch-only entries) + model: root.isCurrentUser ? root.walletStore.ownedCollectibles : null ScrollBar.vertical: StatusScrollBar { } delegate: StatusRoundedImage { width: GridView.view.cellWidth - Style.current.smallPadding diff --git a/vendor/status-go b/vendor/status-go index 3d1b1bab57..10a42e639d 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 3d1b1bab572eeedb7028abf2486dec3d9fe04858 +Subproject commit 10a42e639d6c455e2a14a89006c8d653e9d1d441