more work on the cloud login

This commit is contained in:
Michael Zanetti 2018-08-05 02:14:58 +02:00
parent 245eb9d85d
commit d5839a5229
14 changed files with 468 additions and 98 deletions

View File

@ -5,32 +5,34 @@
#include <QNetworkAccessManager>
#include <QUrlQuery>
#include <QJsonDocument>
extern "C" {
#include "connection/srp.h"
}
#include <QSettings>
static QByteArray clientId = "8rjhfdlf9jf1suok2jcrltd6v";
AWSClient::AWSClient(QObject *parent) : QObject(parent)
{
m_nam = new QNetworkAccessManager(this);
QSettings settings;
settings.beginGroup("cloud");
m_username = settings.value("username").toString();
m_accessToken = settings.value("accessToken").toByteArray();
m_idToken = settings.value("idToken").toByteArray();
}
bool AWSClient::isLoggedIn() const
{
return !m_username.isEmpty() && !m_accessToken.isEmpty() && !m_idToken.isEmpty();
}
void AWSClient::login(const QString &username, const QString &password)
{
if (m_srpUser != nullptr) {
qWarning() << "Already logged in. Cannot log in again";
return;
}
m_username = username;
m_srpUser = srp_user_new(SRP_SHA256, SRP_NG_2048, username.toLocal8Bit(), (const unsigned char*)password.toLocal8Bit().data(), password.length(), nullptr ,nullptr);
char *user;
unsigned char *bytes_A;
int len_A;
srp_user_start_authentication(m_srpUser, (const char**)&user, (const unsigned char**)&bytes_A, &len_A);
QSettings settings;
settings.remove("cloud");
settings.beginGroup("cloud");
settings.setValue("username", username);
QUrl url("https://cognito-idp.eu-west-1.amazonaws.com/");
@ -56,16 +58,9 @@ void AWSClient::login(const QString &username, const QString &password)
QJsonDocument jsonDoc = QJsonDocument::fromVariant(params);
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
qDebug() << "Posting:\nURL:" << request.url().toString();
qDebug() << "HEADERS:";
foreach (const QByteArray &headerName, request.rawHeaderList()) {
qDebug() << headerName << ":" << request.rawHeader(headerName);
}
qDebug().noquote() << "Payload:" << payload;
QNetworkReply *reply = m_nam->post(request, payload);
connect(reply, &QNetworkReply::finished, this, &AWSClient::initiateAuthReply);
qDebug() << "Logging in to AWS as user:" << username;
}
void AWSClient::initiateAuthReply()
@ -73,7 +68,11 @@ void AWSClient::initiateAuthReply()
QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
reply->deleteLater();
QByteArray data = reply->readAll();
qDebug() << "InitiateAuth reply" << reply->error() << reply->errorString() << qUtf8Printable(data);
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error logging in to aws:" << reply->error() << reply->errorString() << qUtf8Printable(data);
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
@ -83,11 +82,18 @@ void AWSClient::initiateAuthReply()
}
QVariantMap authenticationResult = jsonDoc.toVariant().toMap().value("AuthenticationResult").toMap();
QByteArray accessToken = authenticationResult.value("AccessToken").toByteArray();
QByteArray idToken = authenticationResult.value("IdToken").toByteArray();
qDebug() << "Have acess token:" << accessToken;
qDebug() << "have idToken:" << idToken;
m_accessToken = authenticationResult.value("AccessToken").toByteArray();
m_idToken = authenticationResult.value("IdToken").toByteArray();
QSettings settings;
settings.beginGroup("cloud");
settings.setValue("accessToken", m_accessToken);
settings.setValue("idToken", m_idToken);
qDebug() << "AWS login successful";
emit isLoggedInChanged();
return; // Why should we call GetId? Ask Luca
QUrl url("https://cognito-identity.eu-west-1.amazonaws.com/");
@ -102,7 +108,7 @@ void AWSClient::initiateAuthReply()
request.setRawHeader("X-Amz-Target", "AWSCognitoIdentityService.GetId");
QVariantMap logins;
logins.insert("cognito-idp.eu-west-1.amazonaws.com/eu-west-1_6eX6YjmXr", idToken);
logins.insert("cognito-idp.eu-west-1.amazonaws.com/eu-west-1_6eX6YjmXr", m_idToken);
QVariantMap params;
params.insert("IdentityPoolId", "eu-west-1:108a174c-5786-40f9-966a-1a0cd33d6801");
@ -111,14 +117,6 @@ void AWSClient::initiateAuthReply()
jsonDoc = QJsonDocument::fromVariant(params);
QByteArray payload = jsonDoc.toJson(QJsonDocument::Compact);
qDebug() << "Posting:\nURL:" << request.url().toString();
qDebug() << "HEADERS:";
foreach (const QByteArray &headerName, request.rawHeaderList()) {
qDebug() << headerName << ":" << request.rawHeader(headerName);
}
qDebug().noquote() << "Payload:" << payload;
reply = m_nam->post(request, payload);
connect(reply, &QNetworkReply::finished, this, &AWSClient::getIdReply);
}
@ -128,8 +126,39 @@ void AWSClient::getIdReply()
QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
reply->deleteLater();
QByteArray data = reply->readAll();
qDebug() << "RespondToAuthChallenge reply" << reply->error() << reply->errorString() << qUtf8Printable(data);
// QM
qDebug() << "GetID reply" << reply->error() << reply->errorString() << qUtf8Printable(data);
}
void AWSClient::fetchDevices()
{
QUrl url("https://z6368zhf2m.execute-api.eu-west-1.amazonaws.com/dev/devices");
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("x-api-idToken", m_idToken);
QNetworkReply *reply = m_nam->get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
reply->deleteLater();
QByteArray data = reply->readAll();
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Error fetching cloud devices:" << reply->error() << reply->errorString() << qUtf8Printable(data);
return;
}
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Failed to parse JSON from server" << error.errorString() << qUtf8Printable(data);
return;
}
QList<AWSDevice> ret;
foreach (const QVariant &entry, jsonDoc.toVariant().toMap().value("devices").toList()) {
AWSDevice d;
d.id = entry.toMap().value("deviceId").toString();
d.name = entry.toMap().value("name").toString();
d.online = entry.toMap().value("online").toBool();
ret.append(d);
}
emit devicesFetched(ret);
});
}

