(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,86 +59,126 @@ 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
Behavior on Layout.preferredHeight { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
Behavior on opacity { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
StatusDialogDivider {
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
spacing: Style.current.halfPadding
StatusBaseText {
color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
text: qsTr("Sort by:")
}
SortOrderComboBox {
id: cmbTokenOrder
hasCustomOrderDefined: d.controller.hasSettings
model: [
{ value: SortOrderComboBox.TokenOrderDateAdded, text: qsTr("Date added"), icon: "calendar", sortRoleName: "dateAdded" }, // FIXME sortRoleName #12942
{ value: SortOrderComboBox.TokenOrderAlpha, text: qsTr("Collectible name"), icon: "bold", sortRoleName: "name" },
{ value: SortOrderComboBox.TokenOrderGroupName, text: qsTr("Collection/community name"), icon: "group", sortRoleName: "groupName" }, // Custom SFPM role communityName || collectionName
{ value: SortOrderComboBox.TokenOrderCustom, text: qsTr("Custom order"), icon: "exchange", sortRoleName: "" },
{ value: SortOrderComboBox.TokenOrderNone, text: "---", icon: "", sortRoleName: "" }, // separator
{ value: SortOrderComboBox.TokenOrderCreateCustom, text: hasCustomOrderDefined ? qsTr("Edit custom order →") : qsTr("Create custom order →"),
icon: "", sortRoleName: "" }
]
onCreateOrEditRequested: {
root.manageTokensRequested()
}
}
}
StatusDialogDivider {
Layout.fillWidth: true
}
}
ShapeRectangle { ShapeRectangle {
visible: !d.hasCollectibles && !d.hasCommunityCollectibles
Layout.fillWidth: true Layout.fillWidth: true
visible: !d.hasCollectibles && !d.hasCommunityCollectibles
text: qsTr("Collectibles will appear here") 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 { CustomGridView {
id: regularCollectiblesView
cellHeight: d.cellHeight cellHeight: d.cellHeight
model: d.regularCollectiblesModel model: CustomSFPM {}
visible: d.hasCollectibles
} }
StatusDialogDivider { StatusDialogDivider {
@ -170,9 +211,10 @@ StatusScrollView {
} }
CustomGridView { CustomGridView {
id: communityCollectiblesView
cellHeight: d.communityCellHeight cellHeight: d.communityCellHeight
model: d.communityCollectiblesModel model: CustomSFPM { isCommunity: true }
visible: d.hasCommunityCollectibles }
} }
} }
@ -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,6 +93,8 @@ Item {
} }
} }
RowLayout {
Layout.fillWidth: true
StatusTabBar { StatusTabBar {
id: walletTabBar id: walletTabBar
objectName: "rightSideWalletTabBar" objectName: "rightSideWalletTabBar"
@ -116,12 +119,20 @@ Item {
RootStore.setCurrentViewedHoldingType(walletTabBar.currentIndex === 1 ? Constants.TokenType.ERC721 : Constants.TokenType.ERC20) 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 { 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,71 +55,137 @@ 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)
} }
} }
component CustomSFPM: SortFilterProxyModel {
id: customFilter
property bool isCommunity
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 { ColumnLayout {
width: root.availableWidth 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
}
RowLayout {
Layout.fillWidth: true
spacing: Style.current.halfPadding
StatusBaseText {
color: Theme.palette.baseColor1
font.pixelSize: Style.current.additionalTextSize
text: qsTr("Sort by:")
}
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()
}
}
}
StatusDialogDivider {
Layout.fillWidth: true
}
}
StatusScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: Style.current.padding
leftPadding: 0
verticalPadding: 0
contentWidth: availableWidth
ColumnLayout {
width: parent.width
spacing: 0 spacing: 0
StatusListView { StatusListView {
id: regularAssetsLV
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: contentHeight Layout.preferredHeight: contentHeight
interactive: false interactive: false
objectName: "assetViewStatusListView" objectName: "assetViewStatusListView"
model: d.regularAssetsModel model: CustomSFPM {}
delegate: delegateLoader delegate: delegateLoader
} }
@ -149,13 +219,16 @@ StatusScrollView {
} }
StatusListView { StatusListView {
id: communityAssetsLV
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: contentHeight Layout.preferredHeight: contentHeight
interactive: false interactive: false
objectName: "communityAssetViewStatusListView" objectName: "communityAssetViewStatusListView"
model: d.communityAssetsModel model: CustomSFPM { isCommunity: true }
delegate: delegateLoader delegate: delegateLoader
} }
}
}
Component { Component {
id: delegateLoader id: delegateLoader
@ -188,7 +261,8 @@ StatusScrollView {
if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) { if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) {
return "" return ""
} }
return LocaleUtils.currencyAmountToLocaleString(modelData.enabledNetworkBalance) 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 errorMode: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCache && !networkConnectionStore.noMarketConnectionAndNoCache : false
errorIcon.tooltip.text: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : "" errorIcon.tooltip.text: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : ""
@ -196,8 +270,8 @@ StatusScrollView {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
RootStore.getHistoricalDataForToken(modelData.symbol, RootStore.currencyStore.currentCurrency) RootStore.getHistoricalDataForToken(modelData.symbol, RootStore.currencyStore.currentCurrency)
d.selectedAssetIndex = delegateIndex d.selectedAssetIndex = delegateIndex
let selectedModel = !!modelData.communityId ? d.communityAssetsModel: d.regularAssetsModel let selectedView = !!modelData.communityId ? communityAssetsLV : regularAssetsLV
assetClicked(selectedModel.get(delegateIndex)) assetClicked(selectedView.model.get(delegateIndex))
} else if (mouse.button === Qt.RightButton) { } else if (mouse.button === Qt.RightButton) {
Global.openMenu(tokenContextMenu, this, Global.openMenu(tokenContextMenu, this,
{symbol: modelData.symbol, assetName: modelData.name, assetImage: symbolUrl, {symbol: modelData.symbol, assetName: modelData.name, assetImage: symbolUrl,
@ -208,8 +282,8 @@ StatusScrollView {
Component.onCompleted: { Component.onCompleted: {
// on Model reset if the detail view is shown, update the data in background. // on Model reset if the detail view is shown, update the data in background.
if(root.assetDetailsLaunched && delegateIndex === d.selectedAssetIndex) { if(root.assetDetailsLaunched && delegateIndex === d.selectedAssetIndex) {
let selectedModel = !!modelData.communityId ? d.communityAssetsModel: d.regularAssetsModel let selectedView = !!modelData.communityId ? communityAssetsLV : regularAssetsLV
assetClicked(selectedModel.get(delegateIndex)) assetClicked(selectedView.model.get(delegateIndex))
} }
} }
} }
@ -299,7 +373,7 @@ StatusScrollView {
close() close()
Global.displayToastMessage( Global.displayToastMessage(
qsTr("%1 was successfully hidden. You can toggle asset visibility via %2.").arg(formattedName) 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>"), .arg(`<a style="text-decoration:none" href="#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}/${Constants.walletSettingsSubsection.manageAssets}">` + qsTr("Settings", "Go to Settings") + "</a>"),
"", "",
"checkmark-circle", "checkmark-circle",
false, false,
@ -331,7 +405,7 @@ StatusScrollView {
close() close()
Global.displayToastMessage( Global.displayToastMessage(
qsTr("%1 community assets were successfully hidden. You can toggle asset visibility via %2.").arg(communityName) 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>"), .arg(`<a style="text-decoration:none" href="#${Constants.appSection.profile}/${Constants.settingsSubsection.wallet}/${Constants.walletSettingsSubsection.manageAssets}">` + qsTr("Settings", "Go to Settings") + "</a>"),
"", "",
"checkmark-circle", "checkmark-circle",
false, false,
@ -342,4 +416,3 @@ StatusScrollView {
} }
} }
} }
}

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) {