feat(@desktop/wallet): Link out from collectible details view to opensea

fixes #13918
This commit is contained in:
Khushboo Mehta 2024-03-26 16:00:16 +01:00 committed by Khushboo-dev-cpp
parent 1851838e64
commit f45a39bfcf
9 changed files with 141 additions and 49 deletions

View File

@ -1,4 +1,4 @@
import NimQml, strutils, uri, stew/shims/strformat, strutils, stint, re import NimQml, strutils, uri, stew/shims/strformat, strutils, stint, re, httpclient
import stew/byteutils import stew/byteutils
import ./utils/qrcodegen import ./utils/qrcodegen
import ./utils/time_utils import ./utils/time_utils
@ -9,6 +9,8 @@ import ../../app_service/service/visual_identity/service as procs_from_visual_id
include ../../app_service/service/accounts/utils include ../../app_service/service/accounts/utils
const URL_STATUS_OK* = "200 OK"
QtObject: QtObject:
type Utils* = ref object of QObject type Utils* = ref object of QObject
@ -181,4 +183,12 @@ QtObject:
return value return value
proc addTimestampToURL*(self: Utils, url: string): string {.slot.} = proc addTimestampToURL*(self: Utils, url: string): string {.slot.} =
return time_utils.addTimestampToURL(url) return time_utils.addTimestampToURL(url)
proc isValidURL*(self: Utils, url: string): bool {.slot.} =
var client = newHttpClient()
defer: client.close()
try:
return client.head(url).status == URL_STATUS_OK
except:
return false

View File

@ -1,5 +1,5 @@
import import
json, tables, sequtils, httpclient, net json, tables, sequtils, net
import json, strutils, stew/shims/strformat, tables, chronicles, unicode, times import json, strutils, stew/shims/strformat, tables, chronicles, unicode, times
import import
json_serialization, chronicles, libp2p/[multihash, multicodec, cid], stint, nimcrypto json_serialization, chronicles, libp2p/[multihash, multicodec, cid], stint, nimcrypto

View File

@ -1,7 +1,5 @@
import NimQml, Tables, json, sequtils, chronicles, strutils, sets, stint import NimQml, Tables, json, sequtils, chronicles, strutils, sets, stint
import httpclient
import app/core/[main] import app/core/[main]
import app/core/tasks/[qt, threadpool] import app/core/tasks/[qt, threadpool]

View File

@ -1,4 +1,4 @@
import NimQml, Tables, json, sequtils, sugar, chronicles, stew/shims/strformat, stint, httpclient import NimQml, Tables, json, sequtils, sugar, chronicles, stew/shims/strformat, stint
import net, strutils, os, times, algorithm, options import net, strutils, os, times, algorithm, options
import web3/ethtypes import web3/ethtypes

View File