View File

@ -7,26 +7,42 @@
class QNetworkAccessManager;
struct SRPUser;
class AWSDevice {
public:
QString id;
QString name;
bool online;
};
class AWSClient : public QObject
{
Q_OBJECT
Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY isLoggedInChanged)
public:
explicit AWSClient(QObject *parent = nullptr);
bool isLoggedIn() const;
Q_INVOKABLE void login(const QString &username, const QString &password);
Q_INVOKABLE void fetchDevices();
signals:
void isLoggedInChanged();
void devicesFetched(QList<AWSDevice> devices);
private slots:
void initiateAuthReply();
void getIdReply();
private:
QByteArray createClaim(const QByteArray &secretBlock, const QByteArray &srpB, const QByteArray &salt);
QByteArray getPasswordAuthenticationKey(const QByteArray &username, const QByteArray &password);
private:
QNetworkAccessManager *m_nam = nullptr;
SRPUser *m_srpUser = nullptr;
void sign(QNetworkRequest &request);
QString m_username;
QByteArray m_accessToken;
QByteArray m_idToken;
};
#endif // AWSCLIENT_H

View File

@ -3,10 +3,15 @@
#include "jsonrpc/jsonrpcclient.h"
BasicConfiguration::BasicConfiguration(JsonRpcClient* client, QObject *parent) :
QObject(parent),
JsonHandler(parent),
m_client(client)
{
client->registerNotificationHandler(this, "notificationReceived");
}
QString BasicConfiguration::nameSpace() const
{
return "Configuration";
}
bool BasicConfiguration::debugServerEnabled() const
@ -33,6 +38,18 @@ void BasicConfiguration::setServerName(const QString &serverName)
m_client->sendCommand("Configuration.SetServerName", params, this, "setServerNameResponse");
}
bool BasicConfiguration::cloudEnabled() const
{
return m_cloudEnabled;
}
void BasicConfiguration::setCloudEnabled(bool cloudEnabled)
{
QVariantMap params;
params.insert("enabled", cloudEnabled);
m_client->sendCommand("Configuration.SetCloudEnabled", params, this, "setCloudEnabledResponse");
}
void BasicConfiguration::init()
{
m_client->sendCommand("Configuration.GetConfigurations", this, "getConfigurationsResponse");
@ -43,7 +60,17 @@ void BasicConfiguration::getConfigurationsResponse(const QVariantMap &params)
// qDebug() << "have config reply" << params;
QVariantMap basicConfig = params.value("params").toMap().value("basicConfiguration").toMap();
m_debugServerEnabled = basicConfig.value("debugServerEnabled").toBool();
emit debugServerEnabledChanged();
m_serverName = basicConfig.value("serverName").toString();
emit serverNameChanged();
QVariantMap cloudConfig = params.value("params").toMap().value("cloud").toMap();
m_cloudEnabled = cloudConfig.value("enabled").toBool();
emit cloudEnabledChanged();
}
void BasicConfiguration::getCloudConfigurationResponse(const QVariantMap &params)
{
qDebug() << "Cloud config reply" << params;
}
void BasicConfiguration::setDebugServerEnabledResponse(const QVariantMap &params)
@ -55,3 +82,30 @@ void BasicConfiguration::setServerNameResponse(const QVariantMap &params)
{
qDebug() << "Server name set:" << params;
}
void BasicConfiguration::setCloudEnabledResponse(const QVariantMap &params)
{
qDebug() << "Set cloud enabled:" << params;
}
void BasicConfiguration::notificationReceived(const QVariantMap &notification)
{
QString notif = notification.value("notification").toString();
if (notif == "Configuration.BasicConfigurationChanged") {
QVariantMap params = notification.value("params").toMap().value("basicConfiguration").toMap();
qDebug() << "notif" << params;
m_debugServerEnabled = params.value("debugServerEnabled").toBool();
emit debugServerEnabled();
m_serverName = params.value("serverName").toString();
emit serverNameChanged();
return;
}
if (notif == "Configuration.CloudConfigurationChanged") {
QVariantMap params = notification.value("params").toMap().value("cloudConfiguration").toMap();
qDebug() << "notif" << params;
m_cloudEnabled = params.value("enabled").toBool();
emit cloudEnabledChanged();
return;
}
qDebug() << "Unhandled Configuration notification" << notif;
}

