status-desktop/ui/app/AppLayouts/Wallet/views/collectibles/CollectibleDetailView.qml

334 lines
14 KiB
QML

import QtQuick 2.14
import QtQuick.Layouts 1.13
import SortFilterProxyModel 0.2
import StatusQ 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import AppLayouts.Communities.panels 1.0
import utils 1.0
import shared.controls 1.0
import shared.views 1.0
import "../../stores"
import "../../controls"
Item {
id: root
signal launchTransactionDetail(string txID)
required property var rootStore
required property var walletRootStore
required property var communitiesStore
required property var collectible
property var activityModel
property bool isCollectibleLoading
required property string addressFilters
// Community related token props:
readonly property bool isCommunityCollectible: !!collectible ? collectible.communityId !== "" : false
readonly property bool isOwnerTokenType: !!collectible ? (collectible.communityPrivilegesLevel === Constants.TokenPrivilegesLevel.Owner) : false
readonly property bool isTMasterTokenType: !!collectible ? (collectible.communityPrivilegesLevel === Constants.TokenPrivilegesLevel.TMaster) : false
readonly property var communityDetails: isCommunityCollectible ? root.communitiesStore.getCommunityDetailsAsJson(collectible.communityId) : null
QtObject {
id: d
readonly property string collectibleLink: !!collectible ? root.walletRootStore.getOpenSeaCollectibleUrl(collectible.networkShortName, collectible.contractAddress, collectible.tokenId): ""
readonly property string collectionLink: !!collectible ? root.walletRootStore.getOpenSeaCollectionUrl(collectible.networkShortName, collectible.contractAddress): ""
readonly property string blockExplorerLink: !!collectible ? root.walletRootStore.getExplorerUrl(collectible.networkShortName, collectible.contractAddress, collectible.tokenId): ""
readonly property var addrFilters: root.addressFilters.split(":").map((addr) => addr.toLowerCase())
readonly property int imageStackSpacing: 4
property Component balanceTag: Component {
CollectibleBalanceTag {
balance: d.balanceAggregator.value
}
}
property SortFilterProxyModel filteredBalances: SortFilterProxyModel {
sourceModel: !!collectible ? collectible.ownership : null
filters: [
FastExpressionFilter {
expression: {
d.addrFilters
return d.addrFilters.includes(model.accountAddress.toLowerCase())
}
expectedRoles: ["accountAddress"]
}
]
}
property SumAggregator balanceAggregator: SumAggregator {
model: d.filteredBalances
roleName: "balance"
}
}
CollectibleDetailsHeader {
id: collectibleHeader
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
collectibleName: !!collectible && !!collectible.name ? collectible.name : qsTr("Unknown")
collectibleId: !!collectible ? "#" + collectible.tokenId : ""
communityName: !!communityDetails ? communityDetails.name : ""
communityId: !!collectible ? collectible.communityId : ""
collectionName: !!collectible ? collectible.collectionName : ""
communityImage: !!communityDetails ? communityDetails.image : ""
networkShortName: !!collectible ? collectible.networkShortName : ""
networkColor: !!collectible ?collectible.networkColor : ""
networkIconURL: !!collectible ? collectible.networkIconUrl : ""
networkExplorerName: !!collectible ? root.walletRootStore.getExplorerNameForNetwork(collectible.networkShortName): ""
collectibleLinkEnabled: Utils.getUrlStatus(d.collectibleLink)
collectionLinkEnabled: (!!communityDetails && communityDetails.name) || Utils.getUrlStatus(d.collectionLink)
explorerLinkEnabled: Utils.getUrlStatus(d.blockExplorerLink)
onCollectionTagClicked: {
if (root.isCommunityCollectible) {
Global.switchToCommunity(collectible.communityId)
}
else {
Global.openLinkWithConfirmation(d.collectionLink, root.walletRootStore.getOpenseaDomainName())
}
}
onOpenCollectibleExternally: Global.openLinkWithConfirmation(d.collectibleLink, root.walletRootStore.getOpenseaDomainName())
onOpenCollectibleOnExplorer: Global.openLinkWithConfirmation(d.blockExplorerLink, root.walletRootStore.getExplorerDomain(networkShortName))
}
Column {
id: collectibleBody
anchors.top: collectibleHeader.bottom
anchors.topMargin: 25
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
spacing: Style.current.padding
Row {
id: collectibleImageDetails
readonly property real visibleImageHeight: artwork.height
readonly property real visibleImageWidth: artwork.width
height: collectibleImageDetails.visibleImageHeight
width: parent.width
spacing: 24
ColumnLayout {
id: artwork
spacing: 0
Repeater {
id: repeater
model: Math.min(3, d.balanceAggregator.value)
Item {
Layout.preferredWidth: childrenRect.width
Layout.preferredHeight: childrenRect.height
Layout.leftMargin: index * d.imageStackSpacing
Layout.topMargin: index === 0 ? 0 : -Layout.preferredHeight + d.imageStackSpacing
opacity: index === 0 ? 1: 0.4/index
// so that the first item remains on top in the stack
z: -index
Loader {
property int modelIndex: index
anchors.top: parent.top
anchors.left: parent.left
sourceComponent: root.isCommunityCollectible && (root.isOwnerTokenType || root.isTMasterTokenType) ? privilegedCollectibleImage: collectibleimage
active: root.visible
}
Loader {
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: Style.current.padding
sourceComponent: d.balanceTag
// only show balance tag on top of the first image in stack
active: index === 0 && d.balanceAggregator.value > 1 && root.visible
}
}
}
}
Column {
id: collectibleNameAndDescription
spacing: 12
width: parent.width - collectibleImageDetails.visibleImageWidth - Style.current.bigPadding
StatusBaseText {
id: collectibleName
width: parent.width
height: 24
text: root.isCommunityCollectible && !!communityDetails ? qsTr("Minted by %1").arg(root.communityDetails.name):
!!collectible ? collectible.collectionName: ""
color: Theme.palette.directColor1
font.pixelSize: 17
lineHeight: 24
lineHeightMode: Text.FixedHeight
elide: Text.ElideRight
wrapMode: Text.WordWrap
}
StatusBaseText {
id: descriptionText
width: parent.width
height: collectibleImageDetails.height - collectibleName.height - parent.spacing
clip: true
text: !!collectible ? collectible.description : ""
textFormat: Text.MarkdownText
color: Theme.palette.directColor4
font.pixelSize: 15
lineHeight: 22
lineHeightMode: Text.FixedHeight
elide: Text.ElideRight
wrapMode: Text.Wrap
}
}
}
StatusTabBar {
id: collectiblesDetailsTab
width: parent.width
topPadding: Style.current.xlPadding
visible: !!collectible && collectible.traits.count > 0
StatusTabButton {
leftPadding: 0
width: implicitWidth
text: qsTr("Properties")
}
StatusTabButton {
rightPadding: 0
width: implicitWidth
text: qsTr("Activity")
}
}
StatusScrollView {
id: scrollView
width: parent.width
height: parent.height
contentWidth: availableWidth
Loader {
id: tabLoader
width: parent.width
height: parent.height
sourceComponent: {
switch (collectiblesDetailsTab.currentIndex) {
case 0: return traitsView
case 1: return activityView
}
}
Component {
id: traitsView
Flow {
width: scrollView.availableWidth
spacing: 10
Repeater {
model: !!collectible ? collectible.traits: null
InformationTile {
maxWidth: parent.width
primaryText: model.traitType
secondaryText: model.value
}
}
}
}
Component {
id: activityView
StatusListView {
width: scrollView.availableWidth
height: scrollView.availableHeight
model: root.activityModel
delegate: TransactionDelegate {
required property var model
required property int index
width: parent.width
modelData: model.activityEntry
timeStampText: isModelDataValid ? LocaleUtils.formatRelativeTimestamp(modelData.timestamp * 1000, true) : ""
rootStore: root.rootStore
walletRootStore: root.walletRootStore
showAllAccounts: root.walletRootStore.showAllAccounts
displayValues: true
community: isModelDataValid && !!communityId && !!root.communitiesStore ? root.communitiesStore.getCommunityDetailsAsJson(communityId) : null
loading: false
onClicked: {
if (mouse.button === Qt.RightButton) {
// TODO: Implement context menu
} else {
root.launchTransactionDetail(modelData.id)
}
}
}
}
}
}
}
}
Component {
id: privilegedCollectibleImage
// Special artwork representation for community `Owner and Master Token` token types:
PrivilegedTokenArtworkPanel {
size: PrivilegedTokenArtworkPanel.Size.Large
artwork: collectible.imageUrl ?? ""
color: !!root.collectible && !!root.communityDetails ? root.communityDetails.color : "transparent"
isOwner: root.isOwnerTokenType
}
}
Component {
id: collectibleimage
StatusRoundedMedia {
id: collectibleImage
readonly property bool isEmpty: !mediaUrl.toString() && !fallbackImageUrl.toString()
width: 248
height: width
radius: Style.current.radius
color: isError || isEmpty ? Theme.palette.baseColor5 : collectible.backgroundColor
border.color: Theme.palette.directColor8
border.width: 1
mediaUrl: collectible.mediaUrl ?? ""
mediaType: modelIndex === 0 && !!collectible ? collectible.mediaType : ""
fallbackImageUrl: collectible.imageUrl
Column {
anchors.centerIn: parent
visible: collectibleImage.isError || collectibleImage.isEmpty
spacing: 10
StatusIcon {
anchors.horizontalCenter: parent.horizontalCenter
icon: "frowny"
opacity: 0.1
color: Theme.palette.directColor1
}
StatusBaseText {
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Qt.AlignHCenter
color: Theme.palette.directColor6
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")
}
}
}
}
}
}