diff --git a/src/app/modules/shared_models/collectibles_entry.nim b/src/app/modules/shared_models/collectibles_entry.nim index b828ff6d34..1c2e9261d9 100644 --- a/src/app/modules/shared_models/collectibles_entry.nim +++ b/src/app/modules/shared_models/collectibles_entry.nim @@ -348,6 +348,14 @@ QtObject: read = getTwitterHandle notify = twitterHandleChanged + proc isMetadataValidChanged*(self: CollectiblesEntry) {.signal.} + proc getIsMetaDataValid*(self: CollectiblesEntry): bool {.slot.} = + return self.hasCollectibleData() + + QtProperty[bool] isMetadataValid: + read = getIsMetaDataValid + notify = isMetadataValidChanged + proc updateDataIfSameID*(self: CollectiblesEntry, update: backend.Collectible): bool = if self.id != update.id: return false diff --git a/src/app/modules/shared_modules/collectible_details/controller.nim b/src/app/modules/shared_modules/collectible_details/controller.nim index bfba625c9c..2bd8dc9211 100644 --- a/src/app/modules/shared_modules/collectible_details/controller.nim +++ b/src/app/modules/shared_modules/collectible_details/controller.nim @@ -39,8 +39,8 @@ QtObject: read = getDetailedEntry notify = detailedEntryChanged - proc getIsDetailedEntryLoading*(self: Controller): QVariant {.slot.} = - return newQVariant(self.detailedEntry) + proc getIsDetailedEntryLoading*(self: Controller): bool {.slot.} = + return self.isDetailedEntryLoading proc isDetailedEntryLoadingChanged(self: Controller) {.signal.} diff --git a/storybook/pages/CollectibleMediaPage.qml b/storybook/pages/CollectibleMediaPage.qml new file mode 100644 index 0000000000..0066403785 --- /dev/null +++ b/storybook/pages/CollectibleMediaPage.qml @@ -0,0 +1,129 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import Storybook 1.0 + +import AppLayouts.Communities.stores 1.0 as CommunitiesStores +import AppLayouts.Wallet 1.0 +import AppLayouts.Wallet.stores 1.0 as WalletStores +import AppLayouts.Wallet.views.collectibles 1.0 + +import StatusQ.Core.Utils 0.1 + +import shared.controls 1.0 +import shared.stores 1.0 as SharedStores + +import Models 1.0 +import utils 1.0 + +SplitView { + id: root + + // QtObject { + // function isValidURL(url) { + // return true + // } + + // Component.onCompleted: { + // Utils.globalUtilsInst = this + // } + // Component.onDestruction: { + // Utils.globalUtilsInst = {} + // } + // } + + QtObject { + id: d + + readonly property QtObject collectiblesModel: ManageCollectiblesModel { + Component.onCompleted: { + d.refreshCurrentCollectible() + } + } + property var currentCollectible + + function refreshCurrentCollectible() { + currentCollectible = ModelUtils.get(collectiblesModel, collectibleComboBox.currentIndex) + } + } + + SplitView { + orientation: Qt.Vertical + SplitView.fillWidth: true + Item { + SplitView.fillWidth: true + SplitView.fillHeight: true + + Rectangle { + anchors.fill: viewLoader + anchors.margins: -1 + color: "transparent" + border.width: 1 + border.color: "#808080" + } + + Loader { + id: viewLoader + anchors.fill: parent + anchors.margins: 50 + + active: false + + sourceComponent: CollectibleMedia { + backgroundColor: d.currentCollectible.backgroundColor + isCollectibleLoading: isLoadingCheckbox.checked + isMetadataValid: !d.currentCollectible.isMetadataValid + mediaUrl: d.currentCollectible.mediaUrl ?? "" + fallbackImageUrl: d.currentCollectible.imageUrl + interactive: isInteractiveCheckbox.checked + enabled: isEnabledCheckbox.checked + } + Component.onCompleted: viewLoader.active = true + } + } + + LogsAndControlsPanel { + SplitView.minimumHeight: 100 + SplitView.preferredHeight: 150 + + SplitView.fillWidth: true + } + } + + Pane { + SplitView.minimumWidth: 300 + SplitView.preferredWidth: 300 + + ColumnLayout { + Label { + text: "Collectible:" + } + ComboBox { + id: collectibleComboBox + Layout.fillWidth: true + textRole: "name" + model: d.collectiblesModel + currentIndex: 0 + onCurrentIndexChanged: d.refreshCurrentCollectible() + } + CheckBox { // Loading state when model is loading, it doesn't affect internal image loading state + id: isLoadingCheckbox + text: "isLoading" + checked: false + } + CheckBox { + id: isInteractiveCheckbox + text: "isInteractive" + checked: true + } + CheckBox { + id: isEnabledCheckbox + text: "isEnabled" + checked: true + } + } + } +} + +// category: Wallet diff --git a/storybook/qmlTests/tests/tst_ManageCollectiblesPanel.qml b/storybook/qmlTests/tests/tst_ManageCollectiblesPanel.qml index fc3322c97e..c92b554b08 100644 --- a/storybook/qmlTests/tests/tst_ManageCollectiblesPanel.qml +++ b/storybook/qmlTests/tests/tst_ManageCollectiblesPanel.qml @@ -134,7 +134,7 @@ Item { const lvOther = findChild(controlUnderTest, "otherTokensListView") verify(!!lvOther) - tryCompare(lvOther, "count", 9) + tryCompare(lvOther, "count", 10) const delegate0 = findChild(lvOther, "manageTokensDelegate-0") verify(!!delegate0) const title = delegate0.title @@ -144,7 +144,7 @@ Item { tryCompare(notificationSpy, "count", 1) // verify we now have -1 regular tokens after the "hide" operation - tryCompare(lvOther, "count", 8) + tryCompare(lvOther, "count", 9) } function test_showHideCommunityGroup() { diff --git a/storybook/src/Models/ManageCollectiblesModel.qml b/storybook/src/Models/ManageCollectiblesModel.qml index 13ace2f55d..7bee066008 100644 --- a/storybook/src/Models/ManageCollectiblesModel.qml +++ b/storybook/src/Models/ManageCollectiblesModel.qml @@ -64,7 +64,8 @@ ListModel { ], tokenId: "403", twitterHandle: "@punxNotDead", - website: "www.punxnotdead.com" + website: "www.punxnotdead.com", + isMetadataValid: true }, { uid: "pp23", @@ -109,7 +110,8 @@ ListModel { ], tokenId: "123", twitterHandle: "@pepepunks", - website: "www.pepepunks.com" + website: "www.pepepunks.com", + isMetadataValid: true }, { uid: "34545656768", @@ -154,7 +156,8 @@ ListModel { ], tokenId: "7123", twitterHandle: "@kitties", - website: "www.kitties.com" + website: "www.kitties.com", + isMetadataValid: true }, { uid: "123456", @@ -202,7 +205,8 @@ ListModel { ], tokenId: "403123", twitterHandle: "", - website: "www.kitties.com" + website: "www.kitties.com", + isMetadataValid: true }, { uid: "12345645459537432", @@ -252,7 +256,8 @@ ListModel { ], tokenId: "1", twitterHandle: "@kitties", - website: "" + website: "", + isMetadataValid: true }, { uid: "pp21", @@ -289,7 +294,8 @@ ListModel { ], tokenId: "12568", twitterHandle: "@pepepunks", - website: "www.pepepunks.com" + website: "www.pepepunks.com", + isMetadataValid: true }, { uid: "lp#666a", @@ -326,8 +332,42 @@ ListModel { ], tokenId: "1445", twitterHandle: "@lonelyPanda", - website: "www.lonelyPanda.com" + website: "www.lonelyPanda.com", + isMetadataValid: true }, + { + uid: "invalid#123", + chainId: 421613, + userHas: 0, + name: "", + collectionUid: "", + collectionName: "", + collectionImageUrl: "", + communityId: "", + communityName: "", + communityImage: "", + imageUrl: "", + isLoading: false, + backgroundColor: "", + permalink:"", + domain:"", + ownership: [ + { + accountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240", + balance: "1", + txTimestamp: 19 + }, + ], + networkShortName: "OPT", + networkColor: "red", + networkIconUrl: ModelsData.networks.optimism, + description: "", + traits: [], + tokenId: "2121", + twitterHandle: "", + website: "", + isMetadataValid: false + } ] readonly property var communityData: [ @@ -356,7 +396,8 @@ ListModel { networkIconUrl: ModelsData.networks.optimism, description: "Frenly Pandas is a community for all the fiendly pandas! Welcome onboard and enjoy :)", traits: [], - tokenId: "4" + tokenId: "4", + isMetadataValid: true }, { uid: "691", @@ -383,7 +424,8 @@ ListModel { networkIconUrl: ModelsData.networks.optimism, description: "Bearz is a community for all the ferocious Bearz! Welcome onboard and enjoy :)", traits: [], - tokenId: "3" + tokenId: "3", + isMetadataValid: true }, { uid: "8876", @@ -410,7 +452,8 @@ ListModel { networkIconUrl: ModelsData.networks.ethereum, description: "Bearz is a community for all the ferocious Bearz! Welcome onboard and enjoy :)", traits: [], - tokenId: "341" + tokenId: "341", + isMetadataValid: true }, { uid: "fp#3195", @@ -437,7 +480,8 @@ ListModel { networkIconUrl: ModelsData.networks.ethereum, description: "Frenly Pandas is a community for all the fiendly pandas! Welcome onboard and enjoy :)", traits: [], - tokenId: "765" + tokenId: "765", + isMetadataValid: true }, { uid: "fp#4297", @@ -464,7 +508,8 @@ ListModel { networkIconUrl: ModelsData.networks.ethereum, description: "Frenly Pandas is a community for all the fiendly pandas! Welcome onboard and enjoy :)", traits: [], - tokenId: "166" + tokenId: "166", + isMetadataValid: true }, { uid: "fp#909", @@ -491,7 +536,8 @@ ListModel { networkIconUrl: ModelsData.networks.optimism, description: "Frenly Pandas is a community for all the fiendly pandas! Welcome onboard and enjoy :)", traits: [], - tokenId: "1111" + tokenId: "1111", + isMetadataValid: true }, { uid: "lb#666", @@ -523,7 +569,8 @@ ListModel { networkIconUrl: ModelsData.networks.optimism, description: "Bearz is a community for all the ferocious Bearz! Welcome onboard and enjoy", traits: [], - tokenId: "6" + tokenId: "6", + isMetadataValid: true }, { uid: "lb#777", @@ -550,7 +597,8 @@ ListModel { networkIconUrl: ModelsData.networks.optimism, description: "Lonely Turtle is a community for all of us to talk and communicate! Welcome onboard and enjoy", traits: [], - tokenId: "7" + tokenId: "7", + isMetadataValid: true }, { uid: "ID-Custom", @@ -575,7 +623,8 @@ ListModel { communityId: "", networkShortName: "ARB", networkColor: "blue", - networkIconUrl: ModelsData.networks.arbitrum + networkIconUrl: ModelsData.networks.arbitrum, + isMetadataValid: true }, { uid: "ID-MissingMetadata", @@ -600,7 +649,8 @@ ListModel { communityId: "", networkShortName: "OPT", networkColor: "red", - networkIconUrl: ModelsData.networks.optimism + networkIconUrl: ModelsData.networks.optimism, + isMetadataValid: true }, { uid: "ID-Community1", @@ -625,7 +675,8 @@ ListModel { communityId: "community-id-1", networkShortName: "OPT", networkColor: "red", - networkIconUrl: ModelsData.networks.optimism + networkIconUrl: ModelsData.networks.optimism, + isMetadataValid: true }, { uid: "ID-Community-Unknown", @@ -650,7 +701,8 @@ ListModel { communityId: "community-id-unknown", networkShortName: "OPT", networkColor: "red", - networkIconUrl: ModelsData.networks.optimism + networkIconUrl: ModelsData.networks.optimism, + isMetadataValid: true } ] diff --git a/ui/app/AppLayouts/Communities/views/MintedTokensView.qml b/ui/app/AppLayouts/Communities/views/MintedTokensView.qml index 65f6649646..98a67d5ede 100644 --- a/ui/app/AppLayouts/Communities/views/MintedTokensView.qml +++ b/ui/app/AppLayouts/Communities/views/MintedTokensView.qml @@ -238,7 +238,7 @@ StatusScrollView { delegate: CollectibleView { height: collectiblesGrid.cellHeight width: collectiblesGrid.cellWidth - title: model.name ? model.name : "..." + title: model.name ?? "" subTitle: deployState === Constants.ContractTransactionStatus.Completed ? d.getRemainingInfo(model.privilegesLevel === Constants.TokenPrivilegesLevel.Owner, model.privilegesLevel === Constants.TokenPrivilegesLevel.TMaster, @@ -251,6 +251,7 @@ StatusScrollView { fallbackImageUrl: model.image ? model.image : "" backgroundColor: "transparent" isLoading: false + isMetadataValid: true navigationIconVisible: false privilegesLevel: model.privilegesLevel ornamentColor: model.color diff --git a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml index e5f7c650a3..6a11df4cc5 100644 --- a/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml +++ b/ui/app/AppLayouts/Wallet/views/CollectiblesView.qml @@ -92,7 +92,7 @@ ColumnLayout { clear() if (d.isLoading) { for (let i = 0; i < 10; i++) { - append({ isLoading: true }) + append({ isLoading: true, name: qsTr("Loading collectible...") }) } } } @@ -497,13 +497,14 @@ ColumnLayout { CollectibleView { width: d.cellWidth height: isCommunityCollectible ? d.communityCellHeight : d.cellHeight - title: model.name ? model.name : "..." + title: model.name ?? "" subTitle: model.collectionName ? model.collectionName : model.collectionUid ? model.collectionUid : "" mediaUrl: model.mediaUrl ?? "" mediaType: model.mediaType ?? "" fallbackImageUrl: model.imageUrl ?? "" backgroundColor: model.backgroundColor ? model.backgroundColor : "transparent" isLoading: !!model.isLoading + isMetadataValid: !!model.isMetadataValid privilegesLevel: model.communityPrivilegesLevel ?? Constants.TokenPrivilegesLevel.Community ornamentColor: model.communityColor ?? "transparent" communityId: model.communityId ?? "" diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml index 5793ccecfe..55f3cc5d13 100644 --- a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml +++ b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml @@ -361,11 +361,11 @@ Item { Component { id: collectibleimageComponent - StatusRoundedMedia { + CollectibleMedia { id: collectibleImage - readonly property bool isEmpty: !mediaUrl.toString() && !fallbackImageUrl.toString() - radius: Style.current.radius - color: isError || isEmpty ? Theme.palette.baseColor5 : collectible.backgroundColor + backgroundColor: collectible.backgroundColor + isCollectibleLoading: root.isCollectibleLoading + isMetadataValid: !collectible.isMetadataValid mediaUrl: collectible.mediaUrl ?? "" mediaType: !!collectible ? (modelIndex > 0 && collectible.mediaType.startsWith("video")) ? "" : collectible.mediaType: "" fallbackImageUrl: collectible.imageUrl @@ -377,29 +377,6 @@ Item { onOpenImageContextMenu: (url, isGif) => Global.openMenu(imageContextMenu, collectibleImage, { imageSource: url, isGif: isGif, isVideo: false }) onOpenVideoContextMenu: (url) => Global.openMenu(imageContextMenu, collectibleImage, { imageSource: url, url: url, isVideo: true, isGif: false }) - Loader { - anchors.fill: parent - active: collectibleImage.isLoading - sourceComponent: LoadingComponent {radius: collectibleImage.radius} - } - - Loader { - anchors.fill: parent - active: collectibleImage.isError || collectibleImage.isEmpty - sourceComponent: LoadingErrorComponent { - radius: collectibleImage.radius - text: { - if (collectibleImage.isError && collectibleImage.componentMediaType === StatusRoundedMedia.MediaType.Unkown) { - return qsTr("Unsupported\nfile format") - } - if (!collectible.description && !collectible.name) { - return qsTr("Info can't\nbe fetched") - } - return qsTr("Failed\nto load") - } - } - } - Component { id: imageContextMenu ImageContextMenu { diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleMedia.qml b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleMedia.qml new file mode 100644 index 0000000000..079dbccdd6 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleMedia.qml @@ -0,0 +1,42 @@ +import QtQuick 2.14 + +import StatusQ.Components 0.1 +import StatusQ.Core.Theme 0.1 + +import utils 1.0 + +StatusRoundedMedia { + id: root + + readonly property bool isEmpty: !mediaUrl.toString() && !fallbackImageUrl.toString() + property color backgroundColor: Theme.palette.baseColor5 + property bool isCollectibleLoading: false + property bool isMetadataValid: false + + radius: Style.current.radius + color: isError || isEmpty ? Theme.palette.baseColor5 : backgroundColor + + Loader { + id: loadingCompLoader + anchors.fill: parent + active: root.isCollectibleLoading || root.isLoading + sourceComponent: LoadingComponent {radius: root.radius} + } + + Loader { + anchors.fill: parent + active: (root.isError || root.isEmpty) && !loadingCompLoader.active + sourceComponent: LoadingErrorComponent { + radius: root.radius + text: { + if (root.isError && root.componentMediaType === StatusRoundedMedia.MediaType.Unkown) { + return qsTr("Unsupported\nfile format") + } + if (!root.isMetadataValid) { + return qsTr("Info can't\nbe fetched") + } + return qsTr("Failed\nto load") + } + } + } +} diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml index 92cdb8a02d..22d540843a 100644 --- a/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml +++ b/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleView.qml @@ -16,6 +16,7 @@ Control { id: root property string title: "" + property string unknownTitle: "..." property string subTitle: "" property alias subTitleColor: subTitleItem.customColor property string backgroundColor: "transparent" @@ -24,6 +25,7 @@ Control { property url fallbackImageUrl : "" property bool isLoading: false property bool navigationIconVisible: false + property bool isMetadataValid: false property string communityId: "" property string communityName property string communityImage @@ -77,7 +79,7 @@ Control { contentItem: ColumnLayout { spacing: 0 - StatusRoundedMedia { + CollectibleMedia { id: image Layout.alignment: Qt.AlignHCenter @@ -85,21 +87,16 @@ Control { Layout.fillWidth: true Layout.preferredHeight: width + backgroundColor: root.isLoading ? "transparent" : root.backgroundColor visible: !specialCollectible.visible - radius: Style.current.radius + isMetadataValid: root.isMetadataValid mediaUrl: root.mediaUrl mediaType: root.mediaType fallbackImageUrl: root.fallbackImageUrl showLoadingIndicator: true - color: root.isLoading ? "transparent" : root.backgroundColor + isCollectibleLoading: root.isLoading fillMode: Image.PreserveAspectCrop - Loader { - anchors.fill: parent - active: root.isLoading - sourceComponent: LoadingComponent {radius: image.radius} - } - Loader { anchors.top: parent.top anchors.left: parent.left @@ -150,7 +147,7 @@ Control { customColor: Theme.palette.directColor1 font.weight: Font.DemiBold elide: Text.ElideRight - text: root.isLoading ? Constants.dummyText : root.title + text: root.isLoading ? Constants.dummyText : root.title || root.unknownTitle loading: root.isLoading } diff --git a/ui/app/AppLayouts/Wallet/views/collectibles/qmldir b/ui/app/AppLayouts/Wallet/views/collectibles/qmldir index 02f16f67b1..5a580a8ba7 100644 --- a/ui/app/AppLayouts/Wallet/views/collectibles/qmldir +++ b/ui/app/AppLayouts/Wallet/views/collectibles/qmldir @@ -1,2 +1,3 @@ CollectibleDetailView 1.0 CollectibleDetailView.qml CollectibleView 1.0 CollectibleView.qml +CollectibleMedia 1.0 CollectibleMedia.qml