feat(token mgmt): update main wallet view layout with community assets

- update the AssetsView.qml view and delegates according to the latest
design
- add AssetsView to Storybook
- add new section for Community Assets
- (re)use the community badge with tooltip and link action to take the
user to the respective community
- add Community Assets info icon + popup
- create context menu for token delegates with actions
(Send/Receive/Manage tokens/Hide)
- add confirmation popups when hiding a single or all community tokens
- emit a toast bubble after hiding the token(s)
- plus related controller/backend methods for handling the
settings-related actions
- some smaller fixes/cleanups

Fixes #12369
Fixes #12372
This commit is contained in:
Lukáš Tinkl 2023-11-22 20:58:02 +01:00 committed by Lukáš Tinkl
parent 8e0db2e666
commit 3b60506460
17 changed files with 624 additions and 147 deletions

View File

@ -0,0 +1,56 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import StatusQ.Core 0.1
import mainui 1.0
import utils 1.0
import shared.views 1.0
import Storybook 1.0
import Models 1.0
SplitView {
id: root
Logs { id: logs }
orientation: Qt.Horizontal
ManageTokensModel {
id: assetsModel
}
Popups {
popupParent: root
rootStore: QtObject {}
communityTokensStore: QtObject {}
}
AssetsView {
id: assetsView
SplitView.preferredWidth: 600
SplitView.fillHeight: true
assets: assetsModel
onAssetClicked: logs.logEvent("onAssetClicked", ["token"], [token.symbol, token.communityId])
onSendRequested: logs.logEvent("onSendRequested", ["symbol"], arguments)
onReceiveRequested: logs.logEvent("onReceiveRequested", ["symbol"], arguments)
onSwitchToCommunityRequested: logs.logEvent("onSwitchToCommunityRequested", ["communityId"], arguments)
onManageTokensRequested: logs.logEvent("onManageTokensRequested")
}
LogsAndControlsPanel {
id: logsAndControlsPanel
SplitView.minimumWidth: 150
SplitView.preferredWidth: 250
logsView.logText: logs.logText
}
}
// category: Views
// https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?type=design&node-id=17159-67977&mode=design&t=s5EXsh6Vi4nTNYUh-0
// https://www.figma.com/file/FkFClTCYKf83RJWoifWgoX/Wallet-v2?type=design&node-id=17171-285559&mode=design&t=s5EXsh6Vi4nTNYUh-0

View File

@ -44,6 +44,11 @@ ListModel {
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
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
@ -166,6 +171,11 @@ ListModel {
symbol: "EUR", symbol: "EUR",
displayDecimals: 2 displayDecimals: 2
}, },
currencyPrice: {
amount: 1480.113406237,
symbol: "EUR",
displayDecimals: 2
},
changePct24hour: -3.51, changePct24hour: -3.51,
communityId: "", communityId: "",
communityName: "", communityName: "",
@ -185,15 +195,21 @@ ListModel {
enabledNetworkBalance: ({ enabledNetworkBalance: ({
displayDecimals: true, displayDecimals: true,
stripTrailingZeroes: true, stripTrailingZeroes: true,
amount: 324343.3, amount: 0,
symbol: "SNT" symbol: "SNT"
}), }),
enabledNetworkCurrencyBalance: ({ enabledNetworkCurrencyBalance: ({
displayDecimals: 4, displayDecimals: 4,
stripTrailingZeroes: true, stripTrailingZeroes: true,
amount: 2.333321323400, amount: 0,
symbol: "EUR" symbol: "EUR"
}), }),
currencyPrice: {
amount: 1.40627,
symbol: "EUR",
displayDecimals: 2
},
changePct24hour: 1.3,
symbol: "SNT", symbol: "SNT",
name: "Status", name: "Status",
communityId: "", communityId: "",

View File

@ -2,4 +2,5 @@ import QtQuick 2.15
QtObject { QtObject {
property var blockchainNetworksDown: [] property var blockchainNetworksDown: []
property bool sendBuyBridgeEnabled: true
} }

View File

@ -11,7 +11,7 @@ QtObject {
property var currentCurrency property var currentCurrency
property bool neverAskAboutUnfurlingAgain: false property bool neverAskAboutUnfurlingAgain: false
property var currencyStore property var currencyStore: CurrenciesStore {}
property var history property var history
property var getNetworkIcon property var getNetworkIcon
@ -37,4 +37,8 @@ QtObject {
console.log("STUB: setNeverAskAboutUnfurlingAgain:", value) console.log("STUB: setNeverAskAboutUnfurlingAgain:", value)
neverAskAboutUnfurlingAgain = value neverAskAboutUnfurlingAgain = value
} }
function getHistoricalDataForToken(symbol, currency) {
console.log("STUB: getHistoricalDataForToken:", symbol, currency)
}
} }

View File

@ -27,6 +27,11 @@ public:
Q_INVOKABLE QVariant get(QAbstractItemModel *model, int row, Q_INVOKABLE QVariant get(QAbstractItemModel *model, int row,
const QString &roleName) const; const QString &roleName) const;
Q_INVOKABLE QVariantList getAll(QAbstractItemModel* model,
const QString& roleName,
const QString& filterRoleName,
const QVariant& filterValue) const;
Q_INVOKABLE bool contains(QAbstractItemModel *model, const QString &roleName, const QVariant &value, int mode = Qt::CaseSensitive) const; Q_INVOKABLE bool contains(QAbstractItemModel *model, const QString &roleName, const QVariant &value, int mode = Qt::CaseSensitive) const;
///< performs a strict check whether @lhs and @rhs arrays (QList<T>) contain the same elements; ///< performs a strict check whether @lhs and @rhs arrays (QList<T>) contain the same elements;

