Merge PR #193: Improve Kiosk experience
This commit is contained in:
commit
56c4999dfd
@ -177,8 +177,12 @@ void NymeaConnection::onSslErrors(const QList<QSslError> &errors)
|
|||||||
certificateFingerprint.append(digest.mid(i,1).toHex().toUpper());
|
certificateFingerprint.append(digest.mid(i,1).toHex().toUpper());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore self signed certs for connections to localhost
|
||||||
|
if (QHostAddress(transport->url().host()) == QHostAddress::LocalHost || QHostAddress(transport->url().host()) == QHostAddress::LocalHostIPv6) {
|
||||||
|
ignoredErrors.append(error);
|
||||||
|
|
||||||
// Check old style fingerprint storage
|
// Check old style fingerprint storage
|
||||||
if (storedFingerPrint == certificateFingerprint) {
|
} else if (storedFingerPrint == certificateFingerprint) {
|
||||||
qDebug() << "This fingerprint is known to us.";
|
qDebug() << "This fingerprint is known to us.";
|
||||||
ignoredErrors.append(error);
|
ignoredErrors.append(error);
|
||||||
|
|
||||||
|
|||||||
@ -271,6 +271,7 @@ void JsonRpcClient::processCreateUser(const QVariantMap &data)
|
|||||||
{
|
{
|
||||||
qDebug() << "create user response:" << data;
|
qDebug() << "create user response:" << data;
|
||||||
if (data.value("status").toString() == "success" && data.value("params").toMap().value("error").toString() == "UserErrorNoError") {
|
if (data.value("status").toString() == "success" && data.value("params").toMap().value("error").toString() == "UserErrorNoError") {
|
||||||
|
emit createUserSucceeded();
|
||||||
m_initialSetupRequired = false;
|
m_initialSetupRequired = false;
|
||||||
emit initialSetupRequiredChanged();
|
emit initialSetupRequiredChanged();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -93,6 +93,7 @@ signals:
|
|||||||
void invalidProtocolVersion(const QString &actualVersion, const QString &minimumVersion);
|
void invalidProtocolVersion(const QString &actualVersion, const QString &minimumVersion);
|
||||||
void authenticationFailed();
|
void authenticationFailed();
|
||||||
void pushButtonAuthFailed();
|
void pushButtonAuthFailed();
|
||||||
|
void createUserSucceeded();
|
||||||
void createUserFailed(const QString &error);
|
void createUserFailed(const QString &error);
|
||||||
void cloudConnectionStateChanged();
|
void cloudConnectionStateChanged();
|
||||||
|
|
||||||
|
|||||||
@ -75,6 +75,8 @@ int main(int argc, char *argv[])
|
|||||||
parser.addHelpOption();
|
parser.addHelpOption();
|
||||||
QCommandLineOption kioskOption = QCommandLineOption({"k", "kiosk"}, "Start the application in kiosk mode.");
|
QCommandLineOption kioskOption = QCommandLineOption({"k", "kiosk"}, "Start the application in kiosk mode.");
|
||||||
parser.addOption(kioskOption);
|
parser.addOption(kioskOption);
|
||||||
|
QCommandLineOption connectOption = QCommandLineOption({"c", "connect"}, "Connect to nymea:core without discovery.", "host");
|
||||||
|
parser.addOption(connectOption);
|
||||||
parser.process(application);
|
parser.process(application);
|
||||||
|
|
||||||
// Initialize app log controller as early as possible, but after setting app name etc
|
// Initialize app log controller as early as possible, but after setting app name etc
|
||||||
@ -124,6 +126,7 @@ int main(int argc, char *argv[])
|
|||||||
engine->rootContext()->setContextProperty("styleController", &styleController);
|
engine->rootContext()->setContextProperty("styleController", &styleController);
|
||||||
|
|
||||||
engine->rootContext()->setContextProperty("kioskMode", parser.isSet(kioskOption));
|
engine->rootContext()->setContextProperty("kioskMode", parser.isSet(kioskOption));
|
||||||
|
engine->rootContext()->setContextProperty("autoConnectHost", parser.value(connectOption));
|
||||||
|
|
||||||
engine->rootContext()->setContextProperty("systemProductType", QSysInfo::productType());
|
engine->rootContext()->setContextProperty("systemProductType", QSysInfo::productType());
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ linux:!android:LIBS += -lavahi-client -lavahi-common
|
|||||||
PRE_TARGETDEPS += ../libnymea-app-core ../libnymea-common
|
PRE_TARGETDEPS += ../libnymea-app-core ../libnymea-common
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
platformintegration/generic/raspberrypihelper.h \
|
||||||
stylecontroller.h \
|
stylecontroller.h \
|
||||||
pushnotifications.h \
|
pushnotifications.h \
|
||||||
platformhelper.h \
|
platformhelper.h \
|
||||||
@ -24,6 +25,7 @@ HEADERS += \
|
|||||||
applogcontroller.h
|
applogcontroller.h
|
||||||
|
|
||||||
SOURCES += main.cpp \
|
SOURCES += main.cpp \
|
||||||
|
platformintegration/generic/raspberrypihelper.cpp \
|
||||||
stylecontroller.cpp \
|
stylecontroller.cpp \
|
||||||
pushnotifications.cpp \
|
pushnotifications.cpp \
|
||||||
platformhelper.cpp \
|
platformhelper.cpp \
|
||||||
|
|||||||
@ -4,3 +4,18 @@ PlatformHelper::PlatformHelper(QObject *parent) : QObject(parent)
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PlatformHelper::canControlScreen() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PlatformHelper::screenTimeout() const
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformHelper::setScreenTimeout(int screenTimeout)
|
||||||
|
{
|
||||||
|
Q_UNUSED(screenTimeout)
|
||||||
|
}
|
||||||
|
|||||||
@ -12,6 +12,8 @@ class PlatformHelper : public QObject
|
|||||||
Q_PROPERTY(QString deviceModel READ deviceModel CONSTANT)
|
Q_PROPERTY(QString deviceModel READ deviceModel CONSTANT)
|
||||||
Q_PROPERTY(QString deviceManufacturer READ deviceManufacturer CONSTANT)
|
Q_PROPERTY(QString deviceManufacturer READ deviceManufacturer CONSTANT)
|
||||||
Q_PROPERTY(QString machineHostname READ machineHostname CONSTANT)
|
Q_PROPERTY(QString machineHostname READ machineHostname CONSTANT)
|
||||||
|
Q_PROPERTY(bool canControlScreen READ canControlScreen CONSTANT)
|
||||||
|
Q_PROPERTY(int screenTimeout READ screenTimeout WRITE setScreenTimeout NOTIFY screenTimeoutChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum HapticsFeedback {
|
enum HapticsFeedback {
|
||||||
@ -35,10 +37,15 @@ public:
|
|||||||
virtual QString deviceModel() const = 0;
|
virtual QString deviceModel() const = 0;
|
||||||
virtual QString deviceManufacturer() const = 0;
|
virtual QString deviceManufacturer() const = 0;
|
||||||
|
|
||||||
|
virtual bool canControlScreen() const;
|
||||||
|
virtual int screenTimeout() const;
|
||||||
|
virtual void setScreenTimeout(int screenTimeout);
|
||||||
|
|
||||||
Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbackType) = 0;
|
Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbackType) = 0;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void permissionsRequestFinished();
|
void permissionsRequestFinished();
|
||||||
|
void screenTimeoutChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PLATFORMHELPER_H
|
#endif // PLATFORMHELPER_H
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
PlatformHelperGeneric::PlatformHelperGeneric(QObject *parent) : PlatformHelper(parent)
|
PlatformHelperGeneric::PlatformHelperGeneric(QObject *parent) : PlatformHelper(parent)
|
||||||
{
|
{
|
||||||
|
m_piHelper = new RaspberryPiHelper(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlatformHelperGeneric::requestPermissions()
|
void PlatformHelperGeneric::requestPermissions()
|
||||||
@ -49,6 +49,24 @@ QString PlatformHelperGeneric::deviceManufacturer() const
|
|||||||
return QSysInfo::productType();
|
return QSysInfo::productType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PlatformHelperGeneric::canControlScreen() const
|
||||||
|
{
|
||||||
|
return m_piHelper->active();
|
||||||
|
}
|
||||||
|
|
||||||
|
int PlatformHelperGeneric::screenTimeout() const
|
||||||
|
{
|
||||||
|
return m_piHelper->screenTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlatformHelperGeneric::setScreenTimeout(int timeout)
|
||||||
|
{
|
||||||
|
if (m_piHelper->screenTimeout() != timeout) {
|
||||||
|
m_piHelper->setScreenTimeout(timeout);
|
||||||
|
emit screenTimeoutChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PlatformHelperGeneric::vibrate(PlatformHelper::HapticsFeedback feedbyckType)
|
void PlatformHelperGeneric::vibrate(PlatformHelper::HapticsFeedback feedbyckType)
|
||||||
{
|
{
|
||||||
Q_UNUSED(feedbyckType)
|
Q_UNUSED(feedbyckType)
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include "platformhelper.h"
|
#include "platformhelper.h"
|
||||||
|
#include "raspberrypihelper.h"
|
||||||
|
|
||||||
class PlatformHelperGeneric : public PlatformHelper
|
class PlatformHelperGeneric : public PlatformHelper
|
||||||
{
|
{
|
||||||
@ -21,10 +22,14 @@ public:
|
|||||||
virtual QString deviceModel() const override;
|
virtual QString deviceModel() const override;
|
||||||
virtual QString deviceManufacturer() const override;
|
virtual QString deviceManufacturer() const override;
|
||||||
|
|
||||||
Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbyckType) override;
|
virtual bool canControlScreen() const override;
|
||||||
signals:
|
virtual int screenTimeout() const override;
|
||||||
|
virtual void setScreenTimeout(int timeout) override;
|
||||||
|
|
||||||
public slots:
|
Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbyckType) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
RaspberryPiHelper *m_piHelper = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PLATFORMHELPERGENERIC_H
|
#endif // PLATFORMHELPERGENERIC_H
|
||||||
|
|||||||
106
nymea-app/platformintegration/generic/raspberrypihelper.cpp
Normal file
106
nymea-app/platformintegration/generic/raspberrypihelper.cpp
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
#include "raspberrypihelper.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QWindow>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
|
RaspberryPiHelper::RaspberryPiHelper(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
m_sysFsFile.setFileName("/sys/class/backlight/rpi_backlight/bl_power");
|
||||||
|
bool available = m_sysFsFile.open(QFile::ReadWrite | QFile::Text);
|
||||||
|
|
||||||
|
if (!available) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Raspberry Pi detected. Enabling backlight control";
|
||||||
|
|
||||||
|
screenOn();
|
||||||
|
|
||||||
|
foreach (QWindow *w, qApp->topLevelWindows()) {
|
||||||
|
w->installEventFilter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
m_screenOffTimer.setInterval(settings.value("screenOffTimeout", 15000).toInt());
|
||||||
|
m_screenOffTimer.setSingleShot(true);
|
||||||
|
connect(&m_screenOffTimer, &QTimer::timeout, this, &RaspberryPiHelper::screenOff);
|
||||||
|
if (m_screenOffTimer.interval() > 0) {
|
||||||
|
m_screenOffTimer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RaspberryPiHelper::active() const
|
||||||
|
{
|
||||||
|
return m_sysFsFile.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
int RaspberryPiHelper::screenTimeout() const
|
||||||
|
{
|
||||||
|
return m_screenOffTimer.interval();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RaspberryPiHelper::setScreenTimeout(int timeout)
|
||||||
|
{
|
||||||
|
m_screenOffTimer.setInterval(timeout);
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue("screenOffTimeout", timeout);
|
||||||
|
if (timeout > 0) {
|
||||||
|
m_screenOffTimer.start();
|
||||||
|
} else {
|
||||||
|
m_screenOffTimer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RaspberryPiHelper::eventFilter(QObject *watched, QEvent *event)
|
||||||
|
{
|
||||||
|
if (m_screenOffTimer.interval() == 0) {
|
||||||
|
return QObject::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QEvent::Type> watchedTypes = {
|
||||||
|
QEvent::ActivationChange,
|
||||||
|
QEvent::ApplicationStateChange,
|
||||||
|
QEvent::KeyPress,
|
||||||
|
QEvent::KeyRelease,
|
||||||
|
QEvent::MouseButtonPress,
|
||||||
|
QEvent::MouseButtonRelease,
|
||||||
|
QEvent::MouseMove,
|
||||||
|
QEvent::Show,
|
||||||
|
QEvent::TouchBegin,
|
||||||
|
QEvent::TouchEnd,
|
||||||
|
QEvent::TouchUpdate,
|
||||||
|
};
|
||||||
|
if (!watchedTypes.contains(event->type())) {
|
||||||
|
return QObject::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_screenOffTimer.isActive()) {
|
||||||
|
screenOn();
|
||||||
|
m_screenOffTimer.start();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
m_screenOffTimer.start( );
|
||||||
|
return QObject::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RaspberryPiHelper::screenOn()
|
||||||
|
{
|
||||||
|
qDebug() << "Turning screen on";
|
||||||
|
int ret = m_sysFsFile.write("0\n");
|
||||||
|
m_sysFsFile.flush();
|
||||||
|
if (ret < 0) {
|
||||||
|
qWarning() << "Failed to power on screen";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RaspberryPiHelper::screenOff()
|
||||||
|
{
|
||||||
|
qDebug() << "Turning screen off";
|
||||||
|
int ret = m_sysFsFile.write("1\n");
|
||||||
|
m_sysFsFile.flush();
|
||||||
|
if (ret < 0) {
|
||||||
|
qWarning() << "Failed to power off screen";
|
||||||
|
}
|
||||||
|
}
|
||||||
29
nymea-app/platformintegration/generic/raspberrypihelper.h
Normal file
29
nymea-app/platformintegration/generic/raspberrypihelper.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#ifndef RASPBERRYPIHELPER_H
|
||||||
|
#define RASPBERRYPIHELPER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
class RaspberryPiHelper : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit RaspberryPiHelper(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool active() const;
|
||||||
|
int screenTimeout() const;
|
||||||
|
void setScreenTimeout(int timeout);
|
||||||
|
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void screenOn();
|
||||||
|
void screenOff();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTimer m_screenOffTimer;
|
||||||
|
QFile m_sysFsFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RASPBERRYPIHELPER_H
|
||||||
@ -2,7 +2,7 @@
|
|||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file>ui/Nymea.qml</file>
|
<file>ui/Nymea.qml</file>
|
||||||
<file>ui/SettingsPage.qml</file>
|
<file>ui/SettingsPage.qml</file>
|
||||||
<file>ui/LoginPage.qml</file>
|
<file>ui/connection/LoginPage.qml</file>
|
||||||
<file>ui/MagicPage.qml</file>
|
<file>ui/MagicPage.qml</file>
|
||||||
<file>ui/PushButtonAuthPage.qml</file>
|
<file>ui/PushButtonAuthPage.qml</file>
|
||||||
<file>ui/KeyboardLoader.qml</file>
|
<file>ui/KeyboardLoader.qml</file>
|
||||||
@ -182,5 +182,6 @@
|
|||||||
<file>ui/magic/SelectStatePage.qml</file>
|
<file>ui/magic/SelectStatePage.qml</file>
|
||||||
<file>ui/system/SystemUpdatePage.qml</file>
|
<file>ui/system/SystemUpdatePage.qml</file>
|
||||||
<file>ui/components/UpdateRunningOverlay.qml</file>
|
<file>ui/components/UpdateRunningOverlay.qml</file>
|
||||||
|
<file>ui/connection/SetupWizard.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@ -1,24 +1,32 @@
|
|||||||
import QtQuick 2.0
|
import QtQuick 2.4
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
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: {
|
Component.onCompleted: {
|
||||||
if (useVirtualKeyboard) {
|
if (useVirtualKeyboard) {
|
||||||
var kbd = Qt.createQmlObject(virtualKeyboardString, root);
|
d.kbd = Qt.createQmlObject(d.virtualKeyboardString, root);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -168,7 +168,6 @@ Page {
|
|||||||
ColorIcon {
|
ColorIcon {
|
||||||
height: app.iconSize / 2
|
height: app.iconSize / 2
|
||||||
width: height
|
width: height
|
||||||
visible: infoPane.connectedState !== null && infoPane.connectedState.value === false
|
|
||||||
color: "white"
|
color: "white"
|
||||||
name: "../images/system-update.svg"
|
name: "../images/system-update.svg"
|
||||||
RotationAnimation on rotation { from: 0; to: 360; duration: 2000; loops: Animation.Infinite; running: engine.systemController.updateRunning }
|
RotationAnimation on rotation { from: 0; to: 360; duration: 2000; loops: Animation.Infinite; running: engine.systemController.updateRunning }
|
||||||
|
|||||||
@ -54,6 +54,7 @@ ApplicationWindow {
|
|||||||
RootItem {
|
RootItem {
|
||||||
id: rootItem
|
id: rootItem
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
anchors.bottomMargin: keyboardRect.height
|
||||||
}
|
}
|
||||||
|
|
||||||
NymeaDiscovery {
|
NymeaDiscovery {
|
||||||
|
|||||||
@ -33,31 +33,30 @@ Page {
|
|||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter }
|
anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter }
|
||||||
anchors.margins: app.margins
|
anchors.margins: app.margins
|
||||||
spacing: app.margins
|
spacing: app.margins * 2
|
||||||
|
|
||||||
RowLayout {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: app.margins
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
color: app.accentColor
|
||||||
|
text: qsTr("Authentication required")
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
font.pixelSize: app.largeFont
|
||||||
|
}
|
||||||
|
|
||||||
ColorIcon {
|
Image {
|
||||||
height: app.iconSize * 2
|
Layout.preferredWidth: app.iconSize * 6
|
||||||
width: height
|
Layout.preferredHeight: width
|
||||||
color: app.accentColor
|
source: "images/nymea-box-setup.svg"
|
||||||
name: "../images/info.svg"
|
Layout.alignment: Qt.AlignHCenter
|
||||||
}
|
sourceSize.width: width
|
||||||
|
sourceSize.height: height
|
||||||
Label {
|
|
||||||
color: app.accentColor
|
|
||||||
text: qsTr("Authentication required")
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
Layout.fillWidth: true
|
|
||||||
font.pixelSize: app.largeFont
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: qsTr("Please press the button on your %1 box to authenticate this device.").arg(app.systemName)
|
text: qsTr("Please press the button on your %1 box to authenticate this device.").arg(app.systemName)
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,8 +105,10 @@ Item {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
setupPushNotifications();
|
setupPushNotifications();
|
||||||
|
if (autoConnectHost.length > 0) {
|
||||||
if (tabSettings.lastConnectedHost.length > 0) {
|
var host = discovery.nymeaHosts.createLanHost("Manual connection", autoConnectHost);
|
||||||
|
engine.connection.connect(host)
|
||||||
|
} else if (tabSettings.lastConnectedHost.length > 0) {
|
||||||
print("Last connected host was", tabSettings.lastConnectedHost)
|
print("Last connected host was", tabSettings.lastConnectedHost)
|
||||||
var cachedHost = discovery.nymeaHosts.find(tabSettings.lastConnectedHost);
|
var cachedHost = discovery.nymeaHosts.find(tabSettings.lastConnectedHost);
|
||||||
if (cachedHost) {
|
if (cachedHost) {
|
||||||
@ -143,7 +145,17 @@ Item {
|
|||||||
})
|
})
|
||||||
return;
|
return;
|
||||||
} else {
|
} 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() {
|
page.backPressed.connect(function() {
|
||||||
tabSettings.lastConnectedHost = "";
|
tabSettings.lastConnectedHost = "";
|
||||||
engine.connection.disconnect()
|
engine.connection.disconnect()
|
||||||
|
|||||||
@ -89,6 +89,35 @@ Page {
|
|||||||
checked: settings.showConnectionTabs
|
checked: settings.showConnectionTabs
|
||||||
onClicked: settings.showConnectionTabs = checked
|
onClicked: settings.showConnectionTabs = checked
|
||||||
}
|
}
|
||||||
|
CheckDelegate {
|
||||||
|
id: screenOffCheck
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("Turn screen off when idle")
|
||||||
|
visible: PlatformHelper.canControlScreen
|
||||||
|
checked: PlatformHelper.screenTimeout > 0
|
||||||
|
onClicked: PlatformHelper.screenTimeout = (checked ? 15000 : 0)
|
||||||
|
}
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: screenOffCheck.height
|
||||||
|
visible: PlatformHelper.screenTimeout > 0
|
||||||
|
topPadding: 0
|
||||||
|
contentItem: RowLayout {
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("Screen off timeout")
|
||||||
|
}
|
||||||
|
SpinBox {
|
||||||
|
value: PlatformHelper.screenTimeout / 1000
|
||||||
|
onValueModified: {
|
||||||
|
PlatformHelper.screenTimeout = value * 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: qsTr("seconds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
|
|||||||
@ -12,7 +12,6 @@ Page {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
print("Ready to connect")
|
print("Ready to connect")
|
||||||
|
|
||||||
pageStack.push(discoveryPage, StackView.Immediate)
|
pageStack.push(discoveryPage, StackView.Immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
158
nymea-app/ui/connection/LoginPage.qml
Normal file
158
nymea-app/ui/connection/LoginPage.qml
Normal file
@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
nymea-app/ui/connection/SetupWizard.qml
Normal file
28
nymea-app/ui/connection/SetupWizard.qml
Normal file
@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
[Seat:*]
|
[Seat:*]
|
||||||
autologin-user=nymea
|
autologin-user=nymea
|
||||||
user-session=nymea-app-kiosk
|
user-session=nymea-app-kiosk
|
||||||
|
xserver-command=X -nocursor
|
||||||
@ -1,2 +1,3 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
/usr/bin/nymea-app --kiosk
|
export QT_IM_MODULE=qtvirtualkeyboard
|
||||||
|
/usr/bin/nymea-app --kiosk --connect nymeas://127.0.0.1:2222
|
||||||
|
|||||||
2
packaging/linux-common/udev/90-pi-backlight.rules
Normal file
2
packaging/linux-common/udev/90-pi-backlight.rules
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ACTION=="add", SUBSYSTEM=="backlight", RUN+="/bin/chgrp nymea /sys/class/backlight/%k/bl_power"
|
||||||
|
ACTION=="add", SUBSYSTEM=="backlight", RUN+="/bin/chmod g+w /sys/class/backlight/%k/bl_power"
|
||||||
@ -50,5 +50,9 @@ Section: shells
|
|||||||
Multi-Arch: same
|
Multi-Arch: same
|
||||||
Depends: nymea-app,
|
Depends: nymea-app,
|
||||||
openbox,
|
openbox,
|
||||||
|
lightdm,
|
||||||
|
qtvirtualkeyboard-plugin,
|
||||||
|
qtdeclarative5-folderlistmodel-plugin,
|
||||||
|
Provides: lightdm-greeter
|
||||||
Description: Run nymea:app in kiosk mode
|
Description: Run nymea:app in kiosk mode
|
||||||
This package will install nymea:app in kiosk mode on your machine.
|
This package will install nymea:app in kiosk mode on your machine.
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
packaging/linux-common/nymea-app-kiosk.desktop /usr/share/xsessions/
|
packaging/linux-common/nymea-app-kiosk.desktop /usr/share/xsessions/
|
||||||
packaging/linux-common/lightdm/40-nymea-app-kiosk.conf /usr/share/lightdm/lightdm.conf.d/
|
packaging/linux-common/lightdm/60-nymea-app-kiosk.conf /usr/share/lightdm/lightdm.conf.d/
|
||||||
packaging/linux-common/nymea-app-kiosk-wrapper /usr/bin/
|
packaging/linux-common/nymea-app-kiosk-wrapper /usr/bin/
|
||||||
|
packaging/linux-common/udev/90-pi-backlight.rules /lib/udev/rules.d/
|
||||||
|
|||||||
Reference in New Issue
Block a user