diff --git a/libnymea-app-core/configuration/nymeaconfiguration.cpp b/libnymea-app-core/configuration/nymeaconfiguration.cpp index 8fc68999..b9d5e096 100644 --- a/libnymea-app-core/configuration/nymeaconfiguration.cpp +++ b/libnymea-app-core/configuration/nymeaconfiguration.cpp @@ -14,6 +14,7 @@ NymeaConfiguration::NymeaConfiguration(JsonRpcClient *client, QObject *parent): m_client(client), m_tcpServerConfigurations(new ServerConfigurations(this)), m_webSocketServerConfigurations(new ServerConfigurations(this)), + m_webServerConfigurations(new WebServerConfigurations(this)), m_mqttServerConfigurations(new ServerConfigurations(this)), m_mqttPolicies(new MqttPolicies(this)) { @@ -117,6 +118,11 @@ ServerConfigurations *NymeaConfiguration::webSocketServerConfigurations() const return m_webSocketServerConfigurations; } +WebServerConfigurations *NymeaConfiguration::webServerConfigurations() const +{ + return m_webServerConfigurations; +} + ServerConfigurations *NymeaConfiguration::mqttServerConfigurations() const { return m_mqttServerConfigurations; @@ -132,6 +138,13 @@ ServerConfiguration *NymeaConfiguration::createServerConfiguration(const QString return new ServerConfiguration(QUuid::createUuid().toString(), QHostAddress(address), port, authEnabled, sslEnabled); } +WebServerConfiguration *NymeaConfiguration::createWebServerConfiguration(const QString &address, int port, bool authEnabled, bool sslEnabled, const QString &publicFolder) +{ + auto ret = new WebServerConfiguration(QUuid::createUuid().toString(), QHostAddress(address), port, authEnabled, sslEnabled); + ret->setPublicFolder(publicFolder); + return ret; +} + MqttPolicy *NymeaConfiguration::createMqttPolicy() const { return new MqttPolicy(QString(), QString(), QString(), {"#"}, {"#"}); @@ -163,6 +176,20 @@ void NymeaConfiguration::setWebSocketServerConfiguration(ServerConfiguration *co m_client->sendCommand("Configuration.SetWebSocketServerConfiguration", params, this, "setWebSocketConfigReply"); } +void NymeaConfiguration::setWebServerConfiguration(WebServerConfiguration *configuration) +{ + QVariantMap params; + QVariantMap configurationMap; + configurationMap.insert("id", configuration->id()); + configurationMap.insert("address", configuration->address()); + configurationMap.insert("port", configuration->port()); + configurationMap.insert("authenticationEnabled", configuration->authenticationEnabled()); + configurationMap.insert("sslEnabled", configuration->sslEnabled()); + configurationMap.insert("publicFolder", configuration->publicFolder()); + params.insert("configuration", configurationMap); + m_client->sendCommand("Configuration.SetWebServerConfiguration", params, this, "setWebConfigReply"); +} + void NymeaConfiguration::setMqttServerConfiguration(ServerConfiguration *configuration) { QVariantMap params; @@ -190,6 +217,13 @@ void NymeaConfiguration::deleteWebSocketServerConfiguration(const QString &id) m_client->sendCommand("Configuration.DeleteWebSocketServerConfiguration", params, this, "deleteWebSocketConfigReply"); } +void NymeaConfiguration::deleteWebServerConfiguration(const QString &id) +{ + QVariantMap params; + params.insert("id", id); + m_client->sendCommand("Configuration.DeleteWebServerConfiguration", params, this, "deleteWebConfigReply"); +} + void NymeaConfiguration::deleteMqttServerConfiguration(const QString &id) { QVariantMap params; @@ -246,6 +280,15 @@ void NymeaConfiguration::getConfigurationsResponse(const QVariantMap ¶ms) ServerConfiguration *config = new ServerConfiguration(websocketConfigMap.value("id").toString(), QHostAddress(websocketConfigMap.value("address").toString()), websocketConfigMap.value("port").toInt(), websocketConfigMap.value("authenticationEnabled").toBool(), websocketConfigMap.value("sslEnabled").toBool()); m_webSocketServerConfigurations->addConfiguration(config); } + + webServerConfigurations()->clear(); + foreach (const QVariant &webServerVariant, params.value("params").toMap().value("webServerConfigurations").toList()) { + QVariantMap webServerConfigMap = webServerVariant.toMap(); + qDebug() << "**********+ web config" << webServerConfigMap; + WebServerConfiguration* config = new WebServerConfiguration(webServerConfigMap.value("id").toString(), QHostAddress(webServerConfigMap.value("address").toString()), webServerConfigMap.value("port").toInt(), webServerConfigMap.value("authenticationEnabled").toBool(), webServerConfigMap.value("sslEnabled").toBool()); + config->setPublicFolder(webServerConfigMap.value("publicFolder").toString()); + m_webServerConfigurations->addConfiguration(config); + } } void NymeaConfiguration::getAvailableLanguagesResponse(const QVariantMap ¶ms) @@ -289,7 +332,7 @@ void NymeaConfiguration::setDebugServerEnabledResponse(const QVariantMap ¶ms void NymeaConfiguration::setTcpConfigReply(const QVariantMap ¶ms) { - + qDebug() << "Set TCP server config reply" << params; } void NymeaConfiguration::deleteTcpConfigReply(const QVariantMap ¶ms) @@ -300,12 +343,22 @@ void NymeaConfiguration::deleteTcpConfigReply(const QVariantMap ¶ms) void NymeaConfiguration::setWebSocketConfigReply(const QVariantMap ¶ms) { - qDebug() << "set weboscket config reply" << params; + qDebug() << "set websocket config reply" << params; +} + +void NymeaConfiguration::setWebConfigReply(const QVariantMap ¶ms) +{ + qDebug() << "set web server config reply" << params; +} + +void NymeaConfiguration::deleteWebConfigReply(const QVariantMap ¶ms) +{ + qDebug() << "Delete web server config reply" << params; } void NymeaConfiguration::deleteWebSocketConfigReply(const QVariantMap ¶ms) { - + qDebug() << "Delete web socket server config reply" << params; } void NymeaConfiguration::getMqttServerConfigsReply(const QVariantMap ¶ms) @@ -379,6 +432,8 @@ void NymeaConfiguration::notificationReceived(const QVariantMap ¬ification) } if (notif.endsWith("ServerConfigurationChanged")) { ServerConfigurations *configModel = nullptr; + ServerConfiguration *serverConfig = nullptr; + QVariantMap params; if (notif == "Configuration.TcpServerConfigurationChanged") { configModel = m_tcpServerConfigurations; @@ -388,6 +443,10 @@ void NymeaConfiguration::notificationReceived(const QVariantMap ¬ification) configModel = m_webSocketServerConfigurations; params = notification.value("params").toMap().value("webSocketServerConfiguration").toMap(); } + if (notif == "Configuration.WebServerConfigurationChanged") { + configModel = m_webServerConfigurations; + params = notification.value("params").toMap().value("webServerConfiguration").toMap(); + } if (notif == "Configuration.MqttServerConfigurationChanged") { configModel = m_mqttServerConfigurations; params = notification.value("params").toMap().value("mqttServerConfiguration").toMap(); @@ -396,7 +455,6 @@ void NymeaConfiguration::notificationReceived(const QVariantMap ¬ification) return; } - ServerConfiguration *serverConfig = nullptr; for (int i = 0; i < configModel->rowCount(); i++) { ServerConfiguration* config = configModel->get(i); if (config->id() == params.value("id").toString()) { @@ -405,13 +463,21 @@ void NymeaConfiguration::notificationReceived(const QVariantMap ¬ification) } if (!serverConfig) { - serverConfig = new ServerConfiguration(params.value("id").toString()); + if (notif == "Configuration.WebServerConfigurationChanged") { + serverConfig = new WebServerConfiguration(params.value("id").toString()); + } else { + serverConfig = new ServerConfiguration(params.value("id").toString()); + } configModel->addConfiguration(serverConfig); } serverConfig->setAddress(params.value("address").toString()); serverConfig->setPort(params.value("port").toInt()); serverConfig->setAuthenticationEnabled(params.value("authenticationEnabled").toBool()); serverConfig->setSslEnabled(params.value("sslEnabled").toBool()); + if (notif == "Configuration.WebServerConfigurationChanged") { + qobject_cast(serverConfig)->setPublicFolder(params.value("publicFolder").toString()); + } + return; } if (notif == "Configuration.TcpServerConfigurationRemoved") { @@ -422,6 +488,10 @@ void NymeaConfiguration::notificationReceived(const QVariantMap ¬ification) m_webSocketServerConfigurations->removeConfiguration(notification.value("params").toMap().value("id").toString()); return; } + if (notif == "Configuration.WebServerConfigurationRemoved") { + m_webServerConfigurations->removeConfiguration(notification.value("params").toMap().value("id").toString()); + return; + } if (notif == "Configuration.MqttServerConfigurationRemoved") { m_mqttServerConfigurations->removeConfiguration(notification.value("params").toMap().value("id").toString()); return; diff --git a/libnymea-app-core/configuration/nymeaconfiguration.h b/libnymea-app-core/configuration/nymeaconfiguration.h index 83e8f9dd..07c3e50d 100644 --- a/libnymea-app-core/configuration/nymeaconfiguration.h +++ b/libnymea-app-core/configuration/nymeaconfiguration.h @@ -8,6 +8,8 @@ class JsonRpcClient; class ServerConfiguration; class ServerConfigurations; +class WebServerConfiguration; +class WebServerConfigurations; class MqttPolicy; class MqttPolicies; @@ -28,6 +30,7 @@ class NymeaConfiguration : public JsonHandler Q_PROPERTY(ServerConfigurations* tcpServerConfigurations READ tcpServerConfigurations CONSTANT) Q_PROPERTY(ServerConfigurations* webSocketServerConfigurations READ webSocketServerConfigurations CONSTANT) + Q_PROPERTY(WebServerConfigurations* webServerConfigurations READ webServerConfigurations CONSTANT) Q_PROPERTY(ServerConfigurations* mqttServerConfigurations READ mqttServerConfigurations CONSTANT) Q_PROPERTY(MqttPolicies* mqttPolicies READ mqttPolicies CONSTANT) @@ -56,18 +59,22 @@ public: ServerConfigurations *tcpServerConfigurations() const; ServerConfigurations *webSocketServerConfigurations() const; + WebServerConfigurations *webServerConfigurations() const; ServerConfigurations *mqttServerConfigurations() const; MqttPolicies *mqttPolicies() const; Q_INVOKABLE ServerConfiguration* createServerConfiguration(const QString &address = "0.0.0.0", int port = 0, bool authEnabled = false, bool sslEnabled = false); + Q_INVOKABLE WebServerConfiguration* createWebServerConfiguration(const QString &address = "0.0.0.0", int port = 0, bool authEnabled = false, bool sslEnabled = false, const QString &publicFolder = QString()); Q_INVOKABLE MqttPolicy* createMqttPolicy() const; Q_INVOKABLE void setTcpServerConfiguration(ServerConfiguration *configuration); Q_INVOKABLE void setWebSocketServerConfiguration(ServerConfiguration *configuration); + Q_INVOKABLE void setWebServerConfiguration(WebServerConfiguration *configuration); Q_INVOKABLE void setMqttServerConfiguration(ServerConfiguration *configuration); Q_INVOKABLE void deleteTcpServerConfiguration(const QString &id); Q_INVOKABLE void deleteWebSocketServerConfiguration(const QString &id); + Q_INVOKABLE void deleteWebServerConfiguration(const QString &id); Q_INVOKABLE void deleteMqttServerConfiguration(const QString &id); Q_INVOKABLE void updateMqttPolicy(MqttPolicy* policy); @@ -87,6 +94,8 @@ private: Q_INVOKABLE void deleteTcpConfigReply(const QVariantMap ¶ms); Q_INVOKABLE void setWebSocketConfigReply(const QVariantMap ¶ms); Q_INVOKABLE void deleteWebSocketConfigReply(const QVariantMap ¶ms); + Q_INVOKABLE void setWebConfigReply(const QVariantMap ¶ms); + Q_INVOKABLE void deleteWebConfigReply(const QVariantMap ¶ms); Q_INVOKABLE void getMqttServerConfigsReply(const QVariantMap ¶ms); Q_INVOKABLE void setMqttConfigReply(const QVariantMap ¶ms); Q_INVOKABLE void deleteMqttConfigReply(const QVariantMap ¶ms); @@ -118,6 +127,7 @@ private: ServerConfigurations *m_tcpServerConfigurations = nullptr; ServerConfigurations *m_webSocketServerConfigurations = nullptr; + WebServerConfigurations* m_webServerConfigurations = nullptr; ServerConfigurations *m_mqttServerConfigurations = nullptr; MqttPolicies *m_mqttPolicies = nullptr; diff --git a/libnymea-app-core/configuration/serverconfiguration.cpp b/libnymea-app-core/configuration/serverconfiguration.cpp index 030731b0..9600678b 100644 --- a/libnymea-app-core/configuration/serverconfiguration.cpp +++ b/libnymea-app-core/configuration/serverconfiguration.cpp @@ -73,3 +73,23 @@ ServerConfiguration *ServerConfiguration::clone() const ServerConfiguration *ret = new ServerConfiguration(m_id, m_hostAddress, m_port, m_authEnabled, m_sslEnabled); return ret; } + +QString WebServerConfiguration::publicFolder() const +{ + return m_publicFolder; +} + +void WebServerConfiguration::setPublicFolder(const QString &publicFolder) +{ + if (m_publicFolder != publicFolder) { + m_publicFolder = publicFolder; + emit publicFolderChanged(); + } +} + +ServerConfiguration *WebServerConfiguration::clone() const +{ + WebServerConfiguration *ret = new WebServerConfiguration(id(), QHostAddress(address()), port(), authenticationEnabled(), sslEnabled()); + ret->setPublicFolder(m_publicFolder); + return ret; +} diff --git a/libnymea-app-core/configuration/serverconfiguration.h b/libnymea-app-core/configuration/serverconfiguration.h index 2d067928..926ea1a7 100644 --- a/libnymea-app-core/configuration/serverconfiguration.h +++ b/libnymea-app-core/configuration/serverconfiguration.h @@ -31,7 +31,7 @@ public: bool sslEnabled() const; void setSslEnabled(bool sslEnabled); - Q_INVOKABLE ServerConfiguration* clone() const; + Q_INVOKABLE virtual ServerConfiguration* clone() const; signals: void addressChanged(); @@ -47,4 +47,24 @@ private: bool m_sslEnabled; }; +class WebServerConfiguration: public ServerConfiguration +{ + Q_OBJECT + Q_PROPERTY(QString publicFolder READ publicFolder NOTIFY publicFolderChanged) +public: + explicit WebServerConfiguration(const QString &id, const QHostAddress &address = QHostAddress(), int port = 0, bool authEnabled = false, bool sslEnabled = false, QObject *parent = nullptr) + : ServerConfiguration(id, address, port, authEnabled, sslEnabled, parent) {} + + QString publicFolder() const; + void setPublicFolder(const QString &publicFolder); + + Q_INVOKABLE ServerConfiguration* clone() const override; + +signals: + void publicFolderChanged(); + +private: + QString m_publicFolder; +}; + #endif // SERVERCONFIGURATION_H diff --git a/libnymea-app-core/configuration/serverconfigurations.h b/libnymea-app-core/configuration/serverconfigurations.h index c099ebd1..f8b844ad 100644 --- a/libnymea-app-core/configuration/serverconfigurations.h +++ b/libnymea-app-core/configuration/serverconfigurations.h @@ -4,7 +4,7 @@ #include #include -class ServerConfiguration; +#include "serverconfiguration.h" class ServerConfigurations : public QAbstractListModel { @@ -22,6 +22,7 @@ public: Q_ENUM(Roles) explicit ServerConfigurations(QObject *parent = nullptr); + virtual ~ServerConfigurations() override = default; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; @@ -32,13 +33,25 @@ public: void clear(); - Q_INVOKABLE ServerConfiguration* get(int index) const; + Q_INVOKABLE virtual ServerConfiguration* get(int index) const; signals: void countChanged(); -private: +protected: QList m_list; }; + +class WebServerConfigurations: public ServerConfigurations +{ + Q_OBJECT +public: + WebServerConfigurations(QObject *parent = nullptr): ServerConfigurations(parent) {} + + Q_INVOKABLE WebServerConfiguration* getWebServerConfiguration(int index) const { + return dynamic_cast(m_list.at(index)); + } +}; + #endif // SERVERCONFIGURATIONS_H diff --git a/libnymea-app-core/connection/nymeahost.cpp b/libnymea-app-core/connection/nymeahost.cpp index abb86e00..411981ef 100644 --- a/libnymea-app-core/connection/nymeahost.cpp +++ b/libnymea-app-core/connection/nymeahost.cpp @@ -228,6 +228,11 @@ QUrl Connection::url() const return m_url; } +QString Connection::hostAddress() const +{ + return m_url.host(); +} + Connection::BearerType Connection::bearerType() const { return m_bearerType; diff --git a/libnymea-app-core/connection/nymeahost.h b/libnymea-app-core/connection/nymeahost.h index 8f3b43bf..cd1faae4 100644 --- a/libnymea-app-core/connection/nymeahost.h +++ b/libnymea-app-core/connection/nymeahost.h @@ -32,6 +32,7 @@ class Connection: public QObject { Q_OBJECT Q_PROPERTY(QUrl url READ url CONSTANT) + Q_PROPERTY(QString hostAddress READ hostAddress CONSTANT) Q_PROPERTY(BearerType bearerType READ bearerType CONSTANT) Q_PROPERTY(bool secure READ secure CONSTANT) Q_PROPERTY(QString displayName READ displayName CONSTANT) @@ -55,6 +56,7 @@ public: ~Connection(); QUrl url() const; + QString hostAddress() const; BearerType bearerType() const; bool secure() const; QString displayName() const; diff --git a/libnymea-app-core/libnymea-app-core.h b/libnymea-app-core/libnymea-app-core.h index b21ffe36..f11716e8 100644 --- a/libnymea-app-core/libnymea-app-core.h +++ b/libnymea-app-core/libnymea-app-core.h @@ -155,6 +155,8 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "NymeaConfiguration", "Get it from Engine"); qmlRegisterUncreatableType(uri, 1, 0, "ServerConfiguration", "Get it from NymeaConfiguration"); qmlRegisterUncreatableType(uri, 1, 0, "ServerConfigurations", "Get it from NymeaConfiguration"); + qmlRegisterUncreatableType(uri, 1, 0, "WebServerConfiguration", "Get it from NymeaConfiguration"); + qmlRegisterUncreatableType(uri, 1, 0, "WebServerConfigurations", "Get it from NymeaConfiguration"); qmlRegisterUncreatableType(uri, 1, 0, "MqttPolicy", "Get it from NymeaConfiguration"); qmlRegisterUncreatableType(uri, 1, 0, "MqttPolicies", "Get it from NymeaConfiguration"); diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index e5e9efc1..afb8e2d4 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -107,8 +107,8 @@ ui/images/navigation-menu.svg ui/images/network-secure.svg ui/images/network-vpn.svg - ui/images/network-wifi-symbolic.svg - ui/images/network-wired-symbolic.svg + ui/images/network-wifi.svg + ui/images/network-wired.svg ui/images/next.svg ui/images/nm-signal-00-secure.svg ui/images/nm-signal-00.svg @@ -172,5 +172,10 @@ ui/images/dial.svg ui/images/ventilation.svg ui/images/edit-copy.svg + ui/images/stock_website.svg + ui/images/sdk.svg + ui/images/network-wifi-offline.svg + ui/images/network-wired-offline.svg + ui/images/preferences-look-and-feel.svg diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index a4bcf29c..a05b924a 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -172,5 +172,11 @@ ui/fonts/Oswald-Medium.ttf ui/fonts/Oswald-Regular.ttf ui/fonts/Oswald-SemiBold.ttf + ui/system/WebServerSettingsPage.qml + ui/system/WebServerConfigurationDialog.qml + ui/system/DeveloperTools.qml + ui/system/GeneralSettingsPage.qml + ui/components/Imprint.qml + ui/appsettings/LookAndFeelSettingsPage.qml diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index dfb91162..d49177ec 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -179,7 +179,7 @@ ApplicationWindow { case "weather": return Qt.resolvedUrl("images/weather-app-symbolic.svg") case "gateway": - return Qt.resolvedUrl("images/network-wired-symbolic.svg") + return Qt.resolvedUrl("images/network-wired.svg") case "notifications": return Qt.resolvedUrl("images/messaging-app-symbolic.svg") case "inputtrigger": diff --git a/nymea-app/ui/SettingsPage.qml b/nymea-app/ui/SettingsPage.qml index 7374af9f..5a8695cf 100644 --- a/nymea-app/ui/SettingsPage.qml +++ b/nymea-app/ui/SettingsPage.qml @@ -11,190 +11,186 @@ Page { text: qsTr("Box settings") backButtonVisible: true onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: { + switch (engine.connection.currentConnection.bearerType) { + case Connection.BearerTypeLan: + case Connection.BearerTypeWan: + if (engine.connection.availableBearerTypes & NymeaConnection.BearerTypeEthernet != NymeaConnection.BearerTypeNone) { + return "../images/network-wired-offline.svg" + } + return "../images/network-wifi-offline.svg"; + case Connection.BearerTypeBluetooth: + return "../images/network-wifi-offline.svg"; + case Connection.BearerTypeCloud: + return "../images/cloud-offline.svg" + } + return "" + } + onClicked: { + tabSettings.lastConnectedHost = ""; + engine.connection.disconnect(); + } + } } Flickable { anchors.fill: parent - contentHeight: settingsColumn.implicitHeight - interactive: contentHeight > height + contentHeight: layout.implicitHeight - ColumnLayout { - id: settingsColumn - anchors { left: parent.left; right: parent.right; top: parent.top } + GridLayout { + id: layout + property bool isGrid: columns > 1 + anchors { left: parent.left; top: parent.top; right: parent.right; margins: isGrid ? app.margins : 0 } + columns: Math.max(1, Math.floor(parent.width / 300)) + rowSpacing: isGrid ? app.margins : 0 + columnSpacing: isGrid ? app.margins : 0 - ColumnLayout { + Pane { Layout.fillWidth: true - Layout.margins: app.margins - - Label { - Layout.fillWidth: true - text: qsTr("Connected to:") - color: Material.accent - } - RowLayout { - Layout.fillWidth: true - - Label { - Layout.fillWidth: true - elide: Text.ElideMiddle - text: engine.connection.currentConnection.url - } - Button { - text: qsTr("Disconnect") - onClicked: { - tabSettings.lastConnectedHost = ""; - engine.connection.disconnect(); - } - } + Material.elevation: layout.isGrid ? 1 : 0 + padding: 0 + MeaListItemDelegate { + width: parent.width + iconName: "../images/configure.svg" + text: qsTr("General") + subText: qsTr("Change system name and time zone") + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("system/GeneralSettingsPage.qml")) } } - ThinDivider {} - - RowLayout { + Pane { Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - spacing: app.margins - Label { - text: qsTr("Name") - } - TextField { - id: nameTextField - Layout.fillWidth: true - text: engine.nymeaConfiguration.serverName - } - Button { - text: qsTr("OK") - visible: nameTextField.displayText !== engine.nymeaConfiguration.serverName - onClicked: engine.nymeaConfiguration.serverName = nameTextField.displayText + Material.elevation: layout.isGrid ? 1 : 0 + + padding: 0 + MeaListItemDelegate { + width: parent.width + iconName: "../images/logs.svg" + text: qsTr("Log viewer") + subText: qsTr("View system log") + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("system/LogViewerPage.qml")) } } - RowLayout { + Pane { Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - spacing: app.margins - visible: !engine.jsonRpcClient.ensureServerVersion("1.14") + Material.elevation: layout.isGrid ? 1 : 0 - Label { - Layout.fillWidth: true - text: qsTr("Language") - } - ComboBox { - id: languageBox - Layout.fillWidth: true - model: engine.nymeaConfiguration.availableLanguages - currentIndex: model.indexOf(engine.nymeaConfiguration.language) - contentItem: Label { - leftPadding: app.margins / 2 - text: Qt.locale(languageBox.displayText).nativeLanguageName + " (" + Qt.locale(languageBox.displayText).nativeCountryName + ")" - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - - delegate: ItemDelegate { - width: languageBox.width - contentItem: Label { - text: Qt.locale(modelData).nativeLanguageName + " (" + Qt.locale(modelData).nativeCountryName + ")" - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - highlighted: languageBox.highlightedIndex === index - } - onActivated: { - engine.nymeaConfiguration.language = currentText; - } + padding: 0 + MeaListItemDelegate { + width: parent.width + iconName: "../images/cloud.svg" + text: qsTr("Cloud") + subText: qsTr("Connect this box to %1:cloud").arg(app.systemName) + prominentSubText: false + wrapTexts: false + visible: engine.jsonRpcClient.ensureServerVersion("1.9") + onClicked: pageStack.push(Qt.resolvedUrl("system/CloudSettingsPage.qml")) } } - RowLayout { + Pane { Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - spacing: app.margins - Label { - Layout.fillWidth: true - text: qsTr("Time zone") - } - ComboBox { - Layout.minimumWidth: 200 - model: engine.nymeaConfiguration.timezones - currentIndex: model.indexOf(engine.nymeaConfiguration.timezone) - onActivated: { - engine.nymeaConfiguration.timezone = currentText; - } + Material.elevation: layout.isGrid ? 1 : 0 + + padding: 0 + MeaListItemDelegate { + width: parent.width + iconName: "../images/network-vpn.svg" + text: qsTr("API interfaces") + prominentSubText: false + wrapTexts: false + subText: qsTr("Configure how clients interact with this box") + onClicked: pageStack.push(Qt.resolvedUrl("system/ConnectionInterfacesPage.qml")) } } - ColumnLayout { + Pane { Layout.fillWidth: true + Material.elevation: layout.isGrid ? 1 : 0 - RowLayout { - Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - spacing: app.margins - Label { - text: qsTr("Debug server enabled") - Layout.fillWidth: true - } - Switch { - id: debugServerEnabledSwitch - checked: engine.nymeaConfiguration.debugServerEnabled - onClicked: engine.nymeaConfiguration.debugServerEnabled = checked - } + padding: 0 + MeaListItemDelegate { + width: parent.width + iconName: "../images/mqtt.svg" + text: qsTr("MQTT broker") + subText: qsTr("Configure the MQTT broker") + prominentSubText: false + wrapTexts: false + visible: engine.jsonRpcClient.ensureServerVersion("1.11") + onClicked: pageStack.push(Qt.resolvedUrl("system/MqttBrokerSettingsPage.qml")) } + } - Button { - id: debugServerButton - Layout.fillWidth: true - Layout.margins: app.margins - visible: debugServerEnabledSwitch.checked - text: qsTr("Open debug interface") - onClicked: Qt.openUrlExternally("http://" + engine.connection.hostAddress + "/debug") + Pane { + Layout.fillWidth: true + Material.elevation: layout.isGrid ? 1 : 0 + + padding: 0 + MeaListItemDelegate { + width: parent.width + iconName: "../images/stock_website.svg" + text: qsTr("Web server") + subText: qsTr("Configure the web server") + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("system/WebServerSettingsPage.qml")) } } - MeaListItemDelegate { + Pane { Layout.fillWidth: true - iconName: "../images/logs.svg" - text: qsTr("Log viewer") - onClicked: pageStack.push(Qt.resolvedUrl("system/LogViewerPage.qml")) + Material.elevation: layout.isGrid ? 1 : 0 + + padding: 0 + MeaListItemDelegate { + width: parent.width + iconName: "../images/plugin.svg" + text: qsTr("Plugins") + subText: qsTr("List and cofigure installed plugins") + prominentSubText: false + wrapTexts: false + onClicked:pageStack.push(Qt.resolvedUrl("system/PluginsPage.qml")) + } } - MeaListItemDelegate { + + Pane { Layout.fillWidth: true - iconName: "../images/cloud.svg" - text: qsTr("Cloud") - visible: engine.jsonRpcClient.ensureServerVersion("1.9") - onClicked: pageStack.push(Qt.resolvedUrl("system/CloudSettingsPage.qml")) + Material.elevation: layout.isGrid ? 1 : 0 + + padding: 0 + MeaListItemDelegate { + width: parent.width + iconName: "../images/sdk.svg" + text: qsTr("Developer tools") + subText: qsTr("Access tools for debugging and error reporting") + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("system/DeveloperTools.qml")) + } } - MeaListItemDelegate { + + Pane { Layout.fillWidth: true - iconName: "../images/network-vpn.svg" - text: qsTr("Server interfaces") - onClicked: pageStack.push(Qt.resolvedUrl("system/ConnectionInterfacesPage.qml")) - } - MeaListItemDelegate { - Layout.fillWidth: true - iconName: "../images/mqtt.svg" - text: qsTr("MQTT broker") - visible: engine.jsonRpcClient.ensureServerVersion("1.11") - onClicked: pageStack.push(Qt.resolvedUrl("system/MqttBrokerSettingsPage.qml")) - } - MeaListItemDelegate { - Layout.fillWidth: true - iconName: "../images/plugin.svg" - text: qsTr("Plugins") - onClicked:pageStack.push(Qt.resolvedUrl("system/PluginsPage.qml")) - } - MeaListItemDelegate { - Layout.fillWidth: true - iconName: "../images/info.svg" - text: qsTr("About %1:core").arg(app.systemName) - onClicked: pageStack.push(Qt.resolvedUrl("system/AboutNymeaPage.qml")) + Material.elevation: layout.isGrid ? 1 : 0 + + padding: 0 + MeaListItemDelegate { + width: parent.width + iconName: "../images/info.svg" + text: qsTr("About %1:core").arg(app.systemName) + subText: qsTr("Find server UUID and versions") + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("system/AboutNymeaPage.qml")) + } } } } diff --git a/nymea-app/ui/appsettings/AboutPage.qml b/nymea-app/ui/appsettings/AboutPage.qml index afe61970..8e945169 100644 --- a/nymea-app/ui/appsettings/AboutPage.qml +++ b/nymea-app/ui/appsettings/AboutPage.qml @@ -15,188 +15,27 @@ Page { Flickable { anchors.fill: parent - contentHeight: aboutColumn.implicitHeight + contentHeight: imprint.implicitHeight - ColumnLayout { - id: aboutColumn + Imprint { + id: imprint width: parent.width + title: app.appName + githubLink: "https://github.com/guh/nymea-app" - RowLayout { + MeaListItemDelegate { Layout.fillWidth: true - Layout.margins: app.margins - spacing: app.margins - - Image { - id: logo - Layout.preferredHeight: app.iconSize * 2 - Layout.preferredWidth: height - fillMode: Image.PreserveAspectFit - source: "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle) - - MouseArea { - anchors.fill: parent - property int clickCounter: 0 - onClicked: { - clickCounter++; - if (clickCounter >= 10) { - settings.showHiddenOptions = !settings.showHiddenOptions - var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml")); - var text = settings.showHiddenOptions - ? qsTr("Developer options are now enabled. If you have found this by accident, it is most likely not of any use for you. It will just enable some nerdy developer gibberish in the app. Tap the icon another 10 times to disable it again.") - : qsTr("Developer options are now disabled.") - var popup = dialog.createObject(app, {headerIcon: "../images/dialog-warning-symbolic.svg", title: qsTr("Howdy cowboy!"), text: text}) - popup.open(); - clickCounter = 0; - } - } - } - } - - GridLayout { - Layout.fillWidth: true - columns: 2 - - Label { - text: qsTr("App version:") - } - Label { - text: appVersion - } - Label { - text: qsTr("Qt version:") - } - Label { - text: qtVersion - } - } - } - - ThinDivider {} - - Label { - Layout.fillWidth: true - Layout.topMargin: app.margins - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - wrapMode: Text.WordWrap - font.bold: true - text: "Copyright (C) 2019 guh GmbH" - } - - Label { - Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - wrapMode: Text.WordWrap - text: qsTr("nymea is a registered trademark of guh GmbH.") - } - - Label { - Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - wrapMode: Text.WordWrap - text: qsTr("Licensed under the terms of the GNU general public license, version 2. Please visit the GitHub page for source code and build instructions.") - } - - ColumnLayout { - Layout.fillWidth: true - - MeaListItemDelegate { - Layout.fillWidth: true - text: qsTr("Visit the nymea website") - onClicked: { - Qt.openUrlExternally("https://nymea.io") - } - } - - MeaListItemDelegate { - Layout.fillWidth: true - text: qsTr("Visit GitHub page") - onClicked: { - Qt.openUrlExternally("https://github.com/guh/nymea-app") - } - } - - MeaListItemDelegate { - Layout.fillWidth: true - text: qsTr("View license text") - onClicked: { - pageStack.push(licenseTextComponent) - } - } - MeaListItemDelegate { - Layout.fillWidth: true - text: qsTr("Privacy policy") - onClicked: { - Qt.openUrlExternally(app.privacyPolicyUrl) - } - } - } - - - ThinDivider { } - - RowLayout { - Layout.fillWidth: true - Layout.margins: app.margins - spacing: app.margins - - Image { - Layout.preferredHeight: app.iconSize * 2 - Layout.preferredWidth: height - fillMode: Image.PreserveAspectFit - source: "qrc:/ui/images/Built_with_Qt_RGB_logo_vertical.svg" - sourceSize.width: app.iconSize * 2 - sourceSize.height: app.iconSize * 2 - } - - Label { - Layout.fillWidth: true - text: qsTr("Qt is a registered trademark of The Qt Company Ltd. and its subsidiaries.") - wrapMode: Text.WordWrap - } + text: qsTr("App version:") + subText: appVersion + progressive: false + prominentSubText: false } MeaListItemDelegate { Layout.fillWidth: true - text: qsTr("Visit the Qt website") - onClicked: { - Qt.openUrlExternally("https://www.qt.io") - } - } - } - } - - - Component { - id: licenseTextComponent - Page { - header: GuhHeader { - text: qsTr("License text") - onBackPressed: pageStack.pop() - } - Flickable { - anchors.fill: parent - contentHeight: licenseText.implicitHeight - clip: true - ScrollBar.vertical: ScrollBar {} - TextArea { - id: licenseText - wrapMode: Text.WordWrap - font.pixelSize: app.smallFont - anchors { left: parent.left; right: parent.right; margins: app.margins } - readOnly: true - Component.onCompleted: { - var xhr = new XMLHttpRequest; - xhr.open("GET", "../../LICENSE"); - xhr.onreadystatechange = function() { - if (xhr.readyState === XMLHttpRequest.DONE) { - text = xhr.responseText.replace(/(^\ *)/gm, "").replace(/(\n\n)/gm,"\t").replace(/(\n)/gm, " ").replace(/(\t)/gm, "\n\n"); - } - }; - xhr.send(); - } - } + text: qsTr("Qt version:") + subText: qtVersion + progressive: false + prominentSubText: false } } } diff --git a/nymea-app/ui/appsettings/AppSettingsPage.qml b/nymea-app/ui/appsettings/AppSettingsPage.qml index 9d26fb33..96d685f2 100644 --- a/nymea-app/ui/appsettings/AppSettingsPage.qml +++ b/nymea-app/ui/appsettings/AppSettingsPage.qml @@ -15,131 +15,72 @@ Page { Flickable { anchors.fill: parent - contentHeight: contentColumn.implicitHeight - interactive: contentHeight > height + contentHeight: layout.implicitHeight - ColumnLayout { - id: contentColumn - width: parent.width + GridLayout { + id: layout + property bool isGrid: columns > 1 + anchors { left: parent.left; top: parent.top; right: parent.right; margins: isGrid ? app.margins : 0 } + columns: Math.max(1, Math.floor(parent.width / 300)) + rowSpacing: isGrid ? app.margins : 0 + columnSpacing: isGrid ? app.margins : 0 - RowLayout { - Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins - Label { - Layout.fillWidth: true - text: qsTr("View mode") - } - ComboBox { - model: [qsTr("Windowed"), qsTr("Maximized"), qsTr("Fullscreen")] - currentIndex: { - switch (settings.viewMode) { - case ApplicationWindow.Windowed: - return 0; - case ApplicationWindow.Maximized: - return 1; - case ApplicationWindow.FullScreen: - return 2; - } - } - - onCurrentIndexChanged: { - switch (currentIndex) { - case 0: - settings.viewMode = ApplicationWindow.Windowed; - break; - case 1: - settings.viewMode = ApplicationWindow.Maximized; - break; - case 2: - settings.viewMode = ApplicationWindow.FullScreen; - } - } + Pane { + Layout.fillWidth: true + Material.elevation: layout.isGrid ? 1 : 0 + padding: 0 + MeaListItemDelegate { + width: parent.width + text: qsTr("Look & feel") + subText: qsTr("Customize the app's look and behavior") + iconName: "../images/preferences-look-and-feel.svg" + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("LookAndFeelSettingsPage.qml")) } } - RowLayout { - Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins - visible: appBranding.length === 0 - Label { - Layout.fillWidth: true - text: "Style" - } - ComboBox { - model: styleController.allStyles - currentIndex: styleController.allStyles.indexOf(styleController.currentStyle) - - onActivated: { - styleController.currentStyle = model[index] - } - } - - Connections { - target: styleController - onCurrentStyleChanged: { - var popup = styleChangedDialog.createObject(root) - popup.open() - } + Pane { + Layout.fillWidth: true + Material.elevation: layout.isGrid ? 1 : 0 + padding: 0 + MeaListItemDelegate { + width: parent.width + text: qsTr("Cloud login") + subText: qsTr("Log into %1:cloud and manage connected boxes").arg(app.systemName) + iconName: "../images/cloud.svg" + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("CloudLoginPage.qml")) } } - - CheckDelegate { + Pane { Layout.fillWidth: true - text: qsTr("Return to home on idle") - checked: settings.returnToHome - onClicked: settings.returnToHome = checked + Material.elevation: layout.isGrid ? 1 : 0 + padding: 0 + MeaListItemDelegate { + width: parent.width + visible: settings.showHiddenOptions + text: qsTr("Developer options") + subText: qsTr("Yeehaaa!") + iconName: "../images/sdk.svg" + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("DeveloperOptionsPage.qml")) + } } - CheckDelegate { + Pane { Layout.fillWidth: true - text: qsTr("Show connection tabs") - checked: settings.showConnectionTabs - onClicked: settings.showConnectionTabs = checked - } - ThinDivider {} - MeaListItemDelegate { - Layout.fillWidth: true - text: qsTr("Cloud login") - iconName: "../images/cloud.svg" - onClicked: pageStack.push(Qt.resolvedUrl("CloudLoginPage.qml")) - } - MeaListItemDelegate { - Layout.fillWidth: true - text: qsTr("About %1").arg(app.appName) - iconName: "../images/info.svg" - onClicked: pageStack.push(Qt.resolvedUrl("AboutPage.qml")) - } - MeaListItemDelegate { - Layout.fillWidth: true - Layout.bottomMargin: app.margins - visible: settings.showHiddenOptions - text: qsTr("Developer options") - iconName: "../images/configure.svg" - onClicked: pageStack.push(Qt.resolvedUrl("DeveloperOptionsPage.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.") + Material.elevation: layout.isGrid ? 1 : 0 + padding: 0 + MeaListItemDelegate { + width: parent.width + text: qsTr("About %1").arg(app.appName) + subText: qsTr("Find app versions and licence information") + iconName: "../images/info.svg" + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("AboutPage.qml")) } } } diff --git a/nymea-app/ui/appsettings/LookAndFeelSettingsPage.qml b/nymea-app/ui/appsettings/LookAndFeelSettingsPage.qml new file mode 100644 index 00000000..0ed3101f --- /dev/null +++ b/nymea-app/ui/appsettings/LookAndFeelSettingsPage.qml @@ -0,0 +1,118 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" + +Page { + id: root + header: GuhHeader { + text: qsTr("Look and feel") + backButtonVisible: true + onBackPressed: pageStack.pop() + } + + ColumnLayout { + id: contentColumn + width: parent.width + + RowLayout { + Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins + Label { + Layout.fillWidth: true + text: qsTr("View mode") + } + ComboBox { + model: [qsTr("Windowed"), qsTr("Maximized"), qsTr("Fullscreen")] + currentIndex: { + switch (settings.viewMode) { + case ApplicationWindow.Windowed: + return 0; + case ApplicationWindow.Maximized: + return 1; + case ApplicationWindow.FullScreen: + return 2; + } + } + + onCurrentIndexChanged: { + switch (currentIndex) { + case 0: + settings.viewMode = ApplicationWindow.Windowed; + break; + case 1: + settings.viewMode = ApplicationWindow.Maximized; + break; + case 2: + settings.viewMode = ApplicationWindow.FullScreen; + } + } + } + } + + RowLayout { + Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + visible: appBranding.length === 0 + Label { + Layout.fillWidth: true + text: "Style" + } + ComboBox { + model: styleController.allStyles + currentIndex: styleController.allStyles.indexOf(styleController.currentStyle) + + onActivated: { + styleController.currentStyle = model[index] + } + } + + Connections { + target: styleController + onCurrentStyleChanged: { + var popup = styleChangedDialog.createObject(root) + popup.open() + } + } + } + + CheckDelegate { + Layout.fillWidth: true + text: qsTr("Return to home on idle") + checked: settings.returnToHome + onClicked: settings.returnToHome = checked + } + CheckDelegate { + Layout.fillWidth: true + text: qsTr("Show connection tabs") + checked: settings.showConnectionTabs + onClicked: settings.showConnectionTabs = checked + } + } + + 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.") + } + } + } + } +} diff --git a/nymea-app/ui/components/Imprint.qml b/nymea-app/ui/components/Imprint.qml new file mode 100644 index 00000000..96f83cf1 --- /dev/null +++ b/nymea-app/ui/components/Imprint.qml @@ -0,0 +1,214 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Nymea 1.0 + +Item { + id: root + implicitHeight: aboutColumn.implicitHeight + + property alias title: titleLabel.text + property url githubLink + default property alias content: contentGrid.data + + ColumnLayout { + id: aboutColumn + anchors { left: parent.left; right: parent.right; top: parent.top } + + RowLayout { + Layout.fillWidth: true + Layout.margins: app.margins + spacing: app.margins + + Image { + id: logo + Layout.preferredHeight: app.iconSize * 2 + Layout.preferredWidth: height + fillMode: Image.PreserveAspectFit + source: "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle) + + MouseArea { + anchors.fill: parent + property int clickCounter: 0 + onClicked: { + clickCounter++; + if (clickCounter >= 10) { + settings.showHiddenOptions = !settings.showHiddenOptions + var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml")); + var text = settings.showHiddenOptions + ? qsTr("Developer options are now enabled. If you have found this by accident, it is most likely not of any use for you. It will just enable some nerdy developer gibberish in the app. Tap the icon another 10 times to disable it again.") + : qsTr("Developer options are now disabled.") + var popup = dialog.createObject(app, {headerIcon: "../images/dialog-warning-symbolic.svg", title: qsTr("Howdy cowboy!"), text: text}) + popup.open(); + clickCounter = 0; + } + } + } + } + + Label { + id: titleLabel + font.pixelSize: app.largeFont + } + } + + ThinDivider {} + + GridLayout { + id: contentGrid + Layout.fillWidth: true + columns: Math.max(1, root.width / 300) + } + + ThinDivider {} + + Label { + Layout.fillWidth: true + Layout.topMargin: app.margins + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + wrapMode: Text.WordWrap + font.bold: true + text: "Copyright (C) 2019 guh GmbH" + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + wrapMode: Text.WordWrap + text: qsTr("nymea is a registered trademark of guh GmbH.") + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + wrapMode: Text.WordWrap + font.pixelSize: app.smallFont + text: qsTr("Licensed under the terms of the GNU general public license, version 2. Please visit the GitHub page for source code and build instructions.") + } + + ColumnLayout { + Layout.fillWidth: true + + MeaListItemDelegate { + Layout.fillWidth: true + iconName: "../images/stock_website.svg" + text: qsTr("Visit the nymea website") + subText: "https://nymea.io" + prominentSubText: false + wrapTexts: false + onClicked: { + Qt.openUrlExternally("https://nymea.io") + } + } + + MeaListItemDelegate { + Layout.fillWidth: true + iconName: "../images/stock_website.svg" + text: qsTr("Visit GitHub page") + subText: root.githubLink + prominentSubText: false + wrapTexts: false + onClicked: { + Qt.openUrlExternally(root.githubLink) + } + } + + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("View privacy policy") + iconName: "../images/stock_website.svg" + subText: app.privacyPolicyUrl + prominentSubText: false + wrapTexts: false + onClicked: { + Qt.openUrlExternally(app.privacyPolicyUrl) + } + } + + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("View license text") + iconName: "../images/logs.svg" + subText: "GNU General Public License v2" + prominentSubText: false + wrapTexts: false + onClicked: { + pageStack.push(licenseTextComponent) + } + } + } + + + ThinDivider { } + + RowLayout { + Layout.fillWidth: true + Layout.margins: app.margins + spacing: app.margins + + Image { + Layout.preferredHeight: app.iconSize * 2 + Layout.preferredWidth: height + fillMode: Image.PreserveAspectFit + source: "qrc:/ui/images/Built_with_Qt_RGB_logo_vertical.svg" + sourceSize.width: app.iconSize * 2 + sourceSize.height: app.iconSize * 2 + } + + Label { + Layout.fillWidth: true + text: qsTr("Qt is a registered trademark of The Qt Company Ltd. and its subsidiaries.") + wrapMode: Text.WordWrap + } + } + MeaListItemDelegate { + Layout.fillWidth: true + iconName: "../images/stock_website.svg" + text: qsTr("Visit the Qt website") + subText: "https://www.qt.io" + prominentSubText: false + wrapTexts: false + onClicked: { + Qt.openUrlExternally("https://www.qt.io") + } + } + } + + + Component { + id: licenseTextComponent + Page { + header: GuhHeader { + text: qsTr("License text") + onBackPressed: pageStack.pop() + } + Flickable { + anchors.fill: parent + contentHeight: licenseText.implicitHeight + clip: true + ScrollBar.vertical: ScrollBar {} + TextArea { + id: licenseText + wrapMode: Text.WordWrap + font.pixelSize: app.smallFont + anchors { left: parent.left; right: parent.right; margins: app.margins } + readOnly: true + Component.onCompleted: { + var xhr = new XMLHttpRequest; + xhr.open("GET", "../../LICENSE"); + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + text = xhr.responseText.replace(/(^\ *)/gm, "").replace(/(\n\n)/gm,"\t").replace(/(\n)/gm, " ").replace(/(\t)/gm, "\n\n"); + } + }; + xhr.send(); + } + } + } + } + } +} + diff --git a/nymea-app/ui/connection/ConnectPage.qml b/nymea-app/ui/connection/ConnectPage.qml index a6c08298..10a5a868 100644 --- a/nymea-app/ui/connection/ConnectPage.qml +++ b/nymea-app/ui/connection/ConnectPage.qml @@ -148,9 +148,9 @@ Page { case Connection.BearerTypeLan: case Connection.BearerTypeWan: if (engine.connection.availableBearerTypes & NymeaConnection.BearerTypeEthernet != NymeaConnection.BearerTypeNone) { - return "../images/network-wired-symbolic.svg" + return "../images/network-wired.svg" } - return "../images/network-wifi-symbolic.svg"; + return "../images/network-wifi.svg"; case Connection.BearerTypeBluetooth: return "../images/bluetooth.svg"; case Connection.BearerTypeCloud: @@ -369,9 +369,9 @@ Page { case Connection.BearerTypeLan: case Connection.BearerTypeWan: if (engine.connection.availableBearerTypes & NymeaConnection.BearerTypeEthernet != NymeaConnection.BearerTypeNone) { - return "../images/network-wired-symbolic.svg" + return "../images/network-wired.svg" } - return "../images/network-wifi-symbolic.svg"; + return "../images/network-wifi.svg"; case Connection.BearerTypeBluetooth: return "../images/bluetooth.svg"; case Connection.BearerTypeCloud: diff --git a/nymea-app/ui/images/cloud-offline.svg b/nymea-app/ui/images/cloud-offline.svg index 0f11fddd..761bd88d 100644 --- a/nymea-app/ui/images/cloud-offline.svg +++ b/nymea-app/ui/images/cloud-offline.svg @@ -13,9 +13,9 @@ height="90" id="svg6138" version="1.1" - inkscape:version="0.91+devel r" + inkscape:version="0.92.3 (2405546, 2018-03-11)" viewBox="0 0 90 90.000001" - sodipodi:docname="sync-offline.svg"> + sodipodi:docname="cloud-offline.svg"> + inkscape:guide-bbox="true" + inkscape:window-width="2792" + inkscape:window-height="1698" + inkscape:window-x="88" + inkscape:window-y="44" + inkscape:window-maximized="1"> + id="guide4084" + inkscape:locked="false" /> + id="guide4086" + inkscape:locked="false" /> + id="guide4088" + inkscape:locked="false" /> + id="guide4090" + inkscape:locked="false" /> + id="guide4092" + inkscape:locked="false" /> + id="guide4094" + inkscape:locked="false" /> + id="guide4096" + inkscape:locked="false" /> + id="guide4098" + inkscape:locked="false" /> + id="guide4100" + inkscape:locked="false" /> + id="guide4102" + inkscape:locked="false" /> + id="guide4104" + inkscape:locked="false" /> + id="guide4106" + inkscape:locked="false" /> @@ -135,10 +152,17 @@ x="174" y="1700.3622" /> + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccccccccccccccsccscccccscsccccsccccc" /> + diff --git a/nymea-app/ui/images/network-wifi-offline.svg b/nymea-app/ui/images/network-wifi-offline.svg new file mode 100644 index 00000000..4ce50009 --- /dev/null +++ b/nymea-app/ui/images/network-wifi-offline.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/nymea-app/ui/images/network-wifi-symbolic.svg b/nymea-app/ui/images/network-wifi.svg similarity index 100% rename from nymea-app/ui/images/network-wifi-symbolic.svg rename to nymea-app/ui/images/network-wifi.svg diff --git a/nymea-app/ui/images/network-wired-offline.svg b/nymea-app/ui/images/network-wired-offline.svg new file mode 100644 index 00000000..06b30220 --- /dev/null +++ b/nymea-app/ui/images/network-wired-offline.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/network-wired-symbolic.svg b/nymea-app/ui/images/network-wired.svg similarity index 100% rename from nymea-app/ui/images/network-wired-symbolic.svg rename to nymea-app/ui/images/network-wired.svg diff --git a/nymea-app/ui/images/preferences-look-and-feel.svg b/nymea-app/ui/images/preferences-look-and-feel.svg new file mode 100644 index 00000000..5918bee2 --- /dev/null +++ b/nymea-app/ui/images/preferences-look-and-feel.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/sdk.svg b/nymea-app/ui/images/sdk.svg new file mode 100644 index 00000000..baf4789c --- /dev/null +++ b/nymea-app/ui/images/sdk.svg @@ -0,0 +1,18 @@ + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/nymea-app/ui/images/stock_website.svg b/nymea-app/ui/images/stock_website.svg new file mode 100644 index 00000000..da8dca4e --- /dev/null +++ b/nymea-app/ui/images/stock_website.svg @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/system/AboutNymeaPage.qml b/nymea-app/ui/system/AboutNymeaPage.qml index 96483c9a..f5592176 100644 --- a/nymea-app/ui/system/AboutNymeaPage.qml +++ b/nymea-app/ui/system/AboutNymeaPage.qml @@ -8,32 +8,48 @@ Page { id: root header: GuhHeader { - text: qsTr("About %1").arg(app.systemName) + text: qsTr("About %1:core").arg(app.systemName) onBackPressed: pageStack.pop() } - ColumnLayout { - anchors { left: parent.left; top: parent.top; right: parent.right } + Flickable { + anchors.fill: parent + contentHeight: imprint.implicitHeight - MeaListItemDelegate { - Layout.fillWidth: true - text: qsTr("Server UUID:") - subText: engine.jsonRpcClient.serverUuid - progressive: false - } + Imprint { + id: imprint + width: parent.width + title: qsTr("%1:core").arg(app.systemName) + githubLink: "https://github.com/guh/nymea" - MeaListItemDelegate { - Layout.fillWidth: true - text: qsTr("Server version:") - subText: engine.jsonRpcClient.serverVersion - progressive: false - } - - MeaListItemDelegate { - Layout.fillWidth: true - text: qsTr("Protocol version:") - subText: engine.jsonRpcClient.jsonRpcVersion - progressive: false + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("Connection:") + subText: engine.connection.currentConnection.url + progressive: false + prominentSubText: false + } + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("Server UUID:") + subText: engine.jsonRpcClient.serverUuid + progressive: false + prominentSubText: false + } + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("Server version:") + subText: engine.jsonRpcClient.serverVersion + progressive: false + prominentSubText: false + } + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("JSON-RPC version:") + subText: engine.jsonRpcClient.jsonRpcVersion + progressive: false + prominentSubText: false + } } } } diff --git a/nymea-app/ui/system/ConnectionInterfacesPage.qml b/nymea-app/ui/system/ConnectionInterfacesPage.qml index ee7e0232..808b37d3 100644 --- a/nymea-app/ui/system/ConnectionInterfacesPage.qml +++ b/nymea-app/ui/system/ConnectionInterfacesPage.qml @@ -25,7 +25,7 @@ Page { Layout.leftMargin: app.margins Layout.rightMargin: app.margins Layout.topMargin: app.margins - text: qsTr("TCP Server Interfaces") + text: qsTr("TCP server interfaces") wrapMode: Text.WordWrap color: app.accentColor } @@ -78,7 +78,7 @@ Page { Layout.leftMargin: app.margins Layout.rightMargin: app.margins Layout.topMargin: app.margins - text: qsTr("WebSocket Server Interfaces") + text: qsTr("WebSocket server interfaces") wrapMode: Text.WordWrap color: app.accentColor } diff --git a/nymea-app/ui/system/DeveloperTools.qml b/nymea-app/ui/system/DeveloperTools.qml new file mode 100644 index 00000000..8053c6cf --- /dev/null +++ b/nymea-app/ui/system/DeveloperTools.qml @@ -0,0 +1,98 @@ +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("Developer tools") + onBackPressed: pageStack.pop(); + } + + property WebServerConfiguration usedConfig: { + var config = null + for (var i = 0; i < engine.nymeaConfiguration.webServerConfigurations.count; i++) { + var tmp = engine.nymeaConfiguration.webServerConfigurations.get(i) + print("checking config:", tmp.id, tmp.address, tmp.port, tmp.sslEnabled) + if (tmp.address === engine.connection.currentConnection.hostAddress || tmp.address === "0.0.0.0") { + if (config === null || (!config.sslEnabled && tmp.sslEnabled)) { + config = tmp; + + } + continue; + } + } + return config; + } + + ColumnLayout { + anchors { left: parent.left; top: parent.top; right: parent.right } + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + spacing: app.margins + Label { + text: qsTr("Debug server enabled") + Layout.fillWidth: true + } + Switch { + id: debugServerEnabledSwitch + checked: engine.nymeaConfiguration.debugServerEnabled + onClicked: engine.nymeaConfiguration.debugServerEnabled = checked + } + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("In order to access the debug interface, please enable the web server.") + font.pixelSize: app.smallFont + color: "red" + wrapMode: Text.WordWrap + visible: engine.nymeaConfiguration.webServerConfigurations.count === 0 + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("The web server cannot be reached on %1.").arg(engine.connection.currentConnection.hostAddress) + wrapMode: Text.WordWrap + font.pixelSize: app.smallFont + color: "red" + visible: engine.nymeaConfiguration.webServerConfigurations.count > 0 && root.usedConfig === null + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("Please enable the web server to be accessed on this address.") + wrapMode: Text.WordWrap + font.pixelSize: app.smallFont + visible: engine.nymeaConfiguration.webServerConfigurations.count > 0 && root.usedConfig === null + } + + Button { + id: debugServerButton + Layout.fillWidth: true + Layout.margins: app.margins + visible: debugServerEnabledSwitch.checked + enabled: root.usedConfig !== null && engine.nymeaConfiguration.webServerConfigurations.count > 0 + text: qsTr("Open debug interface") + onClicked: { + print("opening:", engine.connection.currentConnection.url) + + var proto = "http" + (root.usedConfig.sslEnabled ? "s" : "") + "://" + var path = engine.connection.currentConnection.hostAddress + ":" + root.usedConfig.port + "/debug" + print("opening:", proto + path) + Qt.openUrlExternally(proto + path) + } + } + } +} diff --git a/nymea-app/ui/system/GeneralSettingsPage.qml b/nymea-app/ui/system/GeneralSettingsPage.qml new file mode 100644 index 00000000..bc5cc95f --- /dev/null +++ b/nymea-app/ui/system/GeneralSettingsPage.qml @@ -0,0 +1,97 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" + +Page { + id: root + header: GuhHeader { + text: qsTr("Box settings") + backButtonVisible: true + onBackPressed: pageStack.pop() + } + + ColumnLayout { + id: settingsColumn + anchors { left: parent.left; right: parent.right; top: parent.top } + + RowLayout { + Layout.fillWidth: true + Layout.topMargin: app.margins + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + spacing: app.margins + Label { + text: qsTr("Name") + } + TextField { + id: nameTextField + Layout.fillWidth: true + text: engine.nymeaConfiguration.serverName + } + Button { + text: qsTr("OK") + visible: nameTextField.displayText !== engine.nymeaConfiguration.serverName + onClicked: engine.nymeaConfiguration.serverName = nameTextField.displayText + } + } + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + spacing: app.margins + + Label { + Layout.fillWidth: true + text: qsTr("Language") + } + ComboBox { + id: languageBox + Layout.fillWidth: true + model: engine.nymeaConfiguration.availableLanguages + currentIndex: model.indexOf(engine.nymeaConfiguration.language) + contentItem: Label { + leftPadding: app.margins / 2 + text: Qt.locale(languageBox.displayText).nativeLanguageName + " (" + Qt.locale(languageBox.displayText).nativeCountryName + ")" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + delegate: ItemDelegate { + width: languageBox.width + contentItem: Label { + text: Qt.locale(modelData).nativeLanguageName + " (" + Qt.locale(modelData).nativeCountryName + ")" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + highlighted: languageBox.highlightedIndex === index + } + onActivated: { + engine.nymeaConfiguration.language = currentText; + } + } + } + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + spacing: app.margins + Label { + Layout.fillWidth: true + text: qsTr("Time zone") + } + ComboBox { + Layout.minimumWidth: 200 + model: engine.nymeaConfiguration.timezones + currentIndex: model.indexOf(engine.nymeaConfiguration.timezone) + onActivated: { + engine.nymeaConfiguration.timezone = currentText; + } + } + } + } +} diff --git a/nymea-app/ui/system/LogViewerPage.qml b/nymea-app/ui/system/LogViewerPage.qml index 52d06372..9ebafae3 100644 --- a/nymea-app/ui/system/LogViewerPage.qml +++ b/nymea-app/ui/system/LogViewerPage.qml @@ -68,6 +68,11 @@ Page { ScrollBar.vertical: ScrollBar {} + BusyIndicator { + anchors.centerIn: parent + visible: listView.model.busy + } + onContentYChanged: { if (!engine.jsonRpcClient.ensureServerVersion("1.10")) { if (!logsModel.busy && contentY - originY < 5 * height) { diff --git a/nymea-app/ui/system/WebServerConfigurationDialog.qml b/nymea-app/ui/system/WebServerConfigurationDialog.qml new file mode 100644 index 00000000..4a18c14d --- /dev/null +++ b/nymea-app/ui/system/WebServerConfigurationDialog.qml @@ -0,0 +1,105 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 + +Dialog { + id: root + title: qsTr("Server configuration") + width: parent.width * .8 + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + + property WebServerConfiguration serverConfiguration: null + standardButtons: Dialog.Ok | Dialog.Cancel + + ColumnLayout { + anchors { left: parent.left; top: parent.top; right: parent.right } + RowLayout { + Label { + text: qsTr("Interface") + Layout.fillWidth: true + } + ComboBox { + id: interfaceCombobox + model: [qsTr("Any"), qsTr("Localhost"), qsTr("Custom")] + Layout.fillWidth: true + currentIndex: !root.serverConfiguration + ? 0 : root.serverConfiguration.address === "0.0.0.0" + ? 0 + : root.serverConfiguration.address === "127.0.0.1" + ? 1 : 2 + onActivated: { + switch (index) { + case 0: + root.serverConfiguration.address = "0.0.0.0"; + break; + case 1: + root.serverConfiguration.address = "127.0.0.1"; + break; + } + } + } + } + RowLayout { + visible: interfaceCombobox.currentIndex === 2 + Label { + text: qsTr("Address:") + Layout.fillWidth: true + } + TextField { + id: addressTextField + Layout.fillWidth: true + inputMethodHints: Qt.ImhPreferNumbers + inputMask: "000.000.000.000" + text: root.serverConfiguration ? root.serverConfiguration.address : "" + onEditingFinished: root.serverConfiguration.address = text + } + } + + RowLayout { + Label { + text: qsTr("Port:") + Layout.fillWidth: true + } + TextField { + inputMethodHints: Qt.ImhDigitsOnly + text: root.serverConfiguration ? root.serverConfiguration.port : 0 + validator: IntValidator { bottom: 0; top: 65535 } + onEditingFinished: root.serverConfiguration.port = text + } + } + + RowLayout { + Label { + Layout.fillWidth: true + text: qsTr("SSL enabled") + } + CheckBox { + checkState: root.serverConfiguration && root.serverConfiguration.sslEnabled ? Qt.Checked : Qt.Unchecked + onClicked: root.serverConfiguration.sslEnabled = checked + } + } + RowLayout { + Label { + Layout.fillWidth: true + text: qsTr("Login required") + } + CheckBox { + checkState: root.serverConfiguration && root.serverConfiguration.authenticationEnabled ? Qt.Checked : Qt.Unchecked + onClicked: root.serverConfiguration.authenticationEnabled = checked + } + } + RowLayout { + Label { + Layout.fillWidth: true + text: qsTr("Public folder") + } + TextField { + text: root.serverConfiguration ? root.serverConfiguration.publicFolder : "" + onEditingFinished: root.serverConfiguration.publicFolder = text + } + } + } +} diff --git a/nymea-app/ui/system/WebServerSettingsPage.qml b/nymea-app/ui/system/WebServerSettingsPage.qml new file mode 100644 index 00000000..99a5433e --- /dev/null +++ b/nymea-app/ui/system/WebServerSettingsPage.qml @@ -0,0 +1,76 @@ +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("Web server") + onBackPressed: pageStack.pop(); + } + + Flickable { + anchors.fill: parent + contentHeight: connectionsColumn.implicitHeight + interactive: contentHeight > height + + ColumnLayout { + id: connectionsColumn + anchors { left: parent.left; top: parent.top; right: parent.right } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + Layout.topMargin: app.margins + text: qsTr("Web server interfaces") + wrapMode: Text.WordWrap + color: app.accentColor + } + + Repeater { + model: engine.nymeaConfiguration.webServerConfigurations + delegate: ConnectionInterfaceDelegate { + Layout.fillWidth: true + canDelete: true + onClicked: { + var component = Qt.createComponent(Qt.resolvedUrl("WebServerConfigurationDialog.qml")); + var popup = component.createObject(root, { serverConfiguration: engine.nymeaConfiguration.webServerConfigurations.get(index).clone() }); + popup.accepted.connect(function() { + engine.nymeaConfiguration.setWebServerConfiguration(popup.serverConfiguration) + popup.serverConfiguration.destroy(); + }) + popup.rejected.connect(function() { + popup.serverConfiguration.destroy(); + }) + popup.open() + } + onDeleteClicked: { + print("should delete") + engine.nymeaConfiguration.deleteWebServerConfiguration(model.id) + } + } + } + Button { + Layout.fillWidth: true + Layout.margins: app.margins + text: qsTr("Add") + onClicked: { + var config = engine.nymeaConfiguration.createWebServerConfiguration("0.0.0.0", 80 + engine.nymeaConfiguration.webServerConfigurations.count, false, false, "/var/www/"); + var component = Qt.createComponent(Qt.resolvedUrl("WebServerConfigurationDialog.qml")); + var popup = component.createObject(root, { serverConfiguration: config }); + popup.accepted.connect(function() { + engine.nymeaConfiguration.setWebServerConfiguration(popup.serverConfiguration) + popup.serverConfiguration.destroy(); + }) + popup.rejected.connect(function() { + popup.serverConfiguration.destroy(); + }) + popup.open() + } + } + } + } +}