View File

@ -2,38 +2,53 @@
#define BASICCONFIGURATION_H
#include <QObject>
#include "jsonrpc/jsonhandler.h"
class JsonRpcClient;
class BasicConfiguration : public QObject
class BasicConfiguration : public JsonHandler
{
Q_OBJECT
Q_PROPERTY(bool debugServerEnabled READ debugServerEnabled WRITE setDebugServerEnabled NOTIFY debugServerEnabledChanged)
Q_PROPERTY(QString serverName READ serverName WRITE setServerName NOTIFY serverNameChanged)
Q_PROPERTY(bool cloudEnabled READ cloudEnabled WRITE setCloudEnabled NOTIFY cloudEnabledChanged)
public:
explicit BasicConfiguration(JsonRpcClient* client, QObject *parent = nullptr);
QString nameSpace() const override;
bool debugServerEnabled() const;
void setDebugServerEnabled(bool debugServerEnabled);
QString serverName() const;
void setServerName(const QString &serverName);
bool cloudEnabled() const;
void setCloudEnabled(bool cloudEnabled);
void init();
private:
Q_INVOKABLE void getConfigurationsResponse(const QVariantMap &params);
Q_INVOKABLE void getCloudConfigurationResponse(const QVariantMap &params);
Q_INVOKABLE void setDebugServerEnabledResponse(const QVariantMap &params);
Q_INVOKABLE void setServerNameResponse(const QVariantMap &params);
Q_INVOKABLE void setCloudEnabledResponse(const QVariantMap &params);
Q_INVOKABLE void notificationReceived(const QVariantMap &notification);
signals:
void debugServerEnabledChanged();
void serverNameChanged();
void cloudEnabledChanged();
private:
JsonRpcClient* m_client = nullptr;
bool m_debugServerEnabled = false;
QString m_serverName;
bool m_cloudEnabled = false;
};
#endif // BASICCONFIGURATION_H

View File

@ -1,8 +1,9 @@
#include "nymeadiscovery.h"
#include "engine.h"
#include "upnpdiscovery.h"
#include "zeroconfdiscovery.h"
#include "bluetoothservicediscovery.h"
#include "awsclient.h"
#include <QUuid>
#include <QBluetoothUuid>
@ -17,6 +18,8 @@ NymeaDiscovery::NymeaDiscovery(QObject *parent) : QObject(parent)
#ifndef Q_OS_IOS
m_bluetooth = new BluetoothServiceDiscovery(m_discoveryModel, this);
#endif
connect(Engine::instance()->awsClient(), &AWSClient::devicesFetched, this, &NymeaDiscovery::cloudDevicesFetched);
}
bool NymeaDiscovery::discovering() const
@ -36,6 +39,9 @@ void NymeaDiscovery::setDiscovering(bool discovering)
if (m_bluetooth) {
m_bluetooth->discover();
}
if (Engine::instance()->awsClient()->isLoggedIn()) {
Engine::instance()->awsClient()->fetchDevices();
}
} else {
m_upnp->stopDiscovery();
if (m_bluetooth) {
@ -50,3 +56,13 @@ DiscoveryModel *NymeaDiscovery::discoveryModel() const
{
return m_discoveryModel;
}
void NymeaDiscovery::cloudDevicesFetched(const QList<AWSDevice> &devices)
{
foreach (const AWSDevice &d, devices) {
DiscoveryDevice *device = m_discoveryModel->find(d.id);
if (!device) {
}
}
}

View File

@ -3,6 +3,8 @@
#include <QObject>
#include "awsclient.h"
class DiscoveryModel;
class UpnpDiscovery;
class ZeroconfDiscovery;
@ -25,6 +27,9 @@ public:
signals:
void discoveringChanged();
private slots:
void cloudDevicesFetched(const QList<AWSDevice> &devices);
private:
bool m_discovering = false;
DiscoveryModel *m_discoveryModel = nullptr;

View File

@ -77,6 +77,13 @@ void JsonRpcClient::setNotificationsEnabled(bool enabled)
sendRequest(reply->requestMap());
}
void JsonRpcClient::getCloudConnectionStatus()
{
JsonRpcReply *reply = createReply("JSONRPC.IsCloudConnected", QVariantMap(), this, "isCloudConnectedReply");
m_replies.insert(reply->commandId(), reply);
sendRequest(reply->requestMap());
}
void JsonRpcClient::setNotificationsEnabledResponse(const QVariantMap &params)
{
qDebug() << "Notifications enabled:" << params;
@ -88,8 +95,6 @@ void JsonRpcClient::setNotificationsEnabledResponse(const QVariantMap &params)
void JsonRpcClient::notificationReceived(const QVariantMap &data)
{
qDebug() << "JsonRpcClient: Notification received" << data;
//JsonRpcClient: Notification received QMap(("id", QVariant(double, 2))("notification", QVariant(QString, "JSONRPC.PushButtonAuthFinished"))("params", QVariant(QVariantMap, QMap(("success", QVariant(bool, true))("token", QVariant(QString, "FJPaAJ8FEtrqcC+/s0s/lAcDubz0OyEtwbRsyFIWM9c="))("transactionId", QVariant(double, 2))))))
if (data.value("notification").toString() == "JSONRPC.PushButtonAuthFinished") {
qDebug() << "Push button auth finished.";
@ -111,7 +116,23 @@ void JsonRpcClient::notificationReceived(const QVariantMap &data)
} else {
emit pushButtonAuthFailed();
}
return;
}
if (data.value("notification").toString() == "JSONRPC.CloudConnectedChanged") {
m_cloudConnected = data.value("params").toMap().value("connected").toBool();
emit cloudConnectedChanged();
return;
}
qDebug() << "JsonRpcClient: Unhandled notification received" << data;
}
void JsonRpcClient::isCloudConnectedReply(const QVariantMap &data)
{
qDebug() << "Cloud is connected" << data;
m_cloudConnected = data.value("params").toMap().value("connected").toBool();
emit cloudConnectedChanged();
}
bool JsonRpcClient::connected() const
@ -134,6 +155,11 @@ bool JsonRpcClient::pushButtonAuthAvailable() const
return m_pushButtonAuthAvailable;
}
bool JsonRpcClient::cloudConnected() const
{
return m_cloudConnected;
}
QString JsonRpcClient::serverVersion() const
{
return m_serverVersion;
@ -267,7 +293,7 @@ void JsonRpcClient::onInterfaceConnectedChanged(bool connected)
void JsonRpcClient::dataReceived(const QByteArray &data)
{
qDebug() << "JsonRpcClient: received data:" << qUtf8Printable(data);
// qDebug() << "JsonRpcClient: received data:" << qUtf8Printable(data);
m_receiveBuffer.append(data);
int splitIndex = m_receiveBuffer.indexOf("}\n{") + 1;
@ -334,6 +360,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data)
}
setNotificationsEnabled(true);
getCloudConnectionStatus();
}
// check if this is a reply to a request

