diff --git a/CMakeLists.txt b/CMakeLists.txt index 23bfdea..5a834fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,19 +2,35 @@ cmake_minimum_required(VERSION 3.0) project(autocat_gnome) set(CMAKE_CXX_STANDARD 20) +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -g -Og") -#if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") -# add_compile_options(-stdlib=libc++) -#endif() - -find_package(PkgConfig) +find_package(PkgConfig REQUIRED) +find_package(nlohmann_json REQUIRED) +find_package(Boost 1.70 COMPONENTS thread REQUIRED) pkg_check_modules(GTKMM REQUIRED gtkmm-4.0) pkg_check_modules(GLIBMM REQUIRED glibmm-2.68) pkg_check_modules(LIBSOUP REQUIRED libsoup-2.4) +pkg_check_modules(LIBADWAITA REQUIRED libadwaita-1) -include_directories(${GTKMM_INCLUDE_DIRS} ${GLIBMM_INCLUDE_DIRS} ${LIBSOUP_INCLUDE_DIRS}) -link_directories(${GTKMM_LIBRARY_DIRS} ${GLIBMM_LIBRARY_DIRS} ${LIBSOUP_LIBRARY_DIRS}) +include_directories(${GTKMM_INCLUDE_DIRS} + ${GLIBMM_INCLUDE_DIRS} + ${LIBSOUP_INCLUDE_DIRS} + ${Boost_INCLUDE_DIR} + ${LIBADWAITA_INCLUDE_DIRS}) -add_executable(autocat_gnome main.cpp gui/MainWindow.cpp gui/MainWindow.h gui/LoginWindow.cpp gui/LoginWindow.h services/Api.cpp services/Api.h coro/Task.h models/User.cpp models/User.h) -target_link_libraries(autocat_gnome ${GTKMM_LIBRARIES} ${GLIBMM_LIBRARIES} ${LIBSOUP_LIBRARIES}) +link_directories(${GTKMM_LIBRARY_DIRS} + ${GLIBMM_LIBRARY_DIRS} + ${LIBSOUP_LIBRARY_DIRS} + ${Boost_LIBRARY_DIRS} + ${LIBADWAITA_LIBRARY_DIRS}) + +add_definitions(-DBOOST_THREAD_PROVIDES_FUTURE -DBOOST_THREAD_PROVIDES_FUTURE_CONTINUATION -DBOOST_THREAD_PROVIDES_PROMISE_LAZY -DBOOST_THREAD_VERSION=4) + +add_executable(autocat_gnome main.cpp gui/MainWindow.cpp gui/MainWindow.h gui/LoginWindow.cpp gui/LoginWindow.h services/Api.cpp services/Api.h coro/Task.h models/User.cpp models/User.h services/Settings.cpp services/Settings.h app/App.cpp app/App.h gui/TitleBar.cpp gui/TitleBar.h) +target_link_libraries(autocat_gnome ${GTKMM_LIBRARIES} + ${GLIBMM_LIBRARIES} + ${LIBSOUP_LIBRARIES} + nlohmann_json::nlohmann_json + ${Boost_LIBRARIES} + ${LIBADWAITA_LIBRARIES}) diff --git a/app/App.cpp b/app/App.cpp new file mode 100644 index 0000000..f60a34a --- /dev/null +++ b/app/App.cpp @@ -0,0 +1,48 @@ +// +// Created by selim on 02.02.2022. +// + +#include "App.h" +#include "../gui/MainWindow.h" + +static void activateApp(GtkApplication *gtkApp, void* data) { + auto app = reinterpret_cast(data); + + int i = 0; + + auto win = std::make_shared(); + app->setWindow(win); + +// GtkWidget *window = gtk_application_window_new (gtkApp); +// GtkWidget *label = gtk_label_new ("Hello World"); +// +// gtk_window_set_title (GTK_WINDOW (window), "Hello"); +// gtk_window_set_default_size (GTK_WINDOW (window), 200, 200); +// gtk_window_set_child (GTK_WINDOW (window), label); +// gtk_window_present (GTK_WINDOW (window)); +} + +App::App(): _app(nullptr), _window(nullptr) { + _app = adw_application_new("pro.aliencat.aliencat", G_APPLICATION_FLAGS_NONE); + g_signal_connect_data(_app, "activate", G_CALLBACK(activateApp), this, nullptr, G_CONNECT_AFTER); +} + +App::~App() { + glib_autoptr_cleanup_AdwApplication(&_app); +} + +int App::run(int argc, char **argv) { + return g_application_run(G_APPLICATION(_app), argc, argv); +} + +void App::setWindow(std::shared_ptr window) { + gtk_application_add_window(GTK_APPLICATION(_app), window->gobj()); + + if(_window) { + _window->hide(); + gtk_application_remove_window(GTK_APPLICATION(_app), _window->gobj()); + } + + window->show(); + _window = window; +} diff --git a/app/App.h b/app/App.h new file mode 100644 index 0000000..7846f25 --- /dev/null +++ b/app/App.h @@ -0,0 +1,25 @@ +// +// Created by selim on 02.02.2022. +// + +#ifndef AUTOCAT_GNOME_APP_H +#define AUTOCAT_GNOME_APP_H + +#include +#include +#include + +class App { +private: + AdwApplication* _app; + std::shared_ptr _window; + +public: + explicit App(); + ~App(); + int run(int argc, char** argv); + void setWindow(std::shared_ptr window); +}; + + +#endif //AUTOCAT_GNOME_APP_H diff --git a/coro/Task.h b/coro/Task.h index 799a09a..428b127 100644 --- a/coro/Task.h +++ b/coro/Task.h @@ -8,94 +8,140 @@ #include #include #include +#include + +#include +#include +#include +#include + +class FutureException: public virtual std::exception, public virtual boost::exception { +private: + std::string _message; -template -class Task { public: + explicit FutureException(const char* msg): _message(msg) {} + explicit FutureException(std::string msg): _message(std::move(msg)) {} + + const char* what() const noexcept { + return _message.c_str(); + } +}; + +// Enable the use of std::future as a coroutine type +// by using a std::promise as the promise type. +template +requires(!std::is_void_v && !std::is_reference_v) +struct std::coroutine_traits, Args...> { + struct promise_type : boost::promise { + boost::future get_return_object() noexcept { + return this->get_future(); + } + + std::suspend_never initial_suspend() const noexcept { + return {}; + } + std::suspend_never final_suspend() const noexcept { + return {}; + } + + void return_value(const T &value) + noexcept(std::is_nothrow_copy_constructible_v) { + this->set_value(value); + } + void return_value(T &&value) + noexcept(std::is_nothrow_move_constructible_v) { + this->set_value(std::move(value)); + } + void unhandled_exception() noexcept { + this->set_exception(boost::current_exception()); + } + }; +}; + +// Same for std::future. +template +struct std::coroutine_traits, Args...> { + struct promise_type : boost::promise { + boost::future get_return_object() noexcept { + return this->get_future(); + } + + std::suspend_never initial_suspend() const noexcept { + return {}; + } + std::suspend_never final_suspend() const noexcept { + return {}; + } + + void return_void() noexcept { + this->set_value(); + } + void unhandled_exception() noexcept { + this->set_exception(boost::current_exception()); + } + }; +}; + +template +struct std::coroutine_traits { struct promise_type { - auto initial_suspend() const noexcept { - std::cout << "initial_suspend" << std::endl; - return std::suspend_never(); + void get_return_object() noexcept {} + + std::suspend_never initial_suspend() const noexcept { + return {}; + } + std::suspend_never final_suspend() const noexcept { + return {}; } - auto final_suspend() const noexcept { - std::cout << "final_suspend" << std::endl; - return std::suspend_never(); + void return_void() noexcept {} + + void unhandled_exception() noexcept { + try { + boost::rethrow_exception(boost::current_exception()); + } catch (std::exception& ex) { + std::cout << "Unhandled exception (in void coroutine) detected: " << ex.what() << std::endl; + } + } + }; +}; + +template +auto operator co_await(boost::future future) noexcept requires(!std::is_reference_v) { + + struct awaiter : public boost::future { + + bool await_ready() const noexcept { + return this->is_ready(); + } + void await_suspend(std::coroutine_handle<> cont) { + this->then([this, cont](boost::future&& fut){ + + // future was moved out of awaiter to this lambda + // so, we need to bring it back + this->swap(fut); + + // Resume coroutine on the main thread (i.e. GMainContext) + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, awaiter::resumeContinuation, (gpointer)&cont, nullptr); + }); } - Task get_return_object() { - std::cout << "get_return_object" << std::endl; - return Task{ std::coroutine_handle::from_promise(*this) }; + T await_resume() { + return this->get(); } - void return_value(const T& value) noexcept(std::is_nothrow_copy_constructible_v) { - std::cout << "return_value: " << value << std::endl; - _value = value; - } + static int resumeContinuation(void* data) { + auto handler = reinterpret_cast*>(data); + if(*handler) { + handler->resume(); + } -// void return_void() { -// std::cout << "return_void" << std::endl; -// } - - void unhandled_exception() { - std::cout << "unhandled_exception" << std::endl; - throw; + return G_SOURCE_REMOVE; } }; -private: - std::optional _value = std::nullopt; - std::coroutine_handle _handle; - -//public: -// bool await_ready() const noexcept { -// std::cout << "Awaiter::await_ready" << std::endl; -// return false; -// } -// -// void await_suspend(std::coroutine_handle handle) const { -// std::cout << "Awaiter::await_suspend" << std::endl; -// _handle = handle; -// } -// -// T await_resume() { -// std::cout << "Awaiter::await_resume" << std::endl; -// return _value.value(); -// } - -public: - void set_result(T result) { - _value = result; - _handle(); - } -}; - -template struct Awaiter { -public: - bool await_ready() const noexcept { - std::cout << "Awaiter::await_ready" << std::endl; - return false; - } - - void await_suspend(std::coroutine_handle::promise_type> cont) const { - std::cout << "Awaiter::await_suspend" << std::endl; - } - - U await_resume() { - std::cout << "Awaiter::await_resume" << std::endl; - return _task.value(); - } - -private: - Task _task; - -public: - explicit Awaiter(Task task): _task(task) {} -}; - -template -auto operator co_await(Task task) noexcept /*requires(!std::is_reference_v)*/ { - return Awaiter(task); + return awaiter{std::move(future)}; } -#endif //AUTOCAT_GNOME_TASK_H +#endif //AUTOCAT_GNOME_TASK_H \ No newline at end of file diff --git a/gui/LoginWindow.cpp b/gui/LoginWindow.cpp index 6d20787..e7591e6 100644 --- a/gui/LoginWindow.cpp +++ b/gui/LoginWindow.cpp @@ -3,8 +3,10 @@ // #include "LoginWindow.h" +#include "MainWindow.h" #include "../services/Api.h" +#include #include #include @@ -17,13 +19,13 @@ LoginWindow::LoginWindow() { _passwordField.set_input_purpose(Gtk::InputPurpose::PASSWORD); _passwordField.set_visibility(false); - _emailField.signal_changed().connect(sigc::mem_fun(*this, &LoginWindow::ValidateFields)); - _passwordField.signal_changed().connect(sigc::mem_fun(*this, &LoginWindow::ValidateFields)); + _emailField.signal_changed().connect(sigc::mem_fun(*this, &LoginWindow::validateFields)); + _passwordField.signal_changed().connect(sigc::mem_fun(*this, &LoginWindow::validateFields)); _loginButton.set_margin_top(8); _loginButton.set_margin_bottom(8); _loginButton.set_label("Log in"); - _loginButton.signal_clicked().connect(sigc::mem_fun(*this, &LoginWindow::LoginClicked)); + _loginButton.signal_clicked().connect(sigc::mem_fun(*this, &LoginWindow::loginClicked)); _loginButton.set_sensitive(false); Gtk::Box box(Gtk::Orientation::VERTICAL, 8); @@ -38,32 +40,48 @@ LoginWindow::LoginWindow() { set_child(box); } -//Task foo() { -// co_return 42; -//} -// -//Task bar() { -// auto xxx = co_await foo(); -// std::cout << "xxx: " << xxx << std::endl; -//} - -void LoginWindow::LoginClicked() { +void LoginWindow::loginClicked() { auto email = _emailField.get_text(); auto password = _passwordField.get_text(); - _loginButton.set_sensitive(false); - _emailField.set_sensitive(false); - _passwordField.set_sensitive(false); + enableControls(false); _spinner.start(); - std::cout << "Login clicked" << std::endl; - std::cout << "Login: " << email << std::endl; - std::cout << "Password: " << password << std::endl; - - User user = co_await Api::login(email, password); + try { + auto user = co_await Api::login(email, password); + auto app = this->get_application(); + auto mainWindow = new MainWindow(); + mainWindow->show(); + hide(); + app->add_window(*mainWindow); + app->remove_window(*this); + } catch (std::exception& ex) { + enableControls(true); + _spinner.stop(); + showError(ex.what()); + } } -void LoginWindow::ValidateFields() { +void LoginWindow::validateFields() { bool buttonEnabled = _emailField.get_text_length() > 0 && _passwordField.get_text_length() > 0; _loginButton.set_sensitive(buttonEnabled); } + +void LoginWindow::showError(const std::string& message) { + _dialog = std::make_unique("Error", + false, + Gtk::MessageType::ERROR, + Gtk::ButtonsType::OK, + true); + _dialog->set_secondary_text(message); + _dialog->set_transient_for(*this); + _dialog->set_hide_on_close(true); + _dialog->signal_response().connect(sigc::hide(sigc::mem_fun(*_dialog, &Gtk::Widget::hide))); + _dialog->show(); +} + +void LoginWindow::enableControls(bool enable) { + _loginButton.set_sensitive(enable); + _emailField.set_sensitive(enable); + _passwordField.set_sensitive(enable); +} diff --git a/gui/LoginWindow.h b/gui/LoginWindow.h index e2b8fd5..e690a3b 100644 --- a/gui/LoginWindow.h +++ b/gui/LoginWindow.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include class LoginWindow: public Gtk::Window { private: @@ -16,13 +18,16 @@ private: Gtk::Entry _passwordField; Gtk::Button _loginButton; Gtk::Spinner _spinner; + std::unique_ptr _dialog; public: LoginWindow(); ~LoginWindow() override = default; - void LoginClicked(); - void ValidateFields(); + void loginClicked(); + void validateFields(); + void showError(const std::string& message); + void enableControls(bool enable); }; diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index 40ce501..1f17714 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -3,8 +3,34 @@ // #include "MainWindow.h" +#include MainWindow::MainWindow() { - set_title("Main window"); + //set_title("Main window"); set_default_size(640, 480); + //set_decorated(false); + + set_titlebar(_titleBar); + + _masterLabel.set_text("master"); + _masterBox.set_orientation(Gtk::Orientation::VERTICAL); + _masterBox.append(_masterLabel); + + _detailLabel.set_text("detail"); + _detailBox.set_orientation(Gtk::Orientation::VERTICAL); + _detailBox.append(_detailLabel); + + _paned.set_start_child(_masterBox); + _paned.set_end_child(_detailBox); + _paned.set_resize_start_child(true); + _paned.set_shrink_end_child(true); + _paned.set_resize_end_child(true); + set_child(_paned); + + _sizeGroup = Gtk::SizeGroup::create(Gtk::SizeGroup::Mode::HORIZONTAL); + _sizeGroup->add_widget(_titleBar.masterHeader()); + _sizeGroup->add_widget(_masterBox); + + _titleBar.masterHeader().set_size_request(50, -1); + _masterBox.set_size_request(50, -1); } diff --git a/gui/MainWindow.h b/gui/MainWindow.h index 183db06..c450b71 100644 --- a/gui/MainWindow.h +++ b/gui/MainWindow.h @@ -5,9 +5,24 @@ #ifndef AUTOCAT_GNOME_MAINWINDOW_H #define AUTOCAT_GNOME_MAINWINDOW_H +#include "TitleBar.h" + #include +#include +#include +#include +#include class MainWindow: public Gtk::Window { +private: + TitleBar _titleBar; + Gtk::Paned _paned; + Gtk::Label _masterLabel; + Gtk::Label _detailLabel; + Gtk::Box _masterBox; + Gtk::Box _detailBox; + Glib::RefPtr _sizeGroup; + public: MainWindow(); }; diff --git a/gui/TitleBar.cpp b/gui/TitleBar.cpp new file mode 100644 index 0000000..334e47f --- /dev/null +++ b/gui/TitleBar.cpp @@ -0,0 +1,21 @@ +// +// Created by selim on 13.02.2022. +// + +#include "TitleBar.h" + +TitleBar::TitleBar() { + set_start_child(_masterHeader); + set_end_child(_detailHeader); + set_resize_start_child(true); + set_shrink_end_child(true); + set_resize_end_child(true); +} + +Gtk::HeaderBar &TitleBar::masterHeader() { + return _masterHeader; +} + +Gtk::HeaderBar &TitleBar::detailHeader() { + return _detailHeader; +} diff --git a/gui/TitleBar.h b/gui/TitleBar.h new file mode 100644 index 0000000..ce67fd5 --- /dev/null +++ b/gui/TitleBar.h @@ -0,0 +1,23 @@ +// +// Created by selim on 13.02.2022. +// + +#ifndef AUTOCAT_GNOME_TITLEBAR_H +#define AUTOCAT_GNOME_TITLEBAR_H + +#include +#include + +class TitleBar: public Gtk::Paned { +private: + Gtk::HeaderBar _masterHeader; + Gtk::HeaderBar _detailHeader; + +public: + TitleBar(); + Gtk::HeaderBar& masterHeader(); + Gtk::HeaderBar& detailHeader(); +}; + + +#endif //AUTOCAT_GNOME_TITLEBAR_H diff --git a/main.cpp b/main.cpp index fad08aa..6e66506 100644 --- a/main.cpp +++ b/main.cpp @@ -1,8 +1,29 @@ #include "gui/MainWindow.h" #include "gui/LoginWindow.h" +#include "services/Settings.h" +#include "app/App.h" #include +#include +#include +#include + +std::unique_ptr createStartWindow() { + auto settings = Settings::instance(); + if(settings.user().token.empty()) { + return std::make_unique(); + } else { + return std::make_unique(); + } +} int main(int argc, char* argv[]) { + auto app = Gtk::Application::create("pro.aliencat.aliencat"); - return app->make_window_and_run(argc, argv); + auto window = createStartWindow(); + + app->signal_activate().connect([&](){ + app->add_window(*window); + window->show(); + }); + return app->run(argc, argv); } diff --git a/models/User.cpp b/models/User.cpp index 3fb4b85..d0f8f1b 100644 --- a/models/User.cpp +++ b/models/User.cpp @@ -4,6 +4,6 @@ #include "User.h" -User::User(std::string_view login, std::string_view token): login(login), token(token) { +User::User(std::string_view email, std::string_view token): email(email), token(token) { } diff --git a/models/User.h b/models/User.h index 31245c1..4b2504e 100644 --- a/models/User.h +++ b/models/User.h @@ -7,16 +7,21 @@ #include #include +#include class User { public: - std::string login; + std::string email; std::string token; std::optional googleIdToken; std::optional googleRefreshToken; public: - User(std::string_view login, std::string_view token); + User() = default; + User(const User& user) = default; + User(std::string_view email, std::string_view token); + + NLOHMANN_DEFINE_TYPE_INTRUSIVE(User, email, token) }; diff --git a/services/Api.cpp b/services/Api.cpp index 2e5cc94..652dd85 100644 --- a/services/Api.cpp +++ b/services/Api.cpp @@ -3,47 +3,76 @@ // #include "Api.h" +#include "Settings.h" #include template -struct callback { +struct Callback { void(*function)(Args..., void*) = nullptr; - std::unique_ptr state; + void* state = nullptr; }; template -callback voidify(Lambda&& l) { +Callback voidify(Lambda&& l) { using Func = typename std::decay::type; - std::unique_ptr data( - new Func(std::forward(l)), - +[](void* ptr){ - std::cout << "!!!!!!!!!!!!!!!!! Deleting data" << std::endl; - delete (Func*)ptr; - } - ); + auto data = new Func(std::forward(l)); return { +[](Args... args, void* v)->void { Func* f = static_cast< Func* >(v); (*f)(std::forward(args)...); + delete f; }, - std::move(data) + data }; } const std::string Api::_baseUrl = "https://vps.aliencat.pro:8443/"; SoupSession* Api::_session = soup_session_new(); -Task Api::login(std::string email, std::string password) { - std::string url = _baseUrl + "user/login"; +template +boost::future Api::post(const std::string &method, const nlohmann::json& params) { + std::string url = _baseUrl + method; auto msg = soup_message_new(SOUP_METHOD_POST, url.c_str()); - auto task = Task(); - - auto callback = voidify([&](SoupSession* session, SoupMessage* message) { - std::cout << "Callback called" << std::endl; - task.set_result(User("qwe", "asdf")); + auto promise = std::make_shared>(); + auto callback = voidify([&, promise](SoupSession* session, SoupMessage* message) { + if(message->status_code >= 200 && message->status_code < 300) { + auto responseString = std::string(message->response_body->data, message->response_body->length); + auto json = nlohmann::json::parse(responseString); + if(json["success"].get()) { + std::cout << "response: " << responseString << std::endl; + auto user = json["data"].get(); + promise->set_value(user); + } else { + auto error = json["error"].get(); + promise->set_exception(FutureException(error)); + } + } else { + promise->set_exception(FutureException(message->reason_phrase)); + } }); - soup_session_queue_message(_session, msg, callback.function, callback.state.get()); - return task; + auto jsonStr = params.dump(); + + soup_message_set_request(msg, "application/json", SOUP_MEMORY_COPY, jsonStr.c_str(), jsonStr.size()); + soup_session_queue_message(_session, msg, callback.function, callback.state); + + return promise->get_future(); +} + +boost::future Api::login(std::string email, std::string password) { + + nlohmann::json params = { + { "email", email }, + { "password", password } + }; + + auto user = Settings::instance().user(); + auto result = co_await post("user/login", params); + + user.email = result.email; + user.token = result.token; + Settings::instance().setUser(user); + + co_return user; } diff --git a/services/Api.h b/services/Api.h index ec17397..4e0befd 100644 --- a/services/Api.h +++ b/services/Api.h @@ -7,6 +7,8 @@ #include #include +#include + #include "../models/User.h" #include "../coro/Task.h" @@ -15,8 +17,12 @@ private: static const std::string _baseUrl; static SoupSession* _session; +private: + template + static boost::future post(const std::string& method, const nlohmann::json& params); + public: - static Task login(std::string email, std::string password); + static boost::future login(std::string email, std::string password); }; diff --git a/services/Settings.cpp b/services/Settings.cpp new file mode 100644 index 0000000..1a4dd9c --- /dev/null +++ b/services/Settings.cpp @@ -0,0 +1,71 @@ +// +// Created by selim on 09.01.2022. +// + +#include "Settings.h" +#include + +#include +#include +#include + +namespace fs = std::filesystem; + +fs::path getDataPath() { + auto dataDir = std::getenv("XDG_DATA_HOME"); + if(dataDir) { + return fs::path(dataDir); + } + + auto home = std::getenv("HOME"); + if(home) { + return fs::path(home) / ".local" / "share"; + } + + throw std::runtime_error("Failed to find data home directory"); +} + +std::string readString(const fs::path& path) { + std::ifstream stream(path); + stream.seekg(0, std::ios::end); + auto size = stream.tellg(); + std::string buffer(size, ' '); + stream.seekg(0); + stream.read(&buffer[0], size); + return buffer; +} + +void writeString(const fs::path& path, const std::string& text) { + std::ofstream stream(path); + stream << text; +} + +Settings Settings::instance() { + static Settings instance; + return instance; +} + +Settings::Settings() { + _dataPath = getDataPath() / "autocat"; + if(!fs::exists(_dataPath)) { + fs::create_directories(_dataPath); + } +} + +User Settings::user() const { + auto userFile = _dataPath / "user.json"; + if(fs::exists(userFile)) { + auto jsonText = readString(userFile); + std::cout << "User json text: " << jsonText << std::endl; + return nlohmann::json::parse(jsonText).get(); + } else { + return {}; + } +} + +void Settings::setUser(const User &user) { + auto userFile = _dataPath / "user.json"; + nlohmann::json json = user; + auto userString = json.dump(4); + writeString(userFile, userString); +} diff --git a/services/Settings.h b/services/Settings.h new file mode 100644 index 0000000..f0f743d --- /dev/null +++ b/services/Settings.h @@ -0,0 +1,24 @@ +// +// Created by selim on 09.01.2022. +// + +#ifndef AUTOCAT_GNOME_SETTINGS_H +#define AUTOCAT_GNOME_SETTINGS_H + +#include "../models/User.h" +#include + +class Settings { +private: + std::filesystem::path _dataPath; + +public: + static Settings instance(); + Settings(); + + [[nodiscard]] User user() const; + void setUser(const User& user); +}; + + +#endif //AUTOCAT_GNOME_SETTINGS_H