View File

@ -116,6 +116,7 @@ Row {
id: textLayout id: textLayout
width: !iconOrImage.active ? parent.width : width: !iconOrImage.active ? parent.width :
parent.width - iconOrImage.width - parent.spacing parent.width - iconOrImage.width - parent.spacing
anchors.verticalCenter: parent.verticalCenter
Row { Row {
id: headerTitleRow id: headerTitleRow
width: parent.width width: parent.width

View File

@ -60,6 +60,32 @@ QVariant ModelUtilsInternal::get(QAbstractItemModel *model,
return {}; return {};
} }
QVariantList ModelUtilsInternal::getAll(QAbstractItemModel* model,
const QString& roleName,
const QString& filterRoleName,
const QVariant& filterValue) const
{
if (!model || filterValue.isNull())
return {};
const auto role = roleByName(model, roleName);
if (role == -1)
return {};
const auto filterRole = roleByName(model, filterRoleName);
if (filterRole == -1)
return {};
QVariantList result;
const auto size = model->rowCount();
for (auto i = 0; i < size; i++) {
const auto srcIndex = model->index(i, 0);
if (srcIndex.data(filterRole) == filterValue)
result.append(srcIndex.data(role));
}
return result;
}
bool ModelUtilsInternal::contains(QAbstractItemModel* model, bool ModelUtilsInternal::contains(QAbstractItemModel* model,
const QString& roleName, const QString& roleName,
const QVariant& value, const QVariant& value,

View File

@ -110,7 +110,7 @@ QHash<int, QByteArray> RolesRenamingModel::roleNames() const
return {}; return {};
} }
if (renameMap.size()) { if (!renameMap.isEmpty()) {
qWarning().nospace() qWarning().nospace()
<< "RolesRenamingModel: specified source roles not found: " << "RolesRenamingModel: specified source roles not found: "
<< renameMap.keys() << "!"; << renameMap.keys() << "!";

View File

@ -1,6 +1,7 @@
#include "managetokenscontroller.h" #include "managetokenscontroller.h"
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QMutableHashIterator>
ManageTokensController::ManageTokensController(QObject* parent) ManageTokensController::ManageTokensController(QObject* parent)
: QObject(parent) : QObject(parent)
@ -112,18 +113,24 @@ void ManageTokensController::showHideGroup(const QString& groupId, bool flag)
rebuildCommunityTokenGroupsModel(); rebuildCommunityTokenGroupsModel();
} }
void ManageTokensController::saveSettings() void ManageTokensController::saveSettings(bool reuseCurrent)
{ {
Q_ASSERT(!m_settingsKey.isEmpty()); Q_ASSERT(!m_settingsKey.isEmpty());
setSettingsDirty(true);
if (m_arrangeByCommunity) if (m_arrangeByCommunity)
m_communityTokensModel->applySort(); m_communityTokensModel->applySort();
// gather the data to save // gather the data to save
SerializedTokenData result; SerializedTokenData result;
for (auto model: {m_regularTokensModel, m_communityTokensModel}) if (reuseCurrent) {
result.insert(model->save()); result = m_settingsData;
result.insert(m_hiddenTokensModel->save(false)); } else {
for(auto model : {m_regularTokensModel, m_communityTokensModel})
result.insert(model->save());
result.insert(m_hiddenTokensModel->save(false));
}
// save to QSettings // save to QSettings
m_settings.beginGroup(settingsGroupName()); m_settings.beginGroup(settingsGroupName());
@ -144,6 +151,8 @@ void ManageTokensController::saveSettings()
// unset dirty // unset dirty
for (auto model: m_allModels) for (auto model: m_allModels)
model->setDirty(false); model->setDirty(false);
setSettingsDirty(false);
} }
void ManageTokensController::clearSettings() void ManageTokensController::clearSettings()
@ -182,6 +191,13 @@ void ManageTokensController::loadSettings()
m_settings.endGroup(); m_settings.endGroup();
} }
void ManageTokensController::setSettingsDirty(bool dirty)
{
if (m_settingsDirty == dirty) return;
m_settingsDirty = dirty;
emit settingsDirtyChanged(m_settingsDirty);
}
void ManageTokensController::revert() void ManageTokensController::revert()
{ {
parseSourceModel(); parseSourceModel();
@ -199,6 +215,40 @@ bool ManageTokensController::hasSettings() const
return !groups.isEmpty() && groups.contains(settingsGroupName()); return !groups.isEmpty() && groups.contains(settingsGroupName());
} }
void ManageTokensController::settingsHideToken(const QString& symbol)
{
if (m_settingsData.contains(symbol)) { // find or create the settings entry
auto [pos, visible, group] = m_settingsData.value(symbol);
m_settingsData.remove(symbol); // remove all
m_settingsData.insert(symbol, {pos, false, group});
} else {
m_settingsData.insert(symbol, {0, false, QString()});
}
saveSettings(true);
}
void ManageTokensController::settingsHideCommunityTokens(const QString& communityId, const QStringList& symbols)
{
QMutableHashIterator<QString, std::tuple<int, bool, QString>> i(m_settingsData);
bool found = false;
while (i.hasNext()) {
i.next();
const auto groupID = std::get<2>(i.value());
if (groupID == communityId) {
found = true;
i.setValue({0, false, communityId});
}
}
if (!found) {
for (const auto& symbol: symbols)
m_settingsData.insert(symbol, {0, false, communityId});
}
saveSettings(true);
}
bool ManageTokensController::lessThan(const QString& lhsSymbol, const QString& rhsSymbol) const bool ManageTokensController::lessThan(const QString& lhsSymbol, const QString& rhsSymbol) const
{ {
int leftPos, rightPos; int leftPos, rightPos;
@ -216,7 +266,7 @@ bool ManageTokensController::lessThan(const QString& lhsSymbol, const QString& r
bool ManageTokensController::filterAcceptsSymbol(const QString& symbol) const bool ManageTokensController::filterAcceptsSymbol(const QString& symbol) const
{ {
const auto& [pos, visible, groupId] = m_settingsData.value(symbol, {INT_MAX, false, QString()}); const auto& [pos, visible, groupId] = m_settingsData.value(symbol, {INT_MAX, true, QString()});
return visible; return visible;
} }

View File

@ -14,9 +14,9 @@ class ManageTokensController : public QObject, public QQmlParserStatus
Q_INTERFACES(QQmlParserStatus) Q_INTERFACES(QQmlParserStatus)
// input properties // input properties
Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged FINAL REQUIRED) Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged FINAL)
Q_PROPERTY(QString settingsKey READ settingsKey WRITE setSettingsKey NOTIFY settingsKeyChanged FINAL REQUIRED) Q_PROPERTY(QString settingsKey READ settingsKey WRITE setSettingsKey NOTIFY settingsKeyChanged FINAL REQUIRED)
Q_PROPERTY(bool arrangeByCommunity READ arrangeByCommunity WRITE setArrangeByCommunity NOTIFY arrangeByCommunityChanged FINAL) Q_PROPERTY(bool arrangeByCommunity READ arrangeByCommunity WRITE setArrangeByCommunity NOTIFY arrangeByCommunityChanged FINAL) // TODO persist in settings
// output properties // output properties
Q_PROPERTY(QAbstractItemModel* regularTokensModel READ regularTokensModel CONSTANT FINAL) Q_PROPERTY(QAbstractItemModel* regularTokensModel READ regularTokensModel CONSTANT FINAL)
@ -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 settingsDirty READ settingsDirty NOTIFY settingsDirtyChanged FINAL)
public: public:
explicit ManageTokensController(QObject* parent = nullptr); explicit ManageTokensController(QObject* parent = nullptr);
@ -33,11 +34,15 @@ public:
Q_INVOKABLE void showHideCommunityToken(int row, bool flag); Q_INVOKABLE void showHideCommunityToken(int row, bool flag);
Q_INVOKABLE void showHideGroup(const QString& groupId, bool flag); Q_INVOKABLE void showHideGroup(const QString& groupId, bool flag);
Q_INVOKABLE void saveSettings(); Q_INVOKABLE void loadSettings();
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 bool hasSettings() const;
Q_INVOKABLE void settingsHideToken(const QString& symbol);
Q_INVOKABLE void settingsHideCommunityTokens(const QString& communityId, const QStringList& symbols);
Q_INVOKABLE bool lessThan(const QString& lhsSymbol, const QString& rhsSymbol) const; Q_INVOKABLE bool lessThan(const QString& lhsSymbol, const QString& rhsSymbol) const;
Q_INVOKABLE bool filterAcceptsSymbol(const QString& symbol) const; Q_INVOKABLE bool filterAcceptsSymbol(const QString& symbol) const;
@ -50,6 +55,7 @@ signals:
void dirtyChanged(); void dirtyChanged();
void arrangeByCommunityChanged(); void arrangeByCommunityChanged();
void settingsKeyChanged(); void settingsKeyChanged();
void settingsDirtyChanged(bool dirty);
private: private:
QAbstractItemModel* m_sourceModel{nullptr}; QAbstractItemModel* m_sourceModel{nullptr};
@ -60,16 +66,16 @@ private:
void addItem(int index); void addItem(int index);
ManageTokensModel* m_regularTokensModel{nullptr}; ManageTokensModel* m_regularTokensModel{nullptr};
QAbstractItemModel* regularTokensModel() const { return m_regularTokensModel; }; QAbstractItemModel* regularTokensModel() const { return m_regularTokensModel; }
ManageTokensModel* m_communityTokensModel{nullptr}; ManageTokensModel* m_communityTokensModel{nullptr};
QAbstractItemModel* communityTokensModel() const { return m_communityTokensModel; }; QAbstractItemModel* communityTokensModel() const { return m_communityTokensModel; }
ManageTokensModel* m_communityTokenGroupsModel{nullptr}; ManageTokensModel* m_communityTokenGroupsModel{nullptr};
QAbstractItemModel* communityTokenGroupsModel() const { return m_communityTokenGroupsModel; }; QAbstractItemModel* communityTokenGroupsModel() const { return m_communityTokenGroupsModel; }
ManageTokensModel* m_hiddenTokensModel{nullptr}; ManageTokensModel* m_hiddenTokensModel{nullptr};
QAbstractItemModel* hiddenTokensModel() const { return m_hiddenTokensModel; }; QAbstractItemModel* hiddenTokensModel() const { return m_hiddenTokensModel; }
bool dirty() const; bool dirty() const;
@ -88,8 +94,11 @@ private:
QString settingsGroupName() const; QString settingsGroupName() const;
void setSettingsKey(const QString& newSettingsKey); void setSettingsKey(const QString& newSettingsKey);
QSettings m_settings; QSettings m_settings;
void loadSettings();
SerializedTokenData m_settingsData; // symbol -> {sortOrder, visible, groupId} SerializedTokenData m_settingsData; // symbol -> {sortOrder, visible, groupId}
bool m_settingsDirty{false};
bool settingsDirty() const { return m_settingsDirty; }
void setSettingsDirty(bool dirty);
bool m_modelConnectionsInitialized{false}; bool m_modelConnectionsInitialized{false};
}; };

