diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp index aef15ae8..92e62dac 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp @@ -271,6 +271,7 @@ void JsonRpcClient::processCreateUser(const QVariantMap &data) { qDebug() << "create user response:" << data; if (data.value("status").toString() == "success" && data.value("params").toMap().value("error").toString() == "UserErrorNoError") { + emit createUserSucceeded(); m_initialSetupRequired = false; emit initialSetupRequiredChanged(); } else { diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.h b/libnymea-app-core/jsonrpc/jsonrpcclient.h index 0a7048a9..09ff11d4 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.h +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.h @@ -93,6 +93,7 @@ signals: void invalidProtocolVersion(const QString &actualVersion, const QString &minimumVersion); void authenticationFailed(); void pushButtonAuthFailed(); + void createUserSucceeded(); void createUserFailed(const QString &error); void cloudConnectionStateChanged(); diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 3dc0cb73..6bcfd6bd 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -2,7 +2,7 @@ ui/Nymea.qml ui/SettingsPage.qml - ui/LoginPage.qml + ui/connection/LoginPage.qml ui/MagicPage.qml ui/PushButtonAuthPage.qml ui/KeyboardLoader.qml @@ -182,5 +182,6 @@ ui/magic/SelectStatePage.qml ui/system/SystemUpdatePage.qml ui/components/UpdateRunningOverlay.qml + ui/connection/SetupWizard.qml diff --git a/nymea-app/ui/KeyboardLoader.qml b/nymea-app/ui/KeyboardLoader.qml index 8c33f1c6..d7e2445a 100644 --- a/nymea-app/ui/KeyboardLoader.qml +++ b/nymea-app/ui/KeyboardLoader.qml @@ -1,24 +1,32 @@ -import QtQuick 2.0 +import QtQuick 2.4 Item { id: root - implicitHeight: childrenRect.height + implicitHeight: active ? childrenRect.height : 0 + property bool active: d.kbd && d.kbd.active + + Behavior on implicitHeight { NumberAnimation { duration: 130; easing.type: Easing.InOutQuad } } + + QtObject { + id: d + property var kbd: null + property string virtualKeyboardString: + ' + import QtQuick 2.8; + import QtQuick.VirtualKeyboard 2.1 + InputPanel { + id: inputPanel + y: Qt.inputMethod.visible ? parent.height - inputPanel.height : parent.height + anchors.left: parent.left + anchors.right: parent.right + } + ' + } - property string virtualKeyboardString: - ' - import QtQuick 2.8; - import QtQuick.VirtualKeyboard 2.1 - InputPanel { - id: inputPanel - y: Qt.inputMethod.visible ? parent.height - inputPanel.height : parent.height - anchors.left: parent.left - anchors.right: parent.right - } - ' Component.onCompleted: { if (useVirtualKeyboard) { - var kbd = Qt.createQmlObject(virtualKeyboardString, root); + d.kbd = Qt.createQmlObject(d.virtualKeyboardString, root); } } } diff --git a/nymea-app/ui/LoginPage.qml b/nymea-app/ui/LoginPage.qml deleted file mode 100644 index 6eb7703a..00000000 --- a/nymea-app/ui/LoginPage.qml +++ /dev/null @@ -1,141 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 2.1 -import QtQuick.Layouts 1.1 -import Nymea 1.0 -import "components" - -Page { - id: root - signal backPressed(); - - header: GuhHeader { - text: qsTr("Welcome to %1!").arg(app.systemName) - backButtonVisible: true - onBackPressed: root.backPressed() - } - - - Connections { - target: engine.jsonRpcClient - onAuthenticationFailed: { - var popup = errorDialog.createObject(root) - popup.text = qsTr("Sorry, that wasn't right. Try again please.") - popup.open(); - } - onCreateUserFailed: { - print("createUser failed") - var message; - switch (error) { - case "UserErrorInvalidUserId": - message = qsTr("The email you've entered isn't valid.") - break; - case "UserErrorDuplicateUserId": - message = qsTr("The email you've entered is already used.") - break; - case "UserErrorBadPassword": - message = qsTr("The password you've chose is too weak.") - break; - case "UserErrorBackendError": - message = qsTr("An error happened with the user storage. Please make sure your %1 box is installed correctly.") - break; - } - var popup = errorDialog.createObject(root, {text: message}); - popup.open(); - } - } - - ColumnLayout { - anchors.fill: parent - anchors.margins: app.margins - spacing: app.margins - - Label { - Layout.fillWidth: true - text: engine.jsonRpcClient.initialSetupRequired ? - qsTr("In order to use your %1 system, please enter your email address and set a password for your nymea box.").arg(app.systemName) - : qsTr("In order to use your %1 system, please log in.").arg(app.systemName) - wrapMode: Text.WordWrap - } - - ColumnLayout { - Layout.fillWidth: true - - Label { - text: qsTr("Your e-mail address:") - Layout.fillWidth: true - } - TextField { - id: usernameTextField - Layout.fillWidth: true - inputMethodHints: Qt.ImhEmailCharactersOnly - placeholderText: "john.smith@cooldomain.com" - } - } - ColumnLayout { - Layout.fillWidth: true - - Label { - Layout.fillWidth: true - text: qsTr("Password:") - } - TextField { - id: passwordTextField - Layout.fillWidth: true - echoMode: TextInput.Password - } - } - - ColumnLayout { - Layout.fillWidth: true - visible: engine.jsonRpcClient.initialSetupRequired - - Label { - Layout.fillWidth: true - text: qsTr("Confirm password:") - } - TextField { - id: confirmPasswordTextField - Layout.fillWidth: true - echoMode: TextInput.Password - } - } - - Label { - Layout.fillWidth: true - visible: engine.jsonRpcClient.initialSetupRequired - opacity: (passwordTextField.text.length > 0 && passwordTextField.text.length < 8) || passwordTextField.text != confirmPasswordTextField.text ? 1 : 0 - text: passwordTextField.text.length < 8 ? qsTr("This password isn't long enought to be secure, add some more characters please.") - : qsTr("The passwords don't match.") - wrapMode: Text.WordWrap - Layout.preferredHeight: confirmPasswordTextField.height * 2 - color: app.accentColor - } - - Button { - Layout.fillWidth: true - text: qsTr("OK") - enabled: usernameTextField.text.length >= 5 && passwordTextField.text.length >= 8 - && (!engine.jsonRpcClient.initialSetupRequired || confirmPasswordTextField.text == passwordTextField.text) - onClicked: { - if (engine.jsonRpcClient.initialSetupRequired) { - print("create user") - engine.jsonRpcClient.createUser(usernameTextField.text, passwordTextField.text); - } else { - print("authenticate", usernameTextField.text, passwordTextField.text, "nymea-app") - engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.text, "nymea-app"); - } - } - } - Item { - Layout.fillHeight: true - Layout.fillWidth: true - } - } - - Component { - id: errorDialog - ErrorDialog { - - } - } -} diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 9ce24790..4603f481 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -54,6 +54,7 @@ ApplicationWindow { RootItem { id: rootItem anchors.fill: parent + anchors.bottomMargin: keyboardRect.height } NymeaDiscovery { diff --git a/nymea-app/ui/PushButtonAuthPage.qml b/nymea-app/ui/PushButtonAuthPage.qml index 80423a0d..7423ad3f 100644 --- a/nymea-app/ui/PushButtonAuthPage.qml +++ b/nymea-app/ui/PushButtonAuthPage.qml @@ -33,31 +33,30 @@ Page { ColumnLayout { anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter } anchors.margins: app.margins - spacing: app.margins + spacing: app.margins * 2 - RowLayout { + Label { Layout.fillWidth: true - spacing: app.margins + horizontalAlignment: Text.AlignHCenter + color: app.accentColor + text: qsTr("Authentication required") + wrapMode: Text.WordWrap + font.pixelSize: app.largeFont + } - ColorIcon { - height: app.iconSize * 2 - width: height - color: app.accentColor - name: "../images/info.svg" - } - - Label { - color: app.accentColor - text: qsTr("Authentication required") - wrapMode: Text.WordWrap - Layout.fillWidth: true - font.pixelSize: app.largeFont - } + Image { + Layout.preferredWidth: app.iconSize * 6 + Layout.preferredHeight: width + source: "images/nymea-box-setup.svg" + Layout.alignment: Qt.AlignHCenter + sourceSize.width: width + sourceSize.height: height } Label { Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter text: qsTr("Please press the button on your %1 box to authenticate this device.").arg(app.systemName) wrapMode: Text.WordWrap } diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index a927d7bb..60e85991 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -145,7 +145,17 @@ Item { }) return; } else { - var page = pageStack.push(Qt.resolvedUrl("LoginPage.qml")); + if (engine.jsonRpcClient.initialSetupRequired) { + var page = pageStack.push(Qt.resolvedUrl("connection/SetupWizard.qml")); + page.backPressed.connect(function() { + tabSettings.lastConnectedHost = ""; + engine.connection.disconnect() + init(); + }) + return; + } + + var page = pageStack.push(Qt.resolvedUrl("connection/LoginPage.qml")); page.backPressed.connect(function() { tabSettings.lastConnectedHost = ""; engine.connection.disconnect() diff --git a/nymea-app/ui/connection/LoginPage.qml b/nymea-app/ui/connection/LoginPage.qml new file mode 100644 index 00000000..9c761b6f --- /dev/null +++ b/nymea-app/ui/connection/LoginPage.qml @@ -0,0 +1,158 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" + +Page { + id: root + signal backPressed(); + + header: GuhHeader { + text: qsTr("Welcome to %1!").arg(app.systemName) + backButtonVisible: true + onBackPressed: root.backPressed() + } + + + Connections { + target: engine.jsonRpcClient + onAuthenticationFailed: { + var popup = errorDialog.createObject(root) + popup.text = qsTr("Sorry, that wasn't right. Try again please.") + popup.open(); + } + onCreateUserSucceeded: { + engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.text, "nymea-app"); + } + + onCreateUserFailed: { + print("createUser failed") + var message; + switch (error) { + case "UserErrorInvalidUserId": + message = qsTr("The email you've entered isn't valid.") + break; + case "UserErrorDuplicateUserId": + message = qsTr("The email you've entered is already used.") + break; + case "UserErrorBadPassword": + message = qsTr("The password you've chose is too weak.") + break; + case "UserErrorBackendError": + message = qsTr("An error happened with the user storage. Please make sure your %1 box is installed correctly.") + break; + } + var popup = errorDialog.createObject(root, {text: message}); + popup.open(); + } + } + + Flickable { + anchors.fill: parent + contentHeight: contentColumn.implicitHeight + + ColumnLayout { + id: contentColumn + width: parent.width + + spacing: app.margins + + RowLayout { + Layout.margins: app.margins + spacing: app.margins + + ColorIcon { + Layout.preferredHeight: app.iconSize * 2 + Layout.preferredWidth: app.iconSize * 2 + name: "../images/lock-closed.svg" + color: app.accentColor + } + + Label { + Layout.fillWidth: true + text: engine.jsonRpcClient.initialSetupRequired ? + qsTr("In order to use your %1 system, please enter your email address and set a password for your %1 box.").arg(app.systemName) + : qsTr("In order to use your %1 system, please log in.").arg(app.systemName) + wrapMode: Text.WordWrap + } + } + + + GridLayout { + Layout.fillWidth: true + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + columns: app.width > 400 ? 2 : 1 + + Label { + text: qsTr("Your e-mail address:") + Layout.fillWidth: true + } + TextField { + id: usernameTextField + Layout.fillWidth: true + inputMethodHints: Qt.ImhEmailCharactersOnly + placeholderText: "john.smith@cooldomain.com" + } + Label { + Layout.fillWidth: true + text: qsTr("Password:") + } + TextField { + id: passwordTextField + Layout.fillWidth: true + echoMode: TextInput.Password + } + + Label { + visible: engine.jsonRpcClient.initialSetupRequired + Layout.fillWidth: true + text: qsTr("Confirm password:") + } + TextField { + id: confirmPasswordTextField + visible: engine.jsonRpcClient.initialSetupRequired + Layout.fillWidth: true + echoMode: TextInput.Password + } + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + visible: engine.jsonRpcClient.initialSetupRequired + opacity: (passwordTextField.text.length > 0 && passwordTextField.text.length < 8) || passwordTextField.text != confirmPasswordTextField.text ? 1 : 0 + text: passwordTextField.text.length < 8 ? qsTr("This password isn't long enought to be secure, add some more characters please.") + : qsTr("The passwords don't match.") + wrapMode: Text.WordWrap + color: app.accentColor + font.pixelSize: app.smallFont + } + + Button { + Layout.fillWidth: true + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.bottomMargin: app.margins + text: qsTr("OK") + enabled: usernameTextField.text.length >= 5 && passwordTextField.text.length >= 8 + && (!engine.jsonRpcClient.initialSetupRequired || confirmPasswordTextField.text == passwordTextField.text) + onClicked: { + if (engine.jsonRpcClient.initialSetupRequired) { + print("create user") + engine.jsonRpcClient.createUser(usernameTextField.text, passwordTextField.text); + } else { + print("authenticate", usernameTextField.text, passwordTextField.text, "nymea-app") + engine.jsonRpcClient.authenticate(usernameTextField.text, passwordTextField.text, "nymea-app"); + } + } + } + } + } + + + Component { + id: errorDialog + ErrorDialog { + + } + } +} diff --git a/nymea-app/ui/connection/SetupWizard.qml b/nymea-app/ui/connection/SetupWizard.qml new file mode 100644 index 00000000..7f230d44 --- /dev/null +++ b/nymea-app/ui/connection/SetupWizard.qml @@ -0,0 +1,28 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" + +Page { + id: root + signal backPressed(); + + header: GuhHeader { + text: qsTr("First setup") + backButtonVisible: true + onBackPressed: root.backPressed() + } + + EmptyViewPlaceholder { + anchors.centerIn: parent + width: parent.width - app.margins * 2 + title: qsTr("Welcome to %1!").arg(app.systemName) + text: qsTr("This %1 system has not been set up yet. This wizard will guide you through a few simple steps to set it up.").arg(app.systemName) + imageSource: "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle) + buttonText: qsTr("Next") + onButtonClicked: { + var page = pageStack.push(Qt.resolvedUrl("LoginPage.qml")); + } + } +}