(wallet) implement sorting for assets/collectibles views

- add the sort combobox to assets/collectibles main wallet view pages
- preserve the current view settings
- add the possibility to navigate/drill down into wallet settings
(sub)subsection
- some other minor changes/fixes

Note: currently assets can't be sorted by "1W change" and collectibles
by "Date added" due to missing backend for these

Fixes #12517
Fixes #12518
This commit is contained in:
Lukáš Tinkl 2023-12-06 11:54:36 +01:00 committed by Lukáš Tinkl
parent e24e3d734c
commit fe3b442155
23 changed files with 697 additions and 468 deletions

View File

@ -34,6 +34,7 @@ SplitView {
SplitView.preferredWidth: 600 SplitView.preferredWidth: 600
SplitView.fillHeight: true SplitView.fillHeight: true
assets: assetsModel assets: assetsModel
filterVisible: ctrlFilterVisible.checked
onAssetClicked: logs.logEvent("onAssetClicked", ["token"], [token.symbol, token.communityId]) onAssetClicked: logs.logEvent("onAssetClicked", ["token"], [token.symbol, token.communityId])
onSendRequested: logs.logEvent("onSendRequested", ["symbol"], arguments) onSendRequested: logs.logEvent("onSendRequested", ["symbol"], arguments)
onReceiveRequested: logs.logEvent("onReceiveRequested", ["symbol"], arguments) onReceiveRequested: logs.logEvent("onReceiveRequested", ["symbol"], arguments)
@ -48,6 +49,14 @@ SplitView {
SplitView.preferredWidth: 250 SplitView.preferredWidth: 250
logsView.logText: logs.logText logsView.logText: logs.logText
ColumnLayout {
Switch {
id: ctrlFilterVisible
text: "Filter visible"
checked: true
}
}
} }
} }

View File

