From 922a38108d105829a9e8f0a80182093fafdb2efa Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Sun, 27 Feb 2022 17:48:16 +0100 Subject: [PATCH] feat(@desktop/cpp-app): base structure for new cpp app - applied new project structure - new cmake scripts organizations - conan script for adding gtest and boost::di - boost::di introduced - gtest introduced Fixes #4894 #4834 --- .gitignore | 6 + src-cpp-structure/CMakeLists.txt | 27 ++ src-cpp-structure/cmake/app-linux.cmake | 22 + src-cpp-structure/cmake/app-mac.cmake | 30 ++ src-cpp-structure/cmake/app-win.cmake | 22 + src-cpp-structure/cmake/conan.cmake | 27 ++ src-cpp-structure/cmake/project-config.cmake | 16 + src-cpp-structure/cmake/services-linux.cmake | 10 + src-cpp-structure/cmake/services-mac.cmake | 22 + src-cpp-structure/cmake/services-win.cmake | 10 + src-cpp-structure/cmake/translation.cmake | 36 ++ src-cpp-structure/conan-debug-profile | 4 + src-cpp-structure/conan-release-profile | 4 + src-cpp-structure/conanfile.py | 10 + .../projects/App/Boot/AppController.cpp | 100 +++++ .../projects/App/Boot/AppController.h | 12 + .../projects/App/Boot/AppWindow.cpp | 50 +++ .../projects/App/Boot/AppWindow.h | 49 +++ .../projects/App/Boot/AppWindow.mm | 37 ++ src-cpp-structure/projects/App/Boot/DI.h | 18 + src-cpp-structure/projects/App/Boot/main.cpp | 6 + src-cpp-structure/projects/App/CMakeLists.txt | 37 ++ .../projects/App/Common/Constants.h | 14 + src-cpp-structure/projects/App/Common/Utils.h | 67 +++ .../projects/App/Core/Engine.cpp | 60 +++ src-cpp-structure/projects/App/Core/Engine.h | 25 ++ .../projects/App/Core/GlobalEvents.cpp | 18 + .../projects/App/Core/GlobalEvents.h | 26 ++ .../projects/App/Core/SignalsManager.cpp | 96 +++++ .../projects/App/Core/SignalsManager.h | 36 ++ .../App/Global/LocalAccountSettings.cpp | 47 ++ .../App/Global/LocalAccountSettings.h | 45 ++ .../projects/App/Global/LocalAppSettings.cpp | 50 +++ .../projects/App/Global/LocalAppSettings.h | 42 ++ .../projects/App/Global/SettingsProperties.h | 45 ++ .../projects/App/Modules/Module.cpp | 26 ++ .../projects/App/Modules/Module.h | 27 ++ .../projects/App/Modules/ModuleBuilder.cpp | 15 + .../projects/App/Modules/ModuleBuilder.h | 21 + .../projects/App/Modules/ModuleInterface.h | 12 + .../App/Modules/SharedModels/SectionItem.cpp | 151 +++++++ .../App/Modules/SharedModels/SectionItem.h | 113 +++++ .../App/Modules/SharedModels/SectionModel.cpp | 123 ++++++ .../App/Modules/SharedModels/SectionModel.h | 55 +++ .../App/Modules/Startup/Controller.cpp | 67 +++ .../projects/App/Modules/Startup/Controller.h | 31 ++ .../App/Modules/Startup/ControllerInterface.h | 22 + .../App/Modules/Startup/Login/Controller.cpp | 98 +++++ .../App/Modules/Startup/Login/Controller.h | 42 ++ .../Startup/Login/ControllerInterface.h | 28 ++ .../App/Modules/Startup/Login/Item.cpp | 34 ++ .../projects/App/Modules/Startup/Login/Item.h | 26 ++ .../App/Modules/Startup/Login/Model.cpp | 73 ++++ .../App/Modules/Startup/Login/Model.h | 31 ++ .../App/Modules/Startup/Login/Module.cpp | 96 +++++ .../App/Modules/Startup/Login/Module.h | 44 ++ .../Modules/Startup/Login/ModuleBuilder.cpp | 27 ++ .../App/Modules/Startup/Login/ModuleBuilder.h | 24 ++ .../Modules/Startup/Login/ModuleInterface.h | 19 + .../Modules/Startup/Login/SelectedAccount.cpp | 38 ++ .../Modules/Startup/Login/SelectedAccount.h | 33 ++ .../App/Modules/Startup/Login/View.cpp | 77 ++++ .../projects/App/Modules/Startup/Login/View.h | 47 ++ .../App/Modules/Startup/Login/ViewInterface.h | 34 ++ .../projects/App/Modules/Startup/Module.cpp | 93 ++++ .../projects/App/Modules/Startup/Module.h | 63 +++ .../App/Modules/Startup/ModuleBuilder.cpp | 30 ++ .../App/Modules/Startup/ModuleBuilder.h | 27 ++ .../App/Modules/Startup/ModuleInterface.h | 21 + .../Modules/Startup/Onboarding/Controller.cpp | 72 ++++ .../Modules/Startup/Onboarding/Controller.h | 33 ++ .../Startup/Onboarding/ControllerInterface.h | 30 ++ .../App/Modules/Startup/Onboarding/Item.cpp | 38 ++ .../App/Modules/Startup/Onboarding/Item.h | 26 ++ .../App/Modules/Startup/Onboarding/Model.cpp | 63 +++ .../App/Modules/Startup/Onboarding/Model.h | 30 ++ .../App/Modules/Startup/Onboarding/Module.cpp | 83 ++++ .../App/Modules/Startup/Onboarding/Module.h | 46 ++ .../Startup/Onboarding/ModuleBuilder.cpp | 25 ++ .../Startup/Onboarding/ModuleBuilder.h | 21 + .../Startup/Onboarding/ModuleInterface.h | 19 + .../App/Modules/Startup/Onboarding/View.cpp | 90 ++++ .../App/Modules/Startup/Onboarding/View.h | 49 +++ .../Startup/Onboarding/ViewInterface.h | 35 ++ .../projects/App/Modules/Startup/View.cpp | 52 +++ .../projects/App/Modules/Startup/View.h | 37 ++ .../App/Modules/Startup/ViewInterface.h | 32 ++ .../projects/Backend/CMakeLists.txt | 25 ++ .../Backend/include/StatusBackend/Accounts.h | 26 ++ .../Backend/include/StatusBackend/Types.h | 37 ++ .../Backend/include/StatusBackend/Utils.h | 125 ++++++ .../include/StatusBackend/WalletAccounts.h | 24 ++ .../projects/Backend/src/Accounts.cpp | 221 ++++++++++ .../projects/Backend/src/WalletAccounts.cpp | 106 +++++ src-cpp-structure/projects/CMakeLists.txt | 6 + .../projects/Services/CMakeLists.txt | 35 ++ .../StatusServices/Accounts/AccountDto.h | 86 ++++ .../Accounts/GeneratedAccountDto.h | 116 +++++ .../include/StatusServices/Accounts/Service.h | 74 ++++ .../Accounts/ServiceInterface.h | 42 ++ .../StatusServices/Accounts/ServiceMock.h | 29 ++ .../include/StatusServices/AccountsService | 5 + .../include/StatusServices/Common/Constants.h | 45 ++ .../include/StatusServices/Common/Json.h | 29 ++ .../include/StatusServices/Common/Logger.h | 45 ++ .../StatusServices/Common/SigningPhrases.h | 53 +++ .../include/StatusServices/CommonService | 6 + .../include/StatusServices/Keychain/Service.h | 30 ++ .../Keychain/ServiceInterface.h | 32 ++ .../include/StatusServices/KeychainService | 3 + .../StatusServices/WalletAccounts/Service.h | 35 ++ .../WalletAccounts/ServiceInterface.h | 31 ++ .../WalletAccounts/WalletAccountDto.h | 57 +++ .../StatusServices/WalletAccountsService | 4 + .../Services/src/Accounts/Service.cpp | 403 ++++++++++++++++++ .../projects/Services/src/Keychain/Keychain.h | 37 ++ .../Services/src/Keychain/Keychain.mm | 162 +++++++ .../Services/src/Keychain/KeychainManager.cpp | 57 +++ .../Services/src/Keychain/KeychainManager.h | 95 +++++ .../Services/src/Keychain/KeychainManager.mm | 74 ++++ .../src/Keychain/LocalAuthentication.h | 30 ++ .../src/Keychain/LocalAuthentication.mm | 152 +++++++ .../Services/src/Keychain/Service.cpp | 47 ++ .../Services/src/WalletAccounts/Service.cpp | 108 +++++ .../projects/ServicesTest/CMakeLists.txt | 32 ++ .../ServicesTest/src/AccountsService.cpp | 75 ++++ .../projects/ServicesTest/src/Constants.h | 11 + .../src/StatusTestEnvironment.cpp | 24 ++ .../ServicesTest/src/StatusTestEnvironment.h | 21 + .../projects/ServicesTest/src/Utils.h | 59 +++ .../projects/ServicesTest/src/main.cpp | 21 + ui/generate-rcc.go | 4 + ui/imports/shared/status/StatusChatInput.qml | 6 +- 133 files changed, 6317 insertions(+), 3 deletions(-) create mode 100644 src-cpp-structure/CMakeLists.txt create mode 100644 src-cpp-structure/cmake/app-linux.cmake create mode 100644 src-cpp-structure/cmake/app-mac.cmake create mode 100644 src-cpp-structure/cmake/app-win.cmake create mode 100644 src-cpp-structure/cmake/conan.cmake create mode 100644 src-cpp-structure/cmake/project-config.cmake create mode 100644 src-cpp-structure/cmake/services-linux.cmake create mode 100644 src-cpp-structure/cmake/services-mac.cmake create mode 100644 src-cpp-structure/cmake/services-win.cmake create mode 100644 src-cpp-structure/cmake/translation.cmake create mode 100644 src-cpp-structure/conan-debug-profile create mode 100644 src-cpp-structure/conan-release-profile create mode 100644 src-cpp-structure/conanfile.py create mode 100644 src-cpp-structure/projects/App/Boot/AppController.cpp create mode 100644 src-cpp-structure/projects/App/Boot/AppController.h create mode 100644 src-cpp-structure/projects/App/Boot/AppWindow.cpp create mode 100644 src-cpp-structure/projects/App/Boot/AppWindow.h create mode 100644 src-cpp-structure/projects/App/Boot/AppWindow.mm create mode 100644 src-cpp-structure/projects/App/Boot/DI.h create mode 100644 src-cpp-structure/projects/App/Boot/main.cpp create mode 100644 src-cpp-structure/projects/App/CMakeLists.txt create mode 100644 src-cpp-structure/projects/App/Common/Constants.h create mode 100644 src-cpp-structure/projects/App/Common/Utils.h create mode 100644 src-cpp-structure/projects/App/Core/Engine.cpp create mode 100644 src-cpp-structure/projects/App/Core/Engine.h create mode 100644 src-cpp-structure/projects/App/Core/GlobalEvents.cpp create mode 100644 src-cpp-structure/projects/App/Core/GlobalEvents.h create mode 100644 src-cpp-structure/projects/App/Core/SignalsManager.cpp create mode 100644 src-cpp-structure/projects/App/Core/SignalsManager.h create mode 100644 src-cpp-structure/projects/App/Global/LocalAccountSettings.cpp create mode 100644 src-cpp-structure/projects/App/Global/LocalAccountSettings.h create mode 100644 src-cpp-structure/projects/App/Global/LocalAppSettings.cpp create mode 100644 src-cpp-structure/projects/App/Global/LocalAppSettings.h create mode 100644 src-cpp-structure/projects/App/Global/SettingsProperties.h create mode 100644 src-cpp-structure/projects/App/Modules/Module.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Module.h create mode 100644 src-cpp-structure/projects/App/Modules/ModuleBuilder.cpp create mode 100644 src-cpp-structure/projects/App/Modules/ModuleBuilder.h create mode 100644 src-cpp-structure/projects/App/Modules/ModuleInterface.h create mode 100644 src-cpp-structure/projects/App/Modules/SharedModels/SectionItem.cpp create mode 100644 src-cpp-structure/projects/App/Modules/SharedModels/SectionItem.h create mode 100644 src-cpp-structure/projects/App/Modules/SharedModels/SectionModel.cpp create mode 100644 src-cpp-structure/projects/App/Modules/SharedModels/SectionModel.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Controller.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Controller.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/ControllerInterface.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/Controller.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/Controller.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/ControllerInterface.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/Item.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/Item.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/Model.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/Model.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/Module.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/Module.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/ModuleBuilder.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/ModuleBuilder.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/ModuleInterface.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/SelectedAccount.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/SelectedAccount.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/View.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/View.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Login/ViewInterface.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Module.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Module.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/ModuleBuilder.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/ModuleBuilder.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/ModuleInterface.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/Controller.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/Controller.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/ControllerInterface.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/Item.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/Item.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/Model.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/Model.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/Module.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/Module.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleBuilder.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleBuilder.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleInterface.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/View.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/View.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/Onboarding/ViewInterface.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/View.cpp create mode 100644 src-cpp-structure/projects/App/Modules/Startup/View.h create mode 100644 src-cpp-structure/projects/App/Modules/Startup/ViewInterface.h create mode 100644 src-cpp-structure/projects/Backend/CMakeLists.txt create mode 100644 src-cpp-structure/projects/Backend/include/StatusBackend/Accounts.h create mode 100644 src-cpp-structure/projects/Backend/include/StatusBackend/Types.h create mode 100644 src-cpp-structure/projects/Backend/include/StatusBackend/Utils.h create mode 100644 src-cpp-structure/projects/Backend/include/StatusBackend/WalletAccounts.h create mode 100644 src-cpp-structure/projects/Backend/src/Accounts.cpp create mode 100644 src-cpp-structure/projects/Backend/src/WalletAccounts.cpp create mode 100644 src-cpp-structure/projects/CMakeLists.txt create mode 100644 src-cpp-structure/projects/Services/CMakeLists.txt create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/Accounts/AccountDto.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/Accounts/GeneratedAccountDto.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/Accounts/Service.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/Accounts/ServiceInterface.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/Accounts/ServiceMock.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/AccountsService create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/Common/Constants.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/Common/Json.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/Common/Logger.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/Common/SigningPhrases.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/CommonService create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/Keychain/Service.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/Keychain/ServiceInterface.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/KeychainService create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/Service.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/ServiceInterface.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/WalletAccountDto.h create mode 100644 src-cpp-structure/projects/Services/include/StatusServices/WalletAccountsService create mode 100644 src-cpp-structure/projects/Services/src/Accounts/Service.cpp create mode 100644 src-cpp-structure/projects/Services/src/Keychain/Keychain.h create mode 100644 src-cpp-structure/projects/Services/src/Keychain/Keychain.mm create mode 100644 src-cpp-structure/projects/Services/src/Keychain/KeychainManager.cpp create mode 100644 src-cpp-structure/projects/Services/src/Keychain/KeychainManager.h create mode 100644 src-cpp-structure/projects/Services/src/Keychain/KeychainManager.mm create mode 100644 src-cpp-structure/projects/Services/src/Keychain/LocalAuthentication.h create mode 100644 src-cpp-structure/projects/Services/src/Keychain/LocalAuthentication.mm create mode 100644 src-cpp-structure/projects/Services/src/Keychain/Service.cpp create mode 100644 src-cpp-structure/projects/Services/src/WalletAccounts/Service.cpp create mode 100644 src-cpp-structure/projects/ServicesTest/CMakeLists.txt create mode 100644 src-cpp-structure/projects/ServicesTest/src/AccountsService.cpp create mode 100644 src-cpp-structure/projects/ServicesTest/src/Constants.h create mode 100644 src-cpp-structure/projects/ServicesTest/src/StatusTestEnvironment.cpp create mode 100644 src-cpp-structure/projects/ServicesTest/src/StatusTestEnvironment.h create mode 100644 src-cpp-structure/projects/ServicesTest/src/Utils.h create mode 100644 src-cpp-structure/projects/ServicesTest/src/main.cpp diff --git a/.gitignore b/.gitignore index 716b8fd1d1..9fb72ceb94 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,9 @@ build/ *.exe *.out *.app + +# Cpp App +*build-src-cpp-structure* +src-cpp-structure/projects/App/translations.qrc +src-cpp-structure/projects/App/i18n/* +src-cpp-structure/projects/App/translations/* \ No newline at end of file diff --git a/src-cpp-structure/CMakeLists.txt b/src-cpp-structure/CMakeLists.txt new file mode 100644 index 0000000000..c328671d21 --- /dev/null +++ b/src-cpp-structure/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) + +project(Status DESCRIPTION "Status project" LANGUAGES CXX) + +include("${CMAKE_SOURCE_DIR}/cmake/project-config.cmake") +include("${CMAKE_SOURCE_DIR}/cmake/conan.cmake") + +find_package(Qt5 REQUIRED COMPONENTS + Core + Gui + Quick + Widgets + Qml + Quick + QuickControls2 + QuickTemplates2 + Multimedia + Concurrent + LinguistTools) + +# The following should be moved to conan. +# But so far we're just adding libs from the vendor folder this way. +# statusgo lib is registered globaly - /vendor/status-go/CMakeLists.txt +SET(STATUS_GO_LIB statusgo_shared) +add_subdirectory(${CMAKE_SOURCE_DIR}/../vendor/status-go bin/status-go) + +add_subdirectory(projects) diff --git a/src-cpp-structure/cmake/app-linux.cmake b/src-cpp-structure/cmake/app-linux.cmake new file mode 100644 index 0000000000..982302420a --- /dev/null +++ b/src-cpp-structure/cmake/app-linux.cmake @@ -0,0 +1,22 @@ +add_executable(${PROJECT_NAME}) + +target_link_libraries( + ${PROJECT_NAME} + Qt5::Core + Qt5::Gui + Qt5::Widgets + Qt5::Quick + Qt5::Qml + Qt5::Quick + Qt5::QuickControls2 + Qt5::QuickTemplates2 + Qt5::Multimedia + ) + +file(GLOB_RECURSE SOURCES + "*.h" + "*.cpp" + ${STATUS_RCC} + ${STATUS_RESOURCES_QRC} + ${STATUS_QRC} + ) diff --git a/src-cpp-structure/cmake/app-mac.cmake b/src-cpp-structure/cmake/app-mac.cmake new file mode 100644 index 0000000000..a1eb707116 --- /dev/null +++ b/src-cpp-structure/cmake/app-mac.cmake @@ -0,0 +1,30 @@ +add_executable(${PROJECT_NAME} MACOSX_BUNDLE) + +find_library(FOUNDATION_FRAMEWORK Foundation) +find_library(IO_KIT_FRAMEWORK IOKit) + +target_link_libraries( + ${PROJECT_NAME} + Qt5::Core + Qt5::Gui + Qt5::Widgets + Qt5::Quick + Qt5::Qml + Qt5::Quick + Qt5::QuickControls2 + Qt5::QuickTemplates2 + Qt5::Multimedia + Qt5::Concurrent + ${FOUNDATION_FRAMEWORK} + ${IO_KIT_FRAMEWORK} + Status.Services + ) + +file(GLOB_RECURSE SOURCES + "*.h" + "*.cpp" + "*.mm" + ${STATUS_RCC} + ${STATUS_RESOURCES_QRC} + ${STATUS_QRC} + ) diff --git a/src-cpp-structure/cmake/app-win.cmake b/src-cpp-structure/cmake/app-win.cmake new file mode 100644 index 0000000000..1b92ff1073 --- /dev/null +++ b/src-cpp-structure/cmake/app-win.cmake @@ -0,0 +1,22 @@ +add_executable(${PROJECT_NAME} WIN32) + +target_link_libraries( + ${PROJECT_NAME} + Qt5::Core + Qt5::Gui + Qt5::Widgets + Qt5::Quick + Qt5::Qml + Qt5::Quick + Qt5::QuickControls2 + Qt5::QuickTemplates2 + Qt5::Multimedia + ) + +file(GLOB_RECURSE SOURCES + "*.h" + "*.cpp" + ${STATUS_RCC} + ${STATUS_RESOURCES_QRC} + ${STATUS_QRC} + ) diff --git a/src-cpp-structure/cmake/conan.cmake b/src-cpp-structure/cmake/conan.cmake new file mode 100644 index 0000000000..8ad2539de0 --- /dev/null +++ b/src-cpp-structure/cmake/conan.cmake @@ -0,0 +1,27 @@ +set(CONAN_WORKING_DIR ${CMAKE_BINARY_DIR}/conan) + +if (EXISTS ${CONAN_WORKING_DIR}) + file(REMOVE_RECURSE ${CONAN_WORKING_DIR}) +endif () + +file(MAKE_DIRECTORY ${CONAN_WORKING_DIR}) + +if (${CMAKE_BUILD_TYPE} STREQUAL Debug) + set(CONAN_PROFILE ${PROJECT_SOURCE_DIR}/conan-debug-profile) +else () + set(CONAN_PROFILE ${PROJECT_SOURCE_DIR}/conan-release-profile) +endif () + +execute_process( + COMMAND conan install ${PROJECT_SOURCE_DIR} -pr=${CONAN_PROFILE} + WORKING_DIRECTORY ${CONAN_WORKING_DIR} + RESULT_VARIABLE CONAN_RESULT + ) + +if (NOT ${CONAN_RESULT} EQUAL 0) + message(FATAL_ERROR "Conan failed: ${CONAN_RESULT}.") +endif () + +include(${CONAN_WORKING_DIR}/conanbuildinfo.cmake) + +conan_basic_setup(KEEP_RPATHS) diff --git a/src-cpp-structure/cmake/project-config.cmake b/src-cpp-structure/cmake/project-config.cmake new file mode 100644 index 0000000000..45428d0235 --- /dev/null +++ b/src-cpp-structure/cmake/project-config.cmake @@ -0,0 +1,16 @@ +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +#set(CMAKE_AUTORCC ON) This is disabled because of `/ui/generate-rcc.go` script usage for generating .qrc and cmake command for .rcc + +# Set this to TRUE if you want to create .ts files and translations.qrc +set(UPDATE_TRANSLATIONS FALSE) + +if ($ENV{QTDIR} LESS_EQUAL "") + message(FATAL_ERROR "Please set the path to your Qt dir as `QTDIR` variable in your ENV. Example: QTDIR=/Qt/Qt5.14.2/5.14.2/clang_64") +endif() + +add_definitions(-DSTATUS_SOURCE_DIR="${CMAKE_SOURCE_DIR}") +add_definitions(-DSTATUS_DEVELOPMENT=true) diff --git a/src-cpp-structure/cmake/services-linux.cmake b/src-cpp-structure/cmake/services-linux.cmake new file mode 100644 index 0000000000..784ecfba54 --- /dev/null +++ b/src-cpp-structure/cmake/services-linux.cmake @@ -0,0 +1,10 @@ +target_link_libraries( + ${PROJECT_NAME} + Qt5::Core + Status.Backend + ) + +file(GLOB_RECURSE SOURCES + "*.h" + "*.cpp" + ) diff --git a/src-cpp-structure/cmake/services-mac.cmake b/src-cpp-structure/cmake/services-mac.cmake new file mode 100644 index 0000000000..04c570fe52 --- /dev/null +++ b/src-cpp-structure/cmake/services-mac.cmake @@ -0,0 +1,22 @@ +find_library(FOUNDATION_FRAMEWORK Foundation) +find_library(IO_KIT_FRAMEWORK IOKit) +find_library(SECURITY_FRAMEWORK Security) +find_library(CORE_SERVICES_FRAMEWORK CoreServices) +find_library(LOCAL_AUTHENTICATION_FRAMEWORK LocalAuthentication) + +target_link_libraries( + ${PROJECT_NAME} + Qt5::Core + ${FOUNDATION_FRAMEWORK} + ${IO_KIT_FRAMEWORK} + ${SECURITY_FRAMEWORK} + ${CORE_SERVICES_FRAMEWORK} + ${LOCAL_AUTHENTICATION_FRAMEWORK} + Status.Backend + ) + +file(GLOB_RECURSE SOURCES + "*.h" + "*.cpp" + "*.mm" + ) diff --git a/src-cpp-structure/cmake/services-win.cmake b/src-cpp-structure/cmake/services-win.cmake new file mode 100644 index 0000000000..784ecfba54 --- /dev/null +++ b/src-cpp-structure/cmake/services-win.cmake @@ -0,0 +1,10 @@ +target_link_libraries( + ${PROJECT_NAME} + Qt5::Core + Status.Backend + ) + +file(GLOB_RECURSE SOURCES + "*.h" + "*.cpp" + ) diff --git a/src-cpp-structure/cmake/translation.cmake b/src-cpp-structure/cmake/translation.cmake new file mode 100644 index 0000000000..0c8ccbf59b --- /dev/null +++ b/src-cpp-structure/cmake/translation.cmake @@ -0,0 +1,36 @@ +function(check_translations) + set(TRANSLATION_DIR "translations") + set(TRANSLATION_BASE_FILE "app_en_US.ts") + set(QM_RESOURCE_FILE "translations.qrc") + set(QM_FILES_FOLDER_NAME "i18n") + + if(NOT UPDATE_TRANSLATIONS) + file(REMOVE ${QM_RESOURCE_FILE}) + else() + # New translations will be added just here + set(TS_FILES + ${TRANSLATION_DIR}/${TRANSLATION_BASE_FILE} + ${TRANSLATION_DIR}/app_es_ES.ts + ) + + if (NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${TRANSLATION_DIR}/${TRANSLATION_BASE_FILE}) + + set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/${QM_FILES_FOLDER_NAME}) + qt5_create_translation(QM_FILES ${TS_FILES} ${SOURCES}) + + endif () + + if (NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${QM_RESOURCE_FILE}) + + file(WRITE ${QM_RESOURCE_FILE} "\n \n") + + foreach(qm_file ${QM_FILES}) + get_filename_component(qm_name ${qm_file} NAME) + file(APPEND ${QM_RESOURCE_FILE} " ${QM_FILES_FOLDER_NAME}/${qm_name}\n") + endforeach(qm_file) + + file(APPEND ${QM_RESOURCE_FILE} " \n\n") + + endif () + endif () +endfunction() diff --git a/src-cpp-structure/conan-debug-profile b/src-cpp-structure/conan-debug-profile new file mode 100644 index 0000000000..2aea5b36dc --- /dev/null +++ b/src-cpp-structure/conan-debug-profile @@ -0,0 +1,4 @@ +include(default) + +[settings] +build_type=Debug \ No newline at end of file diff --git a/src-cpp-structure/conan-release-profile b/src-cpp-structure/conan-release-profile new file mode 100644 index 0000000000..e5cd0fbd07 --- /dev/null +++ b/src-cpp-structure/conan-release-profile @@ -0,0 +1,4 @@ +include(default) + +[settings] +build_type=Release \ No newline at end of file diff --git a/src-cpp-structure/conanfile.py b/src-cpp-structure/conanfile.py new file mode 100644 index 0000000000..9e555dd450 --- /dev/null +++ b/src-cpp-structure/conanfile.py @@ -0,0 +1,10 @@ +from conans import ConanFile + +class StatusConan(ConanFile): + name = "status-desktop" + settings = "os", "compiler", "build_type" + generators = "cmake" + + def requirements(self): + self.requires("di/1.2.0") + self.requires("gtest/1.10.0") \ No newline at end of file diff --git a/src-cpp-structure/projects/App/Boot/AppController.cpp b/src-cpp-structure/projects/App/Boot/AppController.cpp new file mode 100644 index 0000000000..7efcfee094 --- /dev/null +++ b/src-cpp-structure/projects/App/Boot/AppController.cpp @@ -0,0 +1,100 @@ +#include "AppController.h" + +#include "DI.h" +#include "../Core/Engine.h" +#include "../Common/Utils.h" +#include "../Global/LocalAppSettings.h" +#include "../Global/LocalAccountSettings.h" + +#include "AppWindow.h" +#include "../Modules/ModuleBuilder.h" + +#include +#include +//#include <- include for QTranslator + +using namespace Status; + +AppController::AppController() +{ +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif + + QGuiApplication::setOrganizationName("Status"); + QGuiApplication::setOrganizationDomain("status.im"); + QGuiApplication::setApplicationName("Status Desktop"); + + Status::Logger::init(); + Utils::ensureDirectories(); +} + +void registerResources() +{ + Engine::instance()->addImportPath("qrc:/./StatusQ/src"); + Engine::instance()->addImportPath("qrc:/./imports"); + Engine::instance()->addImportPath("qrc:/./app"); + + // This will be removed once we completely move to c++, it's here to align with the current qml code. + Engine::instance()->rootContext()->setContextProperty("cppApp", true); + Engine::instance()->rootContext()->setContextProperty("production", STATUS_DEVELOPMENT); + + Engine::instance()->rootContext()->setContextProperty("localAppSettings", &LocalAppSettings::instance()); + Engine::instance()->rootContext()->setContextProperty("localAccountSettings", &LocalAccountSettings::instance()); + + QString statusSourceDir(STATUS_SOURCE_DIR); + QResource::registerResource(statusSourceDir + "/../resources.rcc"); +} + +int AppController::exec(int& argc, char** argv) +{ + int code; + +// Once we fully move to c++ we should include the following line instead the line below it (it's here just to align with the current qml files). +// qmlRegisterType("AppWindow", 0 , 1, "AppWindow"); + qmlRegisterType("DotherSide", 0 , 1, "StatusWindow"); + + try + { + QGuiApplication app(argc, argv); + + // This is here just to check loading translation on the cmake side, + // will be handled much better later. + // + // QTranslator translator; + // const QString baseName = "app_es_ES"; + // if (translator.load(baseName, QLatin1String(":/i18n"))){ + // app.installTranslator(&translator); + // } + + auto rootModule = Injector.create()(); + rootModule->load(); + + registerResources(); + + QString qmlFile = QStringLiteral("qrc:/main.qml"); + Engine::create(qmlFile); + QObject::connect(Engine::instance(), &Engine::objectCreated, &app, + [url = qmlFile](QObject* obj, const QUrl& objUrl) { + if(!obj && url == objUrl.toString()) + { + auto err = "Failed to create: " + url; + throw std::runtime_error(err.toStdString()); + } + }); + + code = app.exec(); + } + catch (std::exception& e) + { + fprintf(stderr, "error: %s\n", e.what()); + code = -1; + } + catch (...) + { + fprintf(stderr, "unknown error\n"); + code = -1; + } + + return code; +} diff --git a/src-cpp-structure/projects/App/Boot/AppController.h b/src-cpp-structure/projects/App/Boot/AppController.h new file mode 100644 index 0000000000..d02daf72d6 --- /dev/null +++ b/src-cpp-structure/projects/App/Boot/AppController.h @@ -0,0 +1,12 @@ +#pragma once + +namespace Status +{ + class AppController final + { + public: + + AppController(); + int exec(int& argc, char** argv); + }; +} diff --git a/src-cpp-structure/projects/App/Boot/AppWindow.cpp b/src-cpp-structure/projects/App/Boot/AppWindow.cpp new file mode 100644 index 0000000000..070f3cc2f9 --- /dev/null +++ b/src-cpp-structure/projects/App/Boot/AppWindow.cpp @@ -0,0 +1,50 @@ +#include "AppWindow.h" + +using namespace Status; + +AppWindow::AppWindow(QWindow *parent) + : QQuickWindow(parent), + m_isFullScreen(false) +{ + removeTitleBar(); + + connect(this, &QQuickWindow::windowStateChanged, [&](Qt::WindowState windowState) { + if (windowState == Qt::WindowNoState) { + removeTitleBar(); + m_isFullScreen = false; + emit isFullScreenChanged(); + } else if (windowState == Qt::WindowFullScreen) { + m_isFullScreen = true; + emit isFullScreenChanged(); + showTitleBar(); + } + }); +} + +void AppWindow::toggleFullScreen() +{ + if (m_isFullScreen) { + showNormal(); + } else { + showFullScreen(); + } +} + +bool AppWindow::isFullScreen() const +{ + return m_isFullScreen; +} + +void AppWindow::removeTitleBar() +{ +#ifdef Q_OS_MACOS + removeTitleBarMacOs(); +#endif +} + +void AppWindow::showTitleBar() +{ +#ifdef Q_OS_MACOS + showTitleBarMacOs(); +#endif +} diff --git a/src-cpp-structure/projects/App/Boot/AppWindow.h b/src-cpp-structure/projects/App/Boot/AppWindow.h new file mode 100644 index 0000000000..f81493dee7 --- /dev/null +++ b/src-cpp-structure/projects/App/Boot/AppWindow.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +namespace Status +{ + class AppWindow: public QQuickWindow + { + Q_OBJECT + + /*********************************************** + * Not Refactored, Just Taken From DOtherSide + ***********************************************/ + + Q_PROPERTY(bool isFullScreen READ isFullScreen NOTIFY isFullScreenChanged) + + public: + + explicit AppWindow(QWindow *parent = nullptr); + + Q_INVOKABLE void toggleFullScreen(); + + bool isFullScreen() const; + + Q_INVOKABLE void updatePosition() { + auto point = QPoint(screen()->geometry().center().x() - geometry().width() / 2, + screen()->geometry().center().y() - geometry().height() / 2); + if (point != this->position()) { + this->setPosition(point); + } + } + + signals: + void isFullScreenChanged(); + void secondInstanceDetected(); + + private: + void initCallbacks(); + void removeTitleBar(); + void showTitleBar(); +#ifdef Q_OS_MACOS + void removeTitleBarMacOs(); + void showTitleBarMacOs(); +#endif + + private: + bool m_isFullScreen; + }; +} diff --git a/src-cpp-structure/projects/App/Boot/AppWindow.mm b/src-cpp-structure/projects/App/Boot/AppWindow.mm new file mode 100644 index 0000000000..d606d08963 --- /dev/null +++ b/src-cpp-structure/projects/App/Boot/AppWindow.mm @@ -0,0 +1,37 @@ +#include "AppWindow.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace Status; + +void AppWindow::removeTitleBarMacOs() +{ + NSView *nsView = reinterpret_cast(this->winId()); + NSWindow *window = [nsView window]; + + window.titlebarAppearsTransparent = true; + window.titleVisibility = NSWindowTitleHidden; + window.styleMask |= NSWindowStyleMaskFullSizeContentView; + NSButton* close = [window standardWindowButton:NSWindowCloseButton]; + NSView* titleBarContainerView = close.superview.superview; + [titleBarContainerView setHidden:YES]; +} + +void AppWindow::showTitleBarMacOs() +{ + NSView *nsView = reinterpret_cast(this->winId()); + NSWindow *window = [nsView window]; + + window.titlebarAppearsTransparent = true; + window.titleVisibility = NSWindowTitleHidden; + window.styleMask |= NSWindowStyleMaskFullSizeContentView; + NSButton* close = [window standardWindowButton:NSWindowCloseButton]; + NSView* titleBarContainerView = close.superview.superview; + [titleBarContainerView setHidden:NO]; +} diff --git a/src-cpp-structure/projects/App/Boot/DI.h b/src-cpp-structure/projects/App/Boot/DI.h new file mode 100644 index 0000000000..0597dad61d --- /dev/null +++ b/src-cpp-structure/projects/App/Boot/DI.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +#include + +namespace Status +{ + using namespace boost::di; + + const auto Injector = make_injector( + bind.to(), + bind.to(), + bind.to() + ); +} diff --git a/src-cpp-structure/projects/App/Boot/main.cpp b/src-cpp-structure/projects/App/Boot/main.cpp new file mode 100644 index 0000000000..dcaf640088 --- /dev/null +++ b/src-cpp-structure/projects/App/Boot/main.cpp @@ -0,0 +1,6 @@ +#include "AppController.h" + +int main(int argc, char *argv[]) +{ + return Status::AppController().exec(argc, argv); +} diff --git a/src-cpp-structure/projects/App/CMakeLists.txt b/src-cpp-structure/projects/App/CMakeLists.txt new file mode 100644 index 0000000000..fe737c0f13 --- /dev/null +++ b/src-cpp-structure/projects/App/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) + +project(Status.App DESCRIPTION "Main Status App Project") + +include("${CMAKE_SOURCE_DIR}/cmake/translation.cmake") + +# Building resource.rcc from multiple .qrc files +SET(STATUS_RCC ${CMAKE_SOURCE_DIR}/../resources.rcc) +SET(STATUS_QRC_SOURCE ${CMAKE_SOURCE_DIR}/../ui) +SET(STATUS_QRC ${STATUS_QRC_SOURCE}/resources.qrc) +SET(STATUS_RESOURCES_QRC ${CMAKE_SOURCE_DIR}/../resources/resources.qrc) + +add_custom_target(rcc ALL DEPENDS resources.rcc) +add_custom_command( + OUTPUT resources.rcc + COMMENT "Building resources.rcc" + COMMAND ${CMAKE_COMMAND} -E rm -f ${STATUS_RCC} + COMMAND ${CMAKE_COMMAND} -E rm -f ${STATUS_QRC} + COMMAND go run ${CMAKE_SOURCE_DIR}/../ui/generate-rcc.go -source=${STATUS_QRC_SOURCE} -output=${STATUS_QRC} + COMMAND rcc -binary --no-compress ${STATUS_QRC} ${STATUS_RESOURCES_QRC} -o ${STATUS_RCC} + VERBATIM + USES_TERMINAL + PRE_BUILD +) + +# Platform specific stuff are place in the corresponding .cmake file. +if (WIN32) + include("${CMAKE_SOURCE_DIR}/cmake/app-win.cmake") +elseif (APPLE) + include("${CMAKE_SOURCE_DIR}/cmake/app-mac.cmake") +elseif(UNIX) + include("${CMAKE_SOURCE_DIR}/cmake/app-linux.cmake") +endif () + +check_translations() + +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/src-cpp-structure/projects/App/Common/Constants.h b/src-cpp-structure/projects/App/Common/Constants.h new file mode 100644 index 0000000000..23260405c2 --- /dev/null +++ b/src-cpp-structure/projects/App/Common/Constants.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace Status::Constants +{ + const QString UserDataDirName = "Status"; + const QString StatusGoDataDirName = "data"; + const QString TmpDataDirName = "tmp"; + const QString LogsDataDirName = "logs"; + const QString QtDataDirName = "qt"; + const QString KeystoreDataDirName = "keystore"; + const QString GlobalSettingsFileName = "global"; +} diff --git a/src-cpp-structure/projects/App/Common/Utils.h b/src-cpp-structure/projects/App/Common/Utils.h new file mode 100644 index 0000000000..3515cef99b --- /dev/null +++ b/src-cpp-structure/projects/App/Common/Utils.h @@ -0,0 +1,67 @@ +#pragma once + +#include "Constants.h" + +#include +#include + +namespace Status +{ + class Utils final { + public: + static QString applicationDir() + { + return qGuiApp->applicationDirPath(); + } + + static QString defaultDataDir() + { + auto d = QDir(); + if(STATUS_DEVELOPMENT){ + d = QDir(STATUS_SOURCE_DIR); + d.cdUp(); + } + else { + // We should handle paths for different platforms here in case of non development + } + + return d.absolutePath() + QDir::separator() + Constants::UserDataDirName; + } + + static QString statusGoDataDir() + { + return defaultDataDir() + QDir::separator() + Constants::StatusGoDataDirName; + } + + static QString keystoreDataDir() + { + return statusGoDataDir() + QDir::separator() + Constants::KeystoreDataDirName; + } + + static QString tmpDataDir() + { + return defaultDataDir() + QDir::separator() + Constants::TmpDataDirName; + } + + static QString logsDataDir() + { + return defaultDataDir() + QDir::separator() + Constants::LogsDataDirName; + } + + static QString qtDataDir() + { + return defaultDataDir() + QDir::separator() + Constants::QtDataDirName; + } + + static void ensureDirectories() + { + QDir d; + d.mkpath(defaultDataDir()); + d.mkpath(statusGoDataDir()); + d.mkpath(keystoreDataDir()); + d.mkpath(tmpDataDir()); + d.mkpath(logsDataDir()); + d.mkpath(qtDataDir()); + } + }; +} diff --git a/src-cpp-structure/projects/App/Core/Engine.cpp b/src-cpp-structure/projects/App/Core/Engine.cpp new file mode 100644 index 0000000000..7198dda046 --- /dev/null +++ b/src-cpp-structure/projects/App/Core/Engine.cpp @@ -0,0 +1,60 @@ +#include "Engine.h" + +using namespace Status; + +Engine* Engine::instance() +{ + static auto* engine = new Engine(); + return engine; +} + +Engine::Engine() + : QQmlApplicationEngine(nullptr) +{ +} + +Engine::~Engine() +{ +} + +std::shared_ptr Engine::load(const QString& qmlFile) +{ + return std::shared_ptr(new QQmlComponent(instance(), qmlFile, QQmlComponent::Asynchronous)); +} + +void Engine::create(const QString& qmlFile, QQmlContext* context) +{ + QObject* createdObj = nullptr; + auto component = Engine::load(qmlFile); + const auto status = component->status(); + + if (status == QQmlComponent::Ready) + { + createdObj = component->create(); + emit instance()->objectCreated(createdObj, qmlFile); + } + else if (status == QQmlComponent::Loading) + { + const auto create = [c = component, f = qmlFile](const QQmlComponent::Status status) mutable + { + if (status == QQmlComponent::Loading) + { + return; + } + + if (status == QQmlComponent::Ready) + { + emit instance()->objectCreated(c->create(), f); + } + else if (status == QQmlComponent::Null || status == QQmlComponent::Error) + { + emit instance()->objectCreated(nullptr, f); + } + }; + QObject::connect(component.get(), &QQmlComponent::statusChanged, create); + } + else + { + emit instance()->objectCreated(createdObj, qmlFile); + } +} diff --git a/src-cpp-structure/projects/App/Core/Engine.h b/src-cpp-structure/projects/App/Core/Engine.h new file mode 100644 index 0000000000..e077b605d3 --- /dev/null +++ b/src-cpp-structure/projects/App/Core/Engine.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace Status +{ + class Engine final : public QQmlApplicationEngine + { + Q_OBJECT + + public: + + static Engine* instance(); + + // Methos here are just a concept how we can have a single engine instance shared accross the app + // and use it for async instantiation for all qml files over the app. Also we can this way register + // context only within a certain file, not globally. + static std::shared_ptr load(const QString& qmlFile); + static void create(const QString& qmlFile, QQmlContext *context = nullptr); + + private: + explicit Engine(); + ~Engine(); + }; +} diff --git a/src-cpp-structure/projects/App/Core/GlobalEvents.cpp b/src-cpp-structure/projects/App/Core/GlobalEvents.cpp new file mode 100644 index 0000000000..5d9d467faf --- /dev/null +++ b/src-cpp-structure/projects/App/Core/GlobalEvents.cpp @@ -0,0 +1,18 @@ +#include "GlobalEvents.h" + +using namespace Status; + +GlobalEvents& GlobalEvents::instance() +{ + static GlobalEvents events; + + return events; +} + +GlobalEvents::GlobalEvents() +{ +} + +GlobalEvents::~GlobalEvents() +{ +} diff --git a/src-cpp-structure/projects/App/Core/GlobalEvents.h b/src-cpp-structure/projects/App/Core/GlobalEvents.h new file mode 100644 index 0000000000..fce4755ab3 --- /dev/null +++ b/src-cpp-structure/projects/App/Core/GlobalEvents.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace Status +{ + class GlobalEvents final : public QObject + { + Q_OBJECT + + public: + + static GlobalEvents& instance(); + + private: + explicit GlobalEvents(); + ~GlobalEvents(); + + signals: + void nodeReady(const QString& error); + void nodeStarted(const QString& error); + void nodeStopped(const QString& error); + void nodeLogin(const QString& error); + void nodeCrashed(const QString& error); + }; +} diff --git a/src-cpp-structure/projects/App/Core/SignalsManager.cpp b/src-cpp-structure/projects/App/Core/SignalsManager.cpp new file mode 100644 index 0000000000..3b8876e136 --- /dev/null +++ b/src-cpp-structure/projects/App/Core/SignalsManager.cpp @@ -0,0 +1,96 @@ +#include "SignalsManager.h" + +#include "GlobalEvents.h" +#include + +#include "libstatus.h" + +using namespace Status; + +std::map SignalsManager::signalMap; + +SignalsManager* SignalsManager::instance() +{ + static auto* manager = new SignalsManager(); + return manager; +} + +SignalsManager::SignalsManager() + : QObject(nullptr) +{ + SetSignalEventCallback((void*)&SignalsManager::signalCallback); + + signalMap = { + {"node.ready", SignalType::NodeReady}, + {"node.started", SignalType::NodeStarted}, + {"node.stopped", SignalType::NodeStopped}, + {"node.login", SignalType::NodeLogin}, + {"node.crashed", SignalType::NodeCrashed} + }; +} + +SignalsManager::~SignalsManager() +{ +} + +void SignalsManager::processSignal(const QString& statusSignal) +{ + try + { + QJsonParseError json_error; + const QJsonDocument signalEventDoc(QJsonDocument::fromJson(statusSignal.toUtf8(), &json_error)); + if(json_error.error != QJsonParseError::NoError) + { + qWarning() << "Invalid signal received"; + return; + } + decode(signalEventDoc.object()); + } + catch(const std::exception& e) + { + qWarning() << "Error decoding signal, err: ", e.what(); + return; + } +} + +void SignalsManager::decode(const QJsonObject& signalEvent) +{ + SignalType signalType(Unknown); + if(!signalMap.count(signalEvent["type"].toString())) + { + qWarning() << "Unknown signal received: " << signalEvent["type"].toString(); + return; + } + + signalType = signalMap[signalEvent["type"].toString()]; + auto signalError = signalEvent["event"]["error"].toString(); + + switch(signalType) + { + // TODO: create extractor functions like in nim + case NodeLogin: + emit GlobalEvents::instance().nodeLogin(signalError); + break; + case NodeReady: + emit GlobalEvents::instance().nodeReady(signalError); + break; + case NodeStarted: + emit GlobalEvents::instance().nodeStarted(signalError); + break; + case NodeStopped: + emit GlobalEvents::instance().nodeStopped(signalError); + break; + case NodeCrashed: { + qWarning() << "node.crashed, error: " << signalError; + emit GlobalEvents::instance().nodeCrashed(signalError); + break; + } + default: + qWarning() << "Signal decoding not implemented: " << signalEvent; +} +} + +void SignalsManager::signalCallback(const char* data) +{ + QtConcurrent::run(instance(), &SignalsManager::processSignal, QString(data)); +} diff --git a/src-cpp-structure/projects/App/Core/SignalsManager.h b/src-cpp-structure/projects/App/Core/SignalsManager.h new file mode 100644 index 0000000000..739da91006 --- /dev/null +++ b/src-cpp-structure/projects/App/Core/SignalsManager.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +namespace Status +{ + enum SignalType + { + Unknown, + NodeLogin, + NodeReady, + NodeStarted, + NodeStopped, + NodeCrashed + }; + + class SignalsManager final : public QObject + { + Q_OBJECT + + public: + + static SignalsManager* instance(); + + private: + explicit SignalsManager(); + ~SignalsManager(); + + private: + static std::map signalMap; + static void signalCallback(const char* data); + void processSignal(const QString& ev); + void decode(const QJsonObject& signalEvent); + }; + +} diff --git a/src-cpp-structure/projects/App/Global/LocalAccountSettings.cpp b/src-cpp-structure/projects/App/Global/LocalAccountSettings.cpp new file mode 100644 index 0000000000..08f15334c6 --- /dev/null +++ b/src-cpp-structure/projects/App/Global/LocalAccountSettings.cpp @@ -0,0 +1,47 @@ +#include "LocalAccountSettings.h" + +#include "../Common/Utils.h" + +using namespace Status; + +LocalAccountSettings& LocalAccountSettings::instance() +{ + static LocalAccountSettings laSettings; + + return laSettings; +} + +LocalAccountSettings::LocalAccountSettings() +{ +} + +LocalAccountSettings::~LocalAccountSettings() +{ +} + +void LocalAccountSettings::setFileName(const QString& fileName) +{ + auto filePath = Utils::qtDataDir() + QDir::separator() + fileName; + LocalAccountSettings::instance().settings.reset(new QSettings(filePath, QSettings::IniFormat)); +} + +bool LocalAccountSettings::containsKey(const QString& key) +{ + return LocalAccountSettings::instance().settings->contains(key); +} + +void LocalAccountSettings::removeKey(const QString& key) +{ + LocalAccountSettings::instance().settings->remove(key); +} + +QVariant LocalAccountSettingsKeys::getDefaultValue(const QString& key) { + static QMap defaults; + if (defaults.isEmpty()) + { + defaults.insert(storeToKeychain, LocalAccountSettingsPossibleValues::StoreToKeychain::NotNow); + defaults.insert(isKeycardEnabled, false); + } + + return defaults.value(key); +} diff --git a/src-cpp-structure/projects/App/Global/LocalAccountSettings.h b/src-cpp-structure/projects/App/Global/LocalAccountSettings.h new file mode 100644 index 0000000000..7198bebfd7 --- /dev/null +++ b/src-cpp-structure/projects/App/Global/LocalAccountSettings.h @@ -0,0 +1,45 @@ +#pragma once + +#include "SettingsProperties.h" + +#include + +namespace Status +{ + namespace LocalAccountSettingsKeys { + const QString storeToKeychain = "storeToKeychain"; + const QString isKeycardEnabled = "isKeycardEnabled"; + + QVariant getDefaultValue(const QString& key); + } + + namespace LocalAccountSettingsPossibleValues { + namespace StoreToKeychain { + const QString Store = "store"; + const QString NotNow = "notNow"; + const QString Never = "never"; + } + } + + class LocalAccountSettings final : public QObject + { + Q_OBJECT + + public: + + static LocalAccountSettings& instance(); + + void setFileName(const QString& fileName); + bool containsKey(const QString& key); + void removeKey(const QString& key); + + REGISTER_RW_PROPERTY(LocalAccountSettings, LocalAccountSettingsKeys, isKeycardEnabled, bool) + REGISTER_RW_PROPERTY(LocalAccountSettings, LocalAccountSettingsKeys, storeToKeychain, QString) + + private: + explicit LocalAccountSettings(); + ~LocalAccountSettings(); + + std::unique_ptr settings; + }; +} diff --git a/src-cpp-structure/projects/App/Global/LocalAppSettings.cpp b/src-cpp-structure/projects/App/Global/LocalAppSettings.cpp new file mode 100644 index 0000000000..6870aa402a --- /dev/null +++ b/src-cpp-structure/projects/App/Global/LocalAppSettings.cpp @@ -0,0 +1,50 @@ +#include "LocalAppSettings.h" + +#include "../Common/Utils.h" +#include "../Common/Constants.h" + +using namespace Status; + +LocalAppSettings& LocalAppSettings::instance() +{ + static LocalAppSettings laSettings; + + return laSettings; +} + +LocalAppSettings::LocalAppSettings() +{ + if(!settings) + { + auto filePath = Utils::qtDataDir() + QDir::separator() + Constants::GlobalSettingsFileName; + settings.reset(new QSettings(filePath, QSettings::IniFormat)); + } +} + +LocalAppSettings::~LocalAppSettings() +{ +} + +bool LocalAppSettings::containsKey(const QString& key) +{ + return LocalAppSettings::instance().settings->contains(key); +} + +void LocalAppSettings::removeKey(const QString& key) +{ + LocalAppSettings::instance().settings->remove(key); +} + +QVariant LocalAppSettingsKeys::getDefaultValue(const QString& key) { + static QMap defaults; + if (defaults.isEmpty()) + { + defaults.insert(locale, "en"); + defaults.insert(theme, 2); + defaults.insert(appWidth, 1232); + defaults.insert(appHeight, 770); + defaults.insert(appSizeInitialized, false); + } + + return defaults.value(key); +} diff --git a/src-cpp-structure/projects/App/Global/LocalAppSettings.h b/src-cpp-structure/projects/App/Global/LocalAppSettings.h new file mode 100644 index 0000000000..8a6ba6f8af --- /dev/null +++ b/src-cpp-structure/projects/App/Global/LocalAppSettings.h @@ -0,0 +1,42 @@ +#pragma once + +#include "SettingsProperties.h" + +#include + +namespace Status +{ + namespace LocalAppSettingsKeys { + const QString locale = "global/locale"; + const QString theme = "global/theme"; + const QString appWidth = "global/app_width"; + const QString appHeight = "global/app_height"; + const QString appSizeInitialized = "global/app_size_initialized"; + + QVariant getDefaultValue(const QString& key); + } + + class LocalAppSettings final : public QObject + { + Q_OBJECT + + public: + + static LocalAppSettings& instance(); + + bool containsKey(const QString& key); + void removeKey(const QString& key); + + REGISTER_RW_PROPERTY(LocalAppSettings, LocalAppSettingsKeys, locale, QString) + REGISTER_RW_PROPERTY(LocalAppSettings, LocalAppSettingsKeys, theme, int) + REGISTER_RW_PROPERTY(LocalAppSettings, LocalAppSettingsKeys, appWidth, int) + REGISTER_RW_PROPERTY(LocalAppSettings, LocalAppSettingsKeys, appHeight, int) + REGISTER_RW_PROPERTY(LocalAppSettings, LocalAppSettingsKeys, appSizeInitialized, bool) + + private: + explicit LocalAppSettings(); + ~LocalAppSettings(); + + std::unique_ptr settings; + }; +} diff --git a/src-cpp-structure/projects/App/Global/SettingsProperties.h b/src-cpp-structure/projects/App/Global/SettingsProperties.h new file mode 100644 index 0000000000..60883a33e2 --- /dev/null +++ b/src-cpp-structure/projects/App/Global/SettingsProperties.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +template +[[nodiscard]] T extractValue(const QVariant& value) +{ + if constexpr (std::is_same_v) + return value.toBool(); + else if constexpr (std::is_same_v) + return value.toString(); + else if constexpr (std::is_same_v) + return value.toInt(); + else if constexpr (std::is_same_v) + return value.toFloat(); + return T(); +} + +#define REGISTER_RW_PROPERTY(class, nspace, key, type) \ + Q_SIGNALS: \ + void key##Changed(); \ + public: \ + Q_PROPERTY(type key READ get_##key WRITE set_##key NOTIFY key##Changed) \ + [[nodiscard]] type get_##key() const \ + { \ + if(!class::instance().settings || \ + class::instance().settings->fileName().isEmpty()) \ + { \ + qFatal("Please set settings file name first"); \ + return type(); \ + } \ + auto def = nspace::getDefaultValue(nspace::key); \ + return extractValue(class::instance().settings->value(nspace::key, def)); \ + } \ + void set_##key(const type& value) \ + { \ + if(!class::instance().settings || \ + class::instance().settings->fileName().isEmpty()) \ + { \ + qFatal("Please set settings file name first"); \ + return; \ + } \ + class::instance().settings->setValue(nspace::key, value); \ + emit key##Changed(); \ + } diff --git a/src-cpp-structure/projects/App/Modules/Module.cpp b/src-cpp-structure/projects/App/Modules/Module.cpp new file mode 100644 index 0000000000..fca1b9304f --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Module.cpp @@ -0,0 +1,26 @@ +#include "Module.h" + +#include "Startup/Module.h" + +using namespace Status::Modules; + +RootModule::RootModule(Startup::ModuleBuilder moduleBuilder) + : m_startupModuleBuilder(std::move(moduleBuilder)) +{ +} + +void RootModule::load() +{ + m_startupModule = m_startupModuleBuilder(shared_from_this()); + m_startupModule->load(); +} + +void RootModule::startupDidLoad() +{ + m_startupModule->emitStartUpUIRaisedSignal(); +} + +void RootModule::userLoggedIn() +{ + +} diff --git a/src-cpp-structure/projects/App/Modules/Module.h b/src-cpp-structure/projects/App/Modules/Module.h new file mode 100644 index 0000000000..8bb50bd701 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Module.h @@ -0,0 +1,27 @@ +#pragma once + +#include "ModuleInterface.h" +#include "Startup/ModuleBuilder.h" + +#include + +namespace Status::Modules +{ + + class RootModule final : public ModuleAccessInterface + , public Startup::ModuleDelegateInterface + , public std::enable_shared_from_this + { + public: + RootModule(Startup::ModuleBuilder moduleBuilder); + + void load() override; + void startupDidLoad() override; + void userLoggedIn() override; + + private: + + Startup::ModuleBuilder m_startupModuleBuilder; + std::shared_ptr m_startupModule; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/ModuleBuilder.cpp b/src-cpp-structure/projects/App/Modules/ModuleBuilder.cpp new file mode 100644 index 0000000000..94d7924506 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/ModuleBuilder.cpp @@ -0,0 +1,15 @@ +#include "ModuleBuilder.h" + +#include "Module.h" + +using namespace Status::Modules; + +ModuleBuilder::ModuleBuilder(Startup::ModuleBuilder moduleBuilder) + : m_startupModuleBuilder(std::move(moduleBuilder)) +{ +} + +std::shared_ptr ModuleBuilder::operator()() +{ + return std::make_shared(m_startupModuleBuilder); +} diff --git a/src-cpp-structure/projects/App/Modules/ModuleBuilder.h b/src-cpp-structure/projects/App/Modules/ModuleBuilder.h new file mode 100644 index 0000000000..aec9be51c5 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/ModuleBuilder.h @@ -0,0 +1,21 @@ +#pragma once + +#include "ModuleInterface.h" +#include "Startup/ModuleBuilder.h" + +#include + +namespace Status::Modules +{ + + class ModuleBuilder final + { + public: + ModuleBuilder(Startup::ModuleBuilder moduleBuilder); + + [[nodiscard]] std::shared_ptr operator()(); + + private: + Startup::ModuleBuilder m_startupModuleBuilder; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/ModuleInterface.h b/src-cpp-structure/projects/App/Modules/ModuleInterface.h new file mode 100644 index 0000000000..a65c2b61fe --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/ModuleInterface.h @@ -0,0 +1,12 @@ +#pragma once + +namespace Status::Modules +{ + class ModuleAccessInterface + { + public: + virtual ~ModuleAccessInterface() = default; + + virtual void load() = 0; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/SharedModels/SectionItem.cpp b/src-cpp-structure/projects/App/Modules/SharedModels/SectionItem.cpp new file mode 100644 index 0000000000..d92411ee2d --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/SharedModels/SectionItem.cpp @@ -0,0 +1,151 @@ +#include + +#include "SectionItem.h" + +using namespace Status::Shared::Models; + +SectionItem::SectionItem(QObject* parent, + const QString& id, + SectionType sectionType, + const QString& name, + const QString& description, + const QString& image, + const QString& icon, + const QString& color, + bool active, + bool enabled, + bool amISectionAdmin, + bool hasNotification, + int notificationsCount, + bool isMember, + bool joined, + bool canJoin, + bool canManageUsers, + bool canRequestAccess, + int access, + bool ensOnly) + : QObject(parent) + , m_id(id) + , m_sectionType(sectionType) + , m_name(name) + , m_amISectionAdmin(amISectionAdmin) + , m_description(description) + , m_image(image) + , m_icon(icon) + , m_color(color) + , m_hasNotification(hasNotification) + , m_notificationsCount(notificationsCount) + , m_active(active) + , m_enabled(enabled) + , m_isMember(isMember) + , m_joined(joined) + , m_canJoin(canJoin) + , m_canManageUsers(canManageUsers) + , m_canRequestAccess(canRequestAccess) + , m_access(access) + , m_ensOnly(ensOnly) +{ } + +SectionType SectionItem::getSectionType() const +{ + return m_sectionType; +} + +const QString& SectionItem::getId() const +{ + return m_id; +} + +const QString& SectionItem::getName() const +{ + return m_name; +} + +bool SectionItem::getAmISectionAdmin() const +{ + return m_amISectionAdmin; +} + +const QString& SectionItem::getDescription() const +{ + return m_description; +} + +const QString& SectionItem::getImage() const +{ + return m_image; +} + +const QString& SectionItem::getIcon() const +{ + return m_icon; +} + +const QString& SectionItem::getColor() const +{ + return m_color; +} + +bool SectionItem::getHasNotification() const +{ + return m_hasNotification; +} + +int SectionItem::getNotificationsCount() const +{ + return m_notificationsCount; +} + +bool SectionItem::getIsActive() const +{ + return m_active; +} + +bool SectionItem::getIsEnabled() const +{ + return m_enabled; +} + +bool SectionItem::getIsMember() const +{ + return m_isMember; +} + +bool SectionItem::getHasJoined() const +{ + return m_joined; +} + +bool SectionItem::getCanJoin() const +{ + return m_canJoin; +} + +bool SectionItem::getCanManageUsers() const +{ + return m_canManageUsers; +} + +bool SectionItem::getCanRequestAccess() const +{ + return m_canRequestAccess; +} + +int SectionItem::getAccess() const +{ + return m_access; +} + +bool SectionItem::getIsEnsOnly() const +{ + return m_ensOnly; +} + +void SectionItem::setIsActive(bool isActive) +{ + if(m_active != isActive) + { + m_active = isActive; + activeChanged(); + } +} diff --git a/src-cpp-structure/projects/App/Modules/SharedModels/SectionItem.h b/src-cpp-structure/projects/App/Modules/SharedModels/SectionItem.h new file mode 100644 index 0000000000..bc9ef6c390 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/SharedModels/SectionItem.h @@ -0,0 +1,113 @@ +#pragma once + +#include + +namespace Status::Shared::Models +{ + enum SectionType + { + Unkown = -1, + Chat, + Community, + Wallet, + Browser, + ProfileSettings, + NodeManagement + }; + class SectionItem : public QObject + { + Q_OBJECT + Q_PROPERTY(QString id READ getId) + Q_PROPERTY(int sectionType READ getSectionType) + Q_PROPERTY(QString name READ getName) + Q_PROPERTY(bool amISectionAdmin READ getAmISectionAdmin) + Q_PROPERTY(QString description READ getDescription) + Q_PROPERTY(QString image READ getImage) + Q_PROPERTY(QString icon READ getIcon) + Q_PROPERTY(QString color READ getColor) + Q_PROPERTY(bool hasNotification READ getHasNotification) + Q_PROPERTY(int notificationsCount READ getNotificationsCount) + Q_PROPERTY(bool active READ getIsActive NOTIFY activeChanged) + Q_PROPERTY(bool enabled READ getIsEnabled) + Q_PROPERTY(bool joined READ getHasJoined) + Q_PROPERTY(bool isMember READ getIsMember) + Q_PROPERTY(bool canJoin READ getCanJoin) + Q_PROPERTY(bool canManageUsers READ getCanManageUsers) + Q_PROPERTY(bool canRequestAccess READ getCanRequestAccess) + Q_PROPERTY(int access READ getAccess) + Q_PROPERTY(bool ensOnly READ getIsEnsOnly) + + public: + SectionItem(QObject* parent = nullptr, + const QString& id = "", + SectionType sectionType = SectionType::Unkown, + const QString& name = "", + const QString& description = "", + const QString& image = "", + const QString& icon = "", + const QString& color = "", + bool active = false, + bool enabled = false, + bool amISectionAdmin = false, + bool hasNotification = false, + int notificationsCount = 0, + bool isMember = false, + bool joined = false, + bool canJoin = false, + bool canManageUsers = false, + bool canRequestAccess = false, + int access = 0, + bool ensOnly = false); + ~SectionItem() = default; + + // Getters + SectionType getSectionType() const; + const QString& getId() const; + const QString& getName() const; + bool getAmISectionAdmin() const; + const QString& getDescription() const; + const QString& getImage() const; + const QString& getIcon() const; + const QString& getColor() const; + bool getHasNotification() const; + int getNotificationsCount() const; + bool getIsActive() const; + bool getIsEnabled() const; + bool getIsMember() const; + bool getHasJoined() const; + bool getCanJoin() const; + bool getCanManageUsers() const; + bool getCanRequestAccess() const; + int getAccess() const; + bool getIsEnsOnly() const; + + // Setters + void setIsActive(bool isActive); + + signals: + void activeChanged(); + + private: + SectionType m_sectionType; + QString m_id; + QString m_name; + bool m_amISectionAdmin; + QString m_description; + QString m_image; + QString m_icon; + QString m_color; + bool m_hasNotification; + int m_notificationsCount; + bool m_active; + bool m_enabled; + bool m_isMember; + bool m_joined; + bool m_canJoin; + bool m_canManageUsers; + bool m_canRequestAccess; + int m_access; + bool m_ensOnly; + // membersModel: user_model.Model + // pendingRequestsToJoinModel: PendingRequestModel + }; +} diff --git a/src-cpp-structure/projects/App/Modules/SharedModels/SectionModel.cpp b/src-cpp-structure/projects/App/Modules/SharedModels/SectionModel.cpp new file mode 100644 index 0000000000..5af9cdaad1 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/SharedModels/SectionModel.cpp @@ -0,0 +1,123 @@ +#include "SectionModel.h" + +using namespace Status::Shared::Models; + +SectionModel::SectionModel(QObject* parent) + : QAbstractListModel(parent) +{ } + +QHash SectionModel::roleNames() const +{ + QHash roles; + + roles[Id] = "id"; + roles[SectionType] = "sectionType"; + roles[Name] = "name"; + roles[AmISectionAdmin] = "amISectionAdmin"; + roles[Description] = "description"; + roles[Image] = "image"; + roles[Icon] = "icon"; + roles[Color] = "color"; + roles[HasNotification] = "hasNotification"; + roles[NotificationsCount] = "notificationsCount"; + roles[Active] = "active"; + roles[Enabled] = "enabled"; + roles[Joined] = "joined"; + roles[IsMember] = "isMember"; + roles[CanJoin] = "canJoin"; + roles[CanManageUsers] = "canManageUsers"; + roles[CanRequestAccess] = "canRequestAccess"; + roles[Access] = "access"; + roles[EnsOnly] = "ensOnly"; + roles[MembersModel] = "members"; + roles[PendingRequestsToJoinModel] = "pendingRequestsToJoin"; + return roles; +} + +int SectionModel::rowCount(const QModelIndex& parent = QModelIndex()) const +{ + return m_items.size(); +} + +QVariant SectionModel::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + { + return QVariant(); + } + + if(index.row() < 0 || index.row() >= m_items.size()) + { + return QVariant(); + } + + SectionItem* item = m_items.at(index.row()); + + switch(role) + { + case Id: return item->getId(); + case SectionType: return item->getSectionType(); + case Name: return item->getName(); + case AmISectionAdmin: return item->getAmISectionAdmin(); + case Description: return item->getDescription(); + case Image: return item->getImage(); + case Icon: return item->getIcon(); + case Color: return item->getColor(); + case HasNotification: return item->getHasNotification(); + case NotificationsCount: return item->getNotificationsCount(); + case Active: return item->getIsActive(); + case Enabled: return item->getIsEnabled(); + case Joined: return item->getHasJoined(); + case IsMember: return item->getIsMember(); + case CanJoin: return item->getCanJoin(); + case CanManageUsers: return item->getCanManageUsers(); + case CanRequestAccess: return item->getCanRequestAccess(); + case Access: return item->getAccess(); + case EnsOnly: return item->getIsEnsOnly(); + // To Do + case MembersModel: return QVariant(); + case PendingRequestsToJoinModel: return QVariant(); + } + + return QVariant(); +} + +void SectionModel::addItem(SectionItem* item) +{ + beginInsertRows(QModelIndex(), m_items.size(), m_items.size()); + m_items.append(item); + endInsertRows(); +} + +void SectionModel::setActiveSection(const QString& Id) +{ + + for(int i = 0; i < m_items.size(); ++i) + { + auto newIndex = createIndex(i, 0, nullptr); + if(m_items.at(i)->getIsActive()) + { + m_items.at(i)->setIsActive(false); + dataChanged(newIndex, newIndex, QVector(ModelRole::Active)); + } + if(m_items.at(i)->getId() == Id) + { + m_items.at(i)->setIsActive(true); + dataChanged(newIndex, newIndex, QVector(ModelRole::Active)); + } + } +} + +QPointer SectionModel::getActiveItem() +{ + SectionItem* activeItem = nullptr; + for(auto item : m_items) + { + if(item->getIsActive()) + { + activeItem = item; + break; + } + } + return activeItem; +} diff --git a/src-cpp-structure/projects/App/Modules/SharedModels/SectionModel.h b/src-cpp-structure/projects/App/Modules/SharedModels/SectionModel.h new file mode 100644 index 0000000000..f7af8e73a7 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/SharedModels/SectionModel.h @@ -0,0 +1,55 @@ +#pragma once + +#include "SectionItem.h" + +#include + +namespace Status::Shared::Models +{ + class SectionModel : public QAbstractListModel + { + Q_OBJECT + + public: + enum ModelRole + { + Id = Qt::UserRole + 1, + SectionType, + Name, + AmISectionAdmin, + Description, + Image, + Icon, + Color, + HasNotification, + NotificationsCount, + Active, + Enabled, + Joined, + IsMember, + CanJoin, + CanManageUsers, + CanRequestAccess, + Access, + EnsOnly, + MembersModel, + PendingRequestsToJoinModel + }; + + explicit SectionModel(QObject* parent = nullptr); + ~SectionModel() = default; + + QHash roleNames() const override; + int rowCount(const QModelIndex&) const override; + QVariant data(const QModelIndex& index, int role) const override; + + void addItem(SectionItem* item); + void setActiveSection(const QString& Id); + QPointer getActiveItem(); + + // To add other api's later as needed + + private: + QVector m_items; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Controller.cpp b/src-cpp-structure/projects/App/Modules/Startup/Controller.cpp new file mode 100644 index 0000000000..cbc0a4f301 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Controller.cpp @@ -0,0 +1,67 @@ +#include "Controller.h" + +#include "../../Core/GlobalEvents.h" +#include "../../Common/Utils.h" + +#include + +using namespace Status::Modules::Startup; + +Controller::Controller(std::shared_ptr accountsService) + : QObject(nullptr) + , m_delegate(nullptr) + , m_accountsService(std::move(accountsService)) +{ +} + +void Controller::setDelegate(std::shared_ptr delegate) +{ + m_delegate = std::move(delegate); +} + +void Controller::init() +{ + m_accountsService->init(Utils::statusGoDataDir()); + + QObject::connect(&GlobalEvents::instance(), &GlobalEvents::nodeLogin, this, &Controller::onLogin); + QObject::connect(&GlobalEvents::instance(), &GlobalEvents::nodeStopped, this, &Controller::onNodeStopped); + QObject::connect(&GlobalEvents::instance(), &GlobalEvents::nodeReady, this, &Controller::onNodeReady); +} + +bool Controller::shouldStartWithOnboardingScreen() +{ + return m_accountsService->openedAccounts().isEmpty(); +} + +void Controller::onLogin(const QString& error) +{ + if(!error.isEmpty()) + { + qWarning() << error; + return; + } + + m_delegate->userLoggedIn(); +} + +void Controller::onNodeStopped(const QString& error) +{ + if(!error.isEmpty()) + { + qWarning() << error; + } + + m_accountsService->clear(); + m_delegate->emitLogOutSignal(); +} + +void Controller::onNodeReady(const QString& error) +{ + if(!error.isEmpty()) + { + qWarning() << error; + return; + } + + // In case of ready node we can do something here if needed. +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Controller.h b/src-cpp-structure/projects/App/Modules/Startup/Controller.h new file mode 100644 index 0000000000..bc5d91bf7e --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Controller.h @@ -0,0 +1,31 @@ +#pragma once + +#include "ControllerInterface.h" + +#include + +namespace Status::Modules::Startup +{ + class Controller : public QObject, + public ControllerInterface + { + Q_OBJECT + + public: + explicit Controller(std::shared_ptr accountsService); + void setDelegate(std::shared_ptr delegate); + + // Controller Interface + void init() override; + bool shouldStartWithOnboardingScreen() override; + + private slots: + void onLogin(const QString& error); + void onNodeStopped(const QString& error); + void onNodeReady(const QString& error); + + private: + std::shared_ptr m_accountsService; + std::shared_ptr m_delegate; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/ControllerInterface.h b/src-cpp-structure/projects/App/Modules/Startup/ControllerInterface.h new file mode 100644 index 0000000000..e6be9ddd38 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/ControllerInterface.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace Status::Modules::Startup +{ + class ControllerInterface + { + public: + virtual ~ControllerInterface() = default; + + virtual void init() = 0; + virtual bool shouldStartWithOnboardingScreen() = 0; + }; + + class ControllerDelegateInterface + { + public: + virtual void userLoggedIn() = 0; + virtual void emitLogOutSignal() = 0; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/Controller.cpp b/src-cpp-structure/projects/App/Modules/Startup/Login/Controller.cpp new file mode 100644 index 0000000000..0212db4f8e --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/Controller.cpp @@ -0,0 +1,98 @@ +#include "Controller.h" + +#include "../../../Core/GlobalEvents.h" +#include "../../../Global/LocalAccountSettings.h" + +using namespace Status::Modules::Startup::Login; + +Controller::Controller(std::shared_ptr accountsService, + std::shared_ptr keychainService) + : QObject(nullptr) + , m_delegate(nullptr) + , m_accountsService(std::move(accountsService)) + , m_keychainService(std::move(keychainService)) +{ +} + +void Controller::setDelegate(std::shared_ptr delegate) +{ + m_delegate = std::move(delegate); +} + +void Controller::init() +{ + m_keychainService->subscribe(shared_from_this()); + + QObject::connect(&GlobalEvents::instance(), &GlobalEvents::nodeLogin, this, &Controller::onLogin); +} + +void Controller::onLogin(const QString& error) +{ + if(!error.isEmpty()) + { + m_delegate->emitAccountLoginError(error); + } +} + +QVector Controller::getOpenedAccounts() const +{ + return m_accountsService->openedAccounts(); +} + +Status::Accounts::AccountDto Controller::getSelectedAccount() const +{ + auto openedAccounts = getOpenedAccounts(); + foreach(const auto& acc, openedAccounts) + { + if(acc.keyUid == m_selectedAccountKeyUid) + { + return acc; + } + } + + // TODO: For situations like this, should be better to return a std::optional instead? + return Accounts::AccountDto(); +} + +void Controller::setSelectedAccountKeyUid(const QString& keyUid) +{ + m_selectedAccountKeyUid = keyUid; + +#ifdef Q_OS_MACOS + // Dealing with Keychain is the MacOS only feature + + auto selectedAccount = getSelectedAccount(); + LocalAccountSettings::instance().setFileName(selectedAccount.name); + + auto value = LocalAccountSettings::instance().get_storeToKeychain(); + if (value != LocalAccountSettingsPossibleValues::StoreToKeychain::Store) + return; + + m_keychainService->tryToObtainPassword(selectedAccount.name); +#endif +} + +void Controller::login(const QString& password) +{ + auto selectedAccount = Controller::getSelectedAccount(); + auto error = m_accountsService->login(selectedAccount, password); + if(!error.isEmpty()) + { + m_delegate->emitAccountLoginError(error); + } +} + +void Controller::onKeychainManagerError(const QString& errorType, const int errorCode, const QString& errorDescription) +{ + // We are notifying user only about keychain errors. + if (errorType == Keychain::ErrorTypeAuthentication) + return; + + LocalAccountSettings::instance().removeKey(LocalAccountSettingsKeys::storeToKeychain); + m_delegate->emitObtainingPasswordError(errorDescription); +} + +void Controller::onKeychainManagerSuccess(const QString& data) +{ + m_delegate->emitObtainingPasswordSuccess(data); +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/Controller.h b/src-cpp-structure/projects/App/Modules/Startup/Login/Controller.h new file mode 100644 index 0000000000..18bce5c66c --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/Controller.h @@ -0,0 +1,42 @@ +#pragma once + +#include "ControllerInterface.h" + +namespace Status::Modules::Startup::Login +{ + class Controller : public QObject + , public ControllerInterface + , public Keychain::Listener + , public std::enable_shared_from_this + { + Q_OBJECT + + public: + explicit Controller(std::shared_ptr accountsService, + std::shared_ptr keychainService); + void setDelegate(std::shared_ptr delegate); + + // Controller Interface + void init() override; + QVector getOpenedAccounts() const override; + void setSelectedAccountKeyUid(const QString& keyUid) override; + void login(const QString& password) override; + + // Listener Interface + void onKeychainManagerError(const QString& errorType, const int errorCode, + const QString& errorDescription) override; + void onKeychainManagerSuccess(const QString& data) override; + + private slots: + void onLogin(const QString& error); + + private: + Accounts::AccountDto getSelectedAccount() const; + + private: + std::shared_ptr m_accountsService; + std::shared_ptr m_keychainService; + std::shared_ptr m_delegate; + QString m_selectedAccountKeyUid; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/ControllerInterface.h b/src-cpp-structure/projects/App/Modules/Startup/Login/ControllerInterface.h new file mode 100644 index 0000000000..810238d77a --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/ControllerInterface.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include + +namespace Status::Modules::Startup::Login +{ + class ControllerInterface + { + public: + virtual ~ControllerInterface() = default; + + virtual void init() = 0; + virtual QVector getOpenedAccounts() const = 0; + virtual void setSelectedAccountKeyUid(const QString& keyUid) = 0; + virtual void login(const QString& password) = 0; + }; + + class ControllerDelegateInterface + { + public: + virtual void emitAccountLoginError(const QString& error) = 0; + virtual void emitObtainingPasswordError(const QString& errorDescription) = 0; + virtual void emitObtainingPasswordSuccess(const QString& password) = 0; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/Item.cpp b/src-cpp-structure/projects/App/Modules/Startup/Login/Item.cpp new file mode 100644 index 0000000000..ad582c1c4c --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/Item.cpp @@ -0,0 +1,34 @@ +#include "Item.h" + +using namespace Status::Modules::Startup::Login; + +Item::Item(const QString& name, const QString& identicon, const QString& thumbnailImage, const QString& largeImage, + const QString& keyUid) + : m_name(name) + , m_identicon(identicon) + , m_thumbnailImage(thumbnailImage) + , m_largeImage(largeImage) + , m_keyUid(keyUid) +{ +} + +QString Item::getName() const +{ + return m_name; +} +QString Item::getIdenticon() const +{ + return m_identicon; +} +QString Item::getThumbnailImage() const +{ + return m_thumbnailImage; +} +QString Item::getLargeImage() const +{ + return m_largeImage; +} +QString Item::getKeyUid() const +{ + return m_keyUid; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/Item.h b/src-cpp-structure/projects/App/Modules/Startup/Login/Item.h new file mode 100644 index 0000000000..7d6ead8d77 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/Item.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace Status::Modules::Startup::Login +{ + class Item + { + public: + Item() {} + Item(const QString& name, const QString& identicon, const QString& thumbnailImage, const QString& largeImage, + const QString& keyUid); + QString getName() const; + QString getIdenticon() const; + QString getThumbnailImage() const; + QString getLargeImage() const; + QString getKeyUid() const; + + private: + QString m_name; + QString m_identicon; + QString m_thumbnailImage; + QString m_largeImage; + QString m_keyUid; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/Model.cpp b/src-cpp-structure/projects/App/Modules/Startup/Login/Model.cpp new file mode 100644 index 0000000000..e8672eaf57 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/Model.cpp @@ -0,0 +1,73 @@ +#include "Model.h" + +using namespace Status::Modules::Startup::Login; + +Model::Model(QObject* parent) + : QAbstractListModel(parent) +{ +} + +QHash Model::roleNames() const +{ + QHash roles; + roles[Name] = "username"; + roles[Identicon] = "identicon"; + roles[ThumbnailImage] = "thumbnailImage"; + roles[LargeImage] = "largeImage"; + roles[KeyUid] = "keyUid"; + return roles; +} + +int Model::rowCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent) + return m_items.size(); +} + +QVariant Model::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + { + return QVariant(); + } + + if(index.row() < 0 || index.row() > m_items.size()) + { + return QVariant(); + } + + Item item = m_items[index.row()]; + + switch(role) + { + case Name: + return item.getName(); + case Identicon: + return item.getIdenticon(); + case ThumbnailImage: + return item.getThumbnailImage(); + case LargeImage: + return item.getLargeImage(); + case KeyUid: + return item.getKeyUid(); + } + + return QVariant(); +} + +void Model::setItems(QVector items) +{ + beginResetModel(); + m_items = std::move(items); + endResetModel(); +} + +Item Model::getItemAtIndex(const int index) const +{ + if(index < 0 || index >= m_items.size()) + { + return Item(); + } + + return m_items[index]; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/Model.h b/src-cpp-structure/projects/App/Modules/Startup/Login/Model.h new file mode 100644 index 0000000000..bcc42668b0 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/Model.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Item.h" + +namespace Status::Modules::Startup::Login +{ + class Model : public QAbstractListModel + { + Q_OBJECT + + public: + enum ModelRole + { + Name = Qt::UserRole + 1, + Identicon, + ThumbnailImage, + LargeImage, + KeyUid + }; + + explicit Model(QObject* parent = nullptr); + QHash roleNames() const; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex& index, int role) const; + void setItems(QVector items); + Item getItemAtIndex(const int index) const; + + private: + QVector m_items; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/Module.cpp b/src-cpp-structure/projects/App/Modules/Startup/Login/Module.cpp new file mode 100644 index 0000000000..c0ca48646e --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/Module.cpp @@ -0,0 +1,96 @@ +#include "Module.h" + +#include "Controller.h" +#include "View.h" + +#include "../../../Core/Engine.h" + +using namespace Status::Modules::Startup::Login; + +Module::Module(std::shared_ptr delegate, + std::shared_ptr controller, + std::shared_ptr view) + : m_delegate(std::move(delegate)) + , m_controller(std::move(controller)) + , m_view(std::move(view)) +{ +} + +void Module::load() +{ + Engine::instance()->rootContext()->setContextProperty("loginModule", m_view->getQObject()); + m_controller->init(); + m_view->load(); + + const QVector openedAccounts = m_controller->getOpenedAccounts(); + if(openedAccounts.size() > 0) + { + QVector items; + foreach(const Accounts::AccountDto& acc, openedAccounts) + { + QString thumbnailImage; + QString largeImage; + extractImages(acc, thumbnailImage, largeImage); + items << Item(acc.name, acc.identicon, thumbnailImage, largeImage, acc.keyUid); + } + + m_view->setModelItems(items); + + // set the first account as selected one + m_controller->setSelectedAccountKeyUid(items[0].getKeyUid()); + setSelectedAccount(items[0]); + } +} + +bool Module::isLoaded() +{ + return m_moduleLoaded; +} + +void Module::viewDidLoad() +{ + m_moduleLoaded = true; + m_delegate->loginDidLoad(); +} + +void Module::extractImages(const Accounts::AccountDto& account, QString& thumbnailImage, QString& largeImage) +{ + foreach(const Accounts::Image& img, account.images) + { + if(img.imgType == "thumbnail") + { + thumbnailImage = img.uri; + } + else if(img.imgType == "large") + { + largeImage = img.uri; + } + } +} + +void Module::setSelectedAccount(const Item& item) +{ + m_controller->setSelectedAccountKeyUid(item.getKeyUid()); + m_view->setSelectedAccount(item); +} + +void Module::login(const QString& password) +{ + m_controller->login(password); +} + +void Module::emitAccountLoginError(const QString& error) +{ + m_view->emitAccountLoginError(error); +} + +void Module::emitObtainingPasswordError(const QString& errorDescription) +{ + m_view->emitObtainingPasswordError(errorDescription); +} + +void Module::emitObtainingPasswordSuccess(const QString& password) +{ + m_view->emitObtainingPasswordSuccess(password); +} + diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/Module.h b/src-cpp-structure/projects/App/Modules/Startup/Login/Module.h new file mode 100644 index 0000000000..0cd8f4cc2f --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/Module.h @@ -0,0 +1,44 @@ +#pragma once + +#include "ModuleInterface.h" +#include "ControllerInterface.h" +#include "ViewInterface.h" + +namespace Status::Modules::Startup::Login +{ + + class Module final : public ModuleAccessInterface + , public ControllerDelegateInterface + , public ViewDelegateInterface + , public std::enable_shared_from_this + { + public: + Module(std::shared_ptr delegate, + std::shared_ptr controller, + std::shared_ptr view); + + // Module Access + void load() override; + bool isLoaded() override; + + // Controller Delegate + void emitAccountLoginError(const QString& error) override; + void emitObtainingPasswordError(const QString& errorDescription) override; + void emitObtainingPasswordSuccess(const QString& password) override; + + // View Delegate + void viewDidLoad() override; + void setSelectedAccount(const Item& item) override; + void login(const QString& password) override; + + private: + void checkIfModuleDidLoad(); + void extractImages(const Accounts::AccountDto& account, QString& thumbnailImage, QString& largeImage); + + private: + std::shared_ptr m_delegate; + std::shared_ptr m_controller; + std::shared_ptr m_view; + bool m_moduleLoaded {false}; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/ModuleBuilder.cpp b/src-cpp-structure/projects/App/Modules/Startup/Login/ModuleBuilder.cpp new file mode 100644 index 0000000000..316c66b398 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/ModuleBuilder.cpp @@ -0,0 +1,27 @@ +#include "ModuleBuilder.h" + +#include "Module.h" +#include "Controller.h" +#include "View.h" + +using namespace Status::Modules::Startup::Login; + +ModuleBuilder::ModuleBuilder(std::shared_ptr accountsService, + std::shared_ptr keychainService) + : m_accountsService(std::move(accountsService)) + , m_keychainService(std::move(keychainService)) +{ +} + +std::shared_ptr ModuleBuilder::operator()(std::shared_ptr delegate) { + + auto controller = std::make_shared(m_accountsService, m_keychainService); + auto view = std::make_shared(); + + auto module = std::make_shared(delegate, controller, view); + + controller->setDelegate(module); + view->setDelegate(module); + + return module; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/ModuleBuilder.h b/src-cpp-structure/projects/App/Modules/Startup/Login/ModuleBuilder.h new file mode 100644 index 0000000000..8a146a5ef3 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/ModuleBuilder.h @@ -0,0 +1,24 @@ +#pragma once + +#include "ModuleInterface.h" + +#include +#include + +#include + +namespace Status::Modules::Startup::Login +{ + class ModuleBuilder final + { + public: + ModuleBuilder(std::shared_ptr accountsService, + std::shared_ptr keychainService); + + [[nodiscard]] std::shared_ptr operator()(std::shared_ptr delegate); + + private: + std::shared_ptr m_accountsService; + std::shared_ptr m_keychainService; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/ModuleInterface.h b/src-cpp-structure/projects/App/Modules/Startup/Login/ModuleInterface.h new file mode 100644 index 0000000000..54b385bba8 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/ModuleInterface.h @@ -0,0 +1,19 @@ +#pragma once + +namespace Status::Modules::Startup::Login +{ + class ModuleAccessInterface + { + public: + virtual ~ModuleAccessInterface() = default; + + virtual void load() = 0; + virtual bool isLoaded() = 0; + }; + + class ModuleDelegateInterface + { + public: + virtual void loginDidLoad() = 0; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/SelectedAccount.cpp b/src-cpp-structure/projects/App/Modules/Startup/Login/SelectedAccount.cpp new file mode 100644 index 0000000000..21420e3bfe --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/SelectedAccount.cpp @@ -0,0 +1,38 @@ +#include "SelectedAccount.h" + +using namespace Status::Modules::Startup::Login; + +SelectedAccount::SelectedAccount(QObject* parent) + : QObject(parent) +{ +} + +void SelectedAccount::setSelectedAccountData(const Item& item) +{ + m_item = item; +} + +QString SelectedAccount::getName() +{ + return m_item.getName(); +} + +QString SelectedAccount::getIdenticon() +{ + return m_item.getIdenticon(); +} + +QString SelectedAccount::getKeyUid() +{ + return m_item.getKeyUid(); +} + +QString SelectedAccount::getThumbnailImage() +{ + return m_item.getThumbnailImage(); +} + +QString SelectedAccount::getLargeImage() +{ + return m_item.getLargeImage(); +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/SelectedAccount.h b/src-cpp-structure/projects/App/Modules/Startup/Login/SelectedAccount.h new file mode 100644 index 0000000000..d46445313c --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/SelectedAccount.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Item.h" + +#include + +namespace Status::Modules::Startup::Login +{ + class SelectedAccount : public QObject + { + Q_OBJECT + + Q_PROPERTY(QString username READ getName CONSTANT) + Q_PROPERTY(QString identicon READ getIdenticon CONSTANT) + Q_PROPERTY(QString keyUid READ getKeyUid CONSTANT) + Q_PROPERTY(QString thumbnailImage READ getThumbnailImage CONSTANT) + Q_PROPERTY(QString largeImage READ getLargeImage CONSTANT) + + public: + explicit SelectedAccount(QObject* parent = nullptr); + + void setSelectedAccountData(const Item& item); + + QString getName(); + QString getIdenticon(); + QString getKeyUid(); + QString getThumbnailImage(); + QString getLargeImage(); + + private: + Item m_item; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/View.cpp b/src-cpp-structure/projects/App/Modules/Startup/Login/View.cpp new file mode 100644 index 0000000000..a0ddda722b --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/View.cpp @@ -0,0 +1,77 @@ +#include "View.h" + +#include "../../../Core/Engine.h" + +using namespace Status::Modules::Startup::Login; + +View::View() : QObject(nullptr) + , m_model(new Model(this)) + , m_selectedAccount(new SelectedAccount(this)) +{ +} + +void View::setDelegate(std::shared_ptr delegate) +{ + m_delegate = std::move(delegate); +} + +QObject* View::getQObject() +{ + Engine::instance()->setObjectOwnership(this, QQmlEngine::CppOwnership); + return this; +} + +void View::load() +{ + m_delegate->viewDidLoad(); +} + +Model* View::getModel() +{ + Engine::instance()->setObjectOwnership(m_model, QQmlEngine::CppOwnership); + return m_model; +} + +SelectedAccount* View::getSelectedAccount() +{ + Engine::instance()->setObjectOwnership(m_selectedAccount, QQmlEngine::CppOwnership); + return m_selectedAccount; +} + +void View::setModelItems(QVector accounts) +{ + m_model->setItems(std::move(accounts)); + emit modelChanged(); +} + +void View::setSelectedAccount(const Item& item) +{ + m_selectedAccount->setSelectedAccountData(item); + emit selectedAccountChanged(); +} + +void View::emitAccountLoginError(const QString& error) +{ + emit accountLoginError(error); +} + +void View::emitObtainingPasswordError(const QString& errorDescription) +{ + emit obtainingPasswordError(errorDescription); +} + +void View::emitObtainingPasswordSuccess(const QString& password) +{ + emit obtainingPasswordSuccess(password); +} + +void View::setSelectedAccountByIndex(const int index) +{ + Item item = m_model->getItemAtIndex(index); + m_delegate->setSelectedAccount(item); +} + +void View::login(const QString& password) +{ + m_delegate->login(password); +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/View.h b/src-cpp-structure/projects/App/Modules/Startup/Login/View.h new file mode 100644 index 0000000000..a7526dd121 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/View.h @@ -0,0 +1,47 @@ +#pragma once + +#include "ViewInterface.h" + +namespace Status::Modules::Startup::Login +{ + class View final : public QObject + , public ViewInterface + { + Q_OBJECT + + Q_PROPERTY(SelectedAccount* selectedAccount READ getSelectedAccount NOTIFY selectedAccountChanged) + Q_PROPERTY(Model* accountsModel READ getModel NOTIFY modelChanged) + + public: + explicit View(); + void setDelegate(std::shared_ptr delegate); + + // View Interface + QObject* getQObject() override; + void load() override; + Model* getModel() override; + void setModelItems(QVector accounts) override; + void setSelectedAccount(const Item& item) override; + void emitAccountLoginError(const QString& error) override; + void emitObtainingPasswordError(const QString& errorDescription) override; + void emitObtainingPasswordSuccess(const QString& password) override; + + Q_INVOKABLE void setSelectedAccountByIndex(const int index); + Q_INVOKABLE void login(const QString& password); + + signals: + void selectedAccountChanged(); + void modelChanged(); + void accountLoginError(const QString& error); + void obtainingPasswordError(const QString& errorDescription); + void obtainingPasswordSuccess(const QString& password); + + private: + SelectedAccount* getSelectedAccount(); + + private: + std::shared_ptr m_delegate; + Model* m_model {nullptr}; + SelectedAccount* m_selectedAccount {nullptr}; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Login/ViewInterface.h b/src-cpp-structure/projects/App/Modules/Startup/Login/ViewInterface.h new file mode 100644 index 0000000000..56fa16711e --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Login/ViewInterface.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Model.h" +#include "SelectedAccount.h" + +#include + +#include + +namespace Status::Modules::Startup::Login +{ + class ViewInterface + { + public: + virtual ~ViewInterface() = default; + + virtual QObject* getQObject() = 0; + virtual void load() = 0; + virtual Model* getModel() = 0; + virtual void setModelItems(QVector accounts) = 0; + virtual void setSelectedAccount(const Item& item) = 0; + virtual void emitAccountLoginError(const QString& error) = 0; + virtual void emitObtainingPasswordError(const QString& errorDescription) = 0; + virtual void emitObtainingPasswordSuccess(const QString& password) = 0; + }; + + class ViewDelegateInterface + { + public: + virtual void viewDidLoad() = 0; + virtual void setSelectedAccount(const Item& item) = 0; + virtual void login(const QString& password) = 0; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Module.cpp b/src-cpp-structure/projects/App/Modules/Startup/Module.cpp new file mode 100644 index 0000000000..01e5e43a84 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Module.cpp @@ -0,0 +1,93 @@ +#include "Module.h" + +#include "Controller.h" +#include "View.h" + +#include "../../Core/Engine.h" + +using namespace Status::Modules::Startup; + +Module::Module(std::shared_ptr delegate, + std::shared_ptr controller, + std::shared_ptr view, + Onboarding::ModuleBuilder onboardingModuleBuilder, + Login::ModuleBuilder loginModuleBuilder) + : m_delegate(std::move(delegate)) + , m_controller(std::move(controller)) + , m_view(std::move(view)) + , m_onboardingModuleBuilder(std::move(onboardingModuleBuilder)) + , m_loginModuleBuilder(std::move(loginModuleBuilder)) +{ +} + +void Module::load() +{ + Engine::instance()->rootContext()->setContextProperty("startupModule", m_view->getQObject()); + m_controller->init(); + m_view->load(); +} + +void Module::checkIfModuleDidLoad() +{ + if(!m_onboardingModule->isLoaded()) + { + return; + } + + if(!m_loginModule->isLoaded()) + { + return; + } + + m_delegate->startupDidLoad(); +} + +void Module::viewDidLoad() +{ + AppState initialAppState(AppState::OnboardingState); + if(!m_controller->shouldStartWithOnboardingScreen()) + { + initialAppState = AppState::LoginState; + } + + m_view->setAppState(initialAppState); + + + m_onboardingModule = m_onboardingModuleBuilder(shared_from_this()); + m_loginModule = m_loginModuleBuilder(shared_from_this()); + + m_onboardingModule->load(); + m_loginModule->load(); + + checkIfModuleDidLoad(); +} + +void Module::onboardingDidLoad() +{ + checkIfModuleDidLoad(); +} + +void Module::loginDidLoad() +{ + checkIfModuleDidLoad(); +} + +void Module::userLoggedIn() +{ + m_delegate->userLoggedIn(); +} + +void Module::moveToAppState() +{ + m_view->setAppState(AppState::MainAppState); +} + +void Module::emitLogOutSignal() +{ + m_view->emitLogOut(); +} + +void Module::emitStartUpUIRaisedSignal() +{ + m_view->emitStartUpUIRaised(); +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Module.h b/src-cpp-structure/projects/App/Modules/Startup/Module.h new file mode 100644 index 0000000000..0ca835a974 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Module.h @@ -0,0 +1,63 @@ +#pragma once + +#include "ModuleInterface.h" +#include "ControllerInterface.h" +#include "ViewInterface.h" + +#include "Onboarding/ModuleBuilder.h" +#include "Onboarding/ModuleInterface.h" +#include "Login/ModuleBuilder.h" +#include "Login/ModuleInterface.h" + +#include + +namespace Status::Modules::Startup +{ + + class Module final : public ModuleAccessInterface + , public ControllerDelegateInterface + , public ViewDelegateInterface + , public std::enable_shared_from_this + , public Onboarding::ModuleDelegateInterface + , public Login::ModuleDelegateInterface + { + public: + Module(std::shared_ptr delegate, + std::shared_ptr controller, + std::shared_ptr view, + Onboarding::ModuleBuilder onboardingModuleBuilder, + Login::ModuleBuilder loginModuleBuilder); + + // Module Access + void load() override; + void moveToAppState() override; + void emitStartUpUIRaisedSignal() override; + + // Controller Delegate + void userLoggedIn() override; + void emitLogOutSignal() override; + + // View Delegate + void viewDidLoad() override; + + // Onboarding Module Delegate + void onboardingDidLoad() override; + + // Login Module Delegate + void loginDidLoad() override; + + private: + void checkIfModuleDidLoad(); + + private: + std::shared_ptr m_delegate; + std::shared_ptr m_controller; + std::shared_ptr m_view; + + Onboarding::ModuleBuilder m_onboardingModuleBuilder; + std::shared_ptr m_onboardingModule; + + Login::ModuleBuilder m_loginModuleBuilder; + std::shared_ptr m_loginModule; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/ModuleBuilder.cpp b/src-cpp-structure/projects/App/Modules/Startup/ModuleBuilder.cpp new file mode 100644 index 0000000000..53722bcbee --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/ModuleBuilder.cpp @@ -0,0 +1,30 @@ +#include "ModuleBuilder.h" + +#include "Module.h" +#include "Controller.h" +#include "View.h" + +using namespace Status::Modules::Startup; + +ModuleBuilder::ModuleBuilder(std::shared_ptr accountsService, + Onboarding::ModuleBuilder onboardingModuleBuilder, + Login::ModuleBuilder loginModuleBuilder) + : m_accountsService(std::move(accountsService)) + , m_onboardingModuleBuilder(std::move(onboardingModuleBuilder)) + , m_loginModuleBuilder(std::move(loginModuleBuilder)) +{ +} + +std::shared_ptr ModuleBuilder::operator()(std::shared_ptr delegate) { + + auto controller = std::make_shared(m_accountsService); + auto view = std::make_shared(); + + auto module = std::make_shared(delegate, controller, view, + m_onboardingModuleBuilder, m_loginModuleBuilder); + + controller->setDelegate(module); + view->setDelegate(module); + + return module; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/ModuleBuilder.h b/src-cpp-structure/projects/App/Modules/Startup/ModuleBuilder.h new file mode 100644 index 0000000000..42214a889e --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/ModuleBuilder.h @@ -0,0 +1,27 @@ +#pragma once + +#include "ModuleInterface.h" +#include "Onboarding/ModuleBuilder.h" +#include "Login/ModuleBuilder.h" + +#include + +#include + +namespace Status::Modules::Startup +{ + class ModuleBuilder final + { + public: + ModuleBuilder(std::shared_ptr accountsService, + Onboarding::ModuleBuilder onboardingModuleBuilder, + Login::ModuleBuilder loginModuleBuilder); + + [[nodiscard]] std::shared_ptr operator()(std::shared_ptr delegate); + + private: + std::shared_ptr m_accountsService; + Onboarding::ModuleBuilder m_onboardingModuleBuilder; + Login::ModuleBuilder m_loginModuleBuilder; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/ModuleInterface.h b/src-cpp-structure/projects/App/Modules/Startup/ModuleInterface.h new file mode 100644 index 0000000000..2293548aa8 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/ModuleInterface.h @@ -0,0 +1,21 @@ +#pragma once + +namespace Status::Modules::Startup +{ + class ModuleAccessInterface + { + public: + virtual ~ModuleAccessInterface() = default; + + virtual void load() = 0; + virtual void moveToAppState() = 0; + virtual void emitStartUpUIRaisedSignal() = 0; + }; + + class ModuleDelegateInterface + { + public: + virtual void startupDidLoad() = 0; + virtual void userLoggedIn() = 0; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Controller.cpp b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Controller.cpp new file mode 100644 index 0000000000..af89f8436f --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Controller.cpp @@ -0,0 +1,72 @@ +#include "Controller.h" + +#include "../../../Core/GlobalEvents.h" + +using namespace Status::Modules::Startup::Onboarding; + +Controller::Controller(std::shared_ptr accountsService) + : QObject(nullptr) + , m_delegate(nullptr) + , m_accountsService(std::move(accountsService)) +{ +} + +void Controller::setDelegate(std::shared_ptr delegate) +{ + m_delegate = std::move(delegate); +} + +void Controller::init() +{ + QObject::connect(&GlobalEvents::instance(), &GlobalEvents::nodeLogin, this, &Controller::onLogin); +} + +void Controller::onLogin(const QString& error) +{ + if(!error.isEmpty()) + { + m_delegate->setupAccountError(); + } +} + +const QVector& Controller::getGeneratedAccounts() const +{ + return m_accountsService->generatedAccounts(); +} + +const Status::Accounts::GeneratedAccountDto& Controller::getImportedAccount() const +{ + return m_accountsService->getImportedAccount(); +} + +void Controller::setSelectedAccountByIndex(const int index) +{ + auto accounts = getGeneratedAccounts(); + m_selectedAccountId = accounts[index].id; +} + +void Controller::storeSelectedAccountAndLogin(const QString& password) +{ + if(!m_accountsService->setupAccount(m_selectedAccountId, password)) + { + m_delegate->setupAccountError(); + } +} + +QString Controller::validateMnemonic(const QString& mnemonic) +{ + return m_accountsService->validateMnemonic(mnemonic); +} + +void Controller::importMnemonic(const QString& mnemonic) +{ + if(m_accountsService->importMnemonic(mnemonic)) + { + m_selectedAccountId = getImportedAccount().id; + m_delegate->importAccountSuccess(); + } + else + { + m_delegate->importAccountError(); + } +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Controller.h b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Controller.h new file mode 100644 index 0000000000..1e7c8b1d4a --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Controller.h @@ -0,0 +1,33 @@ +#pragma once + +#include "ControllerInterface.h" + +namespace Status::Modules::Startup::Onboarding +{ + class Controller : public QObject, + public ControllerInterface + { + Q_OBJECT + + public: + explicit Controller(std::shared_ptr accountsService); + void setDelegate(std::shared_ptr delegate); + + // Controller Interface + void init() override; + const QVector& getGeneratedAccounts() const override; + const Accounts::GeneratedAccountDto& getImportedAccount() const override; + void setSelectedAccountByIndex(const int index) override; + void storeSelectedAccountAndLogin(const QString& password) override; + QString validateMnemonic(const QString& mnemonic) override; + void importMnemonic(const QString& mnemonic) override; + + private slots: + void onLogin(const QString& error); + + private: + std::shared_ptr m_accountsService; + std::shared_ptr m_delegate; + QString m_selectedAccountId; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ControllerInterface.h b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ControllerInterface.h new file mode 100644 index 0000000000..201b33c2d3 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ControllerInterface.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +namespace Status::Modules::Startup::Onboarding +{ + class ControllerInterface + { + public: + virtual ~ControllerInterface() = default; + + virtual void init() = 0; + virtual const QVector& getGeneratedAccounts() const = 0; + virtual void setSelectedAccountByIndex(const int index) = 0; + virtual void storeSelectedAccountAndLogin(const QString& password) = 0; + virtual const Accounts::GeneratedAccountDto& getImportedAccount() const = 0; + virtual QString validateMnemonic(const QString& mnemonic) = 0; + virtual void importMnemonic(const QString& mnemonic) = 0; + }; + + class ControllerDelegateInterface + { + public: + virtual void importAccountError() = 0; + virtual void setupAccountError() = 0; + virtual void importAccountSuccess() = 0; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Item.cpp b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Item.cpp new file mode 100644 index 0000000000..7ba2ed7357 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Item.cpp @@ -0,0 +1,38 @@ +#include "Item.h" + +using namespace Status::Modules::Startup::Onboarding; + +Item::Item(const QString& id, const QString& alias, const QString& identicon, const QString& address, + const QString& keyUid) + : m_id(id) + , m_alias(alias) + , m_identicon(identicon) + , m_address(address) + , m_keyUid(keyUid) +{ +} + +QString Item::getId() const +{ + return m_id; +} + +QString Item::getAlias() const +{ + return m_alias; +} + +QString Item::getIdenticon() const +{ + return m_identicon; +} + +QString Item::getAddress() const +{ + return m_address; +} + +QString Item::getKeyUid() const +{ + return m_keyUid; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Item.h b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Item.h new file mode 100644 index 0000000000..75b8b42ea8 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Item.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace Status::Modules::Startup::Onboarding +{ + class Item + { + public: + Item(const QString& id, const QString& alias, const QString& identicon, const QString& address, + const QString& keyUid); + + QString getId() const; + QString getAlias() const; + QString getIdenticon() const; + QString getAddress() const; + QString getKeyUid() const; + + private: + QString m_id; + QString m_alias; + QString m_identicon; + QString m_address; + QString m_keyUid; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Model.cpp b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Model.cpp new file mode 100644 index 0000000000..31a722df7f --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Model.cpp @@ -0,0 +1,63 @@ +#include "Model.h" + +using namespace Status::Modules::Startup::Onboarding; + +Model::Model(QObject* parent) + : QAbstractListModel(parent) +{ +} + +QHash Model::roleNames() const +{ + QHash roles; + roles[Id] = "accountId"; + roles[Alias] = "username"; + roles[Identicon] = "identicon"; + roles[Address] = "address"; + roles[KeyUid] = "keyUid"; + return roles; +} + +int Model::rowCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent) + return m_items.size(); +} + +QVariant Model::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + { + return QVariant(); + } + + if(index.row() < 0 || index.row() > m_items.size()) + { + return QVariant(); + } + + Item item = m_items[index.row()]; + + switch(role) + { + case Id: + return item.getId(); + case Alias: + return item.getAlias(); + case Identicon: + return item.getIdenticon(); + case Address: + return item.getAddress(); + case KeyUid: + return item.getKeyUid(); + } + + return QVariant(); +} + +void Model::setItems(QVector items) +{ + beginResetModel(); + m_items = std::move(items); + endResetModel(); +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Model.h b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Model.h new file mode 100644 index 0000000000..3e8ee92cdd --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Model.h @@ -0,0 +1,30 @@ +#pragma once + +#include "Item.h" + +namespace Status::Modules::Startup::Onboarding +{ + class Model : public QAbstractListModel + { + Q_OBJECT + + public: + enum ModelRole + { + Id = Qt::UserRole + 1, + Alias, + Identicon, + Address, + KeyUid + }; + + explicit Model(QObject* parent = nullptr); + QHash roleNames() const; + virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex& index, int role) const; + void setItems(QVector items); + + private: + QVector m_items; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Module.cpp b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Module.cpp new file mode 100644 index 0000000000..e932a1cc62 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Module.cpp @@ -0,0 +1,83 @@ +#include "Module.h" + +#include "Controller.h" +#include "View.h" + +#include "../../../Core/Engine.h" + +using namespace Status::Modules::Startup::Onboarding; + +Module::Module(std::shared_ptr delegate, + std::shared_ptr controller, + std::shared_ptr view) + : m_delegate(std::move(delegate)) + , m_controller(std::move(controller)) + , m_view(std::move(view)) +{ +} + +void Module::load() +{ + Engine::instance()->rootContext()->setContextProperty("onboardingModule", m_view->getQObject()); + m_controller->init(); + m_view->load(); + + const QVector& gAcc = m_controller->getGeneratedAccounts(); + QVector accounts; + foreach(const Accounts::GeneratedAccountDto& acc, gAcc) + { + accounts << Item(acc.id, acc.alias, acc.identicon, acc.address, acc.keyUid); + } + + m_view->setAccountList(accounts); +} + +bool Module::isLoaded() +{ + return m_moduleLoaded; +} + +void Module::viewDidLoad() +{ + m_moduleLoaded = true; + m_delegate->onboardingDidLoad(); +} + +void Module::setSelectedAccountByIndex(const int index) +{ + m_controller->setSelectedAccountByIndex(index); +} + +void Module::storeSelectedAccountAndLogin(const QString& password) +{ + m_controller->storeSelectedAccountAndLogin(password); +} +void Module::setupAccountError() +{ + m_view->setupAccountError(); +} + +const Status::Accounts::GeneratedAccountDto& Module::getImportedAccount() const +{ + return m_controller->getImportedAccount(); +} + +QString Module::validateMnemonic(const QString& mnemonic) +{ + return m_controller->validateMnemonic(mnemonic); +} + +void Module::importMnemonic(const QString& mnemonic) +{ + m_controller->importMnemonic(mnemonic); +} + +void Module::importAccountError() +{ + m_view->importAccountError(); +} + +void Module::importAccountSuccess() +{ + m_view->importAccountSuccess(); +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Module.h b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Module.h new file mode 100644 index 0000000000..f00441be51 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/Module.h @@ -0,0 +1,46 @@ +#pragma once + +#include "ModuleInterface.h" +#include "ControllerInterface.h" +#include "ViewInterface.h" + +namespace Status::Modules::Startup::Onboarding +{ + + class Module final : public ModuleAccessInterface + , public ControllerDelegateInterface + , public ViewDelegateInterface + , public std::enable_shared_from_this + { + public: + Module(std::shared_ptr delegate, + std::shared_ptr controller, + std::shared_ptr view); + + // Module Access + void load() override; + bool isLoaded() override; + + // Controller Delegate + void importAccountError() override; + void setupAccountError() override; + void importAccountSuccess() override; + + // View Delegate + void viewDidLoad() override; + void setSelectedAccountByIndex(const int index) override; + void storeSelectedAccountAndLogin(const QString& password) override; + const Accounts::GeneratedAccountDto& getImportedAccount() const override; + QString validateMnemonic(const QString& mnemonic) override; + void importMnemonic(const QString& mnemonic) override; + + private: + void checkIfModuleDidLoad(); + + private: + std::shared_ptr m_delegate; + std::shared_ptr m_controller; + std::shared_ptr m_view; + bool m_moduleLoaded {false}; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleBuilder.cpp b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleBuilder.cpp new file mode 100644 index 0000000000..a0dce8143e --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleBuilder.cpp @@ -0,0 +1,25 @@ +#include "ModuleBuilder.h" + +#include "Module.h" +#include "Controller.h" +#include "View.h" + +using namespace Status::Modules::Startup::Onboarding; + +ModuleBuilder::ModuleBuilder(std::shared_ptr accountsService) + : m_accountsService(std::move(accountsService)) +{ +} + +std::shared_ptr ModuleBuilder::operator()(std::shared_ptr delegate) { + + auto controller = std::make_shared(m_accountsService); + auto view = std::make_shared(); + + auto module = std::make_shared(delegate, controller, view); + + controller->setDelegate(module); + view->setDelegate(module); + + return module; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleBuilder.h b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleBuilder.h new file mode 100644 index 0000000000..920f6783cb --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleBuilder.h @@ -0,0 +1,21 @@ +#pragma once + +#include "ModuleInterface.h" + +#include + +#include + +namespace Status::Modules::Startup::Onboarding +{ + class ModuleBuilder final + { + public: + ModuleBuilder(std::shared_ptr accountsService); + + [[nodiscard]] std::shared_ptr operator()(std::shared_ptr delegate); + + private: + std::shared_ptr m_accountsService; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleInterface.h b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleInterface.h new file mode 100644 index 0000000000..06194d3697 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ModuleInterface.h @@ -0,0 +1,19 @@ +#pragma once + +namespace Status::Modules::Startup::Onboarding +{ + class ModuleAccessInterface + { + public: + virtual ~ModuleAccessInterface() = default; + + virtual void load() = 0; + virtual bool isLoaded() = 0; + }; + + class ModuleDelegateInterface + { + public: + virtual void onboardingDidLoad() = 0; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/View.cpp b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/View.cpp new file mode 100644 index 0000000000..66fe51b37c --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/View.cpp @@ -0,0 +1,90 @@ +#include "View.h" + +#include "../../../Core/Engine.h" + +using namespace Status::Modules::Startup::Onboarding; + +View::View() : QObject(nullptr) + , m_model(new Model(this)) +{ +} + +void View::setDelegate(std::shared_ptr delegate) +{ + m_delegate = std::move(delegate); +} + +QObject* View::getQObject() +{ + Engine::instance()->setObjectOwnership(this, QQmlEngine::CppOwnership); + return this; +} + +void View::load() +{ + m_delegate->viewDidLoad(); +} + +Model* View::getModel() +{ + Engine::instance()->setObjectOwnership(m_model, QQmlEngine::CppOwnership); + return m_model; +} + +void View::setAccountList(QVector accounts) +{ + m_model->setItems(std::move(accounts)); + emit modelChanged(); +} + +QString View::getImportedAccountIdenticon() const +{ + return m_delegate->getImportedAccount().identicon; +} + +QString View::getImportedAccountAlias() const +{ + return m_delegate->getImportedAccount().alias; +} + +QString View::getImportedAccountAddress() const +{ + return m_delegate->getImportedAccount().address; +} + +void View::setSelectedAccountByIndex(const int index) +{ + m_delegate->setSelectedAccountByIndex(index); +} + +void View::storeSelectedAccountAndLogin(const QString& password) +{ + m_delegate->storeSelectedAccountAndLogin(password); +} + +void View::setupAccountError() +{ + emit accountSetupError(); +} + +QString View::validateMnemonic(const QString& mnemonic) +{ + return m_delegate->validateMnemonic(mnemonic); +} + +void View::importMnemonic(const QString& mnemonic) +{ + m_delegate->importMnemonic(mnemonic); +} + +void View::importAccountError() +{ + // In QML we can connect to this signal and notify a user + // before refactoring we didn't have this signal + emit accountImportError(); +} + +void View::importAccountSuccess() +{ + emit importedAccountChanged(); +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/View.h b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/View.h new file mode 100644 index 0000000000..07319aae1a --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/View.h @@ -0,0 +1,49 @@ +#pragma once + +#include "ViewInterface.h" + +namespace Status::Modules::Startup::Onboarding +{ + class View final : public QObject + , public ViewInterface + { + Q_OBJECT + + Q_PROPERTY(Model* accountsModel READ getModel NOTIFY modelChanged) + Q_PROPERTY(QString importedAccountIdenticon READ getImportedAccountIdenticon NOTIFY importedAccountChanged) + Q_PROPERTY(QString importedAccountAlias READ getImportedAccountAlias NOTIFY importedAccountChanged) + Q_PROPERTY(QString importedAccountAddress READ getImportedAccountAddress NOTIFY importedAccountChanged) + + public: + explicit View(); + void setDelegate(std::shared_ptr delegate); + + // View Interface + QObject* getQObject() override; + void load() override; + Model* getModel() override; + void setAccountList(QVector accounts) override; + void importAccountError() override; + void setupAccountError() override; + void importAccountSuccess() override; + + QString getImportedAccountIdenticon() const; + QString getImportedAccountAlias() const; + QString getImportedAccountAddress() const; + + Q_INVOKABLE void setSelectedAccountByIndex(const int index); + Q_INVOKABLE void storeSelectedAccountAndLogin(const QString& password); + Q_INVOKABLE QString validateMnemonic(const QString& mnemonic); + Q_INVOKABLE void importMnemonic(const QString& mnemonic); + + signals: + void modelChanged(); + void importedAccountChanged(); + void accountSetupError(); + void accountImportError(); + + private: + std::shared_ptr m_delegate; + Model* m_model {nullptr}; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ViewInterface.h b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ViewInterface.h new file mode 100644 index 0000000000..7e7b4d6e40 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/Onboarding/ViewInterface.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Model.h" + +#include + +#include + +namespace Status::Modules::Startup::Onboarding +{ + class ViewInterface + { + public: + virtual ~ViewInterface() = default; + + virtual QObject* getQObject() = 0; + virtual void load() = 0; + virtual Model* getModel() = 0; + virtual void setAccountList(QVector accounts) = 0; + virtual void importAccountError() = 0; + virtual void setupAccountError() = 0; + virtual void importAccountSuccess() = 0; + }; + + class ViewDelegateInterface + { + public: + virtual void viewDidLoad() = 0; + virtual void setSelectedAccountByIndex(const int index) = 0; + virtual void storeSelectedAccountAndLogin(const QString& password) = 0; + virtual const Accounts::GeneratedAccountDto& getImportedAccount() const = 0; + virtual QString validateMnemonic(const QString& mnemonic) = 0; + virtual void importMnemonic(const QString& mnemonic) = 0; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/View.cpp b/src-cpp-structure/projects/App/Modules/Startup/View.cpp new file mode 100644 index 0000000000..cca04b4b5f --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/View.cpp @@ -0,0 +1,52 @@ +#include "View.h" + +#include "../../Core/Engine.h" + +using namespace Status::Modules::Startup; + +View::View() : QObject(nullptr) + , m_appState(AppState::OnboardingState) +{ +} + +void View::setDelegate(std::shared_ptr delegate) +{ + m_delegate = std::move(delegate); +} + +QObject* View::getQObject() +{ + Engine::instance()->setObjectOwnership(this, QQmlEngine::CppOwnership); + return this; +} + +void View::load() +{ + m_delegate->viewDidLoad(); +} + +int View::getAppState() +{ + return static_cast(m_appState); +} + +void View::setAppState(AppState state) +{ + if(m_appState == state) + { + return; + } + + m_appState = state; + emit appStateChanged(m_appState); +} + +void View::emitLogOut() +{ + emit logOut(); +} + +void View::emitStartUpUIRaised() +{ + emit startUpUIRaised(); +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/View.h b/src-cpp-structure/projects/App/Modules/Startup/View.h new file mode 100644 index 0000000000..088e0126a1 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/View.h @@ -0,0 +1,37 @@ +#pragma once + +#include "ViewInterface.h" + +namespace Status::Modules::Startup +{ + class View final : public QObject + , public ViewInterface + { + Q_OBJECT + + Q_PROPERTY(int appState READ getAppState NOTIFY appStateChanged) + + public: + explicit View(); + void setDelegate(std::shared_ptr delegate); + + // View Interface + QObject* getQObject() override; + void emitLogOut() override; + void emitStartUpUIRaised() override; + void setAppState(AppState state) override; + void load() override; + + public slots: + int getAppState(); + + signals: + void appStateChanged(int state); + void logOut(); + void startUpUIRaised(); + + private: + std::shared_ptr m_delegate; + AppState m_appState; + }; +} diff --git a/src-cpp-structure/projects/App/Modules/Startup/ViewInterface.h b/src-cpp-structure/projects/App/Modules/Startup/ViewInterface.h new file mode 100644 index 0000000000..ec1d898960 --- /dev/null +++ b/src-cpp-structure/projects/App/Modules/Startup/ViewInterface.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace Status::Modules::Startup +{ + enum AppState + { + OnboardingState = 0, + LoginState = 1, + MainAppState = 2 + // TODO: is Pending + }; + + class ViewInterface + { + public: + virtual ~ViewInterface() = default; + + virtual QObject* getQObject() = 0; + virtual void emitLogOut() = 0; + virtual void emitStartUpUIRaised() = 0; + virtual void setAppState(AppState state) = 0; + virtual void load() = 0; + }; + + class ViewDelegateInterface + { + public: + virtual void viewDidLoad() = 0; + }; +} diff --git a/src-cpp-structure/projects/Backend/CMakeLists.txt b/src-cpp-structure/projects/Backend/CMakeLists.txt new file mode 100644 index 0000000000..6de5ed1fd2 --- /dev/null +++ b/src-cpp-structure/projects/Backend/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) + +project(Status.Backend DESCRIPTION "Status project used to integrate with status-go") + +set(CMAKE_DEBUG_POSTFIX d) +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +add_library(${PROJECT_NAME} SHARED) + +file( + GLOB_RECURSE SOURCES + "*.h" + "*.cpp" +) + +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) + +target_include_directories(${PROJECT_NAME} PUBLIC include) + +target_link_libraries( + ${PROJECT_NAME} + Qt5::Core + ${STATUS_GO_LIB} +) diff --git a/src-cpp-structure/projects/Backend/include/StatusBackend/Accounts.h b/src-cpp-structure/projects/Backend/include/StatusBackend/Accounts.h new file mode 100644 index 0000000000..ab2e2567d4 --- /dev/null +++ b/src-cpp-structure/projects/Backend/include/StatusBackend/Accounts.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Types.h" + +#include + +namespace Backend::Accounts +{ + RpcResponse generateAddresses(const QVector& paths); + + RpcResponse generateIdenticon(const QString& publicKey); + + RpcResponse generateAlias(const QString& publicKey); + + RpcResponse storeDerivedAccounts(const QString& accountId, const QString& hashedPassword, + const QVector& paths); + + RpcResponse saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account, + const QJsonArray& subaccounts, const QJsonObject& settings, + const QJsonObject& nodeConfig); + + RpcResponse openAccounts(const QString& path); + + RpcResponse login(const QString& name, const QString& keyUid, const QString& hashedPassword, + const QString& identicon, const QString& thumbnail, const QString& large); +} diff --git a/src-cpp-structure/projects/Backend/include/StatusBackend/Types.h b/src-cpp-structure/projects/Backend/include/StatusBackend/Types.h new file mode 100644 index 0000000000..a4ef6f8b92 --- /dev/null +++ b/src-cpp-structure/projects/Backend/include/StatusBackend/Types.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace Backend +{ + // Used in calls where we don't have version and id returned from `status-go` + static QString DefaultVersion = "2.0"; + static constexpr int DefaultId = 0; + + struct RpcError + { + int code; + QString message; + }; + + template + struct RpcResponse + { + T result; + QString jsonRpcVersion; + int id; + RpcError error; + + RpcResponse(T result, QString version = DefaultVersion, int id = DefaultId, + RpcError error = RpcError{-1, QString()}) + : result(result) + , jsonRpcVersion(version) + , id(id) + , error(error) + { } + + bool containsError() const { + return !error.message.isEmpty(); + } + }; +} diff --git a/src-cpp-structure/projects/Backend/include/StatusBackend/Utils.h b/src-cpp-structure/projects/Backend/include/StatusBackend/Utils.h new file mode 100644 index 0000000000..7ce2bb7180 --- /dev/null +++ b/src-cpp-structure/projects/Backend/include/StatusBackend/Utils.h @@ -0,0 +1,125 @@ +#pragma once + +#include "Types.h" +#include "libstatus.h" + +#include + +namespace Backend +{ + namespace Param { + const QString Id = "id"; + const QString JsonRpc = "jsonrpc"; + const QString Result = "result"; + const QString Error = "error"; + const QString ErrorMessage = "message"; + const QString ErrorCode = "code"; + } + + class Utils + { + public: + template static QByteArray jsonToByteArray(const T& json){ + if constexpr (std::is_same_v || + std::is_same_v) + { + return QJsonDocument(json).toJson(QJsonDocument::Compact); + } + + return QByteArray(); + } + + static QJsonArray toJsonArray(const QVector& value){ + QJsonArray array; + for(auto& v : value) + array << v; + return array; + } + + template static bool checkReceivedResponse(const QString& response, T& json) + { + QJsonParseError error; + auto jsonDocument = QJsonDocument::fromJson(response.toUtf8(), &error); + + if (error.error != QJsonParseError::NoError) + return false; + + if constexpr (std::is_same_v) + { + json = jsonDocument.object(); + return true; + } + else if constexpr (std::is_same_v) + { + json = jsonDocument.array(); + return true; + } + + return false; + } + + template static RpcResponse buildJsonRpcResponse(const T& json) { + auto response = RpcResponse(T()); + + if constexpr (std::is_same_v) + { + if (!json[Param::Id].isNull() && !json[Param::Id].isUndefined()) + response.id = json[Param::Id].toInt(); + + if (!json[Param::JsonRpc].isNull() && !json[Param::JsonRpc].isUndefined()) + response.jsonRpcVersion = json[Param::JsonRpc].toString(); + + if (!json[Param::Error].isNull() && !json[Param::Error].isUndefined()) + { + auto errObj = json[Param::Id].toObject(); + if (!errObj[Param::ErrorCode].isNull() && !errObj[Param::ErrorCode].isUndefined()) + response.error.code = errObj[Param::ErrorCode].toInt(); + if (!errObj[Param::ErrorMessage].isNull() && !errObj[Param::ErrorMessage].isUndefined()) + response.error.message = errObj[Param::ErrorMessage].toString(); + } + + if (!json[Param::Result].isNull() && !json[Param::Result].isUndefined()) + response.result = json[Param::Result].toObject(); + } + else if constexpr (std::is_same_v) + { + response.result = json; + } + + return response; + } + + template static RpcResponse callPrivateRpc(const QByteArray& payload) + { + try + { + auto result = CallPrivateRPC(const_cast(payload).data()); + T jsonResult; + if(!Utils::checkReceivedResponse(result, jsonResult)) + { + auto msg = QObject::tr("parsing response failed"); + throw std::domain_error(msg.toStdString()); + } + + return Utils::buildJsonRpcResponse(jsonResult); + } + catch (std::exception& e) + { + auto response = RpcResponse(T()); + response.error.message = QObject::tr("an error executing rpc call occurred, msg: %1").arg(e.what()); + return response; + } + catch (...) + { + auto response = RpcResponse(T()); + response.error.message = QObject::tr("an error executing rpc call"); + return response; + } + } + + static QString hashString(QString str) { + return "0x" + QString::fromUtf8(QCryptographicHash::hash(str.toUtf8(), + QCryptographicHash::Keccak_256).toHex()); + } + }; +} diff --git a/src-cpp-structure/projects/Backend/include/StatusBackend/WalletAccounts.h b/src-cpp-structure/projects/Backend/include/StatusBackend/WalletAccounts.h new file mode 100644 index 0000000000..28b7d81ef6 --- /dev/null +++ b/src-cpp-structure/projects/Backend/include/StatusBackend/WalletAccounts.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Types.h" + +#include + +namespace Backend::Wallet::Accounts +{ + RpcResponse getAccounts(); + + RpcResponse generateNewAccount(const QString& password, const QString& accountName, + const QString& color); + + RpcResponse addAccountsFromPrivateKey(const QString& privateKey, const QString& password, + const QString& accountName, const QString& color); + + RpcResponse addAccountsFromSeed(const QString& seedPhrase, const QString& password, + const QString& accountName, const QString& color); + + RpcResponse addWatchOnlyAccount(const QString& address, const QString& accountName, + const QString& color); + + RpcResponse deleteAccount(const QString& address); +} diff --git a/src-cpp-structure/projects/Backend/src/Accounts.cpp b/src-cpp-structure/projects/Backend/src/Accounts.cpp new file mode 100644 index 0000000000..8c134c7412 --- /dev/null +++ b/src-cpp-structure/projects/Backend/src/Accounts.cpp @@ -0,0 +1,221 @@ +#include "StatusBackend/Accounts.h" + +#include "StatusBackend/Utils.h" +#include "libstatus.h" + +const int NUMBER_OF_ADDRESSES_TO_GENERATE = 5; +const int MNEMONIC_PHRASE_LENGTH = 12; + +using namespace Backend; + +RpcResponse Accounts::generateAddresses(const QVector& paths) +{ + QJsonObject payload{ + {"n", NUMBER_OF_ADDRESSES_TO_GENERATE}, + {"mnemonicPhraseLength", MNEMONIC_PHRASE_LENGTH}, + {"bip32Passphrase", ""}, + {"paths", Utils::toJsonArray(paths)} + }; + + try + { + auto result = MultiAccountGenerateAndDeriveAddresses(Utils::jsonToByteArray(std::move(payload)).data()); + QJsonArray jsonResult; + if(!Utils::checkReceivedResponse(result, jsonResult)) + { + auto msg = QObject::tr("parsing response failed"); + throw std::domain_error(msg.toStdString()); + } + + return Utils::buildJsonRpcResponse(jsonResult); + } + catch (std::exception& e) + { + auto response = RpcResponse(QJsonArray()); + response.error.message = QObject::tr("an error generating address occurred, msg: %1").arg(e.what()); + return response; + } + catch (...) + { + auto response = RpcResponse(QJsonArray()); + response.error.message = QObject::tr("an error generating address occurred"); + return response; + } +} + +RpcResponse Accounts::generateIdenticon(const QString& publicKey) +{ + try + { + QString identicon; + if(!publicKey.isEmpty()) + { + identicon = Identicon(publicKey.toUtf8().data()); + } + return Utils::buildJsonRpcResponse(identicon); + } + catch (...) + { + auto response = RpcResponse(QString()); + response.error.message = QObject::tr("an error generating identicon occurred"); + return response; + } +} + +RpcResponse Accounts::generateAlias(const QString& publicKey) +{ + try + { + QString alias; + if(!publicKey.isEmpty()) + { + alias = GenerateAlias(publicKey.toUtf8().data()); + } + + return Utils::buildJsonRpcResponse(alias); + } + catch (...) + { + auto response = RpcResponse(QString()); + response.error.message = QObject::tr("an error generating alias occurred"); + return response; + } +} + +RpcResponse Accounts::storeDerivedAccounts(const QString& id, const QString& hashedPassword, + const QVector& paths) +{ + QJsonObject payload{ + {"accountID", id}, + {"paths", Utils::toJsonArray(paths)}, + {"password", hashedPassword} + }; + + try + { + auto result = MultiAccountStoreDerivedAccounts(Utils::jsonToByteArray(std::move(payload)).data()); + QJsonObject jsonResult; + if(!Utils::checkReceivedResponse(result, jsonResult)) + { + auto msg = QObject::tr("parsing response failed"); + throw std::domain_error(msg.toStdString()); + } + + return Utils::buildJsonRpcResponse(jsonResult); + } + catch (std::exception& e) + { + auto response = RpcResponse(QJsonObject()); + response.error.message = QObject::tr("an error storing derived accounts occurred, msg: %1").arg(e.what()); + return response; + } + catch (...) + { + auto response = RpcResponse(QJsonObject()); + response.error.message = QObject::tr("an error storing derived accounts occurred"); + return response; + } +} + +RpcResponse Accounts::saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account, + const QJsonArray& subaccounts, const QJsonObject& settings, + const QJsonObject& nodeConfig) +{ + try + { + auto result = SaveAccountAndLogin(Utils::jsonToByteArray(std::move(account)).data(), + hashedPassword.toUtf8().data(), + Utils::jsonToByteArray(std::move(settings)).data(), + Utils::jsonToByteArray(std::move(nodeConfig)).data(), + Utils::jsonToByteArray(std::move(subaccounts)).data()); + QJsonObject jsonResult; + if(!Utils::checkReceivedResponse(result, jsonResult)) + { + auto msg = QObject::tr("parsing response failed"); + throw std::domain_error(msg.toStdString()); + } + + return Utils::buildJsonRpcResponse(jsonResult); + } + catch (std::exception& e) + { + auto response = RpcResponse(QJsonObject()); + response.error.message = QObject::tr("an error saving account and login occurred, msg: %1").arg(e.what()); + return response; + } + catch (...) + { + auto response = RpcResponse(QJsonObject()); + response.error.message = QObject::tr("an error saving account and login occurred"); + return response; + } +} + +Backend::RpcResponse Backend::Accounts::openAccounts(const QString& path) +{ + try + { + auto result = OpenAccounts(path.toUtf8().data()); + QJsonArray jsonResult; + if(!Utils::checkReceivedResponse(result, jsonResult)) + { + auto msg = QObject::tr("parsing response failed"); + throw std::domain_error(msg.toStdString()); + } + + return Utils::buildJsonRpcResponse(jsonResult); + } + catch (std::exception& e) + { + auto response = RpcResponse(QJsonArray()); + response.error.message = QObject::tr("an error opening accounts occurred, msg: %1").arg(e.what()); + return response; + } + catch (...) + { + auto response = RpcResponse(QJsonArray()); + response.error.message = QObject::tr("an error opening accounts occurred"); + return response; + } +} + +RpcResponse Accounts::login(const QString& name, const QString& keyUid, const QString& hashedPassword, + const QString& identicon, const QString& thumbnail, const QString& large) +{ + QJsonObject payload{ + {"name", name}, + {"key-uid", keyUid}, + {"identityImage", QJsonValue()}, + {"identicon", identicon} + }; + + if(!thumbnail.isEmpty() && !large.isEmpty()) + { + payload["identityImage"] = QJsonObject{{"thumbnail", thumbnail}, {"large", large}}; + } + + try + { + auto result = Login(Utils::jsonToByteArray(std::move(payload)).data(), hashedPassword.toUtf8().data()); + QJsonObject jsonResult; + if(!Utils::checkReceivedResponse(result, jsonResult)) + { + auto msg = QObject::tr("parsing response failed"); + throw std::domain_error(msg.toStdString()); + } + + return Utils::buildJsonRpcResponse(jsonResult); + } + catch (std::exception& e) + { + auto response = RpcResponse(QJsonObject()); + response.error.message = QObject::tr("an error logining in account occurred, msg: %1").arg(e.what()); + return response; + } + catch (...) + { + auto response = RpcResponse(QJsonObject()); + response.error.message = QObject::tr("an error logining in account occurred"); + return response; + } +} diff --git a/src-cpp-structure/projects/Backend/src/WalletAccounts.cpp b/src-cpp-structure/projects/Backend/src/WalletAccounts.cpp new file mode 100644 index 0000000000..5e52849e28 --- /dev/null +++ b/src-cpp-structure/projects/Backend/src/WalletAccounts.cpp @@ -0,0 +1,106 @@ +#include "StatusBackend/WalletAccounts.h" + +#include "StatusBackend/Utils.h" + +using namespace Backend; + +RpcResponse Wallet::Accounts::getAccounts() +{ + QJsonObject payload{ + {"jsonrpc", "2.0"}, + {"method", "accounts_getAccounts"}, + {"params", QJsonValue()} + }; + + return Utils::callPrivateRpc(Utils::jsonToByteArray(std::move(payload))); +} + +RpcResponse Wallet::Accounts::generateNewAccount(const QString& password, const QString& accountName, const QString& color) +{ + QString hashedPassword(Utils::hashString(password)); + QJsonArray params = { + hashedPassword, + accountName, + color + }; + + QJsonObject payload{ + {"jsonrpc", "2.0"}, + {"method", "accounts_generateAccount"}, + {"params", params} + }; + + return Utils::callPrivateRpc(Utils::jsonToByteArray(std::move(payload))); +} + +RpcResponse Wallet::Accounts::addAccountsFromPrivateKey(const QString& privateKey, const QString& password, + const QString& accountName, const QString& color) +{ + QString hashedPassword(Utils::hashString(password)); + QJsonArray params = { + privateKey, + hashedPassword, + accountName, + color + }; + + QJsonObject payload{ + {"jsonrpc", "2.0"}, + {"method", "accounts_addAccountWithMnemonic"}, + {"params", params} + }; + + return Utils::callPrivateRpc(Utils::jsonToByteArray(std::move(payload))); +} + +RpcResponse Wallet::Accounts::addAccountsFromSeed(const QString& seedPhrase, const QString& password, const QString& accountName, const QString& color) +{ + QString hashedPassword(Utils::hashString(password)); + + QJsonArray params = { + seedPhrase, + hashedPassword, + accountName, + color + }; + + QJsonObject payload { + {"jsonrpc", "2.0"}, + {"method", "accounts_addAccountWithPrivateKey"}, + {"params", params} + }; + + return Utils::callPrivateRpc(Utils::jsonToByteArray(std::move(payload))); +} + +RpcResponse Wallet::Accounts::addWatchOnlyAccount(const QString& address, const QString& accountName , const QString& color) +{ + QJsonArray params = { + address, + accountName, + color + }; + + QJsonObject payload { + {"jsonrpc", "2.0"}, + {"method", "accounts_addAccountWatch"}, + {"params", params} + }; + + return Utils::callPrivateRpc(Utils::jsonToByteArray(std::move(payload))); +} + +RpcResponse Wallet::Accounts::deleteAccount(const QString& address) +{ + QJsonArray params = { + address + }; + + QJsonObject payload { + {"jsonrpc", "2.0"}, + {"method", "accounts_deleteAccount"}, + {"params", params} + }; + + return Utils::callPrivateRpc(Utils::jsonToByteArray(std::move(payload))); +} diff --git a/src-cpp-structure/projects/CMakeLists.txt b/src-cpp-structure/projects/CMakeLists.txt new file mode 100644 index 0000000000..70300dce93 --- /dev/null +++ b/src-cpp-structure/projects/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) + +add_subdirectory(App) +add_subdirectory(Services) +add_subdirectory(Backend) +add_subdirectory(ServicesTest) diff --git a/src-cpp-structure/projects/Services/CMakeLists.txt b/src-cpp-structure/projects/Services/CMakeLists.txt new file mode 100644 index 0000000000..b9a8717410 --- /dev/null +++ b/src-cpp-structure/projects/Services/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) + +project(Status.Services DESCRIPTION "Status services") + +set(CMAKE_DEBUG_POSTFIX d) +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +add_library(${PROJECT_NAME} SHARED) + +# Platform specific stuff are place in the corresponding .cmake file. +if (WIN32) + include("${CMAKE_SOURCE_DIR}/cmake/services-win.cmake") +elseif (APPLE) + include("${CMAKE_SOURCE_DIR}/cmake/services-mac.cmake") +elseif(UNIX) + include("${CMAKE_SOURCE_DIR}/cmake/services-linux.cmake") +endif () + +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) + +target_include_directories(${PROJECT_NAME} PUBLIC include) + +# default token is a free-tier token with limited capabilities and usage +# limits; setup your own infura key with +# cmake -DINFURA_KEY=infura_key_goes_here .. +if( "${INFURA_KEY}" STREQUAL "") + message("-- Using default Infura key") + file (STRINGS ${CMAKE_SOURCE_DIR}/../resources/infura_key INFURA_KEY) +else() + message("-- Using custom Infura key") +endif() + +# Build constants +target_compile_definitions(${PROJECT_NAME} PRIVATE INFURA_KEY="${INFURA_KEY}") diff --git a/src-cpp-structure/projects/Services/include/StatusServices/Accounts/AccountDto.h b/src-cpp-structure/projects/Services/include/StatusServices/Accounts/AccountDto.h new file mode 100644 index 0000000000..f599af0d53 --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/Accounts/AccountDto.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include + +namespace Status::Accounts +{ + struct Image + { + QString keyUid; + QString imgType; + QString uri; + int width; + int height; + int fileSize; + int resizeTarget; + + static Image toImage(const QJsonObject& jsonObj) + { + auto result = Image(); + + try + { + result.keyUid = Json::getProp(jsonObj, "keyUid")->toString(); + result.imgType = Json::getProp(jsonObj, "type")->toString(); + result.uri = Json::getProp(jsonObj, "uri")->toString(); + result.width = Json::getProp(jsonObj, "width")->toInt(); + result.height = Json::getProp(jsonObj, "height")->toInt(); + result.fileSize = Json::getProp(jsonObj, "fileSize")->toInt(); + result.resizeTarget = Json::getProp(jsonObj, "resizeTarget")->toInt(); + } + catch (std::exception e) + { + qWarning() << QObject::tr("Mapping Image failed: %1").arg(e.what()); + } + + return result; + } + }; + + struct AccountDto + { + QString name; + long timestamp; + QString identicon; + QString keycardPairing; + QString keyUid; + QVector images; + + bool isValid() const + { + return !(name.isEmpty() || keyUid.isEmpty()); + } + + static AccountDto toAccountDto(const QJsonObject& jsonObj) + { + auto result = AccountDto(); + + try + { + result.name = Json::getMandatoryProp(jsonObj, "name")->toString(); + bool ok; + result.timestamp = Json::getMandatoryProp(jsonObj, "timestamp")->toString().toLong(&ok); + result.identicon = Json::getMandatoryProp(jsonObj, "identicon")->toString(); + result.keycardPairing = Json::getMandatoryProp(jsonObj, "keycard-pairing")->toString(); + result.keyUid = Json::getMandatoryProp(jsonObj, "key-uid")->toString(); + + foreach(const auto& value, jsonObj["images"].toArray()) + { + result.images << Image::toImage(value.toObject()); + } + } + catch (std::exception e) + { + qWarning() << QObject::tr("Mapping AccountDto failed: %1").arg(e.what()); + } + + return result; + } + }; + + + + +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/Accounts/GeneratedAccountDto.h b/src-cpp-structure/projects/Services/include/StatusServices/Accounts/GeneratedAccountDto.h new file mode 100644 index 0000000000..756fb372bb --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/Accounts/GeneratedAccountDto.h @@ -0,0 +1,116 @@ +#pragma once + +#include + +#include + +namespace Status::Accounts +{ + struct DerivedAccountDetails + { + QString publicKey; + QString address; + QString derivationPath; + + static DerivedAccountDetails toDerivedAccountDetails(const QJsonObject& jsonObj, const QString& derivationPath) + { + // Mapping this DTO is not strightforward since only keys are used for id. We + // handle it a bit different. + auto result = DerivedAccountDetails(); + + try + { + result.derivationPath = derivationPath; + result.publicKey = Json::getMandatoryProp(jsonObj, "publicKey")->toString(); + result.address = Json::getMandatoryProp(jsonObj, "address")->toString(); + } + catch (std::exception e) + { + qWarning() << QObject::tr("Mapping DerivedAccountDetails failed: %1").arg(e.what()); + } + + return result; + } + }; + + struct DerivedAccounts + { + DerivedAccountDetails whisper; + DerivedAccountDetails walletRoot; + DerivedAccountDetails defaultWallet; + DerivedAccountDetails eip1581; + + static DerivedAccounts toDerivedAccounts(const QJsonObject& jsonObj) + { + auto result = DerivedAccounts(); + + foreach(const auto& derivationPath, jsonObj.keys()) + { + auto derivedObj = jsonObj.value(derivationPath).toObject(); + if(derivationPath == Constants::General::PathWhisper) + { + result.whisper = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath); + } + else if(derivationPath == Constants::General::PathWalletRoot) + { + result.walletRoot = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath); + } + else if(derivationPath == Constants::General::PathDefaultWallet) + { + result.defaultWallet = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath); + } + else if(derivationPath == Constants::General::PathEIP1581) + { + result.eip1581 = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath); + } + } + + return result; + } + }; + + struct GeneratedAccountDto + { + QString id; + QString publicKey; + QString address; + QString keyUid; + QString mnemonic; + DerivedAccounts derivedAccounts; + + // The following two are set additionally. + QString alias; + QString identicon; + + bool isValid() const + { + return !(id.isEmpty() || publicKey.isEmpty() || address.isEmpty() || keyUid.isEmpty()); + } + + static GeneratedAccountDto toGeneratedAccountDto(const QJsonObject& jsonObj) + { + auto result = GeneratedAccountDto(); + + try + { + result.id = Json::getMandatoryProp(jsonObj, "id")->toString(); + result.address = Json::getMandatoryProp(jsonObj, "address")->toString(); + result.keyUid = Json::getMandatoryProp(jsonObj, "keyUid")->toString(); + result.mnemonic = Json::getMandatoryProp(jsonObj, "mnemonic")->toString(); + result.publicKey = Json::getMandatoryProp(jsonObj, "publicKey")->toString(); + + auto derivedObj = Json::getProp(jsonObj, "derived")->toObject(); + if(!derivedObj.isEmpty()) + { + result.derivedAccounts = DerivedAccounts::toDerivedAccounts(derivedObj); + } + } + catch (std::exception e) + { + qWarning() << QObject::tr("Mapping GeneratedAccountDto failed: %1").arg(e.what()); + } + + return result; + } + }; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/Accounts/Service.h b/src-cpp-structure/projects/Services/include/StatusServices/Accounts/Service.h new file mode 100644 index 0000000000..b3a44fb589 --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/Accounts/Service.h @@ -0,0 +1,74 @@ +#pragma once + +#include "ServiceInterface.h" + +namespace Status::Accounts +{ + + class Service : public ServiceInterface + { + public: + Service(); + + void init(const QString& statusgoDataDir) override; + + [[nodiscard]] QVector openedAccounts() override; + + [[nodiscard]] const QVector& generatedAccounts() const override; + + bool setupAccount(const QString& accountId, const QString& password) override; + + [[nodiscard]] const AccountDto& getLoggedInAccount() const override; + + [[nodiscard]] const GeneratedAccountDto& getImportedAccount() const override; + + [[nodiscard]] bool isFirstTimeAccountLogin() const override; + + QString validateMnemonic(const QString& mnemonic) override; + + bool importMnemonic(const QString& mnemonic) override; + + QString login(AccountDto account, const QString& password) override; + + void clear() override; + + QString generateAlias(const QString& publicKey) override; + + QString generateIdenticon(const QString& publicKey) override; + + bool verifyAccountPassword(const QString& account, const QString& password) override; + + private: + QJsonObject prepareAccountJsonObject(const GeneratedAccountDto& account) const; + + DerivedAccounts storeDerivedAccounts(const QString& accountId, const QString& hashedPassword, + const QVector& paths); + + AccountDto saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account, + const QJsonArray& subaccounts, const QJsonObject& settings, + const QJsonObject& config); + + QJsonObject getAccountDataForAccountId(const QString& accountId) const; + + QJsonArray prepareSubaccountJsonObject(const GeneratedAccountDto& account) const; + + QJsonArray getSubaccountDataForAccountId(const QString& accountId) const; + + QString generateSigningPhrase(const int count) const; + + QJsonObject prepareAccountSettingsJsonObject(const GeneratedAccountDto& account, + const QString& installationId) const; + + QJsonObject getAccountSettings(const QString& accountId, const QString& installationId) const; + + QJsonObject getDefaultNodeConfig(const QString& installationId) const; + + private: + QVector m_generatedAccounts; + + QString m_statusgoDataDir; + bool m_isFirstTimeAccountLogin; + AccountDto m_loggedInAccount; + GeneratedAccountDto m_importedAccount; + }; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/Accounts/ServiceInterface.h b/src-cpp-structure/projects/Services/include/StatusServices/Accounts/ServiceInterface.h new file mode 100644 index 0000000000..019a40c5b9 --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/Accounts/ServiceInterface.h @@ -0,0 +1,42 @@ +#pragma once + +#include "AccountDto.h" +#include "GeneratedAccountDto.h" + +namespace Status::Accounts +{ + class ServiceInterface + { + public: + + virtual ~ServiceInterface() = default; + + virtual void init(const QString& statusgoDataDir) = 0; + + [[nodiscard]] virtual QVector openedAccounts() = 0; + + [[nodiscard]] virtual const QVector& generatedAccounts() const = 0; + + virtual bool setupAccount(const QString& accountId, const QString& password) = 0; + + [[nodiscard]] virtual const AccountDto& getLoggedInAccount() const = 0; + + [[nodiscard]] virtual const GeneratedAccountDto& getImportedAccount() const = 0; + + [[nodiscard]] virtual bool isFirstTimeAccountLogin() const = 0; + + virtual QString validateMnemonic(const QString& mnemonic) = 0; + + virtual bool importMnemonic(const QString& mnemonic) = 0; + + virtual QString login(AccountDto account, const QString& password) = 0; + + virtual void clear() = 0; + + virtual QString generateAlias(const QString& publicKey) = 0; + + virtual QString generateIdenticon(const QString& publicKey) = 0; + + virtual bool verifyAccountPassword(const QString& account, const QString& password) = 0; + }; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/Accounts/ServiceMock.h b/src-cpp-structure/projects/Services/include/StatusServices/Accounts/ServiceMock.h new file mode 100644 index 0000000000..83eee124ec --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/Accounts/ServiceMock.h @@ -0,0 +1,29 @@ +#pragma once + +#include "ServiceInterface.h" + +#include + +namespace Status::Test +{ + class AccountsServiceMock final : public Accounts::ServiceInterface + { + public: + virtual ~AccountsServiceMock() override {}; + + MOCK_METHOD(void, init, (const QString&), (override)); + MOCK_METHOD(QVector, openedAccounts, (), (override)); + MOCK_METHOD(const QVector&, generatedAccounts, (), (const, override)); + MOCK_METHOD(bool, setupAccount, (const QString&, const QString&), (override)); + MOCK_METHOD(const Accounts::AccountDto&, getLoggedInAccount, (), (const, override)); + MOCK_METHOD(const Accounts::GeneratedAccountDto&, getImportedAccount, (), (const, override)); + MOCK_METHOD(bool, isFirstTimeAccountLogin, (), (const, override)); + MOCK_METHOD(QString, validateMnemonic, (const QString&), (override)); + MOCK_METHOD(bool, importMnemonic, (const QString&), (override)); + MOCK_METHOD(QString, login, (Accounts::AccountDto, const QString&), (override)); + MOCK_METHOD(void, clear, (), (override)); + MOCK_METHOD(QString, generateAlias, (const QString&), (override)); + MOCK_METHOD(QString, generateIdenticon, (const QString&), (override)); + MOCK_METHOD(bool, verifyAccountPassword, (const QString&, const QString&), (override)); + }; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/AccountsService b/src-cpp-structure/projects/Services/include/StatusServices/AccountsService new file mode 100644 index 0000000000..b10bbb7416 --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/AccountsService @@ -0,0 +1,5 @@ +#pragma once + +#include "Accounts/AccountDto.h" +#include "Accounts/GeneratedAccountDto.h" +#include "Accounts/ServiceInterface.h" \ No newline at end of file diff --git a/src-cpp-structure/projects/Services/include/StatusServices/Common/Constants.h b/src-cpp-structure/projects/Services/include/StatusServices/Common/Constants.h new file mode 100644 index 0000000000..55f209af4b --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/Common/Constants.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +namespace Status::Constants +{ + namespace Fleet + { + const QString Prod = "eth.prod"; + const QString Staging = "eth.staging"; + const QString Test = "eth.test"; + const QString WakuV2Prod = "wakuv2.prod"; + const QString WakuV2Test = "wakuv2.test"; + const QString GoWakuTest = "go-waku.test"; + } + + namespace FleetNodes + { + const QString Bootnodes = "boot"; + const QString Mailservers = "mail"; + const QString Rendezvous = "rendezvous"; + const QString Whisper = "whisper"; + const QString Waku = "waku"; + const QString LibP2P = "libp2p"; + const QString Websocket = "websocket"; + } + + namespace General + { + const QString DefaultNetworkName = "mainnet_rpc"; + //const DEFAULT_NETWORKS_IDS* = @["mainnet_rpc", "testnet_rpc", "rinkeby_rpc", "goerli_rpc", "xdai_rpc", "poa_rpc" ] + + const QString ZeroAddress = "0x0000000000000000000000000000000000000000"; + + const QString PathWalletRoot = "m/44'/60'/0'/0"; + // EIP1581 Root Key, the extended key from which any whisper key/encryption key can be derived + const QString PathEIP1581 = "m/43'/60'/1581'"; + // BIP44-0 Wallet key, the default wallet key + const QString PathDefaultWallet = PathWalletRoot + "/0"; + // EIP1581 Chat Key 0, the default whisper key + const QString PathWhisper = PathEIP1581 + "/0'/0"; + + const QVector AccountDefaultPaths {PathWalletRoot, PathEIP1581, PathWhisper, PathDefaultWallet}; + } +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/Common/Json.h b/src-cpp-structure/projects/Services/include/StatusServices/Common/Json.h new file mode 100644 index 0000000000..8cca78ea2d --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/Common/Json.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace Status +{ + + class Json + { + public: + static QJsonObject::const_iterator getProp(const QJsonObject& object, const QString& field) + { + const auto it = object.constFind(field); + return it; + } + + static QJsonObject::const_iterator getMandatoryProp(const QJsonObject& object, const QString& field) + { + const auto it = getProp(object, field); + + if (it == object.constEnd()) + { + throw std::logic_error(QString("No field `%1`").arg(field).toStdString()); + } + + return it; + } + }; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/Common/Logger.h b/src-cpp-structure/projects/Services/include/StatusServices/Common/Logger.h new file mode 100644 index 0000000000..e9f972b967 --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/Common/Logger.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +namespace Status +{ + // This logger class should be done far better, but this is like a first aid. :) + // We should improve this class using `qInstallMessageHandler` and `fmt` library for example + // and make custom logging in the form and place/file we want. + + class Logger final { + public: + + static void init() + { + qInstallMessageHandler(messageHandler); + } + + static void messageHandler(const QtMsgType type, const QMessageLogContext &context, const QString &msg) + { + const char* time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz").toLocal8Bit().constData(); + const char* localMsg = msg.toLocal8Bit().constData(); + const char* file = context.file? context.file : ""; + const char* function = context.function? context.function : ""; + + switch (type) { + case QtDebugMsg: + fprintf(stderr, "Status-DEB [%s] (%s:%u, %s)\nMSG: %s\n", time, file, context.line, function, localMsg); + break; + case QtInfoMsg: + fprintf(stderr, "Status-INF [%s] (%s:%u, %s)\nMSG: %s\n", time, file, context.line, function, localMsg); + break; + case QtWarningMsg: + fprintf(stderr, "Status-WRN [%s] (%s:%u, %s)\nMSG: %s\n", time, file, context.line, function, localMsg); + break; + case QtCriticalMsg: + fprintf(stderr, "Status-CRT [%s] (%s:%u, %s)\nMSG: %s\n", time, file, context.line, function, localMsg); + break; + case QtFatalMsg: + fprintf(stderr, "Status-FAT [%s] (%s:%u, %s)\nMSG: %s\n", time, file, context.line, function, localMsg); + break; + } + } + }; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/Common/SigningPhrases.h b/src-cpp-structure/projects/Services/include/StatusServices/Common/SigningPhrases.h new file mode 100644 index 0000000000..23f95dafed --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/Common/SigningPhrases.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +namespace Status::Constants +{ + const std::array SigningPhrases { + "area", "army", "atom", "aunt", "babe", "baby", "back", "bail", "bait", "bake", "ball", "band", "bank", "barn", + "base", "bass", "bath", "bead", "beak", "beam", "bean", "bear", "beat", "beef", "beer", "beet", "bell", "belt", + "bend", "bike", "bill", "bird", "bite", "blow", "blue", "boar", "boat", "body", "bolt", "bomb", "bone", "book", + "boot", "bore", "boss", "bowl", "brow", "bulb", "bull", "burn", "bush", "bust", "cafe", "cake", "calf", "call", + "calm", "camp", "cane", "cape", "card", "care", "carp", "cart", "case", "cash", "cast", "cave", "cell", "cent", + "chap", "chef", "chin", "chip", "chop", "chub", "chug", "city", "clam", "clef", "clip", "club", "clue", "coal", + "coat", "code", "coil", "coin", "coke", "cold", "colt", "comb", "cone", "cook", "cope", "copy", "cord", "cork", + "corn", "cost", "crab", "craw", "crew", "crib", "crop", "crow", "curl", "cyst", "dame", "dare", "dark", "dart", + "dash", "data", "date", "dead", "deal", "dear", "debt", "deck", "deep", "deer", "desk", "dhow", "diet", "dill", + "dime", "dirt", "dish", "disk", "dock", "doll", "door", "dory", "drag", "draw", "drop", "drug", "drum", "duck", + "dump", "dust", "duty", "ease", "east", "eave", "eddy", "edge", "envy", "epee", "exam", "exit", "face", "fact", + "fail", "fall", "fame", "fang", "farm", "fawn", "fear", "feed", "feel", "feet", "file", "fill", "film", "find", + "fine", "fire", "fish", "flag", "flat", "flax", "flow", "foam", "fold", "font", "food", "foot", "fork", "form", + "fort", "fowl", "frog", "fuel", "full", "gain", "gale", "galn", "game", "garb", "gate", "gear", "gene", "gift", + "girl", "give", "glad", "glen", "glue", "glut", "goal", "goat", "gold", "golf", "gong", "good", "gown", "grab", + "gram", "gray", "grey", "grip", "grit", "gyro", "hail", "hair", "half", "hall", "hand", "hang", "harm", "harp", + "hate", "hawk", "head", "heat", "heel", "hell", "helo", "help", "hemp", "herb", "hide", "high", "hill", "hire", + "hive", "hold", "hole", "home", "hood", "hoof", "hook", "hope", "hops", "horn", "hose", "host", "hour", "hunt", + "hurt", "icon", "idea", "inch", "iris", "iron", "item", "jail", "jeep", "jeff", "joey", "join", "joke", "judo", + "jump", "junk", "jury", "jute", "kale", "keep", "kick", "kill", "kilt", "kind", "king", "kiss", "kite", "knee", + "knot", "lace", "lack", "lady", "lake", "lamb", "lamp", "land", "lark", "lava", "lawn", "lead", "leaf", "leek", + "lier", "life", "lift", "lily", "limo", "line", "link", "lion", "lisa", "list", "load", "loaf", "loan", "lock", + "loft", "long", "look", "loss", "lout", "love", "luck", "lung", "lute", "lynx", "lyre", "maid", "mail", "main", + "make", "male", "mall", "manx", "many", "mare", "mark", "mask", "mass", "mate", "math", "meal", "meat", "meet", + "menu", "mess", "mice", "midi", "mile", "milk", "mime", "mind", "mine", "mini", "mint", "miss", "mist", "moat", + "mode", "mole", "mood", "moon", "most", "moth", "move", "mule", "mutt", "nail", "name", "neat", "neck", "need", + "neon", "nest", "news", "node", "nose", "note", "oboe", "okra", "open", "oval", "oven", "oxen", "pace", "pack", + "page", "pail", "pain", "pair", "palm", "pard", "park", "part", "pass", "past", "path", "peak", "pear", "peen", + "peer", "pelt", "perp", "pest", "pick", "pier", "pike", "pile", "pimp", "pine", "ping", "pink", "pint", "pipe", + "piss", "pith", "plan", "play", "plot", "plow", "poem", "poet", "pole", "polo", "pond", "pony", "poof", "pool", + "port", "post", "prow", "pull", "puma", "pump", "pupa", "push", "quit", "race", "rack", "raft", "rage", "rail", + "rain", "rake", "rank", "rate", "read", "rear", "reef", "rent", "rest", "rice", "rich", "ride", "ring", "rise", + "risk", "road", "robe", "rock", "role", "roll", "roof", "room", "root", "rope", "rose", "ruin", "rule", "rush", + "ruth", "sack", "safe", "sage", "sail", "sale", "salt", "sand", "sari", "sash", "save", "scow", "seal", "seat", + "seed", "self", "sell", "shed", "shin", "ship", "shoe", "shop", "shot", "show", "sick", "side", "sign", "silk", + "sill", "silo", "sing", "sink", "site", "size", "skin", "sled", "slip", "smog", "snob", "snow", "soap", "sock", + "soda", "sofa", "soft", "soil", "song", "soot", "sort", "soup", "spot", "spur", "stag", "star", "stay", "stem", + "step", "stew", "stop", "stud", "suck", "suit", "swan", "swim", "tail", "tale", "talk", "tank", "tard", "task", + "taxi", "team", "tear", "teen", "tell", "temp", "tent", "term", "test", "text", "thaw", "tile", "till", "time", + "tire", "toad", "toga", "togs", "tone", "tool", "toot", "tote", "tour", "town", "tram", "tray", "tree", "trim", + "trip", "tuba", "tube", "tuna", "tune", "turn", "tutu", "twig", "type", "unit", "user", "vane", "vase", "vast", + "veal", "veil", "vein", "vest", "vibe", "view", "vise", "wait", "wake", "walk", "wall", "wash", "wasp", "wave", + "wear", "weed", "week", "well", "west", "whip", "wife", "will", "wind", "wine", "wing", "wire", "wish", "wolf", + "wood", "wool", "word", "work", "worm", "wrap", "wren", "yard", "yarn", "yawl", "year", "yoga", "yoke", "yurt", + "zinc", "zone"}; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/CommonService b/src-cpp-structure/projects/Services/include/StatusServices/CommonService new file mode 100644 index 0000000000..6e9d44630c --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/CommonService @@ -0,0 +1,6 @@ +#pragma once + +#include "Common/Constants.h" +#include "Common/SigningPhrases.h" +#include "Common/Json.h" +#include "Common/Logger.h" \ No newline at end of file diff --git a/src-cpp-structure/projects/Services/include/StatusServices/Keychain/Service.h b/src-cpp-structure/projects/Services/include/StatusServices/Keychain/Service.h new file mode 100644 index 0000000000..f286ff775b --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/Keychain/Service.h @@ -0,0 +1,30 @@ +#pragma once + +#include "ServiceInterface.h" +#include "../src/Keychain/KeychainManager.h" + +namespace Status::Keychain +{ + class Service : public QObject, + public ServiceInterface + { + Q_OBJECT + + public: + explicit Service(); + + void storePassword(const QString& username, const QString& password) override; + + void tryToObtainPassword(const QString& username) override; + + void subscribe(std::shared_ptr listener) override; + + private slots: + void onKeychainManagerError(const QString& errorType, const int errorCode, const QString& errorDescription); + void onKeychainManagerSuccess(const QString& data); + + private: + QVector> m_listeners; + std::unique_ptr m_keychainManager; + }; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/Keychain/ServiceInterface.h b/src-cpp-structure/projects/Services/include/StatusServices/Keychain/ServiceInterface.h new file mode 100644 index 0000000000..279798292e --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/Keychain/ServiceInterface.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace Status::Keychain +{ + const QString ErrorTypeAuthentication = "authentication"; + const QString ErrorTypeKeychain = "keychain"; + + class Listener + { + public: + + virtual ~Listener() = default; + + virtual void onKeychainManagerError(const QString& errorType, const int errorCode, const QString& errorDescription) = 0; + virtual void onKeychainManagerSuccess(const QString& data) = 0; + }; + + class ServiceInterface + { + public: + + virtual ~ServiceInterface() = default; + + virtual void storePassword(const QString& username, const QString& password) = 0; + + virtual void tryToObtainPassword(const QString& username) = 0; + + virtual void subscribe(std::shared_ptr listener) = 0; + }; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/KeychainService b/src-cpp-structure/projects/Services/include/StatusServices/KeychainService new file mode 100644 index 0000000000..edca1d59e9 --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/KeychainService @@ -0,0 +1,3 @@ +#pragma once + +#include "Keychain/ServiceInterface.h" \ No newline at end of file diff --git a/src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/Service.h b/src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/Service.h new file mode 100644 index 0000000000..4cf4752cbe --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/Service.h @@ -0,0 +1,35 @@ +#pragma once + +#include "ServiceInterface.h" + +namespace Status::WalletAccount +{ + class Service : public ServiceInterface + { + public: + Service(); + + void init() override; + + [[nodiscard]] QList getWalletAccounts() override; + + QString generateNewAccount(const QString& password, const QString& accountName, const QString& color) override; + + QString addAccountsFromPrivateKey(const QString& privateKey, const QString& password, + const QString& accountName, const QString& color) override; + + QString addAccountsFromSeed(const QString& seedPhrase, const QString& password, const QString& accountName, + const QString& color) override; + + QString addWatchOnlyAccount(const QString& address, const QString& accountName , const QString& color) override; + + void deleteAccount(const QString& address) override; + + private: + void fetchAccounts(); + void refreshAccounts(); + + private: + QMap m_walletAccounts; + }; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/ServiceInterface.h b/src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/ServiceInterface.h new file mode 100644 index 0000000000..6d081b280e --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/ServiceInterface.h @@ -0,0 +1,31 @@ +#pragma once + +#include "WalletAccountDto.h" + +namespace Status::WalletAccount +{ + class ServiceInterface + { + public: + + virtual ~ServiceInterface() = default; + + virtual void init() = 0; + + [[nodiscard]] virtual QList getWalletAccounts() = 0; + + virtual QString generateNewAccount(const QString& password, const QString& accountName, + const QString& color) = 0; + + virtual QString addAccountsFromPrivateKey(const QString& privateKey, const QString& password, + const QString& accountName, const QString& color) = 0; + + virtual QString addAccountsFromSeed(const QString& seedPhrase, const QString& password, + const QString& accountName, const QString& color) = 0; + + virtual QString addWatchOnlyAccount(const QString& address, const QString& accountName , + const QString& color) = 0; + + virtual void deleteAccount(const QString& address) = 0; + }; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/WalletAccountDto.h b/src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/WalletAccountDto.h new file mode 100644 index 0000000000..bc6bf3b80e --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/WalletAccounts/WalletAccountDto.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include + +namespace Status::WalletAccount +{ + struct WalletToken + { + QString name; + QString address; + QString symbol; + int decimals; + bool hasIcon; + QString color; + bool isCustom; + float balance; + float currencyBalance; + }; + + struct WalletAccountDto + { + QString name; + QString address; + QString path; + QString color; + QString publicKey; + QString walletType; + bool isWallet; + bool isChat; + QVector tokens; // this is not set by mapping remote DTO, but built on the app side (set later) + + static WalletAccountDto toWalletAccountDto(const QJsonObject& jsonObj) + { + auto result = WalletAccountDto(); + + try + { + result.name = Json::getMandatoryProp(jsonObj, "name")->toString(); + result.address = Json::getMandatoryProp(jsonObj, "address")->toString(); + result.path = Json::getMandatoryProp(jsonObj, "path")->toString(); + result.color = Json::getMandatoryProp(jsonObj, "color")->toString(); + result.publicKey = Json::getMandatoryProp(jsonObj, "public-key")->toString(); + result.walletType = Json::getMandatoryProp(jsonObj, "type")->toString(); + result.isWallet = Json::getMandatoryProp(jsonObj, "wallet")->toBool(); + result.isChat = Json::getMandatoryProp(jsonObj, "chat")->toBool(); + } + catch (std::exception e) + { + qWarning() << QObject::tr("Mapping WalletAccountDto failed: %1").arg(e.what()); + } + + return result; + } + }; +} diff --git a/src-cpp-structure/projects/Services/include/StatusServices/WalletAccountsService b/src-cpp-structure/projects/Services/include/StatusServices/WalletAccountsService new file mode 100644 index 0000000000..c4990a27ff --- /dev/null +++ b/src-cpp-structure/projects/Services/include/StatusServices/WalletAccountsService @@ -0,0 +1,4 @@ +#pragma once + +#include "WalletAccounts/WalletAccountDto.h" +#include "WalletAccounts/ServiceInterface.h" \ No newline at end of file diff --git a/src-cpp-structure/projects/Services/src/Accounts/Service.cpp b/src-cpp-structure/projects/Services/src/Accounts/Service.cpp new file mode 100644 index 0000000000..3a0c499c16 --- /dev/null +++ b/src-cpp-structure/projects/Services/src/Accounts/Service.cpp @@ -0,0 +1,403 @@ +#include "StatusServices/Accounts/Service.h" + +#include "StatusBackend/Accounts.h" +#include "StatusBackend/Utils.h" + +using namespace Status::Accounts; + +Service::Service() + : m_isFirstTimeAccountLogin(false) +{ +} + +void Service::init(const QString& statusgoDataDir) +{ + m_statusgoDataDir = statusgoDataDir; + auto response = Backend::Accounts::generateAddresses(Constants::General::AccountDefaultPaths); + if(response.containsError()) + { + qWarning() << response.error.message; + return; + } + + foreach(const auto& genAddressObj, response.result) + { + auto gAcc = GeneratedAccountDto::toGeneratedAccountDto(genAddressObj.toObject()); + gAcc.alias = generateAlias(gAcc.derivedAccounts.whisper.publicKey); + gAcc.identicon = generateIdenticon(gAcc.derivedAccounts.whisper.publicKey); + m_generatedAccounts.append(std::move(gAcc)); + } +} + +QVector Service::openedAccounts() +{ + auto response = Backend::Accounts::openAccounts(m_statusgoDataDir); + if(response.containsError()) + { + qWarning() << response.error.message; + return QVector(); + } + + QJsonArray multiAccounts = response.result; + QVector result; + foreach(const auto& value, multiAccounts) + { + result << AccountDto::toAccountDto(value.toObject()); + } + return result; +} + +const QVector& Service::generatedAccounts() const +{ + return m_generatedAccounts; +} + +bool Service::setupAccount(const QString& accountId, const QString& password) +{ + QString installationId(QUuid::createUuid().toString(QUuid::WithoutBraces)); + QJsonObject accountData(getAccountDataForAccountId(accountId)); + QJsonArray subAccountData(getSubaccountDataForAccountId(accountId)); + QJsonObject settings(getAccountSettings(accountId, installationId)); + QJsonObject nodeConfig(getDefaultNodeConfig(installationId)); + + QString hashedPassword(Backend::Utils::hashString(password)); + + Service::storeDerivedAccounts(accountId, hashedPassword, Constants::General::AccountDefaultPaths); + + m_loggedInAccount = saveAccountAndLogin(hashedPassword, accountData, subAccountData, settings, nodeConfig); + + return getLoggedInAccount().isValid(); +} + +const AccountDto& Service::getLoggedInAccount() const +{ + return m_loggedInAccount; +} + +const GeneratedAccountDto& Service::getImportedAccount() const +{ + return m_importedAccount; +} + +bool Service::isFirstTimeAccountLogin() const +{ + return m_isFirstTimeAccountLogin; +} + +QString Service::validateMnemonic(const QString& mnemonic) +{ + // TODO: + return ""; +} + +bool Service::importMnemonic(const QString& mnemonic) +{ + // TODO: + return false; +} + +QString Service::login(AccountDto account, const QString& password) +{ + QString hashedPassword(Backend::Utils::hashString(password)); + + QString thumbnailImage; + QString largeImage; + + foreach(const Image& img, account.images) + { + if(img.imgType == "thumbnail") + { + thumbnailImage = img.uri; + } + else if(img.imgType == "large") + { + largeImage = img.uri; + } + } + + auto response = Backend::Accounts::login(account.name, account.keyUid, hashedPassword, account.identicon, + thumbnailImage, largeImage); + if(response.containsError()) + { + qWarning() << response.error.message; + return QString(); + } + + qInfo() << "Account logged in"; + + m_loggedInAccount = std::move(account); + + return QString(); +} + +void Service::clear() +{ + m_generatedAccounts.clear(); + m_loggedInAccount = AccountDto(); + m_importedAccount = GeneratedAccountDto(); + m_isFirstTimeAccountLogin = false; +} + +QString Service::generateAlias(const QString& publicKey) +{ + auto response = Backend::Accounts::generateAlias(publicKey); + if(response.containsError()) + { + qWarning() << response.error.message; + return QString(); + } + + return response.result; +} + +QString Service::generateIdenticon(const QString& publicKey) +{ + auto response = Backend::Accounts::generateIdenticon(publicKey); + if(response.containsError()) + { + qWarning() << response.error.message; + return QString(); + } + + return response.result; +} + +bool Service::verifyAccountPassword(const QString& account, const QString& password) +{ + // TODO: + return false; +} + +DerivedAccounts Service::storeDerivedAccounts(const QString& accountId, const QString& hashedPassword, + const QVector& paths) +{ + auto response = Backend::Accounts::storeDerivedAccounts(accountId, hashedPassword, paths); + if(response.containsError()) + { + qWarning() << response.error.message; + return DerivedAccounts(); + } + return DerivedAccounts::toDerivedAccounts(response.result); +} + +AccountDto Service::saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account, + const QJsonArray& subaccounts, const QJsonObject& settings, + const QJsonObject& config) +{ + auto response = Backend::Accounts::saveAccountAndLogin(hashedPassword, account, subaccounts, settings, config); + if(response.containsError()) + { + qWarning() << response.error.message; + return AccountDto(); + } + + m_isFirstTimeAccountLogin = true; + return AccountDto::toAccountDto(response.result); +} + +QJsonObject Service::prepareAccountJsonObject(const GeneratedAccountDto& account) const +{ + return QJsonObject{{"name", account.alias}, + {"address", account.address}, + {"photo-path", account.identicon}, + {"identicon", account.identicon}, + {"key-uid", account.keyUid}, + {"keycard-pairing", QJsonValue()}}; +} + +QJsonObject Service::getAccountDataForAccountId(const QString& accountId) const +{ + + foreach(const GeneratedAccountDto& acc, m_generatedAccounts) + { + if(acc.id == accountId) + { + return Service::prepareAccountJsonObject(acc); + } + } + + if(m_importedAccount.isValid()) + { + if(m_importedAccount.id == accountId) + { + return Service::prepareAccountJsonObject(m_importedAccount); + } + } + + qDebug() << "account not found"; + return QJsonObject(); +} + +QJsonArray Service::prepareSubaccountJsonObject(const GeneratedAccountDto& account) const +{ + return QJsonArray{ + QJsonObject{ + {"public-key", account.derivedAccounts.defaultWallet.publicKey}, + {"address", account.derivedAccounts.defaultWallet.address}, + {"color", "#4360df"}, + {"wallet", true}, + {"path", Constants::General::PathDefaultWallet}, + {"name", "Status account"} + }, + QJsonObject{ + {"public-key", account.derivedAccounts.whisper.publicKey}, + {"address", account.derivedAccounts.whisper.address}, + {"path", Constants::General::PathWhisper}, + {"name", account.alias}, + {"identicon", account.identicon}, + {"chat", true} + } + }; +} + +QJsonArray Service::getSubaccountDataForAccountId(const QString& accountId) const +{ + foreach(const GeneratedAccountDto& acc, m_generatedAccounts) + { + if(acc.id == accountId) + { + return prepareSubaccountJsonObject(acc); + } + } + if(m_importedAccount.isValid()) + { + if(m_importedAccount.id == accountId) + { + return prepareSubaccountJsonObject(m_importedAccount); + } + } + + qDebug() << "account not found"; + return QJsonArray(); +} + +QString Service::generateSigningPhrase(const int count) const +{ + QStringList words; + for(int i = 0; i < count; i++) + { + words.append(Constants::SigningPhrases[QRandomGenerator::global()->bounded( + static_cast(Constants::SigningPhrases.size()))]); + } + return words.join(" "); +} + +QJsonObject Service::prepareAccountSettingsJsonObject(const GeneratedAccountDto& account, + const QString& installationId) const +{ + QFile defaultNetworks(":/resources/default-networks.json"); + if(!defaultNetworks.open(QIODevice::ReadOnly)) + { + qDebug() << "unable to open `default-networks.json` for reading"; + return QJsonObject(); + } + + QByteArray readData = defaultNetworks.readAll(); + defaultNetworks.close(); + + QString defaultNetworksContent = readData.replace("%INFURA_KEY%", INFURA_KEY); + QJsonArray defaultNetworksJson = QJsonDocument::fromJson(defaultNetworksContent.toUtf8()).array(); + + return QJsonObject{ + {"key-uid", account.keyUid}, + {"mnemonic", account.mnemonic}, + {"public-key", account.derivedAccounts.whisper.publicKey}, + {"name", account.alias}, + {"address", account.address}, + {"eip1581-address", account.derivedAccounts.eip1581.address}, + {"dapps-address", account.derivedAccounts.defaultWallet.address}, + {"wallet-root-address", account.derivedAccounts.walletRoot.address}, + {"preview-privacy?", true}, + {"signing-phrase", generateSigningPhrase(3)}, + {"log-level", "INFO"}, + {"latest-derived-path", 0}, + {"networks/networks", defaultNetworksJson}, + {"currency", "usd"}, + {"identicon", account.identicon}, + {"waku-enabled", true}, + {"wallet/visible-tokens", { + {Constants::General::DefaultNetworkName, QJsonArray{"SNT"}} + } + }, + {"appearance", 0}, + {"networks/current-network", Constants::General::DefaultNetworkName}, + {"installation-id", installationId} + }; +} + +QJsonObject Service::getAccountSettings(const QString& accountId, const QString& installationId) const +{ + foreach(const GeneratedAccountDto& acc, m_generatedAccounts) + + if(acc.id == accountId) + { + return Service::prepareAccountSettingsJsonObject(acc, installationId); + } + + if(m_importedAccount.isValid()) + { + if(m_importedAccount.id == accountId) + { + return Service::prepareAccountSettingsJsonObject(m_importedAccount, installationId); + } + } + + qDebug() << "account not found"; + return QJsonObject(); +} + +QJsonArray getNodes(const QJsonObject& fleet, const QString& nodeType) +{ + auto nodes = fleet[nodeType].toObject(); + QJsonArray result; + for(auto it = nodes.begin(); it != nodes.end(); ++it) + result << *it; + return result; +} + +QJsonObject Service::getDefaultNodeConfig(const QString& installationId) const +{ + QFile nodeConfig(":/resources/node-config.json"); + if(!nodeConfig.open(QIODevice::ReadOnly)) + { + qDebug() << "unable to open `node-config.json` for reading"; + return QJsonObject(); + } + + QString nodeConfigContent = nodeConfig.readAll(); + nodeConfig.close(); + + + QFile fleets(":/resources/fleets.json"); + if(!fleets.open(QIODevice::ReadOnly)) + { + qDebug() << "unable to open `fleets.json` for reading"; + return QJsonObject(); + } + + QByteArray readFleetData = fleets.readAll(); + fleets.close(); + + + nodeConfigContent = nodeConfigContent.replace("%INSTALLATIONID%", installationId); + nodeConfigContent = nodeConfigContent.replace("%INFURA_KEY%", INFURA_KEY); + QJsonObject nodeConfigJson = QJsonDocument::fromJson(nodeConfigContent.toUtf8()).object(); + QJsonObject clusterConfig = nodeConfigJson["ClusterConfig"].toObject(); + + QJsonObject fleetsJson = QJsonDocument::fromJson(readFleetData).object()["fleets"].toObject(); + auto fleet = fleetsJson[Constants::Fleet::Prod].toObject(); + + clusterConfig["Fleet"] = Constants::Fleet::Prod; + clusterConfig["BootNodes"] = getNodes(fleet, Constants::FleetNodes::Bootnodes); + clusterConfig["TrustedMailServers"] = getNodes(fleet, Constants::FleetNodes::Mailservers); + clusterConfig["StaticNodes"] = getNodes(fleet, Constants::FleetNodes::Whisper); + clusterConfig["RendezvousNodes"] = getNodes(fleet, Constants::FleetNodes::Rendezvous); + clusterConfig["RelayNodes"] = getNodes(fleet, Constants::FleetNodes::Waku); + clusterConfig["StoreNodes"] = getNodes(fleet, Constants::FleetNodes::Waku); + clusterConfig["FilterNodes"] = getNodes(fleet, Constants::FleetNodes::Waku); + clusterConfig["LightpushNodes"] = getNodes(fleet, Constants::FleetNodes::Waku); + + nodeConfigJson["ClusterConfig"] = clusterConfig; + + return nodeConfigJson; +} diff --git a/src-cpp-structure/projects/Services/src/Keychain/Keychain.h b/src-cpp-structure/projects/Services/src/Keychain/Keychain.h new file mode 100644 index 0000000000..6d24266a09 --- /dev/null +++ b/src-cpp-structure/projects/Services/src/Keychain/Keychain.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace Status::Keychain +{ + class Keychain : public QObject + { + Q_OBJECT + + public: + + enum Error { + NoError=0, + EntryNotFound, + CouldNotDeleteEntry, + AccessDeniedByUser, + AccessDenied, + NoBackendAvailable, + NotImplemented, + OtherError + }; + + Keychain(const QString& service, QObject *parent = nullptr); + + void readItem(const QString& key); + void writeItem(const QString& key, const QString& data); + void deleteItem(const QString& key); + + signals: + void success(QString data); + void error(int error, const QString& errorString); + + private: + QString m_service; + }; +} diff --git a/src-cpp-structure/projects/Services/src/Keychain/Keychain.mm b/src-cpp-structure/projects/Services/src/Keychain/Keychain.mm new file mode 100644 index 0000000000..3654f3b882 --- /dev/null +++ b/src-cpp-structure/projects/Services/src/Keychain/Keychain.mm @@ -0,0 +1,162 @@ +#include "Keychain.h" + +#import +#import + +using namespace Status::Keychain; + +struct ErrorDescription +{ + Keychain::Error code; + QString message; + + ErrorDescription(Keychain::Error code, const QString &message) + : code(code) + , message(message) + {} + + static ErrorDescription fromStatus(OSStatus status) + { + switch(status) { + case errSecSuccess: + return ErrorDescription(Keychain::NoError, + "No error"); + case errSecItemNotFound: + return ErrorDescription(Keychain::EntryNotFound, + "The specified item could not be found in the keychain"); + case errSecUserCanceled: + return ErrorDescription(Keychain::AccessDeniedByUser, + "User canceled the operation"); + case errSecInteractionNotAllowed: + return ErrorDescription(Keychain::AccessDenied, + "User interaction is not allowed"); + case errSecNotAvailable: + return ErrorDescription(Keychain::AccessDenied, + "No keychain is available. You may need to restart your computer"); + case errSecAuthFailed: + return ErrorDescription(Keychain::AccessDenied, + "The user name or passphrase you entered is not correct"); + case errSecVerifyFailed: + return ErrorDescription(Keychain::AccessDenied, + "A cryptographic verification failure has occurred"); + case errSecUnimplemented: + return ErrorDescription(Keychain::NotImplemented, + "Function or operation not implemented"); + case errSecIO: + return ErrorDescription(Keychain::OtherError, + "I/O error"); + case errSecOpWr: + return ErrorDescription(Keychain::OtherError, + "Already open with with write permission"); + case errSecParam: + return ErrorDescription(Keychain::OtherError, + "Invalid parameters passed to a function"); + case errSecAllocate: + return ErrorDescription(Keychain::OtherError, + "Failed to allocate memory"); + case errSecBadReq: + return ErrorDescription(Keychain::OtherError, + "Bad parameter or invalid state for operation"); + case errSecInternalComponent: + return ErrorDescription(Keychain::OtherError, + "An internal component failed"); + case errSecDuplicateItem: + return ErrorDescription(Keychain::OtherError, + "The specified item already exists in the keychain"); + case errSecDecode: + return ErrorDescription(Keychain::OtherError, + "Unable to decode the provided data"); + } + + return ErrorDescription(Keychain::OtherError, "Unknown error"); + } +}; + +Keychain::Keychain(const QString& service, QObject *parent) + : QObject(parent) + , m_service(service) +{} + +void Keychain::readItem(const QString& key) +{ + NSDictionary *const query = @{ + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecAttrService: (__bridge NSString *) m_service.toCFString(), + (__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(), + (__bridge id) kSecReturnData: @YES, + }; + + CFTypeRef dataRef = nil; + const OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &dataRef); + + if (status == errSecSuccess) { + QByteArray data; + if (dataRef) + data = QByteArray::fromCFData((CFDataRef) dataRef); + + emit success(QString::fromUtf8(data)); + } else { + const ErrorDescription ed = ErrorDescription::fromStatus(status); + emit error(ed.code, + QString("Could not retrieve private key from keystore: %1").arg(ed.message)); + } + + if (dataRef) + [dataRef release]; +} + +void Keychain::writeItem(const QString& key, const QString& data) +{ + NSDictionary *const query = @{ + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecAttrService: (__bridge NSString *) m_service.toCFString(), + (__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(), + }; + + OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, nil); + + QByteArray baData = data.toUtf8(); + if (status == errSecSuccess) { + NSDictionary *const update = @{ + (__bridge id) kSecValueData: (__bridge NSData *) baData.toCFData(), + }; + + status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) update); + } else { + NSDictionary *const insert = @{ + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecAttrService: (__bridge NSString *) m_service.toCFString(), + (__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(), + (__bridge id) kSecValueData: (__bridge NSData *) baData.toCFData(), + }; + + status = SecItemAdd((__bridge CFDictionaryRef) insert, nil); + } + + if (status == errSecSuccess) { + emit success(QString()); + } else { + const ErrorDescription ed = ErrorDescription::fromStatus(status); + emit error(ed.code, + QString("Could not store data in settings: %1").arg(ed.message)); + } +} + +void Keychain::deleteItem(const QString& key) +{ + const NSDictionary *const query = @{ + (__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword, + (__bridge id) kSecAttrService: (__bridge NSString *) m_service.toCFString(), + (__bridge id) kSecAttrAccount: (__bridge NSString *) key.toCFString(), + }; + + const OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query); + + if (status == errSecSuccess) { + emit success(QString()); + } else { + const ErrorDescription ed = ErrorDescription::fromStatus(status); + emit error(ed.code, + QString("Could not remove private key from keystore: %1").arg(ed.message)); + } +} diff --git a/src-cpp-structure/projects/Services/src/Keychain/KeychainManager.cpp b/src-cpp-structure/projects/Services/src/Keychain/KeychainManager.cpp new file mode 100644 index 0000000000..af5662c9af --- /dev/null +++ b/src-cpp-structure/projects/Services/src/Keychain/KeychainManager.cpp @@ -0,0 +1,57 @@ +#include "KeychainManager.h" + +using namespace Status::Keychain; + +KeychainManager::KeychainManager(const QString& service, + const QString& authenticationReason, QObject* parent) + : QObject(parent) +{ +#ifdef Q_OS_MACOS + m_authenticationReason = authenticationReason; + m_localAuth = std::unique_ptr(new LocalAuthentication()); + m_keychain = std::unique_ptr(new Keychain(service)); + + connect(m_localAuth.get(), &LocalAuthentication::error, [this](int code, const QString& errorString){ + emit error("authentication", code, errorString); + }); + + connect(m_keychain.get(), &Keychain::success, this, &KeychainManager::success); + connect(m_keychain.get(), &Keychain::error, [this](int code, const QString& errorString){ + emit error("keychain", code, errorString); + }); +#else + // Marked params unused until we need them for Win/Linux. + Q_UNUSED(authenticationReason); + Q_UNUSED(service); +#endif +} + +QString KeychainManager::readDataSync(const QString& key) const +{ +#ifdef Q_OS_MACOS + return readDataSyncMacOs(key); +#endif + + return QString(); +} + +void KeychainManager::readDataAsync(const QString& key) +{ +#ifdef Q_OS_MACOS + readDataAsyncMacOs(key); +#endif +} + +void KeychainManager::storeDataAsync(const QString& key, const QString& data) +{ +#ifdef Q_OS_MACOS + storeDataAsyncMacOs(key, data); +#endif +} + +void KeychainManager::deleteDataAsync(const QString& key) +{ +#ifdef Q_OS_MACOS + deleteDataAsyncMacOs(key); +#endif +} diff --git a/src-cpp-structure/projects/Services/src/Keychain/KeychainManager.h b/src-cpp-structure/projects/Services/src/Keychain/KeychainManager.h new file mode 100644 index 0000000000..c5eb4f5b93 --- /dev/null +++ b/src-cpp-structure/projects/Services/src/Keychain/KeychainManager.h @@ -0,0 +1,95 @@ +#pragma once + +#include "Keychain.h" +#include "LocalAuthentication.h" + +#include + +namespace Status::Keychain +{ + class KeychainManager : public QObject + { + Q_OBJECT + + public: + + /*! + * Constructor defining name of the service for storing in the Keychain + * and the reason for requesting authorisation via touch id. + * + * @param service Service name used in Keychain. + * @param authenticationReason Reason for requestion touch id authorization. + */ + KeychainManager(const QString& service, + const QString& authenticationReason, QObject* parent = nullptr); + + /*! + * Synchronously reads @data stored in the Keychain under the @key and + * returns stored @data. In case of any error an empty string will be + * returned and error signal will be emitted. + * + * @param key Key which is stored in the Keychain. + */ + QString readDataSync(const QString& key) const; + + /*! + * Asynchronously reads @data stored in the Keychain under the @key. + * Onces it's read success signal will be emitted containing read data, + * otherwise error signal will be emitted. + * + * @param key Key which is stored in the Keychain. + */ + void readDataAsync(const QString& key); + /*! + * Asynchronously stores @data under the @key in the Keychain. + * Onces @data is stored success signal will be emitted, otherwise error + * signal will be emitted. + * + * @param key Key which is stored in the Keychain. + * @param data Data which is stored in the Keychain. + */ + void storeDataAsync(const QString& key, const QString& data); + /*! + * Asynchronously deletes @data stored in the Keychain under the @key. + * Onces it's deleted success signal will be emitted, otherwise error + * signal will be emitted. + * + * @param key Key which is stored in the Keychain. + */ + void deleteDataAsync(const QString& key); + + signals: + /*! + * Notifies that action was performed successfully and in case of asyc + * read contains read @data, in other cases @data param is empty. + * + * @param data Data read from the Keychain. + */ + void success(const QString& data); + /*! + * Notifies that an error with @error code and @errorString description + * occured. + * + * @param type Determins origin of the error ("authentication" or "keychain") + * @param code Error code. + * @param errorString Error description. + */ + void error(const QString& type, const int code, const QString& errorString); + +#ifdef Q_OS_MACOS + private: + void process(const std::function action); + + QString readDataSyncMacOs(const QString& key) const; + void readDataAsyncMacOs(const QString& key); + void storeDataAsyncMacOs(const QString& key, const QString& data); + void deleteDataAsyncMacOs(const QString& key); + + private: + QString m_authenticationReason; + std::unique_ptr m_localAuth; + std::unique_ptr m_keychain; + QMetaObject::Connection m_actionConnection; +#endif + }; +} diff --git a/src-cpp-structure/projects/Services/src/Keychain/KeychainManager.mm b/src-cpp-structure/projects/Services/src/Keychain/KeychainManager.mm new file mode 100644 index 0000000000..28698238a9 --- /dev/null +++ b/src-cpp-structure/projects/Services/src/Keychain/KeychainManager.mm @@ -0,0 +1,74 @@ +#include "KeychainManager.h" + +using namespace Status::Keychain; + +QString KeychainManager::readDataSyncMacOs(const QString& key) const +{ + QString storedData; + QEventLoop loop; + + auto onAuthenticationSuccess = [this, &key](){ + m_keychain->readItem(key); + }; + + auto onReadItemSuccess = [&loop, &storedData](QString data){ + storedData = data; + loop.quit(); + }; + + connect(m_localAuth.get(), &LocalAuthentication::success, onAuthenticationSuccess); + + connect(m_keychain.get(), &Keychain::success, onReadItemSuccess); + + connect(m_localAuth.get(), &LocalAuthentication::error, [this, &loop](int code, const QString& errorString){ + Q_UNUSED(code) + Q_UNUSED(errorString) + loop.quit(); + }); + + connect(m_keychain.get(), &Keychain::error, [this, &loop](int code, const QString& errorString){ + Q_UNUSED(code) + Q_UNUSED(errorString) + loop.quit(); + }); + + m_localAuth->runAuthentication(m_authenticationReason); + loop.exec(); + + return storedData; +} + +void KeychainManager::readDataAsyncMacOs(const QString& key) +{ + auto readAction = [this, key](){ + m_keychain->readItem(key); + }; + + process(readAction); +} + +void KeychainManager::storeDataAsyncMacOs(const QString& key, const QString& data) +{ + auto writeAction = [this, key, data](){ + m_keychain->writeItem(key, data); + }; + + process(writeAction); +} + +void KeychainManager::deleteDataAsyncMacOs(const QString& key) +{ + auto deleteAction = [this, key](){ + m_keychain->deleteItem(key); + }; + + process(deleteAction); +} + +void KeychainManager::process(const std::function action) +{ + disconnect(m_actionConnection); + m_actionConnection = connect(m_localAuth.get(), &LocalAuthentication::success, action); + + m_localAuth->runAuthentication(m_authenticationReason); +} diff --git a/src-cpp-structure/projects/Services/src/Keychain/LocalAuthentication.h b/src-cpp-structure/projects/Services/src/Keychain/LocalAuthentication.h new file mode 100644 index 0000000000..11fea9ed7d --- /dev/null +++ b/src-cpp-structure/projects/Services/src/Keychain/LocalAuthentication.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +namespace Status::Keychain +{ + class LocalAuthentication : public QObject + { + Q_OBJECT + + public: + + enum Error { + Domain=0, + AppCanceled, + SystemCanceled, + UserCanceled, + TouchIdNotAvailable, + TouchIdNotConfigured, + WrongCredentials, + OtherError + }; + + void runAuthentication(const QString& authenticationReason); + + signals: + void success(); + void error(int error, const QString& errorString); + }; +} diff --git a/src-cpp-structure/projects/Services/src/Keychain/LocalAuthentication.mm b/src-cpp-structure/projects/Services/src/Keychain/LocalAuthentication.mm new file mode 100644 index 0000000000..03eaf6b16e --- /dev/null +++ b/src-cpp-structure/projects/Services/src/Keychain/LocalAuthentication.mm @@ -0,0 +1,152 @@ +#include "LocalAuthentication.h" + +#include +#import + +using namespace Status::Keychain; + +struct ErrorDescription +{ + LocalAuthentication::Error code; + QString message; + + ErrorDescription(LocalAuthentication::Error code, const QString &message) + : code(code) + , message(message) + {} + + static ErrorDescription fromLAError(NSInteger err) + { + NSProcessInfo *pInfo = [NSProcessInfo processInfo]; + NSOperatingSystemVersion info = [pInfo operatingSystemVersion]; + +#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED +#if defined MAC_OS_VERSION_11_2 \ + && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_11_2 + switch(err) { + case LAErrorBiometryDisconnected: /* MacOs 11.2+ */ + return ErrorDescription(LocalAuthentication::OtherError, + "The device supports biometry only using a removable accessory, but the paired accessory isn’t connected"); + case LAErrorBiometryNotPaired: /* MacOs 11.2+ */ + return ErrorDescription(LocalAuthentication::OtherError, + "The device supports biometry only using a removable accessory, but no accessory is paired"); + } +#endif + +#if defined MAC_OS_X_VERSION_10_15 \ + && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15 + switch(err) { + case LAErrorWatchNotAvailable: /* MacOs 10.15+ */ + return ErrorDescription(LocalAuthentication::OtherError, + "An attempt to authenticate with Apple Watch failed"); + } +#endif + +#if defined MAC_OS_X_VERSION_10_13 \ + && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13 + switch(err) { + case LAErrorBiometryLockout: /* MacOs 10.13+ */ + return ErrorDescription(LocalAuthentication::OtherError, + "Biometry is locked because there were too many failed attempts"); + case LAErrorBiometryNotAvailable: /* MacOs 10.13+ */ + return ErrorDescription(LocalAuthentication::TouchIdNotAvailable, + "Biometry is not available on the device"); + case LAErrorBiometryNotEnrolled: /* MacOs 10.13+ */ + return ErrorDescription(LocalAuthentication::TouchIdNotConfigured, + "The user has no enrolled biometric identities"); + } +#endif + +#if defined MAC_OS_X_VERSION_10_11 \ + && defined MAC_OS_X_VERSION_10_13 \ + && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11 \ + && MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_13 + switch(err) { + case LAErrorTouchIDLockout: /* MacOs 10.11 - 10.13 */ + return ErrorDescription(LocalAuthentication::OtherError, + "Touch ID is locked because there were too many failed attempts"); + } +#endif + +#if defined MAC_OS_X_VERSION_10_10 \ + && defined MAC_OS_X_VERSION_10_13 \ + && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 \ + && MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_13 + switch(err) { + case LAErrorTouchIDNotAvailable: /* MacOs 10.10 - 10.13 */ + return ErrorDescription(LocalAuthentication::TouchIdNotAvailable, + "Touch ID is not available on the device"); + case LAErrorTouchIDNotEnrolled: /* MacOs 10.10 - 10.13 */ + return ErrorDescription(LocalAuthentication::TouchIdNotConfigured, + "The user has no enrolled Touch ID fingers"); + } +#endif + +#if defined MAC_OS_X_VERSION_10_11 \ + && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11 + switch(err) { + case LAErrorInvalidContext: /* MacOs 10.11+ */ + return ErrorDescription(LocalAuthentication::OtherError, + "The context was previously invalidated"); + } +#endif + +#if defined MAC_OS_X_VERSION_10_10 \ + && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 + switch(err) { + case LAErrorSystemCancel: /* MacOs 10.10+ */ + return ErrorDescription(LocalAuthentication::SystemCanceled, + "The system canceled authentication"); + case LAErrorUserCancel: /* MacOs 10.10+ */ + return ErrorDescription(LocalAuthentication::UserCanceled, + "The user tapped the cancel button in the authentication dialog"); + case LAErrorAuthenticationFailed: /* MacOs 10.10+ */ + return ErrorDescription(LocalAuthentication::WrongCredentials, + "The user failed to provide valid credentials"); + case LAErrorNotInteractive: /* MacOs 10.10+ */ + return ErrorDescription(LocalAuthentication::OtherError, + "Displaying the required authentication user interface is forbidden"); + case LAErrorPasscodeNotSet: /* MacOs 10.10+ */ + return ErrorDescription(LocalAuthentication::OtherError, + "A passcode isn’t set on the device"); + case LAErrorUserFallback: /* MacOs 10.10+ */ + return ErrorDescription(LocalAuthentication::OtherError, + "The user tapped the fallback button in the authentication dialog, but no fallback is available for the authentication policy"); + } +#endif + +#endif + + return ErrorDescription(LocalAuthentication::OtherError, "Unknown error"); + } +}; + +void LocalAuthentication::runAuthentication(const QString& authenticationReason) +{ + LAContext *laContext = [[LAContext alloc] init]; + NSError *authError = nil; + NSString *localizedReasonString = authenticationReason.toNSString(); + + if ([laContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError]) + { + [laContext evaluatePolicy:LAPolicyDeviceOwnerAuthentication + localizedReason:localizedReasonString + reply:^(BOOL authenticated, NSError *err) + { + if (authenticated) + { + emit success(); + } + else + { + const ErrorDescription ed = ErrorDescription::fromLAError([err code]); + emit error(ed.code, QString("User did not authenticate successfully: %1").arg(ed.message)); + } + }]; + } + else + { + const ErrorDescription ed = ErrorDescription::fromLAError([authError code]); + emit error(ed.code, QString("Could not evaluate policy: %1").arg(ed.message)); + } +} diff --git a/src-cpp-structure/projects/Services/src/Keychain/Service.cpp b/src-cpp-structure/projects/Services/src/Keychain/Service.cpp new file mode 100644 index 0000000000..8299788d58 --- /dev/null +++ b/src-cpp-structure/projects/Services/src/Keychain/Service.cpp @@ -0,0 +1,47 @@ +#include "StatusServices/Keychain/Service.h" + +#include "KeychainManager.h" + +using namespace Status::Keychain; + +Service::Service() : QObject(nullptr) + , m_keychainManager(std::make_unique("StatusDesktop", "authenticate you")) +{ + connect(m_keychainManager.get(), &KeychainManager::success, this, &Service::onKeychainManagerSuccess, Qt::QueuedConnection); + connect(m_keychainManager.get(), &KeychainManager::error, this, &Service::onKeychainManagerError, Qt::QueuedConnection); +} + +void Service::storePassword(const QString& username, const QString& password) +{ + m_keychainManager->storeDataAsync(username, password); +} + +void Service::tryToObtainPassword(const QString& username) +{ + m_keychainManager->readDataAsync(username); +} + +void Service::subscribe(std::shared_ptr listener) +{ + m_listeners.append(std::move(listener)); +} + +void Service::onKeychainManagerError(const QString& errorType, const int errorCode, const QString& errorDescription) +{ + // This slot is called in case an error occured while we're dealing with + // KeychainManager. So far we're just logging the error. + qWarning() << "KeychainManager stopped, code: " << errorCode << " desc: " << errorDescription; + + for(const auto& it : m_listeners) + { + it->onKeychainManagerError(errorType, errorCode, errorDescription); + } +} + +void Service::onKeychainManagerSuccess(const QString& data) +{ + for(const auto& it : m_listeners) + { + it->onKeychainManagerSuccess(data); + } +} diff --git a/src-cpp-structure/projects/Services/src/WalletAccounts/Service.cpp b/src-cpp-structure/projects/Services/src/WalletAccounts/Service.cpp new file mode 100644 index 0000000000..2a8e59578e --- /dev/null +++ b/src-cpp-structure/projects/Services/src/WalletAccounts/Service.cpp @@ -0,0 +1,108 @@ +#include "StatusServices/WalletAccounts/Service.h" + +#include "StatusBackend/WalletAccounts.h" + +using namespace Status::WalletAccount; + +Service::Service() +{ +} + +void Service::init() +{ + fetchAccounts(); +} + +void Service::fetchAccounts() +{ + auto response = Backend::Wallet::Accounts::getAccounts(); + if(response.containsError()) + { + qWarning() << response.error.message; + return; + } + + foreach(const auto& value, response.result) + { + auto account = WalletAccountDto::toWalletAccountDto(value.toObject()); + if(!account.isChat) + { + m_walletAccounts[account.address] = std::move(account); + } + } +} + +void Service::refreshAccounts() +{ + fetchAccounts(); +} + +QList Service::getWalletAccounts() +{ + return m_walletAccounts.values(); +} + +QString Service::generateNewAccount(const QString& password, const QString& accountName, const QString& color) +{ + auto response = Backend::Wallet::Accounts::generateNewAccount(password, accountName, color); + if(response.containsError()) + { + qWarning() << response.error.message; + return response.error.message; + } + + refreshAccounts(); + return QString(); +} + +QString Service::addAccountsFromPrivateKey(const QString& privateKey, const QString& password, + const QString& accountName, const QString& color) +{ + auto response = Backend::Wallet::Accounts::addAccountsFromPrivateKey(privateKey, password, accountName, color); + if(response.containsError()) + { + qWarning() << response.error.message; + return response.error.message; + } + + refreshAccounts(); + return QString(); +} + +QString Service::addAccountsFromSeed(const QString& seedPhrase, const QString& password, const QString& accountName, const QString& color) +{ + auto response = Backend::Wallet::Accounts::addAccountsFromSeed(seedPhrase, password, accountName, color); + if(response.containsError()) + { + qWarning() << response.error.message; + return response.error.message; + } + + refreshAccounts(); + return QString(); +} + +QString Service::addWatchOnlyAccount(const QString& address, const QString& accountName , const QString& color) +{ + auto response = Backend::Wallet::Accounts::addWatchOnlyAccount(address, accountName, color); + if(response.containsError()) + { + qWarning() << response.error.message; + return response.error.message; + } + + refreshAccounts(); + return QString(); +} + +void Service::deleteAccount(const QString& address) +{ + auto response = Backend::Wallet::Accounts::deleteAccount(address); + if(response.containsError()) + { + qWarning() << response.error.message; + return; + } + + refreshAccounts(); +} diff --git a/src-cpp-structure/projects/ServicesTest/CMakeLists.txt b/src-cpp-structure/projects/ServicesTest/CMakeLists.txt new file mode 100644 index 0000000000..7ca3ec35b6 --- /dev/null +++ b/src-cpp-structure/projects/ServicesTest/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) + +project(Status.ServicesTest DESCRIPTION "Test Status Services Project") + +SET(STATUS_RCC ${CMAKE_SOURCE_DIR}/../resources.rcc) +SET(STATUS_QRC ${CMAKE_SOURCE_DIR}/../ui/resources.qrc) +SET(STATUS_RESOURCES_QRC ${CMAKE_SOURCE_DIR}/../resources/resources.qrc) + +conan_basic_setup(KEEP_RPATHS) + +add_executable(${PROJECT_NAME}) + +add_dependencies(${PROJECT_NAME} Status.Services) + +target_link_libraries( + ${PROJECT_NAME} + Qt5::Core + ${CONAN_LIBS_GTEST} + ${FOUNDATION_FRAMEWORK} + ${IO_KIT_FRAMEWORK} + Status.Services + ) + +file(GLOB_RECURSE SOURCES + "*.h" + "*.cpp" + ${STATUS_RCC} + ${STATUS_RESOURCES_QRC} + ${STATUS_QRC} + ) + +target_sources(${PROJECT_NAME} PRIVATE ${SOURCES}) diff --git a/src-cpp-structure/projects/ServicesTest/src/AccountsService.cpp b/src-cpp-structure/projects/ServicesTest/src/AccountsService.cpp new file mode 100644 index 0000000000..cb90505b15 --- /dev/null +++ b/src-cpp-structure/projects/ServicesTest/src/AccountsService.cpp @@ -0,0 +1,75 @@ +#include +#include + +#include "Utils.h" +#include "StatusTestEnvironment.h" + +#include +#include + +using namespace Status::Test; + +class AccountsServices : public ::testing::Test { + +public: + static std::shared_ptr AccountsServiceInst; + +protected: + + static void SetUpTestSuite() + { + init(); + } + + static void init() { + AccountsServices::AccountsServiceInst = std::make_shared(); + if(!AccountsServices::initialized) + { + AccountsServices::AccountsServiceInst->init(Utils::statusGoDataDir()); + initialized = true; + } + } + + static bool initialized; +}; + +bool AccountsServices::initialized = false; +std::shared_ptr AccountsServices::AccountsServiceInst = nullptr; + +TEST_F(AccountsServices, InitService) +{ + EXPECT_CALL(*StatusTestEnvironment::AccountsServiceMockInst, init(Utils::statusGoDataDir())).Times(1); + + StatusTestEnvironment::AccountsServiceMockInst->init(Utils::statusGoDataDir()); + + EXPECT_TRUE(::testing::Mock::VerifyAndClearExpectations(StatusTestEnvironment::AccountsServiceMockInst.get())); +} + +TEST_F(AccountsServices, GeneratedAccounts) +{ + EXPECT_CALL(::testing::Const(*StatusTestEnvironment::AccountsServiceMockInst), generatedAccounts()).WillRepeatedly( + ::testing::Invoke(AccountsServices::AccountsServiceInst.get(), &Status::Accounts::Service::generatedAccounts)); + + auto genAccounts = StatusTestEnvironment::AccountsServiceMockInst->generatedAccounts(); + + ASSERT_EQ(5, genAccounts.size()); + + for(const auto& acc : genAccounts) + { + ASSERT_STRNE(qUtf8Printable(acc.id), ""); + ASSERT_STRNE(qUtf8Printable(acc.publicKey), ""); + ASSERT_STRNE(qUtf8Printable(acc.address), ""); + ASSERT_STRNE(qUtf8Printable(acc.keyUid), ""); + } +} + +TEST_F(AccountsServices, DISABLED_GenerateAlias) // temporary disabled till we see what's happening on the status-go side since it doesn't return aliases for any pk +{ + QString testPubKey = "0x04487f44bac3e90825bfa9720148308cb64835bebb7e888f519cebc127223187067629f8b70d0661a35d4af6516b225286"; + + QString alias; + EXPECT_CALL(*StatusTestEnvironment::AccountsServiceMockInst, generateAlias(testPubKey)).WillRepeatedly(::testing::ReturnPointee(&alias)); + + ASSERT_STRNE(qUtf8Printable(alias), ""); + ASSERT_EQ(alias, AccountsServices::AccountsServiceInst->generateAlias(testPubKey)); +} diff --git a/src-cpp-structure/projects/ServicesTest/src/Constants.h b/src-cpp-structure/projects/ServicesTest/src/Constants.h new file mode 100644 index 0000000000..b4543a384a --- /dev/null +++ b/src-cpp-structure/projects/ServicesTest/src/Constants.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +const QString UserDataDirName = "StatusTest"; +const QString StatusGoDataDirName = "data"; +const QString TmpDataDirName = "tmp"; +const QString LogsDataDirName = "logs"; +const QString QtDataDirName = "qt"; +const QString KeystoreDataDirName = "keystore"; +const QString GlobalSettingsFileName = "global"; diff --git a/src-cpp-structure/projects/ServicesTest/src/StatusTestEnvironment.cpp b/src-cpp-structure/projects/ServicesTest/src/StatusTestEnvironment.cpp new file mode 100644 index 0000000000..5f3f26799c --- /dev/null +++ b/src-cpp-structure/projects/ServicesTest/src/StatusTestEnvironment.cpp @@ -0,0 +1,24 @@ +#include "StatusTestEnvironment.h" + +using namespace Status::Test; + +std::shared_ptr StatusTestEnvironment::AccountsServiceMockInst = nullptr; + + StatusTestEnvironment::~StatusTestEnvironment() +{ +} + +void StatusTestEnvironment::SetUp() +{ + init(); +} + +void StatusTestEnvironment::TearDown() +{ +} + +void StatusTestEnvironment::init() { + StatusTestEnvironment::AccountsServiceMockInst = std::make_shared(); +} + + diff --git a/src-cpp-structure/projects/ServicesTest/src/StatusTestEnvironment.h b/src-cpp-structure/projects/ServicesTest/src/StatusTestEnvironment.h new file mode 100644 index 0000000000..34f8458191 --- /dev/null +++ b/src-cpp-structure/projects/ServicesTest/src/StatusTestEnvironment.h @@ -0,0 +1,21 @@ +#include + +#include "Utils.h" + +#include + +using namespace Status::Test; + +class StatusTestEnvironment : public ::testing::Environment { +public: + ~StatusTestEnvironment() override; + + void SetUp() override; + + void TearDown() override; + + static std::shared_ptr AccountsServiceMockInst; + +protected: + void init(); +}; diff --git a/src-cpp-structure/projects/ServicesTest/src/Utils.h b/src-cpp-structure/projects/ServicesTest/src/Utils.h new file mode 100644 index 0000000000..8c493ed679 --- /dev/null +++ b/src-cpp-structure/projects/ServicesTest/src/Utils.h @@ -0,0 +1,59 @@ +#pragma once + +#include "Constants.h" + +#include + +class Utils final { +public: + + static QString defaultDataDir() + { + auto d = QDir(); + if(STATUS_DEVELOPMENT){ + d = QDir(STATUS_SOURCE_DIR); + d.cdUp(); + } + else { + // We should handle paths for different platforms here in case of non development + } + + return d.absolutePath() + QDir::separator() + UserDataDirName; + } + + static QString statusGoDataDir() + { + return defaultDataDir() + QDir::separator() + StatusGoDataDirName; + } + + static QString keystoreDataDir() + { + return statusGoDataDir() + QDir::separator() + KeystoreDataDirName; + } + + static QString tmpDataDir() + { + return defaultDataDir() + QDir::separator() + TmpDataDirName; + } + + static QString logsDataDir() + { + return defaultDataDir() + QDir::separator() + LogsDataDirName; + } + + static QString qtDataDir() + { + return defaultDataDir() + QDir::separator() + QtDataDirName; + } + + static void ensureDirectories() + { + QDir d; + d.mkpath(defaultDataDir()); + d.mkpath(statusGoDataDir()); + d.mkpath(keystoreDataDir()); + d.mkpath(tmpDataDir()); + d.mkpath(logsDataDir()); + d.mkpath(qtDataDir()); + } +}; diff --git a/src-cpp-structure/projects/ServicesTest/src/main.cpp b/src-cpp-structure/projects/ServicesTest/src/main.cpp new file mode 100644 index 0000000000..b9fdbce195 --- /dev/null +++ b/src-cpp-structure/projects/ServicesTest/src/main.cpp @@ -0,0 +1,21 @@ +#include + +#include "Utils.h" +#include "StatusTestEnvironment.h" + +#include + +int main(int argc, char *argv[]) +{ + Utils::ensureDirectories(); + + ::testing::InitGoogleTest(&argc, argv); + + StatusTestEnvironment* const env = new StatusTestEnvironment(); + if (::testing::AddGlobalTestEnvironment(env) != env) { + qWarning() << "FAILED: AddGlobalTestEnvironment() should return its argument\n"; + ::testing::internal::posix::Abort(); + } + + return RUN_ALL_TESTS(); +} diff --git a/ui/generate-rcc.go b/ui/generate-rcc.go index c4a1a9826c..f33813b776 100644 --- a/ui/generate-rcc.go +++ b/ui/generate-rcc.go @@ -52,6 +52,10 @@ func main() { if err != nil { return err } + + // clean relative path + *sourceDirName, _ = filepath.Abs(*sourceDirName) + if !info.IsDir() { ext := filepath.Ext(path) base := filepath.Base(path) diff --git a/ui/imports/shared/status/StatusChatInput.qml b/ui/imports/shared/status/StatusChatInput.qml index 0c9c0985ea..d9d1637dd8 100644 --- a/ui/imports/shared/status/StatusChatInput.qml +++ b/ui/imports/shared/status/StatusChatInput.qml @@ -1041,9 +1041,9 @@ Rectangle { lastClick = now } - StatusSyntaxHighlighter { - quickTextDocument: messageInputField.textDocument - } +// StatusSyntaxHighlighter { +// quickTextDocument: messageInputField.textDocument +// } MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton