diff --git a/API/api.cpp b/API/api.cpp index 3e57766..77d7940 100644 --- a/API/api.cpp +++ b/API/api.cpp @@ -2,8 +2,10 @@ #include #include #include +#include constexpr const char* json = "application/json"; +constexpr const char* urlEncoded = "application/x-www-form-urlencoded"; struct NetworkReplyDeleter { void operator () (QNetworkReply* reply) { @@ -14,23 +16,53 @@ struct NetworkReplyDeleter { API::API(const QUrl& address, QObject* parent): QObject(parent), address(address), - network() -{ + network(), + state(NoServer) +{} +QUrl API::getAddress() const { + return address; +} + +API::State API::getState() const { + return state; +} + +void API::setAddress(const QUrl& path) { + if (address == path) + return; + + if (state == Authenticated) { + //do something + } + + address = path; + state = address.isEmpty() ? NoServer : NotAuthenticated; + + emit addressChanged(address); + emit stateChanged(state); } void API::test(const QString& path, const QJSValue& finished) { qDebug() << "Testing" << path; + + if (state == Offline) { + QString err = "Need to be online to test"; + qDebug() << "Test for" << path << "failed:" << err; + callCallback(finished, err); + return; + } + QNetworkRequest request(path + "/info"); request.setHeader(QNetworkRequest::ContentTypeHeader, json); QNetworkReply* reply = network.get(request); connect(reply, &QNetworkReply::finished, - std::bind(&API::onTestSuccess, this, reply, finished) + std::bind(&API::onTestFinished, this, reply, finished) ); } -void API::onTestSuccess(QNetworkReply* reply, const QJSValue& finished) const { +void API::onTestFinished(QNetworkReply* reply, const QJSValue& finished) const { std::unique_ptr rpl(reply); QNetworkReply::NetworkError error = reply->error(); if (error != QNetworkReply::NoError) { @@ -65,7 +97,7 @@ void API::onTestSuccess(QNetworkReply* reply, const QJSValue& finished) const { return; } - if (type.toString() != "Pica") { + if (type.toString() != "pica") { QString err("server of this type (" + type.toString() + ") is not supported"); qDebug() << "Test for" << reply->url() << "failed:" << err; callCallback(finished, err); @@ -82,6 +114,74 @@ void API::onTestSuccess(QNetworkReply* reply, const QJSValue& finished) const { callCallback(finished, QString(), {QJSValue(true)}); } +void API::sendRegister(const QString& login, const QString& password, const QJSValue &finished) { + qDebug() << "Registering..."; + + if (state != NotAuthenticated) { + QString err = "Can not register in current state"; + qDebug() << "Register failed:" << err; + callCallback(finished, err); + return; + } + + QUrlQuery params({ + {"login", login}, + {"password", password} + }); + + QNetworkRequest request(address.path() + "/register"); + request.setHeader(QNetworkRequest::ContentTypeHeader, urlEncoded); + + QNetworkReply* reply = network.post(request, params.toString(QUrl::FullyEncoded).toUtf8()); + connect(reply, &QNetworkReply::finished, + std::bind(&API::onRegisterFinished, this, reply, finished) + ); +} + +void API::onRegisterFinished(QNetworkReply *reply, const QJSValue &finished) const { + std::unique_ptr rpl(reply); + QNetworkReply::NetworkError error = reply->error(); + if (error != QNetworkReply::NoError) { + QString err = reply->errorString(); + qDebug() << "Register failed:" << err; + callCallback(finished, err); + return; + } + + QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader); + if (! + contentType.isValid() || + !contentType.canConvert() || + contentType.toString() != json + ) { + QString err("wrong response content type"); + qDebug() << "Register failed:" << err; + callCallback(finished, err); + return; + } + + QByteArray data = reply->readAll(); + QJsonDocument document = QJsonDocument::fromJson(data); + QJsonObject rootObj = document.object(); + + QJsonValue result = rootObj.value("result"); + if (!result.isString()) { + QString err("malformed json"); + qDebug() << "Register failed:" << err; + callCallback(finished, err); + return; + } + + if (result.toString() != "ok") { + QString err("Registration result was not okay"); + qDebug() << "Register failed:" << err; + callCallback(finished, err); + return; + } + + callCallback(finished, QString(), {QJSValue(true)}); +} + void API::callCallback(const QJSValue& callback, const QString& error, const QJSValueList& arguments) const { if (callback.isCallable()) { if (error.isEmpty()) diff --git a/API/api.h b/API/api.h index 492d37c..fde275a 100644 --- a/API/api.h +++ b/API/api.h @@ -11,13 +11,33 @@ class API : public QObject { Q_OBJECT +public: + enum State {Offline, NoServer, NotAuthenticated, Authenticated}; + Q_ENUM(State) + +private: + Q_PROPERTY(QUrl address READ getAddress WRITE setAddress NOTIFY addressChanged) + Q_PROPERTY(State state READ getState NOTIFY stateChanged) + public: explicit API(const QUrl& path = QString(), QObject* parent = nullptr); - Q_INVOKABLE void test(const QString& path, const QJSValue& finished = QJSValue()); + QUrl getAddress() const; + State getState() const; + + void setAddress(const QUrl& path); + +signals: + void addressChanged(const QUrl& path); + void stateChanged(State state); + +public slots: + void test(const QString& path, const QJSValue& finished = QJSValue()); + void sendRegister(const QString& login, const QString& password, const QJSValue& finished = QJSValue()); private slots: - void onTestSuccess(QNetworkReply* reply, const QJSValue& finished) const; + void onTestFinished(QNetworkReply* reply, const QJSValue& finished) const; + void onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) const; private: void callCallback(const QJSValue& callback, const QString& error = QString(), const QJSValueList& arguments = QJSValueList()) const; @@ -25,4 +45,5 @@ private: private: QUrl address; QNetworkAccessManager network; + State state; }; diff --git a/qml/ServerPick.qml b/qml/ServerPick.qml index f7c4f19..3d94015 100644 --- a/qml/ServerPick.qml +++ b/qml/ServerPick.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import API + Page { property string address property bool valid: false diff --git a/qml/Welcome.qml b/qml/Welcome.qml index 97fbe33..b24a020 100644 --- a/qml/Welcome.qml +++ b/qml/Welcome.qml @@ -2,9 +2,9 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -Page { - property string serverAddress +import API +Page { signal pickServer(address: string) title: qsTr("Welcome") @@ -32,7 +32,7 @@ Page { } Label { horizontalAlignment: Label.AlignLeft - text: serverAddress || qsTr("choose") + text: API.state === API.NoServer ? qsTr("choose") : API.address font { italic: true underline: true @@ -40,7 +40,157 @@ Page { MouseArea { anchors.fill: parent - onClicked: pickServer(serverAddress) + onClicked: pickServer(API.address) + } + } + } + + Row { + id: forms + property bool registering: false + + visible: API.state === API.NotAuthenticated + anchors.horizontalCenter: parent.horizontalCenter + topPadding: 10 + + Column { + visible: forms.registering === false + spacing: 10 + + Label { + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Please, log in to your account") + font { + pixelSize: 14 + } + } + + Grid { + anchors.horizontalCenter: parent.horizontalCenter + columns: 2 + columnSpacing: 10 + rowSpacing: 5 + verticalItemAlignment: Grid.AlignVCenter + horizontalItemAlignment: Grid.AlignRight + + Label { + text: qsTr("Login") + ":"; + } + + TextField { + id: login + } + + Label { + text: qsTr("Password") + ":"; + } + + TextField { + id: password + echoMode: TextField.Password + } + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Login") + onClicked: function () { + console.log("Not implemented"); + } + } + + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 5 + topPadding: 10 + + Label { + text: qsTr("Don't have account?") + } + Label { + text: qsTr("Sign up") + "!" + font { + italic: true + underline: true + } + + MouseArea { + anchors.fill: parent + onClicked: forms.registering = true + } + } + } + } + + Column { + visible: forms.registering === true + spacing: 10 + + Label { + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Please, chose login and password") + font { + pixelSize: 14 + } + } + + Grid { + anchors.horizontalCenter: parent.horizontalCenter + columns: 2 + columnSpacing: 10 + rowSpacing: 5 + verticalItemAlignment: Grid.AlignVCenter + horizontalItemAlignment: Grid.AlignRight + + Label { + text: qsTr("Login") + ":"; + } + + TextField { + id: newLogin + } + + Label { + text: qsTr("Password") + ":"; + } + + TextField { + id: newPassword + echoMode: TextField.Password + } + } + + Button { + anchors.horizontalCenter: parent.horizontalCenter + text: qsTr("Register") + onClicked: API.sendRegister(newLogin.text, newPassword.text, function (err, result) { + if (err) + console.error("err") + + console.log(result); + }) + } + + Row { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 5 + topPadding: 10 + + Label { + text: qsTr("Already have an account?") + } + Label { + text: qsTr("Log in") + "!" + font { + italic: true + underline: true + } + + MouseArea { + anchors.fill: parent + onClicked: forms.registering = false + } + } } } } diff --git a/qml/main.qml b/qml/main.qml index 52626a0..3793c65 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -4,6 +4,7 @@ import QtQuick.Controls import QtQuick.Layouts import QtCore +import API ApplicationWindow { property int counter: 0 @@ -27,25 +28,18 @@ ApplicationWindow { Welcome { id: welcome - serverAddress: settings.serverAddress onPickServer: function (address) { pick.address = address; stack.push(pick) } } - Settings { - id: settings - - property string serverAddress - } - ServerPick { visible: false id: pick onBack: stack.pop() onSuccess: function (address) { - settings.serverAddress = address; + API.address = address stack.pop(); } } diff --git a/root.cpp b/root.cpp index 442b50b..8b9efd3 100644 --- a/root.cpp +++ b/root.cpp @@ -1,5 +1,7 @@ #include "root.h" +#include + Root::Root(const QUrl& root, int& argc, char* argv[]) : QGuiApplication(argc, argv), root(root), @@ -21,11 +23,19 @@ Root::Root(const QUrl& root, int& argc, char* argv[]) : Qt::QueuedConnection ); + QSettings settings; + api.setAddress(settings.value("address").toUrl()); + + qRegisterMetaType("API"); + connect(&api, &API::addressChanged, this, &Root::onAPIAddressChanged); + + qmlRegisterSingletonType("API", 1, 0, "API", [this] (QQmlEngine *engine, QJSEngine *scriptEngine) { + return &api; + }); + engine.load(root); if (engine.rootObjects().isEmpty()) throw std::runtime_error("Couldn't looad root qml object"); - - context->setContextProperty("API", &api); } Root::~Root() { @@ -52,3 +62,8 @@ void Root::onObjectCreated(QObject* obj, const QUrl& objUrl) { if (!obj && objUrl == root) exit(-2); } + +void Root::onAPIAddressChanged(const QUrl& url) { + QSettings settings; + settings.setValue("address", url); +} diff --git a/root.h b/root.h index a5ce5c0..79d4706 100644 --- a/root.h +++ b/root.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,7 @@ public: private slots: void onObjectCreated(QObject* obj, const QUrl& objUrl); + void onAPIAddressChanged(const QUrl& url); private: QUrl root;