@ -24,6 +24,10 @@ ColumnLayout {
property string networkIconURL property string networkIconURL
property string networkExplorerName property string networkExplorerName
property bool collectibleLinkEnabled
property bool collectionLinkEnabled
property bool explorerLinkEnabled
signal collectionTagClicked() signal collectionTagClicked()
signal openCollectibleExternally() signal openCollectibleExternally()
signal openCollectibleOnExplorer() signal openCollectibleOnExplorer()
@ -58,15 +62,15 @@ ColumnLayout {
size: StatusBaseButton.Size.Small size: StatusBaseButton.Size.Small
text: root.networkExplorerName text: root.networkExplorerName
icon.name: "external" icon.name: "external"
asset.emoji: d.effectiveEmoji
onClicked: root.openCollectibleOnExplorer() onClicked: root.openCollectibleOnExplorer()
visible: root.explorerLinkEnabled
} }
StatusButton { StatusButton {
size: StatusBaseButton.Size.Small size: StatusBaseButton.Size.Small
text: "OpenSea" text: "OpenSea"
icon.name: "external" icon.name: "external"
asset.emoji: d.effectiveEmoji
onClicked: root.openCollectibleExternally() onClicked: root.openCollectibleExternally()
visible: root.collectibleLinkEnabled
} }
} }
} }
@ -78,11 +82,13 @@ ColumnLayout {
id: collectionTag id: collectionTag
asset.name: !!root.communityImage ? root.communityImage: !sensor.containsMouse ? root.isCollection ? "tiny/folder" : "tiny/profile" : "tiny/external" asset.name: !!root.communityImage ? root.communityImage: !sensor.containsMouse ? root.isCollection ? "tiny/folder" : "tiny/profile" : "tiny/external"
asset.isImage: !!root.communityImage asset.isImage: !!root.communityImage
enabled: root.collectionLinkEnabled
MouseArea { MouseArea {
id: sensor id: sensor
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: root.collectionLinkEnabled
cursorShape: Qt.PointingHandCursor cursorShape: root.collectionLinkEnabled ? Qt.PointingHandCursor: undefined
enabled: root.collectionLinkEnabled
onClicked: { onClicked: {
root.collectionTagClicked() root.collectionTagClicked()
} }

View File

@ -493,7 +493,7 @@ QtObject {
return root.mainModuleInst.addressWasShown(address) return root.mainModuleInst.addressWasShown(address)
} }
function getExplorerUrl(networkShortName, contractAddress, tokenId) { function getExplorerDomain(networkShortName) {
let link = Constants.networkExplorerLinks.etherscan let link = Constants.networkExplorerLinks.etherscan
if (networkShortName === Constants.networkShortChainNames.mainnet) { if (networkShortName === Constants.networkShortChainNames.mainnet) {
if (root.areTestNetworksEnabled) { if (root.areTestNetworksEnabled) {
@ -503,7 +503,6 @@ QtObject {
link = Constants.networkExplorerLinks.goerliEtherscan link = Constants.networkExplorerLinks.goerliEtherscan
} }
} }
return "%1/nft/%2/%3".arg(link).arg(contractAddress).arg(tokenId)
} }
if (networkShortName === Constants.networkShortChainNames.arbitrum) { if (networkShortName === Constants.networkShortChainNames.arbitrum) {
link = Constants.networkExplorerLinks.arbiscan link = Constants.networkExplorerLinks.arbiscan
@ -514,7 +513,6 @@ QtObject {
link = Constants.networkExplorerLinks.goerliArbiscan link = Constants.networkExplorerLinks.goerliArbiscan
} }
} }
return "%1/token/%2?a=%3".arg(link).arg(contractAddress).arg(tokenId)
} else if (networkShortName === Constants.networkShortChainNames.optimism) { } else if (networkShortName === Constants.networkShortChainNames.optimism) {
link = Constants.networkExplorerLinks.optimism link = Constants.networkExplorerLinks.optimism
if (root.areTestNetworksEnabled) { if (root.areTestNetworksEnabled) {
@ -524,6 +522,16 @@ QtObject {
link = Constants.networkExplorerLinks.goerliOptimism link = Constants.networkExplorerLinks.goerliOptimism
} }
} }
}
return link
}
function getExplorerUrl(networkShortName, contractAddress, tokenId) {
let link = getExplorerDomain(networkShortName)
if (networkShortName === Constants.networkShortChainNames.mainnet) {
return "%1/nft/%2/%3".arg(link).arg(contractAddress).arg(tokenId)
}
else {
return "%1/token/%2?a=%3".arg(link).arg(contractAddress).arg(tokenId) return "%1/token/%2?a=%3".arg(link).arg(contractAddress).arg(tokenId)
} }
} }
@ -537,4 +545,53 @@ QtObject {
} }
return qsTr("Etherscan Explorer") return qsTr("Etherscan Explorer")
} }
function getOpenSeaNetworkName(networkShortName) {
let networkName = Constants.openseaExplorerLinks.ethereum
if (networkShortName === Constants.networkShortChainNames.mainnet) {
if (root.areTestNetworksEnabled) {
if (!root.isGoerliEnabled) {
networkName = Constants.openseaExplorerLinks.sepoliaEthereum
} else {
networkName = Constants.openseaExplorerLinks.goerliEthereum
}
}
}
if (networkShortName === Constants.networkShortChainNames.arbitrum) {
networkName = Constants.openseaExplorerLinks.arbitrum
if (root.areTestNetworksEnabled) {
if (!root.isGoerliEnabled) {
networkName = Constants.openseaExplorerLinks.sepoliaArbitrum
} else {
networkName = Constants.openseaExplorerLinks.goerliArbitrum
}
}
} else if (networkShortName === Constants.networkShortChainNames.optimism) {
networkName = Constants.openseaExplorerLinks.optimism
if (root.areTestNetworksEnabled) {
if (!root.isGoerliEnabled) {
networkName = Constants.openseaExplorerLinks.sepoliaOptimism
} else {
networkName = Constants.openseaExplorerLinks.goerliOptimism
}
}
}
return networkName
}
function getOpenseaDomainName() {
return root.areTestNetworksEnabled ? Constants.openseaExplorerLinks.testnetLink : Constants.openseaExplorerLinks.mainnetLink
}
function getOpenSeaCollectionUrl(networkShortName, contractAddress) {
let networkName = getOpenSeaNetworkName(networkShortName)
let baseLink = root.areTestNetworksEnabled ? Constants.openseaExplorerLinks.testnetLink : Constants.openseaExplorerLinks.mainnetLink
return "%1/assets/%2/%3".arg(baseLink).arg(networkName).arg(contractAddress)
}
function getOpenSeaCollectibleUrl(networkShortName, contractAddress, tokenId) {
let networkName = getOpenSeaNetworkName(networkShortName)
let baseLink = root.areTestNetworksEnabled ? Constants.openseaExplorerLinks.testnetLink : Constants.openseaExplorerLinks.mainnetLink
return "%1/assets/%2/%3/%4".arg(baseLink).arg(networkName).arg(contractAddress).arg(tokenId)
}
} }

