2023-11-29 15:48:33 +00:00
import QtQuick 2.15
import QtQuick . Layouts 1.15
import QtQuick . Controls 2.15
2023-12-06 10:54:36 +00:00
import Qt . labs . settings 1.1
2024-01-24 12:36:23 +00:00
import QtQml 2.15
2021-09-28 15:04:06 +00:00
2023-11-29 15:48:33 +00:00
import StatusQ 0.1
2022-07-13 12:29:38 +00:00
import StatusQ . Components 0.1
2023-03-06 16:46:32 +00:00
import StatusQ . Controls 0.1
2024-01-24 12:36:23 +00:00
import StatusQ . Core 0.1
import StatusQ . Core . Theme 0.1
import StatusQ . Core . Utils 0.1
2023-11-29 15:48:33 +00:00
import StatusQ . Internal 0.1
2024-01-24 12:36:23 +00:00
import StatusQ . Models 0.1
2023-11-29 15:48:33 +00:00
import StatusQ . Popups 0.1
import StatusQ . Popups . Dialog 0.1
2023-01-31 10:49:07 +00:00
2023-11-29 15:48:33 +00:00
import shared . controls 1.0
2021-10-27 21:27:49 +00:00
import shared . panels 1.0
2023-11-29 15:48:33 +00:00
import shared . popups 1.0
2023-01-31 10:49:07 +00:00
import utils 1.0
2021-10-21 08:22:05 +00:00
2023-11-29 15:48:33 +00:00
import AppLayouts . Wallet . views . collectibles 1.0
2023-12-06 10:54:36 +00:00
import AppLayouts . Wallet . controls 1.0
2023-11-29 15:48:33 +00:00
import SortFilterProxyModel 0.2
2021-10-21 08:22:05 +00:00
2023-12-06 10:54:36 +00:00
ColumnLayout {
2020-08-18 18:46:11 +00:00
id: root
2023-11-29 15:48:33 +00:00
required property var collectiblesModel
2024-01-17 20:19:58 +00:00
required property string addressFilters
required property string networkFilters
2023-11-29 15:48:33 +00:00
property bool sendEnabled: true
2023-12-06 10:54:36 +00:00
property bool filterVisible
2023-01-20 00:44:35 +00:00
2023-09-11 10:20:36 +00:00
signal collectibleClicked ( int chainId , string contractAddress , string tokenId , string uid )
2023-11-29 15:48:33 +00:00
signal sendRequested ( string symbol )
signal receiveRequested ( string symbol )
signal switchToCommunityRequested ( string communityId )
signal manageTokensRequested ( )
2023-12-06 10:54:36 +00:00
spacing: 0
2023-11-29 15:48:33 +00:00
QtObject {
id: d
readonly property int cellHeight: 225
readonly property int communityCellHeight: 242
readonly property int cellWidth: 176
2024-01-24 12:36:23 +00:00
readonly property int headerHeight: 56
2023-01-20 00:44:35 +00:00
2023-12-06 10:54:36 +00:00
readonly property bool isCustomView: cmbTokenOrder . currentValue === SortOrderComboBox . TokenOrderCustom
2023-11-29 15:48:33 +00:00
readonly property var renamedModel: RolesRenamingModel {
sourceModel: root . collectiblesModel
mapping: [
RoleRename {
from: "uid"
to: "symbol"
}
]
}
2024-01-24 12:36:23 +00:00
readonly property bool hasCollectibles: nonCommunityModel . count
readonly property bool hasCommunityCollectibles: communityModel . count
readonly property bool onlyOneType: ! hasCollectibles || ! hasCommunityCollectibles
2023-11-29 15:48:33 +00:00
readonly property var controller: ManageTokensController {
settingsKey: "WalletCollectibles"
2023-12-18 10:12:57 +00:00
sourceModel: d . renamedModel
2023-11-29 15:48:33 +00:00
}
2024-01-17 20:19:58 +00:00
readonly property var nwFilters: root . networkFilters . split ( ":" )
readonly property var addrFilters: root . addressFilters . split ( ":" ) . map ( ( addr ) = > addr . toLowerCase ( ) )
2023-11-29 15:48:33 +00:00
function hideAllCommunityTokens ( communityId ) {
2023-12-06 10:54:36 +00:00
const tokenSymbols = ModelUtils . getAll ( communityCollectiblesView . model , "symbol" , "communityId" , communityId )
2024-01-19 09:53:32 +00:00
d . controller . settingsHideGroupTokens ( communityId , tokenSymbols )
2021-10-21 08:22:05 +00:00
}
2024-01-17 20:19:58 +00:00
function containsAny ( list , filterList ) {
for ( let i = 0 ; i < list . length ; i ++ ) {
if ( filterList . includes ( list [ i ] . toLowerCase ( ) ) ) {
return true
}
}
return false
}
2020-08-19 15:58:25 +00:00
}
2023-12-06 10:54:36 +00:00
component CustomSFPM: SortFilterProxyModel {
id: customFilter
property bool isCommunity
2023-11-29 15:48:33 +00:00
2023-12-06 10:54:36 +00:00
sourceModel: d . renamedModel
proxyRoles: JoinRole {
name: "groupName"
roleNames: [ "collectionName" , "communityName" ]
2023-11-29 15:48:33 +00:00
}
2023-12-06 10:54:36 +00:00
filters: [
2024-01-17 20:19:58 +00:00
FastExpressionFilter {
expression: {
d . addrFilters
return d . nwFilters . includes ( model . chainId + "" ) && d . containsAny ( model . ownershipAddresses . split ( ":" ) , d . addrFilters )
}
expectedRoles: [ "chainId" , "ownershipAddresses" ]
} ,
2024-01-04 12:05:54 +00:00
FastExpressionFilter {
2023-12-06 10:54:36 +00:00
expression: {
d . controller . settingsDirty
return d . controller . filterAcceptsSymbol ( model . symbol ) && ( customFilter . isCommunity ? ! ! model.communityId : ! model . communityId )
}
2024-01-04 12:05:54 +00:00
expectedRoles: [ "symbol" , "communityId" ]
2023-12-18 10:12:57 +00:00
} ,
2024-01-04 12:05:54 +00:00
FastExpressionFilter {
2023-12-18 10:12:57 +00:00
enabled: customFilter . isCommunity && cmbFilter . hasEnabledFilters
expression: cmbFilter . selectedFilterGroupIds . includes ( model . communityId ) ||
( ! model . communityId && cmbFilter . selectedFilterGroupIds . includes ( "" ) )
2024-01-04 12:05:54 +00:00
expectedRoles: [ "communityId" ]
2023-12-18 10:12:57 +00:00
} ,
2024-01-04 12:05:54 +00:00
FastExpressionFilter {
2023-12-18 10:12:57 +00:00
enabled: ! customFilter . isCommunity && cmbFilter . hasEnabledFilters
expression: cmbFilter . selectedFilterGroupIds . includes ( model . collectionUid ) ||
( ! model . collectionUid && cmbFilter . selectedFilterGroupIds . includes ( "" ) )
2024-01-04 12:05:54 +00:00
expectedRoles: [ "collectionUid" ]
2023-12-06 10:54:36 +00:00
}
]
sorters: [
2024-01-04 12:05:54 +00:00
FastExpressionSorter {
2023-12-06 10:54:36 +00:00
expression: {
d . controller . settingsDirty
return d . controller . lessThan ( modelLeft . symbol , modelRight . symbol )
}
enabled: d . isCustomView
2024-01-04 12:05:54 +00:00
expectedRoles: [ "symbol" ]
2023-12-06 10:54:36 +00:00
} ,
RoleSorter {
roleName: cmbTokenOrder . currentSortRoleName
sortOrder: cmbTokenOrder . currentSortOrder
enabled: ! d . isCustomView
}
]
}
2023-11-29 15:48:33 +00:00
2024-01-24 12:36:23 +00:00
CustomSFPM {
id: communityModel
isCommunity: true
}
CustomSFPM {
id: nonCommunityModel
}
2023-12-06 10:54:36 +00:00
Settings {
2024-01-24 12:02:03 +00:00
id: settings
2023-12-06 10:54:36 +00:00
category: "CollectiblesViewSortSettings"
2024-01-24 12:02:03 +00:00
property int currentSortValue: SortOrderComboBox . TokenOrderDateAdded
2023-12-06 10:54:36 +00:00
property alias currentSortOrder: cmbTokenOrder . currentSortOrder
2023-12-18 10:12:57 +00:00
property alias selectedFilterGroupIds: cmbFilter . selectedFilterGroupIds
2023-12-06 10:54:36 +00:00
}
2024-01-24 12:02:03 +00:00
Component.onCompleted: {
settings . sync ( )
cmbTokenOrder . currentIndex = cmbTokenOrder . indexOfValue ( settings . currentSortValue )
}
Component.onDestruction: {
settings . currentSortValue = cmbTokenOrder . currentValue
}
2023-12-06 10:54:36 +00:00
ColumnLayout {
Layout.fillWidth: true
2024-01-24 12:36:23 +00:00
Layout.fillHeight: false
2023-12-18 10:12:57 +00:00
Layout.preferredHeight: root . filterVisible ? implicitHeight : 0
2023-12-06 10:54:36 +00:00
spacing: 20
2023-12-18 10:12:57 +00:00
opacity: root . filterVisible ? 1 : 0
2023-12-06 10:54:36 +00:00
Behavior on Layout . preferredHeight { NumberAnimation { duration: 200 ; easing.type: Easing . InOutQuad } }
Behavior on opacity { NumberAnimation { duration: 200 ; easing.type: Easing . InOutQuad } }
2023-11-29 15:48:33 +00:00
StatusDialogDivider {
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
2023-12-06 10:54:36 +00:00
spacing: Style . current . halfPadding
2023-12-18 10:12:57 +00:00
FilterComboBox {
id: cmbFilter
regularTokensModel: d . controller . regularTokensModel
2024-01-19 09:53:32 +00:00
collectionGroupsModel: d . controller . collectionGroupsModel
2023-12-18 10:12:57 +00:00
communityTokenGroupsModel: d . controller . communityTokenGroupsModel
hasCommunityGroups: d . hasCommunityCollectibles
}
Rectangle {
Layout.preferredHeight: 34
Layout.preferredWidth: 1
Layout.leftMargin: 12
Layout.rightMargin: 12
color: Theme . palette . baseColor2
}
2023-11-29 15:48:33 +00:00
StatusBaseText {
color: Theme . palette . baseColor1
2023-12-06 10:54:36 +00:00
font.pixelSize: Style . current . additionalTextSize
text: qsTr ( "Sort by:" )
2023-11-29 15:48:33 +00:00
}
2023-12-06 10:54:36 +00:00
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 ( )
}
2020-08-20 21:59:07 +00:00
}
2023-12-18 10:12:57 +00:00
Item { Layout.fillWidth: true }
StatusLinkText {
visible: cmbFilter . hasEnabledFilters
normalColor: Theme . palette . primaryColor1
text: qsTr ( "Clear filter" )
onClicked: cmbFilter . clearFilter ( )
}
2020-08-20 21:59:07 +00:00
}
2023-11-29 15:48:33 +00:00
2023-12-06 10:54:36 +00:00
StatusDialogDivider {
Layout.fillWidth: true
}
}
ShapeRectangle {
Layout.fillWidth: true
2023-12-18 10:12:57 +00:00
Layout.topMargin: Style . current . padding
2023-12-06 10:54:36 +00:00
visible: ! d . hasCollectibles && ! d . hasCommunityCollectibles
text: qsTr ( "Collectibles will appear here" )
}
2024-01-24 12:36:23 +00:00
DoubleFlickableWithFolding {
id: doubleFlickable
2023-12-06 10:54:36 +00:00
Layout.fillWidth: true
Layout.fillHeight: true
2024-01-24 12:36:23 +00:00
clip: true
flickable1: CustomGridView {
id: communityCollectiblesView
header: Item {
id: communityHeader
2023-12-06 10:54:36 +00:00
2024-01-24 12:36:23 +00:00
height: d . headerHeight
width: doubleFlickable . width
HeaderDelegate {
width: communityHeader . width
height: communityHeader . height
parent: doubleFlickable . contentItem
y: doubleFlickable . gridHeader1YInContentItem
z: 1
text: qsTr ( "Community minted" )
scrolled: ! doubleFlickable . atYBeginning
checked: doubleFlickable . flickable1Folded
onToggleClicked: doubleFlickable . flip1Folding ( )
onInfoClicked: Global . openPopup ( communityInfoPopupCmp )
}
2023-12-06 10:54:36 +00:00
}
2024-01-24 12:36:23 +00:00
Binding {
target: communityCollectiblesView
property: "header"
when: d . onlyOneType
value: null
restoreMode: Binding . RestoreBindingOrValue
2023-12-06 10:54:36 +00:00
}
2024-01-24 12:36:23 +00:00
width: doubleFlickable . width
cellHeight: d . communityCellHeight
model: communityModel
}
flickable2: CustomGridView {
id: regularCollectiblesView
header: Item {
id: nonCommunityHeader
height: d . headerHeight
width: doubleFlickable . width
HeaderDelegate {
width: nonCommunityHeader . width
height: nonCommunityHeader . height
parent: doubleFlickable . contentItem
y: doubleFlickable . gridHeader2YInContentItem
z: 1
text: qsTr ( "Others" )
checked: doubleFlickable . flickable2Folded
scrolled: ( doubleFlickable . contentY >
communityCollectiblesView . contentHeight
- d . headerHeight )
showInfoButton: false
onToggleClicked: doubleFlickable . flip2Folding ( )
2023-12-06 10:54:36 +00:00
}
}
2024-01-24 12:36:23 +00:00
Binding {
target: regularCollectiblesView
property: "header"
when: d . onlyOneType
value: null
restoreMode: Binding . RestoreBindingOrValue
2023-12-06 10:54:36 +00:00
}
2024-01-24 12:36:23 +00:00
width: doubleFlickable . width
cellHeight: d . cellHeight
model: nonCommunityModel
}
}
component HeaderDelegate: Rectangle {
id: sectionDelegate
property alias text: headerLabel . text
property alias checked: toggleButton . checked
property bool scrolled: false
property alias showInfoButton: infoButton . visible
signal toggleClicked
signal infoClicked
color: Theme . palette . statusListItem . backgroundColor
RowLayout {
anchors.fill: parent
StatusFlatButton {
id: toggleButton
checkable: true
size: StatusBaseButton . Size . Small
icon.name: checked ? "chevron-down" : "next"
textColor: Theme . palette . baseColor1
textHoverColor: Theme . palette . directColor1
onToggled: sectionDelegate . toggleClicked ( )
}
StatusBaseText {
id: headerLabel
Layout.fillWidth: true
color: Theme . palette . baseColor1
elide: Text . ElideRight
}
StatusFlatButton {
id: infoButton
icon.name: "info"
textColor: Theme . palette . baseColor1
onClicked: sectionDelegate . infoClicked ( )
}
}
Rectangle {
width: parent . width
height: 4
anchors.top: parent . bottom
color: Theme . palette . directColor8
visible: ! sectionDelegate . checked && sectionDelegate . scrolled
2023-11-29 15:48:33 +00:00
}
}
component CustomGridView: StatusGridView {
id: gridView
interactive: false
cellWidth: d . cellWidth
delegate: collectibleDelegate
2020-08-20 21:59:07 +00:00
}
2020-08-18 18:46:11 +00:00
2021-10-21 08:22:05 +00:00
Component {
2023-11-29 15:48:33 +00:00
id: collectibleDelegate
CollectibleView {
width: d . cellWidth
height: isCommunityCollectible ? d.communityCellHeight : d . cellHeight
title: model . name ? model.name : "..."
2023-12-06 10:54:36 +00:00
subTitle: model . collectionName ? model.collectionName : model . collectionUid ? model.collectionUid : ""
2023-11-29 15:48:33 +00:00
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 } )
2020-08-19 15:58:25 +00:00
}
2023-11-29 15:48:33 +00:00
onSwitchToCommunityRequested: ( communityId ) = > root . switchToCommunityRequested ( communityId )
}
}
2023-03-06 16:46:32 +00:00
2023-11-29 15:48:33 +00:00
Component {
id: tokenContextMenu
StatusMenu {
onClosed: destroy ( )
2023-08-03 16:05:04 +00:00
2023-11-29 15:48:33 +00:00
property string symbol
property string tokenName
property string tokenImage
property string communityId
property string communityName
property string communityImage
2023-08-03 16:05:04 +00:00
2023-11-29 15:48:33 +00:00
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 ( )
2023-08-03 16:05:04 +00:00
}
2023-11-29 15:48:33 +00:00
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 } )
}
}
}
2023-08-03 16:05:04 +00:00
2023-11-29 15:48:33 +00:00
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 )
2023-12-06 10:54:36 +00:00
. arg ( ` < a style = "text-decoration:none" href = "#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}/${Constants.walletSettingsSubsection.manageCollectibles}" > ` + qsTr ( "Settings" , "Go to Settings" ) + "</a>" ) ,
2023-11-29 15:48:33 +00:00
"" ,
"checkmark-circle" ,
false ,
Constants . ephemeralNotificationType . success ,
""
)
2023-08-03 16:05:04 +00:00
}
2023-11-29 15:48:33 +00:00
}
}
Component {
id: confirmHideCommunityCollectiblesPopup
ConfirmationDialog {
property string communityId
property string communityName
property string communityImage
2023-08-03 16:05:04 +00:00
2023-11-29 15:48:33 +00:00
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 )
2023-12-06 10:54:36 +00:00
. arg ( ` < a style = "text-decoration:none" href = "#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}/${Constants.walletSettingsSubsection.manageCollectibles}" > ` + qsTr ( "Settings" , "Go to Settings" ) + "</a>" ) ,
2023-11-29 15:48:33 +00:00
"" ,
"checkmark-circle" ,
false ,
Constants . ephemeralNotificationType . success ,
""
)
2023-08-03 16:05:04 +00:00
}
2020-08-18 18:46:11 +00:00
}
2020-07-28 18:19:46 +00:00
}
2020-05-28 14:54:42 +00:00
}