import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import Qt.labs.settings 1.1
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Models 0.1
import StatusQ.Internal 0.1
import StatusQ.Popups 0.1
import StatusQ.Popups.Dialog 0.1
import shared.controls 1.0
import shared.panels 1.0
import shared.popups 1.0
import utils 1.0
import AppLayouts.Wallet.views.collectibles 1.0
import AppLayouts.Wallet.controls 1.0
import SortFilterProxyModel 0.2
ColumnLayout {
id: root
required property var collectiblesModel
property bool sendEnabled: true
property bool filterVisible
signal collectibleClicked(int chainId, string contractAddress, string tokenId, string uid)
signal sendRequested(string symbol)
signal receiveRequested(string symbol)
signal switchToCommunityRequested(string communityId)
signal manageTokensRequested()
spacing: 0
QtObject {
id: d
readonly property int cellHeight: 225
readonly property int communityCellHeight: 242
readonly property int cellWidth: 176
readonly property bool isCustomView: cmbTokenOrder.currentValue === SortOrderComboBox.TokenOrderCustom
readonly property var renamedModel: RolesRenamingModel {
sourceModel: root.collectiblesModel
mapping: [
RoleRename {
from: "uid"
to: "symbol"
}
]
}
readonly property bool hasCollectibles: regularCollectiblesView.count
readonly property bool hasCommunityCollectibles: communityCollectiblesView.count
readonly property var controller: ManageTokensController {
settingsKey: "WalletCollectibles"
sourceModel: d.renamedModel
}
function hideAllCommunityTokens(communityId) {
const tokenSymbols = ModelUtils.getAll(communityCollectiblesView.model, "symbol", "communityId", communityId)
d.controller.settingsHideCommunityTokens(communityId, tokenSymbols)
}
}
component CustomSFPM: SortFilterProxyModel {
id: customFilter
property bool isCommunity
sourceModel: d.renamedModel
proxyRoles: JoinRole {
name: "groupName"
roleNames: ["collectionName", "communityName"]
}
filters: [
ExpressionFilter {
expression: {
d.controller.settingsDirty
return d.controller.filterAcceptsSymbol(model.symbol) && (customFilter.isCommunity ? !!model.communityId : !model.communityId)
}
},
ExpressionFilter {
enabled: customFilter.isCommunity && cmbFilter.hasEnabledFilters
expression: cmbFilter.selectedFilterGroupIds.includes(model.communityId) ||
(!model.communityId && cmbFilter.selectedFilterGroupIds.includes(""))
},
ExpressionFilter {
enabled: !customFilter.isCommunity && cmbFilter.hasEnabledFilters
expression: cmbFilter.selectedFilterGroupIds.includes(model.collectionUid) ||
(!model.collectionUid && cmbFilter.selectedFilterGroupIds.includes(""))
}
]
sorters: [
ExpressionSorter {
expression: {
d.controller.settingsDirty
return d.controller.lessThan(modelLeft.symbol, modelRight.symbol)
}
enabled: d.isCustomView
},
RoleSorter {
roleName: cmbTokenOrder.currentSortRoleName
sortOrder: cmbTokenOrder.currentSortOrder
enabled: !d.isCustomView
}
]
}
Settings {
category: "CollectiblesViewSortSettings"
property alias currentSortField: cmbTokenOrder.currentIndex
property alias currentSortOrder: cmbTokenOrder.currentSortOrder
property alias selectedFilterGroupIds: cmbFilter.selectedFilterGroupIds
}
ColumnLayout {
Layout.fillWidth: true
Layout.preferredHeight: root.filterVisible ? implicitHeight : 0
spacing: 20
opacity: root.filterVisible ? 1 : 0
Behavior on Layout.preferredHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
StatusDialogDivider {
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
spacing: Style.current.halfPadding
FilterComboBox {
id: cmbFilter
regularTokensModel: d.controller.regularTokensModel
regularTokenGroupsModel: d.controller.regularTokenGroupsModel
communityTokenGroupsModel: d.controller.communityTokenGroupsModel
hasCommunityGroups: d.hasCommunityCollectibles
}
Rectangle {
Layout.preferredHeight: 34
Layout.preferredWidth: 1
Layout.leftMargin: 12
Layout.rightMargin: 12
color: Theme.palette.baseColor2
}
StatusBaseText {
color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
text: qsTr("Sort by:")
}
SortOrderComboBox {
id: cmbTokenOrder
hasCustomOrderDefined: d.controller.hasSettings
model: [
{ value: SortOrderComboBox.TokenOrderDateAdded, text: qsTr("Date added"), icon: "calendar", sortRoleName: "dateAdded" }, // FIXME sortRoleName #12942
{ value: SortOrderComboBox.TokenOrderAlpha, text: qsTr("Collectible name"), icon: "bold", sortRoleName: "name" },
{ value: SortOrderComboBox.TokenOrderGroupName, text: qsTr("Collection/community name"), icon: "group", sortRoleName: "groupName" }, // Custom SFPM role communityName || collectionName
{ value: SortOrderComboBox.TokenOrderCustom, text: qsTr("Custom order"), icon: "exchange", sortRoleName: "" },
{ value: SortOrderComboBox.TokenOrderNone, text: "---", icon: "", sortRoleName: "" }, // separator
{ value: SortOrderComboBox.TokenOrderCreateCustom, text: hasCustomOrderDefined ? qsTr("Edit custom order →") : qsTr("Create custom order →"),
icon: "", sortRoleName: "" }
]
onCreateOrEditRequested: {
root.manageTokensRequested()
}
}
Item { Layout.fillWidth: true }
StatusLinkText {
visible: cmbFilter.hasEnabledFilters
normalColor: Theme.palette.primaryColor1
text: qsTr("Clear filter")
onClicked: cmbFilter.clearFilter()
}
}
StatusDialogDivider {
Layout.fillWidth: true
}
}
ShapeRectangle {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
visible: !d.hasCollectibles && !d.hasCommunityCollectibles
text: qsTr("Collectibles will appear here")
}
StatusScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Style.current.padding
leftPadding: 0
verticalPadding: 0
contentWidth: availableWidth
ColumnLayout {
width: parent.width
spacing: 0
CustomGridView {
id: regularCollectiblesView
cellHeight: d.cellHeight
model: CustomSFPM {}
}
StatusDialogDivider {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
Layout.bottomMargin: Style.current.halfPadding
visible: d.hasCollectibles && d.hasCommunityCollectibles
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.smallPadding
Layout.bottomMargin: 4
visible: d.hasCommunityCollectibles
StatusBaseText {
text: qsTr("Community collectibles")
color: Theme.palette.baseColor1
}
Item { Layout.fillWidth: true }
StatusFlatButton {
Layout.preferredWidth: 32
Layout.preferredHeight: 32
icon.name: "info"
textColor: Theme.palette.baseColor1
horizontalPadding: 0
verticalPadding: 0
onClicked: Global.openPopup(communityInfoPopupCmp)
}
}
CustomGridView {
id: communityCollectiblesView
cellHeight: d.communityCellHeight
model: CustomSFPM { isCommunity: true }
}
}
}
component CustomGridView: StatusGridView {
id: gridView
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
cellWidth: d.cellWidth
delegate: collectibleDelegate
// For some reason fetchMore is not working properly.
// Adding some logic here as a workaround.
visibleArea.onYPositionChanged: checkLoadMore()
visibleArea.onHeightRatioChanged: checkLoadMore()
Connections {
target: gridView
function onVisibleChanged() {
gridView.checkLoadMore()
}
}
Connections {
target: root.collectiblesModel
function onHasMoreChanged() {
gridView.checkLoadMore()
}
function onIsFetchingChanged() {
gridView.checkLoadMore()
}
}
function checkLoadMore() {
// If there is no more items to load or we're already fetching, return
if (!gridView.visible || !root.collectiblesModel.hasMore || root.collectiblesModel.isFetching)
return
// Only trigger if close to the bottom of the list
if (visibleArea.yPosition + visibleArea.heightRatio > 0.9)
root.collectiblesModel.loadMore()
}
}
Component {
id: collectibleDelegate
CollectibleView {
width: d.cellWidth
height: isCommunityCollectible ? d.communityCellHeight : d.cellHeight
title: model.name ? 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
privilegesLevel: model.communityPrivilegesLevel ?? Constants.TokenPrivilegesLevel.Community
ornamentColor: model.communityColor ?? "transparent"
communityId: model.communityId ?? ""
communityName: model.communityName ?? ""
communityImage: model.communityImage ?? ""
onClicked: root.collectibleClicked(model.chainId, model.contractAddress, model.tokenId, model.symbol)
onRightClicked: {
Global.openMenu(tokenContextMenu, this,
{symbol: model.symbol, tokenName: model.name, tokenImage: model.imageUrl,
communityId: model.communityId, communityName: model.communityName, communityImage: model.communityImage})
}
onSwitchToCommunityRequested: (communityId) => root.switchToCommunityRequested(communityId)
}
}
Component {
id: tokenContextMenu
StatusMenu {
onClosed: destroy()
property string symbol
property string tokenName
property string tokenImage
property string communityId
property string communityName
property string communityImage
StatusAction {
enabled: root.sendEnabled
icon.name: "send"
text: qsTr("Send")
onTriggered: root.sendRequested(symbol)
}
StatusAction {
icon.name: "receive"
text: qsTr("Receive")
onTriggered: root.receiveRequested(symbol)
}
StatusMenuSeparator {}
StatusAction {
icon.name: "settings"
text: qsTr("Manage tokens")
onTriggered: root.manageTokensRequested()
}
StatusAction {
enabled: symbol !== "ETH"
type: StatusAction.Type.Danger
icon.name: "hide"
text: qsTr("Hide collectible")
onTriggered: Global.openPopup(confirmHideCollectiblePopup, {symbol, tokenName, tokenImage, communityId})
}
StatusAction {
enabled: !!communityId
type: StatusAction.Type.Danger
icon.name: "hide"
text: qsTr("Hide all collectibles from this community")
onTriggered: Global.openPopup(confirmHideCommunityCollectiblesPopup, {communityId, communityName, communityImage})
}
}
}
Component {
id: communityInfoPopupCmp
StatusDialog {
destroyOnClose: true
title: qsTr("What are community collectibles?")
standardButtons: Dialog.Ok
width: 520
contentItem: StatusBaseText {
wrapMode: Text.Wrap
text: qsTr("Community collectibles are collectibles that have been minted by a community. As these collectibles cannot be verified, always double check their origin and validity before interacting with them. If in doubt, ask a trusted member or admin of the relevant community.")
}
}
}
Component {
id: confirmHideCollectiblePopup
ConfirmationDialog {
property string symbol
property string tokenName
property string tokenImage
property string communityId
readonly property string formattedName: tokenName + (communityId ? " (" + qsTr("community collectible") + ")" : "")
width: 520
destroyOnClose: true
confirmButtonLabel: qsTr("Hide %1").arg(tokenName)
cancelBtnType: ""
showCancelButton: true
headerSettings.title: qsTr("Hide %1").arg(formattedName)
headerSettings.asset.name: tokenImage
confirmationText: qsTr("Are you sure you want to hide %1? You will no longer see or be able to interact with this collectible anywhere inside Status.").arg(formattedName)
onCancelButtonClicked: close()
onConfirmButtonClicked: {
d.controller.settingsHideToken(symbol)
close()
Global.displayToastMessage(
qsTr("%1 was successfully hidden. You can toggle collectible visibility via %2.").arg(formattedName)
.arg(`` + qsTr("Settings", "Go to Settings") + ""),
"",
"checkmark-circle",
false,
Constants.ephemeralNotificationType.success,
""
)
}
}
}
Component {
id: confirmHideCommunityCollectiblesPopup
ConfirmationDialog {
property string communityId
property string communityName
property string communityImage
width: 520
destroyOnClose: true
confirmButtonLabel: qsTr("Hide all collectibles minted by this community")
cancelBtnType: ""
showCancelButton: true
headerSettings.title: qsTr("Hide %1 community collectibles").arg(communityName)
headerSettings.asset.name: communityImage
confirmationText: qsTr("Are you sure you want to hide all community collectibles minted by %1? You will no longer see or be able to interact with these collectibles anywhere inside Status.").arg(communityName)
onCancelButtonClicked: close()
onConfirmButtonClicked: {
d.hideAllCommunityTokens(communityId)
close()
Global.displayToastMessage(
qsTr("%1 community collectibles were successfully hidden. You can toggle collectible visibility via %2.").arg(communityName)
.arg(`` + qsTr("Settings", "Go to Settings") + ""),
"",
"checkmark-circle",
false,
Constants.ephemeralNotificationType.success,
""
)
}
}
}
}