View File

@ -40,6 +40,7 @@ class JsonRpcClient : public JsonHandler
Q_PROPERTY(bool initialSetupRequired READ initialSetupRequired NOTIFY initialSetupRequiredChanged)
Q_PROPERTY(bool authenticationRequired READ authenticationRequired NOTIFY authenticationRequiredChanged)
Q_PROPERTY(bool pushButtonAuthAvailable READ pushButtonAuthAvailable NOTIFY pushButtonAuthAvailableChanged)
Q_PROPERTY(bool cloudConnected READ cloudConnected NOTIFY cloudConnectedChanged)
Q_PROPERTY(QString serverVersion READ serverVersion NOTIFY handshakeReceived)
Q_PROPERTY(QString jsonRpcVersion READ jsonRpcVersion NOTIFY handshakeReceived)
Q_PROPERTY(QString serverUuid READ serverUuid NOTIFY handshakeReceived)
@ -59,6 +60,7 @@ public:
bool initialSetupRequired() const;
bool authenticationRequired() const;
bool pushButtonAuthAvailable() const;
bool cloudConnected() const;
QString serverVersion() const;
QString jsonRpcVersion() const;
@ -82,6 +84,7 @@ signals:
void authenticationFailed();
void pushButtonAuthFailed();
void createUserFailed(const QString &error);
void cloudConnectedChanged();
void responseReceived(const int &commandId, const QVariantMap &response);
@ -102,6 +105,7 @@ private:
bool m_initialSetupRequired = false;
bool m_authenticationRequired = false;
bool m_pushButtonAuthAvailable = false;
bool m_cloudConnected = false;
int m_pendingPushButtonTransaction = -1;
QString m_serverUuid;
QVersionNumber m_jsonRpcVersion;
@ -110,6 +114,7 @@ private:
QByteArray m_receiveBuffer;
void setNotificationsEnabled(bool enabled);
void getCloudConnectionStatus();
// json handler
Q_INVOKABLE void processAuthenticate(const QVariantMap &data);
@ -118,6 +123,7 @@ private:
Q_INVOKABLE void setNotificationsEnabledResponse(const QVariantMap &params);
Q_INVOKABLE void notificationReceived(const QVariantMap &data);
Q_INVOKABLE void isCloudConnectedReply(const QVariantMap &data);
void sendRequest(const QVariantMap &request);

View File

@ -240,5 +240,8 @@
<file>ui/images/configure.svg</file>
<file>ui/images/network-wifi-symbolic.svg</file>
<file>ui/KeyboardLoader.qml</file>
<file>ui/images/cloud.svg</file>
<file>ui/system/CloudSettingsPage.qml</file>
<file>ui/connection/CloudLoginPage.qml</file>
</qresource>
</RCC>

View File

