feat(wallet) save/load collectibles user handled state in DB

Add a separation layer for save/load/clear to ManageTokensModel
so that we can save/load from external sources.
The separate layer is composed of JSON as protocol, a set of signals
and slots for interface. The implementation forwards data to current
QSettings for storybook and nim controllers for the app.

Updates #13313, #13312
This commit is contained in:
Stefan 2024-02-29 23:04:36 -03:00 committed by Stefan Dunca
parent f45a39bfcf
commit 26542970ee
20 changed files with 553 additions and 265 deletions

View File

@ -32,6 +32,10 @@ QtObject:
proc updateCollectiblePreferences*(self: View, collectiblePreferencesJson: string) {.slot.} = proc updateCollectiblePreferences*(self: View, collectiblePreferencesJson: string) {.slot.} =
self.delegate.updateCollectiblePreferences(collectiblePreferencesJson) self.delegate.updateCollectiblePreferences(collectiblePreferencesJson)
proc clearCollectiblePreferences*(self: View) {.slot.} =
# There is no requirements of clearing the preferences yet but controller is expected to expose it
discard
proc getCollectiblePreferencesJson(self: View): QVariant {.slot.} = proc getCollectiblePreferencesJson(self: View): QVariant {.slot.} =
let preferences = self.delegate.getCollectiblePreferencesJson() let preferences = self.delegate.getCollectiblePreferencesJson()
return newQVariant(preferences) return newQVariant(preferences)

View File

@ -87,14 +87,14 @@ type
contractAddress*: string contractAddress*: string
owners*: seq[CollectibleOwner] owners*: seq[CollectibleOwner]
# see status-go/services/wallet/collectibles/service.go CollectibleDataType # Mirrors status-go/multiaccounts/settings_wallet/database.go CollectiblePreferencesType
CollectiblePreferencesItemType* {.pure.} = enum CollectiblePreferencesItemType* {.pure.} = enum
NonCommunityCollectible = 1, NonCommunityCollectible = 1,
CommunityCollectible, CommunityCollectible,
Collection, Collection,
Community Community
# Mirrors services/wallet/thirdparty/collectible_types.go CollectibleContractOwnership # Mirrors status-go/multiaccounts/settings_wallet/database.go CollectiblePreferences
CollectiblePreferences* = ref object of RootObj CollectiblePreferences* = ref object of RootObj
itemType* {.serializedFieldName("type").}: CollectiblePreferencesItemType itemType* {.serializedFieldName("type").}: CollectiblePreferencesItemType
key* {.serializedFieldName("key").}: string key* {.serializedFieldName("key").}: string

View File

@ -97,6 +97,12 @@ SplitView {
controller: ManageTokensController { controller: ManageTokensController {
sourceModel: d.walletAssetStore.groupedAccountAssetsModel sourceModel: d.walletAssetStore.groupedAccountAssetsModel
settingsKey: "WalletAssets" settingsKey: "WalletAssets"
serializeAsCollectibles: false
onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData)
onRequestLoadSettings: loadFromQSettings()
onRequestClearSettings: clearQSettings()
onTokenHidden: (symbol, name) => Global.displayToastMessage( onTokenHidden: (symbol, name) => Global.displayToastMessage(
qsTr("%1 (%2) was successfully hidden").arg(name).arg(symbol), "", "checkmark-circle", qsTr("%1 (%2) was successfully hidden").arg(name).arg(symbol), "", "checkmark-circle",
false, Constants.ephemeralNotificationType.success, "") false, Constants.ephemeralNotificationType.success, "")

View File

@ -82,6 +82,12 @@ SplitView {
controller: ManageTokensController { controller: ManageTokensController {
sourceModel: renamedModel sourceModel: renamedModel
settingsKey: "WalletCollectibles" settingsKey: "WalletCollectibles"
serializeAsCollectibles: true
onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData)
onRequestLoadSettings: loadFromQSettings()
onRequestClearSettings: clearQSettings()
onTokenHidden: (symbol, name) => Global.displayToastMessage( onTokenHidden: (symbol, name) => Global.displayToastMessage(
qsTr("%1 was successfully hidden").arg(name), "", "checkmark-circle", qsTr("%1 was successfully hidden").arg(name), "", "checkmark-circle",
false, Constants.ephemeralNotificationType.success, "") false, Constants.ephemeralNotificationType.success, "")

View File

@ -50,6 +50,11 @@ SplitView {
controller: ManageTokensController { controller: ManageTokensController {
sourceModel: ctrlEmptyModel.checked ? null : walletAssetStore.groupedAccountAssetsModel sourceModel: ctrlEmptyModel.checked ? null : walletAssetStore.groupedAccountAssetsModel
settingsKey: "WalletAssets" settingsKey: "WalletAssets"
serializeAsCollectibles: false
onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData)
onRequestLoadSettings: loadFromQSettings()
onRequestClearSettings: clearQSettings()
} }
} }

View File

@ -44,6 +44,11 @@ SplitView {
controller: ManageTokensController { controller: ManageTokensController {
sourceModel: renamedModel sourceModel: renamedModel
settingsKey: "WalletCollectibles" settingsKey: "WalletCollectibles"
serializeAsCollectibles: true
onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData)
onRequestLoadSettings: loadFromQSettings()
onRequestClearSettings: clearQSettings()
} }
} }

View File

@ -44,12 +44,22 @@ SplitView {
id: assetsController id: assetsController
sourceModel: ctrlEmptyModel.checked ? null : walletAssetStore.groupedAccountAssetsModel sourceModel: ctrlEmptyModel.checked ? null : walletAssetStore.groupedAccountAssetsModel
settingsKey: "WalletAssets" settingsKey: "WalletAssets"
serializeAsCollectibles: false
onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData)
onRequestLoadSettings: loadFromQSettings()
onRequestClearSettings: clearQSettings()
} }
ManageTokensController { ManageTokensController {
id: collectiblesController id: collectiblesController
sourceModel: ctrlEmptyModel.checked ? null : renamedModel sourceModel: ctrlEmptyModel.checked ? null : renamedModel
settingsKey: "WalletCollectibles" settingsKey: "WalletCollectibles"
serializeAsCollectibles: true
onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData)
onRequestLoadSettings: loadFromQSettings()
onRequestClearSettings: clearQSettings()
} }
ManageHiddenPanel { ManageHiddenPanel {

View File

@ -37,10 +37,20 @@ Item {
controller: ManageTokensController { controller: ManageTokensController {
sourceModel: renamedModel sourceModel: renamedModel
settingsKey: "WalletCollectibles" settingsKey: "WalletCollectibles"
serializeAsCollectibles: true
onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData)
onRequestLoadSettings: loadFromQSettings()
onRequestClearSettings: clearQSettings()
onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage( onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage(
qsTr("%1 community collectibles successfully hidden").arg(communityName), "", "checkmark-circle", qsTr("%1 community collectibles successfully hidden").arg(communityName), "", "checkmark-circle",
false, Constants.ephemeralNotificationType.success, "") false, Constants.ephemeralNotificationType.success, "")
} }
function clearSettings() {
controller.clearQSettings()
}
} }
} }

View File

@ -141,6 +141,8 @@ add_library(StatusQ SHARED
src/wallet/managetokenscontroller.h src/wallet/managetokenscontroller.h
src/wallet/managetokensmodel.cpp src/wallet/managetokensmodel.cpp
src/wallet/managetokensmodel.h src/wallet/managetokensmodel.h
src/wallet/tokendata.cpp
src/wallet/tokendata.h
) )
target_compile_features(StatusQ PRIVATE cxx_std_17) target_compile_features(StatusQ PRIVATE cxx_std_17)

View File