@ -40,6 +40,7 @@ SplitView {
SplitView.preferredWidth: 600 SplitView.preferredWidth: 600
SplitView.fillHeight: true SplitView.fillHeight: true
collectiblesModel: ctrlEmptyModel.checked ? emptyModel : collectiblesModel collectiblesModel: ctrlEmptyModel.checked ? emptyModel : collectiblesModel
filterVisible: ctrlFilterVisible.checked
onCollectibleClicked: logs.logEvent("onCollectibleClicked", ["chainId", "contractAddress", "tokenId", "uid"], arguments) onCollectibleClicked: logs.logEvent("onCollectibleClicked", ["chainId", "contractAddress", "tokenId", "uid"], arguments)
onSendRequested: logs.logEvent("onSendRequested", ["symbol"], arguments) onSendRequested: logs.logEvent("onSendRequested", ["symbol"], arguments)
onReceiveRequested: logs.logEvent("onReceiveRequested", ["symbol"], arguments) onReceiveRequested: logs.logEvent("onReceiveRequested", ["symbol"], arguments)
@ -56,6 +57,11 @@ SplitView {
logsView.logText: logs.logText logsView.logText: logs.logText
ColumnLayout { ColumnLayout {
Switch {
id: ctrlFilterVisible
text: "Filter visible"
checked: true
}
Switch { Switch {
id: ctrlEmptyModel id: ctrlEmptyModel
text: "Empty model" text: "Empty model"
@ -67,3 +73,4 @@ SplitView {
// category: Views // category: Views
// https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?type=design&node-id=19558-95270&mode=design&t=ShZOuMRfiIIl2aR8-0 // https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?type=design&node-id=19558-95270&mode=design&t=ShZOuMRfiIIl2aR8-0
// https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?type=design&node-id=19558-96427&mode=design&t=ShZOuMRfiIIl2aR8-0 // https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?type=design&node-id=19558-96427&mode=design&t=ShZOuMRfiIIl2aR8-0
// https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?node-id=19087%3A293357&mode=dev

View File

@ -47,6 +47,11 @@ SplitView {
text: "Dirty: %1".arg(showcasePanel.dirty ? "true" : "false") text: "Dirty: %1".arg(showcasePanel.dirty ? "true" : "false")
} }
Label {
Layout.fillWidth: true
text: "Has saved settings: %1".arg(showcasePanel.hasSettings ? "true" : "false")
}
Button { Button {
text: "Save" text: "Save"
onClicked: showcasePanel.saveSettings() onClicked: showcasePanel.saveSettings()
@ -67,6 +72,7 @@ SplitView {
} }
Button { Button {
enabled: showcasePanel.hasSettings
text: "Clear settings" text: "Clear settings"
onClicked: showcasePanel.clearSettings() onClicked: showcasePanel.clearSettings()
} }

View File

@ -50,6 +50,11 @@ SplitView {
text: "Dirty: %1".arg(showcasePanel.dirty ? "true" : "false") text: "Dirty: %1".arg(showcasePanel.dirty ? "true" : "false")
} }
Label {
Layout.fillWidth: true
text: "Has saved settings: %1".arg(showcasePanel.hasSettings ? "true" : "false")
}
Button { Button {
text: "Save" text: "Save"
onClicked: showcasePanel.saveSettings() onClicked: showcasePanel.saveSettings()
@ -71,6 +76,7 @@ SplitView {
} }
Button { Button {
enabled: showcasePanel.hasSettings
text: "Clear settings" text: "Clear settings"
onClicked: showcasePanel.clearSettings() onClicked: showcasePanel.clearSettings()
} }

View File

@ -44,11 +44,7 @@ ListModel {
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
currencyPrice: { currencyPrice: {},
amount: 10.37,
symbol: "EUR",
displayDecimals: 2
},
communityId: "ddls", communityId: "ddls",
communityName: "Doodles", communityName: "Doodles",
communityImage: ModelsData.collectibles.doodles // FIXME backend communityImage: ModelsData.collectibles.doodles // FIXME backend
@ -65,6 +61,7 @@ ListModel {
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
currencyPrice: {},
communityId: "sox", communityId: "sox",
communityName: "Socks", communityName: "Socks",
communityImage: ModelsData.icons.socks communityImage: ModelsData.icons.socks
@ -81,17 +78,22 @@ ListModel {
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
currencyPrice: {
amount: 0.25,
symbol: "EUR",
displayDecimals: 2
},
changePct24hour: -2.1, changePct24hour: -2.1,
communityId: "", communityId: "",
communityName: "", communityName: "",
communityImage: "" communityImage: ""
}, },
{ {
name: "Ave Maria", name: "Request",
symbol: "AAVE", symbol: "REQ",
enabledNetworkBalance: { enabledNetworkBalance: {
amount: 23.3, amount: 0.00005,
symbol: "AAVE", symbol: "REQ",
displayDecimals: 2 displayDecimals: 2
}, },
enabledNetworkCurrencyBalance: { enabledNetworkCurrencyBalance: {
@ -99,6 +101,11 @@ ListModel {
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
currencyPrice: {
amount: 0.1000001,
symbol: "EUR",
displayDecimals: 2
},
changePct24hour: 4.56, changePct24hour: 4.56,
communityId: "", communityId: "",
communityName: "", communityName: "",
@ -116,6 +123,11 @@ ListModel {
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
currencyPrice: {
amount: 0.000752089,
symbol: "EUR",
displayDecimals: 2
},
changePct24hour: -11.6789, changePct24hour: -11.6789,
communityId: "", communityId: "",
communityName: "", communityName: "",
@ -134,6 +146,11 @@ ListModel {
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
currencyPrice: {
amount: 0.937718773,
symbol: "EUR",
displayDecimals: 2
},
changePct24hour: 0, changePct24hour: 0,
communityId: "", communityId: "",
communityName: "", communityName: "",
@ -152,6 +169,7 @@ ListModel {
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
currencyPrice: {},
changePct24hour: -1, changePct24hour: -1,
communityId: "", communityId: "",
communityName: "", communityName: "",
@ -161,13 +179,13 @@ ListModel {
name: "Ethereum", name: "Ethereum",
symbol: "ETH", symbol: "ETH",
enabledNetworkBalance: { enabledNetworkBalance: {
amount: 0.12345, amount: 0.123456789,
symbol: "ETH", symbol: "ETH",
displayDecimals: 8, displayDecimals: 8,
stripTrailingZeroes: true stripTrailingZeroes: true
}, },
enabledNetworkCurrencyBalance: { enabledNetworkCurrencyBalance: {
amount: 182.72, amount: 182.73004849,
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
@ -186,6 +204,7 @@ ListModel {
symbol: "InvisibleSYM", symbol: "InvisibleSYM",
enabledNetworkBalance: {}, enabledNetworkBalance: {},
enabledNetworkCurrencyBalance: {}, enabledNetworkCurrencyBalance: {},
currencyPrice: {},
changePct24hour: NaN, changePct24hour: NaN,
communityId: "", communityId: "",
communityName: "", communityName: "",
@ -193,7 +212,7 @@ ListModel {
}, },
{ {
enabledNetworkBalance: ({ enabledNetworkBalance: ({
displayDecimals: true, displayDecimals: 4,
stripTrailingZeroes: true, stripTrailingZeroes: true,
amount: 0, amount: 0,
symbol: "SNT" symbol: "SNT"
@ -201,7 +220,7 @@ ListModel {
enabledNetworkCurrencyBalance: ({ enabledNetworkCurrencyBalance: ({
displayDecimals: 4, displayDecimals: 4,
stripTrailingZeroes: true, stripTrailingZeroes: true,
amount: 0, amount: 0.,
symbol: "EUR" symbol: "EUR"
}), }),
currencyPrice: { currencyPrice: {
@ -228,6 +247,7 @@ ListModel {
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
currencyPrice: {},
communityId: "ddls", communityId: "ddls",
communityName: "Doodles", communityName: "Doodles",
communityImage: ModelsData.collectibles.doodles communityImage: ModelsData.collectibles.doodles
@ -244,6 +264,7 @@ ListModel {
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
currencyPrice: {},
communityId: "ast", communityId: "ast",
communityName: "Astafarians", communityName: "Astafarians",
communityImage: ModelsData.icons.dribble communityImage: ModelsData.icons.dribble

View File

@ -78,11 +78,9 @@ QQmlListProperty<RoleRename> RolesRenamingModel::mapping()
QHash<int, QByteArray> RolesRenamingModel::roleNames() const QHash<int, QByteArray> RolesRenamingModel::roleNames() const
{ {
QHash<int, QByteArray> roles = sourceModel() const auto roles = sourceModel() ? sourceModel()->roleNames() : QHash<int, QByteArray>{};
? sourceModel()->roleNames()
: QHash<int, QByteArray>{};
if (roles.empty()) if (roles.isEmpty())
return roles; return roles;
QHash<QString, RoleRename*> renameMap; QHash<QString, RoleRename*> renameMap;

View File

@ -23,7 +23,7 @@ QVariant SubmodelProxyModel::data(const QModelIndex &index, int role) const
? creationContext : m_delegateModel->engine()->rootContext(); ? creationContext : m_delegateModel->engine()->rootContext();
auto context = new QQmlContext(parentContext, parentContext); auto context = new QQmlContext(parentContext, parentContext);
context->setContextProperty("submodel", submodel); context->setContextProperty(QStringLiteral("submodel"), submodel);
QObject* instance = m_delegateModel->create(context); QObject* instance = m_delegateModel->create(context);
QQmlEngine::setObjectOwnership(instance, QQmlEngine::JavaScriptOwnership); QQmlEngine::setObjectOwnership(instance, QQmlEngine::JavaScriptOwnership);

View File

@ -164,12 +164,15 @@ void ManageTokensController::clearSettings()
m_settings.remove(QString()); m_settings.remove(QString());
m_settings.endGroup(); m_settings.endGroup();
m_settings.sync(); m_settings.sync();
emit settingsDirtyChanged(false);
} }
void ManageTokensController::loadSettings() void ManageTokensController::loadSettings()
{ {
Q_ASSERT(!m_settingsKey.isEmpty()); Q_ASSERT(!m_settingsKey.isEmpty());
setSettingsDirty(true);
m_settingsData.clear(); m_settingsData.clear();
// load from QSettings // load from QSettings
@ -189,6 +192,7 @@ void ManageTokensController::loadSettings()
} }
m_settings.endArray(); m_settings.endArray();
m_settings.endGroup(); m_settings.endGroup();
setSettingsDirty(false);
} }
void ManageTokensController::setSettingsDirty(bool dirty) void ManageTokensController::setSettingsDirty(bool dirty)
@ -212,7 +216,7 @@ bool ManageTokensController::hasSettings() const
{ {
Q_ASSERT(!m_settingsKey.isEmpty()); Q_ASSERT(!m_settingsKey.isEmpty());
const auto groups = m_settings.childGroups(); const auto groups = m_settings.childGroups();
return !groups.isEmpty() && groups.contains(settingsGroupName()); return groups.contains(settingsGroupName());
} }
void ManageTokensController::settingsHideToken(const QString& symbol) void ManageTokensController::settingsHideToken(const QString& symbol)

View File

@ -25,6 +25,7 @@ class ManageTokensController : public QObject, public QQmlParserStatus
Q_PROPERTY(QAbstractItemModel* communityTokenGroupsModel READ communityTokenGroupsModel CONSTANT FINAL) Q_PROPERTY(QAbstractItemModel* communityTokenGroupsModel READ communityTokenGroupsModel CONSTANT FINAL)
Q_PROPERTY(QAbstractItemModel* hiddenTokensModel READ hiddenTokensModel CONSTANT FINAL) Q_PROPERTY(QAbstractItemModel* hiddenTokensModel READ hiddenTokensModel CONSTANT FINAL)
Q_PROPERTY(bool dirty READ dirty NOTIFY dirtyChanged FINAL) Q_PROPERTY(bool dirty READ dirty NOTIFY dirtyChanged FINAL)
Q_PROPERTY(bool hasSettings READ hasSettings NOTIFY settingsDirtyChanged FINAL)
Q_PROPERTY(bool settingsDirty READ settingsDirty NOTIFY settingsDirtyChanged FINAL) Q_PROPERTY(bool settingsDirty READ settingsDirty NOTIFY settingsDirtyChanged FINAL)
public: public:
@ -38,7 +39,6 @@ public:
Q_INVOKABLE void saveSettings(bool reuseCurrent = false); Q_INVOKABLE void saveSettings(bool reuseCurrent = false);
Q_INVOKABLE void clearSettings(); Q_INVOKABLE void clearSettings();
Q_INVOKABLE void revert(); Q_INVOKABLE void revert();
Q_INVOKABLE bool hasSettings() const;
Q_INVOKABLE void settingsHideToken(const QString& symbol); Q_INVOKABLE void settingsHideToken(const QString& symbol);
Q_INVOKABLE void settingsHideCommunityTokens(const QString& communityId, const QStringList& symbols); Q_INVOKABLE void settingsHideCommunityTokens(const QString& communityId, const QStringList& symbols);
@ -95,6 +95,7 @@ private:
void setSettingsKey(const QString& newSettingsKey); void setSettingsKey(const QString& newSettingsKey);
QSettings m_settings; QSettings m_settings;
SerializedTokenData m_settingsData; // symbol -> {sortOrder, visible, groupId} SerializedTokenData m_settingsData; // symbol -> {sortOrder, visible, groupId}
bool hasSettings() const;
bool m_settingsDirty{false}; bool m_settingsDirty{false};
bool settingsDirty() const { return m_settingsDirty; } bool settingsDirty() const { return m_settingsDirty; }

View File

@ -54,6 +54,7 @@ StatusSectionLayout {
keycardView.item.handleBackAction() keycardView.item.handleBackAction()
break; break;
} }
Global.settingsSubSubsection = -1
} }
Component.onCompleted: { Component.onCompleted: {

View File

@ -2,6 +2,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13 import QtGraphicalEffects 1.13
import QtQml 2.15
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
@ -88,6 +89,14 @@ SettingsContentBase {
accountView.height accountView.height
currentIndex: mainViewIndex currentIndex: mainViewIndex
Binding on currentIndex {
value: root.manageTokensViewIndex
when: Global.settingsSubSubsection === Constants.walletSettingsSubsection.manageAssets ||
Global.settingsSubSubsection === Constants.walletSettingsSubsection.manageCollectibles ||
Global.settingsSubSubsection === Constants.walletSettingsSubsection.manageTokenLists
restoreMode: Binding.RestoreNone
}
onCurrentIndexChanged: { onCurrentIndexChanged: {
root.rootStore.backButtonName = "" root.rootStore.backButtonName = ""
root.sectionTitle = root.walletSectionTitle root.sectionTitle = root.walletSectionTitle
@ -257,6 +266,23 @@ SettingsContentBase {
// TODO concat proxy model to include community collectibles (#12519) // TODO concat proxy model to include community collectibles (#12519)
return RootStore.collectiblesStore.ownedCollectibles return RootStore.collectiblesStore.ownedCollectibles
} }
Binding on currentIndex {
value: {
switch (Global.settingsSubSubsection) {
case Constants.walletSettingsSubsection.manageAssets:
return 0
case Constants.walletSettingsSubsection.manageCollectibles:
return 1
case Constants.walletSettingsSubsection.manageTokenLists:
return 2
}
}
when: Global.settingsSubSubsection === Constants.walletSettingsSubsection.manageAssets ||
Global.settingsSubSubsection === Constants.walletSettingsSubsection.manageCollectibles ||
Global.settingsSubSubsection === Constants.walletSettingsSubsection.manageTokenLists
restoreMode: Binding.RestoreNone
}
} }
DappPermissionsView { DappPermissionsView {

View File

@ -18,6 +18,8 @@ ColumnLayout {
required property var baseWalletAssetsModel required property var baseWalletAssetsModel
required property var baseWalletCollectiblesModel required property var baseWalletCollectiblesModel
property alias currentIndex: tabBar.currentIndex
readonly property bool dirty: { readonly property bool dirty: {
if (!loader.item) if (!loader.item)
return false return false

View File

@ -197,6 +197,7 @@ Item {
Component { Component {
id: receiveModalComponent id: receiveModalComponent
ReceiveModal { ReceiveModal {
destroyOnClose: true
anchors.centerIn: parent anchors.centerIn: parent
} }
} }

View File

@ -13,79 +13,71 @@ import utils 1.0
ComboBox { ComboBox {
id: root id: root
property int sortOrder: Qt.DescendingOrder // expected model role names: text, value (enum TokenOrder), sortRoleName, icon (optional)
readonly property string currentSortRoleName: d.currentSortRoleName // text === "---" denotes a separator
property bool hasCustomOrderDefined
property int currentSortOrder: Qt.DescendingOrder
readonly property string currentSortRoleName: root.currentIndex !== -1 ? root.model[root.currentIndex].sortRoleName : ""
signal createOrEditRequested()
model: d.predefinedSortModel
textRole: "text" textRole: "text"
valueRole: "value" valueRole: "value"
displayText: !d.isCustomSortOrder ? "%1 %2".arg(currentText).arg(sortOrder === Qt.DescendingOrder ? "↓" : "↑")
: currentText
Component.onCompleted: currentIndex = indexOfValue(SortOrderComboBox.TokenOrderCustom) displayText: root.currentValue === SortOrderComboBox.TokenOrderCustom ? currentText
: "%1 %2".arg(currentText).arg(currentSortOrder === Qt.DescendingOrder ? "↓" : "↑")
onActivated: {
if (index === indexOfValue(SortOrderComboBox.TokenOrderCreateCustom)) { // restore the previous sort role and signal we want create/edit
currentIndex = d.currentIndex
root.createOrEditRequested()
} else {
if (d.currentIndex === index) // just keep the same sort role and flip the up/down
currentSortOrder = currentSortOrder === Qt.AscendingOrder ? Qt.DescendingOrder : Qt.AscendingOrder
// update internal index
d.currentIndex = index
}
}
Component.onCompleted: {
d.currentIndex = root.currentIndex // sync with settings which might arrive from the outside
}
enum TokenOrder { enum TokenOrder {
TokenOrderNone = 0, TokenOrderNone = 0,
TokenOrderCustom, TokenOrderCurrencyBalance, // FIAT value of asset balance (enabledNetworkCurrencyBalance)
TokenOrderValue, TokenOrderBalance, // Number of tokens (enabledNetworkBalance)
TokenOrderBalance, TokenOrderCurrencyPrice, // Value per token in FIAT (currencyPrice)
TokenOrder1WChange, TokenOrder1WChange, // Level of change in asset balance value (in FIAT) comp. to 7 days earlier
TokenOrderAlpha TokenOrderAlpha, // Alphabetic by asset name (name)
TokenOrderDateAdded, // Date added descending (newest first)
TokenOrderGroupName, // Collection or Community name
TokenOrderCustom, // Custom (user created) order
TokenOrderCreateCustom // special menu entry to create/edit the custom sort order
} }
horizontalPadding: 12 horizontalPadding: 12
verticalPadding: 8 verticalPadding: Style.current.halfPadding
spacing: 8 spacing: Style.current.halfPadding
font.family: Theme.palette.baseFont.name font.family: Theme.palette.baseFont.name
font.pixelSize: Style.current.additionalTextSize font.pixelSize: Style.current.additionalTextSize
QtObject { QtObject {
id: d id: d
readonly property int defaultDelegateHeight: 34 readonly property int defaultDelegateHeight: 34
// // models property int currentIndex: 0
// readonly property SortFilterProxyModel tokensModel: SortFilterProxyModel {
// sourceModel: root.baseModel
// proxyRoles: [
// ExpressionRole {
// name: "currentBalance"
// expression: model.enabledNetworkBalance.amount
// },
// ExpressionRole {
// name: "currentCurrencyBalance"
// expression: model.enabledNetworkCurrencyBalance.amount
// }
// ]
// sorters: RoleSorter {
// roleName: cmbTokenOrder.currentSortRoleName
// sortOrder: cmbTokenOrder.sortOrder
// enabled: !d.isCustomSortOrder
// }
// filters: ValueFilter {
// roleName: "visibleForNetworkWithPositiveBalance"
// value: true
// }
// }
readonly property var predefinedSortModel: [
{ value: SortOrderComboBox.TokenOrderValue, text: qsTr("Token value"), icon: "token-sale", sortRoleName: "currentCurrencyBalance" }, // custom SFPM ExpressionRole
{ value: SortOrderComboBox.TokenOrderBalance, text: qsTr("Token balance"), icon: "wallet", sortRoleName: "currentBalance" }, // custom SFPM ExpressionRole
{ value: SortOrderComboBox.TokenOrder1WChange, text: qsTr("1W change"), icon: "time", sortRoleName: "changePct24hour" }, // FIXME changePct1Week role missing in backend!!!
{ value: SortOrderComboBox.TokenOrderAlpha, text: qsTr("Alphabetic"), icon: "bold", sortRoleName: "name" },
{ value: SortOrderComboBox.TokenOrderNone, text: "---", icon: "", sortRoleName: "" },
{ value: SortOrderComboBox.TokenOrderCustom, text: qsTr("Custom order"), icon: "exchange", sortRoleName: "" }
]
readonly property string currentSortRoleName: root.currentIndex !== -1 ? d.predefinedSortModel[root.currentIndex].sortRoleName : ""
readonly property bool isCustomSortOrder: root.currentValue === SortOrderComboBox.TokenOrderCustom
} }
background: Rectangle { background: Rectangle {
border.width: 1 border.width: 1
border.color: Theme.palette.directColor7 border.color: Theme.palette.directColor7
radius: 8 radius: Style.current.radius
color: root.down ? Theme.palette.baseColor2 : "transparent" color: root.down ? Theme.palette.baseColor2 : "transparent"
HoverHandler { HoverHandler {
cursorShape: root.enabled ? Qt.PointingHandCursor : undefined cursorShape: root.enabled ? Qt.PointingHandCursor : undefined
@ -116,16 +108,15 @@ ComboBox {
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
y: root.height + 4 y: root.height + 4
implicitWidth: root.width implicitWidth: 290
margins: 8 margins: Style.current.halfPadding
padding: 1 padding: 1
verticalPadding: 8 verticalPadding: Style.current.halfPadding
background: Rectangle { background: Rectangle {
color: Theme.palette.statusSelect.menuItemBackgroundColor color: Theme.palette.statusSelect.menuItemBackgroundColor
radius: 8 radius: Style.current.radius
border.color: Theme.palette.baseColor2
layer.enabled: true layer.enabled: true
layer.effect: DropShadow { layer.effect: DropShadow {
horizontalOffset: 0 horizontalOffset: 0
@ -138,6 +129,7 @@ ComboBox {
} }
contentItem: ColumnLayout { contentItem: ColumnLayout {
spacing: 0
StatusBaseText { StatusBaseText {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: d.defaultDelegateHeight Layout.preferredHeight: d.defaultDelegateHeight
@ -164,20 +156,18 @@ ComboBox {
spacing: root.spacing spacing: root.spacing
StatusIcon { StatusIcon {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
visible: !!icon visible: !!icon
icon: iconName icon: iconName
color: root.enabled ? Theme.palette.primaryColor1 : Theme.palette.baseColor1 color: root.enabled ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
width: 16
height: 16
} }
StatusBaseText { StatusBaseText {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true
text: menuText text: menuText
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight elide: Text.ElideRight
color: root.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1 color: isEditAction ? Theme.palette.primaryColor1 : root.enabled ? Theme.palette.directColor1 : Theme.palette.baseColor1
font.pixelSize: root.font.pixelSize font.pixelSize: root.font.pixelSize
font.weight: root.currentIndex === menuIndex ? Font.DemiBold : Font.Normal font.weight: root.currentIndex === menuIndex ? Font.DemiBold : Font.Normal
} }
@ -185,7 +175,7 @@ ComboBox {
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
Row { Row {
visible: !isCustomOrder visible: showUpDownArrows
spacing: 4 spacing: 4
StatusFlatRoundButton { StatusFlatRoundButton {
radius: 6 radius: 6
@ -195,11 +185,12 @@ ComboBox {
icon.width: 18 icon.width: 18
icon.height: 18 icon.height: 18
opacity: root.highlightedIndex === menuIndex || highlighted // not "visible, we want the item to stay put opacity: root.highlightedIndex === menuIndex || highlighted // not "visible, we want the item to stay put
highlighted: root.currentIndex === menuIndex && root.sortOrder === Qt.AscendingOrder highlighted: root.currentIndex === menuIndex && root.currentSortOrder === Qt.AscendingOrder
onClicked: { onClicked: {
if (root.currentIndex !== menuIndex) if (root.currentIndex !== menuIndex)
root.currentIndex = menuIndex root.currentIndex = menuIndex
root.sortOrder = Qt.AscendingOrder d.currentIndex = menuIndex
root.currentSortOrder = Qt.AscendingOrder
root.popup.close() root.popup.close()
} }
} }
@ -211,11 +202,12 @@ ComboBox {
icon.width: 18 icon.width: 18
icon.height: 18 icon.height: 18
opacity: root.highlightedIndex === menuIndex || highlighted // not "visible, we want the item to stay put opacity: root.highlightedIndex === menuIndex || highlighted // not "visible, we want the item to stay put
highlighted: root.currentIndex === menuIndex && root.sortOrder === Qt.DescendingOrder highlighted: root.currentIndex === menuIndex && root.currentSortOrder === Qt.DescendingOrder
onClicked: { onClicked: {
if (root.currentIndex !== menuIndex) if (root.currentIndex !== menuIndex)
root.currentIndex = menuIndex root.currentIndex = menuIndex
root.sortOrder = Qt.DescendingOrder d.currentIndex = menuIndex
root.currentSortOrder = Qt.DescendingOrder
root.popup.close() root.popup.close()
} }
} }
@ -234,9 +226,15 @@ ComboBox {
readonly property bool isSeparator: text === "---" readonly property bool isSeparator: text === "---"
id: menuDelegate id: menuDelegate
width: root.width width: ListView.view.width
highlighted: root.highlightedIndex === index highlighted: root.highlightedIndex === index
enabled: !isSeparator enabled: !isSeparator
visible: {
if (modelData["value"] === SortOrderComboBox.TokenOrderCustom) // hide "Custom order" menu entry if none defined
return root.hasCustomOrderDefined
return true
}
height: visible ? implicitHeight : 0
leftPadding: isSeparator ? 0 : 14 leftPadding: isSeparator ? 0 : 14
rightPadding: isSeparator ? 0 : 8 rightPadding: isSeparator ? 0 : 8
verticalPadding: isSeparator ? 2 : 5 verticalPadding: isSeparator ? 2 : 5
@ -264,9 +262,9 @@ ComboBox {
readonly property int menuIndex: menuDelegate.index readonly property int menuIndex: menuDelegate.index
readonly property string menuText: menuDelegate.text readonly property string menuText: menuDelegate.text
readonly property string iconName: menuDelegate.icon.name readonly property string iconName: menuDelegate.icon.name
readonly property bool isCustomOrder: !menuDelegate.modelData["sortRoleName"] readonly property bool showUpDownArrows: menuDelegate.modelData["sortRoleName"] !== ""
readonly property bool isEditAction: modelData["value"] === SortOrderComboBox.TokenOrderCreateCustom
sourceComponent: menuDelegate.isSeparator ? separatorMenuComponent : regularMenuComponent sourceComponent: menuDelegate.isSeparator ? separatorMenuComponent : regularMenuComponent
} }
onClicked: root.currentIndex = index
} }
} }

View File

@ -19,6 +19,7 @@ Control {
required property var baseModel required property var baseModel
readonly property bool dirty: d.controller.dirty readonly property bool dirty: d.controller.dirty
readonly property bool hasSettings: d.controller.hasSettings
background: null background: null

View File

@ -21,6 +21,7 @@ Control {
required property var baseModel required property var baseModel
readonly property bool dirty: d.controller.dirty readonly property bool dirty: d.controller.dirty
readonly property bool hasSettings: d.controller.hasSettings
background: null background: null

View File

@ -29,6 +29,7 @@ Item {
QtObject { QtObject {
id: d id: d
property var marketValueStore : RootStore.marketValueStore property var marketValueStore : RootStore.marketValueStore
readonly property string symbol: root.token ? root.token.symbol : ""
} }
Connections { Connections {
@ -286,8 +287,8 @@ Item {
} }
Connections { Connections {
target: token target: d
function onSymbolChanged() { graphDetail.updateBalanceStore() } function onSymbolChanged() { if (d.symbol) graphDetail.updateBalanceStore() }
} }
} }
} }

View File

@ -1,6 +1,7 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import Qt.labs.settings 1.1
import StatusQ 0.1 import StatusQ 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
@ -19,14 +20,16 @@ import shared.popups 1.0
import utils 1.0 import utils 1.0
import AppLayouts.Wallet.views.collectibles 1.0 import AppLayouts.Wallet.views.collectibles 1.0
import AppLayouts.Wallet.controls 1.0
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
StatusScrollView { ColumnLayout {
id: root id: root
required property var collectiblesModel required property var collectiblesModel
property bool sendEnabled: true property bool sendEnabled: true
property bool filterVisible
signal collectibleClicked(int chainId, string contractAddress, string tokenId, string uid) signal collectibleClicked(int chainId, string contractAddress, string tokenId, string uid)
signal sendRequested(string symbol) signal sendRequested(string symbol)
@ -34,6 +37,8 @@ StatusScrollView {
signal switchToCommunityRequested(string communityId) signal switchToCommunityRequested(string communityId)
signal manageTokensRequested() signal manageTokensRequested()
spacing: 0
QtObject { QtObject {
id: d id: d
@ -41,11 +46,7 @@ StatusScrollView {
readonly property int communityCellHeight: 242 readonly property int communityCellHeight: 242
readonly property int cellWidth: 176 readonly property int cellWidth: 176
readonly property bool isCustomView: d.controller.hasSettings // TODO add respect other predefined orders (#12517) readonly property bool isCustomView: cmbTokenOrder.currentValue === SortOrderComboBox.TokenOrderCustom
function symbolIsVisible(symbol) {
return d.controller.filterAcceptsSymbol(symbol)
}
readonly property var renamedModel: RolesRenamingModel { readonly property var renamedModel: RolesRenamingModel {
sourceModel: root.collectiblesModel sourceModel: root.collectiblesModel
@ -58,121 +59,162 @@ StatusScrollView {
] ]
} }
readonly property var regularCollectiblesModel: SortFilterProxyModel { readonly property bool hasCollectibles: regularCollectiblesView.count
sourceModel: d.renamedModel readonly property bool hasCommunityCollectibles: communityCollectiblesView.count
filters: [
ExpressionFilter {
expression: {
d.controller.settingsDirty
return d.symbolIsVisible(model.symbol) && !model.communityId
}
}
// TODO add other sort/filter using ManageTokensController (#12517)
]
sorters: [
RoleSorter {
roleName: "name"
enabled: !d.isCustomView
},
ExpressionSorter {
expression: {
d.controller.settingsDirty
return d.controller.lessThan(modelLeft.symbol, modelRight.symbol)
}
enabled: d.isCustomView
}
]
}
readonly property var communityCollectiblesModel: SortFilterProxyModel {
sourceModel: d.renamedModel
filters: [
ExpressionFilter {
expression: {
d.controller.settingsDirty
return d.symbolIsVisible(model.symbol) && !!model.communityId
}
}
// TODO add other sort/filter using ManageTokensController (#12517)
]
sorters: [
RoleSorter {
roleName: "name"
enabled: !d.isCustomView
},
ExpressionSorter {
expression: {
d.controller.settingsDirty
return d.controller.lessThan(modelLeft.symbol, modelRight.symbol)
}
enabled: d.isCustomView
}
]
}
readonly property bool hasCollectibles: d.regularCollectiblesModel.count
readonly property bool hasCommunityCollectibles: d.communityCollectiblesModel.count
readonly property var controller: ManageTokensController { readonly property var controller: ManageTokensController {
settingsKey: "WalletCollectibles" settingsKey: "WalletCollectibles"
} }
function hideAllCommunityTokens(communityId) { function hideAllCommunityTokens(communityId) {
const tokenSymbols = ModelUtils.getAll(communityCollectiblesModel, "symbol", "communityId", communityId) const tokenSymbols = ModelUtils.getAll(communityCollectiblesView.model, "symbol", "communityId", communityId)
d.controller.settingsHideCommunityTokens(communityId, tokenSymbols) 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)
}
}
]
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
}
ColumnLayout { ColumnLayout {
width: root.availableWidth Layout.fillWidth: true
spacing: 0 Layout.preferredHeight: root.filterVisible && (d.hasCollectibles || d.hasCommunityCollectibles) ? implicitHeight : 0
spacing: 20
opacity: Layout.preferredHeight < implicitHeight ? 0 : 1
ShapeRectangle { Behavior on Layout.preferredHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
visible: !d.hasCollectibles && !d.hasCommunityCollectibles Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
Layout.fillWidth: true
text: qsTr("Collectibles will appear here")
}
CustomGridView {
cellHeight: d.cellHeight
model: d.regularCollectiblesModel
visible: d.hasCollectibles
}
StatusDialogDivider { StatusDialogDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.current.padding
Layout.bottomMargin: Style.current.halfPadding
visible: d.hasCommunityCollectibles
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Style.current.padding spacing: Style.current.halfPadding
Layout.rightMargin: Style.current.smallPadding
Layout.bottomMargin: 4
visible: d.hasCommunityCollectibles
StatusBaseText { StatusBaseText {
text: qsTr("Community collectibles")
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
text: qsTr("Sort by:")
} }
Item { Layout.fillWidth: true }
StatusFlatButton { SortOrderComboBox {
Layout.preferredWidth: 32 id: cmbTokenOrder
Layout.preferredHeight: 32 hasCustomOrderDefined: d.controller.hasSettings
icon.name: "info" model: [
textColor: Theme.palette.baseColor1 { value: SortOrderComboBox.TokenOrderDateAdded, text: qsTr("Date added"), icon: "calendar", sortRoleName: "dateAdded" }, // FIXME sortRoleName #12942
horizontalPadding: 0 { value: SortOrderComboBox.TokenOrderAlpha, text: qsTr("Collectible name"), icon: "bold", sortRoleName: "name" },
verticalPadding: 0 { value: SortOrderComboBox.TokenOrderGroupName, text: qsTr("Collection/community name"), icon: "group", sortRoleName: "groupName" }, // Custom SFPM role communityName || collectionName
onClicked: Global.openPopup(communityInfoPopupCmp) { 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()
}
} }
} }
CustomGridView { StatusDialogDivider {
cellHeight: d.communityCellHeight Layout.fillWidth: true
model: d.communityCollectiblesModel }
visible: d.hasCommunityCollectibles }
ShapeRectangle {
Layout.fillWidth: true
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.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 }
}
} }
} }
@ -224,7 +266,7 @@ StatusScrollView {
width: d.cellWidth width: d.cellWidth
height: isCommunityCollectible ? d.communityCellHeight : d.cellHeight height: isCommunityCollectible ? d.communityCellHeight : d.cellHeight
title: model.name ? model.name : "..." title: model.name ? model.name : "..."
subTitle: model.collectionName ?? "" subTitle: model.collectionName ? model.collectionName : model.collectionUid ? model.collectionUid : ""
mediaUrl: model.mediaUrl ?? "" mediaUrl: model.mediaUrl ?? ""
mediaType: model.mediaType ?? "" mediaType: model.mediaType ?? ""
fallbackImageUrl: model.imageUrl ?? "" fallbackImageUrl: model.imageUrl ?? ""
@ -330,7 +372,7 @@ StatusScrollView {
close() close()
Global.displayToastMessage( Global.displayToastMessage(
qsTr("%1 was successfully hidden. You can toggle collectible visibility via %2.").arg(formattedName) qsTr("%1 was successfully hidden. You can toggle collectible visibility via %2.").arg(formattedName)
.arg(`<a style="text-decoration:none" href="#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}">` + qsTr("Settings", "Go to Settings") + "</a>"), .arg(`<a style="text-decoration:none" href="#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}/${Constants.walletSettingsSubsection.manageCollectibles}">` + qsTr("Settings", "Go to Settings") + "</a>"),
"", "",
"checkmark-circle", "checkmark-circle",
false, false,
@ -362,7 +404,7 @@ StatusScrollView {
close() close()
Global.displayToastMessage( Global.displayToastMessage(
qsTr("%1 community collectibles were successfully hidden. You can toggle collectible visibility via %2.").arg(communityName) qsTr("%1 community collectibles were successfully hidden. You can toggle collectible visibility via %2.").arg(communityName)
.arg(`<a style="text-decoration:none" href="#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}">` + qsTr("Settings", "Go to Settings") + "</a>"), .arg(`<a style="text-decoration:none" href="#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}/${Constants.walletSettingsSubsection.manageCollectibles}">` + qsTr("Settings", "Go to Settings") + "</a>"),
"", "",
"checkmark-circle", "checkmark-circle",
false, false,

View File

@ -1,8 +1,9 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0 import utils 1.0
import shared.controls 1.0 import shared.controls 1.0
@ -92,36 +93,46 @@ Item {
} }
} }
StatusTabBar { RowLayout {
id: walletTabBar
objectName: "rightSideWalletTabBar"
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.current.padding StatusTabBar {
id: walletTabBar
objectName: "rightSideWalletTabBar"
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
StatusTabButton { StatusTabButton {
leftPadding: 0 leftPadding: 0
width: implicitWidth width: implicitWidth
text: qsTr("Assets") text: qsTr("Assets")
}
StatusTabButton {
width: implicitWidth
text: qsTr("Collectibles")
}
StatusTabButton {
rightPadding: 0
width: implicitWidth
text: qsTr("Activity")
}
onCurrentIndexChanged: {
RootStore.setCurrentViewedHoldingType(walletTabBar.currentIndex === 1 ? Constants.TokenType.ERC721 : Constants.TokenType.ERC20)
}
} }
StatusTabButton { StatusFlatButton {
width: implicitWidth Layout.alignment: Qt.AlignTop
text: qsTr("Collectibles") id: filterButton
} icon.name: "filter"
StatusTabButton { checkable: true
rightPadding: 0 icon.color: checked ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
width: implicitWidth Behavior on icon.color { ColorAnimation { duration: 200; easing.type: Easing.InOutQuad } }
text: qsTr("Activity") highlighted: checked
}
onCurrentIndexChanged: {
RootStore.setCurrentViewedHoldingType(walletTabBar.currentIndex === 1 ? Constants.TokenType.ERC721 : Constants.TokenType.ERC20)
} }
} }
Loader { Loader {
id: mainViewLoader id: mainViewLoader
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.topMargin: Style.current.padding
Layout.bottomMargin: Style.current.padding
sourceComponent: { sourceComponent: {
switch (walletTabBar.currentIndex) { switch (walletTabBar.currentIndex) {
case 0: return assetsView case 0: return assetsView
@ -138,6 +149,7 @@ Item {
overview: RootStore.overview overview: RootStore.overview
networkConnectionStore: root.networkConnectionStore networkConnectionStore: root.networkConnectionStore
assetDetailsLaunched: stack.currentIndex === 2 assetDetailsLaunched: stack.currentIndex === 2
filterVisible: filterButton.checked
onAssetClicked: { onAssetClicked: {
assetDetailView.token = token assetDetailView.token = token
RootStore.setCurrentViewedHolding(token.symbol, Constants.TokenType.ERC20) RootStore.setCurrentViewedHolding(token.symbol, Constants.TokenType.ERC20)
@ -152,7 +164,8 @@ Item {
} }
onReceiveRequested: (symbol) => root.launchShareAddressModal() onReceiveRequested: (symbol) => root.launchShareAddressModal()
onSwitchToCommunityRequested: (communityId) => Global.switchToCommunity(communityId) onSwitchToCommunityRequested: (communityId) => Global.switchToCommunity(communityId)
onManageTokensRequested: Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.wallet) onManageTokensRequested: Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.wallet,
Constants.walletSettingsSubsection.manageAssets)
} }
} }
Component { Component {
@ -160,6 +173,7 @@ Item {
CollectiblesView { CollectiblesView {
collectiblesModel: RootStore.collectiblesStore.ownedCollectibles collectiblesModel: RootStore.collectiblesStore.ownedCollectibles
sendEnabled: root.networkConnectionStore.sendBuyBridgeEnabled && !RootStore.overview.isWatchOnlyAccount && RootStore.overview.canSend sendEnabled: root.networkConnectionStore.sendBuyBridgeEnabled && !RootStore.overview.isWatchOnlyAccount && RootStore.overview.canSend
filterVisible: filterButton.checked
onCollectibleClicked: { onCollectibleClicked: {
RootStore.collectiblesStore.getDetailedCollectible(chainId, contractAddress, tokenId) RootStore.collectiblesStore.getDetailedCollectible(chainId, contractAddress, tokenId)
RootStore.setCurrentViewedHolding(uid, Constants.TokenType.ERC721) RootStore.setCurrentViewedHolding(uid, Constants.TokenType.ERC721)
@ -174,7 +188,8 @@ Item {
} }
onReceiveRequested: (symbol) => root.launchShareAddressModal() onReceiveRequested: (symbol) => root.launchShareAddressModal()
onSwitchToCommunityRequested: (communityId) => Global.switchToCommunity(communityId) onSwitchToCommunityRequested: (communityId) => Global.switchToCommunity(communityId)
onManageTokensRequested: Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.wallet) onManageTokensRequested: Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.wallet,
Constants.walletSettingsSubsection.manageCollectibles)
} }
} }
Component { Component {

View File

@ -115,6 +115,7 @@ Item {
function onActiveSectionChanged() { function onActiveSectionChanged() {
createChatView.opened = false createChatView.opened = false
Global.settingsSubSubsection = -1
} }
function onOpenActivityCenter() { function onOpenActivityCenter() {
@ -325,13 +326,14 @@ Item {
appMain.rootStore.mainModuleInst.setNthEnabledSectionActive(nthSection) appMain.rootStore.mainModuleInst.setNthEnabledSectionActive(nthSection)
} }
function onAppSectionBySectionTypeChanged(sectionType: int, subsection: int) { function onAppSectionBySectionTypeChanged(sectionType, subsection, settingsSubsection = -1) {
if(!appMain.rootStore.mainModuleInst) if(!appMain.rootStore.mainModuleInst)
return return
appMain.rootStore.mainModuleInst.setActiveSectionBySectionType(sectionType) appMain.rootStore.mainModuleInst.setActiveSectionBySectionType(sectionType)
if (sectionType === Constants.appSection.profile) { if (sectionType === Constants.appSection.profile) {
Global.settingsSubsection = subsection; Global.settingsSubsection = subsection;
Global.settingsSubSubsection = settingsSubsection;
} }
} }
@ -750,7 +752,7 @@ Item {
active: appMain.rootStore.profileSectionStore.walletStore.areTestNetworksEnabled active: appMain.rootStore.profileSectionStore.walletStore.areTestNetworksEnabled
delay: false delay: false
onClicked: Global.openTestnetPopup() onClicked: Global.openTestnetPopup()
onCloseClicked: hide() closeBtnVisible: false
} }
ModuleWarning { ModuleWarning {
@ -1410,7 +1412,11 @@ Item {
id: sendModal id: sendModal
active: false active: false
function open() { function open(address = "") {
if (!!address) {
preSelectedRecipient = address
preSelectedRecipientType = TabAddressSelectorView.Type.Address
}
this.active = true this.active = true
this.item.open() this.item.open()
} }
@ -1626,7 +1632,8 @@ Item {
const sectionArgs = link.substring(1).split("/") const sectionArgs = link.substring(1).split("/")
const section = sectionArgs[0] const section = sectionArgs[0]
let subsection = sectionArgs.length > 1 ? sectionArgs[1] : 0 let subsection = sectionArgs.length > 1 ? sectionArgs[1] : 0
Global.changeAppSectionBySectionType(section, subsection) let subsubsection = sectionArgs.length > 2 ? sectionArgs[2] : -1
Global.changeAppSectionBySectionType(section, subsection, subsubsection)
} }
else else
Global.openLink(link) Global.openLink(link)

View File

@ -1,6 +1,7 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import Qt.labs.settings 1.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
@ -19,7 +20,9 @@ import shared.stores 1.0
import shared.controls 1.0 import shared.controls 1.0
import shared.popups 1.0 import shared.popups 1.0
StatusScrollView { import AppLayouts.Wallet.controls 1.0
ColumnLayout {
id: root id: root
// expected roles: name, symbol, enabledNetworkBalance, enabledNetworkCurrencyBalance, currencyPrice, changePct24hour, communityId, communityName, communityImage // expected roles: name, symbol, enabledNetworkBalance, enabledNetworkCurrencyBalance, currencyPrice, changePct24hour, communityId, communityName, communityImage
@ -28,6 +31,7 @@ StatusScrollView {
property var networkConnectionStore property var networkConnectionStore
property var overview property var overview
property bool assetDetailsLaunched: false property bool assetDetailsLaunched: false
property bool filterVisible
signal assetClicked(var token) signal assetClicked(var token)
signal sendRequested(string symbol) signal sendRequested(string symbol)
@ -35,15 +39,15 @@ StatusScrollView {
signal switchToCommunityRequested(string communityId) signal switchToCommunityRequested(string communityId)
signal manageTokensRequested() signal manageTokensRequested()
contentWidth: availableWidth spacing: 0
QtObject { QtObject {
id: d id: d
property int selectedAssetIndex: -1 property int selectedAssetIndex: -1
readonly property bool isCustomView: d.controller.hasSettings // TODO add respect other predefined orders (#12517) readonly property bool isCustomView: cmbTokenOrder.currentValue === SortOrderComboBox.TokenOrderCustom
function symbolIsVisible(symbol) { function tokenIsVisible(symbol, currencyBalance) {
if (symbol === "ETH") // always visible if (symbol === "ETH") // always visible
return true return true
if (!d.controller.filterAcceptsSymbol(symbol)) // explicitely hidden if (!d.controller.filterAcceptsSymbol(symbol)) // explicitely hidden
@ -51,295 +55,364 @@ StatusScrollView {
if (symbol === "SNT" || symbol === "STT" || symbol === "DAI") // visible by default if (symbol === "SNT" || symbol === "STT" || symbol === "DAI") // visible by default
return true return true
// We'll receive the tokens only with non zero balance except for Eth, Dai or SNT/STT // We'll receive the tokens only with non zero balance except for Eth, Dai or SNT/STT
return true return !!currencyBalance // TODO handle UI threshold (#12611)
} }
readonly property var regularAssetsModel: SortFilterProxyModel {
sourceModel: root.assets
filters: [
ExpressionFilter {
expression: {
d.controller.settingsDirty
return d.symbolIsVisible(model.symbol) && !model.communityId
}
}
// TODO add other sort/filter using ManageTokensController (#12517)
]
sorters: ExpressionSorter {
expression: {
d.controller.settingsDirty
return d.controller.lessThan(modelLeft.symbol, modelRight.symbol)
}
enabled: d.isCustomView
}
}
readonly property var communityAssetsModel: SortFilterProxyModel {
sourceModel: root.assets
filters: [
ExpressionFilter {
expression: {
d.controller.settingsDirty
return d.symbolIsVisible(model.symbol) && !!model.communityId
}
}
// TODO add other sort/filter using ManageTokensController (#12517)
]
sorters: ExpressionSorter {
expression: {
d.controller.settingsDirty
return d.controller.lessThan(modelLeft.symbol, modelRight.symbol)
}
enabled: d.isCustomView
}
}
readonly property bool hasCommunityAssets: d.communityAssetsModel.count
readonly property var controller: ManageTokensController { readonly property var controller: ManageTokensController {
settingsKey: "WalletAssets" settingsKey: "WalletAssets"
} }
readonly property bool hasCommunityAssets: communityAssetsLV.count
function hideAllCommunityTokens(communityId) { function hideAllCommunityTokens(communityId) {
const tokenSymbols = ModelUtils.getAll(communityAssetsModel, "symbol", "communityId", communityId) const tokenSymbols = ModelUtils.getAll(communityAssetsLV.model, "symbol", "communityId", communityId)
d.controller.settingsHideCommunityTokens(communityId, tokenSymbols) d.controller.settingsHideCommunityTokens(communityId, tokenSymbols)
} }
} }
ColumnLayout { component CustomSFPM: SortFilterProxyModel {
width: root.availableWidth id: customFilter
spacing: 0 property bool isCommunity
StatusListView { sourceModel: root.assets
Layout.fillWidth: true proxyRoles: [
Layout.preferredHeight: contentHeight ExpressionRole {
interactive: false name: "currentBalance"
objectName: "assetViewStatusListView" expression: model.enabledNetworkBalance.amount
model: d.regularAssetsModel },
delegate: delegateLoader ExpressionRole {
} name: "currentCurrencyBalance"
expression: model.enabledNetworkCurrencyBalance.amount
},
ExpressionRole {
name: "tokenPrice"
expression: model.currencyPrice.amount
}
]
filters: [
ExpressionFilter {
expression: {
d.controller.settingsDirty
return d.tokenIsVisible(model.symbol, model.currentCurrencyBalance) && (customFilter.isCommunity ? !!model.communityId : !model.communityId)
}
}
]
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: "AssetsViewSortSettings"
property alias currentSortField: cmbTokenOrder.currentIndex
property alias currentSortOrder: cmbTokenOrder.currentSortOrder
}
ColumnLayout {
Layout.fillWidth: true
Layout.preferredHeight: root.filterVisible ? implicitHeight : 0
spacing: 20
opacity: Layout.preferredHeight < implicitHeight ? 0 : 1
Behavior on Layout.preferredHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
StatusDialogDivider { StatusDialogDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.current.padding
Layout.bottomMargin: Style.current.halfPadding
visible: d.hasCommunityAssets
} }
RowLayout { RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: Style.current.padding spacing: Style.current.halfPadding
Layout.rightMargin: Style.current.smallPadding
Layout.bottomMargin: 4
visible: d.hasCommunityAssets
StatusBaseText { StatusBaseText {
text: qsTr("Community assets")
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
text: qsTr("Sort by:")
} }
Item { Layout.fillWidth: true }
StatusFlatButton { SortOrderComboBox {
Layout.preferredWidth: 32 id: cmbTokenOrder
Layout.preferredHeight: 32 hasCustomOrderDefined: d.controller.hasSettings
icon.name: "info" model: [
textColor: Theme.palette.baseColor1 { value: SortOrderComboBox.TokenOrderCurrencyBalance, text: qsTr("Asset balance value"), icon: "token-sale", sortRoleName: "currentCurrencyBalance" }, // custom SFPM ExpressionRole on "enabledNetworkCurrencyBalance" amount
horizontalPadding: 0 { value: SortOrderComboBox.TokenOrderBalance, text: qsTr("Asset balance"), icon: "channel", sortRoleName: "currentBalance" }, // custom SFPM ExpressionRole on "enabledNetworkBalance" amount
verticalPadding: 0 { value: SortOrderComboBox.TokenOrderCurrencyPrice, text: qsTr("Asset value"), icon: "token", sortRoleName: "tokenPrice" }, // custom SFPM ExpressionRole on "currencyPrice" amount
onClicked: Global.openPopup(communityInfoPopupCmp) { value: SortOrderComboBox.TokenOrder1WChange, text: qsTr("1d change: balance value"), icon: "history", sortRoleName: "changePct24hour" }, // FIXME changePct1week role missing in backend!!!
{ value: SortOrderComboBox.TokenOrderAlpha, text: qsTr("Asset name"), icon: "bold", sortRoleName: "name" },
{ 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()
}
} }
} }
StatusListView { StatusDialogDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
objectName: "communityAssetViewStatusListView"
model: d.communityAssetsModel
delegate: delegateLoader
} }
}
Component { StatusScrollView {
id: delegateLoader Layout.fillWidth: true
Loader { Layout.fillHeight: true
property var modelData: model Layout.topMargin: Style.current.padding
property int delegateIndex: index leftPadding: 0
width: ListView.view.width verticalPadding: 0
sourceComponent: model.loading ? loadingTokenDelegate: tokenDelegate contentWidth: availableWidth
ColumnLayout {
width: parent.width
spacing: 0
StatusListView {
id: regularAssetsLV
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
objectName: "assetViewStatusListView"
model: CustomSFPM {}
delegate: delegateLoader
} }
}
Component { StatusDialogDivider {
id: loadingTokenDelegate Layout.fillWidth: true
LoadingTokenDelegate { Layout.topMargin: Style.current.padding
objectName: "AssetView_LoadingTokenDelegate_" + delegateIndex Layout.bottomMargin: Style.current.halfPadding
visible: d.hasCommunityAssets
} }
}
Component { RowLayout {
id: tokenDelegate Layout.fillWidth: true
TokenDelegate { Layout.leftMargin: Style.current.padding
objectName: "AssetView_TokenListItem_" + (!!modelData ? modelData.symbol : "") Layout.rightMargin: Style.current.smallPadding
readonly property string balance: !!modelData ? "%1".arg(modelData.enabledNetworkBalance.amount) : "" // Needed for the tests Layout.bottomMargin: 4
errorTooltipText_1: !!modelData && !!networkConnectionStore ? networkConnectionStore.getBlockchainNetworkDownTextForToken(modelData.balances) : "" visible: d.hasCommunityAssets
errorTooltipText_2: !!networkConnectionStore ? networkConnectionStore.getMarketNetworkDownText() : "" StatusBaseText {
subTitle: { text: qsTr("Community assets")
if (!modelData) { color: Theme.palette.baseColor1
return ""
}
if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) {
return ""
}
return LocaleUtils.currencyAmountToLocaleString(modelData.enabledNetworkBalance)
} }
errorMode: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCache && !networkConnectionStore.noMarketConnectionAndNoCache : false Item { Layout.fillWidth: true }
errorIcon.tooltip.text: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : "" StatusFlatButton {
onClicked: (itemId, mouse) => { Layout.preferredWidth: 32
if (mouse.button === Qt.LeftButton) { Layout.preferredHeight: 32
RootStore.getHistoricalDataForToken(modelData.symbol, RootStore.currencyStore.currentCurrency) icon.name: "info"
d.selectedAssetIndex = delegateIndex textColor: Theme.palette.baseColor1
let selectedModel = !!modelData.communityId ? d.communityAssetsModel: d.regularAssetsModel horizontalPadding: 0
assetClicked(selectedModel.get(delegateIndex)) verticalPadding: 0
} else if (mouse.button === Qt.RightButton) { onClicked: Global.openPopup(communityInfoPopupCmp)
Global.openMenu(tokenContextMenu, this,
{symbol: modelData.symbol, assetName: modelData.name, assetImage: symbolUrl,
communityId: modelData.communityId, communityName: modelData.communityName, communityImage: modelData.communityImage})
}
}
onSwitchToCommunityRequested: root.switchToCommunityRequested(communityId)
Component.onCompleted: {
// on Model reset if the detail view is shown, update the data in background.
if(root.assetDetailsLaunched && delegateIndex === d.selectedAssetIndex) {
let selectedModel = !!modelData.communityId ? d.communityAssetsModel: d.regularAssetsModel
assetClicked(selectedModel.get(delegateIndex))
}
} }
} }
}
Component { StatusListView {
id: tokenContextMenu id: communityAssetsLV
StatusMenu { Layout.fillWidth: true
onClosed: destroy() Layout.preferredHeight: contentHeight
interactive: false
property string symbol objectName: "communityAssetViewStatusListView"
property string assetName model: CustomSFPM { isCommunity: true }
property string assetImage delegate: delegateLoader
property string communityId
property string communityName
property string communityImage
StatusAction {
enabled: root.networkConnectionStore.sendBuyBridgeEnabled && !root.overview.isWatchOnlyAccount && root.overview.canSend
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 asset")
onTriggered: Global.openPopup(confirmHideAssetPopup, {symbol, assetName, assetImage, communityId})
}
StatusAction {
enabled: !!communityId
type: StatusAction.Type.Danger
icon.name: "hide"
text: qsTr("Hide all assets from this community")
onTriggered: Global.openPopup(confirmHideCommunityAssetsPopup, {communityId, communityName, communityImage})
}
} }
} }
}
Component { Component {
id: communityInfoPopupCmp id: delegateLoader
StatusDialog { Loader {
destroyOnClose: true property var modelData: model
title: qsTr("What are community assets?") property int delegateIndex: index
standardButtons: Dialog.Ok width: ListView.view.width
width: 520 sourceComponent: model.loading ? loadingTokenDelegate: tokenDelegate
contentItem: StatusBaseText {
wrapMode: Text.Wrap
text: qsTr("Community assets are assets that have been minted by a community. As these assets 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 { Component {
id: confirmHideAssetPopup id: loadingTokenDelegate
ConfirmationDialog { LoadingTokenDelegate {
property string symbol objectName: "AssetView_LoadingTokenDelegate_" + delegateIndex
property string assetName
property string assetImage
property string communityId
readonly property string formattedName: assetName + (communityId ? " (" + qsTr("community asset") + ")" : "")
width: 520
destroyOnClose: true
confirmButtonLabel: qsTr("Hide %1").arg(assetName)
cancelBtnType: ""
showCancelButton: true
headerSettings.title: qsTr("Hide %1").arg(formattedName)
headerSettings.asset.name: assetImage
confirmationText: qsTr("Are you sure you want to hide %1? You will no longer see or be able to interact with this asset anywhere inside Status.").arg(formattedName)
onCancelButtonClicked: close()
onConfirmButtonClicked: {
d.controller.settingsHideToken(symbol)
close()
Global.displayToastMessage(
qsTr("%1 was successfully hidden. You can toggle asset visibility via %2.").arg(formattedName)
.arg(`<a style="text-decoration:none" href="#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}">` + qsTr("Settings", "Go to Settings") + "</a>"),
"",
"checkmark-circle",
false,
Constants.ephemeralNotificationType.success,
""
)
}
}
} }
}
Component { Component {
id: confirmHideCommunityAssetsPopup id: tokenDelegate
ConfirmationDialog { TokenDelegate {
property string communityId objectName: "AssetView_TokenListItem_" + (!!modelData ? modelData.symbol : "")
property string communityName readonly property string balance: !!modelData ? "%1".arg(modelData.enabledNetworkBalance.amount) : "" // Needed for the tests
property string communityImage errorTooltipText_1: !!modelData && !!networkConnectionStore ? networkConnectionStore.getBlockchainNetworkDownTextForToken(modelData.balances) : ""
errorTooltipText_2: !!networkConnectionStore ? networkConnectionStore.getMarketNetworkDownText() : ""
width: 520 subTitle: {
destroyOnClose: true if (!modelData) {
confirmButtonLabel: qsTr("Hide all assets minted by this community") return ""
cancelBtnType: "" }
showCancelButton: true if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) {
headerSettings.title: qsTr("Hide %1 community assets").arg(communityName) return ""
headerSettings.asset.name: communityImage }
confirmationText: qsTr("Are you sure you want to hide all community assets minted by %1? You will no longer see or be able to interact with these assets anywhere inside Status.").arg(communityName) return "%1 %2".arg(LocaleUtils.stripTrailingZeroes(LocaleUtils.numberToLocaleString(modelData.enabledNetworkBalance.amount, 6), Qt.locale()))
onCancelButtonClicked: close() .arg(modelData.enabledNetworkBalance.symbol)
onConfirmButtonClicked: { }
d.hideAllCommunityTokens(communityId) errorMode: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCache && !networkConnectionStore.noMarketConnectionAndNoCache : false
close() errorIcon.tooltip.text: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : ""
Global.displayToastMessage( onClicked: (itemId, mouse) => {
qsTr("%1 community assets were successfully hidden. You can toggle asset visibility via %2.").arg(communityName) if (mouse.button === Qt.LeftButton) {
.arg(`<a style="text-decoration:none" href="#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}">` + qsTr("Settings", "Go to Settings") + "</a>"), RootStore.getHistoricalDataForToken(modelData.symbol, RootStore.currencyStore.currentCurrency)
"", d.selectedAssetIndex = delegateIndex
"checkmark-circle", let selectedView = !!modelData.communityId ? communityAssetsLV : regularAssetsLV
false, assetClicked(selectedView.model.get(delegateIndex))
Constants.ephemeralNotificationType.success, } else if (mouse.button === Qt.RightButton) {
"" Global.openMenu(tokenContextMenu, this,
) {symbol: modelData.symbol, assetName: modelData.name, assetImage: symbolUrl,
communityId: modelData.communityId, communityName: modelData.communityName, communityImage: modelData.communityImage})
}
}
onSwitchToCommunityRequested: root.switchToCommunityRequested(communityId)
Component.onCompleted: {
// on Model reset if the detail view is shown, update the data in background.
if(root.assetDetailsLaunched && delegateIndex === d.selectedAssetIndex) {
let selectedView = !!modelData.communityId ? communityAssetsLV : regularAssetsLV
assetClicked(selectedView.model.get(delegateIndex))
} }
} }
} }
} }
Component {
id: tokenContextMenu
StatusMenu {
onClosed: destroy()
property string symbol
property string assetName
property string assetImage
property string communityId
property string communityName
property string communityImage
StatusAction {
enabled: root.networkConnectionStore.sendBuyBridgeEnabled && !root.overview.isWatchOnlyAccount && root.overview.canSend
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 asset")
onTriggered: Global.openPopup(confirmHideAssetPopup, {symbol, assetName, assetImage, communityId})
}
StatusAction {
enabled: !!communityId
type: StatusAction.Type.Danger
icon.name: "hide"
text: qsTr("Hide all assets from this community")
onTriggered: Global.openPopup(confirmHideCommunityAssetsPopup, {communityId, communityName, communityImage})
}
}
}
Component {
id: communityInfoPopupCmp
StatusDialog {
destroyOnClose: true
title: qsTr("What are community assets?")
standardButtons: Dialog.Ok
width: 520
contentItem: StatusBaseText {
wrapMode: Text.Wrap
text: qsTr("Community assets are assets that have been minted by a community. As these assets 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: confirmHideAssetPopup
ConfirmationDialog {
property string symbol
property string assetName
property string assetImage
property string communityId
readonly property string formattedName: assetName + (communityId ? " (" + qsTr("community asset") + ")" : "")
width: 520
destroyOnClose: true
confirmButtonLabel: qsTr("Hide %1").arg(assetName)
cancelBtnType: ""
showCancelButton: true
headerSettings.title: qsTr("Hide %1").arg(formattedName)
headerSettings.asset.name: assetImage
confirmationText: qsTr("Are you sure you want to hide %1? You will no longer see or be able to interact with this asset anywhere inside Status.").arg(formattedName)
onCancelButtonClicked: close()
onConfirmButtonClicked: {
d.controller.settingsHideToken(symbol)
close()
Global.displayToastMessage(
qsTr("%1 was successfully hidden. You can toggle asset visibility via %2.").arg(formattedName)
.arg(`<a style="text-decoration:none" href="#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}/${Constants.walletSettingsSubsection.manageAssets}">` + qsTr("Settings", "Go to Settings") + "</a>"),
"",
"checkmark-circle",
false,
Constants.ephemeralNotificationType.success,
""
)
}
}
}
Component {
id: confirmHideCommunityAssetsPopup
ConfirmationDialog {
property string communityId
property string communityName
property string communityImage
width: 520
destroyOnClose: true
confirmButtonLabel: qsTr("Hide all assets minted by this community")
cancelBtnType: ""
showCancelButton: true
headerSettings.title: qsTr("Hide %1 community assets").arg(communityName)
headerSettings.asset.name: communityImage
confirmationText: qsTr("Are you sure you want to hide all community assets minted by %1? You will no longer see or be able to interact with these assets anywhere inside Status.").arg(communityName)
onCancelButtonClicked: close()
onConfirmButtonClicked: {
d.hideAllCommunityTokens(communityId)
close()
Global.displayToastMessage(
qsTr("%1 community assets were successfully hidden. You can toggle asset visibility via %2.").arg(communityName)
.arg(`<a style="text-decoration:none" href="#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}/${Constants.walletSettingsSubsection.manageAssets}">` + qsTr("Settings", "Go to Settings") + "</a>"),
"",
"checkmark-circle",
false,
Constants.ephemeralNotificationType.success,
""
)
}
}
}
} }

View File

@ -352,6 +352,14 @@ QtObject {
readonly property int backUpSeed: 17 readonly property int backUpSeed: 17
} }
readonly property QtObject walletSettingsSubsection: QtObject {
readonly property int manageNetworks: 0
readonly property int manageAccounts: 1
readonly property int manageAssets: 2
readonly property int manageCollectibles: 3
readonly property int manageTokenLists: 4
}
readonly property QtObject currentUserStatus: QtObject{ readonly property QtObject currentUserStatus: QtObject{
readonly property int unknown: 0 readonly property int unknown: 0
readonly property int automatic: 1 readonly property int automatic: 1

View File

@ -9,6 +9,7 @@ QtObject {
property var applicationWindow property var applicationWindow
property bool activityPopupOpened: false property bool activityPopupOpened: false
property int settingsSubsection: Constants.settingsSubsection.profile property int settingsSubsection: Constants.settingsSubsection.profile
property int settingsSubSubsection: -1
property var userProfile property var userProfile
property bool appIsReady: false property bool appIsReady: false
@ -62,7 +63,7 @@ QtObject {
signal activateDeepLink(string link) signal activateDeepLink(string link)
signal setNthEnabledSectionActive(int nthSection) signal setNthEnabledSectionActive(int nthSection)
signal appSectionBySectionTypeChanged(int sectionType, int subsection) signal appSectionBySectionTypeChanged(int sectionType, int subsection, int settingsSubsection)
signal openSendModal(string address) signal openSendModal(string address)
signal switchToCommunity(string communityId) signal switchToCommunity(string communityId)
@ -103,8 +104,8 @@ QtObject {
root.openDownloadModalRequested(available, version, url); root.openDownloadModalRequested(available, version, url);
} }
function changeAppSectionBySectionType(sectionType, subsection = 0) { function changeAppSectionBySectionType(sectionType, subsection = 0, settingsSubsection = -1) {
root.appSectionBySectionTypeChanged(sectionType, subsection); root.appSectionBySectionTypeChanged(sectionType, subsection, settingsSubsection)
} }
function openMenu(menuComponent, menuParent, params = {}, point = undefined) { function openMenu(menuComponent, menuParent, params = {}, point = undefined) {