@ -90,6 +90,13 @@ Page {
}
MeaListItemDelegate {
Layout.fillWidth: true
iconName: "../images/cloud.svg"
text: qsTr("Cloud")
onClicked: pageStack.push(Qt.resolvedUrl("system/CloudSettingsPage.qml"))
}
MeaListItemDelegate {
Layout.fillWidth: true
iconName: "../images/plugin.svg"
@ -110,30 +117,4 @@ Page {
onClicked: pageStack.push(Qt.resolvedUrl("system/AboutNymeaPage.qml"))
}
}
Component {
id: styleChangedDialog
Dialog {
width: Math.min(parent.width * .8, contentLabel.implicitWidth)
x: (parent.width - width) / 2
y: (parent.height - height) / 2
modal: true
title: qsTr("Style changed")
standardButtons: Dialog.Ok
ColumnLayout {
id: content
anchors { left: parent.left; top: parent.top; right: parent.right }
Label {
id: contentLabel
Layout.fillWidth: true
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: qsTr("The application needs to be restarted for style changes to take effect.")
}
}
}
}
}

View File

@ -0,0 +1,43 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Nymea 1.0
import "../components"
Page {
id: root
header: GuhHeader {
text: qsTr("Cloud login")
onBackPressed: pageStack.pop()
}
ColumnLayout {
anchors.fill: parent
Label {
Layout.fillWidth: true
text: "Username (e-mail)"
}
TextField {
id: usernameTextField
Layout.fillWidth: true
placeholderText: "john@dummy.com"
}
Label {
Layout.fillWidth: true
text: qsTr("Password")
}
TextField {
id: passwordTextField
Layout.fillWidth: true
}
Button {
Layout.fillWidth: true
text: qsTr("OK")
enabled: usernameTextField.displayText.length > 0 && passwordTextField.displayText.length > 0
onClicked: {
Engine.awsClient.login(usernameTextField.text, passwordTextField.text);
}
}
}
}

View File

