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

View File

@ -40,6 +40,7 @@ SplitView {
SplitView.preferredWidth: 600
SplitView.fillHeight: true
collectiblesModel: ctrlEmptyModel.checked ? emptyModel : collectiblesModel
filterVisible: ctrlFilterVisible.checked
onCollectibleClicked: logs.logEvent("onCollectibleClicked", ["chainId", "contractAddress", "tokenId", "uid"], arguments)
onSendRequested: logs.logEvent("onSendRequested", ["symbol"], arguments)
onReceiveRequested: logs.logEvent("onReceiveRequested", ["symbol"], arguments)
@ -56,6 +57,11 @@ SplitView {
logsView.logText: logs.logText
ColumnLayout {
Switch {
id: ctrlFilterVisible
text: "Filter visible"
checked: true
}
Switch {
id: ctrlEmptyModel
text: "Empty model"
@ -67,3 +73,4 @@ SplitView {
// 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-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")
}
Label {
Layout.fillWidth: true
text: "Has saved settings: %1".arg(showcasePanel.hasSettings ? "true" : "false")
}
Button {
text: "Save"
onClicked: showcasePanel.saveSettings()
@ -67,6 +72,7 @@ SplitView {
}
Button {
enabled: showcasePanel.hasSettings
text: "Clear settings"
onClicked: showcasePanel.clearSettings()
}

View File

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

View File

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

View File

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

View File

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

View File

@ -164,12 +164,15 @@ void ManageTokensController::clearSettings()
m_settings.remove(QString());
m_settings.endGroup();
m_settings.sync();
emit settingsDirtyChanged(false);
}
void ManageTokensController::loadSettings()
{
Q_ASSERT(!m_settingsKey.isEmpty());
setSettingsDirty(true);
m_settingsData.clear();
// load from QSettings
@ -189,6 +192,7 @@ void ManageTokensController::loadSettings()
}
m_settings.endArray();
m_settings.endGroup();
setSettingsDirty(false);
}
void ManageTokensController::setSettingsDirty(bool dirty)
@ -212,7 +216,7 @@ bool ManageTokensController::hasSettings() const
{
Q_ASSERT(!m_settingsKey.isEmpty());
const auto groups = m_settings.childGroups();
return !groups.isEmpty() && groups.contains(settingsGroupName());
return groups.contains(settingsGroupName());
}
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* hiddenTokensModel READ hiddenTokensModel CONSTANT 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)
public:
@ -38,7 +39,6 @@ public:
Q_INVOKABLE void saveSettings(bool reuseCurrent = false);
Q_INVOKABLE void clearSettings();
Q_INVOKABLE void revert();
Q_INVOKABLE bool hasSettings() const;
Q_INVOKABLE void settingsHideToken(const QString& symbol);
Q_INVOKABLE void settingsHideCommunityTokens(const QString& communityId, const QStringList& symbols);
@ -95,6 +95,7 @@ private:
void setSettingsKey(const QString& newSettingsKey);
QSettings m_settings;
SerializedTokenData m_settingsData; // symbol -> {sortOrder, visible, groupId}
bool hasSettings() const;
bool m_settingsDirty{false};
bool settingsDirty() const { return m_settingsDirty; }

View File

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

View File

@ -2,6 +2,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13
import QtQml 2.15
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
@ -88,6 +89,14 @@ SettingsContentBase {
accountView.height
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: {
root.rootStore.backButtonName = ""
root.sectionTitle = root.walletSectionTitle
@ -257,6 +266,23 @@ SettingsContentBase {
// TODO concat proxy model to include community collectibles (#12519)
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 {

View File

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

View File

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

View File

@ -13,79 +13,71 @@ import utils 1.0
ComboBox {
id: root
property int sortOrder: Qt.DescendingOrder
readonly property string currentSortRoleName: d.currentSortRoleName
// expected model role names: text, value (enum TokenOrder), sortRoleName, icon (optional)
// 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"
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 {
TokenOrderNone = 0,
TokenOrderCustom,
TokenOrderValue,
TokenOrderBalance,
TokenOrder1WChange,
TokenOrderAlpha
TokenOrderCurrencyBalance, // FIAT value of asset balance (enabledNetworkCurrencyBalance)
TokenOrderBalance, // Number of tokens (enabledNetworkBalance)
TokenOrderCurrencyPrice, // Value per token in FIAT (currencyPrice)
TokenOrder1WChange, // Level of change in asset balance value (in FIAT) comp. to 7 days earlier
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
verticalPadding: 8
spacing: 8
verticalPadding: Style.current.halfPadding
spacing: Style.current.halfPadding
font.family: Theme.palette.baseFont.name
font.pixelSize: Style.current.additionalTextSize
QtObject {
id: d
readonly property int defaultDelegateHeight: 34
// // models
// 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
property int currentIndex: 0
}
background: Rectangle {
border.width: 1
border.color: Theme.palette.directColor7
radius: 8
radius: Style.current.radius
color: root.down ? Theme.palette.baseColor2 : "transparent"
HoverHandler {
cursorShape: root.enabled ? Qt.PointingHandCursor : undefined
@ -116,16 +108,15 @@ ComboBox {
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
y: root.height + 4
implicitWidth: root.width
margins: 8
implicitWidth: 290
margins: Style.current.halfPadding
padding: 1
verticalPadding: 8
verticalPadding: Style.current.halfPadding
background: Rectangle {
color: Theme.palette.statusSelect.menuItemBackgroundColor
radius: 8
border.color: Theme.palette.baseColor2
radius: Style.current.radius
layer.enabled: true
layer.effect: DropShadow {
horizontalOffset: 0
@ -138,6 +129,7 @@ ComboBox {
}
contentItem: ColumnLayout {
spacing: 0
StatusBaseText {
Layout.fillWidth: true
Layout.preferredHeight: d.defaultDelegateHeight
@ -164,20 +156,18 @@ ComboBox {
spacing: root.spacing
StatusIcon {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
visible: !!icon
icon: iconName
color: root.enabled ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
width: 16
height: 16
}
StatusBaseText {
Layout.fillWidth: true
Layout.fillHeight: true
text: menuText
verticalAlignment: Text.AlignVCenter
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.weight: root.currentIndex === menuIndex ? Font.DemiBold : Font.Normal
}
@ -185,7 +175,7 @@ ComboBox {
Item { Layout.fillWidth: true }
Row {
visible: !isCustomOrder
visible: showUpDownArrows
spacing: 4
StatusFlatRoundButton {
radius: 6
@ -195,11 +185,12 @@ ComboBox {
icon.width: 18
icon.height: 18
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: {
if (root.currentIndex !== menuIndex)
root.currentIndex = menuIndex
root.sortOrder = Qt.AscendingOrder
d.currentIndex = menuIndex
root.currentSortOrder = Qt.AscendingOrder
root.popup.close()
}
}
@ -211,11 +202,12 @@ ComboBox {
icon.width: 18
icon.height: 18
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: {
if (root.currentIndex !== menuIndex)
root.currentIndex = menuIndex
root.sortOrder = Qt.DescendingOrder
d.currentIndex = menuIndex
root.currentSortOrder = Qt.DescendingOrder
root.popup.close()
}
}
@ -234,9 +226,15 @@ ComboBox {
readonly property bool isSeparator: text === "---"
id: menuDelegate
width: root.width
width: ListView.view.width
highlighted: root.highlightedIndex === index
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
rightPadding: isSeparator ? 0 : 8
verticalPadding: isSeparator ? 2 : 5
@ -264,9 +262,9 @@ ComboBox {
readonly property int menuIndex: menuDelegate.index
readonly property string menuText: menuDelegate.text
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
}
onClicked: root.currentIndex = index
}
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
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
@ -19,14 +20,16 @@ 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
StatusScrollView {
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)
@ -34,6 +37,8 @@ StatusScrollView {
signal switchToCommunityRequested(string communityId)
signal manageTokensRequested()
spacing: 0
QtObject {
id: d
@ -41,11 +46,7 @@ StatusScrollView {
readonly property int communityCellHeight: 242
readonly property int cellWidth: 176
readonly property bool isCustomView: d.controller.hasSettings // TODO add respect other predefined orders (#12517)
function symbolIsVisible(symbol) {
return d.controller.filterAcceptsSymbol(symbol)
}
readonly property bool isCustomView: cmbTokenOrder.currentValue === SortOrderComboBox.TokenOrderCustom
readonly property var renamedModel: RolesRenamingModel {
sourceModel: root.collectiblesModel
@ -58,121 +59,162 @@ StatusScrollView {
]
}
readonly property var regularCollectiblesModel: 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 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 bool hasCollectibles: regularCollectiblesView.count
readonly property bool hasCommunityCollectibles: communityCollectiblesView.count
readonly property var controller: ManageTokensController {
settingsKey: "WalletCollectibles"
}
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)
}
}
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 {
width: root.availableWidth
spacing: 0
Layout.fillWidth: true
Layout.preferredHeight: root.filterVisible && (d.hasCollectibles || d.hasCommunityCollectibles) ? implicitHeight : 0
spacing: 20
opacity: Layout.preferredHeight < implicitHeight ? 0 : 1
ShapeRectangle {
visible: !d.hasCollectibles && !d.hasCommunityCollectibles
Layout.fillWidth: true
text: qsTr("Collectibles will appear here")
}
CustomGridView {
cellHeight: d.cellHeight
model: d.regularCollectiblesModel
visible: d.hasCollectibles
}
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
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
spacing: Style.current.halfPadding
StatusBaseText {
text: qsTr("Community collectibles")
color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
text: qsTr("Sort by:")
}
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)
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()
}
}
}
CustomGridView {
cellHeight: d.communityCellHeight
model: d.communityCollectiblesModel
visible: d.hasCommunityCollectibles
StatusDialogDivider {
Layout.fillWidth: true
}
}
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
height: isCommunityCollectible ? d.communityCellHeight : d.cellHeight
title: model.name ? model.name : "..."
subTitle: model.collectionName ?? ""
subTitle: model.collectionName ? model.collectionName : model.collectionUid ? model.collectionUid : ""
mediaUrl: model.mediaUrl ?? ""
mediaType: model.mediaType ?? ""
fallbackImageUrl: model.imageUrl ?? ""
@ -330,7 +372,7 @@ StatusScrollView {
close()
Global.displayToastMessage(
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",
false,
@ -362,7 +404,7 @@ StatusScrollView {
close()
Global.displayToastMessage(
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",
false,

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt.labs.settings 1.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
@ -19,7 +20,9 @@ import shared.stores 1.0
import shared.controls 1.0
import shared.popups 1.0
StatusScrollView {
import AppLayouts.Wallet.controls 1.0
ColumnLayout {
id: root
// expected roles: name, symbol, enabledNetworkBalance, enabledNetworkCurrencyBalance, currencyPrice, changePct24hour, communityId, communityName, communityImage
@ -28,6 +31,7 @@ StatusScrollView {
property var networkConnectionStore
property var overview
property bool assetDetailsLaunched: false
property bool filterVisible
signal assetClicked(var token)
signal sendRequested(string symbol)
@ -35,15 +39,15 @@ StatusScrollView {
signal switchToCommunityRequested(string communityId)
signal manageTokensRequested()
contentWidth: availableWidth
spacing: 0
QtObject {
id: d
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
return true
if (!d.controller.filterAcceptsSymbol(symbol)) // explicitely hidden
@ -51,295 +55,364 @@ StatusScrollView {
if (symbol === "SNT" || symbol === "STT" || symbol === "DAI") // visible by default
return true
// 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 {
settingsKey: "WalletAssets"
}
readonly property bool hasCommunityAssets: communityAssetsLV.count
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)
}
}
ColumnLayout {
width: root.availableWidth
spacing: 0
component CustomSFPM: SortFilterProxyModel {
id: customFilter
property bool isCommunity
StatusListView {
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
objectName: "assetViewStatusListView"
model: d.regularAssetsModel
delegate: delegateLoader
}
sourceModel: root.assets
proxyRoles: [
ExpressionRole {
name: "currentBalance"
expression: model.enabledNetworkBalance.amount
},
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 {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
Layout.bottomMargin: Style.current.halfPadding
visible: d.hasCommunityAssets
}
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.smallPadding
Layout.bottomMargin: 4
visible: d.hasCommunityAssets
spacing: Style.current.halfPadding
StatusBaseText {
text: qsTr("Community assets")
color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
text: qsTr("Sort by:")
}
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)
SortOrderComboBox {
id: cmbTokenOrder
hasCustomOrderDefined: d.controller.hasSettings
model: [
{ value: SortOrderComboBox.TokenOrderCurrencyBalance, text: qsTr("Asset balance value"), icon: "token-sale", sortRoleName: "currentCurrencyBalance" }, // custom SFPM ExpressionRole on "enabledNetworkCurrencyBalance" amount
{ value: SortOrderComboBox.TokenOrderBalance, text: qsTr("Asset balance"), icon: "channel", sortRoleName: "currentBalance" }, // custom SFPM ExpressionRole on "enabledNetworkBalance" amount
{ value: SortOrderComboBox.TokenOrderCurrencyPrice, text: qsTr("Asset value"), icon: "token", sortRoleName: "tokenPrice" }, // custom SFPM ExpressionRole on "currencyPrice" amount
{ 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.preferredHeight: contentHeight
interactive: false
objectName: "communityAssetViewStatusListView"
model: d.communityAssetsModel
delegate: delegateLoader
}
}
Component {
id: delegateLoader
Loader {
property var modelData: model
property int delegateIndex: index
width: ListView.view.width
sourceComponent: model.loading ? loadingTokenDelegate: tokenDelegate
StatusScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Style.current.padding
leftPadding: 0
verticalPadding: 0
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 {
id: loadingTokenDelegate
LoadingTokenDelegate {
objectName: "AssetView_LoadingTokenDelegate_" + delegateIndex
StatusDialogDivider {
Layout.fillWidth: true
Layout.topMargin: Style.current.padding
Layout.bottomMargin: Style.current.halfPadding
visible: d.hasCommunityAssets
}
}
Component {
id: tokenDelegate
TokenDelegate {
objectName: "AssetView_TokenListItem_" + (!!modelData ? modelData.symbol : "")
readonly property string balance: !!modelData ? "%1".arg(modelData.enabledNetworkBalance.amount) : "" // Needed for the tests
errorTooltipText_1: !!modelData && !!networkConnectionStore ? networkConnectionStore.getBlockchainNetworkDownTextForToken(modelData.balances) : ""
errorTooltipText_2: !!networkConnectionStore ? networkConnectionStore.getMarketNetworkDownText() : ""
subTitle: {
if (!modelData) {
return ""
}
if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) {
return ""
}
return LocaleUtils.currencyAmountToLocaleString(modelData.enabledNetworkBalance)
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.smallPadding
Layout.bottomMargin: 4
visible: d.hasCommunityAssets
StatusBaseText {
text: qsTr("Community assets")
color: Theme.palette.baseColor1
}
errorMode: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCache && !networkConnectionStore.noMarketConnectionAndNoCache : false
errorIcon.tooltip.text: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : ""
onClicked: (itemId, mouse) => {
if (mouse.button === Qt.LeftButton) {
RootStore.getHistoricalDataForToken(modelData.symbol, RootStore.currencyStore.currentCurrency)
d.selectedAssetIndex = delegateIndex
let selectedModel = !!modelData.communityId ? d.communityAssetsModel: d.regularAssetsModel
assetClicked(selectedModel.get(delegateIndex))
} 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 selectedModel = !!modelData.communityId ? d.communityAssetsModel: d.regularAssetsModel
assetClicked(selectedModel.get(delegateIndex))
}
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)
}
}
}
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})
}
StatusListView {
id: communityAssetsLV
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
interactive: false
objectName: "communityAssetViewStatusListView"
model: CustomSFPM { isCommunity: true }
delegate: delegateLoader
}
}
}
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: delegateLoader
Loader {
property var modelData: model
property int delegateIndex: index
width: ListView.view.width
sourceComponent: model.loading ? loadingTokenDelegate: tokenDelegate
}
}
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}">` + qsTr("Settings", "Go to Settings") + "</a>"),
"",
"checkmark-circle",
false,
Constants.ephemeralNotificationType.success,
""
)
}
}
Component {
id: loadingTokenDelegate
LoadingTokenDelegate {
objectName: "AssetView_LoadingTokenDelegate_" + delegateIndex
}
}
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}">` + qsTr("Settings", "Go to Settings") + "</a>"),
"",
"checkmark-circle",
false,
Constants.ephemeralNotificationType.success,
""
)
Component {
id: tokenDelegate
TokenDelegate {
objectName: "AssetView_TokenListItem_" + (!!modelData ? modelData.symbol : "")
readonly property string balance: !!modelData ? "%1".arg(modelData.enabledNetworkBalance.amount) : "" // Needed for the tests
errorTooltipText_1: !!modelData && !!networkConnectionStore ? networkConnectionStore.getBlockchainNetworkDownTextForToken(modelData.balances) : ""
errorTooltipText_2: !!networkConnectionStore ? networkConnectionStore.getMarketNetworkDownText() : ""
subTitle: {
if (!modelData) {
return ""
}
if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) {
return ""
}
return "%1 %2".arg(LocaleUtils.stripTrailingZeroes(LocaleUtils.numberToLocaleString(modelData.enabledNetworkBalance.amount, 6), Qt.locale()))
.arg(modelData.enabledNetworkBalance.symbol)
}
errorMode: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCache && !networkConnectionStore.noMarketConnectionAndNoCache : false
errorIcon.tooltip.text: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : ""
onClicked: (itemId, mouse) => {
if (mouse.button === Qt.LeftButton) {
RootStore.getHistoricalDataForToken(modelData.symbol, RootStore.currencyStore.currentCurrency)
d.selectedAssetIndex = delegateIndex
let selectedView = !!modelData.communityId ? communityAssetsLV : regularAssetsLV
assetClicked(selectedView.model.get(delegateIndex))
} 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 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 int unknown: 0
readonly property int automatic: 1

View File

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