diff --git a/libnymea-app/system/systemcontroller.cpp b/libnymea-app/system/systemcontroller.cpp index 96cd9e85..3a838509 100644 --- a/libnymea-app/system/systemcontroller.cpp +++ b/libnymea-app/system/systemcontroller.cpp @@ -25,20 +25,23 @@ #include "systemcontroller.h" #include "types/package.h" -#include "types/repository.h" #include "types/packages.h" #include "types/repositories.h" +#include "types/repository.h" -#include #include +#include +#include + #include "logging.h" NYMEA_LOGGING_CATEGORY(dcSystemController, "SystemController") -SystemController::SystemController(JsonRpcClient *jsonRpcClient, QObject *parent): - QObject(parent), - m_jsonRpcClient(jsonRpcClient) +SystemController::SystemController(JsonRpcClient *jsonRpcClient, QObject *parent) + : QObject(parent) + , m_jsonRpcClient(jsonRpcClient) { m_jsonRpcClient->registerNotificationHandler(this, "System", "notificationReceived"); + m_packages = new Packages(this); m_repositories = new Repositories(this); @@ -73,6 +76,11 @@ bool SystemController::updateManagementAvailable() const return m_updateManagementAvailable; } +SystemController::UpdateType SystemController::updateManagementType() const +{ + return m_updateManagementType; +} + int SystemController::restart() { return m_jsonRpcClient->sendCommand("System.Restart", this, "restartResponse"); @@ -98,6 +106,11 @@ bool SystemController::updateRunning() const return m_updateRunning; } +int SystemController::updateProgress() const +{ + return m_updateProgress; +} + void SystemController::checkForUpdates() { m_jsonRpcClient->sendCommand("System.CheckForUpdates"); @@ -172,7 +185,7 @@ QString SystemController::serverTimeZone() const // NOTE: Ideally we'd just set the TimeZone of our serverTime prooperly, however, there's a bug on Android // Which doesn't allow to create QTimeZone objects by IANA id.... So, let's keep that separated in a string // https://bugreports.qt.io/browse/QTBUG-83438 -// return m_serverTime.timeZone().id(); + // return m_serverTime.timeZone().id(); return m_serverTimeZone; } @@ -207,12 +220,26 @@ QString SystemController::deviceSerialNumber() const void SystemController::getCapabilitiesResponse(int /*commandId*/, const QVariantMap &data) { + qCDebug(dcSystemController()) << data; m_powerManagementAvailable = data.value("powerManagement").toBool(); emit powerManagementAvailableChanged(); m_updateManagementAvailable = data.value("updateManagement").toBool(); emit updateManagementAvailableChanged(); + // Since API version 8.5 + if (data.contains("updateManagementType")) { + QMetaEnum updateTypeEnum = QMetaEnum::fromType(); + m_updateManagementType = static_cast(updateTypeEnum.keyToValue(data.value("updateManagementType").toByteArray())); + } else { + // Property exists since API 8.5, if there is an update management available, default to UpdateTypePackageManager + if (m_updateManagementAvailable) { + m_updateManagementType = UpdateTypePackageManager; + } else { + m_updateManagementType = UpdateTypeNone; + } + } + m_timeManagementAvailable = data.value("timeManagement").toBool(); emit timeManagementAvailableChanged(); @@ -226,7 +253,15 @@ void SystemController::getCapabilitiesResponse(int /*commandId*/, const QVariant m_jsonRpcClient->sendCommand("System.GetTime", this, "getServerTimeResponse"); } - qCDebug(dcSystemController) << "nymea:core capabilities: Power management:" << m_powerManagementAvailable << "Update management:" << m_updateManagementAvailable << "Time management:" << m_timeManagementAvailable; + qCDebug(dcSystemController()) + << "nymea:core capabilities: Power management:" + << m_powerManagementAvailable + << "Update management:" + << m_updateManagementAvailable + << "Update management type:" + << m_updateManagementType + << "Time management:" + << m_timeManagementAvailable; } void SystemController::getUpdateStatusResponse(int /*commandId*/, const QVariantMap &data) @@ -234,13 +269,17 @@ void SystemController::getUpdateStatusResponse(int /*commandId*/, const QVariant qCDebug(dcSystemController()) << "Update status:" << qUtf8Printable(QJsonDocument::fromVariant(data).toJson(QJsonDocument::Indented)); m_updateManagementBusy = data.value("busy").toBool(); m_updateRunning = data.value("updateRunning").toBool(); + m_updateProgress = data.value("updateProgress", -1).toInt(); // Since API 8.5, optional + emit updateRunningChanged(); + emit updateManagementBusyChanged(); + emit updateProgressChanged(); } void SystemController::getPackagesResponse(int commandId, const QVariantMap &data) { Q_UNUSED(commandId) - qCDebug(dcSystemController) << "Packages:" << qUtf8Printable(QJsonDocument::fromVariant(data).toJson(QJsonDocument::Indented)); + qCDebug(dcSystemController()) << "Packages:" << qUtf8Printable(QJsonDocument::fromVariant(data).toJson(QJsonDocument::Indented)); foreach (const QVariant &packageVariant, data.value("packages").toList()) { QString id = packageVariant.toMap().value("id").toString(); QString displayName = packageVariant.toMap().value("displayName").toString(); @@ -269,12 +308,12 @@ void SystemController::getRepositoriesResponse(int /*commandId*/, const QVariant void SystemController::removePackageResponse(int commandId, const QVariantMap ¶ms) { - qCDebug(dcSystemController) << "Remove result" << commandId << params; + qCDebug(dcSystemController()) << "Remove result" << commandId << params; } void SystemController::enableRepositoryResponse(int commandId, const QVariantMap ¶ms) { - qCDebug(dcSystemController) << "Enable repo response" << params; + qCDebug(dcSystemController()) << "Enable repo response" << params; emit enableRepositoryFinished(commandId, params.value("success").toBool()); } @@ -286,7 +325,7 @@ void SystemController::getServerTimeResponse(int commandId, const QVariantMap &p // Which doesn't allow to create QTimeZone objects by IANA id.... So, let's keep that separated in a string // https://bugreports.qt.io/browse/QTBUG-83438 -// m_serverTime.setTimeZone(QTimeZone(params.value("timeZone").toString().toUtf8())); + // m_serverTime.setTimeZone(QTimeZone(params.value("timeZone").toString().toUtf8())); m_serverTimeZone = params.value("timeZone").toString(); emit serverTimeZoneChanged(); @@ -302,12 +341,12 @@ void SystemController::getServerTimeResponse(int commandId, const QVariantMap &p emit automaticTimeAvailableChanged(); m_automaticTime = params.value("automaticTime").toBool(); emit automaticTimeChanged(); - qCDebug(dcSystemController) << "Server time:" << m_serverTime << "Automatic Time available:" << m_automaticTimeAvailable << "Automatic time:" << m_automaticTime; + qCDebug(dcSystemController()) << "Server time:" << m_serverTime << "Automatic Time available:" << m_automaticTimeAvailable << "Automatic time:" << m_automaticTime; } void SystemController::setTimeResponse(int commandId, const QVariantMap ¶ms) { - qCDebug(dcSystemController) << "set time response" << commandId << params; + qCDebug(dcSystemController()) << "set time response" << commandId << params; } void SystemController::restartResponse(int commandId, const QVariantMap ¶ms) @@ -338,18 +377,26 @@ void SystemController::getSystemInfoResponse(int commandId, const QVariantMap &p void SystemController::notificationReceived(const QVariantMap &data) { QString notification = data.value("notification").toString(); + const QVariantMap paramsMap = data.value("params").toMap(); if (notification == "System.UpdateStatusChanged") { - qCDebug(dcSystemController) << "System.UpdateStatusChanged:" << data.value("params").toMap(); - if (m_updateManagementBusy != data.value("params").toMap().value("busy").toBool()) { - m_updateManagementBusy = data.value("params").toMap().value("busy").toBool(); + qCDebug(dcSystemController()) << "System.UpdateStatusChanged:" << paramsMap; + if (m_updateManagementBusy != paramsMap.value("busy").toBool()) { + m_updateManagementBusy = paramsMap.value("busy").toBool(); emit updateManagementBusyChanged(); } - if (m_updateRunning != data.value("params").toMap().value("updateRunning").toBool()) { - m_updateRunning = data.value("params").toMap().value("updateRunning").toBool(); + if (m_updateRunning != paramsMap.value("updateRunning").toBool()) { + m_updateRunning = paramsMap.value("updateRunning").toBool(); emit updateRunningChanged(); } + + // Since API 8.5, optional, not supported or not running = -1 + if (m_updateProgress != paramsMap.value("updateProgress", -1).toInt()) { + m_updateProgress = paramsMap.value("updateProgress").toInt(); + emit updateProgressChanged(); + } + } else if (notification == "System.PackageAdded") { - QVariantMap packageMap = data.value("params").toMap().value("package").toMap(); + QVariantMap packageMap = paramsMap.value("package").toMap(); QString id = packageMap.value("id").toString(); QString displayName = packageMap.value("displayName").toString(); Package *p = new Package(id, displayName); @@ -362,7 +409,7 @@ void SystemController::notificationReceived(const QVariantMap &data) p->setCanRemove(packageMap.value("canRemove").toBool()); m_packages->addPackage(p); } else if (notification == "System.PackageChanged") { - QVariantMap packageMap = data.value("params").toMap().value("package").toMap(); + QVariantMap packageMap = paramsMap.value("package").toMap(); QString id = packageMap.value("id").toString(); Package *p = m_packages->getPackage(id); if (!p) { @@ -377,17 +424,17 @@ void SystemController::notificationReceived(const QVariantMap &data) p->setRollbackAvailable(packageMap.value("rollbackAvailable").toBool()); p->setCanRemove(packageMap.value("canRemove").toBool()); } else if (notification == "System.PackageRemoved") { - QString packageId = data.value("params").toMap().value("packageId").toString(); + QString packageId = paramsMap.value("packageId").toString(); m_packages->removePackage(packageId); } else if (notification == "System.RepositoryAdded") { - QVariantMap repoMap = data.value("params").toMap().value("repository").toMap(); + QVariantMap repoMap = paramsMap.value("repository").toMap(); QString id = repoMap.value("id").toString(); QString displayName = repoMap.value("displayName").toString(); Repository *repo = new Repository(id, displayName); repo->setEnabled(repoMap.value("enabled").toBool()); m_repositories->addRepository(repo); } else if (notification == "System.RepositoryChanged") { - QVariantMap repoMap = data.value("params").toMap().value("repository").toMap(); + QVariantMap repoMap = paramsMap.value("repository").toMap(); QString id = repoMap.value("id").toString(); Repository *repo = m_repositories->getRepository(id); if (!repo) { @@ -396,16 +443,36 @@ void SystemController::notificationReceived(const QVariantMap &data) } repo->setEnabled(repoMap.value("enabled").toBool()); } else if (notification == "System.RepositoryRemoved") { - QString repositoryId = data.value("params").toMap().value("repositoryId").toString(); + QString repositoryId = paramsMap.value("repositoryId").toString(); m_repositories->removeRepository(repositoryId); } else if (notification == "System.CapabilitiesChanged") { - m_powerManagementAvailable = data.value("params").toMap().value("powerManagement").toBool(); - m_updateManagementAvailable = data.value("params").toMap().value("updateManagement").toBool(); - qWarning() << "System capabilites changed: power management:" << m_powerManagementAvailable << "update management:" << m_updateManagementAvailable; + m_powerManagementAvailable = paramsMap.value("powerManagement").toBool(); + m_updateManagementAvailable = paramsMap.value("updateManagement").toBool(); + if (paramsMap.contains("updateManagementType")) { + QMetaEnum updateTypeEnum = QMetaEnum::fromType(); + m_updateManagementType = static_cast(updateTypeEnum.keyToValue(paramsMap.value("updateManagementType").toByteArray())); + } else { + // Property exists since API 8.5, if there is an update management available, default to UpdateTypePackageManager + if (m_updateManagementAvailable) { + m_updateManagementType = UpdateTypePackageManager; + } else { + m_updateManagementType = UpdateTypeNone; + } + } + + qCDebug(dcSystemController()) + << "System capabilites changed: power management:" + << m_powerManagementAvailable + << "update management:" + << m_updateManagementAvailable + << "update management type:" + << m_updateManagementType; emit powerManagementAvailableChanged(); emit updateManagementAvailableChanged(); + emit updateManagementTypeChanged(); + } else if (notification == "System.TimeConfigurationChanged") { - qCDebug(dcSystemController) << "System time configuration changed" << data.value("params").toMap().value("timeZone").toByteArray(); + qCDebug(dcSystemController()) << "System time configuration changed" << data.value("params").toMap().value("timeZone").toByteArray(); // NOTE: Ideally we'd just set the TimeZone of our serverTime prooperly, however, there's a bug on Android // Which doesn't allow to create QTimeZone objects by IANA id.... So, let's keep that separated in a string diff --git a/libnymea-app/system/systemcontroller.h b/libnymea-app/system/systemcontroller.h index 84a441a8..5003ce35 100644 --- a/libnymea-app/system/systemcontroller.h +++ b/libnymea-app/system/systemcontroller.h @@ -35,14 +35,18 @@ class SystemController : public QObject { Q_OBJECT Q_PROPERTY(bool powerManagementAvailable READ powerManagementAvailable NOTIFY powerManagementAvailableChanged) + // Whether the update mechanism is available in the connected core Q_PROPERTY(bool updateManagementAvailable READ updateManagementAvailable NOTIFY updateManagementAvailableChanged) + Q_PROPERTY(UpdateType updateManagementType READ updateManagementType NOTIFY updateManagementTypeChanged FINAL) Q_PROPERTY(bool timeManagementAvailable READ timeManagementAvailable NOTIFY timeManagementAvailableChanged) Q_PROPERTY(bool updateManagementBusy READ updateManagementBusy NOTIFY updateManagementBusyChanged) Q_PROPERTY(bool updateRunning READ updateRunning NOTIFY updateRunningChanged) - Q_PROPERTY(Packages* packages READ packages CONSTANT) - Q_PROPERTY(Repositories* repositories READ repositories CONSTANT) + Q_PROPERTY(int updateProgress READ updateProgress NOTIFY updateProgressChanged) + + Q_PROPERTY(Packages *packages READ packages CONSTANT) + Q_PROPERTY(Repositories *repositories READ repositories CONSTANT) Q_PROPERTY(QDateTime serverTime READ serverTime WRITE setServerTime NOTIFY serverTimeChanged) Q_PROPERTY(QString serverTimeZone READ serverTimeZone WRITE setServerTimeZone NOTIFY serverTimeZoneChanged) @@ -53,23 +57,34 @@ class SystemController : public QObject Q_PROPERTY(QString deviceSerialNumber READ deviceSerialNumber NOTIFY deviceSerialNumberChanged) public: + enum UpdateType { + UpdateTypeNone, + UpdateTypeSystem, + UpdateTypePackageManager + }; + Q_ENUM(UpdateType) + explicit SystemController(JsonRpcClient *jsonRpcClient, QObject *parent = nullptr); void init(); bool powerManagementAvailable() const; + Q_INVOKABLE int restart(); Q_INVOKABLE int reboot(); Q_INVOKABLE int shutdown(); bool updateManagementAvailable() const; + UpdateType updateManagementType() const; + bool updateManagementBusy() const; bool updateRunning() const; + int updateProgress() const; Q_INVOKABLE void checkForUpdates(); - Packages* packages() const; + Packages *packages() const; Q_INVOKABLE void updatePackages(const QString packageId = QString()); Q_INVOKABLE void removePackages(const QString packageId = QString()); - Repositories* repositories() const; + Repositories *repositories() const; Q_INVOKABLE int enableRepository(const QString &id, bool enabled); bool timeManagementAvailable() const; @@ -87,9 +102,11 @@ public: signals: void powerManagementAvailableChanged(); void updateManagementAvailableChanged(); + void updateManagementTypeChanged(); void timeManagementAvailableChanged(); void updateManagementBusyChanged(); void updateRunningChanged(); + void updateProgressChanged(); void enableRepositoryFinished(int id, bool success); void serverTimeChanged(); void serverTimeZoneChanged(); @@ -117,7 +134,6 @@ private slots: void notificationReceived(const QVariantMap &data); - protected: void timerEvent(QTimerEvent *event) override; @@ -126,10 +142,12 @@ private: bool m_powerManagementAvailable = false; bool m_updateManagementAvailable = false; + UpdateType m_updateManagementType = UpdateTypeNone; bool m_timeManagementAvailable = false; bool m_updateManagementBusy = false; bool m_updateRunning = false; + int m_updateProgress = -1; Packages *m_packages = nullptr; Repositories *m_repositories = nullptr; diff --git a/libnymea-app/types/thingclass.cpp b/libnymea-app/types/thingclass.cpp index a9358286..6a1d69af 100644 --- a/libnymea-app/types/thingclass.cpp +++ b/libnymea-app/types/thingclass.cpp @@ -3,7 +3,7 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH -* Copyright (C) 2024 - 2025, chargebyte austria GmbH +* Copyright (C) 2024 - 2026, chargebyte austria GmbH * * This file is part of libnymea-app. * @@ -26,10 +26,9 @@ #include -ThingClass::ThingClass(QObject *parent) : - QObject(parent) -{ -} +ThingClass::ThingClass(QObject *parent) + : QObject(parent) +{} QUuid ThingClass::id() const { @@ -330,3 +329,8 @@ bool ThingClass::hasActionType(const QUuid &actionTypeId) } return false; } + +bool ThingClass::isAutoCreated() const +{ + return m_createMethods.contains("CreateMethodAuto"); +} diff --git a/libnymea-app/types/thingclass.h b/libnymea-app/types/thingclass.h index ae4c905d..df1e8630 100644 --- a/libnymea-app/types/thingclass.h +++ b/libnymea-app/types/thingclass.h @@ -3,7 +3,7 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH -* Copyright (C) 2024 - 2025, chargebyte austria GmbH +* Copyright (C) 2024 - 2026, chargebyte austria GmbH * * This file is part of libnymea-app. * @@ -25,15 +25,15 @@ #ifndef THINGCLASS_H #define THINGCLASS_H -#include -#include #include +#include #include +#include +#include "actiontypes.h" +#include "eventtypes.h" #include "paramtypes.h" #include "statetypes.h" -#include "eventtypes.h" -#include "actiontypes.h" class ThingClass : public QObject { @@ -135,6 +135,8 @@ public: Q_INVOKABLE bool hasActionType(const QUuid &actionTypeId); + Q_INVOKABLE bool isAutoCreated() const; + signals: void paramTypesChanged(); void settingsTypesChanged(); diff --git a/nymea-app/ui/Configuration.qml b/nymea-app/ui/Configuration.qml index 60f5afb7..83d4f229 100644 --- a/nymea-app/ui/Configuration.qml +++ b/nymea-app/ui/Configuration.qml @@ -32,6 +32,7 @@ ConfigurationBase { company: "chargebyte GmbH" connectionWizard: "/ui/connection/ConnectionWizard.qml" + iosSafeAreaBottomMarginScale: 1.0 magicEnabled: true networkSettingsEnabled: true diff --git a/nymea-app/ui/ConfigurationBase.qml b/nymea-app/ui/ConfigurationBase.qml index 9f77919d..36292715 100644 --- a/nymea-app/ui/ConfigurationBase.qml +++ b/nymea-app/ui/ConfigurationBase.qml @@ -53,6 +53,9 @@ Item { property string alternativeMainPage: "" + // iOS only: scales the safe area bottom inset before applying it as bottom margin. + property real iosSafeAreaBottomMarginScale: 1.0 + property var mainMenuLinks: null property bool closedSource: false diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml index 23832c1f..c4b7ad0c 100644 --- a/nymea-app/ui/MainPage.qml +++ b/nymea-app/ui/MainPage.qml @@ -43,6 +43,8 @@ Page { // a deepter layer as we need to include it in the blurring of the header and footer. // We don't want to paint the background on the entire screen twice (overdraw is costly) background: null + readonly property bool applyRootItemBottomMarginCompaction: true + readonly property int bottomMargin: footer.shown ? contentContainer.footerSize : 0 function configureViews() { if (Configuration.hasOwnProperty("mainViewsFilter")) { diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index ed301478..0920bf9d 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -37,6 +37,27 @@ import "connection" Item { id: root + readonly property var currentPage: swipeView.currentItem ? swipeView.currentItem.pageStack.currentItem : null + readonly property bool currentPageCompactsBottomMargin: currentPage + && currentPage.hasOwnProperty("applyRootItemBottomMarginCompaction") + && currentPage.applyRootItemBottomMarginCompaction + readonly property bool currentPageDefinesBottomMargin: currentPage && currentPage.hasOwnProperty("bottomMargin") + readonly property int currentPageBottomMargin: currentPageDefinesBottomMargin ? currentPage.bottomMargin : 0 + + readonly property int safeAreaBottomMargin: { + var margin = PlatformHelper.bottomPadding + + if (Qt.platform.os === "ios") { + margin = Math.round(PlatformHelper.bottomPadding * Math.max(0, Configuration.iosSafeAreaBottomMarginScale)) + + if (currentPageCompactsBottomMargin && !app.landscape && currentPageBottomMargin > 0 && margin > 0) { + margin = Math.floor(margin * 0.5) + } + } + + return margin + } + function handleAndroidBackButton() { return swipeView.currentItem.handleAndroidBackButton() } @@ -80,7 +101,7 @@ Item { anchors.fill: parent anchors.topMargin: PlatformHelper.topPadding - anchors.bottomMargin: PlatformHelper.bottomPadding + anchors.bottomMargin: root.safeAreaBottomMargin anchors.leftMargin: PlatformHelper.leftPadding anchors.rightMargin: PlatformHelper.rightPadding diff --git a/nymea-app/ui/appsettings/LookAndFeelSettingsPage.qml b/nymea-app/ui/appsettings/LookAndFeelSettingsPage.qml index 95657a19..07688e9d 100644 --- a/nymea-app/ui/appsettings/LookAndFeelSettingsPage.qml +++ b/nymea-app/ui/appsettings/LookAndFeelSettingsPage.qml @@ -69,12 +69,16 @@ SettingsPageBase { Layout.fillWidth: true Layout.leftMargin: app.margins Layout.rightMargin: app.margins + visible: !kioskMode && Qt.platform.os !== "ios" + Label { Layout.fillWidth: true text: qsTr("View mode") } + ComboBox { + Layout.minimumWidth: 200 model: [qsTr("Windowed"), qsTr("Maximized"), qsTr("Fullscreen"), qsTr("Automatic")] currentIndex: { switch (settings.viewMode) { diff --git a/nymea-app/ui/components/MultiSelectionTabs.qml b/nymea-app/ui/components/MultiSelectionTabs.qml index d62708f5..f65421b9 100644 --- a/nymea-app/ui/components/MultiSelectionTabs.qml +++ b/nymea-app/ui/components/MultiSelectionTabs.qml @@ -37,6 +37,7 @@ Rectangle { property color selectionColor: Style.tileOverlayColor property alias model: repeater.model property var selectedItems: [] + property int tabHeight: 0 Rectangle { id: clipMask @@ -56,7 +57,7 @@ Rectangle { delegate: Item { Layout.fillWidth: true - height: label.implicitHeight + Style.smallMargins + height: root.tabHeight > 0 ? root.tabHeight : label.implicitHeight + Style.smallMargins Rectangle { anchors.fill: parent color: root.selectionColor @@ -99,4 +100,3 @@ Rectangle { } } - diff --git a/nymea-app/ui/components/PasswordTextField.qml b/nymea-app/ui/components/PasswordTextField.qml index 1d4df06a..b4bce10e 100644 --- a/nymea-app/ui/components/PasswordTextField.qml +++ b/nymea-app/ui/components/PasswordTextField.qml @@ -56,6 +56,42 @@ ColumnLayout { readonly property bool hasNumbers: passwordTextField.text.search(/[0-9]/) >= 0 readonly property bool hasSpecialChar: passwordTextField.text.search(/(?=.*?[$*.\[\]{}()?\-'"!@#%&/\\,><':;|_~`^])/) >= 0 readonly property bool confirmationMatches: passwordTextField.text === confirmationPasswordTextField.text + readonly property var passwordRequirements: { + var requirements = [] + requirements.push({ + text: qsTr("Minimum %1 characters").arg(root.minPasswordLength), + fulfilled: root.isLongEnough + }) + + if (root.requireLowerCaseLetter) { + requirements.push({ + text: qsTr("Lowercase letters"), + fulfilled: root.hasLower + }) + } + + if (root.requireUpperCaseLetter) { + requirements.push({ + text: qsTr("Uppercase letters"), + fulfilled: root.hasUpper + }) + } + + if (root.requireNumber) { + requirements.push({ + text: qsTr("Numbers"), + fulfilled: root.hasNumbers + }) + } + + if (root.requireSpecialChar) { + requirements.push({ + text: qsTr("Special characters"), + fulfilled: root.hasSpecialChar + }) + } + return requirements + } property bool hiddenPassword: true @@ -73,39 +109,49 @@ ColumnLayout { placeholderText: root.signup ? qsTr("Pick a password") : qsTr("Password") error: root.showErrors && !root.isValidPassword -// palette.toolTipBase: Style.tooltipBackgroundColor - ToolTip.visible: root.signup && focus && !root.isValidPassword - ToolTip.delay: 1000 - ToolTip.onVisibleChanged: print("Tooltip visible changed:", ToolTip.visible, focus, root.isValidPassword) - ToolTip.text: { - var texts = [] - var checks = [] - texts.push(qsTr("Minimum %1 characters").arg(root.minPasswordLength)) - checks.push(root.isLongEnough) - if (root.requireLowerCaseLetter) { - texts.push(qsTr("Lowercase letters")) - checks.push(root.hasLower) + + ToolTip { + id: passwordRequirementsToolTip + parent: passwordTextField + visible: root.signup && passwordTextField.focus && !root.isValidPassword + delay: 1000 + timeout: -1 + x: 0 + y: passwordTextField.height + Style.smallMargins + leftPadding: Style.smallMargins + rightPadding: Style.smallMargins + topPadding: Style.smallMargins + bottomPadding: Style.smallMargins + + background: Rectangle { + color: Style.tooltipBackgroundColor + radius: Style.smallCornerRadius } - if (root.requireUpperCaseLetter) { - texts.push(qsTr("Uppercase letters")) - checks.push(root.hasUpper) + + contentItem: Column { + spacing: Style.extraSmallMargins + + Repeater { + model: root.passwordRequirements + + delegate: Row { + spacing: Style.extraSmallMargins + + CheckBox { + anchors.verticalCenter: parent.verticalCenter + checked: modelData.fulfilled + checkable: false + opacity: 1 + } + + Label { + anchors.verticalCenter: parent.verticalCenter + text: modelData.text + color: Style.foregroundColor + } + } + } } - if (root.requireNumber) { - texts.push(qsTr("Numbers")) - checks.push(root.hasNumbers) - } - if (root.requireSpecialChar) { - texts.push(qsTr("Special characters")) - checks.push(root.hasSpecialChar) - } - var ret = [] - for (var i = 0; i < texts.length; i++) { - var entry = "• ".arg(checks[i] ? "#ffffff" : Style.red) - entry += texts[i] - entry += "" - ret.push(entry) - } - return ret.join("
") } onAccepted: { diff --git a/nymea-app/ui/components/UpdateRunningOverlay.qml b/nymea-app/ui/components/UpdateRunningOverlay.qml index 636fc458..d0c945a3 100644 --- a/nymea-app/ui/components/UpdateRunningOverlay.qml +++ b/nymea-app/ui/components/UpdateRunningOverlay.qml @@ -64,6 +64,15 @@ Rectangle { wrapMode: Text.WordWrap font.pixelSize: app.largeFont } + + ProgressBar { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + visible: engine.systemController.updateProgress >= 0 + value: engine.systemController.updateProgress / 100.0 + } + Label { Layout.fillWidth: true Layout.margins: app.margins * 2 diff --git a/nymea-app/ui/connection/ConnectionWizard.qml b/nymea-app/ui/connection/ConnectionWizard.qml index c8e48337..d6baada5 100644 --- a/nymea-app/ui/connection/ConnectionWizard.qml +++ b/nymea-app/ui/connection/ConnectionWizard.qml @@ -153,7 +153,7 @@ WizardPageBase { Layout.fillWidth: true onClicked: { - if (PlatformPermissions.bluetoothPermission != PlatformPermissions.PermissionStatusGranted) { + if (PlatformPermissions.bluetoothPermission !== PlatformPermissions.PermissionStatusGranted) { PlatformPermissions.requestPermission(PlatformPermissions.PermissionBluetooth) } pageStack.push(wirelessInstructionsComponent) @@ -219,7 +219,7 @@ WizardPageBase { ColumnLayout { anchors.centerIn: parent width: parent.width - visible: hostsProxy.count == 0 + visible: hostsProxy.count === 0 spacing: Style.margins BusyIndicator { Layout.alignment: Qt.AlignHCenter diff --git a/nymea-app/ui/connection/ManualConnectionEntry.qml b/nymea-app/ui/connection/ManualConnectionEntry.qml index a468fbfa..6e752a90 100644 --- a/nymea-app/ui/connection/ManualConnectionEntry.qml +++ b/nymea-app/ui/connection/ManualConnectionEntry.qml @@ -49,23 +49,23 @@ ColumnLayout { port = portTextInput.text } - if (connectionTypeComboBox.currentIndex == 0) { + if (connectionTypeComboBox.currentIndex === 0) { if (secureCheckBox.checked) { rpcUrl = "nymeas://" + hostAddress + ":" + port } else { rpcUrl = "nymea://" + hostAddress + ":" + port } - } else if (connectionTypeComboBox.currentIndex == 1) { + } else if (connectionTypeComboBox.currentIndex === 1) { if (secureCheckBox.checked) { rpcUrl = "wss://" + hostAddress + ":" + port } else { rpcUrl = "ws://" + hostAddress + ":" + port } - } else if (connectionTypeComboBox.currentIndex == 2) { + } else if (connectionTypeComboBox.currentIndex === 2) { if (secureCheckBox.checked) { - rpcUrl = "tunnels://" + hostAddress + ":" + port + "?uuid=" + serverUuidTextInput.text + rpcUrl = "tunnels://" + hostAddress + ":" + port + "?uuid=" + serverUuidTextInput.text.replace('{', '').replace('}', '') } else { - rpcUrl = "tunnel://" + hostAddress + ":" + port + "?uuid=" + serverUuidTextInput.text + rpcUrl = "tunnel://" + hostAddress + ":" + port + "?uuid=" + serverUuidTextInput.text.replace('{', '').replace('}', '') } } @@ -90,41 +90,72 @@ ColumnLayout { Label { text: connectionTypeComboBox.currentIndex < 2 ? qsTr("Address:") : qsTr("Proxy address:") } + TextField { id: addressTextInput + objectName: "addressTextInput" Layout.fillWidth: true - placeholderText: connectionTypeComboBox.currentIndex < 2 ? "127.0.0.1" : Configuration.tunnelProxyUrl - } + placeholderText: { + if (focus || text) + return "" - Label { - text: qsTr("%1 UUID:").arg(Configuration.systemName) - visible: connectionTypeComboBox.currentIndex == 2 - } - TextField { - id: serverUuidTextInput - Layout.fillWidth: true - visible: connectionTypeComboBox.currentIndex == 2 - } - Label { text: qsTr("Port:") } - TextField { - id: portTextInput - Layout.fillWidth: true - placeholderText: connectionTypeComboBox.currentIndex === 0 - ? "2222" - : connectionTypeComboBox.currentIndex == 1 - ? "4444" - : Configuration.tunnelProxyPort - validator: IntValidator{bottom: 1; top: 65535;} + return connectionTypeComboBox.currentIndex < 2 ? "127.0.0.1" : Configuration.tunnelProxyUrl + } } Label { Layout.fillWidth: true text: qsTr("SSL:") } + CheckBox { id: secureCheckBox + checked: true } + + Label { text: qsTr("Port:") } + TextField { + id: portTextInput + + Layout.fillWidth: true + validator: IntValidator{bottom: 1; top: 65535;} + placeholderText: { + if (focus || text) + return "" + + if (connectionTypeComboBox.currentIndex === 0) { + if (secureCheckBox.checked) { + return "2222" + } else { + return "2223" + } + } + + if (connectionTypeComboBox.currentIndex === 1) { + if (secureCheckBox.checked) { + return "4444" + } else { + return "4445" + } + } + + if (connectionTypeComboBox.currentIndex === 2) + return Configuration.tunnelProxyPort + + return "2222" + } + } + + + TextField { + id: serverUuidTextInput + Layout.fillWidth: true + Layout.columnSpan: 2 + placeholderText: qsTr("%1 UUID:").arg(Configuration.systemName) + visible: connectionTypeComboBox.currentIndex === 2 + } + } } diff --git a/nymea-app/ui/customviews/GenericTypeGraph.qml b/nymea-app/ui/customviews/GenericTypeGraph.qml index 78e42f0e..c4c3c5b9 100644 --- a/nymea-app/ui/customviews/GenericTypeGraph.qml +++ b/nymea-app/ui/customviews/GenericTypeGraph.qml @@ -126,7 +126,7 @@ Item { ValueAxis { id: yAxis max: { - if (root.stateType && root.stateType.type.toLowerCase() == "bool") { + if (root.stateType && root.stateType.type.toLowerCase() === "bool") { return 1; } else { Math.ceil(logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05)) @@ -137,7 +137,7 @@ Item { // onMaxChanged: applyNiceNumbers(); labelsFont: Style.extraSmallFont labelFormat: { - if (root.stateType && root.stateType.type.toLowerCase() == "bool") { + if (root.stateType && root.stateType.type.toLowerCase() === "bool") { return "x"; } else { return "%d"; diff --git a/nymea-app/ui/customviews/MultiStateChart.qml b/nymea-app/ui/customviews/MultiStateChart.qml index d21a6a99..28607082 100644 --- a/nymea-app/ui/customviews/MultiStateChart.qml +++ b/nymea-app/ui/customviews/MultiStateChart.qml @@ -147,7 +147,7 @@ Item { // y: chartView.plotArea.y // height: chartView.plotArea.height // width: chartView.plotArea.x - x - // visible: root.stateType.type.toLowerCase() != "bool" && logsModel.minValue != logsModel.maxValue + // visible: root.stateType.type.toLowerCase() !== "bool" && logsModel.minValue != logsModel.maxValue // property double range: Math.abs(valueAxis.max - valueAxis.min) // property double stepSize: range / (valueAxis.tickCount - 1) // property int precision: valueAxis.max - valueAxis.min < 5 ? 2 : 0 @@ -256,7 +256,7 @@ Item { print("entry", entry.timestamp, entry.source, JSON.stringify(entry.values)) d.ensureValue(zeroSeries, entry.timestamp) - if (stateType.type.toLowerCase() == "bool") { + if (stateType.type.toLowerCase() === "bool") { var value = entry.values[stateType.name] if (value == null) { value = false; @@ -290,7 +290,7 @@ Item { } } - if (stateType.type.toLowerCase() == "bool") { + if (stateType.type.toLowerCase() === "bool") { var last = series.at(series.count-1); if (last.x < d.endTime) { @@ -304,7 +304,7 @@ Item { } onEntriesRemoved: (index, count) => { print("removing:", index, count, series.count) - if (stateType.type.toLowerCase() == "bool") { + if (stateType.type.toLowerCase() === "bool") { series.removePoints(index * 2, count * 2) if (series.count == 1) { series.removePoints(0, 1); diff --git a/nymea-app/ui/customviews/SensorView.qml b/nymea-app/ui/customviews/SensorView.qml index 1c995cf7..c4ae4563 100644 --- a/nymea-app/ui/customviews/SensorView.qml +++ b/nymea-app/ui/customviews/SensorView.qml @@ -57,7 +57,7 @@ Item { if (root.interfaceName == "closablesensor") { return true } - return sensorStateType && sensorStateType.type.toLowerCase() == "bool" && sensorState.value === true + return sensorStateType && sensorStateType.type.toLowerCase() === "bool" && sensorState.value === true } iconSource: { if (root.interfaceName == "closablesensor") { @@ -100,7 +100,7 @@ Item { } sourceComponent: { - if (stateType.type.toLowerCase() == "bool") { + if (stateType.type.toLowerCase() === "bool") { return boolComponent; } diff --git a/nymea-app/ui/customviews/StateChart.qml b/nymea-app/ui/customviews/StateChart.qml index e5707291..8caf6e8f 100644 --- a/nymea-app/ui/customviews/StateChart.qml +++ b/nymea-app/ui/customviews/StateChart.qml @@ -56,7 +56,7 @@ Item { property date now: new Date() readonly property int range: selectionTabs.currentValue.range - readonly property int sampleRate: root.stateType == null || root.stateType.type.toLowerCase() == "bool" ? NewLogsModel.SampleRateAny : selectionTabs.currentValue.sampleRate + readonly property int sampleRate: root.stateType == null || root.stateType.type.toLowerCase() === "bool" ? NewLogsModel.SampleRateAny : selectionTabs.currentValue.sampleRate readonly property int visibleValues: range / sampleRate @@ -114,7 +114,7 @@ Item { // print("entry", entry.timestamp, entry.source, JSON.stringify(entry.values)) zeroSeries.ensureValue(entry.timestamp) - if (root.stateType.type.toLowerCase() == "bool") { + if (root.stateType.type.toLowerCase() === "bool") { var value = entry.values[root.stateType.name] if (value == null) { value = false; @@ -148,7 +148,7 @@ Item { } } - if (root.stateType.type.toLowerCase() == "bool") { + if (root.stateType.type.toLowerCase() === "bool") { var last = valueSeries.at(valueSeries.count-1); if (last.x < d.endTime) { valueSeries.append(d.endTime, last.y) @@ -160,7 +160,7 @@ Item { } onEntriesRemoved: (index, count) => { print("removing:", index, count, valueSeries.count) - if (root.stateType.type.toLowerCase() == "bool") { + if (root.stateType.type.toLowerCase() === "bool") { valueSeries.removePoints(index * 2, count * 2) if (valueSeries.count == 1) { valueSeries.removePoints(0, 1); @@ -419,7 +419,7 @@ Item { y: chartView.y + chartView.plotArea.y height: chartView.plotArea.height width: Math.max(0, chartContainer.yAxisLabelAreaWidth - Style.smallMargins) - visible: root.stateType && root.stateType.type.toLowerCase() != "bool" && logsModel.minValue != logsModel.maxValue + visible: root.stateType && root.stateType.type.toLowerCase() !== "bool" && logsModel.minValue != logsModel.maxValue property double range: Math.abs(valueAxis.max - valueAxis.min) property double stepSize: range / (valueAxis.tickCount - 1) property int precision: valueAxis.max - valueAxis.min < 5 ? 2 : 0 @@ -589,7 +589,7 @@ Item { elide: Text.ElideRight text: toolTip.value === null ? qsTr("No data") - : root.stateType.type.toLowerCase() == "bool" + : root.stateType.type.toLowerCase() === "bool" ? root.stateType.displayName + ": " + (toolTip.value ? qsTr("Yes") : qsTr("No")) : Types.toUiValue(toolTip.value, root.stateType.unit).toFixed(root.roundTo) + Types.toUiUnit(root.stateType.unit) font: Style.extraSmallFont diff --git a/nymea-app/ui/delegates/ParamDelegate.qml b/nymea-app/ui/delegates/ParamDelegate.qml index 6819d759..a85c0caf 100644 --- a/nymea-app/ui/delegates/ParamDelegate.qml +++ b/nymea-app/ui/delegates/ParamDelegate.qml @@ -37,6 +37,7 @@ ItemDelegate { property alias value: d.value property Param param: Param { id: d + paramTypeId: paramType.id value: paramType.defaultValue } @@ -48,9 +49,11 @@ ItemDelegate { bottomPadding: 0 contentItem: ColumnLayout { id: contentItemColumn + anchors.fill: parent anchors.leftMargin: Style.margins anchors.rightMargin: Style.margins + RowLayout { Layout.fillWidth: true spacing: Style.margins @@ -70,8 +73,6 @@ ItemDelegate { Layout.fillWidth: true//!parent.labelFillsWidth Layout.maximumWidth: root.nameVisible ? contentItemColumn.width / 2 : contentItemColumn.width sourceComponent: { - print("Loading ParamDelegate"); - print("Writable:", root.writable, "type:", root.paramType.type, "min:", root.paramType.minValue, "max:", root.paramType.maxValue, "value:", root.param.value) if (!root.writable) { return stringComponent; } @@ -123,12 +124,11 @@ ItemDelegate { return null; } } - } - Component { id: stringComponent + Label { text: { switch (root.paramType.type.toLowerCase()) { @@ -141,15 +141,17 @@ ItemDelegate { elide: Text.ElideRight } } + Component { id: boolComponent + Item { implicitHeight: theSwitch.implicitHeight implicitWidth: theSwitch.implicitWidth Switch { id: theSwitch anchors { top: parent.top; right: parent.right; bottom: parent.bottom } - width: Math.min(parent.width, implicitiWidth) + width: Math.min(parent.width, implicitWidth) checked: root.param.value === true Component.onCompleted: { if (root.param.value === undefined) { @@ -162,10 +164,11 @@ ItemDelegate { } } } - } + Component { id: sliderComponent + RowLayout { spacing: Style.margins @@ -206,6 +209,7 @@ ItemDelegate { root.param.value = newValue; } } + Label { text: Types.toUiValue(root.param.value, root.paramType.unit).toFixed(slider.decimals) + Types.toUiUnit(root.paramType.unit) } @@ -231,18 +235,19 @@ ItemDelegate { : 2000000000 editable: true width: 150 - onValueModified: root.param.value = value + floatingPoint: root.paramType.type.toLowerCase() === "double" - floatingPoint: root.paramType.type.toLowerCase() == "double" + onValueModified: (value) => { root.param.value = value } Component.onCompleted: { print("from:", from, "min", root.paramType.minValue) print("to:", to, "max", root.paramType.maxValue) - if (root.value === undefined) { - root.value = value + if (root.param.value === undefined) { + root.param.value = value } } } + Label { text: Types.toUiUnit(root.paramType.unit) visible: text.length > 0 @@ -255,40 +260,48 @@ ItemDelegate { TextField { text: root.param.value !== undefined ? root.param.value - : root.paramType.defaultValue + : root.paramType.defaultValue !== undefined ? root.paramType.defaultValue : "" + + placeholderText: root.placeholderText onEditingFinished: { root.param.value = text } + Component.onCompleted: { if (root.param.value === undefined) { root.param.value = text; } } - placeholderText: root.placeholderText + } } Component { id: comboBoxComponent + ComboBox { id: control Layout.fillWidth: true model: root.paramType.allowedValues - displayText: currentText + ( root.paramType.unit != Types.UnitNone ? " " + Types.toUiUnit(root.paramType.unit) : "") + displayText: { + if (currentIndex < 0 || currentIndex >= root.paramType.allowedValues.length) { + return ""; + } + return Types.toUiValue(root.paramType.allowedValues[currentIndex], root.paramType.unit) + + (root.paramType.unit !== Types.UnitNone ? " " + Types.toUiUnit(root.paramType.unit) : ""); + } currentIndex: root.paramType.allowedValues.indexOf(root.param.value !== undefined ? root.param.value : root.paramType.defaultValue) delegate: ItemDelegate { width: control.width - text: Types.toUiValue(modelData, root.paramType.unit) + ( root.paramType.unit != Types.UnitNone ? " " + Types.toUiUnit(root.paramType.unit) : "") + text: Types.toUiValue(modelData, root.paramType.unit) + ( root.paramType.unit !== Types.UnitNone ? " " + Types.toUiUnit(root.paramType.unit) : "") highlighted: control.highlightedIndex === index } - onActivated: (index) => { - root.param.value = root.paramType.allowedValues[index] - } + onActivated: (index) => { root.param.value = root.paramType.allowedValues[index] } Component.onCompleted: { - if (root.value === undefined) { - root.value = model[0] + if (root.param.value === undefined) { + root.param.value = model[0] } } } @@ -296,6 +309,7 @@ ItemDelegate { Component { id: colorPickerComponent + ColorPickerPre510 { id: colorPicker implicitHeight: 200 @@ -333,6 +347,7 @@ ItemDelegate { Component { id: colorTemperaturePickerComponent + ColorPickerCt { id: colorPickerCt implicitHeight: 50 @@ -340,7 +355,7 @@ ItemDelegate { maxCt: root.paramType.maxValue ct: root.param.value !== undefined ? root.param.value - : root.paramType.defaultValue + : root.paramType.defaultValue !== undefined ? root.paramType.defaultValue : root.paramType.minValue @@ -359,6 +374,7 @@ ItemDelegate { Component { id: colorPreviewComponent + Rectangle { implicitHeight: app.mediumFont implicitWidth: implicitHeight diff --git a/nymea-app/ui/delegates/StateDelegate.qml b/nymea-app/ui/delegates/StateDelegate.qml index 97bf68e5..bd0fb3d4 100644 --- a/nymea-app/ui/delegates/StateDelegate.qml +++ b/nymea-app/ui/delegates/StateDelegate.qml @@ -63,7 +63,7 @@ ItemDelegate { id: loader Layout.fillWidth: !parent.labelFillsWidth sourceComponent: { - print("Loading ParamDelegate"); + print("Loading StateDelegate"); print("Writable:", root.writable, "type:", root.stateType.type, "min:", root.stateType.minValue, "max:", root.stateType.maxValue, "value:", root.param.value) if (!root.writable) { return stringComponent; @@ -222,7 +222,7 @@ ItemDelegate { width: 150 onValueModified: root.param.value = value - floatingPoint: root.stateType.type.toLowerCase() == "double" + floatingPoint: root.stateType.type.toLowerCase() === "double" Component.onCompleted: { print("from:", from, "min", root.stateType.minValue) @@ -264,11 +264,11 @@ ItemDelegate { id: control Layout.fillWidth: true model: root.stateType.allowedValues - displayText: currentText + ( root.stateType.unit != Types.UnitNone ? " " + Types.toUiUnit(root.stateType.unit) : "") + displayText: currentText + ( root.stateType.unit !== Types.UnitNone ? " " + Types.toUiUnit(root.stateType.unit) : "") currentIndex: root.stateType.allowedValues.indexOf(root.param.value !== undefined ? root.param.value : root.stateType.defaultValue) delegate: ItemDelegate { width: control.width - text: Types.toUiValue(modelData, root.stateType.unit) + ( root.stateType.unit != Types.UnitNone ? " " + Types.toUiUnit(root.stateType.unit) : "") + text: Types.toUiValue(modelData, root.stateType.unit) + ( root.stateType.unit !== Types.UnitNone ? " " + Types.toUiUnit(root.stateType.unit) : "") highlighted: control.highlightedIndex === index } onActivated: (index) => { diff --git a/nymea-app/ui/delegates/statedelegates/SpinBoxDelegate.qml b/nymea-app/ui/delegates/statedelegates/SpinBoxDelegate.qml index bee05013..75c76573 100644 --- a/nymea-app/ui/delegates/statedelegates/SpinBoxDelegate.qml +++ b/nymea-app/ui/delegates/statedelegates/SpinBoxDelegate.qml @@ -36,7 +36,7 @@ SpinBox { stepSize: Math.min(10, (to - from) / 10) property var unit: Types.UnitNone editable: true - onValueModified: { + onValueModified: (value) => { changed(value) } textFromValue: function(value) { diff --git a/nymea-app/ui/devicepages/DeviceDetailsPage.qml b/nymea-app/ui/devicepages/DeviceDetailsPage.qml index ea09c028..f9fb1fbd 100644 --- a/nymea-app/ui/devicepages/DeviceDetailsPage.qml +++ b/nymea-app/ui/devicepages/DeviceDetailsPage.qml @@ -172,17 +172,9 @@ Page { var maxValue = stateDelegate.stateType.maxValue !== undefined ? stateDelegate.stateType.maxValue : 2000000000; - print("pushing delegate for", stateDelegate.stateType.name, "from:", minValue, "to:", maxValue, "possible:", stateDelegate.stateType.possibleValuesDisplayNames) - stateDelegateLoader.setSource("../delegates/statedelegates/" + sourceComp, - { - value: root.thing.states.getState(stateType.id).value, - possibleValues: stateDelegate.stateType.possibleValues, - possibleValuesDisplayNames: stateDelegate.stateType.possibleValuesDisplayNames, - from: minValue, - to: maxValue, - unit: stateDelegate.stateType.unit, - writable: false, - stateType: stateDelegate.stateType + console.log("pushing delegate for", stateDelegate.stateType.name, "from:", minValue, "to:", maxValue, "possible:", stateDelegate.stateType.possibleValuesDisplayNames) + stateDelegateLoader.setSource("../delegates/statedelegates/" + sourceComp, { + value: root.thing.states.getState(stateType.id).value }) } @@ -231,6 +223,16 @@ Page { property: "unit" value: stateDelegate.stateType.unit } + Binding { + target: stateDelegateLoader.item.hasOwnProperty("writable") ? stateDelegateLoader.item : null + property: "writable" + value: false + } + Binding { + target: stateDelegateLoader.item.hasOwnProperty("stateType") ? stateDelegateLoader.item : null + property: "stateType" + value: stateDelegate.stateType + } } } diff --git a/nymea-app/ui/magic/EventDescriptorDelegate.qml b/nymea-app/ui/magic/EventDescriptorDelegate.qml index 3a378fcd..8d6a6085 100644 --- a/nymea-app/ui/magic/EventDescriptorDelegate.qml +++ b/nymea-app/ui/magic/EventDescriptorDelegate.qml @@ -61,7 +61,7 @@ NymeaSwipeDelegate { operatorString = " = "; break; case ParamDescriptor.ValueOperatorNotEquals: - operatorString = " != "; + operatorString = " !== "; break; case ParamDescriptor.ValueOperatorGreater: operatorString = " > "; diff --git a/nymea-app/ui/mainviews/dashboard/DashboardStateDelegate.qml b/nymea-app/ui/mainviews/dashboard/DashboardStateDelegate.qml index 5bcc84dc..1d8fa1bf 100644 --- a/nymea-app/ui/mainviews/dashboard/DashboardStateDelegate.qml +++ b/nymea-app/ui/mainviews/dashboard/DashboardStateDelegate.qml @@ -84,7 +84,7 @@ DashboardDelegateBase { Label { Layout.fillWidth: true - visible: root.stateType && root.stateType.type.toLowerCase() != "bool" + visible: root.stateType && root.stateType.type.toLowerCase() !== "bool" horizontalAlignment: Text.AlignHCenter font: Style.largeFont elide: Text.ElideRight diff --git a/nymea-app/ui/system/SystemUpdatePage.qml b/nymea-app/ui/system/SystemUpdatePage.qml index 3f11bb72..ea6be6d2 100644 --- a/nymea-app/ui/system/SystemUpdatePage.qml +++ b/nymea-app/ui/system/SystemUpdatePage.qml @@ -26,6 +26,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Controls.Material import QtQuick.Layouts + import Nymea import "../components" @@ -48,12 +49,12 @@ Page { Connections { target: engine.systemController - onEnableRepositoryFinished: { - if (!success) { - var popup = errorDialogComponent.createObject(app, {errorCode: qsTr("Failure adding repository.") }) - popup.open(); - } - } + onEnableRepositoryFinished: (id, success) => { + if (!success) { + var popup = errorDialogComponent.createObject(app, {errorCode: qsTr("Failure adding repository.") }) + popup.open(); + } + } } PackagesFilterModel { @@ -62,151 +63,350 @@ Page { updatesOnly: true } - ColumnLayout { - id: contentColumn + Loader { + id: updateTypeLoader anchors.fill: parent + sourceComponent: engine.systemController.updateManagementType === SystemController.UpdateTypeSystem ? + updateSystemComponent : + updatePackageManagerComponent - Item { - Layout.fillHeight: true - Layout.fillWidth: true - visible: updatesModel.count === 0 + } + + Component { + id: updateSystemComponent + + ColumnLayout { + id: systemUpdateContentColumn + + property Package systemPackage: engine.systemController.packages.get(0) + property bool changelogAvailable: systemUpdateContentColumn.systemPackage.changelog.length !== 0 + + anchors.fill: parent + anchors.margins: app.margins + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + visible: updatesModel.count === 0 + + ColumnLayout { + width: parent.width + anchors.centerIn: parent + spacing: app.margins * 2 + + ColorIcon { + Layout.preferredHeight: Style.iconSize * 4 + Layout.preferredWidth: height + Layout.alignment: Qt.AlignHCenter + name: "qrc:/icons/system-update.svg" + color: Style.accentColor + RotationAnimation on rotation { + from: 0; to: 360 + duration: 2000 + running: engine.systemController.updateManagementBusy + loops: Animation.Infinite + } + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + text: engine.systemController.updateManagementBusy ? qsTr("Checking for updates...") : qsTr("Your system is up to date.") + } + + Label { + id: versionLabel + Layout.alignment: Qt.AlignHCenter + wrapMode: Text.WordWrap + text: qsTr("Version:") + " " + systemUpdateContentColumn.systemPackage.installedVersion + + MouseArea { + anchors.fill: parent + onClicked: { + PlatformHelper.toClipBoard(versionLabel.text); + ToolTip.show(qsTr("Version copied to clipboard"), 500); + } + } + } + + Button { + Layout.fillWidth: true + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + + text: qsTr("Check for updates") + enabled: !engine.systemController.updateManagementBusy + onClicked: { + engine.systemController.checkForUpdates() + } + } + } + } ColumnLayout { - width: parent.width - anchors.centerIn: parent - spacing: app.margins * 2 + Layout.fillHeight: true + Layout.fillWidth: true + visible: updatesModel.count > 0 - ColorIcon { - Layout.preferredHeight: Style.iconSize * 4 - Layout.preferredWidth: height - Layout.alignment: Qt.AlignHCenter - name: "qrc:/icons/system-update.svg" - color: Style.accentColor - RotationAnimation on rotation { - from: 0; to: 360 - duration: 2000 - running: engine.systemController.updateManagementBusy - loops: Animation.Infinite + RowLayout { + Layout.margins: app.margins + spacing: app.margins + + ColorIcon { + Layout.preferredHeight: Style.iconSize * 2 + Layout.preferredWidth: height + name: "qrc:/icons/system-update.svg" + color: Style.accentColor + RotationAnimation on rotation { + from: 0; to: 360 + duration: 2000 + running: engine.systemController.updateManagementBusy + loops: Animation.Infinite + } } + + Label { + Layout.fillWidth: true + text: engine.systemController.updateManagementBusy ? qsTr("Checking for updates...") : qsTr("%1 update available").arg(Configuration.systemName) + } + } + + + Button { + Layout.fillWidth: true + text: qsTr("Check again") + enabled: !engine.systemController.updateManagementBusy + onClicked: engine.systemController.checkForUpdates() + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Installed version:") + subText: systemUpdateContentColumn.systemPackage.installedVersion + progressive: false + } + + NymeaSwipeDelegate { + Layout.fillWidth: true + text: qsTr("Candidate version:") + subText: systemUpdateContentColumn.systemPackage.candidateVersion + visible: systemUpdateContentColumn.systemPackage.updateAvailable || systemUpdateContentColumn.systemPackage.installedVersion.length === 0 + progressive: false + } + + ThinDivider { + visible: changelogAvailable } Label { Layout.fillWidth: true - Layout.margins: app.margins - horizontalAlignment: Text.AlignHCenter + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + + visible: changelogAvailable + text: qsTr("Update changelog:") + } + + Label { + Layout.fillWidth: true + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + + Layout.alignment: Qt.AlignHCenter wrapMode: Text.WordWrap - text: engine.systemController.updateManagementBusy ? qsTr("Checking for updates...") : qsTr("Your system is up to date.") + visible: changelogAvailable + text: systemPackage.changelog + + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true } Button { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Check for updates") + Layout.fillWidth: true + //Layout.leftMargin: Style.margins + //Layout.rightMargin: Style.margins + + text: qsTr("Update") + visible: updatesModel.count > 0 enabled: !engine.systemController.updateManagementBusy onClicked: { - engine.systemController.checkForUpdates() + var dialog = Qt.createComponent(Qt.resolvedUrl("../components/NymeaDialog.qml")); + var text = qsTr("This will start a system update. Note that the update might take several minutes and your %1 system might not be functioning properly during this time and restart during the process.\nDo you want to proceed?").arg(Configuration.systemName) + var popup = dialog.createObject(app, + { + headerIcon: "qrc:/icons/system-update.svg", + title: qsTr("System update"), + text: text, + standardButtons: Dialog.Ok | Dialog.Cancel + }); + popup.open(); + popup.accepted.connect(function() { + engine.systemController.updatePackages() + }) } } } } + } + + + Component { + id: updatePackageManagerComponent ColumnLayout { - Layout.fillHeight: true - Layout.fillWidth: true - visible: updatesModel.count > 0 + id: contentColumn + anchors.fill: parent - RowLayout { - Layout.margins: app.margins - spacing: app.margins + Item { + Layout.fillHeight: true + Layout.fillWidth: true + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins - ColorIcon { - Layout.preferredHeight: Style.iconSize * 2 - Layout.preferredWidth: height - name: "qrc:/icons/system-update.svg" - color: Style.accentColor - RotationAnimation on rotation { - from: 0; to: 360 - duration: 2000 - running: engine.systemController.updateManagementBusy - loops: Animation.Infinite - } - } + visible: updatesModel.count === 0 ColumnLayout { + width: parent.width + anchors.centerIn: parent + spacing: app.margins * 2 + + ColorIcon { + Layout.preferredHeight: Style.iconSize * 4 + Layout.preferredWidth: height + Layout.alignment: Qt.AlignHCenter + name: "qrc:/icons/system-update.svg" + color: Style.accentColor + RotationAnimation on rotation { + from: 0; to: 360 + duration: 2000 + running: engine.systemController.updateManagementBusy + loops: Animation.Infinite + } + } + Label { Layout.fillWidth: true - text: engine.systemController.updateManagementBusy ? qsTr("Checking for updates...") : qsTr("%n update(s) available", "", updatesModel.count) + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + text: engine.systemController.updateManagementBusy ? qsTr("Checking for updates...") : qsTr("Your system is up to date.") } - GridLayout { - columns: width > 250 ? 2 : 1 - Button { + + Button { + Layout.fillWidth: true + text: qsTr("Check for updates") + enabled: !engine.systemController.updateManagementBusy + onClicked: engine.systemController.checkForUpdates() + } + } + } + + ColumnLayout { + Layout.fillHeight: true + Layout.fillWidth: true + visible: updatesModel.count > 0 + + RowLayout { + spacing: Style.margins + Layout.leftMargin: Style.margins + Layout.rightMargin: Style.margins + + ColorIcon { + Layout.preferredHeight: Style.iconSize * 2 + Layout.preferredWidth: height + name: "qrc:/icons/system-update.svg" + color: Style.accentColor + RotationAnimation on rotation { + from: 0; to: 360 + duration: 2000 + running: engine.systemController.updateManagementBusy + loops: Animation.Infinite + } + } + + ColumnLayout { + Label { Layout.fillWidth: true - text: qsTr("Check again") - enabled: !engine.systemController.updateManagementBusy - onClicked: { - engine.systemController.checkForUpdates() + text: engine.systemController.updateManagementBusy ? qsTr("Checking for updates...") : qsTr("%n update(s) available", "", updatesModel.count) + } + GridLayout { + columns: width > 250 ? 2 : 1 + Button { + Layout.fillWidth: true + text: qsTr("Check again") + enabled: !engine.systemController.updateManagementBusy + onClicked: { + engine.systemController.checkForUpdates() + } + } + Button { + Layout.fillWidth: true + text: qsTr("Update all") + visible: updatesModel.count > 0 + enabled: !engine.systemController.updateManagementBusy + onClicked: { + var dialog = Qt.createComponent(Qt.resolvedUrl("../components/NymeaDialog.qml")); + var text = qsTr("This will start a system update. Note that the update might take several minutes and your %1 system might not be functioning properly during this time and restart during the process.\nDo you want to proceed?").arg(Configuration.systemName) + var popup = dialog.createObject(app, + { + headerIcon: "qrc:/icons/system-update.svg", + title: qsTr("System update"), + text: text, + standardButtons: Dialog.Ok | Dialog.Cancel + }); + popup.open(); + popup.accepted.connect(function() { + engine.systemController.updatePackages() + }) + } } } - Button { - Layout.fillWidth: true - text: qsTr("Update all") - visible: updatesModel.count > 0 - enabled: !engine.systemController.updateManagementBusy - onClicked: { - var dialog = Qt.createComponent(Qt.resolvedUrl("../components/NymeaDialog.qml")); - var text = qsTr("This will start a system update. Note that the update might take several minutes and your %1 system might not be functioning properly during this time and restart during the process.\nDo you want to proceed?").arg(Configuration.systemName) - var popup = dialog.createObject(app, - { - headerIcon: "qrc:/icons/system-update.svg", - title: qsTr("System update"), - text: text, - standardButtons: Dialog.Ok | Dialog.Cancel - }); - popup.open(); - popup.accepted.connect(function() { - engine.systemController.updatePackages() - }) - } + } + + } + + ThinDivider {} + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + visible: count > 0 + model: updatesModel + clip: true + delegate: NymeaSwipeDelegate { + width: parent.width + text: model.displayName + subText: model.candidateVersion + prominentSubText: false + iconName: model.updateAvailable + ? Qt.resolvedUrl("qrc:/icons/system-update.svg") + : Qt.resolvedUrl("qrc:/icons/view-" + (model.installedVersion.length > 0 ? "expand" : "collapse") + ".svg") + iconColor: model.updateAvailable + ? "green" + : model.installedVersion.length > 0 ? "blue" : Style.iconColor + onClicked: { + pageStack.push(Qt.resolvedUrl("PackageDetailsPage.qml"), {pkg: updatesModel.get(index)}) } } } - } ThinDivider {} - ListView { + NymeaSwipeDelegate { Layout.fillWidth: true - Layout.fillHeight: true - visible: count > 0 - model: updatesModel - clip: true - delegate: NymeaSwipeDelegate { - width: parent.width - text: model.displayName - subText: model.candidateVersion - prominentSubText: false - iconName: model.updateAvailable - ? Qt.resolvedUrl("qrc:/icons/system-update.svg") - : Qt.resolvedUrl("qrc:/icons/view-" + (model.installedVersion.length > 0 ? "expand" : "collapse") + ".svg") - iconColor: model.updateAvailable - ? "green" - : model.installedVersion.length > 0 ? "blue" : Style.iconColor - onClicked: { - pageStack.push(Qt.resolvedUrl("PackageDetailsPage.qml"), {pkg: updatesModel.get(index)}) - } + text: qsTr("Install or remove software") + onClicked: { + pageStack.push("PackageListPage.qml") } } } - - ThinDivider {} - - NymeaSwipeDelegate { - Layout.fillWidth: true - text: qsTr("Install or remove software") - onClicked: { - pageStack.push("PackageListPage.qml") - } - } } Component { @@ -253,8 +453,7 @@ Page { } } - UpdateRunningOverlay { - } + UpdateRunningOverlay { } Component { id: errorDialogComponent diff --git a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml index 768c0606..b353e493 100644 --- a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml +++ b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml @@ -59,20 +59,27 @@ SettingsPageBase { width: implicitWidth + app.margins x: parent.width - width - Component.onCompleted: { - deviceMenu.addItem(menuEntryComponent.createObject(deviceMenu, {text: qsTr("Rename"), iconSource: "qrc:/icons/edit.svg", functionName: "renameThing"})) - // FIXME: This isn't entirely correct... we should have a way to know if a particular thing is in fact autocreated - // This check might be wrong for thingClasses with multiple create methods... - if (!root.thing.isChild || root.thing.thingClass.createMethods.indexOf("CreateMethodAuto") < 0) { - deviceMenu.addItem(menuEntryComponent.createObject(deviceMenu, {text: qsTr("Delete"), iconSource: "qrc:/icons/delete.svg", functionName: "deleteThing"})) - } - // FIXME: This isn't entirely correct... we should have a way to know if a particular thing is in fact autocreated - // This check might be wrong for thingClasses with multiple create methods... - if (!root.thing.isChild || root.thingClass.createMethods.indexOf("CreateMethodAuto") < 0) { - deviceMenu.addItem(menuEntryComponent.createObject(deviceMenu, {text: qsTr("Reconfigure"), iconSource: "qrc:/icons/configure.svg", functionName: "reconfigureThing"})) + function addMenuEntry(properties) { + var item = menuEntryComponent.createObject(null, properties) + if (!item) { + console.warn("Failed to create menu entry", properties && properties.text) + return } - deviceMenu.addItem(menuEntryComponent.createObject(deviceMenu, {text: qsTr("Details"), iconSource: "qrc:/icons/info.svg", functionName: "thingDetails"})) + deviceMenu.addItem(item) + } + + Component.onCompleted: { + addMenuEntry({text: qsTr("Rename"), iconSource: "qrc:/icons/edit.svg", functionName: "renameThing"}) + + // FIXME: This isn't entirely correct... we should have a way to know if a particular thing is in fact autocreated + // This check might be wrong for thingClasses with multiple create methods... + if (!root.thing.isChild && !root.thing.thingClass.isAutoCreated()) { + addMenuEntry({text: qsTr("Delete"), iconSource: "qrc:/icons/delete.svg", functionName: "deleteThing"}) + addMenuEntry({text: qsTr("Reconfigure"), iconSource: "qrc:/icons/configure.svg", functionName: "reconfigureThing"}) + } + + addMenuEntry({text: qsTr("Details"), iconSource: "qrc:/icons/info.svg", functionName: "thingDetails"}) } function renameThing() { @@ -106,8 +113,8 @@ SettingsPageBase { Connections { target: engine.thingManager - onRemoveThingReply: { - if (d.pendingCommand != commandId) { + function onRemoveThingReply(commandId, thingError, ruleIds) { + if (d.pendingCommand !== commandId) { return; } @@ -140,6 +147,7 @@ SettingsPageBase { prominentSubText: false progressive: false } + NymeaItemDelegate { Layout.fillWidth: true text: root.thing.thingClass.displayName @@ -191,6 +199,7 @@ SettingsPageBase { StateTypesProxy { id: ioModel + stateTypes: root.thing.thingClass.stateTypes digitalInputs: true digitalOutputs: true @@ -200,13 +209,14 @@ SettingsPageBase { Repeater { model: ioModel + delegate: NymeaSwipeDelegate { Layout.fillWidth: true iconName: "qrc:/icons/io-connections.svg" text: model.displayName subText: { - if (ioStateType.ioType == Types.IOTypeDigitalInput || ioStateType.ioType == Types.IOTypeAnalogInput) { + if (ioStateType.ioType === Types.IOTypeDigitalInput || ioStateType.ioType === Types.IOTypeAnalogInput) { if (inputConnectionWatcher.ioConnection) { return "%1: %2".arg(inputConnectionWatcher.outputThing.name).arg(inputConnectionWatcher.outputStateType.displayName) } @@ -223,14 +233,17 @@ SettingsPageBase { IOInputConnectionWatcher { id: inputConnectionWatcher + ioConnections: engine.thingManager.ioConnections inputThingId: root.thing.id inputStateTypeId: ioStateType.id property Thing outputThing: ioConnection ? engine.thingManager.things.getThing(ioConnection.outputThingId) : null property StateType outputStateType: ioConnection ? outputThing.thingClass.stateTypes.getStateType(ioConnection.outputStateTypeId) : null } + IOOutputConnectionWatcher { id: outputConnectionWatcher + ioConnections: engine.thingManager.ioConnections outputThingId: root.thing.id outputStateTypeId: ioStateType.id @@ -253,6 +266,7 @@ SettingsPageBase { Repeater { id: settingsRepeater + model: root.thing.settings delegate: ParamDelegate { Layout.fillWidth: true @@ -360,7 +374,7 @@ SettingsPageBase { property IOInputConnectionWatcher inputWatcher: null property IOOutputConnectionWatcher outputWatcher: null - readonly property bool isInput: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput + readonly property bool isInput: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput Label { Layout.fillWidth: true @@ -377,16 +391,16 @@ SettingsPageBase { model: ThingsProxy { id: connectableIODevices engine: _engine - showDigitalInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalOutput - showDigitalOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput - showAnalogInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogOutput - showAnalogOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput + showDigitalInputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalOutput + showDigitalOutputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput + showAnalogInputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogOutput + showAnalogOutputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput } textRole: "name" Layout.fillWidth: true Component.onCompleted: { for (var i = 0; i < connectableIODevices.count; i++) { - if (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) { + if (ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput) { if (connectableIODevices.get(i).id === ioConnectionDialog.inputWatcher.ioConnection.outputThingId) { ioThingComboBox.currentIndex = i; break; @@ -407,10 +421,10 @@ SettingsPageBase { model: StateTypesProxy { id: connectableStateTypes stateTypes: connectableIODevices.get(ioThingComboBox.currentIndex).thingClass.stateTypes - digitalInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalOutput - digitalOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput - analogInputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogOutput - analogOutputs: ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput + digitalInputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalOutput + digitalOutputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput + analogInputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogOutput + analogOutputs: ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput } textRole: "displayName" Layout.fillWidth: true @@ -418,7 +432,7 @@ SettingsPageBase { // print("loading for:", ioConnectionDialog.inputWatcher.ioConnection.outputStateTypeId) for (var i = 0; i < connectableStateTypes.count; i++) { print("checking:", connectableStateTypes.get(i).id) - if (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) { + if (ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput || ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput) { if (connectableStateTypes.get(i).id === ioConnectionDialog.inputWatcher.ioConnection.outputStateTypeId) { ioStateComboBox.currentIndex = i; break; @@ -442,6 +456,7 @@ SettingsPageBase { CheckBox { id: invertCheckBox + checked: ioConnectionDialog.isInput ? ioConnectionDialog.inputWatcher.ioConnection.inverted : ioConnectionDialog.outputWatcher.ioConnection.inverted } } @@ -453,7 +468,7 @@ SettingsPageBase { columns: width > (cancelButton.implicitWidth + disconnectButton.implicitWidth + connectButton.implicitWidth) ? 4 : 1 - layoutDirection: columns == 1 ? Qt.RightToLeft : Qt.LeftToRight + layoutDirection: columns === 1 ? Qt.RightToLeft : Qt.LeftToRight Item { Layout.fillWidth: true @@ -473,8 +488,8 @@ SettingsPageBase { Layout.fillWidth: buttonGrid.columns === 1 onClicked: { - if (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput - || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) { + if (ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput + || ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput) { engine.thingManager.disconnectIO(ioConnectionDialog.inputWatcher.ioConnection.id); } else { engine.thingManager.disconnectIO(ioConnectionDialog.outputWatcher.ioConnection.id); @@ -495,8 +510,8 @@ SettingsPageBase { var inputStateTypeId; var outputThingId; var outputStateTypeId; - if (ioConnectionDialog.ioStateType.ioType == Types.IOTypeDigitalInput - || ioConnectionDialog.ioStateType.ioType == Types.IOTypeAnalogInput) { + if (ioConnectionDialog.ioStateType.ioType === Types.IOTypeDigitalInput + || ioConnectionDialog.ioStateType.ioType === Types.IOTypeAnalogInput) { inputThingId = root.thing.id; inputStateTypeId = ioConnectionDialog.ioStateType.id; outputThingId = connectableIODevices.get(ioThingComboBox.currentIndex).id; diff --git a/nymea-app/ui/thingconfiguration/SetupWizard.qml b/nymea-app/ui/thingconfiguration/SetupWizard.qml index e636333e..de4f472b 100644 --- a/nymea-app/ui/thingconfiguration/SetupWizard.qml +++ b/nymea-app/ui/thingconfiguration/SetupWizard.qml @@ -156,13 +156,12 @@ Page { Connections { target: engine.thingManager - onPairThingReply: { + function onPairThingReply(commandId, thingError, pairingTransactionId, setupMethod, displayMessage, oAuthUrl) { busyOverlay.shown = false if (thingError !== Thing.ThingErrorNoError) { busyOverlay.shown = false; internalPageStack.push(resultsPage, {thingError: thingError, message: displayMessage}); return; - } d.pairingTransactionId = pairingTransactionId; @@ -179,17 +178,21 @@ Page { break; default: print("Setup method reply not handled:", setupMethod); + break; } } - onConfirmPairingReply: { + + function onConfirmPairingReply(commandId, thingError, thingId, displayMessage) { busyOverlay.shown = false internalPageStack.push(resultsPage, {thingError: thingError, thingId: thingId, message: displayMessage}) } - onAddThingReply: { + + function onAddThingReply(commandId, thingError, thingId, displayMessage) { busyOverlay.shown = false; internalPageStack.push(resultsPage, {thingError: thingError, thingId: thingId, message: displayMessage}) } - onReconfigureThingReply: { + + function onReconfigureThingReply(commandId, thingError, displayMessage) { busyOverlay.shown = false; internalPageStack.push(resultsPage, {thingError: thingError, thingId: root.thing.id, message: displayMessage}) } @@ -372,7 +375,7 @@ Page { Component.onCompleted: { if (root.thingClass.id.toString().match(/\{?f0dd4c03-0aca-42cc-8f34-9902457b05de\}?/)) { console.warn("checking Notification permission!") - if (PlatformPermissions.notificationsPermission != PlatformPermissions.PermissionStatusGranted) { + if (PlatformPermissions.notificationsPermission !== PlatformPermissions.PermissionStatusGranted) { console.warn("Notification permission missing!") PlatformPermissions.requestPermission(PlatformPermissions.PermissionNotifications) } @@ -460,6 +463,11 @@ Page { wrapMode: Text.WordWrap } + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.margins + } + TextField { id: usernameTextField Layout.fillWidth: true @@ -468,6 +476,11 @@ Page { visible: pairingPage.setupMethod === "SetupMethodUserAndPassword" } + Item { + Layout.fillWidth: true + Layout.preferredHeight: Style.margins + } + PasswordTextField { id: pinTextField Layout.fillWidth: true @@ -476,7 +489,6 @@ Page { signup: false } - Button { Layout.fillWidth: true Layout.margins: app.margins @@ -549,8 +561,9 @@ Page { WebView { id: oAuthWebView - anchors.fill: parent + url: oAuthPage.oAuthUrl + anchors.fill: parent function finishProcess(url) { print("Confirm pairing") @@ -559,7 +572,7 @@ Page { oAuthWebView.visible = false } - onUrlChanged: { + onUrlChanged: (url) => { print("OAUTH URL changed", url) if (url.toString().indexOf("https://127.0.0.1") == 0) { print("Redirect URL detected!") diff --git a/version.txt b/version.txt index bb070667..54e2f1d3 100644 --- a/version.txt +++ b/version.txt @@ -1,2 +1,2 @@ -1.11.3 -700 +1.11.5 +709