@ -85,29 +85,13 @@ Page {
header: FancyHeader {
title: qsTr("Connect %1").arg(app.systemName)
model: ListModel {
ListElement { iconSource: "../images/network-vpn.svg"; text: qsTr("Manual connection"); page: "ManualConnectPage.qml" }
ListElement { iconSource: "../images/bluetooth.svg"; text: qsTr("Wireless setup"); page: "BluetoothDiscoveryPage.qml"; }
ListElement { iconSource: "../images/private-browsing.svg"; text: qsTr("Demo mode"); page: "" }
ListElement { iconSource: "../images/network-vpn.svg"; text: qsTr("Manual connection"); page: "connection/ManualConnectPage.qml" }
ListElement { iconSource: "../images/bluetooth.svg"; text: qsTr("Wireless setup"); page: "connection/BluetoothDiscoveryPage.qml"; }
ListElement { iconSource: "../images/cloud.svg"; text: qsTr("Cloud login"); page: "connection/CloudLoginPage.qml" }
ListElement { iconSource: "../images/stock_application.svg"; text: qsTr("App settings"); page: "AppSettingsPage.qml" }
ListElement { iconSource: "../images/stock_application.svg"; text: qsTr("Cloud login"); page: "AppSettingsPage.qml" }
}
onClicked: {
switch (index) {
case 0:
case 1:
case 3:
pageStack.push(model.get(index).page);
break;
case 2:
var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml"))
page.cancel.connect(function() {
Engine.connection.disconnect()
})
Engine.connection.connect("nymea://nymea.nymea.io:2222")
break;
case 4:
Engine.awsClient.login("michael.zanetti@guh.io", "H22*xgemmmmm");
}
pageStack.push(model.get(index).page);
}
}

View File

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg4874"
version="1.1"
inkscape:version="0.91+devel r"
viewBox="0 0 96 96.000001"
sodipodi:docname="weather-clouds-symbolic.svg">
<defs
id="defs4876" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4.4959994"
inkscape:cx="11.154354"
inkscape:cy="34.719727"
inkscape:document-units="px"
inkscape:current-layer="g4780"
showgrid="false"
showborder="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-global="true">
<inkscape:grid
type="xygrid"
id="grid5451"
empspacing="8" />
<sodipodi:guide
orientation="1,0"
position="8,-8.0000001"
id="guide4063" />
<sodipodi:guide
orientation="1,0"
position="4,-8.0000001"
id="guide4065" />
<sodipodi:guide
orientation="0,1"
position="-8,88.000001"
id="guide4067" />
<sodipodi:guide
orientation="0,1"
position="-8,92.000001"
id="guide4069" />
<sodipodi:guide
orientation="0,1"
position="104,4"
id="guide4071" />
<sodipodi:guide
orientation="0,1"
position="-5,8.0000001"
id="guide4073" />
<sodipodi:guide
orientation="1,0"
position="88,-8.0000001"
id="guide4077" />
<sodipodi:guide
orientation="0,1"
position="-8,84.000001"
id="guide4074" />
<sodipodi:guide
orientation="1,0"
position="12,-8.0000001"
id="guide4076" />
<sodipodi:guide
orientation="1,0"
position="84,-8.0000001"
id="guide4080" />
<sodipodi:guide
position="48,-8.0000001"
orientation="1,0"
id="guide4170" />
<sodipodi:guide
position="-8,48"
orientation="0,1"
id="guide4172" />
<sodipodi:guide
position="92,-8.0000001"
orientation="1,0"
id="guide4760" />
</sodipodi:namedview>
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(67.857146,-78.50504)">
<g
transform="matrix(0,-1,-1,0,373.50506,516.50504)"
id="g4845"
style="display:inline">
<g
inkscape:export-ydpi="90"
inkscape:export-xdpi="90"
inkscape:export-filename="next01.png"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)"
id="g4778"
inkscape:label="Layer 1">
<g
transform="matrix(-1,0,0,1,575.99999,611)"
id="g4780"
style="display:inline">
<rect
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate"
id="rect4782"
width="96.037987"
height="96"
x="-438.00244"
y="345.36221"
transform="scale(-1,1)" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:normal;font-family:sans-serif;-inkscape-font-specification:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#808080;fill-opacity:1;stroke:none;stroke-width:4.00000048;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="M 51.539062 16.888672 C 39.655652 16.888672 29.559451 24.212821 24.738281 34.675781 L 24.416016 35.376953 L 23.646484 35.433594 C 12.678934 36.232468 3.9980469 45.714566 3.9980469 57.255859 C 3.9980469 69.316737 13.439055 79.21875 25.109375 79.21875 L 51.539062 79.21875 L 76.175781 79.21875 C 84.926381 79.21875 92 71.787763 92 62.779297 C 92 55.94799 87.899411 50.217256 82.082031 47.746094 L 81.324219 47.423828 L 81.287109 46.603516 C 80.540729 30.134022 67.596276 16.888672 51.541016 16.888672 L 51.539062 16.888672 z M 51.539062 20.888672 C 65.393253 20.888672 76.634896 32.305371 77.291016 46.783203 L 77.289062 46.779297 L 77.4375 50.117188 L 80.515625 51.427734 C 84.906955 53.293126 87.998047 57.547827 87.998047 62.779297 C 87.998047 69.694971 82.658528 75.21875 76.173828 75.21875 L 51.539062 75.21875 L 25.109375 75.21875 C 15.702915 75.21875 7.9980469 67.230513 7.9980469 57.255859 C 7.9980469 47.719033 15.103887 40.06517 23.935547 39.421875 L 23.9375 39.421875 L 27.064453 39.191406 L 28.371094 36.349609 C 32.601004 27.169791 41.300243 20.888672 51.539062 20.888672 z "
transform="matrix(0,-1,-1.0003957,0,438.00245,441.36222)"
id="path4181" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -0,0 +1,31 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Nymea 1.0
import "../components"
Page {
id: root
header: GuhHeader {
text: qsTr("Cloud settings")
onBackPressed: pageStack.pop();
}
ColumnLayout {
anchors { left: parent.left; top: parent.top; right: parent.right }
Label {
Layout.fillWidth: true
text: Engine.jsonRpcClient.cloudConnected
}
SwitchDelegate {
Layout.fillWidth: true
text: qsTr("Cloud connection enabled")
checked: Engine.basicConfiguration.cloudEnabled
onToggled: {
Engine.basicConfiguration.cloudEnabled = checked;
}
}
}
}