some more thoughts about state management

This commit is contained in:
Blue 2023-12-26 20:31:55 -03:00
parent 437e76067f
commit b38ed2107b
Signed by: blue
GPG Key ID: 9B203B252A63EE38
13 changed files with 125 additions and 13 deletions

View File

@ -1,11 +1,13 @@
set(HEADERS set(HEADERS
api.h api.h
codes.h codes.h
finalaction.h
) )
set(SOURCES set(SOURCES
api.cpp api.cpp
codes.cpp codes.cpp
finalaction.cpp
) )
target_sources(magpie PRIVATE ${SOURCES}) target_sources(magpie PRIVATE ${SOURCES})

View File

@ -3,8 +3,10 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QUrlQuery> #include <QUrlQuery>
#include <QTimer>
#include "codes.h" #include "codes.h"
#include "finalaction.h"
constexpr const char* json = "application/json"; constexpr const char* json = "application/json";
constexpr const char* urlEncoded = "application/x-www-form-urlencoded"; constexpr const char* urlEncoded = "application/x-www-form-urlencoded";
@ -15,7 +17,7 @@ const std::map<QString, QMetaType::Type> testStructure({
}); });
const std::map<QString, QMetaType::Type> resultStructure({ const std::map<QString, QMetaType::Type> resultStructure({
{"result", QMetaType::Double}, {"result", QMetaType::LongLong},
}); });
const std::map<QString, QMetaType::Type> tokensStructure({ const std::map<QString, QMetaType::Type> tokensStructure({
@ -47,8 +49,12 @@ API::State API::getState() const {
void API::setTokens(const QString access, const QString &renew) { void API::setTokens(const QString access, const QString &renew) {
accessToken = access; accessToken = access;
renewToken = renew; renewToken = renew;
state = Authenticating; setState(Authenticating);
emit stateChanged(state);
//dont forget to remove
QTimer::singleShot(1000, this, [this] () {
setState(Authenticated);
});
} }
void API::setAddress(const QUrl& path) { void API::setAddress(const QUrl& path) {
@ -60,10 +66,8 @@ void API::setAddress(const QUrl& path) {
} }
address = path; address = path;
state = address.isEmpty() ? NoServer : NotAuthenticated;
emit addressChanged(address); emit addressChanged(address);
emit stateChanged(state); setState(address.isEmpty() ? NoServer : NotAuthenticated);
} }
std::optional<QVariantMap> API::readResult(QNetworkReply* reply) { std::optional<QVariantMap> API::readResult(QNetworkReply* reply) {
@ -100,6 +104,14 @@ bool API::validateResponse(const std::optional<QVariantMap>& data, const std::ma
return true; return true;
} }
void API::setState(State newState) {
if (newState == state)
return;
state = newState;
emit stateChanged(state);
}
void API::test(const QString& path, const QJSValue& finished) { void API::test(const QString& path, const QJSValue& finished) {
qDebug() << "Testing" << path; qDebug() << "Testing" << path;
if (state == Offline) if (state == Offline)
@ -177,7 +189,7 @@ void API::sendLogin(const QString& login, const QString& password, const QJSValu
QNetworkRequest request(url); QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, urlEncoded); request.setHeader(QNetworkRequest::ContentTypeHeader, urlEncoded);
setState(Authenticating);
QNetworkReply* reply = network.post(request, params.toString(QUrl::FullyEncoded).toUtf8()); QNetworkReply* reply = network.post(request, params.toString(QUrl::FullyEncoded).toUtf8());
connect(reply, &QNetworkReply::finished, connect(reply, &QNetworkReply::finished,
std::bind(&API::onLoginFinished, this, reply, finished) std::bind(&API::onLoginFinished, this, reply, finished)
@ -208,6 +220,11 @@ void API::onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) con
} }
void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) { void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) {
State state = NotAuthenticated;
FinalAction action([this, &state]() { //this should be executed on leaving the function in any way
setState(state); //setting the state as a result of this object destruction
}); //this way any error will result in NotAuthenticated, and on success it should be Authenticated
std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply); std::unique_ptr<QNetworkReply, NetworkReplyDeleter> rpl(reply);
QNetworkReply::NetworkError error = reply->error(); QNetworkReply::NetworkError error = reply->error();
std::optional<QVariantMap> data = readResult(reply); std::optional<QVariantMap> data = readResult(reply);
@ -237,7 +254,6 @@ void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) {
accessToken = data->value("accessToken").toString(); accessToken = data->value("accessToken").toString();
renewToken = data->value("renewToken").toString(); renewToken = data->value("renewToken").toString();
emit storeTokens(accessToken, renewToken); emit storeTokens(accessToken, renewToken);
emit stateChanged(state);
} }
void API::callCallback(const QJSValue& callback, const QString& error, const QJSValueList& arguments) const { void API::callCallback(const QJSValue& callback, const QString& error, const QJSValueList& arguments) const {

View File

@ -57,6 +57,7 @@ private:
void setAddress(const QUrl& path); void setAddress(const QUrl& path);
static std::optional<QVariantMap> readResult(QNetworkReply* reply); static std::optional<QVariantMap> readResult(QNetworkReply* reply);
static bool validateResponse(const std::optional<QVariantMap>& data, const std::map<QString, QMetaType::Type>& structure); static bool validateResponse(const std::optional<QVariantMap>& data, const std::map<QString, QMetaType::Type>& structure);
void setState(State newState);
private: private:
QUrl address; QUrl address;

10
API/finalaction.cpp Normal file
View File

@ -0,0 +1,10 @@
#include "finalaction.h"
FinalAction::FinalAction(const std::function<void()>& action):
action(action)
{}
FinalAction::~FinalAction() {
action();
}

19
API/finalaction.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <functional>
class FinalAction {
public:
FinalAction(const std::function<void()>& action);
~FinalAction();
FinalAction() = delete;
FinalAction(const FinalAction&) = delete;
FinalAction(FinalAction&&) = delete;
FinalAction& operator = (const FinalAction&) = delete;
FinalAction& operator = (FinalAction&&) = delete;
private:
std::function<void()> action;
};

View File

@ -0,0 +1,12 @@
qt_add_qml_module(magpieApplication
URI magpie.Application
VERSION 1.0
STATIC
RESOURCE_PREFIX /
OUTPUT_DIRECTORY ../magpie/Application
NO_PLUGIN
QML_FILES
Root.qml
)
target_link_libraries(magpie PRIVATE magpieApplication)

13
qml/Application/Root.qml Normal file
View File

@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Controls
Page {
Label {
anchors.centerIn: parent
text: "Here we go!"
font {
pixelSize: 24
bold: true
}
}
}

View File

@ -18,3 +18,4 @@ target_link_libraries(magpie PRIVATE magpieQml)
add_subdirectory(Forms) add_subdirectory(Forms)
add_subdirectory(Components) add_subdirectory(Components)
add_subdirectory(Application)

View File

@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
Popup { Popup {
property bool closable: true
property bool inProgress: false property bool inProgress: false
property string status: "" property string status: ""
@ -11,7 +12,7 @@ Popup {
focus: true focus: true
width: column.width + 60 width: column.width + 60
height: column.height + 60 height: column.height + 60
closePolicy: Popup.CloseOnEscape closePolicy: closable ? Popup.CloseOnEscape : Popup.NoAutoClose
onClosed: inProgress = false onClosed: inProgress = false
Column { Column {
@ -32,11 +33,11 @@ Popup {
Button { Button {
text: qsTr("Close") text: qsTr("Close")
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: !inProgress visible: closable && !inProgress
onClicked: modal.close() onClicked: modal.close()
focus: true focus: true
Keys.onReturnPressed: { Keys.onReturnPressed: {
if (!inProgress) if (closable && !inProgress)
modal.close() modal.close()
} }
} }

View File

@ -6,11 +6,13 @@ import magpie.Components as Components
Column { Column {
signal register() signal register()
signal loggingIn(value: bool)
function login(login, password) { function login(login, password) {
if (modal.inProgress) if (modal.inProgress)
return; return;
loggingIn(true);
loginField.text = login; loginField.text = login;
passwordField.text = password; passwordField.text = password;
@ -28,6 +30,7 @@ Column {
else else
modal.status = qsTr("Success"); modal.status = qsTr("Success");
loggingIn(false);
if (!!result) if (!!result)
modal.close(); modal.close();
}); });

View File

@ -4,9 +4,15 @@ import QtQuick.Layouts
import magpie.API import magpie.API
import magpie.Forms as Forms import magpie.Forms as Forms
import magpie.Components as Components
Page { Page {
id: page id: page
QtObject {
id: priv
property bool loggingIn: false
}
signal pickServer(address: string) signal pickServer(address: string)
// title: qsTr("Welcome") // title: qsTr("Welcome")
@ -48,8 +54,15 @@ Page {
} }
} }
Components.Modal {
inProgress: API.state === API.Authenticating
visible: !priv.loggingIn && API.state === API.Authenticating
closable: API.state === API.NotAuthenticated
status: "Logging into " + API.address + "..."
}
Item { Item {
visible: API.state === API.NotAuthenticated visible: priv.loggingIn || API.state === API.NotAuthenticated || API.state === API.Authenticating
width: page.width width: page.width
height: stack.currentItem ? stack.currentItem.implicitHeight: 0 height: stack.currentItem ? stack.currentItem.implicitHeight: 0
@ -67,6 +80,9 @@ Page {
id: loginForm id: loginForm
Forms.Login { Forms.Login {
onRegister: stack.replace(registerForm) onRegister: stack.replace(registerForm)
onLoggingIn: function (value) {
priv.loggingIn = value;
}
Component.onCompleted: { Component.onCompleted: {
if (stack.pendingLogin && stack.pendingPassword) if (stack.pendingLogin && stack.pendingPassword)
this.login(stack.pendingLogin, stack.pendingPassword) this.login(stack.pendingLogin, stack.pendingPassword)
@ -74,6 +90,7 @@ Page {
stack.pendingLogin = ""; stack.pendingLogin = "";
stack.pendingPassword = ""; stack.pendingPassword = "";
} }
Component.onDestruction: priv.loggingIn = false
} }
} }

View File

@ -5,6 +5,7 @@ import QtQuick.Layouts
import QtCore import QtCore
import magpie.API import magpie.API
import magpie.Application as Application
ApplicationWindow { ApplicationWindow {
property int counter: 0 property int counter: 0
@ -41,6 +42,13 @@ ApplicationWindow {
StackView.onDeactivating: pickingServer = false; StackView.onDeactivating: pickingServer = false;
} }
} }
Component {
id: app
Application.Root {
}
}
} }
Connections { Connections {
@ -49,5 +57,9 @@ ApplicationWindow {
if (pickingServer && url.toString().length > 0) if (pickingServer && url.toString().length > 0)
stack.pop() stack.pop()
} }
function onStateChanged (state) {
if (state === API.Authenticated)
stack.push(app)
}
} }
} }

View File

@ -26,6 +26,11 @@ Root::Root(const QUrl& root, int& argc, char* argv[]) :
QSettings settings; QSettings settings;
api.setAddress(settings.value("address").toUrl()); api.setAddress(settings.value("address").toUrl());
QString acc = settings.value("accessToken").toString();
QString ren = settings.value("renewToken").toString();
if (!acc.isEmpty() && !ren.isEmpty())
api.setTokens(acc, ren);
qRegisterMetaType<API>("API"); qRegisterMetaType<API>("API");
connect(&api, &API::addressChanged, this, &Root::onAPIAddressChanged); connect(&api, &API::addressChanged, this, &Root::onAPIAddressChanged);
connect(&api, &API::storeTokens, this, &Root::onStoreTokens); connect(&api, &API::storeTokens, this, &Root::onStoreTokens);
@ -72,6 +77,6 @@ void Root::onAPIAddressChanged(const QUrl& url) {
void Root::onStoreTokens(const QString& access, const QString& renew) { void Root::onStoreTokens(const QString& access, const QString& renew) {
QSettings settings; QSettings settings;
settings.setValue("assessToken", access); settings.setValue("accessToken", access);
settings.setValue("renewToken", renew); settings.setValue("renewToken", renew);
} }