diff --git a/src/app/modules/main/wallet_section/all_collectibles/view.nim b/src/app/modules/main/wallet_section/all_collectibles/view.nim index 339f50c75e..8d394ff38b 100644 --- a/src/app/modules/main/wallet_section/all_collectibles/view.nim +++ b/src/app/modules/main/wallet_section/all_collectibles/view.nim @@ -32,6 +32,10 @@ QtObject: proc updateCollectiblePreferences*(self: View, collectiblePreferencesJson: string) {.slot.} = 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.} = let preferences = self.delegate.getCollectiblePreferencesJson() return newQVariant(preferences) diff --git a/src/backend/collectibles_types.nim b/src/backend/collectibles_types.nim index e8da69505e..3cca245f81 100644 --- a/src/backend/collectibles_types.nim +++ b/src/backend/collectibles_types.nim @@ -87,14 +87,14 @@ type contractAddress*: string owners*: seq[CollectibleOwner] - # see status-go/services/wallet/collectibles/service.go CollectibleDataType + # Mirrors status-go/multiaccounts/settings_wallet/database.go CollectiblePreferencesType CollectiblePreferencesItemType* {.pure.} = enum - NonCommunityCollectible = 1, - CommunityCollectible, - Collection, + NonCommunityCollectible = 1, + CommunityCollectible, + Collection, Community - # Mirrors services/wallet/thirdparty/collectible_types.go CollectibleContractOwnership + # Mirrors status-go/multiaccounts/settings_wallet/database.go CollectiblePreferences CollectiblePreferences* = ref object of RootObj itemType* {.serializedFieldName("type").}: CollectiblePreferencesItemType key* {.serializedFieldName("key").}: string diff --git a/storybook/pages/AssetsViewPage.qml b/storybook/pages/AssetsViewPage.qml index 29b29696b7..c9df721c0c 100644 --- a/storybook/pages/AssetsViewPage.qml +++ b/storybook/pages/AssetsViewPage.qml @@ -97,6 +97,12 @@ SplitView { controller: ManageTokensController { sourceModel: d.walletAssetStore.groupedAccountAssetsModel settingsKey: "WalletAssets" + serializeAsCollectibles: false + + onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData) + onRequestLoadSettings: loadFromQSettings() + onRequestClearSettings: clearQSettings() + onTokenHidden: (symbol, name) => Global.displayToastMessage( qsTr("%1 (%2) was successfully hidden").arg(name).arg(symbol), "", "checkmark-circle", false, Constants.ephemeralNotificationType.success, "") diff --git a/storybook/pages/CollectiblesViewPage.qml b/storybook/pages/CollectiblesViewPage.qml index de30950da4..6ed2fb528b 100644 --- a/storybook/pages/CollectiblesViewPage.qml +++ b/storybook/pages/CollectiblesViewPage.qml @@ -82,6 +82,12 @@ SplitView { controller: ManageTokensController { sourceModel: renamedModel settingsKey: "WalletCollectibles" + serializeAsCollectibles: true + + onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData) + onRequestLoadSettings: loadFromQSettings() + onRequestClearSettings: clearQSettings() + onTokenHidden: (symbol, name) => Global.displayToastMessage( qsTr("%1 was successfully hidden").arg(name), "", "checkmark-circle", false, Constants.ephemeralNotificationType.success, "") diff --git a/storybook/pages/ManageAssetsPanelPage.qml b/storybook/pages/ManageAssetsPanelPage.qml index d6babd399d..ecda3c610c 100644 --- a/storybook/pages/ManageAssetsPanelPage.qml +++ b/storybook/pages/ManageAssetsPanelPage.qml @@ -50,6 +50,11 @@ SplitView { controller: ManageTokensController { sourceModel: ctrlEmptyModel.checked ? null : walletAssetStore.groupedAccountAssetsModel settingsKey: "WalletAssets" + serializeAsCollectibles: false + + onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData) + onRequestLoadSettings: loadFromQSettings() + onRequestClearSettings: clearQSettings() } } diff --git a/storybook/pages/ManageCollectiblesPanelPage.qml b/storybook/pages/ManageCollectiblesPanelPage.qml index 25bbaf613a..a513ccffdf 100644 --- a/storybook/pages/ManageCollectiblesPanelPage.qml +++ b/storybook/pages/ManageCollectiblesPanelPage.qml @@ -44,6 +44,11 @@ SplitView { controller: ManageTokensController { sourceModel: renamedModel settingsKey: "WalletCollectibles" + serializeAsCollectibles: true + + onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData) + onRequestLoadSettings: loadFromQSettings() + onRequestClearSettings: clearQSettings() } } diff --git a/storybook/pages/ManageHiddenPanelPage.qml b/storybook/pages/ManageHiddenPanelPage.qml index cbd8fe1d31..5de6173a7f 100644 --- a/storybook/pages/ManageHiddenPanelPage.qml +++ b/storybook/pages/ManageHiddenPanelPage.qml @@ -44,12 +44,22 @@ SplitView { id: assetsController sourceModel: ctrlEmptyModel.checked ? null : walletAssetStore.groupedAccountAssetsModel settingsKey: "WalletAssets" + serializeAsCollectibles: false + + onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData) + onRequestLoadSettings: loadFromQSettings() + onRequestClearSettings: clearQSettings() } ManageTokensController { id: collectiblesController sourceModel: ctrlEmptyModel.checked ? null : renamedModel settingsKey: "WalletCollectibles" + serializeAsCollectibles: true + + onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData) + onRequestLoadSettings: loadFromQSettings() + onRequestClearSettings: clearQSettings() } ManageHiddenPanel { diff --git a/storybook/qmlTests/tests/tst_ManageCollectiblesPanel.qml b/storybook/qmlTests/tests/tst_ManageCollectiblesPanel.qml index 305a78b07a..3c1b7353e0 100644 --- a/storybook/qmlTests/tests/tst_ManageCollectiblesPanel.qml +++ b/storybook/qmlTests/tests/tst_ManageCollectiblesPanel.qml @@ -37,10 +37,20 @@ Item { controller: ManageTokensController { sourceModel: renamedModel settingsKey: "WalletCollectibles" + serializeAsCollectibles: true + + onRequestSaveSettings: (jsonData) => saveToQSettings(jsonData) + onRequestLoadSettings: loadFromQSettings() + onRequestClearSettings: clearQSettings() + onCommunityTokenGroupHidden: (communityName) => Global.displayToastMessage( qsTr("%1 community collectibles successfully hidden").arg(communityName), "", "checkmark-circle", false, Constants.ephemeralNotificationType.success, "") } + + function clearSettings() { + controller.clearQSettings() + } } } diff --git a/ui/StatusQ/CMakeLists.txt b/ui/StatusQ/CMakeLists.txt index e88ad1e652..e6b55e9f8b 100644 --- a/ui/StatusQ/CMakeLists.txt +++ b/ui/StatusQ/CMakeLists.txt @@ -141,6 +141,8 @@ add_library(StatusQ SHARED src/wallet/managetokenscontroller.h src/wallet/managetokensmodel.cpp src/wallet/managetokensmodel.h + src/wallet/tokendata.cpp + src/wallet/tokendata.h ) target_compile_features(StatusQ PRIVATE cxx_std_17) diff --git a/ui/StatusQ/src/wallet/managetokenscontroller.cpp b/ui/StatusQ/src/wallet/managetokenscontroller.cpp index 7569ca409e..71f662cba5 100644 --- a/ui/StatusQ/src/wallet/managetokenscontroller.cpp +++ b/ui/StatusQ/src/wallet/managetokenscontroller.cpp @@ -1,6 +1,6 @@ #include "managetokenscontroller.h" -#include +#include "tokendata.h" #include #include @@ -26,30 +26,38 @@ ManageTokensController::ManageTokensController(QObject* parent) } if (m_modelConnectionsInitialized) 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 - QElapsedTimer t; - t.start(); - qCDebug(manageTokens) << "!!! ADDING" << last-first+1 << "NEW TOKENS"; + QElapsedTimer t; + t.start(); + qCDebug(manageTokens) << "!!! ADDING" << last - first + 1 << "NEW TOKENS"; #endif - for (int i = first; i <= last; i++) - addItem(i); + for (int i = first; i <= last; i++) + addItem(i); - rebuildCommunityTokenGroupsModel(); - rebuildHiddenCommunityTokenGroupsModel(); - rebuildCollectionGroupsModel(); - rebuildHiddenCollectionGroupsModel(); + rebuildCommunityTokenGroupsModel(); + rebuildHiddenCommunityTokenGroupsModel(); + rebuildCollectionGroupsModel(); + rebuildHiddenCollectionGroupsModel(); - for (auto model: m_allModels) { - model->applySort(); - model->saveCustomSortOrder(); - } + for (auto model : m_allModels) { + model->applySort(); + model->saveCustomSortOrder(); + } #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 }); - connect(m_sourceModel, &QAbstractItemModel::rowsRemoved, this, &ManageTokensController::parseSourceModel); - 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::rowsRemoved, this, &ManageTokensController::requestLoadSettings); + 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; }); } @@ -69,7 +77,7 @@ void ManageTokensController::showHideRegularToken(const QString& symbol, bool fl emit tokenHidden(shownItem->symbol, shownItem->name); } } - saveSettings(); + requestSaveSettings(serializeSettingsAsJson()); } void ManageTokensController::showHideCommunityToken(const QString& symbol, bool flag) @@ -90,7 +98,7 @@ void ManageTokensController::showHideCommunityToken(const QString& symbol, bool m_communityTokensModel->saveCustomSortOrder(); rebuildCommunityTokenGroupsModel(); rebuildHiddenCommunityTokenGroupsModel(); - saveSettings(); + requestSaveSettings(serializeSettingsAsJson()); } void ManageTokensController::showHideGroup(const QString& groupId, bool flag) @@ -98,7 +106,7 @@ void ManageTokensController::showHideGroup(const QString& groupId, bool flag) if (flag) { // show const auto tokens = m_hiddenTokensModel->takeAllItems(groupId); if (!tokens.isEmpty()) { - for (const auto& token: tokens) { + for (const auto& token : tokens) { m_communityTokensModel->addItem(token); } emit communityTokenGroupShown(tokens.constFirst().communityName); @@ -109,7 +117,7 @@ void ManageTokensController::showHideGroup(const QString& groupId, bool flag) } else { // hide const auto tokens = m_communityTokensModel->takeAllItems(groupId); if (!tokens.isEmpty()) { - for (const auto& token: tokens) { + for (const auto& token : tokens) { m_hiddenTokensModel->addItem(token, false /*prepend*/); } emit communityTokenGroupHidden(tokens.constFirst().communityName); @@ -122,7 +130,7 @@ void ManageTokensController::showHideGroup(const QString& groupId, bool flag) rebuildCommunityTokenGroupsModel(); m_communityTokenGroupsModel->applySort(); rebuildHiddenCommunityTokenGroupsModel(); - saveSettings(); + requestSaveSettings(serializeSettingsAsJson()); } void ManageTokensController::showHideCollectionGroup(const QString& groupId, bool flag) @@ -130,7 +138,7 @@ void ManageTokensController::showHideCollectionGroup(const QString& groupId, boo if (flag) { // show const auto tokens = m_hiddenTokensModel->takeAllItems(groupId); if (!tokens.isEmpty()) { - for (const auto& token: tokens) { + for (const auto& token : tokens) { m_regularTokensModel->addItem(token); } emit collectionTokenGroupShown(tokens.constFirst().collectionName); @@ -141,7 +149,7 @@ void ManageTokensController::showHideCollectionGroup(const QString& groupId, boo } else { // hide const auto tokens = m_regularTokensModel->takeAllItems(groupId); if (!tokens.isEmpty()) { - for (const auto& token: tokens) { + for (const auto& token : tokens) { m_hiddenTokensModel->addItem(token, false /*prepend*/); } emit collectionTokenGroupHidden(tokens.constFirst().collectionName); @@ -154,71 +162,28 @@ void ManageTokensController::showHideCollectionGroup(const QString& groupId, boo rebuildCollectionGroupsModel(); m_collectionGroupsModel->applySort(); rebuildHiddenCollectionGroupsModel(); - saveSettings(); + requestSaveSettings(serializeSettingsAsJson()); } -void ManageTokensController::saveSettings() +void ManageTokensController::saveToQSettings(const QString& json) { Q_ASSERT(!m_settingsKey.isEmpty()); - setSettingsDirty(true); - - // 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)); + savingStarted(); // save to QSettings m_settings.beginGroup(settingsGroupName()); - // arrange by - m_settings.setValue(QStringLiteral("ArrangeByCommunity"), m_arrangeByCommunity); - m_settings.setValue(QStringLiteral("ArrangeByCollection"), m_arrangeByCollection); - // data - m_settings.beginWriteArray(m_settingsKey); - 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.setValue(m_settingsKey, json); m_settings.endGroup(); m_settings.sync(); - // unset dirty - for (auto model: m_allModels) - model->setDirty(false); - - loadSettingsData(true); // reload positions and visibility - - incRevision(); - - setSettingsDirty(false); + savingFinished(); } -void ManageTokensController::clearSettings() +void ManageTokensController::clearQSettings() { Q_ASSERT(!m_settingsKey.isEmpty()); @@ -231,73 +196,24 @@ void ManageTokensController::clearSettings() emit settingsDirtyChanged(false); } -void ManageTokensController::loadSettingsData(bool withGroup) -{ - 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() +void ManageTokensController::loadFromQSettings() { Q_ASSERT(!m_settingsKey.isEmpty()); - setSettingsDirty(true); - m_settingsData.clear(); + loadingStarted(); // load from QSettings m_settings.beginGroup(settingsGroupName()); - - 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()); - + const auto jsonData = m_settings.value(m_settingsKey).toString(); m_settings.endGroup(); - setSettingsDirty(false); + loadingFinished(jsonData); } void ManageTokensController::setSettingsDirty(bool dirty) { - if (m_settingsDirty == dirty) return; + if (m_settingsDirty == dirty) + return; m_settingsDirty = dirty; emit settingsDirtyChanged(m_settingsDirty); } @@ -318,9 +234,81 @@ QStringList ManageTokensController::hiddenCollectionGroups() const 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(); + 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 @@ -337,26 +325,12 @@ bool ManageTokensController::hasSettings() const int ManageTokensController::compareTokens(const QString& lhsSymbol, const QString& rhsSymbol) const { - constexpr auto defaultVal = std::make_tuple(INT_MAX, false, QLatin1String(), false, false); - - 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)); - } + const auto left = m_settingsData.value(lhsSymbol, TokenOrder()); + const auto right = m_settingsData.value(rhsSymbol, TokenOrder()); // check if visible - leftPos = leftVisible ? leftPos : INT_MAX; - rightPos = rightVisible ? rightPos : INT_MAX; + auto leftPos = left.visible ? left.sortOrder : undefinedTokenOrder; + auto rightPos = right.visible ? right.sortOrder : undefinedTokenOrder; if (leftPos < rightPos) return -1; @@ -367,9 +341,13 @@ int ManageTokensController::compareTokens(const QString& lhsSymbol, const QStrin 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() @@ -377,19 +355,17 @@ void ManageTokensController::classBegin() // empty on purpose } -void ManageTokensController::componentComplete() -{ - loadSettings(); -} +void ManageTokensController::componentComplete() { requestLoadSettings(); } void ManageTokensController::setSourceModel(QAbstractItemModel* newSourceModel) { - if(m_sourceModel == newSourceModel) return; + if (m_sourceModel == newSourceModel) + return; - if(!newSourceModel) { + if (!newSourceModel) { disconnect(sourceModel()); // clear all the models - for (auto model: m_allModels) + for (auto model : m_allModels) model->clear(); m_settingsData.clear(); m_hiddenCommunityGroups.clear(); @@ -402,14 +378,15 @@ void ManageTokensController::setSourceModel(QAbstractItemModel* 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 - connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &ManageTokensController::parseSourceModel); + connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &ManageTokensController::requestLoadSettings); return; } else { - parseSourceModel(); + requestLoadSettings(); } } @@ -418,7 +395,7 @@ void ManageTokensController::parseSourceModel() if (!m_sourceModel) return; - disconnect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &ManageTokensController::parseSourceModel); + disconnect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, &ManageTokensController::requestLoadSettings); #ifdef QT_DEBUG QElapsedTimer t; @@ -426,12 +403,9 @@ void ManageTokensController::parseSourceModel() #endif // clear all the models - for (auto model: m_allModels) + for (auto model : m_allModels) model->clear(); - // load settings - loadSettings(); - // read and transform the original data const auto newSize = m_sourceModel->rowCount(); qCDebug(manageTokens) << "!!! PARSING" << newSize << "TOKENS"; @@ -448,13 +422,13 @@ void ManageTokensController::parseSourceModel() rebuildHiddenCollectionGroupsModel(); // (pre)sort - for (auto model: m_allModels) { + for (auto model : m_allModels) { model->applySort(); model->setDirty(false); } #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 emit sourceModelChanged(); @@ -464,7 +438,7 @@ void ManageTokensController::addItem(int index) { 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); if (key == -1) return {}; @@ -475,7 +449,7 @@ void ManageTokensController::addItem(int index) const auto symbol = dataForIndex(srcIndex, kSymbolRoleName).toString(); const auto communityId = dataForIndex(srcIndex, kCommunityIdRoleName).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(); const auto collectionUid = dataForIndex(srcIndex, kCollectionUidRoleName).toString(); @@ -497,8 +471,8 @@ void ManageTokensController::addItem(int index) token.decimals = dataForIndex(srcIndex, kDecimalsRoleName); token.marketDetails = dataForIndex(srcIndex, kMarketDetailsRoleName); - token.customSortOrderNo = m_settingsData.contains(symbol) ? std::get<0>(m_settingsData.value(symbol)) - : (visible ? INT_MAX : 0); // append/prepend + token.customSortOrderNo = m_settingsData.contains(symbol) ? m_settingsData.value(symbol).sortOrder + : (visible ? undefinedTokenOrder : 0); // append/prepend if (!visible) m_hiddenTokensModel->addItem(token, /*append*/ false); @@ -510,19 +484,15 @@ void ManageTokensController::addItem(int index) bool ManageTokensController::dirty() const { - return std::any_of(m_allModels.cbegin(), m_allModels.cend(), [](auto model) { - return model->dirty(); - }); + return std::any_of(m_allModels.cbegin(), m_allModels.cend(), [](auto model) { return model->dirty(); }); } -bool ManageTokensController::arrangeByCommunity() const -{ - return m_arrangeByCommunity; -} +bool ManageTokensController::arrangeByCommunity() const { return m_arrangeByCommunity; } void ManageTokensController::setArrangeByCommunity(bool newArrangeByCommunity) { - if(m_arrangeByCommunity == newArrangeByCommunity) return; + if (m_arrangeByCommunity == newArrangeByCommunity) + return; m_arrangeByCommunity = newArrangeByCommunity; if (m_arrangeByCommunity) { rebuildCommunityTokenGroupsModel(); @@ -532,14 +502,12 @@ void ManageTokensController::setArrangeByCommunity(bool newArrangeByCommunity) emit arrangeByCommunityChanged(); } -bool ManageTokensController::arrangeByCollection() const -{ - return m_arrangeByCollection; -} +bool ManageTokensController::arrangeByCollection() const { return m_arrangeByCollection; } void ManageTokensController::setArrangeByCollection(bool newArrangeByCollection) { - if(m_arrangeByCollection == newArrangeByCollection) return; + if (m_arrangeByCollection == newArrangeByCollection) + return; m_arrangeByCollection = newArrangeByCollection; if (m_arrangeByCollection) { rebuildCollectionGroupsModel(); @@ -570,7 +538,7 @@ void ManageTokensController::rebuildCommunityTokenGroupsModel() tokenGroup.balance = 1; if (m_settingsData.contains(communityId)) { - tokenGroup.customSortOrderNo = std::get<0>(m_settingsData.value(communityId)); + tokenGroup.customSortOrderNo = m_settingsData.value(communityId).sortOrder; } result.append(tokenGroup); @@ -587,7 +555,7 @@ void ManageTokensController::rebuildCommunityTokenGroupsModel() } m_communityTokenGroupsModel->clear(); - for (const auto& group: std::as_const(result)) + for (const auto& group : std::as_const(result)) m_communityTokenGroupsModel->addItem(group); qCDebug(manageTokens) << "!!! GROUPS MODEL REBUILT WITH GROUPS:" << communityIds; @@ -604,7 +572,8 @@ void ManageTokensController::rebuildHiddenCommunityTokenGroupsModel() const auto communityId = communityToken.communityId; if (communityId.isEmpty()) 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); TokenData tokenGroup; @@ -628,7 +597,7 @@ void ManageTokensController::rebuildHiddenCommunityTokenGroupsModel() } m_hiddenCommunityTokenGroupsModel->clear(); - for (const auto& group: std::as_const(result)) + for (const auto& group : std::as_const(result)) m_hiddenCommunityTokenGroupsModel->addItem(group); qCDebug(manageTokens) << "!!! HIDDEN GROUPS MODEL REBUILT WITH GROUPS:" << communityIds; @@ -647,7 +616,8 @@ void ManageTokensController::rebuildCollectionGroupsModel() if (!collectionIds.contains(collectionId)) { // insert into groups 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; tokenGroup.symbol = collectionId; @@ -659,7 +629,7 @@ void ManageTokensController::rebuildCollectionGroupsModel() tokenGroup.balance = 1; if (m_settingsData.contains(collectionId)) { - tokenGroup.customSortOrderNo = std::get<0>(m_settingsData.value(collectionId)); + tokenGroup.customSortOrderNo = m_settingsData.value(collectionId).sortOrder; } result.append(tokenGroup); @@ -676,7 +646,7 @@ void ManageTokensController::rebuildCollectionGroupsModel() } m_collectionGroupsModel->clear(); - for (const auto& group: std::as_const(result)) + for (const auto& group : std::as_const(result)) m_collectionGroupsModel->addItem(group); qCDebug(manageTokens) << "!!! COLLECTION MODEL REBUILT WITH GROUPS:" << collectionIds; @@ -692,10 +662,12 @@ void ManageTokensController::rebuildHiddenCollectionGroupsModel() const auto& collectionToken = m_hiddenTokensModel->itemAt(i); const auto collectionId = collectionToken.collectionUid; 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); - const auto collectionName = !collectionToken.collectionName.isEmpty() ? collectionToken.collectionName : collectionToken.name; + const auto collectionName = + !collectionToken.collectionName.isEmpty() ? collectionToken.collectionName : collectionToken.name; TokenData tokenGroup; tokenGroup.symbol = collectionId; @@ -719,16 +691,13 @@ void ManageTokensController::rebuildHiddenCollectionGroupsModel() } m_hiddenCollectionGroupsModel->clear(); - for (const auto& group: std::as_const(result)) + for (const auto& group : std::as_const(result)) m_hiddenCollectionGroupsModel->addItem(group); qCDebug(manageTokens) << "!!! HIDDEN COLLECTION GROUPS MODEL REBUILT WITH GROUPS:" << collectionIds; } -QString ManageTokensController::settingsKey() const -{ - return m_settingsKey; -} +QString ManageTokensController::settingsKey() const { return m_settingsKey; } void ManageTokensController::setSettingsKey(const QString& newSettingsKey) { @@ -737,3 +706,13 @@ void ManageTokensController::setSettingsKey(const QString& newSettingsKey) m_settingsKey = newSettingsKey; 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(); +} \ No newline at end of file diff --git a/ui/StatusQ/src/wallet/managetokenscontroller.h b/ui/StatusQ/src/wallet/managetokenscontroller.h index 6111f3f32e..9905d663a2 100644 --- a/ui/StatusQ/src/wallet/managetokenscontroller.h +++ b/ui/StatusQ/src/wallet/managetokenscontroller.h @@ -8,6 +8,12 @@ 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 { Q_OBJECT @@ -16,6 +22,7 @@ class ManageTokensController : public QObject, public QQmlParserStatus // input properties 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(bool serializeAsCollectibles READ serializeAsCollectibles WRITE setSerializeAsCollectibles NOTIFY serializeAsCollectiblesChanged FINAL REQUIRED) Q_PROPERTY(bool arrangeByCommunity READ arrangeByCommunity WRITE setArrangeByCommunity NOTIFY arrangeByCommunityChanged 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 showHideCollectionGroup(const QString& groupId, bool flag); - Q_INVOKABLE void loadSettings(); - Q_INVOKABLE void saveSettings(); - Q_INVOKABLE void clearSettings(); + Q_INVOKABLE void loadFromQSettings(); + Q_INVOKABLE void saveToQSettings(const QString& json); + Q_INVOKABLE void clearQSettings(); 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 bool filterAcceptsSymbol(const QString& symbol) const; @@ -64,6 +79,7 @@ signals: void arrangeByCollectionChanged(); void settingsKeyChanged(); void settingsDirtyChanged(bool dirty); + void serializeAsCollectiblesChanged(); void tokenHidden(const QString& symbol, const QString& name); void tokenShown(const QString& symbol, const QString& name); @@ -77,6 +93,17 @@ signals: 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: QAbstractItemModel* m_sourceModel{nullptr}; QAbstractItemModel* sourceModel() const { return m_sourceModel; } @@ -128,6 +155,11 @@ private: QString settingsKey() const; QString settingsGroupName() const; void setSettingsKey(const QString& newSettingsKey); + + bool m_serializeAsCollectibles{false}; + bool serializeAsCollectibles() const; + void setSerializeAsCollectibles(const bool newSerializeAsCollectibles); + QSettings m_settings; SerializedTokenData m_settingsData; // symbol -> {sortOrder, visible, groupId, isCommunityGroup, isCollectionGroup} bool hasSettings() const; diff --git a/ui/StatusQ/src/wallet/managetokensmodel.cpp b/ui/StatusQ/src/wallet/managetokensmodel.cpp index f7fba0969e..c206c57574 100644 --- a/ui/StatusQ/src/wallet/managetokensmodel.cpp +++ b/ui/StatusQ/src/wallet/managetokensmodel.cpp @@ -4,8 +4,7 @@ Q_LOGGING_CATEGORY(manageTokens, "status.models.manageTokens", QtInfoMsg) -ManageTokensModel::ManageTokensModel(QObject* parent) - : QAbstractListModel(parent) +ManageTokensModel::ManageTokensModel(QObject* parent) : QAbstractListModel(parent) { connect(this, &QAbstractItemModel::rowsInserted, this, &ManageTokensModel::countChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &ManageTokensModel::countChanged); @@ -40,9 +39,8 @@ void ManageTokensModel::addItem(const TokenData& item, bool append) std::optional ManageTokensModel::takeItem(const QString& symbol) { - const auto token = std::find_if(m_data.cbegin(), m_data.cend(), [symbol](const auto& item) { - return symbol == item.symbol; - }); + const auto token = + 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); if (row < 0 || row >= rowCount()) @@ -60,7 +58,7 @@ QList ManageTokensModel::takeAllItems(const QString& groupId) QList indexesToRemove; 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) { result.append(token); indexesToRemove.append(i); @@ -68,7 +66,7 @@ QList ManageTokensModel::takeAllItems(const QString& groupId) } QList::reverse_iterator its; - for(its = indexesToRemove.rbegin(); its != indexesToRemove.rend(); ++its) { + for (its = indexesToRemove.rbegin(); its != indexesToRemove.rend(); ++its) { const auto row = *its; beginRemoveRows({}, row, row); m_data.removeAt(row); @@ -86,7 +84,7 @@ void ManageTokensModel::clear() setDirty(false); } -SerializedTokenData ManageTokensModel::save(bool isVisible) +SerializedTokenData ManageTokensModel::save(bool isVisible, bool itemsAreGroups) { saveCustomSortOrder(); const auto size = rowCount(); @@ -96,21 +94,25 @@ SerializedTokenData ManageTokensModel::save(bool isVisible) const auto& token = itemAt(i); const auto isCommunityGroup = !token.communityId.isEmpty(); const auto isCollectionGroup = !token.collectionUid.isEmpty(); - const auto groupId = isCommunityGroup ? token.communityId : isCollectionGroup ? token.collectionUid : QString(); - result.insert(token.symbol, {i, isVisible, groupId, isCommunityGroup, isCollectionGroup}); + result.insert(token.symbol, + TokenOrder{token.symbol, + i, + isVisible, + isCommunityGroup, + token.communityId, + isCollectionGroup, + token.collectionUid, + tokenDataToCollectiblePreferencesItemType(token, isCommunityGroup, itemsAreGroups)}); } setDirty(false); return result; } -int ManageTokensModel::rowCount(const QModelIndex& parent) const -{ - return m_data.size(); -} +int ManageTokensModel::rowCount(const QModelIndex& parent) const { return m_data.size(); } QHash ManageTokensModel::roleNames() const { - static const QHash roles { + static const QHash roles{ {SymbolRole, kSymbolRoleName}, {NameRole, kNameRoleName}, {CommunityIdRole, kCommunityIdRoleName}, @@ -134,42 +136,57 @@ QHash ManageTokensModel::roleNames() 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 {}; const auto& token = m_data.at(index.row()); - switch(static_cast(role)) - { - case SymbolRole: return token.symbol; - case NameRole: return token.name; - case CommunityIdRole: return token.communityId; - case CommunityNameRole: return token.communityName; - case CommunityImageRole: return token.communityImage; - case CollectionUidRole: return token.collectionUid; - case CollectionNameRole: return token.collectionName; - case BalanceRole: return token.balance; - 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; + switch (static_cast(role)) { + case SymbolRole: + return token.symbol; + case NameRole: + return token.name; + case CommunityIdRole: + return token.communityId; + case CommunityNameRole: + return token.communityName; + case CommunityImageRole: + return token.communityImage; + case CollectionUidRole: + return token.collectionUid; + case CollectionNameRole: + return token.collectionName; + case BalanceRole: + return token.balance; + 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 {}; } -bool ManageTokensModel::dirty() const -{ - return m_dirty; -} +bool ManageTokensModel::dirty() const { return m_dirty; } void ManageTokensModel::setDirty(bool flag) { - if (m_dirty == flag) return; + if (m_dirty == flag) + return; m_dirty = flag; emit dirtyChanged(); } diff --git a/ui/StatusQ/src/wallet/managetokensmodel.h b/ui/StatusQ/src/wallet/managetokensmodel.h index 30f815bacc..b9105a85d1 100644 --- a/ui/StatusQ/src/wallet/managetokensmodel.h +++ b/ui/StatusQ/src/wallet/managetokensmodel.h @@ -1,5 +1,7 @@ #pragma once +#include "tokendata.h" + #include #include #include @@ -30,18 +32,6 @@ const auto kIsSelfCollectionRoleName = QByteArrayLiteral("isSelfCollection"); // TODO add communityPrivilegesLevel for collectibles } // 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>; - class ManageTokensModel : public QAbstractListModel { Q_OBJECT @@ -78,7 +68,7 @@ public: QList takeAllItems(const QString& groupId); void clear(); - SerializedTokenData save(bool isVisible = true); + SerializedTokenData save(bool isVisible = true, bool itemsAreGroups = false); bool dirty() const; void setDirty(bool flag); diff --git a/ui/StatusQ/src/wallet/tokendata.cpp b/ui/StatusQ/src/wallet/tokendata.cpp new file mode 100644 index 0000000000..e8a1f6a210 --- /dev/null +++ b/ui/StatusQ/src/wallet/tokendata.cpp @@ -0,0 +1,124 @@ +#include "tokendata.h" + +#include +#include +#include +#include + +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(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(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; +} \ No newline at end of file diff --git a/ui/StatusQ/src/wallet/tokendata.h b/ui/StatusQ/src/wallet/tokendata.h new file mode 100644 index 0000000000..1d4f269386 --- /dev/null +++ b/ui/StatusQ/src/wallet/tokendata.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +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 tokenOrdersToJson(const SerializedTokenData& data, bool areCollectibles); +SerializedTokenData tokenOrdersFromJson(const QString& json_string, bool areCollectibles); diff --git a/ui/app/AppLayouts/Wallet/panels/ManageAssetsPanel.qml b/ui/app/AppLayouts/Wallet/panels/ManageAssetsPanel.qml index 514bfc7668..5fa30645c6 100644 --- a/ui/app/AppLayouts/Wallet/panels/ManageAssetsPanel.qml +++ b/ui/app/AppLayouts/Wallet/panels/ManageAssetsPanel.qml @@ -22,7 +22,8 @@ DoubleFlickableWithFolding { property var getCurrentCurrencyAmount: function(balance) {} function saveSettings() { - root.controller.saveSettings(); + let jsonSettings = root.controller.serializeSettingsAsJson() + root.controller.requestSaveSettings(jsonSettings); } function revert() { @@ -30,7 +31,7 @@ DoubleFlickableWithFolding { } function clearSettings() { - root.controller.clearSettings(); + root.controller.requestClearSettings() } clip: true diff --git a/ui/app/AppLayouts/Wallet/panels/ManageCollectiblesPanel.qml b/ui/app/AppLayouts/Wallet/panels/ManageCollectiblesPanel.qml index 90f21a446e..f05dd521f9 100644 --- a/ui/app/AppLayouts/Wallet/panels/ManageCollectiblesPanel.qml +++ b/ui/app/AppLayouts/Wallet/panels/ManageCollectiblesPanel.qml @@ -19,7 +19,8 @@ DoubleFlickableWithFolding { readonly property bool hasSettings: root.controller.hasSettings function saveSettings() { - root.controller.saveSettings(); + let jsonSettings = root.controller.serializeSettingsAsJson() + root.controller.requestSaveSettings(jsonSettings) } function revert() { @@ -27,7 +28,7 @@ DoubleFlickableWithFolding { } function clearSettings() { - root.controller.clearSettings(); + root.controller.requestClearSettings() } clip: true diff --git a/ui/app/AppLayouts/Wallet/panels/ManageHiddenPanel.qml b/ui/app/AppLayouts/Wallet/panels/ManageHiddenPanel.qml index 44044ffaa6..9fd96d75b2 100644 --- a/ui/app/AppLayouts/Wallet/panels/ManageHiddenPanel.qml +++ b/ui/app/AppLayouts/Wallet/panels/ManageHiddenPanel.qml @@ -31,8 +31,8 @@ Control { background: null function clearSettings() { - root.assetsController.clearSettings(); - root.collectiblesController.clearSettings(); + root.assetsController.requestClearSettings(); + root.collectiblesController.requestClearSettings(); } QtObject { diff --git a/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml b/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml index e0a12302d8..c571cddfad 100644 --- a/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/CollectiblesStore.qml @@ -29,6 +29,24 @@ QtObject { readonly property var collectiblesController: ManageTokensController { sourceModel: allCollectiblesModel 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( qsTr("%1 community collectibles successfully hidden").arg(communityName), "", "checkmark-circle", false, Constants.ephemeralNotificationType.success, "") diff --git a/ui/app/AppLayouts/Wallet/stores/WalletAssetsStore.qml b/ui/app/AppLayouts/Wallet/stores/WalletAssetsStore.qml index 87f9c3736d..d45003f255 100644 --- a/ui/app/AppLayouts/Wallet/stores/WalletAssetsStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/WalletAssetsStore.qml @@ -18,6 +18,25 @@ QtObject { readonly property var assetsController: ManageTokensController { sourceModel: groupedAccountAssetsModel 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( qsTr("%1 community assets successfully hidden").arg(communityName), "", "checkmark-circle", false, Constants.ephemeralNotificationType.success, "")