Merge PR #1136: Introduce system based updates and add progress and changelog

This commit is contained in:
jenkins 2026-02-27 20:07:59 +01:00
commit 43857e6512
28 changed files with 762 additions and 309 deletions

View File

@ -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 <QTimeZone>
#include <QJsonDocument>
#include <QMetaEnum>
#include <QTimeZone>
#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<SystemController::UpdateType>();
m_updateManagementType = static_cast<SystemController::UpdateType>(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 &params)
{
qCDebug(dcSystemController) << "Remove result" << commandId << params;
qCDebug(dcSystemController()) << "Remove result" << commandId << params;
}
void SystemController::enableRepositoryResponse(int commandId, const QVariantMap &params)
{
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 &params)
{
qCDebug(dcSystemController) << "set time response" << commandId << params;
qCDebug(dcSystemController()) << "set time response" << commandId << params;
}
void SystemController::restartResponse(int commandId, const QVariantMap &params)
@ -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<SystemController::UpdateType>();
m_updateManagementType = static_cast<SystemController::UpdateType>(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

View File

@ -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;

View File

@ -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 <QDebug>
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");
}

View File

@ -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 <QObject>
#include <QUuid>
#include <QList>
#include <QObject>
#include <QString>
#include <QUuid>
#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();

View File

@ -32,6 +32,7 @@ ConfigurationBase {
company: "chargebyte GmbH"
connectionWizard: "/ui/connection/ConnectionWizard.qml"
iosSafeAreaBottomMarginScale: 1.0
magicEnabled: true
networkSettingsEnabled: true

View File

@ -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

View File

@ -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")) {

View File

@ -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

View File

@ -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) {

View File

@ -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 {
}
}

View File

@ -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 = "<font color=\"%1\">• ".arg(checks[i] ? "#ffffff" : Style.red)
entry += texts[i]
entry += "</font>"
ret.push(entry)
}
return ret.join("<br>")
}
onAccepted: {

View File

@ -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

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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";

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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) => {

View File

@ -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) {

View File

@ -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
}
}
}

View File

@ -61,7 +61,7 @@ NymeaSwipeDelegate {
operatorString = " = ";
break;
case ParamDescriptor.ValueOperatorNotEquals:
operatorString = " != ";
operatorString = " !== ";
break;
case ParamDescriptor.ValueOperatorGreater:
operatorString = " > ";

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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!")

View File

@ -1,2 +1,2 @@
1.11.3
700
1.11.5
709