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
This commit is contained in:
Sale Djenic 2022-02-27 17:48:16 +01:00 committed by Stefan Dunca
parent 75a3ff858c
commit 922a38108d
133 changed files with 6317 additions and 3 deletions

6
.gitignore vendored
View File

@ -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/*

View File

@ -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)

View File

@ -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}
)

View File

@ -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}
)

View File

@ -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}
)

View File

@ -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)

View File

@ -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)

View File

@ -0,0 +1,10 @@
target_link_libraries(
${PROJECT_NAME}
Qt5::Core
Status.Backend
)
file(GLOB_RECURSE SOURCES
"*.h"
"*.cpp"
)

View File

@ -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"
)

View File

@ -0,0 +1,10 @@
target_link_libraries(
${PROJECT_NAME}
Qt5::Core
Status.Backend
)
file(GLOB_RECURSE SOURCES
"*.h"
"*.cpp"
)

View File

@ -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} "<RCC>\n <qresource prefix=\"/${QM_FILES_FOLDER_NAME}\">\n")
foreach(qm_file ${QM_FILES})
get_filename_component(qm_name ${qm_file} NAME)
file(APPEND ${QM_RESOURCE_FILE} " <file alias=\"${qm_name}\">${QM_FILES_FOLDER_NAME}/${qm_name}</file>\n")
endforeach(qm_file)
file(APPEND ${QM_RESOURCE_FILE} " </qresource>\n</RCC>\n")
endif ()
endif ()
endfunction()

View File

@ -0,0 +1,4 @@
include(default)
[settings]
build_type=Debug

View File

@ -0,0 +1,4 @@
include(default)
[settings]
build_type=Release

View File

@ -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")

View File

@ -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 <QtGui>
#include <QtQml>
//#include <QtCore> <- 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>("AppWindow", 0 , 1, "AppWindow");
qmlRegisterType<AppWindow>("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<Modules::ModuleBuilder>()();
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;
}

View File

@ -0,0 +1,12 @@
#pragma once
namespace Status
{
class AppController final
{
public:
AppController();
int exec(int& argc, char** argv);
};
}

View File

@ -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
}

View File

@ -0,0 +1,49 @@
#pragma once
#include <QtQuick>
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;
};
}

View File

@ -0,0 +1,37 @@
#include "AppWindow.h"
#include <Foundation/Foundation.h>
#include <AppKit/NSView.h>
#include <AppKit/NSWindow.h>
#include <AppKit/NSColor.h>
#include <AppKit/NSToolbar.h>
#include <AppKit/NSButton.h>
#include <AppKit/AppKit.h>
using namespace Status;
void AppWindow::removeTitleBarMacOs()
{
NSView *nsView = reinterpret_cast<NSView*>(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<NSView*>(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];
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <StatusServices/Accounts/Service.h>
#include <StatusServices/WalletAccounts/Service.h>
#include <StatusServices/Keychain/Service.h>
#include <boost/di.hpp>
namespace Status
{
using namespace boost::di;
const auto Injector = make_injector(
bind<Accounts::ServiceInterface>.to<Accounts::Service>(),
bind<WalletAccount::ServiceInterface>.to<WalletAccount::Service>(),
bind<Keychain::ServiceInterface>.to<Keychain::Service>()
);
}

View File

@ -0,0 +1,6 @@
#include "AppController.h"
int main(int argc, char *argv[])
{
return Status::AppController().exec(argc, argv);
}

View File

@ -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})

View File

@ -0,0 +1,14 @@
#pragma once
#include <QtCore>
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";
}

View File

@ -0,0 +1,67 @@
#pragma once
#include "Constants.h"
#include <QtCore>
#include <QtGui>
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());
}
};
}

View File

@ -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<QQmlComponent> Engine::load(const QString& qmlFile)
{
return std::shared_ptr<QQmlComponent>(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);
}
}

View File

@ -0,0 +1,25 @@
#pragma once
#include <QtQml>
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<QQmlComponent> load(const QString& qmlFile);
static void create(const QString& qmlFile, QQmlContext *context = nullptr);
private:
explicit Engine();
~Engine();
};
}

View File

@ -0,0 +1,18 @@
#include "GlobalEvents.h"
using namespace Status;
GlobalEvents& GlobalEvents::instance()
{
static GlobalEvents events;
return events;
}
GlobalEvents::GlobalEvents()
{
}
GlobalEvents::~GlobalEvents()
{
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <QtCore>
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);
};
}

View File

@ -0,0 +1,96 @@
#include "SignalsManager.h"
#include "GlobalEvents.h"
#include <QtConcurrent>
#include "libstatus.h"
using namespace Status;
std::map<QString, SignalType> 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));
}

View File

@ -0,0 +1,36 @@
#pragma once
#include <QtCore>
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<QString, SignalType> signalMap;
static void signalCallback(const char* data);
void processSignal(const QString& ev);
void decode(const QJsonObject& signalEvent);
};
}

View File

@ -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<QString, QVariant> defaults;
if (defaults.isEmpty())
{
defaults.insert(storeToKeychain, LocalAccountSettingsPossibleValues::StoreToKeychain::NotNow);
defaults.insert(isKeycardEnabled, false);
}
return defaults.value(key);
}

View File

@ -0,0 +1,45 @@
#pragma once
#include "SettingsProperties.h"
#include <QtCore>
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<QSettings> settings;
};
}

View File

@ -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<QString, QVariant> 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);
}

View File

@ -0,0 +1,42 @@
#pragma once
#include "SettingsProperties.h"
#include <QtCore>
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<QSettings> settings;
};
}

View File

@ -0,0 +1,45 @@
#pragma once
#include <QtCore>
template<class T>
[[nodiscard]] T extractValue(const QVariant& value)
{
if constexpr (std::is_same_v<bool, T>)
return value.toBool();
else if constexpr (std::is_same_v<QString, T>)
return value.toString();
else if constexpr (std::is_same_v<int, T>)
return value.toInt();
else if constexpr (std::is_same_v<float, T>)
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<type>(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(); \
}

View File

@ -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()
{
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "ModuleInterface.h"
#include "Startup/ModuleBuilder.h"
#include <memory>
namespace Status::Modules
{
class RootModule final : public ModuleAccessInterface
, public Startup::ModuleDelegateInterface
, public std::enable_shared_from_this<RootModule>
{
public:
RootModule(Startup::ModuleBuilder moduleBuilder);
void load() override;
void startupDidLoad() override;
void userLoggedIn() override;
private:
Startup::ModuleBuilder m_startupModuleBuilder;
std::shared_ptr<Startup::ModuleAccessInterface> m_startupModule;
};
}

View File

@ -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<ModuleAccessInterface> ModuleBuilder::operator()()
{
return std::make_shared<RootModule>(m_startupModuleBuilder);
}

View File

@ -0,0 +1,21 @@
#pragma once
#include "ModuleInterface.h"
#include "Startup/ModuleBuilder.h"
#include <memory>
namespace Status::Modules
{
class ModuleBuilder final
{
public:
ModuleBuilder(Startup::ModuleBuilder moduleBuilder);
[[nodiscard]] std::shared_ptr<ModuleAccessInterface> operator()();
private:
Startup::ModuleBuilder m_startupModuleBuilder;
};
}

View File

@ -0,0 +1,12 @@
#pragma once
namespace Status::Modules
{
class ModuleAccessInterface
{
public:
virtual ~ModuleAccessInterface() = default;
virtual void load() = 0;
};
}

View File

@ -0,0 +1,151 @@
#include <QDebug>
#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();
}
}

View File

@ -0,0 +1,113 @@
#pragma once
#include <QtCore>
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
};
}

View File

@ -0,0 +1,123 @@
#include "SectionModel.h"
using namespace Status::Shared::Models;
SectionModel::SectionModel(QObject* parent)
: QAbstractListModel(parent)
{ }
QHash<int, QByteArray> SectionModel::roleNames() const
{
QHash<int, QByteArray> 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<int>(ModelRole::Active));
}
if(m_items.at(i)->getId() == Id)
{
m_items.at(i)->setIsActive(true);
dataChanged(newIndex, newIndex, QVector<int>(ModelRole::Active));
}
}
}
QPointer<SectionItem> SectionModel::getActiveItem()
{
SectionItem* activeItem = nullptr;
for(auto item : m_items)
{
if(item->getIsActive())
{
activeItem = item;
break;
}
}
return activeItem;
}

View File

@ -0,0 +1,55 @@
#pragma once
#include "SectionItem.h"
#include <QtCore>
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<int, QByteArray> 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<SectionItem> getActiveItem();
// To add other api's later as needed
private:
QVector<SectionItem*> m_items;
};
}

View File

@ -0,0 +1,67 @@
#include "Controller.h"
#include "../../Core/GlobalEvents.h"
#include "../../Common/Utils.h"
#include <StatusServices/CommonService>
using namespace Status::Modules::Startup;
Controller::Controller(std::shared_ptr<Accounts::ServiceInterface> accountsService)
: QObject(nullptr)
, m_delegate(nullptr)
, m_accountsService(std::move(accountsService))
{
}
void Controller::setDelegate(std::shared_ptr<ControllerDelegateInterface> 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.
}

View File

@ -0,0 +1,31 @@
#pragma once
#include "ControllerInterface.h"
#include <StatusServices/AccountsService>
namespace Status::Modules::Startup
{
class Controller : public QObject,
public ControllerInterface
{
Q_OBJECT
public:
explicit Controller(std::shared_ptr<Accounts::ServiceInterface> accountsService);
void setDelegate(std::shared_ptr<ControllerDelegateInterface> 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<Accounts::ServiceInterface> m_accountsService;
std::shared_ptr<ControllerDelegateInterface> m_delegate;
};
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <QtCore>
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;
};
}

View File

@ -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<Accounts::ServiceInterface> accountsService,
std::shared_ptr<Keychain::ServiceInterface> keychainService)
: QObject(nullptr)
, m_delegate(nullptr)
, m_accountsService(std::move(accountsService))
, m_keychainService(std::move(keychainService))
{
}
void Controller::setDelegate(std::shared_ptr<ControllerDelegateInterface> 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<Status::Accounts::AccountDto> 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);
}

View File

@ -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<Controller>
{
Q_OBJECT
public:
explicit Controller(std::shared_ptr<Accounts::ServiceInterface> accountsService,
std::shared_ptr<Keychain::ServiceInterface> keychainService);
void setDelegate(std::shared_ptr<ControllerDelegateInterface> delegate);
// Controller Interface
void init() override;
QVector<Accounts::AccountDto> 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<Accounts::ServiceInterface> m_accountsService;
std::shared_ptr<Keychain::ServiceInterface> m_keychainService;
std::shared_ptr<ControllerDelegateInterface> m_delegate;
QString m_selectedAccountKeyUid;
};
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <StatusServices/AccountsService>
#include <StatusServices/KeychainService>
#include <QtCore>
namespace Status::Modules::Startup::Login
{
class ControllerInterface
{
public:
virtual ~ControllerInterface() = default;
virtual void init() = 0;
virtual QVector<Accounts::AccountDto> 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;
};
}

View File

@ -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;
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <QtCore>
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;
};
}

View File

@ -0,0 +1,73 @@
#include "Model.h"
using namespace Status::Modules::Startup::Login;
Model::Model(QObject* parent)
: QAbstractListModel(parent)
{
}
QHash<int, QByteArray> Model::roleNames() const
{
QHash<int, QByteArray> 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<Item> 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];
}

View File

@ -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<int, QByteArray> roleNames() const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex& index, int role) const;
void setItems(QVector<Item> items);
Item getItemAtIndex(const int index) const;
private:
QVector<Item> m_items;
};
}

View File

@ -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<ModuleDelegateInterface> delegate,
std::shared_ptr<ControllerInterface> controller,
std::shared_ptr<ViewInterface> 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<Accounts::AccountDto> openedAccounts = m_controller->getOpenedAccounts();
if(openedAccounts.size() > 0)
{
QVector<Item> 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);
}

View File

@ -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<Module>
{
public:
Module(std::shared_ptr<ModuleDelegateInterface> delegate,
std::shared_ptr<ControllerInterface> controller,
std::shared_ptr<ViewInterface> 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<ModuleDelegateInterface> m_delegate;
std::shared_ptr<ControllerInterface> m_controller;
std::shared_ptr<ViewInterface> m_view;
bool m_moduleLoaded {false};
};
}

View File

@ -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<Accounts::ServiceInterface> accountsService,
std::shared_ptr<Keychain::ServiceInterface> keychainService)
: m_accountsService(std::move(accountsService))
, m_keychainService(std::move(keychainService))
{
}
std::shared_ptr<ModuleAccessInterface> ModuleBuilder::operator()(std::shared_ptr<ModuleDelegateInterface> delegate) {
auto controller = std::make_shared<Controller>(m_accountsService, m_keychainService);
auto view = std::make_shared<View>();
auto module = std::make_shared<Module>(delegate, controller, view);
controller->setDelegate(module);
view->setDelegate(module);
return module;
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "ModuleInterface.h"
#include <StatusServices/AccountsService>
#include <StatusServices/KeychainService>
#include <memory>
namespace Status::Modules::Startup::Login
{
class ModuleBuilder final
{
public:
ModuleBuilder(std::shared_ptr<Accounts::ServiceInterface> accountsService,
std::shared_ptr<Keychain::ServiceInterface> keychainService);
[[nodiscard]] std::shared_ptr<ModuleAccessInterface> operator()(std::shared_ptr<ModuleDelegateInterface> delegate);
private:
std::shared_ptr<Accounts::ServiceInterface> m_accountsService;
std::shared_ptr<Keychain::ServiceInterface> m_keychainService;
};
}

View File

@ -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;
};
}

View File

@ -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();
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "Item.h"
#include <QtCore>
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;
};
}

View File

@ -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<ViewDelegateInterface> 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<Item> 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);
}

View File

@ -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<ViewDelegateInterface> delegate);
// View Interface
QObject* getQObject() override;
void load() override;
Model* getModel() override;
void setModelItems(QVector<Item> 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<ViewDelegateInterface> m_delegate;
Model* m_model {nullptr};
SelectedAccount* m_selectedAccount {nullptr};
};
}

View File

@ -0,0 +1,34 @@
#pragma once
#include "Model.h"
#include "SelectedAccount.h"
#include <StatusServices/AccountsService>
#include <QtCore>
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<Item> 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;
};
}

View File

@ -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<Startup::ModuleDelegateInterface> delegate,
std::shared_ptr<ControllerInterface> controller,
std::shared_ptr<ViewInterface> 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();
}

View File

@ -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 <StatusServices/AccountsService>
namespace Status::Modules::Startup
{
class Module final : public ModuleAccessInterface
, public ControllerDelegateInterface
, public ViewDelegateInterface
, public std::enable_shared_from_this<Module>
, public Onboarding::ModuleDelegateInterface
, public Login::ModuleDelegateInterface
{
public:
Module(std::shared_ptr<Startup::ModuleDelegateInterface> delegate,
std::shared_ptr<ControllerInterface> controller,
std::shared_ptr<ViewInterface> 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<Startup::ModuleDelegateInterface> m_delegate;
std::shared_ptr<ControllerInterface> m_controller;
std::shared_ptr<ViewInterface> m_view;
Onboarding::ModuleBuilder m_onboardingModuleBuilder;
std::shared_ptr<Onboarding::ModuleAccessInterface> m_onboardingModule;
Login::ModuleBuilder m_loginModuleBuilder;
std::shared_ptr<Login::ModuleAccessInterface> m_loginModule;
};
}

View File

@ -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<Accounts::ServiceInterface> 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<ModuleAccessInterface> ModuleBuilder::operator()(std::shared_ptr<ModuleDelegateInterface> delegate) {
auto controller = std::make_shared<Controller>(m_accountsService);
auto view = std::make_shared<View>();
auto module = std::make_shared<Module>(delegate, controller, view,
m_onboardingModuleBuilder, m_loginModuleBuilder);
controller->setDelegate(module);
view->setDelegate(module);
return module;
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "ModuleInterface.h"
#include "Onboarding/ModuleBuilder.h"
#include "Login/ModuleBuilder.h"
#include <StatusServices/Accounts/ServiceInterface.h>
#include <memory>
namespace Status::Modules::Startup
{
class ModuleBuilder final
{
public:
ModuleBuilder(std::shared_ptr<Accounts::ServiceInterface> accountsService,
Onboarding::ModuleBuilder onboardingModuleBuilder,
Login::ModuleBuilder loginModuleBuilder);
[[nodiscard]] std::shared_ptr<ModuleAccessInterface> operator()(std::shared_ptr<ModuleDelegateInterface> delegate);
private:
std::shared_ptr<Accounts::ServiceInterface> m_accountsService;
Onboarding::ModuleBuilder m_onboardingModuleBuilder;
Login::ModuleBuilder m_loginModuleBuilder;
};
}

View File

@ -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;
};
}

View File

@ -0,0 +1,72 @@
#include "Controller.h"
#include "../../../Core/GlobalEvents.h"
using namespace Status::Modules::Startup::Onboarding;
Controller::Controller(std::shared_ptr<Accounts::ServiceInterface> accountsService)
: QObject(nullptr)
, m_delegate(nullptr)
, m_accountsService(std::move(accountsService))
{
}
void Controller::setDelegate(std::shared_ptr<ControllerDelegateInterface> 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<Status::Accounts::GeneratedAccountDto>& 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();
}
}

View File

@ -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<Accounts::ServiceInterface> accountsService);
void setDelegate(std::shared_ptr<ControllerDelegateInterface> delegate);
// Controller Interface
void init() override;
const QVector<Accounts::GeneratedAccountDto>& 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<Accounts::ServiceInterface> m_accountsService;
std::shared_ptr<ControllerDelegateInterface> m_delegate;
QString m_selectedAccountId;
};
}

View File

@ -0,0 +1,30 @@
#pragma once
#include <StatusServices/AccountsService>
#include <QtCore>
namespace Status::Modules::Startup::Onboarding
{
class ControllerInterface
{
public:
virtual ~ControllerInterface() = default;
virtual void init() = 0;
virtual const QVector<Accounts::GeneratedAccountDto>& 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;
};
}

View File

@ -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;
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <QtCore>
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;
};
}

View File

@ -0,0 +1,63 @@
#include "Model.h"
using namespace Status::Modules::Startup::Onboarding;
Model::Model(QObject* parent)
: QAbstractListModel(parent)
{
}
QHash<int, QByteArray> Model::roleNames() const
{
QHash<int, QByteArray> 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<Item> items)
{
beginResetModel();
m_items = std::move(items);
endResetModel();
}

View File

@ -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<int, QByteArray> roleNames() const;
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex& index, int role) const;
void setItems(QVector<Item> items);
private:
QVector<Item> m_items;
};
}

View File

@ -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<ModuleDelegateInterface> delegate,
std::shared_ptr<ControllerInterface> controller,
std::shared_ptr<ViewInterface> 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<Accounts::GeneratedAccountDto>& gAcc = m_controller->getGeneratedAccounts();
QVector<Item> 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();
}

View File

@ -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<Module>
{
public:
Module(std::shared_ptr<ModuleDelegateInterface> delegate,
std::shared_ptr<ControllerInterface> controller,
std::shared_ptr<ViewInterface> 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<ModuleDelegateInterface> m_delegate;
std::shared_ptr<ControllerInterface> m_controller;
std::shared_ptr<ViewInterface> m_view;
bool m_moduleLoaded {false};
};
}

View File

@ -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<Accounts::ServiceInterface> accountsService)
: m_accountsService(std::move(accountsService))
{
}
std::shared_ptr<ModuleAccessInterface> ModuleBuilder::operator()(std::shared_ptr<ModuleDelegateInterface> delegate) {
auto controller = std::make_shared<Controller>(m_accountsService);
auto view = std::make_shared<View>();
auto module = std::make_shared<Module>(delegate, controller, view);
controller->setDelegate(module);
view->setDelegate(module);
return module;
}

View File

@ -0,0 +1,21 @@
#pragma once
#include "ModuleInterface.h"
#include <StatusServices/AccountsService>
#include <memory>
namespace Status::Modules::Startup::Onboarding
{
class ModuleBuilder final
{
public:
ModuleBuilder(std::shared_ptr<Accounts::ServiceInterface> accountsService);
[[nodiscard]] std::shared_ptr<ModuleAccessInterface> operator()(std::shared_ptr<ModuleDelegateInterface> delegate);
private:
std::shared_ptr<Accounts::ServiceInterface> m_accountsService;
};
}

View File

@ -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;
};
}

View File

@ -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<ViewDelegateInterface> 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<Item> 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();
}

View File

@ -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<ViewDelegateInterface> delegate);
// View Interface
QObject* getQObject() override;
void load() override;
Model* getModel() override;
void setAccountList(QVector<Item> 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<ViewDelegateInterface> m_delegate;
Model* m_model {nullptr};
};
}

View File

@ -0,0 +1,35 @@
#pragma once
#include "Model.h"
#include <StatusServices/AccountsService>
#include <QtCore>
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<Item> 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;
};
}

View File

@ -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<ViewDelegateInterface> 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<int>(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();
}

View File

@ -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<ViewDelegateInterface> 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<ViewDelegateInterface> m_delegate;
AppState m_appState;
};
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <QtCore>
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;
};
}

View File

@ -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}
)

View File

@ -0,0 +1,26 @@
#pragma once
#include "Types.h"
#include <QtCore>
namespace Backend::Accounts
{
RpcResponse<QJsonArray> generateAddresses(const QVector<QString>& paths);
RpcResponse<QString> generateIdenticon(const QString& publicKey);
RpcResponse<QString> generateAlias(const QString& publicKey);
RpcResponse<QJsonObject> storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
const QVector<QString>& paths);
RpcResponse<QJsonObject> saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account,
const QJsonArray& subaccounts, const QJsonObject& settings,
const QJsonObject& nodeConfig);
RpcResponse<QJsonArray> openAccounts(const QString& path);
RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const QString& hashedPassword,
const QString& identicon, const QString& thumbnail, const QString& large);
}

View File

@ -0,0 +1,37 @@
#pragma once
#include <QtCore>
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 <typename T>
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();
}
};
}

View File

@ -0,0 +1,125 @@
#pragma once
#include "Types.h"
#include "libstatus.h"
#include <QtCore>
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<class T> static QByteArray jsonToByteArray(const T& json){
if constexpr (std::is_same_v<T, QJsonObject> ||
std::is_same_v<T, QJsonArray>)
{
return QJsonDocument(json).toJson(QJsonDocument::Compact);
}
return QByteArray();
}
static QJsonArray toJsonArray(const QVector<QString>& value){
QJsonArray array;
for(auto& v : value)
array << v;
return array;
}
template<class T> 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<T, QJsonObject>)
{
json = jsonDocument.object();
return true;
}
else if constexpr (std::is_same_v<T, QJsonArray>)
{
json = jsonDocument.array();
return true;
}
return false;
}
template<class T> static RpcResponse<T> buildJsonRpcResponse(const T& json) {
auto response = RpcResponse<T>(T());
if constexpr (std::is_same_v<T, QJsonObject>)
{
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<T, QJsonArray>)
{
response.result = json;
}
return response;
}
template<class T> static RpcResponse<T> callPrivateRpc(const QByteArray& payload)
{
try
{
auto result = CallPrivateRPC(const_cast<QByteArray&>(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>(T());
response.error.message = QObject::tr("an error executing rpc call occurred, msg: %1").arg(e.what());
return response;
}
catch (...)
{
auto response = RpcResponse<T>(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());
}
};
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "Types.h"
#include <QtCore>
namespace Backend::Wallet::Accounts
{
RpcResponse<QJsonArray> getAccounts();
RpcResponse<QJsonObject> generateNewAccount(const QString& password, const QString& accountName,
const QString& color);
RpcResponse<QJsonObject> addAccountsFromPrivateKey(const QString& privateKey, const QString& password,
const QString& accountName, const QString& color);
RpcResponse<QJsonObject> addAccountsFromSeed(const QString& seedPhrase, const QString& password,
const QString& accountName, const QString& color);
RpcResponse<QJsonObject> addWatchOnlyAccount(const QString& address, const QString& accountName,
const QString& color);
RpcResponse<QJsonObject> deleteAccount(const QString& address);
}

View File

@ -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<QJsonArray> Accounts::generateAddresses(const QVector<QString>& 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>(QJsonArray());
response.error.message = QObject::tr("an error generating address occurred, msg: %1").arg(e.what());
return response;
}
catch (...)
{
auto response = RpcResponse<QJsonArray>(QJsonArray());
response.error.message = QObject::tr("an error generating address occurred");
return response;
}
}
RpcResponse<QString> 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>(QString());
response.error.message = QObject::tr("an error generating identicon occurred");
return response;
}
}
RpcResponse<QString> 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>(QString());
response.error.message = QObject::tr("an error generating alias occurred");
return response;
}
}
RpcResponse<QJsonObject> Accounts::storeDerivedAccounts(const QString& id, const QString& hashedPassword,
const QVector<QString>& 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>(QJsonObject());
response.error.message = QObject::tr("an error storing derived accounts occurred, msg: %1").arg(e.what());
return response;
}
catch (...)
{
auto response = RpcResponse<QJsonObject>(QJsonObject());
response.error.message = QObject::tr("an error storing derived accounts occurred");
return response;
}
}
RpcResponse<QJsonObject> 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>(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>(QJsonObject());
response.error.message = QObject::tr("an error saving account and login occurred");
return response;
}
}
Backend::RpcResponse<QJsonArray> 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>(QJsonArray());
response.error.message = QObject::tr("an error opening accounts occurred, msg: %1").arg(e.what());
return response;
}
catch (...)
{
auto response = RpcResponse<QJsonArray>(QJsonArray());
response.error.message = QObject::tr("an error opening accounts occurred");
return response;
}
}
RpcResponse<QJsonObject> 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>(QJsonObject());
response.error.message = QObject::tr("an error logining in account occurred, msg: %1").arg(e.what());
return response;
}
catch (...)
{
auto response = RpcResponse<QJsonObject>(QJsonObject());
response.error.message = QObject::tr("an error logining in account occurred");
return response;
}
}

View File

@ -0,0 +1,106 @@
#include "StatusBackend/WalletAccounts.h"
#include "StatusBackend/Utils.h"
using namespace Backend;
RpcResponse<QJsonArray> Wallet::Accounts::getAccounts()
{
QJsonObject payload{
{"jsonrpc", "2.0"},
{"method", "accounts_getAccounts"},
{"params", QJsonValue()}
};
return Utils::callPrivateRpc<QJsonArray>(Utils::jsonToByteArray(std::move(payload)));
}
RpcResponse<QJsonObject> 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<QJsonObject>(Utils::jsonToByteArray(std::move(payload)));
}
RpcResponse<QJsonObject> 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<QJsonObject>(Utils::jsonToByteArray(std::move(payload)));
}
RpcResponse<QJsonObject> 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<QJsonObject>(Utils::jsonToByteArray(std::move(payload)));
}
RpcResponse<QJsonObject> 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<QJsonObject>(Utils::jsonToByteArray(std::move(payload)));
}
RpcResponse<QJsonObject> Wallet::Accounts::deleteAccount(const QString& address)
{
QJsonArray params = {
address
};
QJsonObject payload {
{"jsonrpc", "2.0"},
{"method", "accounts_deleteAccount"},
{"params", params}
};
return Utils::callPrivateRpc<QJsonObject>(Utils::jsonToByteArray(std::move(payload)));
}

View File

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
add_subdirectory(App)
add_subdirectory(Services)
add_subdirectory(Backend)
add_subdirectory(ServicesTest)

View File

@ -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}")

View File

@ -0,0 +1,86 @@
#pragma once
#include <StatusServices/CommonService>
#include <QtCore>
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<Image> 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;
}
};
}

View File

@ -0,0 +1,116 @@
#pragma once
#include <StatusServices/CommonService>
#include <QtCore>
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;
}
};
}

View File

@ -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<AccountDto> openedAccounts() override;
[[nodiscard]] const QVector<GeneratedAccountDto>& 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<QString>& 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<GeneratedAccountDto> m_generatedAccounts;
QString m_statusgoDataDir;
bool m_isFirstTimeAccountLogin;
AccountDto m_loggedInAccount;
GeneratedAccountDto m_importedAccount;
};
}

View File

@ -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<AccountDto> openedAccounts() = 0;
[[nodiscard]] virtual const QVector<GeneratedAccountDto>& 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;
};
}

Some files were not shown because too many files have changed in this diff Show More