From 7cf0170a8ac7743a16fea3e8622ff8f160c748eb Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 26 Jul 2022 13:49:03 +0200 Subject: [PATCH] chore(CPP): integrate tokens balance in UI POC Integrate token count in UI Use delete later for QML exposed items to avoid errors Closes #6321 --- app/README.md | 2 +- libs/Helpers/src/Helpers/QObjectVectorModel.h | 2 + libs/Helpers/src/Helpers/helpers.h | 24 +++++ .../Status/Onboarding/SetupNewProfileView.qml | 4 +- .../qml/Status/Onboarding/TempTextInput.qml | 1 + .../qml/Status/Onboarding/WelcomeView.qml | 13 +-- .../base/SetupNewProfilePageBase.qml | 4 +- .../src/Onboarding/OnboardingController.cpp | 6 +- .../src/Onboarding/OnboardingController.h | 2 +- .../src/Onboarding/OnboardingModule.cpp | 3 +- .../OnboardingTestHelpers/CMakeLists.txt | 4 - .../ScopedTestAccount.cpp | 6 +- .../tests/test_OnboardingController.cpp | 4 +- libs/StatusGoQt/src/StatusGo/Wallet/Token.h | 3 +- .../src/StatusGo/Wallet/WalletApi.h | 5 +- libs/Wallet/CMakeLists.txt | 6 +- .../Status/Wallet/AccountAssetsController.h | 54 ++++++++++ .../Status/Wallet/DerivedWalletAddress.h | 12 ++- .../Wallet/NewWalletAccountController.h | 11 +- .../include/Status/Wallet/WalletAccount.h | 8 +- .../include/Status/Wallet/WalletAsset.h | 45 ++++++++ .../include/Status/Wallet/WalletController.h | 5 +- libs/Wallet/qml/Status/Wallet/AssetView.qml | 47 +++++++- libs/Wallet/qml/Status/Wallet/AssetsPanel.qml | 34 ++++-- .../NewAccount/AddWatchOnlyAccountView.qml | 3 +- .../NewAccount/NewWalletAccountView.qml | 6 +- .../qml/Status/Wallet/WalletContentView.qml | 26 +++-- libs/Wallet/qml/Status/Wallet/WalletView.qml | 3 +- libs/Wallet/src/AccountAssetsController.cpp | 100 ++++++++++++++++++ libs/Wallet/src/DerivedWalletAddress.cpp | 2 +- .../Wallet/src/NewWalletAccountController.cpp | 9 +- libs/Wallet/src/WalletAccount.cpp | 2 +- libs/Wallet/src/WalletAsset.cpp | 47 ++++++++ libs/Wallet/src/WalletController.cpp | 13 ++- test/libs/StatusGoQt/test_wallet.cpp | 25 +++-- 35 files changed, 441 insertions(+), 100 deletions(-) create mode 100644 libs/Wallet/include/Status/Wallet/AccountAssetsController.h create mode 100644 libs/Wallet/include/Status/Wallet/WalletAsset.h create mode 100644 libs/Wallet/src/AccountAssetsController.cpp create mode 100644 libs/Wallet/src/WalletAsset.cpp diff --git a/app/README.md b/app/README.md index d48dbbe758..6cd086d97d 100644 --- a/app/README.md +++ b/app/README.md @@ -63,7 +63,7 @@ cmake -B build -S . -DCMAKE_PREFIX_PATH="$HOME/Qt/6.3.2/gcc_64" -DCMAKE_BUILD_TY # Windows: cmake -B build -S . -DCMAKE_PREFIX_PATH="$HOME/Qt/6.3.2/mingw_64" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=build/conan/conan_toolchain.cmake -cmake --build build --config Release +cmake --build build ``` ### Run tests diff --git a/libs/Helpers/src/Helpers/QObjectVectorModel.h b/libs/Helpers/src/Helpers/QObjectVectorModel.h index 4803a17eb4..72dbc5f5a6 100644 --- a/libs/Helpers/src/Helpers/QObjectVectorModel.h +++ b/libs/Helpers/src/Helpers/QObjectVectorModel.h @@ -58,7 +58,9 @@ public: }; void clear() { + beginResetModel(); m_objects.clear(); + endResetModel(); }; void push_back(const std::shared_ptr newValue) { diff --git a/libs/Helpers/src/Helpers/helpers.h b/libs/Helpers/src/Helpers/helpers.h index 2b1cbac507..55e284e95e 100644 --- a/libs/Helpers/src/Helpers/helpers.h +++ b/libs/Helpers/src/Helpers/helpers.h @@ -2,7 +2,11 @@ #include "Helpers/BuildConfiguration.h" +#include + #include +#include +#include namespace Status::Helpers { @@ -29,4 +33,24 @@ bool iequals(const T& a, const T& b, size_t len = -1) }); } +template +std::vector getKeys(const std::map& map) +{ + std::vector keys; + keys.reserve(map.size()); + for (const auto& [key, _] : map) + keys.push_back(key); + return keys; +} + +static void doDeleteLater(QObject *obj) { + obj->deleteLater(); +} + +// TODO: use https://en.cppreference.com/w/cpp/memory/shared_ptr/allocate_shared +template +std::shared_ptr makeSharedQObject(Args&& ...args) { + return std::shared_ptr(new T(std::forward(args)...), doDeleteLater); +} + } diff --git a/libs/Onboarding/qml/Status/Onboarding/SetupNewProfileView.qml b/libs/Onboarding/qml/Status/Onboarding/SetupNewProfileView.qml index e1a6a99b5c..8f697b77fc 100644 --- a/libs/Onboarding/qml/Status/Onboarding/SetupNewProfileView.qml +++ b/libs/Onboarding/qml/Status/Onboarding/SetupNewProfileView.qml @@ -16,8 +16,8 @@ import Status.Onboarding Item { id: root - // TODO: fix error "Unable to assign Status::Onboarding::NewAccountController to Status::Onboarding::NewAccountController" then enable typed properties - required property var/*NewAccountController*/ newAccountController + /// \c NewAccountController + required property var newAccountController signal userLoggedIn() signal abortAccountCreation() diff --git a/libs/Onboarding/qml/Status/Onboarding/TempTextInput.qml b/libs/Onboarding/qml/Status/Onboarding/TempTextInput.qml index fc5f4a15e0..99cc99a74c 100644 --- a/libs/Onboarding/qml/Status/Onboarding/TempTextInput.qml +++ b/libs/Onboarding/qml/Status/Onboarding/TempTextInput.qml @@ -12,6 +12,7 @@ TextInput { Rectangle { anchors.fill: parent border.width: 1 + border.color: "#55555555" z: parent.z - 1 } } diff --git a/libs/Onboarding/qml/Status/Onboarding/WelcomeView.qml b/libs/Onboarding/qml/Status/Onboarding/WelcomeView.qml index babcee1823..665491f377 100644 --- a/libs/Onboarding/qml/Status/Onboarding/WelcomeView.qml +++ b/libs/Onboarding/qml/Status/Onboarding/WelcomeView.qml @@ -11,7 +11,9 @@ import "base" OnboardingPageBase { id: root - required property var onboardingController // OnboardingController + /// \c OnboardingController + /// \todo investigate "Unable to assign Status::Onboarding::OnboardingController to Status::Onboarding::OnboardingController" + required property var onboardingController signal setupNewAccount() /// \param statusAccount \c UserAccount @@ -51,15 +53,6 @@ OnboardingPageBase { id: passwordInput Layout.preferredWidth: 328 Layout.preferredHeight: 44 - - // TODO: remove dev helper - text: "1234567890" - Timer { - interval: 100 - running: loginButton.enabled && accountsComboBox.count - onTriggered: loginButton.clicked() - } - // END dev } Button { diff --git a/libs/Onboarding/qml/Status/Onboarding/base/SetupNewProfilePageBase.qml b/libs/Onboarding/qml/Status/Onboarding/base/SetupNewProfilePageBase.qml index f82b2640bd..92f96d34dc 100644 --- a/libs/Onboarding/qml/Status/Onboarding/base/SetupNewProfilePageBase.qml +++ b/libs/Onboarding/qml/Status/Onboarding/base/SetupNewProfilePageBase.qml @@ -1,10 +1,12 @@ import QtQuick +import Status.Onboarding + /*! Proposal on how to templetize the alignment requirement of some views */ OnboardingPageBase { // TODO: fix error "Unable to assign Status::Onboarding::NewAccountController to Status::Onboarding::NewAccountController" then enable typed properties - required property var/*NewAccountController*/ newAccountController + required property var newAccountController /// Common reference item that doesn't change between common views/pages readonly property Item alignmentItem: alignmentBaselineItem diff --git a/libs/Onboarding/src/Onboarding/OnboardingController.cpp b/libs/Onboarding/src/Onboarding/OnboardingController.cpp index 8dd80f3031..ee0402c4b7 100644 --- a/libs/Onboarding/src/Onboarding/OnboardingController.cpp +++ b/libs/Onboarding/src/Onboarding/OnboardingController.cpp @@ -6,6 +6,8 @@ #include +#include + namespace Status::Onboarding { namespace StatusGo = Status::StatusGo; @@ -17,9 +19,9 @@ OnboardingController::OnboardingController(AccountsServiceInterfacePtr accountsS { // Init accounts std::vector> accounts; for(auto &account : getOpenedAccounts()) { - accounts.push_back(std::make_shared(std::make_unique(std::move(account)))); + accounts.push_back(Helpers::makeSharedQObject(std::make_unique(std::move(account)))); } - m_accounts = std::make_shared(std::move(accounts)); + m_accounts = Helpers::makeSharedQObject(std::move(accounts)); } connect(StatusGo::SignalsManager::instance(), &StatusGo::SignalsManager::nodeLogin, this, &OnboardingController::onLogin); diff --git a/libs/Onboarding/src/Onboarding/OnboardingController.h b/libs/Onboarding/src/Onboarding/OnboardingController.h index a8fca1a591..64d47183e0 100644 --- a/libs/Onboarding/src/Onboarding/OnboardingController.h +++ b/libs/Onboarding/src/Onboarding/OnboardingController.h @@ -46,7 +46,7 @@ public: UserAccountsModel *accounts() const; - Q_INVOKABLE NewAccountController *initNewAccountController(); + Q_INVOKABLE Status::Onboarding::NewAccountController *initNewAccountController(); Q_INVOKABLE void terminateNewAccountController(); NewAccountController *newAccountController() const; std::shared_ptr accountsService() const; diff --git a/libs/Onboarding/src/Onboarding/OnboardingModule.cpp b/libs/Onboarding/src/Onboarding/OnboardingModule.cpp index 4c4da2a5f7..c6c87b86ea 100644 --- a/libs/Onboarding/src/Onboarding/OnboardingModule.cpp +++ b/libs/Onboarding/src/Onboarding/OnboardingModule.cpp @@ -4,6 +4,7 @@ #include +#include namespace AppCore = Status::ApplicationCore; @@ -43,7 +44,7 @@ void OnboardingModule::initWithUserDataPath(const fs::path &path) auto result = m_accountsService->init(path); if(!result) throw std::runtime_error(std::string("Failed to initialize OnboadingService") + path.string()); - m_controller = std::make_shared( + m_controller = Helpers::makeSharedQObject( m_accountsService); emit controllerChanged(); } diff --git a/libs/Onboarding/tests/OnboardingTestHelpers/CMakeLists.txt b/libs/Onboarding/tests/OnboardingTestHelpers/CMakeLists.txt index b3af05fed3..a260dcefe7 100644 --- a/libs/Onboarding/tests/OnboardingTestHelpers/CMakeLists.txt +++ b/libs/Onboarding/tests/OnboardingTestHelpers/CMakeLists.txt @@ -8,8 +8,6 @@ project(OnboardingTestHelpers set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true) -find_package(GTest REQUIRED) - find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED) qt6_standard_project_setup() @@ -47,6 +45,4 @@ target_link_libraries(OnboardingTestHelpers Status::ApplicationCore Status::Onboarding Status::StatusGoQt - - GTest::gtest ) diff --git a/libs/Onboarding/tests/OnboardingTestHelpers/ScopedTestAccount.cpp b/libs/Onboarding/tests/OnboardingTestHelpers/ScopedTestAccount.cpp index dded536f4b..4bc5c0cfe4 100644 --- a/libs/Onboarding/tests/OnboardingTestHelpers/ScopedTestAccount.cpp +++ b/libs/Onboarding/tests/OnboardingTestHelpers/ScopedTestAccount.cpp @@ -10,9 +10,9 @@ #include #include -#include +#include -#include +#include namespace Testing = Status::Testing; namespace Onboarding = Status::Onboarding; @@ -49,7 +49,7 @@ ScopedTestAccount::ScopedTestAccount(const std::string &tempTestSubfolderName, // // Beware, smartpointer is a requirement - m_onboarding = std::make_shared(accountsService); + m_onboarding = Helpers::makeSharedQObject(accountsService); if(m_onboarding->getOpenedAccounts().size() != 0) throw std::runtime_error("ScopedTestAccount - already have opened account"); diff --git a/libs/Onboarding/tests/test_OnboardingController.cpp b/libs/Onboarding/tests/test_OnboardingController.cpp index 49c2ba25ad..1937f0671a 100644 --- a/libs/Onboarding/tests/test_OnboardingController.cpp +++ b/libs/Onboarding/tests/test_OnboardingController.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include #include @@ -49,7 +51,7 @@ std::shared_ptr LoginTest::m_accountsServiceMock; TEST_F(LoginTest, DISABLED_TestLoginController) { // Controller hides as a regular class but at runtime it must be a shared pointer; TODO: refactor - auto controller = std::make_shared(m_accountsServiceMock); + auto controller = Helpers::makeSharedQObject(m_accountsServiceMock); } } // namespace diff --git a/libs/StatusGoQt/src/StatusGo/Wallet/Token.h b/libs/StatusGoQt/src/StatusGo/Wallet/Token.h index 8b31ea50b1..9630b679bd 100644 --- a/libs/StatusGoQt/src/StatusGo/Wallet/Token.h +++ b/libs/StatusGoQt/src/StatusGo/Wallet/Token.h @@ -28,9 +28,10 @@ struct Token ChainID chainId; }; +using TokenPtr = std::shared_ptr; using Tokens = std::vector; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Token, address, name,symbol, color, decimals, chainId); -} \ No newline at end of file +} diff --git a/libs/StatusGoQt/src/StatusGo/Wallet/WalletApi.h b/libs/StatusGoQt/src/StatusGo/Wallet/WalletApi.h index a1e4994b0e..afab60ba8e 100644 --- a/libs/StatusGoQt/src/StatusGo/Wallet/WalletApi.h +++ b/libs/StatusGoQt/src/StatusGo/Wallet/WalletApi.h @@ -28,12 +28,13 @@ NetworkConfigurations getEthereumChains(bool onlyEnabled); /// \note status-go's GetEthereumChains@api.go which calls /// NetworkManager@client.go -> network.Manager.get() -/// \throws \c CallPrivateRpcError +/// \throws \c CallPrivateRpcError.errorResponse().error with \c "no tokens for this network" in case of missing tokens for the network +/// \throws \c CallPrivateRpcError for general RPC call failure NetworkConfigurations getEthereumChains(bool onlyEnabled); /// \note status-go's GetTokens@api.go -> TokenManager.getTokens@token.go -/// \throws \c CallPrivateRpcError +/// \throws \c CallPrivateRpcError with Tokens getTokens(const ChainID &chainId); using TokenBalances = std::map>; diff --git a/libs/Wallet/CMakeLists.txt b/libs/Wallet/CMakeLists.txt index 484a3f8042..164aac17dc 100644 --- a/libs/Wallet/CMakeLists.txt +++ b/libs/Wallet/CMakeLists.txt @@ -66,13 +66,17 @@ install( target_sources(Wallet PRIVATE + include/Status/Wallet/AccountAssetsController.h + src/AccountAssetsController.cpp include/Status/Wallet/DerivedWalletAddress.h src/DerivedWalletAddress.cpp include/Status/Wallet/NewWalletAccountController.h src/NewWalletAccountController.cpp - include/Status/Wallet/WalletAccount.h # Move to Accounts module + include/Status/Wallet/WalletAccount.h src/WalletAccount.cpp + include/Status/Wallet/WalletAsset.h + src/WalletAsset.cpp include/Status/Wallet/WalletController.h src/WalletController.cpp ) diff --git a/libs/Wallet/include/Status/Wallet/AccountAssetsController.h b/libs/Wallet/include/Status/Wallet/AccountAssetsController.h new file mode 100644 index 0000000000..7359f57cc3 --- /dev/null +++ b/libs/Wallet/include/Status/Wallet/AccountAssetsController.h @@ -0,0 +1,54 @@ +#pragma once + +#include "Status/Wallet/WalletAccount.h" +#include "Status/Wallet/WalletAsset.h" + +#include + +#include + +namespace Status::Wallet { + +/// Controlls asset for an account using hardcoded network and token lists +/// +/// \todo add static configuration component to provide networks, tokens and currency list +/// \todo impliement \c AccountsBalanceService to fetch and cache realtime balance (or better implement this in status-go) +/// \todo implement native token fetching +/// \todo double responsibility, split functionality in asset management and balance +class AccountAssetsController: public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("C++ only") + + Q_PROPERTY(QAbstractItemModel* assetModel READ assetsModel CONSTANT) + Q_PROPERTY(float totalValue READ totalValue NOTIFY totalValueChanged) + Q_PROPERTY(bool assetsReady READ assetsReady NOTIFY assetsReadyChanged) + +public: + AccountAssetsController(WalletAccount* address, QObject* parent = nullptr); + + QAbstractItemModel* assetsModel() const; + + float totalValue() const; + + bool assetsReady() const; + +signals: + void totalValueChanged(); + void assetsReadyChanged(); + +private: + void updateBalances(); + bool isTokenEnabled(const StatusGo::Wallet::Token& token) const; + + WalletAccount* m_address; + const std::vector m_enabledTokens; + + using AssetModel = Helpers::QObjectVectorModel; + std::shared_ptr m_assets; + float m_totalValue{}; + bool m_assetsReady{}; +}; + +} // namespace Status::Wallet diff --git a/libs/Wallet/include/Status/Wallet/DerivedWalletAddress.h b/libs/Wallet/include/Status/Wallet/DerivedWalletAddress.h index f7164aa0fe..7cbb204fcf 100644 --- a/libs/Wallet/include/Status/Wallet/DerivedWalletAddress.h +++ b/libs/Wallet/include/Status/Wallet/DerivedWalletAddress.h @@ -2,30 +2,32 @@ #include -#include +#include -namespace GoWallet = Status::StatusGo::Wallet; +namespace WalletGo = Status::StatusGo::Wallet; namespace Status::Wallet { class DerivedWalletAddress : public QObject { Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("C++ only") Q_PROPERTY(QString address READ address CONSTANT) Q_PROPERTY(bool alreadyCreated READ alreadyCreated CONSTANT) public: - explicit DerivedWalletAddress(GoWallet::DerivedAddress address, QObject *parent = nullptr); + explicit DerivedWalletAddress(WalletGo::DerivedAddress address, QObject *parent = nullptr); QString address() const; - const GoWallet::DerivedAddress &data() const { return m_derivedAddress; }; + const WalletGo::DerivedAddress &data() const { return m_derivedAddress; }; bool alreadyCreated() const; private: - const GoWallet::DerivedAddress m_derivedAddress; + const WalletGo::DerivedAddress m_derivedAddress; }; using DerivedWalletAddressPtr = std::shared_ptr; diff --git a/libs/Wallet/include/Status/Wallet/NewWalletAccountController.h b/libs/Wallet/include/Status/Wallet/NewWalletAccountController.h index 1d13a41c5a..a77c6a8ed1 100644 --- a/libs/Wallet/include/Status/Wallet/NewWalletAccountController.h +++ b/libs/Wallet/include/Status/Wallet/NewWalletAccountController.h @@ -5,19 +5,16 @@ #include -#include #include -class QQmlEngine; -class QJSEngine; - namespace Status::Wallet { -/// \note the folowing values are kept in sync \c selectedDerivedAddress, \c derivedAddressIndex and \c derivationPath +/// \note the following values are kept in sync \c selectedDerivedAddress, \c derivedAddressIndex and \c derivationPath /// and \c customDerivationPath; \see connascence.io/value class NewWalletAccountController: public QObject { Q_OBJECT + QML_ELEMENT QML_UNCREATABLE("C++ only") Q_PROPERTY(QAbstractListModel* mainAccountsModel READ mainAccountsModel CONSTANT) @@ -35,10 +32,6 @@ public: /// \note On account creation \c accounts are updated with the newly created wallet account NewWalletAccountController(std::shared_ptr accounts); - /// Called by QML engine to register the instance. QML takes ownership of the instance - static NewWalletAccountController *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); - - QAbstractListModel *mainAccountsModel(); QAbstractItemModel *currentDerivedAddressModel(); diff --git a/libs/Wallet/include/Status/Wallet/WalletAccount.h b/libs/Wallet/include/Status/Wallet/WalletAccount.h index ff1418c1e7..c65110102c 100644 --- a/libs/Wallet/include/Status/Wallet/WalletAccount.h +++ b/libs/Wallet/include/Status/Wallet/WalletAccount.h @@ -2,7 +2,7 @@ #include "Accounts/ChatOrWalletAccount.h" -#include +#include namespace GoAccounts = Status::StatusGo::Accounts; @@ -11,9 +11,11 @@ namespace Status::Wallet { class WalletAccount: public QObject { Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("C++ only, for now") Q_PROPERTY(QString name READ name CONSTANT) - Q_PROPERTY(QString address READ address CONSTANT) + Q_PROPERTY(QString address READ strAddress CONSTANT) Q_PROPERTY(QColor color READ color CONSTANT) public: @@ -21,7 +23,7 @@ public: const QString &name() const; - const QString &address() const; + const QString &strAddress() const; QColor color() const; diff --git a/libs/Wallet/include/Status/Wallet/WalletAsset.h b/libs/Wallet/include/Status/Wallet/WalletAsset.h new file mode 100644 index 0000000000..73022aefde --- /dev/null +++ b/libs/Wallet/include/Status/Wallet/WalletAsset.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#include + +namespace WalletGo = Status::StatusGo::Wallet; + +namespace Status::Wallet { + +class WalletAsset : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("C++ only, for now") + + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString symbol READ symbol CONSTANT) + Q_PROPERTY(QColor color READ color CONSTANT) + Q_PROPERTY(quint64 count READ count CONSTANT) + Q_PROPERTY(float value READ value CONSTANT) + +public: + explicit WalletAsset(const WalletGo::TokenPtr token, StatusGo::Wallet::BigInt balance, QObject *parent = nullptr); + + const QString name() const; + + const QString symbol() const; + + const QColor color() const; + + quint64 count() const; + + float value() const; + +private: + const WalletGo::TokenPtr m_token; + // const GoWallet::NativeToken m_nativeToken; + + StatusGo::Wallet::BigInt m_balance; + int m_count; +}; + +} diff --git a/libs/Wallet/include/Status/Wallet/WalletController.h b/libs/Wallet/include/Status/Wallet/WalletController.h index b0285c3455..b652d3ce08 100644 --- a/libs/Wallet/include/Status/Wallet/WalletController.h +++ b/libs/Wallet/include/Status/Wallet/WalletController.h @@ -5,7 +5,6 @@ #include -#include #include #include @@ -16,6 +15,7 @@ class QJSEngine; namespace Status::Wallet { class NewWalletAccountController; +class AccountAssetsController; /// \todo move account creation to its own controller class WalletController: public QObject @@ -44,6 +44,9 @@ public: WalletAccount *currentAccount() const; Q_INVOKABLE void setCurrentAccountIndex(int index); + /// \note caller (QML) takes ownership of the returned instance + Q_INVOKABLE Status::Wallet::AccountAssetsController* createAccountAssetsController(Status::Wallet::WalletAccount* account); + signals: void currentAccountChanged(); diff --git a/libs/Wallet/qml/Status/Wallet/AssetView.qml b/libs/Wallet/qml/Status/Wallet/AssetView.qml index 6a540bc6f3..ca59c4557a 100644 --- a/libs/Wallet/qml/Status/Wallet/AssetView.qml +++ b/libs/Wallet/qml/Status/Wallet/AssetView.qml @@ -1,14 +1,51 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts + +import Status.Wallet +import Status.Containers Item { id: root - /// WalletAccount - required property var asset + required property AccountAssetsController assetController + required property WalletAccount account - Label { - anchors.centerIn: parent - text: asset.name + ListView { + id: listView + + anchors.fill: parent + + model: root.assetController.assetModel + + delegate: RowLayout { + required property WalletAsset asset + + Label { + text: asset.name + + Layout.preferredWidth: listView.width * 0.4 + } + RowLayout { + Layout.preferredWidth: listView.width * 0.4 + + Label { + text: asset.count + } + Label { + text: asset.symbol + } + LayoutSpacer{} + } + RowLayout { + Label { + text: asset.value + } + Label { + //text: asset.currency + } + } + LayoutSpacer{} + } } } diff --git a/libs/Wallet/qml/Status/Wallet/AssetsPanel.qml b/libs/Wallet/qml/Status/Wallet/AssetsPanel.qml index 815b7d687a..3ce88d9265 100644 --- a/libs/Wallet/qml/Status/Wallet/AssetsPanel.qml +++ b/libs/Wallet/qml/Status/Wallet/AssetsPanel.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQml import Status.Wallet @@ -13,6 +14,7 @@ Item { /// WalletController required property WalletController controller + readonly property AccountAssetsController currentAssetController: listView.currentItem ? listView.currentItem.assetController : null ColumnLayout { anchors.left: leftLine.right @@ -25,7 +27,8 @@ Item { } Label { id: totalValueLabel - text: "" // TODO: Aggregate or API!? + // TODO: source it from the last total cached value of balance service, + text: "-" } Label { text: qsTr("Total value") @@ -37,6 +40,8 @@ Item { } ListView { + id: listView + Layout.fillWidth: true Layout.fillHeight: true @@ -47,10 +52,17 @@ Item { clip: true delegate: ItemDelegate { + required property int index + // Enabling type generates 'Writing to "account" broke the binding to the underlying model' + required property var/*WalletAccount*/ account + + readonly property AccountAssetsController assetController: account && WalletController.createAccountAssetsController(account) + highlighted: ListView.isCurrentItem width: ListView.view.width + onClicked: ListView.view.currentIndex = index contentItem: ColumnLayout { @@ -79,17 +91,19 @@ Item { elide: Label.ElideRight } } - Label { + + RowLayout { Layout.leftMargin: 10 Layout.rightMargin: 10 Layout.bottomMargin: 5 - text: "$" - color: "grey" - - verticalAlignment: Qt.AlignVCenter - - elide: Label.ElideRight + Label { + text: assetController.assetsReady ? assetController.totalValue : "-" + } + Label { + text: "$" + color: "grey" + } } } } @@ -137,13 +151,13 @@ Item { } model: ObjectModel { NewAccountEntry { - title: "New Account" + title: qsTr("New Account") sourceComponent: NewWalletAccountView { controller: root.controller.createNewWalletAccountController() } } NewAccountEntry { - title: "Watch Only Account" + title: qsTr("Watch Only Account") sourceComponent: AddWatchOnlyAccountView { controller: root.controller.createNewWalletAccountController() } diff --git a/libs/Wallet/qml/Status/Wallet/NewAccount/AddWatchOnlyAccountView.qml b/libs/Wallet/qml/Status/Wallet/NewAccount/AddWatchOnlyAccountView.qml index c7e89938d0..ae0ce82b6b 100644 --- a/libs/Wallet/qml/Status/Wallet/NewAccount/AddWatchOnlyAccountView.qml +++ b/libs/Wallet/qml/Status/Wallet/NewAccount/AddWatchOnlyAccountView.qml @@ -11,8 +11,7 @@ import Status.Containers Item { id: root - /// NewWalletAccountController - required property var controller + required property NewWalletAccountController controller signal accountCreated() signal cancel() diff --git a/libs/Wallet/qml/Status/Wallet/NewAccount/NewWalletAccountView.qml b/libs/Wallet/qml/Status/Wallet/NewAccount/NewWalletAccountView.qml index 7cb6abbf0b..61266967bb 100644 --- a/libs/Wallet/qml/Status/Wallet/NewAccount/NewWalletAccountView.qml +++ b/libs/Wallet/qml/Status/Wallet/NewAccount/NewWalletAccountView.qml @@ -9,7 +9,7 @@ import Status.Containers Item { id: root - /// NewWalletAccountController + // Using the type NewWalletAccountController generates "/bin/sh: line 1: 12832 Segmentation fault: 11 qmlcachegen --resource-path /Status/Wallet/qml/Status/Wallet/NewAccount/NewWalletAccountView.qml" required property var controller signal accountCreated() @@ -135,7 +135,7 @@ Item { } highlighted: derivedAddressCombo.highlightedIndex === index - required property var derivedAddress + required property DerivedWalletAddress derivedAddress required property int index } } @@ -174,7 +174,7 @@ Item { } highlighted: derivedFromCombo.highlightedIndex === index - required property var account + required property WalletAccount account required property int index } } diff --git a/libs/Wallet/qml/Status/Wallet/WalletContentView.qml b/libs/Wallet/qml/Status/Wallet/WalletContentView.qml index de90b4d2ca..c1abf23a09 100644 --- a/libs/Wallet/qml/Status/Wallet/WalletContentView.qml +++ b/libs/Wallet/qml/Status/Wallet/WalletContentView.qml @@ -8,17 +8,17 @@ import Status.Wallet Item { id: root - /// WalletAccount - required property var asset + required property WalletAccount account + required property AccountAssetsController assetController ColumnLayout { anchors.fill: parent Label { - text: asset.name + text: account.name } Label { - text: asset.address + text: account.address } TabBar { id: tabBar @@ -37,6 +37,7 @@ Item { Layout.fillWidth: true Layout.fillHeight: true + Layout.margins: 10 currentIndex: tabBar.currentIndex @@ -44,21 +45,24 @@ Item { clip: true Loader { - active: SwipeView.isCurrentItem + active: SwipeView.isCurrentItem && root.assetController && root.account sourceComponent: AssetView { - Layout.fillWidth: true - Layout.fillHeight: true + Rectangle { + anchors.fill: parent + color: "#66000066" + border.width: 1 + } + width: swipeView.width + height: swipeView.height - asset: root.asset + assetController: root.assetController + account: root.account } } Loader { active: SwipeView.isCurrentItem sourceComponent: Item { - Layout.fillWidth: true - Layout.fillHeight: true - Label { anchors.centerIn: parent text: "TODO" diff --git a/libs/Wallet/qml/Status/Wallet/WalletView.qml b/libs/Wallet/qml/Status/Wallet/WalletView.qml index 8724a68c41..3e6bb2bd6e 100644 --- a/libs/Wallet/qml/Status/Wallet/WalletView.qml +++ b/libs/Wallet/qml/Status/Wallet/WalletView.qml @@ -36,7 +36,8 @@ PanelAndContentBase { Layout.fillWidth: true Layout.fillHeight: true - asset: WalletController.currentAccount + account: WalletController.currentAccount + assetController: panel.currentAssetController } } } diff --git a/libs/Wallet/src/AccountAssetsController.cpp b/libs/Wallet/src/AccountAssetsController.cpp new file mode 100644 index 0000000000..472ebda06b --- /dev/null +++ b/libs/Wallet/src/AccountAssetsController.cpp @@ -0,0 +1,100 @@ +#include "Status/Wallet/AccountAssetsController.h" + +#include +#include +#include + +#include + +#include + +namespace WalletGo = Status::StatusGo::Wallet; + +namespace Status::Wallet { + +AccountAssetsController::AccountAssetsController(WalletAccount* address, QObject* parent) + : QObject(parent) + , m_address(address) + , m_enabledTokens({u"SNT"_qs, u"ETH"_qs, u"STT"_qs, u"DAI"_qs}) + , m_assets(Helpers::makeSharedQObject("asset")) +{ + QtConcurrent::run([this, address]{ updateBalances(); }) + .then([this] { + m_assetsReady = true; + emit assetsReadyChanged(); + + m_totalValue = 0; + for(const auto& asset : m_assets->objects()) + m_totalValue += asset->value(); + emit totalValueChanged(); + }) + .onFailed([this] { + emit assetsReadyChanged(); + qWarning() << "Unexpected failure while executing updateBalances for account" << m_address->data().address.get(); + }); +} + +void AccountAssetsController::updateBalances() +{ + const StatusGo::Accounts::EOAddress& address = m_address->data().address; + if(m_assets->size() > 0) + m_assets->clear(); + // TODO: this should be moved to status-go and exposed as "get balances for account and tokens with currency" + std::map tokens; + std::vector chainIds; + auto allNets = WalletGo::getEthereumChains(false); + for(const auto &net : allNets) { + if(net.enabled && !net.isTest) { + try { + const auto allTokens = WalletGo::getTokens(net.chainId); + for(const auto& tokenToMove : allTokens) { + if(isTokenEnabled(tokenToMove)) { + auto address = tokenToMove.address; + tokens.emplace(std::move(address), std::move(tokenToMove)); + } + } + chainIds.push_back(net.chainId); + } + catch (const StatusGo::CallPrivateRpcError& e) { + // Most probably "no tokens for this network" + if(e.errorResponse().error.message.compare("no tokens for this network") != 0) + qWarning() << "Failed retrieving tokens for network" << net.chainId.get() << "; error" << e.errorResponse().error.message.c_str(); + continue; + } + } + } + + auto accountBalances = WalletGo::getTokensBalancesForChainIDs(chainIds, { address }, std::move(Helpers::getKeys(tokens))); + if(accountBalances.size() == 1) { + for(const auto& accountAndBalance : accountBalances.begin()->second) { + auto asset = Helpers::makeSharedQObject(std::make_shared(tokens.at(accountAndBalance.first)), accountAndBalance.second); + m_assets->push_back(asset); + } + } + else + qWarning() << "Failed fetching balances for account" << address.get() << "; balances count" << accountBalances.size(); +} + +bool AccountAssetsController::isTokenEnabled(const StatusGo::Wallet::Token& token) const +{ + return find_if(m_enabledTokens.begin(), m_enabledTokens.end(), [&token](const auto& symbol) { + return token.symbol == symbol; + }) != m_enabledTokens.end(); +} + +QAbstractItemModel* AccountAssetsController::assetsModel() const +{ + return m_assets.get(); +} + +float AccountAssetsController::totalValue() const +{ + return m_totalValue; +} + +bool AccountAssetsController::assetsReady() const +{ + return m_assetsReady; +} + +} diff --git a/libs/Wallet/src/DerivedWalletAddress.cpp b/libs/Wallet/src/DerivedWalletAddress.cpp index a3c2948d23..9f1779aea4 100644 --- a/libs/Wallet/src/DerivedWalletAddress.cpp +++ b/libs/Wallet/src/DerivedWalletAddress.cpp @@ -2,7 +2,7 @@ namespace Status::Wallet { -DerivedWalletAddress::DerivedWalletAddress(GoWallet::DerivedAddress address, QObject *parent) +DerivedWalletAddress::DerivedWalletAddress(WalletGo::DerivedAddress address, QObject *parent) : QObject{parent} , m_derivedAddress{std::move(address)} { diff --git a/libs/Wallet/src/NewWalletAccountController.cpp b/libs/Wallet/src/NewWalletAccountController.cpp index 638835b5e4..206c56b742 100644 --- a/libs/Wallet/src/NewWalletAccountController.cpp +++ b/libs/Wallet/src/NewWalletAccountController.cpp @@ -17,7 +17,7 @@ #include namespace GoAccounts = Status::StatusGo::Accounts; -namespace GoWallet = Status::StatusGo::Wallet; +namespace WalletGo = Status::StatusGo::Wallet; namespace UtilsSG = Status::StatusGo::Utils; namespace StatusGo = Status::StatusGo; @@ -31,7 +31,6 @@ NewWalletAccountController::NewWalletAccountController(std::shared_ptr(std::ceil(static_cast(m_maxDerivedAddresses)/static_cast(m_derivedAddressesPageSize))); std::shared_ptr foundEntry; while(currentPage <= maxPageCount && foundIndex < 0) { - auto all = GoWallet::getDerivedAddressesForPath(StatusGo::HashedPassword(UtilsSG::hashPassword(password)), + auto all = WalletGo::getDerivedAddressesForPath(StatusGo::HashedPassword(UtilsSG::hashPassword(password)), derivedFrom->data().derivedFrom.value(), Status::Constants::General::PathWalletRoot, m_derivedAddressesPageSize, currentPage); @@ -115,7 +114,7 @@ bool NewWalletAccountController::retrieveAndUpdateDerivedAddresses(const QString m_derivedAddress.resize(currentIndex + all.size()); for(auto newDerived : all) { - auto newEntry = std::make_shared(std::move(newDerived)); + auto newEntry = Helpers::makeSharedQObject(std::move(newDerived)); m_derivedAddress.set(currentIndex, newEntry); if(foundIndex < 0 && !newEntry->data().alreadyCreated) { foundIndex = currentIndex; @@ -147,7 +146,7 @@ WalletAccountPtr NewWalletAccountController::findMissingAccount() return std::none_of(m_accounts->objects().begin(), m_accounts->objects().end(), [&a](const auto &eA) { return a.address == eA->data().address; }); }); - return it != accounts.end() ? std:: make_shared(*it) : nullptr; + return it != accounts.end() ? Helpers::makeSharedQObject(*it) : nullptr; } NewWalletAccountController::AccountsModel::ObjectContainer diff --git a/libs/Wallet/src/WalletAccount.cpp b/libs/Wallet/src/WalletAccount.cpp index 1852e0a8e2..00b6c76e0b 100644 --- a/libs/Wallet/src/WalletAccount.cpp +++ b/libs/Wallet/src/WalletAccount.cpp @@ -13,7 +13,7 @@ const QString &WalletAccount::name() const return m_data.name; } -const QString &WalletAccount::address() const +const QString &WalletAccount::strAddress() const { return m_data.address.get(); } diff --git a/libs/Wallet/src/WalletAsset.cpp b/libs/Wallet/src/WalletAsset.cpp new file mode 100644 index 0000000000..1a11e05627 --- /dev/null +++ b/libs/Wallet/src/WalletAsset.cpp @@ -0,0 +1,47 @@ +#include "WalletAsset.h" + +#include + +namespace Status::Wallet { + +WalletAsset::WalletAsset(const WalletGo::TokenPtr token, StatusGo::Wallet::BigInt balance, QObject *parent) + : QObject{parent} + , m_token(token) + , m_balance(std::move(balance)) +{ +} + +const QString WalletAsset::name() const +{ + return m_token ? m_token->name : u""_qs; +} + +const QString WalletAsset::symbol() const +{ + return m_token ? m_token->symbol : u""_qs; +} + +const QColor WalletAsset::color() const +{ + return m_token ? m_token->color : QColor(); +} + +quint64 WalletAsset::count() const +{ + return (m_token) + ? static_cast(m_balance/boost::multiprecision::pow(WalletGo::BigInt(10), m_token->decimals)) + : 0; +} + +float WalletAsset::value() const +{ + if(m_token) { + const int mantissaDigits = m_token->decimals > 3 ? 3 : 0; + const auto scale = 10 * mantissaDigits; + return static_cast((m_balance*scale)/boost::multiprecision::pow(WalletGo::BigInt(10), m_token->decimals))/scale; + } + else + return 0; +} + +} diff --git a/libs/Wallet/src/WalletController.cpp b/libs/Wallet/src/WalletController.cpp index 7cffc4a788..4ecc901609 100644 --- a/libs/Wallet/src/WalletController.cpp +++ b/libs/Wallet/src/WalletController.cpp @@ -1,5 +1,7 @@ #include "Status/Wallet/WalletController.h" + #include "NewWalletAccountController.h" +#include "AccountAssetsController.h" #include @@ -16,14 +18,14 @@ #include namespace GoAccounts = Status::StatusGo::Accounts; -namespace GoWallet = Status::StatusGo::Wallet; +namespace WalletGo = Status::StatusGo::Wallet; namespace UtilsSG = Status::StatusGo::Utils; namespace StatusGo = Status::StatusGo; namespace Status::Wallet { WalletController::WalletController() - : m_accounts(std::make_shared(std::move(getWalletAccounts()), "account")) + : m_accounts(Helpers::makeSharedQObject(std::move(getWalletAccounts()), "account")) , m_currentAccount(m_accounts->get(0)) { } @@ -60,13 +62,18 @@ void WalletController::setCurrentAccountIndex(int index) emit currentAccountChanged(); } +AccountAssetsController *WalletController::createAccountAssetsController(WalletAccount *account) +{ + return new AccountAssetsController(account); +} + std::vector WalletController::getWalletAccounts(bool rootWalletAccountsOnly) const { auto all = GoAccounts::getAccounts(); std::vector result; for(auto account : all) { if(!account.isChat && (!rootWalletAccountsOnly || account.isWallet)) - result.push_back(std::make_shared(std::move(account))); + result.push_back(Helpers::makeSharedQObject(std::move(account))); } return result; } diff --git a/test/libs/StatusGoQt/test_wallet.cpp b/test/libs/StatusGoQt/test_wallet.cpp index b76f241328..c8e93a0b06 100644 --- a/test/libs/StatusGoQt/test_wallet.cpp +++ b/test/libs/StatusGoQt/test_wallet.cpp @@ -1,5 +1,7 @@ #include + #include + #include #include @@ -223,14 +225,14 @@ TEST(WalletApi, TestGetTokensBalancesForChainIDs) ASSERT_GT(balances.size(), 0); ASSERT_TRUE(balances.contains(testAddress)); - const auto &addressBallance = balances[testAddress]; - ASSERT_GT(addressBallance.size(), 0); + const auto &addressBalance = balances[testAddress]; + ASSERT_GT(addressBalance.size(), 0); - ASSERT_TRUE(addressBallance.contains(sntMain.address)); - ASSERT_EQ(toQString(addressBallance.at(sntMain.address)), "0"); + ASSERT_TRUE(addressBalance.contains(sntMain.address)); + ASSERT_EQ(toQString(addressBalance.at(sntMain.address)), "0"); - ASSERT_TRUE(addressBallance.contains(sntTest.address)); - ASSERT_EQ(toQString(addressBallance.at(sntTest.address)), "0"); + ASSERT_TRUE(addressBalance.contains(sntTest.address)); + ASSERT_EQ(toQString(addressBalance.at(sntTest.address)), "0"); } TEST(WalletApi, TestGetTokensBalancesForChainIDs_WatchOnlyAccount) @@ -267,11 +269,14 @@ TEST(WalletApi, TestGetTokensBalancesForChainIDs_WatchOnlyAccount) ASSERT_GT(balances.size(), 0); ASSERT_TRUE(balances.contains(newAccount.address)); - const auto &addressBallance = balances[newAccount.address]; - ASSERT_GT(addressBallance.size(), 0); + const auto &addressBalance = balances[newAccount.address]; + ASSERT_GT(addressBalance.size(), 0); - ASSERT_TRUE(addressBallance.contains(sntMain.address)); - ASSERT_GT(addressBallance.at(sntMain.address), 0); + ASSERT_TRUE(addressBalance.contains(sntMain.address)); + ASSERT_GT(addressBalance.at(sntMain.address), 0); } +// TODO +// "{"jsonrpc":"2.0","id":0,"error":{"code":-32000,"message":"no tokens for this network"}}" + } // namespace