View File

@ -36,6 +36,13 @@ Item {
readonly property var communityDetails: isCommunityCollectible ? root.communitiesStore.getCommunityDetailsAsJson(collectible.communityId) : null readonly property var communityDetails: isCommunityCollectible ? root.communitiesStore.getCommunityDetailsAsJson(collectible.communityId) : null
QtObject {
id: d
readonly property string collectibleLink: root.walletRootStore.getOpenSeaCollectibleUrl(collectible.networkShortName, collectible.contractAddress, collectible.tokenId)
readonly property string collectionLink: root.walletRootStore.getOpenSeaCollectionUrl(collectible.networkShortName, collectible.contractAddress)
readonly property string blockExplorerLink: root.walletRootStore.getExplorerUrl(collectible.networkShortName, collectible.contractAddress, collectible.tokenId)
}
CollectibleDetailsHeader { CollectibleDetailsHeader {
id: collectibleHeader id: collectibleHeader
anchors.top: parent.top anchors.top: parent.top
@ -50,22 +57,22 @@ Item {
networkColor: collectible.networkColor networkColor: collectible.networkColor
networkIconURL: collectible.networkIconUrl networkIconURL: collectible.networkIconUrl
networkExplorerName: root.walletRootStore.getExplorerNameForNetwork(collectible.networkShortName) networkExplorerName: root.walletRootStore.getExplorerNameForNetwork(collectible.networkShortName)
collectibleLinkEnabled: Utils.getUrlStatus(d.collectibleLink)
collectionLinkEnabled: (!!communityDetails && communityDetails.name) || Utils.getUrlStatus(d.collectionLink)
explorerLinkEnabled: Utils.getUrlStatus(d.blockExplorerLink)
onCollectionTagClicked: { onCollectionTagClicked: {
if (root.isCommunityCollectible) { if (root.isCommunityCollectible) {
Global.switchToCommunity(collectible.communityId) Global.switchToCommunity(collectible.communityId)
} }
/* TODO for non community token link out to collection on opensea else {
https://github.com/status-im/status-desktop/issues/13918 */ Global.openLinkWithConfirmation(d.collectionLink, root.walletRootStore.getOpenseaDomainName())
}
} }
onOpenCollectibleExternally: { onOpenCollectibleExternally: Global.openLinkWithConfirmation(d.collectibleLink, root.walletRootStore.getOpenseaDomainName())
/* TODO add link out to opensea onOpenCollectibleOnExplorer: Global.openLinkWithConfirmation(d.blockExplorerLink, root.walletRootStore.getExplorerDomain(networkShortName))
https://github.com/status-im/status-desktop/issues/13918 */
}
onOpenCollectibleOnExplorer: Global.openLink(root.walletRootStore.getExplorerUrl(collectible.networkShortName, collectible.contractAddress, collectible.tokenId))
} }
ColumnLayout { Column {
id: collectibleBody id: collectibleBody
anchors.top: collectibleHeader.bottom anchors.top: collectibleHeader.bottom
anchors.topMargin: 25 anchors.topMargin: 25
@ -82,8 +89,8 @@ Item {
readonly property real visibleImageHeight: (collectibleimage.visible ? collectibleimage.height : privilegedCollectibleImage.height) readonly property real visibleImageHeight: (collectibleimage.visible ? collectibleimage.height : privilegedCollectibleImage.height)
readonly property real visibleImageWidth: (collectibleimage.visible ? collectibleimage.width : privilegedCollectibleImage.width) readonly property real visibleImageWidth: (collectibleimage.visible ? collectibleimage.width : privilegedCollectibleImage.width)
Layout.preferredHeight: collectibleImageDetails.visibleImageHeight height: collectibleImageDetails.visibleImageHeight
Layout.preferredWidth: parent.width width: parent.width
spacing: 24 spacing: 24
// Special artwork representation for community `Owner and Master Token` token types: // Special artwork representation for community `Owner and Master Token` token types:
@ -132,36 +139,28 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
StatusScrollView { StatusBaseText {
id: descriptionScrollView id: descriptionText
width: parent.width width: parent.width
height: collectibleImageDetails.height - collectibleName.height - parent.spacing height: collectibleImageDetails.height - collectibleName.height - parent.spacing
contentWidth: availableWidth clip: true
text: collectible.description
padding: 0 textFormat: Text.MarkdownText
color: Theme.palette.directColor4
StatusBaseText { font.pixelSize: 15
id: descriptionText lineHeight: 22
width: descriptionScrollView.availableWidth lineHeightMode: Text.FixedHeight
elide: Text.ElideRight
text: collectible.description wrapMode: Text.Wrap
textFormat: Text.MarkdownText
color: Theme.palette.directColor4
font.pixelSize: 15
lineHeight: 22
lineHeightMode: Text.FixedHeight
elide: Text.ElideRight
wrapMode: Text.Wrap
}
} }
} }
} }
StatusTabBar { StatusTabBar {
id: collectiblesDetailsTab id: collectiblesDetailsTab
Layout.fillWidth: true width: parent.width
Layout.topMargin: Style.current.xlPadding topPadding: Style.current.xlPadding
visible: collectible.traits.count > 0 visible: collectible.traits.count > 0
StatusTabButton { StatusTabButton {
@ -178,14 +177,15 @@ Item {
StatusScrollView { StatusScrollView {
id: scrollView id: scrollView
Layout.fillWidth: true width: parent.width
Layout.fillHeight: true height: parent.height
contentWidth: availableWidth contentWidth: availableWidth
Loader { Loader {
id: tabLoader id: tabLoader
Layout.fillWidth: true width: parent.width
Layout.fillHeight: true height: parent.height
sourceComponent: { sourceComponent: {
switch (collectiblesDetailsTab.currentIndex) { switch (collectiblesDetailsTab.currentIndex) {
case 0: return traitsView case 0: return traitsView

View File

@ -916,6 +916,23 @@ QtObject {
readonly property string txPath: "tx" readonly property string txPath: "tx"
} }
readonly property QtObject openseaExplorerLinks: QtObject {
readonly property string mainnetLink: "https://opensea.io"
readonly property string testnetLink: "https://testnets.opensea.io"
readonly property string ethereum: "ethereum"
readonly property string arbitrum: "arbitrum"
readonly property string optimism: "optimism"
readonly property string goerliEthereum: "goerli"
readonly property string goerliArbitrum: "arbitrum-goerli"
readonly property string goerliOptimism: "optimism-goerli"
readonly property string sepoliaEthereum: "sepolia"
readonly property string sepoliaArbitrum: "arbitrum-sepolia"
readonly property string sepoliaOptimism: "optimism-sepolia"
}
readonly property string api_request: "api-request" readonly property string api_request: "api-request"
readonly property string web3SendAsyncReadOnly: "web3-send-async-read-only" readonly property string web3SendAsyncReadOnly: "web3-send-async-read-only"
readonly property string web3DisconnectAccount: "web3-disconnect-account" readonly property string web3DisconnectAccount: "web3-disconnect-account"

View File

@ -991,4 +991,8 @@ QtObject {
return communitiesModuleInst.isDisplayNameDupeOfCommunityMember(displayName) return communitiesModuleInst.isDisplayNameDupeOfCommunityMember(displayName)
} }
function getUrlStatus(url) {
return globalUtilsInst.isValidURL(url)
}
} }