@ -1,6 +1,6 @@
#include "managetokenscontroller.h" #include "managetokenscontroller.h"
#include <tuple> #include "tokendata.h"
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QMutableHashIterator> #include <QMutableHashIterator>
@ -26,30 +26,38 @@ ManageTokensController::ManageTokensController(QObject* parent)
} }
if (m_modelConnectionsInitialized) if (m_modelConnectionsInitialized)
return; return;
connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &parent, int first, int last) { connect(m_sourceModel,
&QAbstractItemModel::rowsInserted,
this,
[this](const QModelIndex& parent, int first, int last) {
#ifdef QT_DEBUG #ifdef QT_DEBUG
QElapsedTimer t; QElapsedTimer t;
t.start(); t.start();
qCDebug(manageTokens) << "!!! ADDING" << last-first+1 << "NEW TOKENS"; qCDebug(manageTokens) << "!!! ADDING" << last - first + 1 << "NEW TOKENS";
#endif #endif
for (int i = first; i <= last; i++) for (int i = first; i <= last; i++)
addItem(i); addItem(i);
rebuildCommunityTokenGroupsModel(); rebuildCommunityTokenGroupsModel();
rebuildHiddenCommunityTokenGroupsModel(); rebuildHiddenCommunityTokenGroupsModel();
rebuildCollectionGroupsModel(); rebuildCollectionGroupsModel();
rebuildHiddenCollectionGroupsModel(); rebuildHiddenCollectionGroupsModel();
for (auto model: m_allModels) { for (auto model : m_allModels) {
model->applySort(); model->applySort();
model->saveCustomSortOrder(); model->saveCustomSortOrder();
} }
#ifdef QT_DEBUG #ifdef QT_DEBUG
qCDebug(manageTokens) << "!!! ADDING NEW SOURCE DATA TOOK" << t.nsecsElapsed()/1'000'000.f << "ms"; qCDebug(manageTokens)
<< "!!! ADDING NEW SOURCE DATA TOOK" << t.nsecsElapsed() / 1'000'000.f << "ms";
#endif #endif
}); });
connect(m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &ManageTokensController::parseSourceModel); connect(m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &ManageTokensController::requestLoadSettings);
connect(m_sourceModel, &QAbstractItemModel::dataChanged, this, &ManageTokensController::parseSourceModel); // NB at this point we don't know in which submodel the item is connect(m_sourceModel,
&QAbstractItemModel::dataChanged,
this,
&ManageTokensController::requestLoadSettings); // NB at this point we don't know in
// which submodel the item is
m_modelConnectionsInitialized = true; m_modelConnectionsInitialized = true;
}); });
} }
@ -69,7 +77,7 @@ void ManageTokensController::showHideRegularToken(const QString& symbol, bool fl
emit tokenHidden(shownItem->symbol, shownItem->name); emit tokenHidden(shownItem->symbol, shownItem->name);
} }
} }
saveSettings(); requestSaveSettings(serializeSettingsAsJson());
} }
void ManageTokensController::showHideCommunityToken(const QString& symbol, bool flag) void ManageTokensController::showHideCommunityToken(const QString& symbol, bool flag)
@ -90,7 +98,7 @@ void ManageTokensController::showHideCommunityToken(const QString& symbol, bool
m_communityTokensModel->saveCustomSortOrder(); m_communityTokensModel->saveCustomSortOrder();
rebuildCommunityTokenGroupsModel(); rebuildCommunityTokenGroupsModel();
rebuildHiddenCommunityTokenGroupsModel(); rebuildHiddenCommunityTokenGroupsModel();
saveSettings(); requestSaveSettings(serializeSettingsAsJson());
} }
void ManageTokensController::showHideGroup(const QString& groupId, bool flag) void ManageTokensController::showHideGroup(const QString& groupId, bool flag)
@ -98,7 +106,7 @@ void ManageTokensController::showHideGroup(const QString& groupId, bool flag)
if (flag) { // show if (flag) { // show
const auto tokens = m_hiddenTokensModel->takeAllItems(groupId); const auto tokens = m_hiddenTokensModel->takeAllItems(groupId);
if (!tokens.isEmpty()) { if (!tokens.isEmpty()) {
for (const auto& token: tokens) { for (const auto& token : tokens) {
m_communityTokensModel->addItem(token); m_communityTokensModel->addItem(token);
} }
emit communityTokenGroupShown(tokens.constFirst().communityName); emit communityTokenGroupShown(tokens.constFirst().communityName);
@ -109,7 +117,7 @@ void ManageTokensController::showHideGroup(const QString& groupId, bool flag)
} else { // hide } else { // hide
const auto tokens = m_communityTokensModel->takeAllItems(groupId); const auto tokens = m_communityTokensModel->takeAllItems(groupId);
if (!tokens.isEmpty()) { if (!tokens.isEmpty()) {
for (const auto& token: tokens) { for (const auto& token : tokens) {
m_hiddenTokensModel->addItem(token, false /*prepend*/); m_hiddenTokensModel->addItem(token, false /*prepend*/);
} }
emit communityTokenGroupHidden(tokens.constFirst().communityName); emit communityTokenGroupHidden(tokens.constFirst().communityName);
@ -122,7 +130,7 @@ void ManageTokensController::showHideGroup(const QString& groupId, bool flag)
rebuildCommunityTokenGroupsModel(); rebuildCommunityTokenGroupsModel();
m_communityTokenGroupsModel->applySort(); m_communityTokenGroupsModel->applySort();
rebuildHiddenCommunityTokenGroupsModel(); rebuildHiddenCommunityTokenGroupsModel();
saveSettings(); requestSaveSettings(serializeSettingsAsJson());
} }
void ManageTokensController::showHideCollectionGroup(const QString& groupId, bool flag) void ManageTokensController::showHideCollectionGroup(const QString& groupId, bool flag)
@ -130,7 +138,7 @@ void ManageTokensController::showHideCollectionGroup(const QString& groupId, boo
if (flag) { // show if (flag) { // show
const auto tokens = m_hiddenTokensModel->takeAllItems(groupId); const auto tokens = m_hiddenTokensModel->takeAllItems(groupId);
if (!tokens.isEmpty()) { if (!tokens.isEmpty()) {
for (const auto& token: tokens) { for (const auto& token : tokens) {
m_regularTokensModel->addItem(token); m_regularTokensModel->addItem(token);
} }
emit collectionTokenGroupShown(tokens.constFirst().collectionName); emit collectionTokenGroupShown(tokens.constFirst().collectionName);
@ -141,7 +149,7 @@ void ManageTokensController::showHideCollectionGroup(const QString& groupId, boo
} else { // hide } else { // hide
const auto tokens = m_regularTokensModel->takeAllItems(groupId); const auto tokens = m_regularTokensModel->takeAllItems(groupId);
if (!tokens.isEmpty()) { if (!tokens.isEmpty()) {
for (const auto& token: tokens) { for (const auto& token : tokens) {
m_hiddenTokensModel->addItem(token, false /*prepend*/); m_hiddenTokensModel->addItem(token, false /*prepend*/);
} }
emit collectionTokenGroupHidden(tokens.constFirst().collectionName); emit collectionTokenGroupHidden(tokens.constFirst().collectionName);
@ -154,71 +162,28 @@ void ManageTokensController::showHideCollectionGroup(const QString& groupId, boo
rebuildCollectionGroupsModel(); rebuildCollectionGroupsModel();
m_collectionGroupsModel->applySort(); m_collectionGroupsModel->applySort();
rebuildHiddenCollectionGroupsModel(); rebuildHiddenCollectionGroupsModel();
saveSettings(); requestSaveSettings(serializeSettingsAsJson());
} }
void ManageTokensController::saveSettings() void ManageTokensController::saveToQSettings(const QString& json)
{ {
Q_ASSERT(!m_settingsKey.isEmpty()); Q_ASSERT(!m_settingsKey.isEmpty());
setSettingsDirty(true); savingStarted();
// gather the data to save
SerializedTokenData result;
const auto resultCount = m_regularTokensModel->rowCount() + m_communityTokensModel->rowCount() + m_hiddenTokensModel->rowCount() +
(m_arrangeByCommunity ? m_communityTokenGroupsModel->rowCount() : 0) +
(m_arrangeByCollection ? m_collectionGroupsModel->rowCount() : 0);
result.reserve(resultCount);
for(auto model : {m_regularTokensModel, m_communityTokensModel})
result.insert(model->save());
if (m_arrangeByCommunity)
result.insert(m_communityTokenGroupsModel->save());
if (m_arrangeByCollection)
result.insert(m_collectionGroupsModel->save());
result.insert(m_hiddenTokensModel->save(false));
// save to QSettings // save to QSettings
m_settings.beginGroup(settingsGroupName()); m_settings.beginGroup(settingsGroupName());
// arrange by
m_settings.setValue(QStringLiteral("ArrangeByCommunity"), m_arrangeByCommunity);
m_settings.setValue(QStringLiteral("ArrangeByCollection"), m_arrangeByCollection);
// data // data
m_settings.beginWriteArray(m_settingsKey); m_settings.setValue(m_settingsKey, json);
SerializedTokenData::const_key_value_iterator it = result.constKeyValueBegin();
for (auto i = 0; it != result.constKeyValueEnd() && i < result.size(); it++, i++) {
m_settings.setArrayIndex(i);
const auto& [pos, visible, groupId, isCommunityGroup, isCollectionGroup] = it->second;
m_settings.setValue(QStringLiteral("symbol"), it->first);
m_settings.setValue(QStringLiteral("pos"), pos);
m_settings.setValue(QStringLiteral("visible"), visible);
m_settings.setValue(QStringLiteral("groupId"), groupId);
m_settings.setValue(QStringLiteral("isCommunityGroup"), isCommunityGroup);
m_settings.setValue(QStringLiteral("isCollectionGroup"), isCollectionGroup);
}
m_settings.endArray();
// hidden groups
m_settings.setValue(QStringLiteral("HiddenCommunityGroups"), hiddenCommunityGroups());
m_settings.setValue(QStringLiteral("HiddenCollectionGroups"), hiddenCollectionGroups());
m_settings.endGroup(); m_settings.endGroup();
m_settings.sync(); m_settings.sync();
// unset dirty savingFinished();
for (auto model: m_allModels)
model->setDirty(false);
loadSettingsData(true); // reload positions and visibility
incRevision();
setSettingsDirty(false);
} }
void ManageTokensController::clearSettings() void ManageTokensController::clearQSettings()
{ {
Q_ASSERT(!m_settingsKey.isEmpty()); Q_ASSERT(!m_settingsKey.isEmpty());
@ -231,73 +196,24 @@ void ManageTokensController::clearSettings()
emit settingsDirtyChanged(false); emit settingsDirtyChanged(false);
} }
void ManageTokensController::loadSettingsData(bool withGroup) void ManageTokensController::loadFromQSettings()
{
SerializedTokenData result;
if (withGroup)
m_settings.beginGroup(settingsGroupName());
const auto size = m_settings.beginReadArray(m_settingsKey);
for (auto i = 0; i < size; i++) {
m_settings.setArrayIndex(i);
const auto symbol = m_settings.value(QStringLiteral("symbol")).toString();
if (symbol.isEmpty()) {
qCDebug(manageTokens) << Q_FUNC_INFO << "Missing symbol while reading tokens settings";
continue;
}
const auto pos = m_settings.value(QStringLiteral("pos"), INT_MAX).toInt();
const auto visible = m_settings.value(QStringLiteral("visible"), true).toBool();
const auto groupId = m_settings.value(QStringLiteral("groupId")).toString();
const auto isCommunityGroup = m_settings.value(QStringLiteral("isCommunityGroup"), false).toBool();
const auto isCollectionGroup = m_settings.value(QStringLiteral("isCollectionGroup"), false).toBool();
result.insert(symbol, {pos, visible, groupId, isCommunityGroup, isCollectionGroup});
}
m_settings.endArray();
if (withGroup)
m_settings.endGroup();
if (result != m_settingsData)
m_settingsData = result;
}
void ManageTokensController::loadSettings()
{ {
Q_ASSERT(!m_settingsKey.isEmpty()); Q_ASSERT(!m_settingsKey.isEmpty());
setSettingsDirty(true); loadingStarted();
m_settingsData.clear();
// load from QSettings // load from QSettings
m_settings.beginGroup(settingsGroupName()); m_settings.beginGroup(settingsGroupName());
const auto jsonData = m_settings.value(m_settingsKey).toString();
loadSettingsData();
// hidden groups
const auto groups = m_settings.value(QStringLiteral("HiddenCommunityGroups")).toStringList();
if (!groups.isEmpty()) {
m_hiddenCommunityGroups = {groups.constBegin(), groups.constEnd()};
emit hiddenCommunityGroupsChanged();
}
const auto collections = m_settings.value(QStringLiteral("HiddenCollectionGroups")).toStringList();
if (!collections.isEmpty()) {
m_hiddenCollectionGroups = {collections.constBegin(), collections.constEnd()};
emit hiddenCollectionGroupsChanged();
}
// arrange by
setArrangeByCommunity(m_settings.value(QStringLiteral("ArrangeByCommunity"), false).toBool());
setArrangeByCollection(m_settings.value(QStringLiteral("ArrangeByCollection"), false).toBool());
m_settings.endGroup(); m_settings.endGroup();
setSettingsDirty(false); loadingFinished(jsonData);
} }
void ManageTokensController::setSettingsDirty(bool dirty) void ManageTokensController::setSettingsDirty(bool dirty)
{ {
if (m_settingsDirty == dirty) return; if (m_settingsDirty == dirty)
return;
m_settingsDirty = dirty; m_settingsDirty = dirty;
emit settingsDirtyChanged(m_settingsDirty); emit settingsDirtyChanged(m_settingsDirty);
} }
@ -318,9 +234,81 @@ QStringList ManageTokensController::hiddenCollectionGroups() const
return {m_hiddenCollectionGroups.constBegin(), m_hiddenCollectionGroups.constEnd()}; return {m_hiddenCollectionGroups.constBegin(), m_hiddenCollectionGroups.constEnd()};
} }
void ManageTokensController::revert() void ManageTokensController::revert() { requestLoadSettings(); }
void ManageTokensController::savingStarted()
{ {
setSettingsDirty(true); // save to QSettings
m_settings.beginGroup(settingsGroupName());
m_settings.setValue(QStringLiteral("ArrangeByCommunity"), m_arrangeByCommunity);
m_settings.setValue(QStringLiteral("ArrangeByCollection"), m_arrangeByCollection);
m_settings.endGroup();
}
void ManageTokensController::savingFinished()
{
// unset dirty
for (auto model : m_allModels)
model->setDirty(false);
incRevision();
setSettingsDirty(false);
}
void ManageTokensController::loadingStarted()
{
setSettingsDirty(true);
m_settingsData.clear();
m_settings.beginGroup(settingsGroupName());
// hidden groups
const auto groups = m_settings.value(QStringLiteral("HiddenCommunityGroups")).toStringList();
if (!groups.isEmpty()) {
m_hiddenCommunityGroups = {groups.constBegin(), groups.constEnd()};
emit hiddenCommunityGroupsChanged();
}
const auto collections = m_settings.value(QStringLiteral("HiddenCollectionGroups")).toStringList();
if (!collections.isEmpty()) {
m_hiddenCollectionGroups = {collections.constBegin(), collections.constEnd()};
emit hiddenCollectionGroupsChanged();
}
// arrange by
setArrangeByCommunity(m_settings.value(QStringLiteral("ArrangeByCommunity"), false).toBool());
setArrangeByCollection(m_settings.value(QStringLiteral("ArrangeByCollection"), false).toBool());
m_settings.endGroup();
}
void ManageTokensController::loadingFinished(const QString& jsonData)
{
if (!jsonData.isEmpty()) {
auto result = tokenOrdersFromJson(jsonData, m_serializeAsCollectibles);
if (result != m_settingsData) {
m_settingsData = result;
}
}
parseSourceModel(); parseSourceModel();
setSettingsDirty(false);
}
QString ManageTokensController::serializeSettingsAsJson()
{
SerializedTokenData result;
for (auto model : {m_regularTokensModel, m_communityTokensModel})
result.insert(model->save());
if (m_arrangeByCommunity)
result.insert(m_communityTokenGroupsModel->save(true /* visible */, true /* itemsAreGroups */));
if (m_arrangeByCollection)
result.insert(m_collectionGroupsModel->save(true /* visible */, true /* itemsAreGroups */));
result.insert(m_hiddenTokensModel->save(false));
auto json = tokenOrdersToJson(result, m_serializeAsCollectibles);
return json;
} }
QString ManageTokensController::settingsGroupName() const QString ManageTokensController::settingsGroupName() const
@ -337,26 +325,12 @@ bool ManageTokensController::hasSettings() const
int ManageTokensController::compareTokens(const QString& lhsSymbol, const QString& rhsSymbol) const int ManageTokensController::compareTokens(const QString& lhsSymbol, const QString& rhsSymbol) const
{ {
constexpr auto defaultVal = std::make_tuple(INT_MAX, false, QLatin1String(), false, false); const auto left = m_settingsData.value(lhsSymbol, TokenOrder());
const auto right = m_settingsData.value(rhsSymbol, TokenOrder());
int leftPos, rightPos;
bool leftVisible, rightVisible;
QString leftGroup, rightGroup;
bool leftIsCommunityGroup, rightIsCommunityGroup, leftIsCollectionGroup, rightIsCollectionGroup;
std::tie(leftPos, leftVisible, leftGroup, leftIsCommunityGroup, leftIsCollectionGroup) = m_settingsData.value(lhsSymbol, defaultVal);
std::tie(rightPos, rightVisible, rightGroup, rightIsCommunityGroup, rightIsCollectionGroup) = m_settingsData.value(rhsSymbol, defaultVal);
// check grouped position
if (((m_arrangeByCommunity && leftIsCommunityGroup && rightIsCommunityGroup)
|| (m_arrangeByCollection && leftIsCollectionGroup && rightIsCollectionGroup))) {
leftPos = std::get<0>(m_settingsData.value(leftGroup, defaultVal));
rightPos = std::get<0>(m_settingsData.value(rightGroup, defaultVal));
}
// check if visible // check if visible
leftPos = leftVisible ? leftPos : INT_MAX; auto leftPos = left.visible ? left.sortOrder : undefinedTokenOrder;
rightPos = rightVisible ? rightPos : INT_MAX; auto rightPos = right.visible ? right.sortOrder : undefinedTokenOrder;
if (leftPos < rightPos) if (leftPos < rightPos)
return -1; return -1;
@ -367,9 +341,13 @@ int ManageTokensController::compareTokens(const QString& lhsSymbol, const QStrin
bool ManageTokensController::filterAcceptsSymbol(const QString& symbol) const bool ManageTokensController::filterAcceptsSymbol(const QString& symbol) const
{ {
if (symbol.isEmpty()) return true; if (symbol.isEmpty())
return true;
return std::get<1>(m_settingsData.value(symbol, {INT_MAX, true, QString(), false, false})); if (!m_settingsData.contains(symbol)) {
return true;
}
return m_settingsData.value(symbol).visible;
} }
void ManageTokensController::classBegin() void ManageTokensController::classBegin()
@ -377,19 +355,17 @@ void ManageTokensController::classBegin()
// empty on purpose // empty on purpose
} }
void ManageTokensController::componentComplete() void ManageTokensController::componentComplete() { requestLoadSettings(); }
{
loadSettings();
}
void ManageTokensController::setSourceModel(QAbstractItemModel* newSourceModel) void ManageTokensController::setSourceModel(QAbstractItemModel* newSourceModel)
{ {
if(m_sourceModel == newSourceModel) return; if (m_sourceModel == newSourceModel)
return;
if(!newSourceModel) { if (!newSourceModel) {
disconnect(sourceModel()); disconnect(sourceModel());
// clear all the models // clear all the models
for (auto model: m_allModels) for (auto model : m_allModels)
model->clear(); model->clear();
m_settingsData.clear(); m_settingsData.clear();
m_hiddenCommunityGroups.clear(); m_hiddenCommunityGroups.clear();
@ -402,14 +378,15 @@ void ManageTokensController::setSourceModel(QAbstractItemModel* newSourceModel)
m_sourceModel = newSourceModel; m_sourceModel = newSourceModel;
connect(m_sourceModel, &QAbstractItemModel::modelReset, this, &ManageTokensController::parseSourceModel); connect(m_sourceModel, &QAbstractItemModel::modelReset, this, &ManageTokensController::requestLoadSettings);
if (m_sourceModel && m_sourceModel->roleNames().isEmpty()) { // workaround for when a model has no roles and roles are added when the model is populated (ListModel) if (m_sourceModel && m_sourceModel->roleNames().isEmpty()) { // workaround for when a model has no roles and roles
// are added when the model is populated (ListModel)
// QTBUG-57971 // QTBUG-57971
connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &ManageTokensController::parseSourceModel); connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &ManageTokensController::requestLoadSettings);
return; return;
} else { } else {
parseSourceModel(); requestLoadSettings();
} }
} }
@ -418,7 +395,7 @@ void ManageTokensController::parseSourceModel()
if (!m_sourceModel) if (!m_sourceModel)
return; return;
disconnect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &ManageTokensController::parseSourceModel); disconnect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &ManageTokensController::requestLoadSettings);
#ifdef QT_DEBUG #ifdef QT_DEBUG
QElapsedTimer t; QElapsedTimer t;
@ -426,12 +403,9 @@ void ManageTokensController::parseSourceModel()
#endif #endif
// clear all the models // clear all the models
for (auto model: m_allModels) for (auto model : m_allModels)
model->clear(); model->clear();
// load settings
loadSettings();
// read and transform the original data // read and transform the original data
const auto newSize = m_sourceModel->rowCount(); const auto newSize = m_sourceModel->rowCount();
qCDebug(manageTokens) << "!!! PARSING" << newSize << "TOKENS"; qCDebug(manageTokens) << "!!! PARSING" << newSize << "TOKENS";
@ -448,13 +422,13 @@ void ManageTokensController::parseSourceModel()
rebuildHiddenCollectionGroupsModel(); rebuildHiddenCollectionGroupsModel();
// (pre)sort // (pre)sort
for (auto model: m_allModels) { for (auto model : m_allModels) {
model->applySort(); model->applySort();
model->setDirty(false); model->setDirty(false);
} }
#ifdef QT_DEBUG #ifdef QT_DEBUG
qCDebug(manageTokens) << "!!! PARSING SOURCE DATA TOOK" << t.nsecsElapsed()/1'000'000.f << "ms"; qCDebug(manageTokens) << "!!! PARSING SOURCE DATA TOOK" << t.nsecsElapsed() / 1'000'000.f << "ms";
#endif #endif
emit sourceModelChanged(); emit sourceModelChanged();
@ -464,7 +438,7 @@ void ManageTokensController::addItem(int index)
{ {
const auto sourceRoleNames = m_sourceModel->roleNames(); const auto sourceRoleNames = m_sourceModel->roleNames();
const auto dataForIndex = [&](const QModelIndex &idx, const QByteArray& rolename) -> QVariant { const auto dataForIndex = [&](const QModelIndex& idx, const QByteArray& rolename) -> QVariant {
const auto key = sourceRoleNames.key(rolename, -1); const auto key = sourceRoleNames.key(rolename, -1);
if (key == -1) if (key == -1)
return {}; return {};
@ -475,7 +449,7 @@ void ManageTokensController::addItem(int index)
const auto symbol = dataForIndex(srcIndex, kSymbolRoleName).toString(); const auto symbol = dataForIndex(srcIndex, kSymbolRoleName).toString();
const auto communityId = dataForIndex(srcIndex, kCommunityIdRoleName).toString(); const auto communityId = dataForIndex(srcIndex, kCommunityIdRoleName).toString();
const auto communityName = dataForIndex(srcIndex, kCommunityNameRoleName).toString(); const auto communityName = dataForIndex(srcIndex, kCommunityNameRoleName).toString();
const auto visible = m_settingsData.contains(symbol) ? std::get<1>(m_settingsData.value(symbol)) : true; const auto visible = m_settingsData.contains(symbol) ? m_settingsData.value(symbol).visible : true;
const auto bgColor = dataForIndex(srcIndex, kBackgroundColorRoleName).value<QColor>(); const auto bgColor = dataForIndex(srcIndex, kBackgroundColorRoleName).value<QColor>();
const auto collectionUid = dataForIndex(srcIndex, kCollectionUidRoleName).toString(); const auto collectionUid = dataForIndex(srcIndex, kCollectionUidRoleName).toString();
@ -497,8 +471,8 @@ void ManageTokensController::addItem(int index)
token.decimals = dataForIndex(srcIndex, kDecimalsRoleName); token.decimals = dataForIndex(srcIndex, kDecimalsRoleName);
token.marketDetails = dataForIndex(srcIndex, kMarketDetailsRoleName); token.marketDetails = dataForIndex(srcIndex, kMarketDetailsRoleName);
token.customSortOrderNo = m_settingsData.contains(symbol) ? std::get<0>(m_settingsData.value(symbol)) token.customSortOrderNo = m_settingsData.contains(symbol) ? m_settingsData.value(symbol).sortOrder
: (visible ? INT_MAX : 0); // append/prepend : (visible ? undefinedTokenOrder : 0); // append/prepend
if (!visible) if (!visible)
m_hiddenTokensModel->addItem(token, /*append*/ false); m_hiddenTokensModel->addItem(token, /*append*/ false);
@ -510,19 +484,15 @@ void ManageTokensController::addItem(int index)
bool ManageTokensController::dirty() const bool ManageTokensController::dirty() const
{ {
return std::any_of(m_allModels.cbegin(), m_allModels.cend(), [](auto model) { return std::any_of(m_allModels.cbegin(), m_allModels.cend(), [](auto model) { return model->dirty(); });
return model->dirty();
});
} }
bool ManageTokensController::arrangeByCommunity() const bool ManageTokensController::arrangeByCommunity() const { return m_arrangeByCommunity; }
{
return m_arrangeByCommunity;
}
void ManageTokensController::setArrangeByCommunity(bool newArrangeByCommunity) void ManageTokensController::setArrangeByCommunity(bool newArrangeByCommunity)
{ {
if(m_arrangeByCommunity == newArrangeByCommunity) return; if (m_arrangeByCommunity == newArrangeByCommunity)
return;
m_arrangeByCommunity = newArrangeByCommunity; m_arrangeByCommunity = newArrangeByCommunity;
if (m_arrangeByCommunity) { if (m_arrangeByCommunity) {
rebuildCommunityTokenGroupsModel(); rebuildCommunityTokenGroupsModel();
@ -532,14 +502,12 @@ void ManageTokensController::setArrangeByCommunity(bool newArrangeByCommunity)
emit arrangeByCommunityChanged(); emit arrangeByCommunityChanged();
} }
bool ManageTokensController::arrangeByCollection() const bool ManageTokensController::arrangeByCollection() const { return m_arrangeByCollection; }
{
return m_arrangeByCollection;
}
void ManageTokensController::setArrangeByCollection(bool newArrangeByCollection) void ManageTokensController::setArrangeByCollection(bool newArrangeByCollection)
{ {
if(m_arrangeByCollection == newArrangeByCollection) return; if (m_arrangeByCollection == newArrangeByCollection)
return;
m_arrangeByCollection = newArrangeByCollection; m_arrangeByCollection = newArrangeByCollection;
if (m_arrangeByCollection) { if (m_arrangeByCollection) {
rebuildCollectionGroupsModel(); rebuildCollectionGroupsModel();
@ -570,7 +538,7 @@ void ManageTokensController::rebuildCommunityTokenGroupsModel()
tokenGroup.balance = 1; tokenGroup.balance = 1;
if (m_settingsData.contains(communityId)) { if (m_settingsData.contains(communityId)) {
tokenGroup.customSortOrderNo = std::get<0>(m_settingsData.value(communityId)); tokenGroup.customSortOrderNo = m_settingsData.value(communityId).sortOrder;
} }
result.append(tokenGroup); result.append(tokenGroup);
@ -587,7 +555,7 @@ void ManageTokensController::rebuildCommunityTokenGroupsModel()
} }
m_communityTokenGroupsModel->clear(); m_communityTokenGroupsModel->clear();
for (const auto& group: std::as_const(result)) for (const auto& group : std::as_const(result))
m_communityTokenGroupsModel->addItem(group); m_communityTokenGroupsModel->addItem(group);
qCDebug(manageTokens) << "!!! GROUPS MODEL REBUILT WITH GROUPS:" << communityIds; qCDebug(manageTokens) << "!!! GROUPS MODEL REBUILT WITH GROUPS:" << communityIds;
@ -604,7 +572,8 @@ void ManageTokensController::rebuildHiddenCommunityTokenGroupsModel()
const auto communityId = communityToken.communityId; const auto communityId = communityToken.communityId;
if (communityId.isEmpty()) if (communityId.isEmpty())
continue; continue;
if (!communityIds.contains(communityId) && m_hiddenCommunityGroups.contains(communityId)) { // insert into groups if (!communityIds.contains(communityId) &&
m_hiddenCommunityGroups.contains(communityId)) { // insert into groups
communityIds.append(communityId); communityIds.append(communityId);
TokenData tokenGroup; TokenData tokenGroup;
@ -628,7 +597,7 @@ void ManageTokensController::rebuildHiddenCommunityTokenGroupsModel()
} }
m_hiddenCommunityTokenGroupsModel->clear(); m_hiddenCommunityTokenGroupsModel->clear();
for (const auto& group: std::as_const(result)) for (const auto& group : std::as_const(result))
m_hiddenCommunityTokenGroupsModel->addItem(group); m_hiddenCommunityTokenGroupsModel->addItem(group);
qCDebug(manageTokens) << "!!! HIDDEN GROUPS MODEL REBUILT WITH GROUPS:" << communityIds; qCDebug(manageTokens) << "!!! HIDDEN GROUPS MODEL REBUILT WITH GROUPS:" << communityIds;
@ -647,7 +616,8 @@ void ManageTokensController::rebuildCollectionGroupsModel()
if (!collectionIds.contains(collectionId)) { // insert into groups if (!collectionIds.contains(collectionId)) { // insert into groups
collectionIds.append(collectionId); collectionIds.append(collectionId);
const auto collectionName = !collectionToken.collectionName.isEmpty() ? collectionToken.collectionName : collectionToken.name; const auto collectionName =
!collectionToken.collectionName.isEmpty() ? collectionToken.collectionName : collectionToken.name;
TokenData tokenGroup; TokenData tokenGroup;
tokenGroup.symbol = collectionId; tokenGroup.symbol = collectionId;
@ -659,7 +629,7 @@ void ManageTokensController::rebuildCollectionGroupsModel()
tokenGroup.balance = 1; tokenGroup.balance = 1;
if (m_settingsData.contains(collectionId)) { if (m_settingsData.contains(collectionId)) {
tokenGroup.customSortOrderNo = std::get<0>(m_settingsData.value(collectionId)); tokenGroup.customSortOrderNo = m_settingsData.value(collectionId).sortOrder;
} }
result.append(tokenGroup); result.append(tokenGroup);
@ -676,7 +646,7 @@ void ManageTokensController::rebuildCollectionGroupsModel()
} }
m_collectionGroupsModel->clear(); m_collectionGroupsModel->clear();
for (const auto& group: std::as_const(result)) for (const auto& group : std::as_const(result))
m_collectionGroupsModel->addItem(group); m_collectionGroupsModel->addItem(group);
qCDebug(manageTokens) << "!!! COLLECTION MODEL REBUILT WITH GROUPS:" << collectionIds; qCDebug(manageTokens) << "!!! COLLECTION MODEL REBUILT WITH GROUPS:" << collectionIds;
@ -692,10 +662,12 @@ void ManageTokensController::rebuildHiddenCollectionGroupsModel()
const auto& collectionToken = m_hiddenTokensModel->itemAt(i); const auto& collectionToken = m_hiddenTokensModel->itemAt(i);
const auto collectionId = collectionToken.collectionUid; const auto collectionId = collectionToken.collectionUid;
const auto isSelfCollection = collectionToken.isSelfCollection; const auto isSelfCollection = collectionToken.isSelfCollection;
if (!collectionIds.contains(collectionId) && m_hiddenCollectionGroups.contains(collectionId)) { // insert into groups if (!collectionIds.contains(collectionId) &&
m_hiddenCollectionGroups.contains(collectionId)) { // insert into groups
collectionIds.append(collectionId); collectionIds.append(collectionId);
const auto collectionName = !collectionToken.collectionName.isEmpty() ? collectionToken.collectionName : collectionToken.name; const auto collectionName =
!collectionToken.collectionName.isEmpty() ? collectionToken.collectionName : collectionToken.name;
TokenData tokenGroup; TokenData tokenGroup;
tokenGroup.symbol = collectionId; tokenGroup.symbol = collectionId;
@ -719,16 +691,13 @@ void ManageTokensController::rebuildHiddenCollectionGroupsModel()
} }
m_hiddenCollectionGroupsModel->clear(); m_hiddenCollectionGroupsModel->clear();
for (const auto& group: std::as_const(result)) for (const auto& group : std::as_const(result))
m_hiddenCollectionGroupsModel->addItem(group); m_hiddenCollectionGroupsModel->addItem(group);
qCDebug(manageTokens) << "!!! HIDDEN COLLECTION GROUPS MODEL REBUILT WITH GROUPS:" << collectionIds; qCDebug(manageTokens) << "!!! HIDDEN COLLECTION GROUPS MODEL REBUILT WITH GROUPS:" << collectionIds;
} }
QString ManageTokensController::settingsKey() const QString ManageTokensController::settingsKey() const { return m_settingsKey; }
{
return m_settingsKey;
}
void ManageTokensController::setSettingsKey(const QString& newSettingsKey) void ManageTokensController::setSettingsKey(const QString& newSettingsKey)
{ {
@ -737,3 +706,13 @@ void ManageTokensController::setSettingsKey(const QString& newSettingsKey)
m_settingsKey = newSettingsKey; m_settingsKey = newSettingsKey;
emit settingsKeyChanged(); emit settingsKeyChanged();
} }
bool ManageTokensController::serializeAsCollectibles() const { return m_serializeAsCollectibles; }
void ManageTokensController::setSerializeAsCollectibles(const bool newSerializeAsCollectibles)
{
if (m_serializeAsCollectibles == newSerializeAsCollectibles)
return;
m_serializeAsCollectibles = newSerializeAsCollectibles;
emit serializeAsCollectiblesChanged();
}

View File

@ -8,6 +8,12 @@
class QAbstractItemModel; class QAbstractItemModel;
/// @brief Controller for managing visibility and order for all kind of tokens and groups.
///
/// There is an "abstraction" layer for saving data to different mediums and is controlled form QML.
/// The QML implementation forwards data to current QSettings for storybook and nim controllers for the app.
/// @see request* signals for triggering actions and start/finish methods for notifying about the process.
/// @see *QSettings related methods for saving and loading data in user profile settings.
class ManageTokensController : public QObject, public QQmlParserStatus class ManageTokensController : public QObject, public QQmlParserStatus
{ {
Q_OBJECT Q_OBJECT
@ -16,6 +22,7 @@ class ManageTokensController : public QObject, public QQmlParserStatus
// input properties // input properties
Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged FINAL) 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 serializeAsCollectibles READ serializeAsCollectibles WRITE setSerializeAsCollectibles NOTIFY serializeAsCollectiblesChanged FINAL REQUIRED)
Q_PROPERTY(bool arrangeByCommunity READ arrangeByCommunity WRITE setArrangeByCommunity NOTIFY arrangeByCommunityChanged FINAL) Q_PROPERTY(bool arrangeByCommunity READ arrangeByCommunity WRITE setArrangeByCommunity NOTIFY arrangeByCommunityChanged FINAL)
Q_PROPERTY(bool arrangeByCollection READ arrangeByCollection WRITE setArrangeByCollection NOTIFY arrangeByCollectionChanged FINAL) Q_PROPERTY(bool arrangeByCollection READ arrangeByCollection WRITE setArrangeByCollection NOTIFY arrangeByCollectionChanged FINAL)
@ -45,11 +52,19 @@ public:
Q_INVOKABLE void showHideGroup(const QString& groupId, bool flag); Q_INVOKABLE void showHideGroup(const QString& groupId, bool flag);
Q_INVOKABLE void showHideCollectionGroup(const QString& groupId, bool flag); Q_INVOKABLE void showHideCollectionGroup(const QString& groupId, bool flag);
Q_INVOKABLE void loadSettings(); Q_INVOKABLE void loadFromQSettings();
Q_INVOKABLE void saveSettings(); Q_INVOKABLE void saveToQSettings(const QString& json);
Q_INVOKABLE void clearSettings(); Q_INVOKABLE void clearQSettings();
Q_INVOKABLE void revert(); Q_INVOKABLE void revert();
/// required to be called before the saving is started
Q_INVOKABLE void savingStarted();
Q_INVOKABLE void savingFinished();
Q_INVOKABLE void loadingStarted();
Q_INVOKABLE void loadingFinished(const QString& jsonData);
Q_INVOKABLE QString serializeSettingsAsJson();
Q_INVOKABLE int compareTokens(const QString& lhsSymbol, const QString& rhsSymbol) const; Q_INVOKABLE int compareTokens(const QString& lhsSymbol, const QString& rhsSymbol) const;
Q_INVOKABLE bool filterAcceptsSymbol(const QString& symbol) const; Q_INVOKABLE bool filterAcceptsSymbol(const QString& symbol) const;
@ -64,6 +79,7 @@ signals:
void arrangeByCollectionChanged(); void arrangeByCollectionChanged();
void settingsKeyChanged(); void settingsKeyChanged();
void settingsDirtyChanged(bool dirty); void settingsDirtyChanged(bool dirty);
void serializeAsCollectiblesChanged();
void tokenHidden(const QString& symbol, const QString& name); void tokenHidden(const QString& symbol, const QString& name);
void tokenShown(const QString& symbol, const QString& name); void tokenShown(const QString& symbol, const QString& name);
@ -77,6 +93,17 @@ signals:
void revisionChanged(); void revisionChanged();
/// Emitted when the settings are requested to be saved.
/// Receiver requires to call savingStarted and savingFinished to notify about the saving process.
/// @param jsonData serialized json data
void requestSaveSettings(const QString& jsonData);
/// Emitted when the settings are requested to be loaded. Client should call loadSettings as a response.
/// Receiver requires to call loadingStarted and loadingFinished to notify about the loading process.
void requestLoadSettings();
/// @brief Emitted when the settings are requested to be cleared.
/// Receiver requires to call loadingStarted and loadingFinished to notify about the loading process.
void requestClearSettings();
private: private:
QAbstractItemModel* m_sourceModel{nullptr}; QAbstractItemModel* m_sourceModel{nullptr};
QAbstractItemModel* sourceModel() const { return m_sourceModel; } QAbstractItemModel* sourceModel() const { return m_sourceModel; }
@ -128,6 +155,11 @@ private:
QString settingsKey() const; QString settingsKey() const;
QString settingsGroupName() const; QString settingsGroupName() const;
void setSettingsKey(const QString& newSettingsKey); void setSettingsKey(const QString& newSettingsKey);
bool m_serializeAsCollectibles{false};
bool serializeAsCollectibles() const;
void setSerializeAsCollectibles(const bool newSerializeAsCollectibles);
QSettings m_settings; QSettings m_settings;
SerializedTokenData m_settingsData; // symbol -> {sortOrder, visible, groupId, isCommunityGroup, isCollectionGroup} SerializedTokenData m_settingsData; // symbol -> {sortOrder, visible, groupId, isCommunityGroup, isCollectionGroup}
bool hasSettings() const; bool hasSettings() const;

View File

@ -4,8 +4,7 @@
Q_LOGGING_CATEGORY(manageTokens, "status.models.manageTokens", QtInfoMsg) Q_LOGGING_CATEGORY(manageTokens, "status.models.manageTokens", QtInfoMsg)
ManageTokensModel::ManageTokensModel(QObject* parent) ManageTokensModel::ManageTokensModel(QObject* parent) : QAbstractListModel(parent)
: QAbstractListModel(parent)
{ {
connect(this, &QAbstractItemModel::rowsInserted, this, &ManageTokensModel::countChanged); connect(this, &QAbstractItemModel::rowsInserted, this, &ManageTokensModel::countChanged);
connect(this, &QAbstractItemModel::rowsRemoved, this, &ManageTokensModel::countChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &ManageTokensModel::countChanged);
@ -40,9 +39,8 @@ void ManageTokensModel::addItem(const TokenData& item, bool append)
std::optional<TokenData> ManageTokensModel::takeItem(const QString& symbol) std::optional<TokenData> ManageTokensModel::takeItem(const QString& symbol)
{ {
const auto token = std::find_if(m_data.cbegin(), m_data.cend(), [symbol](const auto& item) { const auto token =
return symbol == item.symbol; std::find_if(m_data.cbegin(), m_data.cend(), [symbol](const auto& item) { return symbol == item.symbol; });
});
const auto row = std::distance(m_data.cbegin(), token); const auto row = std::distance(m_data.cbegin(), token);
if (row < 0 || row >= rowCount()) if (row < 0 || row >= rowCount())
@ -60,7 +58,7 @@ QList<TokenData> ManageTokensModel::takeAllItems(const QString& groupId)
QList<int> indexesToRemove; QList<int> indexesToRemove;
for (int i = 0; i < m_data.count(); i++) { for (int i = 0; i < m_data.count(); i++) {
const auto &token = m_data.at(i); const auto& token = m_data.at(i);
if (token.communityId == groupId || token.collectionUid == groupId) { if (token.communityId == groupId || token.collectionUid == groupId) {
result.append(token); result.append(token);
indexesToRemove.append(i); indexesToRemove.append(i);
@ -68,7 +66,7 @@ QList<TokenData> ManageTokensModel::takeAllItems(const QString& groupId)
} }
QList<int>::reverse_iterator its; QList<int>::reverse_iterator its;
for(its = indexesToRemove.rbegin(); its != indexesToRemove.rend(); ++its) { for (its = indexesToRemove.rbegin(); its != indexesToRemove.rend(); ++its) {
const auto row = *its; const auto row = *its;
beginRemoveRows({}, row, row); beginRemoveRows({}, row, row);
m_data.removeAt(row); m_data.removeAt(row);
@ -86,7 +84,7 @@ void ManageTokensModel::clear()
setDirty(false); setDirty(false);
} }
SerializedTokenData ManageTokensModel::save(bool isVisible) SerializedTokenData ManageTokensModel::save(bool isVisible, bool itemsAreGroups)
{ {
saveCustomSortOrder(); saveCustomSortOrder();
const auto size = rowCount(); const auto size = rowCount();
@ -96,21 +94,25 @@ SerializedTokenData ManageTokensModel::save(bool isVisible)
const auto& token = itemAt(i); const auto& token = itemAt(i);
const auto isCommunityGroup = !token.communityId.isEmpty(); const auto isCommunityGroup = !token.communityId.isEmpty();
const auto isCollectionGroup = !token.collectionUid.isEmpty(); const auto isCollectionGroup = !token.collectionUid.isEmpty();
const auto groupId = isCommunityGroup ? token.communityId : isCollectionGroup ? token.collectionUid : QString(); result.insert(token.symbol,
result.insert(token.symbol, {i, isVisible, groupId, isCommunityGroup, isCollectionGroup}); TokenOrder{token.symbol,
i,
isVisible,
isCommunityGroup,
token.communityId,
isCollectionGroup,
token.collectionUid,
tokenDataToCollectiblePreferencesItemType(token, isCommunityGroup, itemsAreGroups)});
} }
setDirty(false); setDirty(false);
return result; return result;
} }
int ManageTokensModel::rowCount(const QModelIndex& parent) const int ManageTokensModel::rowCount(const QModelIndex& parent) const { return m_data.size(); }
{
return m_data.size();
}
QHash<int, QByteArray> ManageTokensModel::roleNames() const QHash<int, QByteArray> ManageTokensModel::roleNames() const
{ {
static const QHash<int, QByteArray> roles { static const QHash<int, QByteArray> roles{
{SymbolRole, kSymbolRoleName}, {SymbolRole, kSymbolRoleName},
{NameRole, kNameRoleName}, {NameRole, kNameRoleName},
{CommunityIdRole, kCommunityIdRoleName}, {CommunityIdRole, kCommunityIdRoleName},
@ -134,42 +136,57 @@ QHash<int, QByteArray> ManageTokensModel::roleNames() const
QVariant ManageTokensModel::data(const QModelIndex& index, int role) const QVariant ManageTokensModel::data(const QModelIndex& index, int role) const
{ {
if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::ParentIsInvalid)) if (!checkIndex(index,
QAbstractItemModel::CheckIndexOption::IndexIsValid |
QAbstractItemModel::CheckIndexOption::ParentIsInvalid))
return {}; return {};
const auto& token = m_data.at(index.row()); const auto& token = m_data.at(index.row());
switch(static_cast<TokenDataRoles>(role)) switch (static_cast<TokenDataRoles>(role)) {
{ case SymbolRole:
case SymbolRole: return token.symbol; return token.symbol;
case NameRole: return token.name; case NameRole:
case CommunityIdRole: return token.communityId; return token.name;
case CommunityNameRole: return token.communityName; case CommunityIdRole:
case CommunityImageRole: return token.communityImage; return token.communityId;
case CollectionUidRole: return token.collectionUid; case CommunityNameRole:
case CollectionNameRole: return token.collectionName; return token.communityName;
case BalanceRole: return token.balance; case CommunityImageRole:
case CurrencyBalanceRole: return token.currencyBalance; return token.communityImage;
case CustomSortOrderNoRole: return token.customSortOrderNo; case CollectionUidRole:
case TokenImageRole: return token.image; return token.collectionUid;
case TokenBackgroundColorRole: return token.backgroundColor; case CollectionNameRole:
case TokenBalancesRole: return token.balances; return token.collectionName;
case TokenDecimalsRole: return token.decimals; case BalanceRole:
case TokenMarketDetailsRole: return token.marketDetails; return token.balance;
case IsSelfCollectionRole: return token.isSelfCollection; case CurrencyBalanceRole:
return token.currencyBalance;
case CustomSortOrderNoRole:
return token.customSortOrderNo;
case TokenImageRole:
return token.image;
case TokenBackgroundColorRole:
return token.backgroundColor;
case TokenBalancesRole:
return token.balances;
case TokenDecimalsRole:
return token.decimals;
case TokenMarketDetailsRole:
return token.marketDetails;
case IsSelfCollectionRole:
return token.isSelfCollection;
} }
return {}; return {};
} }
bool ManageTokensModel::dirty() const bool ManageTokensModel::dirty() const { return m_dirty; }
{
return m_dirty;
}
void ManageTokensModel::setDirty(bool flag) void ManageTokensModel::setDirty(bool flag)
{ {
if (m_dirty == flag) return; if (m_dirty == flag)
return;
m_dirty = flag; m_dirty = flag;
emit dirtyChanged(); emit dirtyChanged();
} }

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "tokendata.h"
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QColor> #include <QColor>
#include <QLoggingCategory> #include <QLoggingCategory>
@ -30,18 +32,6 @@ const auto kIsSelfCollectionRoleName = QByteArrayLiteral("isSelfCollection");
// TODO add communityPrivilegesLevel for collectibles // TODO add communityPrivilegesLevel for collectibles
} // namespace } // namespace
struct TokenData {
QString symbol, name, communityId, communityName, communityImage, collectionUid, collectionName, image;
QColor backgroundColor{Qt::transparent};
QVariant balance, currencyBalance;
QVariant balances, marketDetails, decimals;
int customSortOrderNo{INT_MAX};
bool isSelfCollection{false};
};
// symbol -> {sortOrder, visible, groupId, isCommunityGroup, isCollectionGroup}
using SerializedTokenData = QHash<QString, std::tuple<int, bool, QString, bool, bool>>;
class ManageTokensModel : public QAbstractListModel class ManageTokensModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
@ -78,7 +68,7 @@ public:
QList<TokenData> takeAllItems(const QString& groupId); QList<TokenData> takeAllItems(const QString& groupId);
void clear(); void clear();
SerializedTokenData save(bool isVisible = true); SerializedTokenData save(bool isVisible = true, bool itemsAreGroups = false);
bool dirty() const; bool dirty() const;
void setDirty(bool flag); void setDirty(bool flag);

View File

@ -0,0 +1,124 @@
#include "tokendata.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QList>
CollectiblePreferencesItemType tokenDataToCollectiblePreferencesItemType(const TokenData& token, bool isCommunity, bool itemsAreGroups)
{
if (itemsAreGroups) {
if (isCommunity) {
return CollectiblePreferencesItemType::Community;
} else {
return CollectiblePreferencesItemType::Collection;
}
} else {
if (isCommunity) {
return CollectiblePreferencesItemType::CommunityCollectible;
} else {
return CollectiblePreferencesItemType::NonCommunityCollectible;
}
}
}
TokenOrder::TokenOrder() : sortOrder(undefinedTokenOrder) {}
TokenOrder::TokenOrder(const QString& symbol,
int sortOrder,
bool visible,
bool isCommunityGroup,
const QString& communityId,
bool isCollectionGroup,
const QString& collectionUid,
CollectiblePreferencesItemType type)
: symbol(symbol)
, sortOrder(sortOrder)
, visible(visible)
, isCommunityGroup(isCommunityGroup)
, communityId(communityId)
, isCollectionGroup(isCollectionGroup)
, collectionUid(collectionUid)
, type(type)
{
}
/// reverse of \c tokenOrdersFromJson
///
/// for protocol structure, see:
/// \see CollectiblePreferences in src/backend/collectibles_types.nim
/// \see TokenPreferences in src/backend/backend.nim
QString tokenOrdersToJson(const SerializedTokenData& dataList, bool areCollectible)
{
QJsonArray jsonArray;
for (const TokenOrder& data : dataList) {
QJsonObject obj;
obj["key"] = data.symbol;
obj["position"] = data.sortOrder;
obj["visible"] = data.visible;
if (data.isCommunityGroup) {
obj["isCommunityGroup"] = true;
obj["communityId"] = data.communityId;
}
if (data.isCollectionGroup) {
obj["isCollectionGroup"] = true;
obj["collectionUid"] = data.collectionUid;
}
if (areCollectible) {
// see CollectiblePreferences in src/backend/collectibles_types.nim
// type cover separation of groups and collectibles
obj["type"] = static_cast<int>(data.type);
} else { // is asset
// see TokenPreferences in src/backend/backend.nim
// TODO #13312: handle "groupPosition" for asset
}
jsonArray.append(obj);
}
QJsonDocument doc(jsonArray);
QString json_string = doc.toJson(QJsonDocument::Compact);
return json_string;
}
/// reverse of \c tokenOrdersToJson
///
/// for protocol structure, see:
/// \see CollectiblePreferences in src/backend/collectibles_types.nim
/// \see TokenPreferences in src/backend/backend.nim
SerializedTokenData tokenOrdersFromJson(const QString& json_string, bool areCollectibles)
{
QJsonDocument doc = QJsonDocument::fromJson(json_string.toUtf8());
QJsonArray jsonArray = doc.array();
SerializedTokenData dataList;
for (const QJsonValue& value : jsonArray) {
QJsonObject obj = value.toObject();
TokenOrder data;
data.symbol = obj["key"].toString();
data.sortOrder = obj["position"].toInt();
data.visible = obj["visible"].toBool();
if (obj.contains("isCommunityGroup")) {
data.isCommunityGroup = obj["isCommunityGroup"].toBool();
data.communityId = obj["communityId"].toString();
}
if (obj.contains("isCollectionGroup")) {
data.isCollectionGroup = obj["isCollectionGroup"].toBool();
data.collectionUid = obj["collectionUid"].toString();
}
if (areCollectibles) {
// see CollectiblePreferences in src/backend/collectibles_types.nim
data.type = static_cast<CollectiblePreferencesItemType>(obj["type"].toInt());
} else { // is asset
// see TokenPreferences in src/backend/backend.nim
// TODO #13312: handle "groupPosition" for assets
}
dataList.insert(data.symbol, data);
}
return dataList;
}

View File

@ -0,0 +1,59 @@
#pragma once
#include <QColor>
#include <QString>
#include <QVariant>
static const auto undefinedTokenOrder = INT_MAX;
// Generic structure representing an asset, collectible, collection or community token
struct TokenData {
QString symbol, name, communityId, communityName, communityImage, collectionUid, collectionName, image;
QColor backgroundColor{Qt::transparent};
QVariant balance, currencyBalance;
QVariant balances, marketDetails, decimals;
int customSortOrderNo{undefinedTokenOrder};
bool isSelfCollection{false};
};
// mirrors CollectiblePreferencesItemType from src/backend/collectibles_types.nim
enum class CollectiblePreferencesItemType { NonCommunityCollectible = 1, CommunityCollectible, Collection, Community };
CollectiblePreferencesItemType tokenDataToCollectiblePreferencesItemType(const TokenData& tokenData, bool isCommunity, bool itemsAreGroups);
struct TokenOrder {
QString symbol;
int sortOrder;
bool visible;
bool isCommunityGroup;
QString communityId;
bool isCollectionGroup;
QString collectionUid;
/// covers separation of groups (collection or community) and collectibles (regular or community)
CollectiblePreferencesItemType type;
// Defines a default TokenOrder, order is not set (undefinedTokenOrder) and visible is false
TokenOrder();
TokenOrder(const QString& symbol,
int sortOrder,
bool visible,
bool isCommunityGroup,
const QString& communityId,
bool isCollectionGroup,
const QString& collectionUid,
CollectiblePreferencesItemType type);
bool operator==(const TokenOrder& rhs) const
{
return symbol == rhs.symbol && sortOrder == rhs.sortOrder && visible == rhs.visible &&
isCommunityGroup == rhs.isCommunityGroup && (!isCommunityGroup || communityId == rhs.communityId) &&
isCollectionGroup == rhs.isCollectionGroup && (!isCollectionGroup || collectionUid == rhs.collectionUid) && type == rhs.type;
}
QString getGroupId() const { return !communityId.isEmpty() ? communityId : collectionUid; }
};
using SerializedTokenData = QHash<QString, TokenOrder>;
QString tokenOrdersToJson(const SerializedTokenData& data, bool areCollectibles);
SerializedTokenData tokenOrdersFromJson(const QString& json_string, bool areCollectibles);

View File

@ -22,7 +22,8 @@ DoubleFlickableWithFolding {
property var getCurrentCurrencyAmount: function(balance) {} property var getCurrentCurrencyAmount: function(balance) {}
function saveSettings() { function saveSettings() {
root.controller.saveSettings(); let jsonSettings = root.controller.serializeSettingsAsJson()
root.controller.requestSaveSettings(jsonSettings);
} }
function revert() { function revert() {
@ -30,7 +31,7 @@ DoubleFlickableWithFolding {
} }
function clearSettings() { function clearSettings() {
root.controller.clearSettings(); root.controller.requestClearSettings()
} }
clip: true clip: true

View File

@ -19,7 +19,8 @@ DoubleFlickableWithFolding {
readonly property bool hasSettings: root.controller.hasSettings readonly property bool hasSettings: root.controller.hasSettings
function saveSettings() { function saveSettings() {
root.controller.saveSettings(); let jsonSettings = root.controller.serializeSettingsAsJson()
root.controller.requestSaveSettings(jsonSettings)
} }
function revert() { function revert() {
@ -27,7 +28,7 @@ DoubleFlickableWithFolding {
} }
function clearSettings() { function clearSettings() {
root.controller.clearSettings(); root.controller.requestClearSettings()
} }
clip: true clip: true

View File

@ -31,8 +31,8 @@ Control {
background: null background: null
function clearSettings() { function clearSettings() {
root.assetsController.clearSettings(); root.assetsController.requestClearSettings();
root.collectiblesController.clearSettings(); root.collectiblesController.requestClearSettings();
} }
QtObject { QtObject {

View File

@ -29,6 +29,24 @@ QtObject {
readonly property var collectiblesController: ManageTokensController { readonly property var collectiblesController: ManageTokensController {
sourceModel: allCollectiblesModel sourceModel: allCollectiblesModel
settingsKey: "WalletCollectibles" settingsKey: "WalletCollectibles"
serializeAsCollectibles: true
onRequestSaveSettings: (jsonData) => {
savingStarted()
_allCollectiblesModule.updateCollectiblePreferences(jsonData)
savingFinished()
}
onRequestLoadSettings: {
loadingStarted()
let jsonData = _allCollectiblesModule.getCollectiblePreferencesJson()
loadingFinished(jsonData)
}
onRequestClearSettings: {
savingStarted()
_allCollectiblesModule.clearCollectiblePreferences()
savingFinished()
}
onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage( onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage(
qsTr("%1 community collectibles successfully hidden").arg(communityName), "", "checkmark-circle", qsTr("%1 community collectibles successfully hidden").arg(communityName), "", "checkmark-circle",
false, Constants.ephemeralNotificationType.success, "") false, Constants.ephemeralNotificationType.success, "")

View File

@ -18,6 +18,25 @@ QtObject {
readonly property var assetsController: ManageTokensController { readonly property var assetsController: ManageTokensController {
sourceModel: groupedAccountAssetsModel sourceModel: groupedAccountAssetsModel
settingsKey: "WalletAssets" settingsKey: "WalletAssets"
serializeAsCollectibles: false
// TODO #13312: call the assets controller for all events
onRequestSaveSettings: (jsonData) => {
// savingStarted()
saveToQSettings(jsonData)
// savingFinished()
}
onRequestLoadSettings: {
// loadingStarted()
loadFromQSettings()
// loadingFinished()
}
onRequestClearSettings: {
// savingStarted()
clearQSettings()
// savingFinished()
}
onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage( onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage(
qsTr("%1 community assets successfully hidden").arg(communityName), "", "checkmark-circle", qsTr("%1 community assets successfully hidden").arg(communityName), "", "checkmark-circle",
false, Constants.ephemeralNotificationType.success, "") false, Constants.ephemeralNotificationType.success, "")