View File

@ -63,7 +63,7 @@ SettingsContentBase {
onSaveChangesClicked: { onSaveChangesClicked: {
manageTokensView.saveChanges() manageTokensView.saveChanges()
Global.displayToastMessage( Global.displayToastMessage(
qsTr("Your new custom asset order has been applied to your %1", "Go to Wallet") qsTr("Your new custom token order has been applied to your %1", "Go to Wallet")
.arg(`<a style="text-decoration:none" href="#${Constants.appSection.wallet}">` + qsTr("Wallet", "Go to Wallet") + "</a>"), .arg(`<a style="text-decoration:none" href="#${Constants.appSection.wallet}">` + qsTr("Wallet", "Go to Wallet") + "</a>"),
"", "",
"checkmark-circle", "checkmark-circle",

View File

@ -23,7 +23,7 @@ ColumnLayout {
return false return false
if (tabBar.currentIndex > d.collectiblesTabIndex) if (tabBar.currentIndex > d.collectiblesTabIndex)
return false return false
if (tabBar.currentIndex === d.collectiblesTabIndex && baseCollectiblesModel.isFetching) if (tabBar.currentIndex === d.collectiblesTabIndex && baseWalletCollectiblesModel.isFetching)
return false return false
return loader.item && loader.item.dirty return loader.item && loader.item.dirty
} }
@ -51,14 +51,14 @@ ColumnLayout {
if (tabBar.currentIndex !== collectiblesTabIndex) if (tabBar.currentIndex !== collectiblesTabIndex)
return return
// If there is no more items to load or we're already fetching, return // If there is no more items to load or we're already fetching, return
if (!root.baseCollectiblesModel.hasMore || root.baseCollectiblesModel.isFetching) if (!root.baseWalletCollectiblesModel.hasMore || root.baseWalletCollectiblesModel.isFetching)
return return
root.baseCollectiblesModel.loadMore() root.baseWalletCollectiblesModel.loadMore()
} }
} }
Connections { Connections {
target: root.baseCollectiblesModel target: root.baseWalletCollectiblesModel
function onHasMoreChanged() { function onHasMoreChanged() {
d.checkLoadMoreCollectibles() d.checkLoadMoreCollectibles()
} }

View File

@ -30,7 +30,8 @@ Item {
function resetView() { function resetView() {
stack.currentIndex = 0 stack.currentIndex = 0
root.currentTabIndex = 0 root.currentTabIndex = 0
historyView.resetView() if (walletTabBar.currentIndex === 2)
mainViewLoader.item.resetView()
} }
function resetStack() { function resetStack() {
@ -94,7 +95,6 @@ Item {
StatusTabBar { StatusTabBar {
id: walletTabBar id: walletTabBar
objectName: "rightSideWalletTabBar" objectName: "rightSideWalletTabBar"
horizontalPadding: Style.current.padding
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.current.padding Layout.topMargin: Style.current.padding
@ -116,41 +116,67 @@ Item {
RootStore.setCurrentViewedHoldingType(walletTabBar.currentIndex === 1 ? Constants.TokenType.ERC721 : Constants.TokenType.ERC20) RootStore.setCurrentViewedHoldingType(walletTabBar.currentIndex === 1 ? Constants.TokenType.ERC721 : Constants.TokenType.ERC20)
} }
} }
StackLayout { Loader {
id: mainViewLoader
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
Layout.topMargin: Style.current.padding Layout.topMargin: Style.current.padding
Layout.bottomMargin: Style.current.padding Layout.bottomMargin: Style.current.padding
currentIndex: walletTabBar.currentIndex sourceComponent: {
switch (walletTabBar.currentIndex) {
case 0: return assetsView
case 1: return collectiblesView
case 2: return historyView
}
}
active: visible
AssetsView { Component {
assets: RootStore.assets id: assetsView
networkConnectionStore: root.networkConnectionStore AssetsView {
assetDetailsLaunched: stack.currentIndex === 2 assets: RootStore.assets
onAssetClicked: { overview: RootStore.overview
assetDetailView.token = token networkConnectionStore: root.networkConnectionStore
RootStore.setCurrentViewedHolding(token.symbol, Constants.TokenType.ERC20) assetDetailsLaunched: stack.currentIndex === 2
stack.currentIndex = 2 onAssetClicked: {
assetDetailView.token = token
RootStore.setCurrentViewedHolding(token.symbol, Constants.TokenType.ERC20)
stack.currentIndex = 2
}
onSendRequested: (symbol) => {
root.sendModal.preSelectedSendType = Constants.SendType.Transfer
root.sendModal.preSelectedHoldingID = symbol
root.sendModal.preSelectedHoldingType = Constants.TokenType.ERC20
root.sendModal.onlyAssets = true
root.sendModal.open()
}
onReceiveRequested: (symbol) => root.launchShareAddressModal()
onSwitchToCommunityRequested: (communityId) => Global.switchToCommunity(communityId)
onManageTokensRequested: Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.wallet)
} }
} }
CollectiblesView { Component {
collectiblesModel: RootStore.collectiblesStore.ownedCollectibles id: collectiblesView
onCollectibleClicked: { CollectiblesView {
RootStore.collectiblesStore.getDetailedCollectible(chainId, contractAddress, tokenId) collectiblesModel: RootStore.collectiblesStore.ownedCollectibles
RootStore.setCurrentViewedHolding(uid, Constants.TokenType.ERC721) onCollectibleClicked: {
stack.currentIndex = 1 RootStore.collectiblesStore.getDetailedCollectible(chainId, contractAddress, tokenId)
RootStore.setCurrentViewedHolding(uid, Constants.TokenType.ERC721)
stack.currentIndex = 1
}
} }
} }
HistoryView { Component {
id: historyView id: historyView
overview: RootStore.overview HistoryView {
showAllAccounts: root.showAllAccounts overview: RootStore.overview
sendModal: root.sendModal showAllAccounts: root.showAllAccounts
onLaunchTransactionDetail: function (entry, entryIndex) { sendModal: root.sendModal
transactionDetailView.transactionIndex = entryIndex onLaunchTransactionDetail: function (entry, entryIndex) {
transactionDetailView.transaction = entry transactionDetailView.transactionIndex = entryIndex
transactionDetailView.transaction = entry
stack.currentIndex = 3 stack.currentIndex = 3
}
} }
} }
} }

View File

@ -1,11 +1,6 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Controls 2.13
import StatusQ.Popups 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import utils 1.0 import utils 1.0
@ -15,15 +10,18 @@ TokenDelegate {
title: Constants.dummyText title: Constants.dummyText
subTitle: Constants.dummyText subTitle: Constants.dummyText
asset.name: Constants.dummyText asset.name: Constants.dummyText
currencyBalance.text: Constants.dummyText
currencyBalance.loading: true
change24HourPercentage.text: Constants.dummyText change24HourPercentage.text: Constants.dummyText
change24Hour.text: Constants.dummyText change24HourPercentage.loading: true
localeCurrencyBalance.text: Constants.dummyText currencyPrice.text: Constants.dummyText
currencyPrice.loading: true
statusListItemSubTitle.loading: true statusListItemSubTitle.loading: true
statusListItemTitle.loading: true statusListItemTitle.loading: true
statusListItemIcon.loading: true statusListItemIcon.loading: true
change24HourPercentage.loading: true
change24Hour.loading: true
localeCurrencyBalance.loading: true
textColor: Theme.palette.baseColor1 textColor: Theme.palette.baseColor1
enabled: false enabled: false
} }

View File

@ -1,20 +1,23 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Controls 2.13 import QtQuick.Controls 2.15
import StatusQ.Popups 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import AppLayouts.Wallet.controls 1.0
import utils 1.0 import utils 1.0
StatusListItem { StatusListItem {
id: root id: root
property alias localeCurrencyBalance: localeCurrencyBalance // expected roles: name, symbol, enabledNetworkBalance, enabledNetworkCurrencyBalance, currencyPrice, changePct24hour, communityId, communityName, communityImage
property alias change24Hour: change24HourText
property alias currencyBalance: currencyBalance
property alias change24HourPercentage: change24HourPercentageText property alias change24HourPercentage: change24HourPercentageText
property alias currencyPrice: currencyPrice
property string currentCurrencySymbol property string currentCurrencySymbol
property string textColor: { property string textColor: {
@ -33,12 +36,26 @@ StatusListItem {
property string errorTooltipText_1 property string errorTooltipText_1
property string errorTooltipText_2 property string errorTooltipText_2
readonly property bool isCommunityToken: !!modelData && !!modelData.communityId
readonly property string symbolUrl: !!modelData && modelData.symbol ? Constants.tokenIcon(modelData.symbol, false) : "" readonly property string symbolUrl: !!modelData && modelData.symbol ? Constants.tokenIcon(modelData.symbol, false) : ""
readonly property string upDownTriangle: {
if (!modelData)
return ""
if (modelData.changePct24hour < 0)
return "▾"
if (modelData.changePct24hour > 0)
return "▴"
return ""
}
signal switchToCommunityRequested(string communityId)
title: modelData ? modelData.name : "" title: modelData ? modelData.name : ""
subTitle: LocaleUtils.currencyAmountToLocaleString(modelData.enabledNetworkBalance) subTitle: LocaleUtils.currencyAmountToLocaleString(modelData.enabledNetworkBalance)
asset.name: symbolUrl asset.name: symbolUrl
asset.isImage: true asset.isImage: true
asset.width: 32
asset.height: 32
errorIcon.tooltip.maxWidth: 300 errorIcon.tooltip.maxWidth: 300
statusListItemTitleIcons.sourceComponent: StatusFlatRoundButton { statusListItemTitleIcons.sourceComponent: StatusFlatRoundButton {
@ -55,7 +72,7 @@ StatusListItem {
components: [ components: [
Column { Column {
id: valueColumn anchors.verticalCenter: parent.verticalCenter
StatusFlatRoundButton { StatusFlatRoundButton {
id: errorIcon id: errorIcon
width: 14 width: 14
@ -69,32 +86,49 @@ StatusListItem {
visible: !!tooltip.text visible: !!tooltip.text
} }
StatusTextWithLoadingState { StatusTextWithLoadingState {
id: localeCurrencyBalance id: currencyBalance
anchors.right: parent.right anchors.right: parent.right
font.pixelSize: 15
text: modelData ? LocaleUtils.currencyAmountToLocaleString(modelData.enabledNetworkCurrencyBalance) : "" text: modelData ? LocaleUtils.currencyAmountToLocaleString(modelData.enabledNetworkCurrencyBalance) : ""
visible: !errorIcon.visible visible: !errorIcon.visible && !root.isCommunityToken
} }
Row { Row {
anchors.horizontalCenter: parent.horizontalCenter anchors.right: parent.right
spacing: 8 spacing: 6
visible: !errorIcon.visible visible: !errorIcon.visible && !root.isCommunityToken
StatusTextWithLoadingState { StatusTextWithLoadingState {
id: change24HourText id: change24HourPercentageText
font.pixelSize: 15 anchors.verticalCenter: parent.verticalCenter
customColor: root.textColor customColor: root.textColor
text: modelData ? LocaleUtils.currencyAmountToLocaleString(modelData.currencyPrice) : "" font.pixelSize: 13
text: modelData && modelData.changePct24hour !== undefined ? "%1 %2%".arg(root.upDownTriangle).arg(LocaleUtils.numberToLocaleString(modelData.changePct24hour, 2))
: "---"
} }
Rectangle { Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 1 width: 1
height: change24HourText.implicitHeight height: 12
color: Theme.palette.directColor9 color: Theme.palette.directColor9
} }
StatusTextWithLoadingState { StatusTextWithLoadingState {
id: change24HourPercentageText id: currencyPrice
font.pixelSize: 15 anchors.verticalCenter: parent.verticalCenter
customColor: root.textColor customColor: root.textColor
text: modelData && modelData.changePct24hour !== "" ? "%1%".arg(LocaleUtils.numberToLocaleString(modelData.changePct24hour, 2)) : "---" font.pixelSize: 13
text: modelData ? LocaleUtils.currencyAmountToLocaleString(modelData.currencyPrice) : ""
}
}
ManageTokensCommunityTag {
anchors.right: parent.right
text: modelData.communityName
imageSrc: modelData.communityImage
visible: root.isCommunityToken
StatusToolTip {
text: qsTr("This token was minted by the %1 community").arg(modelData.communityName)
visible: parent.hovered
}
TapHandler {
acceptedButtons: Qt.LeftButton
onSingleTapped: root.switchToCommunityRequested(modelData.communityId)
} }
} }
} }
@ -102,7 +136,7 @@ StatusListItem {
states: [ states: [
State { State {
name: "unkownToken" name: "unknownToken"
when: !root.symbolUrl when: !root.symbolUrl
PropertyChanges { PropertyChanges {
target: root.asset target: root.asset
@ -111,6 +145,5 @@ StatusListItem {
name: !!modelData && modelData.symbol ? modelData.symbol : "" name: !!modelData && modelData.symbol ? modelData.symbol : ""
} }
} }
] ]
} }

View File

@ -15,7 +15,7 @@ QtObject {
readonly property bool marketValuesCache: walletSectionAssets.hasMarketValuesCache readonly property bool marketValuesCache: walletSectionAssets.hasMarketValuesCache
readonly property var blockchainNetworksDown: !!networkConnectionModule.blockchainNetworkConnection.chainIds ? networkConnectionModule.blockchainNetworkConnection.chainIds.split(";") : [] readonly property var blockchainNetworksDown: !!networkConnectionModule.blockchainNetworkConnection.chainIds ? networkConnectionModule.blockchainNetworkConnection.chainIds.split(";") : []
readonly property bool atleastOneBlockchainNetworkAvailable: blockchainNetworksDown.length < networksModule.all.count readonly property bool atleastOneBlockchainNetworkAvailable: blockchainNetworksDown.length < networksModule.all.count
readonly property bool sendBuyBridgeEnabled: localAppSettings.testEnvironment || (isOnline && readonly property bool sendBuyBridgeEnabled: localAppSettings.testEnvironment || (isOnline &&
(!networkConnectionModule.blockchainNetworkConnection.completelyDown && atleastOneBlockchainNetworkAvailable) && (!networkConnectionModule.blockchainNetworkConnection.completelyDown && atleastOneBlockchainNetworkAvailable) &&
@ -30,17 +30,17 @@ QtObject {
networkConnectionModule.marketValuesNetworkConnection.completelyDown ? networkConnectionModule.marketValuesNetworkConnection.completelyDown ?
qsTr("Requires CryptoCompare or CoinGecko, both of which are currently unavailable"): "" qsTr("Requires CryptoCompare or CoinGecko, both of which are currently unavailable"): ""
readonly property bool notOnlineWithNoCache: !isOnline && !walletSectionAssets.hasBalanceCache && !walletSectionAssets.hasMarketValuesCache readonly property bool notOnlineWithNoCache: !isOnline && !balanceCache && !marketValuesCache
readonly property string notOnlineWithNoCacheText: qsTr("Internet connection lost. Data could not be retrieved.") readonly property string notOnlineWithNoCacheText: qsTr("Internet connection lost. Data could not be retrieved.")
readonly property bool noBlockchainConnectionAndNoCache: networkConnectionModule.blockchainNetworkConnection.completelyDown && !walletSectionAssets.hasBalanceCache readonly property bool noBlockchainConnectionAndNoCache: networkConnectionModule.blockchainNetworkConnection.completelyDown && !balanceCache
readonly property string noBlockchainConnectionAndNoCacheText: qsTr("Token balances are fetched from Pocket Network (POKT) and Infura which are both curently unavailable") readonly property string noBlockchainConnectionAndNoCacheText: qsTr("Token balances are fetched from Pocket Network (POKT) and Infura which are both curently unavailable")
readonly property bool noMarketConnectionAndNoCache: networkConnectionModule.marketValuesNetworkConnection.completelyDown && !walletSectionAssets.hasMarketValuesCache readonly property bool noMarketConnectionAndNoCache: networkConnectionModule.marketValuesNetworkConnection.completelyDown && !marketValuesCache
readonly property string noMarketConnectionAndNoCacheText: qsTr("Market values are fetched from CryptoCompare and CoinGecko which are both currently unavailable") readonly property string noMarketConnectionAndNoCacheText: qsTr("Market values are fetched from CryptoCompare and CoinGecko which are both currently unavailable")
readonly property bool noBlockchainAndMarketConnectionAndNoCache: noBlockchainConnectionAndNoCache && noMarketConnectionAndNoCache readonly property bool noBlockchainAndMarketConnectionAndNoCache: noBlockchainConnectionAndNoCache && noMarketConnectionAndNoCache
readonly property string noBlockchainAndMarketConnectionAndNoCacheText: qsTr("Market values and token balances use CryptoCompare/CoinGecko and POKT/Infura which are all currently unavailable.") readonly property string noBlockchainAndMarketConnectionAndNoCacheText: qsTr("Market values and token balances use CryptoCompare/CoinGecko and POKT/Infura which are all currently unavailable.")
readonly property bool accountBalanceNotAvailable: notOnlineWithNoCache || noBlockchainConnectionAndNoCache || noMarketConnectionAndNoCache readonly property bool accountBalanceNotAvailable: notOnlineWithNoCache || noBlockchainConnectionAndNoCache || noMarketConnectionAndNoCache
readonly property string accountBalanceNotAvailableText: !isOnline ? notOnlineWithNoCacheText : readonly property string accountBalanceNotAvailableText: !isOnline ? notOnlineWithNoCacheText :
@ -48,15 +48,15 @@ QtObject {
networkConnectionModule.blockchainNetworkConnection.completelyDown ? noBlockchainConnectionAndNoCacheText : networkConnectionModule.blockchainNetworkConnection.completelyDown ? noBlockchainConnectionAndNoCacheText :
networkConnectionModule.marketValuesNetworkConnection.completelyDown ? noBlockchainAndMarketConnectionAndNoCacheText : "" networkConnectionModule.marketValuesNetworkConnection.completelyDown ? noBlockchainAndMarketConnectionAndNoCacheText : ""
readonly property bool noTokenBalanceAvailable: networkConnectionStore.notOnlineWithNoCache || networkConnectionStore.noBlockchainConnectionAndNoCache readonly property bool noTokenBalanceAvailable: notOnlineWithNoCache || noBlockchainConnectionAndNoCache
readonly property bool ensNetworkAvailable: !blockchainNetworksDown.includes(profileSectionModule.ensUsernamesModule.chainId.toString()) readonly property bool ensNetworkAvailable: !blockchainNetworksDown.includes(profileSectionModule.ensUsernamesModule.chainId.toString())
readonly property string ensNetworkUnavailableText: qsTr("Requires POKT/Infura for %1, which is currently unavailable").arg( networksModule.all.getNetworkFullName(profileSectionModule.ensUsernamesModule.chainId)) readonly property string ensNetworkUnavailableText: qsTr("Requires POKT/Infura for %1, which is currently unavailable").arg(networksModule.all.getNetworkFullName(profileSectionModule.ensUsernamesModule.chainId))
readonly property bool stickersNetworkAvailable: !blockchainNetworksDown.includes(stickersModule.getChainIdForStickers().toString()) readonly property bool stickersNetworkAvailable: !blockchainNetworksDown.includes(stickersModule.getChainIdForStickers().toString())
readonly property string stickersNetworkUnavailableText: qsTr("Requires POKT/Infura for %1, which is currently unavailable").arg( networksModule.all.getNetworkFullName(stickersModule.getChainIdForStickers())) readonly property string stickersNetworkUnavailableText: qsTr("Requires POKT/Infura for %1, which is currently unavailable").arg(networksModule.all.getNetworkFullName(stickersModule.getChainIdForStickers()))
function getBlockchainNetworkDownTextForToken(balances) { function getBlockchainNetworkDownTextForToken(balances) {
if(!!balances && !networkConnectionModule.blockchainNetworkConnection.completelyDown && !networkConnectionStore.notOnlineWithNoCache) { if(!!balances && !networkConnectionModule.blockchainNetworkConnection.completelyDown && !notOnlineWithNoCache) {
let chainIdsDown = [] let chainIdsDown = []
for (var i =0; i<balances.count; i++) { for (var i =0; i<balances.count; i++) {
let chainId = balances.rowData(i, "chainId") let chainId = balances.rowData(i, "chainId")
@ -73,8 +73,8 @@ QtObject {
} }
function getMarketNetworkDownText() { function getMarketNetworkDownText() {
if(networkConnectionStore.notOnlineWithNoCache) if(notOnlineWithNoCache)
return networkConnectionStore.notOnlineWithNoCacheText return notOnlineWithNoCacheText
else if(noBlockchainAndMarketConnectionAndNoCache) else if(noBlockchainAndMarketConnectionAndNoCache)
return noBlockchainAndMarketConnectionAndNoCacheText return noBlockchainAndMarketConnectionAndNoCacheText
else if(noMarketConnectionAndNoCache) else if(noMarketConnectionAndNoCache)
@ -87,7 +87,7 @@ QtObject {
let jointChainIdString = "" let jointChainIdString = ""
for (const chain of chainIdsDown) { for (const chain of chainIdsDown) {
jointChainIdString = (!!jointChainIdString) ? jointChainIdString + " & " : jointChainIdString jointChainIdString = (!!jointChainIdString) ? jointChainIdString + " & " : jointChainIdString
jointChainIdString += networksModule.all.getNetworkFullName(parseInt(chain)) jointChainIdString += networksModule.all.getNetworkFullName(parseInt(chain))
} }
return jointChainIdString return jointChainIdString
} }

View File

@ -1,88 +1,340 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Controls 2.14 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import StatusQ.Popups.Dialog 0.1
import StatusQ.Models 0.1
import StatusQ.Internal 0.1
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
import utils 1.0 import utils 1.0
import "../stores" import shared.stores 1.0
import shared.controls 1.0 import shared.controls 1.0
import shared.popups 1.0
Item { StatusScrollView {
id: root id: root
property var assets // expected roles: name, symbol, enabledNetworkBalance, enabledNetworkCurrencyBalance, currencyPrice, changePct24hour, communityId, communityName, communityImage
required property var assets
property var networkConnectionStore property var networkConnectionStore
property var overview
property bool assetDetailsLaunched: false property bool assetDetailsLaunched: false
signal assetClicked(var token) signal assetClicked(var token)
signal sendRequested(string symbol)
signal receiveRequested(string symbol)
signal switchToCommunityRequested(string communityId)
signal manageTokensRequested()
contentWidth: availableWidth
QtObject { QtObject {
id: d id: d
property int selectedAssetIndex: -1 property int selectedAssetIndex: -1
}
height: assetListView.height readonly property bool isCustomView: d.controller.hasSettings // TODO add respect other predefined orders (#12517)
StatusListView { function symbolIsVisible(symbol, balance) {
id: assetListView if (symbol === "ETH") // always visible
objectName: "assetViewStatusListView" return true
anchors.fill: parent if (!d.controller.filterAcceptsSymbol(symbol)) // explicitely hidden
model: !!assets ? assets : null return false
reuseItems: true if (symbol === "SNT" || symbol === "DAI") // visible by default
delegate: delegateLoader return true
} return !!balance && !!balance.amount // visible with non-zero balance
}
Component { readonly property var regularAssetsModel: SortFilterProxyModel {
id: delegateLoader sourceModel: root.assets
Loader {
property var modelData: model filters: [
property int index: index ExpressionFilter {
width: ListView.view.width expression: {
sourceComponent: loading ? loadingTokenDelegate: tokenDelegate d.controller.settingsDirty
return d.symbolIsVisible(model.symbol, model.enabledNetworkBalance) && !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.enabledNetworkBalance) && !!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"
}
function hideAllCommunityTokens(communityId) {
const tokenSymbols = ModelUtils.getAll(communityAssetsModel, "symbol", "communityId", communityId)
d.controller.settingsHideCommunityTokens(communityId, tokenSymbols)
} }
} }
Component { ColumnLayout {
id: loadingTokenDelegate width: root.availableWidth
LoadingTokenDelegate { spacing: 0
objectName: "AssetView_LoadingTokenDelegate_" + index
}
}
Component { StatusListView {
id: tokenDelegate Layout.fillWidth: true
TokenDelegate { Layout.preferredHeight: contentHeight
objectName: "AssetView_TokenListItem_" + (!!modelData ? modelData.symbol : "") interactive: false
readonly property string balance: !!modelData ? "%1".arg(modelData.enabledNetworkBalance.amount) : "" // Needed for the tests objectName: "assetViewStatusListView"
errorTooltipText_1: !!modelData && !! networkConnectionStore ? networkConnectionStore.getBlockchainNetworkDownTextForToken(modelData.balances) : "" model: d.regularAssetsModel
errorTooltipText_2: !!networkConnectionStore ? networkConnectionStore.getMarketNetworkDownText() : "" delegate: delegateLoader
subTitle: { }
if (!modelData) {
return "" StatusDialogDivider {
} Layout.fillWidth: true
if (networkConnectionStore && networkConnectionStore.noTokenBalanceAvailable) { Layout.topMargin: Style.current.padding
return "" Layout.bottomMargin: Style.current.halfPadding
} visible: d.hasCommunityAssets
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 Item { Layout.fillWidth: true }
errorIcon.tooltip.text: !!networkConnectionStore ? networkConnectionStore.noBlockchainConnectionAndNoCacheText : "" StatusFlatButton {
onClicked: { Layout.preferredWidth: 32
RootStore.getHistoricalDataForToken(modelData.symbol, RootStore.currencyStore.currentCurrency) Layout.preferredHeight: 32
d.selectedAssetIndex = index icon.name: "info"
assetClicked(modelData) textColor: Theme.palette.baseColor1
horizontalPadding: 0
verticalPadding: 0
onClicked: Global.openPopup(communityInfoPopupCmp)
} }
Component.onCompleted: { }
// on Model reset if the detail view is shown, update the data in background.
if(root.assetDetailsLaunched && index === d.selectedAssetIndex) StatusListView {
assetClicked(modelData) 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 index: index
width: ListView.view.width
sourceComponent: model.loading ? loadingTokenDelegate: tokenDelegate
}
}
Component {
id: loadingTokenDelegate
LoadingTokenDelegate {
objectName: "AssetView_LoadingTokenDelegate_" + index
}
}
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)
}
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 = index
assetClicked(modelData)
} 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 && index === d.selectedAssetIndex)
assetClicked(modelData)
}
}
}
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}">` + 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}">` + qsTr("Settings", "Go to Settings") + "</a>"),
"",
"checkmark-circle",
false,
Constants.ephemeralNotificationType.success,
""
)
}
} }
} }
} }