feat: barebones auth implementation
This commit is contained in:
@@ -3,11 +3,13 @@ cmake_minimum_required(VERSION 3.16)
|
||||
project(llink VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS
|
||||
Core
|
||||
Gui
|
||||
Widgets
|
||||
Network
|
||||
)
|
||||
|
||||
qt_standard_project_setup(REQUIRES 6.8)
|
||||
@@ -19,6 +21,10 @@ qt_add_executable(llink
|
||||
src/VerticalLabel.cpp
|
||||
src/VerticalLabel.h
|
||||
src/mainwindow.ui
|
||||
src/authmanager.cpp
|
||||
src/networkmanager.cpp
|
||||
src/authdialog.ui
|
||||
src/authdialog.cpp
|
||||
)
|
||||
|
||||
qt_add_resources(llink
|
||||
@@ -26,6 +32,7 @@ qt_add_resources(llink
|
||||
FILES
|
||||
assets/icons/mono/basic/menu.svg
|
||||
assets/icons/mono/basic/plus.svg
|
||||
assets/FLOWY-4.png
|
||||
assets/logo.png
|
||||
styles/remarkable.qss
|
||||
)
|
||||
@@ -46,6 +53,7 @@ target_link_libraries(llink
|
||||
Qt6::Core
|
||||
Qt6::Gui
|
||||
Qt6::Widgets
|
||||
Qt6::Network
|
||||
)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
+34
-29
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE QtCreatorProject>
|
||||
<!-- Written by QtCreator 16.0.2, 2026-01-24T08:42:11. -->
|
||||
<!-- Written by QtCreator 16.0.2, 2026-01-27T10:36:09. -->
|
||||
<qtcreator>
|
||||
<data>
|
||||
<variable>EnvironmentId</variable>
|
||||
@@ -86,6 +86,9 @@
|
||||
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
|
||||
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
|
||||
</valuemap>
|
||||
<valuemap type="QVariantMap" key="CppEditor.QuickFix">
|
||||
<value type="bool" key="UseGlobalSettings">true</value>
|
||||
</valuemap>
|
||||
</valuemap>
|
||||
</data>
|
||||
<data>
|
||||
@@ -103,15 +106,15 @@
|
||||
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
||||
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
|
||||
-DCMAKE_BUILD_TYPE:STRING=Debug
|
||||
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
||||
-DCMAKE_GENERATOR:STRING=Ninja
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
||||
-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}</value>
|
||||
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
|
||||
<value type="int" key="EnableQmlDebugging">0</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Users/talksik/Documents/code/llink/build/Qt_6_8_6_for_macOS-Debug</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
@@ -161,15 +164,15 @@
|
||||
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
||||
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
|
||||
-DCMAKE_BUILD_TYPE:STRING=Release
|
||||
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
||||
-DCMAKE_GENERATOR:STRING=Ninja
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
||||
-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}</value>
|
||||
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Users/talksik/Documents/code/llink/build/Qt_6_8_6_for_macOS-Release</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
@@ -216,15 +219,15 @@
|
||||
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
||||
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
|
||||
-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo
|
||||
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
||||
-DCMAKE_GENERATOR:STRING=Ninja
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
||||
-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}</value>
|
||||
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Users/talksik/Documents/code/llink/build/Qt_6_8_6_for_macOS-RelWithDebInfo</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
@@ -271,15 +274,15 @@
|
||||
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
||||
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
|
||||
-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo
|
||||
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
||||
-DCMAKE_GENERATOR:STRING=Ninja
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
||||
-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}</value>
|
||||
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
|
||||
<value type="int" key="EnableQmlDebugging">0</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Users/talksik/Documents/code/llink/build/Qt_6_8_6_for_macOS-Profile</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
@@ -327,15 +330,15 @@
|
||||
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
|
||||
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
|
||||
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
|
||||
-DCMAKE_BUILD_TYPE:STRING=MinSizeRel
|
||||
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
|
||||
-DCMAKE_GENERATOR:STRING=Ninja
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
|
||||
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
|
||||
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
|
||||
-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG}
|
||||
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}</value>
|
||||
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
|
||||
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Users/talksik/Documents/code/llink/build/Qt_6_8_6_for_macOS-MinSizeRel</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
@@ -435,12 +438,14 @@
|
||||
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
|
||||
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
|
||||
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
|
||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">llink</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeRunConfiguration.</value>
|
||||
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">llink</value>
|
||||
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
|
||||
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
|
||||
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/Users/talksik/Documents/code/llink/build/Qt_6_8_6_for_macOS-Debug/llink.app/Contents/MacOS</value>
|
||||
</valuemap>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
|
||||
</valuemap>
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
@@ -1,18 +1,22 @@
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include "src/MainWindow.h"
|
||||
#include <QCoreApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
qDebug() << "hello world";
|
||||
QCoreApplication::setOrganizationName("Flowy Labs, Inc");
|
||||
QCoreApplication::setOrganizationDomain("flowylabs.ai");
|
||||
QCoreApplication::setApplicationName("llink");
|
||||
|
||||
QApplication a(argc, argv);
|
||||
|
||||
// Load reMarkable e-ink stylesheet
|
||||
QFile styleFile(":/styles/remarkable.qss");
|
||||
if (styleFile.open(QFile::ReadOnly | QFile::Text)) {
|
||||
a.setStyleSheet(styleFile.readAll());
|
||||
styleFile.close();
|
||||
}
|
||||
// QFile styleFile(":/styles/remarkable.qss");
|
||||
// if (styleFile.open(QFile::ReadOnly | QFile::Text)) {
|
||||
// a.setStyleSheet(styleFile.readAll());
|
||||
// styleFile.close();
|
||||
// }
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
+47
-34
@@ -3,55 +3,68 @@
|
||||
//
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QMenu>
|
||||
#include <QApplication>
|
||||
#include "ui_mainwindow.h"
|
||||
#include <QApplication>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QSystemTrayIcon>
|
||||
#include "authmanager.h"
|
||||
#include "authdialog.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), m_trayIcon(nullptr)
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent), m_trayIcon(nullptr), m_authManager(new AuthManager(this)), m_authDialog(nullptr)
|
||||
{
|
||||
ui = new Ui::MainWindow;
|
||||
ui->setupUi(this);
|
||||
QWidget *widget = new QWidget(this);
|
||||
ui->tabWidget->addTab(widget, "Flowy Labs, Inc");
|
||||
m_authDialog = new AuthDialog(m_authManager, this);
|
||||
|
||||
connect(ui->pushButton, &QPushButton::clicked, this, [this]() {
|
||||
qDebug() << "Button clicked for signing on";
|
||||
// TODO: this would make some other calls, and then
|
||||
// we listen to another signal from some service/underlying model
|
||||
// when auth state changes
|
||||
// And then we can hide the UI or change the stackview
|
||||
ui->tabWidget->setCurrentIndex(1);
|
||||
ui = new Ui::MainWindow;
|
||||
ui->setupUi(this);
|
||||
|
||||
// we can call some other method to initialize some data whenever we are done logging in
|
||||
// the complexity of this flow in qml is similar but just less imperative, more declarative
|
||||
// managing the ui is technically more work, but not once we get used to it, perhaps.
|
||||
});
|
||||
setupTrayIcon();
|
||||
// Show auth dialog initially (user sees this while init() checks for session)
|
||||
m_authDialog->setModal(true);
|
||||
m_authDialog->reset();
|
||||
m_authDialog->show();
|
||||
|
||||
// Connect signal so dialog hides if valid session found, shows and resets on sign out
|
||||
connect(m_authManager, &AuthManager::isSignedInChanged, this, [this]() {
|
||||
if (m_authManager->isSignedIn())
|
||||
{
|
||||
m_authDialog->hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_authDialog->reset();
|
||||
m_authDialog->show();
|
||||
}
|
||||
});
|
||||
|
||||
// Check for cached session (async)
|
||||
m_authManager->init();
|
||||
|
||||
setupTrayIcon();
|
||||
}
|
||||
|
||||
void MainWindow::setupTrayIcon()
|
||||
{
|
||||
m_trayIcon = new QSystemTrayIcon(this);
|
||||
m_trayIcon->setIcon(QIcon(":/assets/logo.png"));
|
||||
m_trayIcon->setToolTip("llink");
|
||||
m_trayIcon = new QSystemTrayIcon(this);
|
||||
m_trayIcon->setIcon(QIcon(":/assets/logo.png"));
|
||||
m_trayIcon->setToolTip("llink");
|
||||
|
||||
QMenu *trayMenu = new QMenu(this);
|
||||
trayMenu->addAction("Show", this, &MainWindow::show);
|
||||
trayMenu->addAction("Quit", qApp, &QApplication::quit);
|
||||
m_trayIcon->setContextMenu(trayMenu);
|
||||
QMenu *trayMenu = new QMenu(this);
|
||||
trayMenu->addAction("Show", this, &MainWindow::show);
|
||||
trayMenu->addAction("Quit", qApp, &QApplication::quit);
|
||||
m_trayIcon->setContextMenu(trayMenu);
|
||||
|
||||
connect(m_trayIcon, &QSystemTrayIcon::activated, this, [this](QSystemTrayIcon::ActivationReason reason) {
|
||||
if (reason == QSystemTrayIcon::Trigger) {
|
||||
isVisible() ? hide() : show();
|
||||
}
|
||||
});
|
||||
connect(m_trayIcon, &QSystemTrayIcon::activated, this, [this](QSystemTrayIcon::ActivationReason reason) {
|
||||
if (reason == QSystemTrayIcon::Trigger)
|
||||
{
|
||||
isVisible() ? hide() : show();
|
||||
}
|
||||
});
|
||||
|
||||
m_trayIcon->show();
|
||||
m_trayIcon->show();
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete ui;
|
||||
delete ui;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QSystemTrayIcon;
|
||||
class AuthManager;
|
||||
class AuthDialog;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Ui {
|
||||
@@ -29,6 +31,9 @@ private:
|
||||
|
||||
QSystemTrayIcon *m_trayIcon;
|
||||
Ui::MainWindow *ui;
|
||||
|
||||
AuthManager *m_authManager;
|
||||
AuthDialog *m_authDialog;
|
||||
};
|
||||
|
||||
#endif //LLINK_MAINWINDOW_H
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
#include "authdialog.h"
|
||||
#include "authmanager.h"
|
||||
|
||||
AuthDialog::AuthDialog(AuthManager *authManager, QWidget *parent)
|
||||
: QDialog{parent}, m_authManager(authManager), m_state(ENTER_EMAIL)
|
||||
{
|
||||
ui = new Ui::Dialog;
|
||||
ui->setupUi(this);
|
||||
|
||||
// initialize form based on current state
|
||||
updateForm();
|
||||
|
||||
connect(ui->sendCodeButton, &QPushButton::clicked, this, [this]() {
|
||||
QString emailInput = ui->emailLineEdit->text();
|
||||
if (emailInput.isEmpty())
|
||||
{
|
||||
ui->errorMessage->setText("Must provide a valid email");
|
||||
return;
|
||||
}
|
||||
|
||||
ui->errorMessage->setText("");
|
||||
|
||||
m_authManager->requestSignInCode(emailInput);
|
||||
});
|
||||
|
||||
connect(ui->signInButton, &QPushButton::clicked, this, [this]() {
|
||||
QString codeInput = ui->codeLineEdit->text();
|
||||
if (codeInput.isEmpty())
|
||||
{
|
||||
ui->errorMessage->setText("Must provide a valid code");
|
||||
return;
|
||||
}
|
||||
|
||||
ui->errorMessage->setText("");
|
||||
|
||||
m_authManager->signIn(codeInput);
|
||||
});
|
||||
|
||||
connect(m_authManager, &AuthManager::codeSentToEmail, this, [this](const QString &email) {
|
||||
m_state = ENTER_CODE;
|
||||
updateForm();
|
||||
});
|
||||
|
||||
connect(m_authManager, &AuthManager::isSignedInChanged, this, [this]() {
|
||||
if (m_authManager->isSignedIn())
|
||||
{
|
||||
m_state = SIGNED_IN;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_state = ENTER_EMAIL;
|
||||
}
|
||||
|
||||
updateForm();
|
||||
});
|
||||
|
||||
connect(m_authManager, &AuthManager::errorOccurred, this, [this](const QString &message) {
|
||||
ui->errorMessage->setText(message);
|
||||
ui->errorMessage->show();
|
||||
});
|
||||
}
|
||||
|
||||
void AuthDialog::reset()
|
||||
{
|
||||
m_state = ENTER_EMAIL;
|
||||
ui->emailLineEdit->clear();
|
||||
ui->codeLineEdit->clear();
|
||||
updateForm();
|
||||
}
|
||||
|
||||
void AuthDialog::updateForm()
|
||||
{
|
||||
ui->errorMessage->setText("");
|
||||
|
||||
switch (m_state)
|
||||
{
|
||||
case ENTER_EMAIL:
|
||||
ui->successLabel->hide();
|
||||
|
||||
ui->sendCodeButton->show();
|
||||
ui->emailLineEdit->show();
|
||||
ui->emailLabel->show();
|
||||
|
||||
ui->signInButton->hide();
|
||||
ui->codeLabel->hide();
|
||||
ui->codeLineEdit->hide();
|
||||
break;
|
||||
case ENTER_CODE:
|
||||
ui->successLabel->hide();
|
||||
|
||||
ui->sendCodeButton->hide();
|
||||
ui->emailLineEdit->hide();
|
||||
ui->emailLabel->hide();
|
||||
|
||||
ui->signInButton->show();
|
||||
ui->codeLabel->show();
|
||||
ui->codeLineEdit->show();
|
||||
|
||||
break;
|
||||
case SIGNED_IN:
|
||||
ui->successLabel->show();
|
||||
|
||||
ui->sendCodeButton->hide();
|
||||
ui->emailLineEdit->hide();
|
||||
ui->emailLabel->hide();
|
||||
ui->signInButton->hide();
|
||||
ui->codeLabel->hide();
|
||||
ui->codeLineEdit->hide();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AuthDialog::~AuthDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef AUTHDIALOG_H
|
||||
#define AUTHDIALOG_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
|
||||
#include "ui_authdialog.h"
|
||||
|
||||
class AuthManager;
|
||||
|
||||
class AuthDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AuthDialog(AuthManager *, QWidget *parent = nullptr);
|
||||
~AuthDialog();
|
||||
|
||||
enum State
|
||||
{
|
||||
ENTER_EMAIL,
|
||||
ENTER_CODE,
|
||||
SIGNED_IN
|
||||
};
|
||||
|
||||
void reset();
|
||||
|
||||
signals:
|
||||
|
||||
private:
|
||||
Ui::Dialog *ui;
|
||||
AuthManager *m_authManager;
|
||||
State m_state;
|
||||
|
||||
void updateForm();
|
||||
};
|
||||
|
||||
#endif // AUTHDIALOG_H
|
||||
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>80</x>
|
||||
<y>30</y>
|
||||
<width>231</width>
|
||||
<height>183</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Sign in</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="emailLabel">
|
||||
<property name="text">
|
||||
<string>Email</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="emailLineEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>IBeamCursor</cursorShape>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="codeLabel">
|
||||
<property name="text">
|
||||
<string>Code</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="codeLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="errorMessage">
|
||||
<property name="text">
|
||||
<string>Error</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="sendCodeButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>230</x>
|
||||
<y>250</y>
|
||||
<width>101</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Send code</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="signInButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>230</x>
|
||||
<y>250</y>
|
||||
<width>101</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Sign in</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="successLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>120</x>
|
||||
<y>120</y>
|
||||
<width>121</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Successful</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,218 @@
|
||||
#include "authmanager.h"
|
||||
#include "networkmanager.h"
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QSettings>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr char key[] = "network/session_token";
|
||||
}
|
||||
|
||||
AuthManager::AuthManager(QObject *parent)
|
||||
: QObject{parent}, m_sessionData(nullptr), m_settings(new QSettings(this)), m_isSignedIn(false), m_lastEmail(""),
|
||||
m_sessionToken("")
|
||||
{
|
||||
connect(&NetworkManager::instance(), &NetworkManager::unauthorizedDetected, this, [this]() {
|
||||
if (!m_isSignedIn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSignedIn(false);
|
||||
if (m_sessionData)
|
||||
{
|
||||
delete m_sessionData;
|
||||
m_sessionData = nullptr;
|
||||
}
|
||||
setSessionToken("");
|
||||
});
|
||||
}
|
||||
|
||||
void AuthManager::init()
|
||||
{
|
||||
QString storageSessionToken = m_settings->value(key).toString();
|
||||
if (storageSessionToken.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
qDebug() << "Storage session token is: " << storageSessionToken;
|
||||
|
||||
// set it here, and unset if it fails...network manager needs to inject into the request
|
||||
setSessionToken(storageSessionToken);
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().get("/auth/me");
|
||||
connect(reply, &QNetworkReply::finished, this, [=]() {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
qDebug() << "Saved session token is invalid";
|
||||
setSessionToken("");
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Saved session token is valid";
|
||||
|
||||
QByteArray responseData = reply->readAll();
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
|
||||
if (jsonDoc.isNull())
|
||||
{
|
||||
qWarning() << "Failed to create a JSON doc.";
|
||||
setSessionToken("");
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
|
||||
if (!m_sessionData)
|
||||
{
|
||||
m_sessionData = new SessionData{};
|
||||
}
|
||||
m_sessionData->id = jsonObj["id"].toString();
|
||||
m_sessionData->email = jsonObj["email"].toString();
|
||||
m_sessionData->emailPrefix = jsonObj["email_prefix"].toString();
|
||||
|
||||
setSessionToken(storageSessionToken);
|
||||
|
||||
setIsSignedIn(true);
|
||||
});
|
||||
}
|
||||
|
||||
void AuthManager::setIsSignedIn(bool newValue)
|
||||
{
|
||||
if (m_isSignedIn == newValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_isSignedIn = newValue;
|
||||
emit isSignedInChanged();
|
||||
}
|
||||
|
||||
bool AuthManager::isSignedIn() const
|
||||
{
|
||||
return m_isSignedIn;
|
||||
}
|
||||
|
||||
SessionData *AuthManager::sessionData() const
|
||||
{
|
||||
return m_sessionData;
|
||||
}
|
||||
|
||||
void AuthManager::requestSignInCode(const QString &email)
|
||||
{
|
||||
if (isSignedIn())
|
||||
{
|
||||
qWarning() << "Must sign out first";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonDocument body;
|
||||
QJsonObject object;
|
||||
object["email"] = email;
|
||||
body.setObject(object);
|
||||
QNetworkReply *reply = NetworkManager::instance().post("/auth/request-code", body);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [=]() {
|
||||
reply->deleteLater();
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
emit errorOccurred("Unable to send code. Please try again.");
|
||||
qDebug() << "Error in emailing auth code: " << reply->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Code successfully sent";
|
||||
m_lastEmail = email;
|
||||
emit codeSentToEmail(email);
|
||||
});
|
||||
}
|
||||
|
||||
void AuthManager::signIn(const QString &code)
|
||||
{
|
||||
if (isSignedIn())
|
||||
{
|
||||
qWarning() << "Must sign out first";
|
||||
return;
|
||||
}
|
||||
assert(!m_lastEmail.isEmpty());
|
||||
|
||||
QJsonDocument body;
|
||||
QJsonObject object;
|
||||
object["email"] = m_lastEmail;
|
||||
object["code"] = code;
|
||||
body.setObject(object);
|
||||
QNetworkReply *reply = NetworkManager::instance().post("/auth/sign-in", body);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [=]() {
|
||||
reply->deleteLater();
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
emit errorOccurred("Unable to verify code. Please try again.");
|
||||
qDebug() << "Error in verify code: " << reply->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Session successfully created";
|
||||
QByteArray responseData = reply->readAll();
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
|
||||
if (jsonDoc.isNull())
|
||||
{
|
||||
qWarning() << "Failed to create a JSON doc.";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
|
||||
if (!m_sessionData)
|
||||
{
|
||||
m_sessionData = new SessionData{};
|
||||
}
|
||||
QJsonObject human = jsonObj["human"].toObject();
|
||||
m_sessionData->id = human["id"].toString();
|
||||
m_sessionData->email = human["email"].toString();
|
||||
m_sessionData->emailPrefix = human["email_prefix"].toString();
|
||||
|
||||
setSessionToken(jsonObj["token"].toString());
|
||||
|
||||
setIsSignedIn(true);
|
||||
});
|
||||
}
|
||||
|
||||
void AuthManager::signOut()
|
||||
{
|
||||
if (!isSignedIn())
|
||||
{
|
||||
qWarning() << "Not signed in, skipping sign out";
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSignedIn(false);
|
||||
if (m_sessionData)
|
||||
{
|
||||
delete m_sessionData;
|
||||
m_sessionData = nullptr;
|
||||
}
|
||||
setSessionToken("");
|
||||
|
||||
QNetworkReply *reply = NetworkManager::instance().post("/auth/sign-out", QJsonDocument());
|
||||
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
|
||||
}
|
||||
|
||||
void AuthManager::setSessionToken(const QString &newValue)
|
||||
{
|
||||
if (newValue.isEmpty())
|
||||
{
|
||||
m_settings->remove(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings->setValue(key, newValue);
|
||||
}
|
||||
|
||||
m_sessionToken = newValue;
|
||||
NetworkManager::instance().setSessionToken(newValue);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#ifndef AUTHMANAGER_H
|
||||
#define AUTHMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QSettings;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
struct SessionData
|
||||
{
|
||||
QString id;
|
||||
QString email;
|
||||
QString emailPrefix;
|
||||
};
|
||||
|
||||
class AuthManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AuthManager(QObject *parent = nullptr);
|
||||
|
||||
bool isSignedIn() const;
|
||||
|
||||
/// @brief If isSignedIn() this will contain the human's session data.
|
||||
/// @returns Session data for the authed human. nullptr if not signed in.
|
||||
SessionData *sessionData() const;
|
||||
|
||||
public slots:
|
||||
/// @brief Sends a code to the requested email.
|
||||
///
|
||||
/// You must verify the code in the signIn method to receive a session.
|
||||
///
|
||||
/// @params email to send code to.
|
||||
void requestSignInCode(const QString &email);
|
||||
|
||||
/// @brief Creates a session if authenticated.
|
||||
///
|
||||
/// @params code that was sent to the email in the previous step
|
||||
void signIn(const QString &code);
|
||||
|
||||
void signOut();
|
||||
|
||||
/// @brief checks if there is a cached session that is valid
|
||||
void init();
|
||||
|
||||
signals:
|
||||
void isSignedInChanged();
|
||||
void codeSentToEmail(const QString &email);
|
||||
|
||||
void errorOccurred(const QString &message);
|
||||
|
||||
private:
|
||||
QString m_lastEmail;
|
||||
|
||||
bool m_isSignedIn;
|
||||
void setIsSignedIn(bool);
|
||||
|
||||
SessionData *m_sessionData;
|
||||
|
||||
QSettings *m_settings;
|
||||
QString m_sessionToken;
|
||||
|
||||
/// Sets session token internally to class, in storage, and sets networkmanager appropriately
|
||||
/// Pass "" if you want to remove it.
|
||||
void setSessionToken(const QString &);
|
||||
};
|
||||
|
||||
#endif // AUTHMANAGER_H
|
||||
+44
-112
@@ -14,122 +14,52 @@
|
||||
<string>Flowy.llink</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>801</width>
|
||||
<height>541</height>
|
||||
<x>360</x>
|
||||
<y>20</y>
|
||||
<width>61</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../build/Qt_6_8_6_for_macOS-Debug/.qt/rcc/PREFIX_PATH.qrc">:/assets/FLOWY-4.png</pixmap>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>300</x>
|
||||
<y>250</y>
|
||||
<width>181</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Welcome to Flowy.llink</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab_1">
|
||||
<attribute name="title">
|
||||
<string>Sign in</string>
|
||||
</attribute>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>250</x>
|
||||
<y>150</y>
|
||||
<width>291</width>
|
||||
<height>151</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<widget class="QLineEdit" name="lineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>30</y>
|
||||
<width>251</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="lineEdit_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>80</y>
|
||||
<width>71</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>10</y>
|
||||
<width>58</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Email</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>60</y>
|
||||
<width>58</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Code</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>110</y>
|
||||
<width>101</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Continue</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>App</string>
|
||||
</attribute>
|
||||
<widget class="QCalendarWidget" name="calendarWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>110</x>
|
||||
<y>40</y>
|
||||
<width>551</width>
|
||||
<height>401</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar">
|
||||
@@ -141,6 +71,8 @@
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<resources>
|
||||
<include location="../build/Qt_6_8_6_for_macOS-Debug/.qt/rcc/PREFIX_PATH.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "networkmanager.h"
|
||||
#include <QJsonDocument>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr char API_URL[] = "https://orion.dev.flowy.live";
|
||||
}
|
||||
|
||||
NetworkManager::NetworkManager()
|
||||
{
|
||||
m_qnam = new QNetworkAccessManager(this);
|
||||
connect(m_qnam, &QNetworkAccessManager::finished, this, [this](QNetworkReply *reply) {
|
||||
// In case particular instances of QNetworkReply are not cleaned up.
|
||||
// It's safe to be redundant with deleteLater
|
||||
// Calling deleteLater here also doesn't disrupt QNetworkReply::finished slots, as this both here
|
||||
// and in those slots are fired in tandem and deleteLater waits for return to event loop to perform deleteLater
|
||||
reply->deleteLater();
|
||||
int statusCode = reply->attribute(QNetworkRequest::Attribute::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << "Network request status code: " << statusCode;
|
||||
if (statusCode == 401)
|
||||
{
|
||||
qWarning() << "Unauthorized request: " << reply->errorString();
|
||||
emit unauthorizedDetected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
NetworkManager::~NetworkManager()
|
||||
{
|
||||
}
|
||||
|
||||
NetworkManager &NetworkManager::instance()
|
||||
{
|
||||
static NetworkManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void NetworkManager::setAuthHeader(QNetworkRequest &request)
|
||||
{
|
||||
if (m_sessionToken.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
QString headerValue = QString("Bearer %1").arg(m_sessionToken);
|
||||
request.setRawHeader("Authorization", headerValue.toUtf8());
|
||||
}
|
||||
|
||||
QString NetworkManager::getFullUrl(const QString &url)
|
||||
{
|
||||
return QString("%1%2").arg(API_URL, url);
|
||||
}
|
||||
|
||||
QNetworkReply *NetworkManager::get(const QString &path)
|
||||
{
|
||||
QNetworkRequest req = QNetworkRequest(getFullUrl(path));
|
||||
setAuthHeader(req);
|
||||
return m_qnam->get(req);
|
||||
}
|
||||
|
||||
QNetworkReply *NetworkManager::post(const QString &path, const QJsonDocument &doc)
|
||||
{
|
||||
QNetworkRequest request = QNetworkRequest(getFullUrl(path));
|
||||
setAuthHeader(request);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
return m_qnam->post(request, doc.toJson());
|
||||
}
|
||||
|
||||
QString NetworkManager::sessionToken() const
|
||||
{
|
||||
return m_sessionToken;
|
||||
}
|
||||
|
||||
void NetworkManager::setSessionToken(const QString &newToken)
|
||||
{
|
||||
m_sessionToken = newToken;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef NETWORKMANAGER_H
|
||||
#define NETWORKMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QNetworkReply;
|
||||
class QNetworkRequest;
|
||||
class QNetworkAccessManager;
|
||||
class QJsonDocument;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class NetworkManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static NetworkManager &instance();
|
||||
|
||||
// Delete copy constructor and assignment
|
||||
NetworkManager(const NetworkManager &) = delete;
|
||||
void operator=(const NetworkManager &) = delete;
|
||||
|
||||
QString sessionToken() const;
|
||||
|
||||
public slots:
|
||||
// Convenience methods that inject session token.
|
||||
// Network manager discard replies internally, in cases where a consumer callsite prefers to not manage QNetworkReply.
|
||||
QNetworkReply *get(const QString &path);
|
||||
QNetworkReply *post(const QString &path, const QJsonDocument &data);
|
||||
// QNetworkReply *put(QNetworkRequest &request);
|
||||
// QNetworkReply *del(QNetworkRequest &request);
|
||||
|
||||
/// @brief sets session token which will is injected into every request's headers
|
||||
void setSessionToken(const QString &token);
|
||||
|
||||
signals:
|
||||
/// @brief Emitted when a request is made and fails required authentication.
|
||||
void unauthorizedDetected();
|
||||
|
||||
private:
|
||||
NetworkManager();
|
||||
~NetworkManager();
|
||||
|
||||
QString m_sessionToken;
|
||||
QNetworkAccessManager *m_qnam;
|
||||
|
||||
void setAuthHeader(QNetworkRequest &request);
|
||||
/// @brief Combines the api url with given path
|
||||
/// @param path should be like "/auth/me"
|
||||
QString getFullUrl(const QString &path);
|
||||
};
|
||||
|
||||
#endif // NETWORKMANAGER_H
|
||||
@@ -90,7 +90,7 @@ QLineEdit {
|
||||
border: 1px solid #D4D4D4;
|
||||
border-bottom: 1px solid #1A1A1A;
|
||||
border-radius: 0px;
|
||||
padding: 8px;
|
||||
|
||||
selection-background-color: #1A1A1A;
|
||||
selection-color: #FAFAF8;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user