diff --git a/libnymea-app-core/configuration/nymeaconfiguration.cpp b/libnymea-app-core/configuration/nymeaconfiguration.cpp index b9d5e096..ad593fd3 100644 --- a/libnymea-app-core/configuration/nymeaconfiguration.cpp +++ b/libnymea-app-core/configuration/nymeaconfiguration.cpp @@ -284,7 +284,6 @@ void NymeaConfiguration::getConfigurationsResponse(const QVariantMap ¶ms) webServerConfigurations()->clear(); foreach (const QVariant &webServerVariant, params.value("params").toMap().value("webServerConfigurations").toList()) { QVariantMap webServerConfigMap = webServerVariant.toMap(); - qDebug() << "**********+ web config" << webServerConfigMap; WebServerConfiguration* config = new WebServerConfiguration(webServerConfigMap.value("id").toString(), QHostAddress(webServerConfigMap.value("address").toString()), webServerConfigMap.value("port").toInt(), webServerConfigMap.value("authenticationEnabled").toBool(), webServerConfigMap.value("sslEnabled").toBool()); config->setPublicFolder(webServerConfigMap.value("publicFolder").toString()); m_webServerConfigurations->addConfiguration(config); @@ -293,7 +292,7 @@ void NymeaConfiguration::getConfigurationsResponse(const QVariantMap ¶ms) void NymeaConfiguration::getAvailableLanguagesResponse(const QVariantMap ¶ms) { - qDebug() << "available languages" << params; +// qDebug() << "available languages" << params; m_availableLanguages = params.value("params").toMap().value("languages").toStringList(); emit availableLanguagesChanged(); } diff --git a/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp b/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp index d30f1f46..51337163 100644 --- a/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp +++ b/libnymea-app-core/connection/discovery/zeroconfdiscovery.cpp @@ -108,7 +108,7 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) if (!host) { host = new NymeaHost(m_nymeaHosts); host->setUuid(uuid); - qDebug() << "ZeroConf: Adding new host:" << serverName << uuid; +// qDebug() << "ZeroConf: Adding new host:" << serverName << uuid; m_nymeaHosts->addHost(host); } host->setName(serverName); @@ -124,13 +124,13 @@ void ZeroconfDiscovery::serviceEntryAdded(const QZeroConfService &entry) url.setPort(entry.port()); Connection *connection = host->connections()->find(url); if (!connection) { - qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString(); +// qDebug() << "Zeroconf: Adding new connection to host:" << host->name() << url.toString(); QString displayName = QString("%1:%2").arg(url.host()).arg(url.port()); connection = new Connection(url, Connection::BearerTypeLan, sslEnabled, displayName); connection->setOnline(true); host->connections()->addConnection(connection); } else { - qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString(); +// qDebug() << "Zeroconf: Setting connection online:" << host->name() << url.toString(); connection->setOnline(true); } } @@ -183,7 +183,7 @@ void ZeroconfDiscovery::serviceEntryRemoved(const QZeroConfService &entry) return; } - qDebug() << "Zeroconf: Setting connection offline:" << host->name() << url.toString(); +// qDebug() << "Zeroconf: Setting connection offline:" << host->name() << url.toString(); connection->setOnline(false); } #endif diff --git a/libnymea-app-core/engine.cpp b/libnymea-app-core/engine.cpp index 3f511097..cceb8a1c 100644 --- a/libnymea-app-core/engine.cpp +++ b/libnymea-app-core/engine.cpp @@ -25,6 +25,7 @@ #include "tagsmanager.h" #include "configuration/nymeaconfiguration.h" #include "connection/awsclient.h" +#include "system/systemcontroller.h" #include "connection/tcpsockettransport.h" #include "connection/websockettransport.h" @@ -39,7 +40,8 @@ Engine::Engine(QObject *parent) : m_ruleManager(new RuleManager(m_jsonRpcClient, this)), m_logManager(new LogManager(m_jsonRpcClient, this)), m_tagsManager(new TagsManager(m_jsonRpcClient, this)), - m_nymeaConfiguration(new NymeaConfiguration(m_jsonRpcClient, this)) + m_nymeaConfiguration(new NymeaConfiguration(m_jsonRpcClient, this)), + m_systemController(new SystemController(m_jsonRpcClient, this)) { m_connection->registerTransport(new TcpSocketTransportFactory()); m_connection->registerTransport(new WebsocketTransportFactory()); @@ -97,6 +99,11 @@ NymeaConfiguration *Engine::nymeaConfiguration() const return m_nymeaConfiguration; } +SystemController *Engine::systemController() const +{ + return m_systemController; +} + void Engine::deployCertificate() { if (!m_jsonRpcClient->connected()) { @@ -129,6 +136,7 @@ void Engine::onConnectedChanged() m_deviceManager->init(); m_ruleManager->init(); m_nymeaConfiguration->init(); + m_systemController->init(); } } } diff --git a/libnymea-app-core/engine.h b/libnymea-app-core/engine.h index 28cc4759..8184b3ad 100644 --- a/libnymea-app-core/engine.h +++ b/libnymea-app-core/engine.h @@ -32,6 +32,7 @@ class RuleManager; class LogManager; class TagsManager; class NymeaConfiguration; +class SystemController; class Engine : public QObject { @@ -42,6 +43,7 @@ class Engine : public QObject Q_PROPERTY(TagsManager* tagsManager READ tagsManager CONSTANT) Q_PROPERTY(JsonRpcClient* jsonRpcClient READ jsonRpcClient CONSTANT) Q_PROPERTY(NymeaConfiguration* nymeaConfiguration READ nymeaConfiguration CONSTANT) + Q_PROPERTY(SystemController* systemController READ systemController CONSTANT) public: explicit Engine(QObject *parent = nullptr); @@ -56,6 +58,7 @@ public: JsonRpcClient *jsonRpcClient() const; LogManager *logManager() const; NymeaConfiguration *nymeaConfiguration() const; + SystemController *systemController() const; Q_INVOKABLE void deployCertificate(); @@ -67,6 +70,7 @@ private: LogManager *m_logManager; TagsManager *m_tagsManager; NymeaConfiguration *m_nymeaConfiguration; + SystemController *m_systemController; private slots: void onConnectedChanged(); diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp index bd8ec6c2..64d67b29 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp @@ -133,7 +133,7 @@ void JsonRpcClient::notificationReceived(const QVariantMap &data) void JsonRpcClient::isCloudConnectedReply(const QVariantMap &data) { - qDebug() << "Cloud is connected" << data; +// qDebug() << "Cloud is connected" << data; QMetaEnum connectionStateEnum = QMetaEnum::fromType(); m_cloudConnectionState = static_cast(connectionStateEnum.keyToValue(data.value("params").toMap().value("connectionState").toByteArray().data())); emit cloudConnectionStateChanged(); diff --git a/libnymea-app-core/libnymea-app-core.h b/libnymea-app-core/libnymea-app-core.h index f11716e8..03e45ce4 100644 --- a/libnymea-app-core/libnymea-app-core.h +++ b/libnymea-app-core/libnymea-app-core.h @@ -56,6 +56,12 @@ #include "ruletemplates/ruleactionparamtemplate.h" #include "connection/awsclient.h" #include "models/devicemodel.h" +#include "system/systemcontroller.h" +#include "types/package.h" +#include "types/packages.h" +#include "types/repository.h" +#include "types/repositories.h" +#include "models/packagesfiltermodel.h" #include @@ -199,6 +205,13 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "RuleActionTemplate", "Get it from RuleActionTemplates"); qmlRegisterUncreatableType(uri, 1, 0, "RuleActionParamTemplates", "Get it from RuleActionTemplate"); qmlRegisterUncreatableType(uri, 1, 0, "RuleActionParamTemplate", "Get it from RuleActionParamTemplates"); + + qmlRegisterUncreatableType(uri, 1, 0, "SystemController", "Get it from Engine"); + qmlRegisterUncreatableType(uri, 1, 0, "Packages", "Get it from SystemController"); + qmlRegisterUncreatableType(uri, 1, 0, "Package", "Get it from Packages"); + qmlRegisterUncreatableType(uri, 1, 0, "Repositories", "Get it from SystemController"); + qmlRegisterUncreatableType(uri, 1, 0, "Repository", "Get it from Repositories"); + qmlRegisterType(uri, 1, 0, "PackagesFilterModel"); } #endif // LIBNYMEAAPPCORE_H diff --git a/libnymea-app-core/libnymea-app-core.pro b/libnymea-app-core/libnymea-app-core.pro index 6b4127ef..42ab8401 100644 --- a/libnymea-app-core/libnymea-app-core.pro +++ b/libnymea-app-core/libnymea-app-core.pro @@ -46,6 +46,7 @@ SOURCES += \ deviceclasses.cpp \ deviceclassesproxy.cpp \ devicediscovery.cpp \ + models/packagesfiltermodel.cpp \ vendorsproxy.cpp \ pluginsproxy.cpp \ interfacesmodel.cpp \ @@ -81,7 +82,8 @@ SOURCES += \ configuration/nymeaconfiguration.cpp \ configuration/mqttpolicy.cpp \ configuration/mqttpolicies.cpp \ - models/devicemodel.cpp + models/devicemodel.cpp \ + system/systemcontroller.cpp HEADERS += \ engine.h \ @@ -107,6 +109,7 @@ HEADERS += \ deviceclasses.h \ deviceclassesproxy.h \ devicediscovery.h \ + models/packagesfiltermodel.h \ vendorsproxy.h \ pluginsproxy.h \ interfacesmodel.h \ @@ -142,4 +145,10 @@ HEADERS += \ configuration/nymeaconfiguration.h \ configuration/mqttpolicy.h \ configuration/mqttpolicies.h \ - models/devicemodel.h + models/devicemodel.h \ + system/systemcontroller.h + +unix { + target.path = /usr/lib + INSTALLS += target +} diff --git a/libnymea-app-core/models/packagesfiltermodel.cpp b/libnymea-app-core/models/packagesfiltermodel.cpp new file mode 100644 index 00000000..cb1437f2 --- /dev/null +++ b/libnymea-app-core/models/packagesfiltermodel.cpp @@ -0,0 +1,55 @@ +#include "packagesfiltermodel.h" +#include "types/package.h" + +PackagesFilterModel::PackagesFilterModel(QObject *parent): QSortFilterProxyModel(parent) +{ + setSortRole(Packages::RoleDisplayName); + sort(0); +} + +Packages *PackagesFilterModel::packages() const +{ + return m_packages; +} + +void PackagesFilterModel::setPackages(Packages *packages) +{ + if (m_packages != packages) { + m_packages = packages; + setSourceModel(packages); + connect(packages, &Packages::countChanged, this, &PackagesFilterModel::countChanged); + emit packagesChanged(); + emit countChanged(); + invalidate(); + } +} + +bool PackagesFilterModel::updatesOnly() const +{ + return m_updatesOnly; +} + +void PackagesFilterModel::setUpdatesOnly(bool updatesOnly) +{ + if (m_updatesOnly != updatesOnly) { + m_updatesOnly = updatesOnly; + emit updatesOnlyChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +Package *PackagesFilterModel::get(int index) const +{ + return m_packages->get(mapToSource(this->index(index, 0)).row()); +} + +bool PackagesFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + if (m_updatesOnly) { + if (!m_packages->get(source_row)->updateAvailable()) { + return false; + } + } + return true; +} diff --git a/libnymea-app-core/models/packagesfiltermodel.h b/libnymea-app-core/models/packagesfiltermodel.h new file mode 100644 index 00000000..616d38c4 --- /dev/null +++ b/libnymea-app-core/models/packagesfiltermodel.h @@ -0,0 +1,39 @@ +#ifndef PACKAGESFILTERMODEL_H +#define PACKAGESFILTERMODEL_H + +#include +#include "types/packages.h" + +class PackagesFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(Packages* packages READ packages WRITE setPackages NOTIFY packagesChanged) + Q_PROPERTY(bool updatesOnly READ updatesOnly WRITE setUpdatesOnly NOTIFY updatesOnlyChanged) + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + +public: + explicit PackagesFilterModel(QObject *parent = nullptr); + + Packages* packages() const; + void setPackages(Packages *packages); + + bool updatesOnly() const; + void setUpdatesOnly(bool updatesOnly); + + Q_INVOKABLE Package* get(int index) const; + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +signals: + void countChanged(); + void packagesChanged(); + void updatesOnlyChanged(); + +private: + Packages *m_packages; + + bool m_updatesOnly = false; +}; + +#endif // PACKAGESFILTERMODEL_H diff --git a/libnymea-app-core/system/systemcontroller.cpp b/libnymea-app-core/system/systemcontroller.cpp new file mode 100644 index 00000000..726170db --- /dev/null +++ b/libnymea-app-core/system/systemcontroller.cpp @@ -0,0 +1,205 @@ +#include "systemcontroller.h" + +#include "types/package.h" +#include "types/repository.h" +#include "types/packages.h" +#include "types/repositories.h" + +SystemController::SystemController(JsonRpcClient *jsonRpcClient, QObject *parent): + JsonHandler(parent), + m_jsonRpcClient(jsonRpcClient) +{ + m_jsonRpcClient->registerNotificationHandler(this, "notificationReceived"); + m_packages = new Packages(this); + m_repositories = new Repositories(this); +} + +void SystemController::init() +{ + m_packages->clear(); + m_repositories->clear(); + if (m_jsonRpcClient->ensureServerVersion("2.0")) { + m_jsonRpcClient->sendCommand("System.GetCapabilities", this, "getCapabilitiesResponse"); + } else { + m_powerManagementAvailable = false; + } +} + +QString SystemController::nameSpace() const +{ + return "System"; +} + +bool SystemController::powerManagementAvailable() const +{ + return m_powerManagementAvailable; +} + +bool SystemController::updateManagementAvailable() const +{ + return m_updateManagementAvailable; +} + +void SystemController::reboot() +{ + m_jsonRpcClient->sendCommand("System.Reboot"); +} + +void SystemController::shutdown() +{ + m_jsonRpcClient->sendCommand("System.Shutdown"); +} + +bool SystemController::updateRunning() const +{ + return m_updateRunning; +} + +Packages *SystemController::packages() const +{ + return m_packages; +} + +void SystemController::updatePackages(const QString packageId) +{ + QVariantMap params; + if (!packageId.isEmpty()) { + params.insert("packageIds", QStringList() << packageId); + } + m_jsonRpcClient->sendCommand("System.UpdatePackages", params); +} + +void SystemController::removePackages(const QString packageId) +{ + QVariantMap params; + if (!packageId.isEmpty()) { + params.insert("packageIds", QStringList() << packageId); + } + m_jsonRpcClient->sendCommand("System.RemovePackages", params, this, "removePackageResponse"); +} + +Repositories *SystemController::repositories() const +{ + return m_repositories; +} + +void SystemController::enableRepository(const QString &id, bool enabled) +{ + QVariantMap params; + params.insert("repositoryId", id); + params.insert("enabled", enabled); + m_jsonRpcClient->sendCommand("System.EnableRepository", params); +} + +void SystemController::getCapabilitiesResponse(const QVariantMap &data) +{ + qDebug() << "capabilities received" << data; + m_powerManagementAvailable = data.value("params").toMap().value("powerManagement").toBool(); + emit powerManagementAvailableChanged(); + + m_updateManagementAvailable = data.value("params").toMap().value("updateManagement").toBool(); + emit updateManagementAvailableChanged(); + + if (m_updateManagementAvailable) { + m_jsonRpcClient->sendCommand("System.GetUpdateStatus", this, "getUpdateStatusResponse"); + m_jsonRpcClient->sendCommand("System.GetPackages", this, "getPackagesResponse"); + m_jsonRpcClient->sendCommand("System.GetRepositories", this, "getRepositoriesResponse"); + } +} + +void SystemController::getUpdateStatusResponse(const QVariantMap &data) +{ + m_updateRunning = data.value("params").toMap().value("updateRunning").toBool(); + emit updateRunningChanged(); +} + +void SystemController::getPackagesResponse(const QVariantMap &data) +{ + foreach (const QVariant &packageVariant, data.value("params").toMap().value("packages").toList()) { + QString id = packageVariant.toMap().value("id").toString(); + QString displayName = packageVariant.toMap().value("displayName").toString(); + Package *p = new Package(id, displayName); + p->setInstalledVersion(packageVariant.toMap().value("installedVersion").toString()); + p->setCandidateVersion(packageVariant.toMap().value("candidateVersion").toString()); + p->setChangelog(packageVariant.toMap().value("changelog").toString()); + p->setUpdateAvailable(packageVariant.toMap().value("updateAvailable").toBool()); + p->setRollbackAvailable(packageVariant.toMap().value("rollbackAvailable").toBool()); + p->setCanRemove(packageVariant.toMap().value("canRemove").toBool()); + m_packages->addPackage(p); + } +} + +void SystemController::getRepositoriesResponse(const QVariantMap &data) +{ + qDebug() << "******** Repos" << data; + foreach (const QVariant &repoVariant, data.value("params").toMap().value("repositories").toList()) { + QString id = repoVariant.toMap().value("id").toString(); + QString displayName = repoVariant.toMap().value("displayName").toString(); + Repository *repo = new Repository(id, displayName); + repo->setEnabled(repoVariant.toMap().value("enabled").toBool()); + m_repositories->addRepository(repo); + } +} + +void SystemController::removePackageResponse(const QVariantMap ¶ms) +{ + qDebug() << "Remove result" << params; +} + +void SystemController::notificationReceived(const QVariantMap &data) +{ + qDebug() << "System Notification" << data.value("notification"); + QString notification = data.value("notification").toString(); + if (notification == "System.UpdateStatusChanged") { + m_updateRunning = data.value("params").toMap().value("updateRunning").toBool(); + emit updateRunningChanged(); + } else if (notification == "System.PackageAdded") { + QVariantMap packageMap = data.value("params").toMap().value("package").toMap(); + QString id = packageMap.value("id").toString(); + QString displayName = packageMap.value("displayName").toString(); + Package *p = new Package(id, displayName); + p->setInstalledVersion(packageMap.value("installedVersion").toString()); + p->setCandidateVersion(packageMap.value("candidateVersion").toString()); + p->setChangelog(packageMap.value("changelog").toString()); + p->setUpdateAvailable(packageMap.value("updateAvailable").toBool()); + p->setRollbackAvailable(packageMap.value("rollbackAvailable").toBool()); + p->setCanRemove(packageMap.value("canRemove").toBool()); + m_packages->addPackage(p); + } else if (notification == "System.PackageChanged") { + QVariantMap packageMap = data.value("params").toMap().value("package").toMap(); + QString id = packageMap.value("id").toString(); + Package *p = m_packages->getPackage(id); + if (!p) { + qWarning() << "Received a package update notification for a package we don't know"; + return; + } + p->setInstalledVersion(packageMap.value("installedVersion").toString()); + p->setCandidateVersion(packageMap.value("candidateVersion").toString()); + p->setChangelog(packageMap.value("changelog").toString()); + p->setUpdateAvailable(packageMap.value("updateAvailable").toBool()); + 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(); + m_packages->removePackage(packageId); + } else if (notification == "System.RepositoryAdded") { + QVariantMap repoMap = data.value("params").toMap().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(); + QString id = repoMap.value("id").toString(); + Repository *repo = m_repositories->getRepository(id); + if (!repo) { + qWarning() << "Received a repository update notification for a repository we don't know"; + return; + } + repo->setEnabled(repoMap.value("enabled").toBool()); + } else if (notification == "System.RepositoryRemoved") { + QString repositoryId = data.value("params").toMap().value("repositoryId").toString(); + m_repositories->removeRepository(repositoryId); + } +} diff --git a/libnymea-app-core/system/systemcontroller.h b/libnymea-app-core/system/systemcontroller.h new file mode 100644 index 00000000..36bda206 --- /dev/null +++ b/libnymea-app-core/system/systemcontroller.h @@ -0,0 +1,69 @@ +#ifndef SYSTEMCONTROLLER_H +#define SYSTEMCONTROLLER_H + +#include + +#include "jsonrpc/jsonrpcclient.h" + +class Repositories; +class Packages; + +class SystemController : public JsonHandler +{ + 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(bool updateRunning READ updateRunning NOTIFY updateRunningChanged) + Q_PROPERTY(Packages* packages READ packages CONSTANT) + Q_PROPERTY(Repositories* repositories READ repositories CONSTANT) + +public: + explicit SystemController(JsonRpcClient *jsonRpcClient, QObject *parent = nullptr); + + void init(); + QString nameSpace() const override; + + bool powerManagementAvailable() const; + bool updateManagementAvailable() const; + + Q_INVOKABLE void reboot(); + Q_INVOKABLE void shutdown(); + + bool updateRunning() const; + + Packages* packages() const; + Q_INVOKABLE void updatePackages(const QString packageId = QString()); + Q_INVOKABLE void removePackages(const QString packageId = QString()); + + Repositories* repositories() const; + Q_INVOKABLE void enableRepository(const QString &id, bool enabled); + + +signals: + void powerManagementAvailableChanged(); + void updateManagementAvailableChanged(); + void updateRunningChanged(); + +private slots: + void getCapabilitiesResponse(const QVariantMap &data); + void getUpdateStatusResponse(const QVariantMap &data); + void getPackagesResponse(const QVariantMap &data); + void getRepositoriesResponse(const QVariantMap &data); + void removePackageResponse(const QVariantMap ¶ms); + + void notificationReceived(const QVariantMap &data); + +private: + JsonRpcClient *m_jsonRpcClient = nullptr; + + bool m_powerManagementAvailable = false; + bool m_updateManagementAvailable = false; + + bool m_updateRunning = false; + Packages *m_packages = nullptr; + Repositories *m_repositories = nullptr; +}; + +#endif // SYSTEMCONTROLLER_H diff --git a/libnymea-common/libnymea-common.pro b/libnymea-common/libnymea-common.pro index 0e627d64..33bda42a 100644 --- a/libnymea-common/libnymea-common.pro +++ b/libnymea-common/libnymea-common.pro @@ -8,6 +8,10 @@ QT -= gui QT += network HEADERS += \ + types/package.h \ + types/packages.h \ + types/repositories.h \ + types/repository.h \ types/types.h \ types/vendor.h \ types/vendors.h \ @@ -54,6 +58,10 @@ HEADERS += \ types/tags.h SOURCES += \ + types/package.cpp \ + types/packages.cpp \ + types/repositories.cpp \ + types/repository.cpp \ types/vendor.cpp \ types/vendors.cpp \ types/deviceclass.cpp \ diff --git a/libnymea-common/types/package.cpp b/libnymea-common/types/package.cpp new file mode 100644 index 00000000..ce0abbb1 --- /dev/null +++ b/libnymea-common/types/package.cpp @@ -0,0 +1,97 @@ +#include "package.h" + +Package::Package(const QString &id, const QString &displayName, QObject *parent): + QObject(parent), + m_id(id), + m_displayName(displayName) +{ + +} + +QString Package::id() const +{ + return m_id; +} + +QString Package::displayName() const +{ + return m_displayName; +} + +QString Package::installedVersion() const +{ + return m_installedVersion; +} + +void Package::setInstalledVersion(const QString &installedVersion) +{ + if (m_installedVersion != installedVersion) { + m_installedVersion = installedVersion; + emit installedVersionChanged(); + } +} + +QString Package::candidateVersion() const +{ + return m_candidateVersion; +} + +void Package::setCandidateVersion(const QString &candidateVersion) +{ + if (m_candidateVersion != candidateVersion) { + m_candidateVersion = candidateVersion; + emit candidateVersionChanged(); + } +} + +QString Package::changelog() const +{ + return m_changelog; +} + +void Package::setChangelog(const QString &changelog) +{ + if (m_changelog != changelog) { + m_changelog = changelog; + emit changelogChanged(); + } +} + +bool Package::updateAvailable() const +{ + return m_updateAvailable; +} + +void Package::setUpdateAvailable(bool updateAvailable) +{ + if (m_updateAvailable != updateAvailable) { + m_updateAvailable = updateAvailable; + emit updateAvailableChanged(); + } +} + +bool Package::rollbackAvailable() const +{ + return m_rollbackAvailable; +} + +void Package::setRollbackAvailable(bool rollbackAvailable) +{ + if (m_rollbackAvailable != rollbackAvailable) { + m_rollbackAvailable = rollbackAvailable; + emit rollbackAvailableChanged(); + } +} + +bool Package::canRemove() const +{ + return m_canRemove; +} + +void Package::setCanRemove(bool canRemove) +{ + if (m_canRemove != canRemove) { + m_canRemove = canRemove; + emit canRemoveChanged(); + } +} diff --git a/libnymea-common/types/package.h b/libnymea-common/types/package.h new file mode 100644 index 00000000..3cc66044 --- /dev/null +++ b/libnymea-common/types/package.h @@ -0,0 +1,61 @@ +#ifndef PACKAGE_H +#define PACKAGE_H + +#include + +class Package : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(QString displayName READ displayName CONSTANT) + Q_PROPERTY(QString installedVersion READ installedVersion NOTIFY installedVersionChanged) + Q_PROPERTY(QString candidateVersion READ candidateVersion NOTIFY candidateVersionChanged) + Q_PROPERTY(QString changelog READ changelog NOTIFY changelogChanged) + Q_PROPERTY(bool updateAvailable READ updateAvailable NOTIFY updateAvailableChanged) + Q_PROPERTY(bool rollbackAvailable READ rollbackAvailable NOTIFY rollbackAvailableChanged) + Q_PROPERTY(bool canRemove READ canRemove NOTIFY canRemoveChanged) + +public: + explicit Package(const QString &id, const QString &displayName, QObject *parent = nullptr); + + QString id() const; + QString displayName() const; + + QString installedVersion() const; + void setInstalledVersion(const QString &installedVersion); + + QString candidateVersion() const; + void setCandidateVersion(const QString &candidateVersion); + + QString changelog() const; + void setChangelog(const QString &changelog); + + bool updateAvailable() const; + void setUpdateAvailable(bool updateAvailable); + + bool rollbackAvailable() const; + void setRollbackAvailable(bool rollbackAvailable); + + bool canRemove() const; + void setCanRemove(bool canRemove); + +signals: + void installedVersionChanged(); + void candidateVersionChanged(); + void changelogChanged(); + void updateAvailableChanged(); + void rollbackAvailableChanged(); + void canRemoveChanged(); + +private: + QString m_id; + QString m_displayName; + QString m_installedVersion; + QString m_candidateVersion; + QString m_changelog; + bool m_updateAvailable = false; + bool m_rollbackAvailable = false; + bool m_canRemove = false; +}; + +#endif // PACKAGE_H diff --git a/libnymea-common/types/packages.cpp b/libnymea-common/types/packages.cpp new file mode 100644 index 00000000..42e48a51 --- /dev/null +++ b/libnymea-common/types/packages.cpp @@ -0,0 +1,122 @@ +#include "packages.h" +#include "package.h" + +Packages::Packages(QObject *parent) : QAbstractListModel(parent) +{ + +} + +int Packages::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_list.count(); +} + +QVariant Packages::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleId: + return m_list.at(index.row())->id(); + case RoleDisplayName: + return m_list.at(index.row())->displayName(); + case RoleInstalledVersion: + return m_list.at(index.row())->installedVersion(); + case RoleCandidateVersion: + return m_list.at(index.row())->candidateVersion(); + case RoleChangelog: + return m_list.at(index.row())->changelog(); + case RoleUpdateAvailable: + return m_list.at(index.row())->updateAvailable(); + case RoleRollbackAvailable: + return m_list.at(index.row())->rollbackAvailable(); + } + return QVariant(); +} + +QHash Packages::roleNames() const +{ + QHash roles; + roles.insert(RoleId, "id"); + roles.insert(RoleDisplayName, "displayName"); + roles.insert(RoleInstalledVersion, "installedVersion"); + roles.insert(RoleCandidateVersion, "candidateVersion"); + roles.insert(RoleChangelog, "changelog"); + roles.insert(RoleUpdateAvailable, "updateAvailable"); + roles.insert(RoleRollbackAvailable, "rollbackAvailable"); + return roles; +} + +void Packages::addPackage(Package *package) +{ + package->setParent(this); + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + m_list.append(package); + connect(package, &Package::installedVersionChanged, this, [this, package](){ + emit dataChanged(index(m_list.indexOf(package)), index(m_list.indexOf(package)), {RoleInstalledVersion}); + emit countChanged(); + }); + connect(package, &Package::candidateVersionChanged, this, [this, package](){ + emit dataChanged(index(m_list.indexOf(package)), index(m_list.indexOf(package)), {RoleCandidateVersion}); + emit countChanged(); + }); + connect(package, &Package::changelogChanged, this, [this, package](){ + emit dataChanged(index(m_list.indexOf(package)), index(m_list.indexOf(package)), {RoleChangelog}); + emit countChanged(); + }); + connect(package, &Package::updateAvailableChanged, this, [this, package](){ + emit dataChanged(index(m_list.indexOf(package)), index(m_list.indexOf(package)), {RoleUpdateAvailable}); + emit countChanged(); + }); + connect(package, &Package::rollbackAvailableChanged, this, [this, package](){ + emit dataChanged(index(m_list.indexOf(package)), index(m_list.indexOf(package)), {RoleRollbackAvailable}); + emit countChanged(); + }); + endInsertRows(); + emit countChanged(); +} + +void Packages::removePackage(const QString &packageId) +{ + int idx = -1; + for (int i = 0; i < m_list.count(); i++) { + if (m_list.at(i)->id() == packageId) { + idx = i; + break; + } + } + if (idx < 0) { + return; + } + + beginRemoveRows(QModelIndex(), idx, idx); + m_list.takeAt(idx)->deleteLater(); + endRemoveRows(); + emit countChanged(); +} + +Package *Packages::get(int index) const +{ + if (index >= 0 && index < m_list.count()) { + return m_list.at(index); + } + return nullptr; +} + +Package *Packages::getPackage(const QString &packageId) +{ + foreach (Package *p, m_list) { + if (p->id() == packageId) { + return p; + } + } + return nullptr; +} + +void Packages::clear() +{ + beginResetModel(); + qDeleteAll(m_list); + m_list.clear(); + endResetModel(); + emit countChanged(); +} diff --git a/libnymea-common/types/packages.h b/libnymea-common/types/packages.h new file mode 100644 index 00000000..63561178 --- /dev/null +++ b/libnymea-common/types/packages.h @@ -0,0 +1,45 @@ +#ifndef PACKAGES_H +#define PACKAGES_H + +#include + +class Package; + +class Packages: public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) +public: + enum Roles { + RoleId, + RoleDisplayName, + RoleInstalledVersion, + RoleCandidateVersion, + RoleChangelog, + RoleUpdateAvailable, + RoleRollbackAvailable + }; + Q_ENUM(Roles) + + explicit Packages(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + void addPackage(Package *package); + void removePackage(const QString &packageId); + + Q_INVOKABLE Package* get(int index) const; + Q_INVOKABLE Package* getPackage(const QString &packageId); + + void clear(); + +signals: + void countChanged(); + +private: + QList m_list; +}; + +#endif // PACKAGES_H diff --git a/libnymea-common/types/repositories.cpp b/libnymea-common/types/repositories.cpp new file mode 100644 index 00000000..20791da6 --- /dev/null +++ b/libnymea-common/types/repositories.cpp @@ -0,0 +1,94 @@ +#include "repositories.h" +#include "repository.h" + +Repositories::Repositories(QObject *parent): QAbstractListModel(parent) +{ + +} + +int Repositories::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_list.count(); +} + +QVariant Repositories::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleId: + return m_list.at(index.row())->id(); + case RoleDisplayName: + return m_list.at(index.row())->displayName(); + case RoleEnabled: + return m_list.at(index.row())->enabled(); + } + return QVariant(); +} + +QHash Repositories::roleNames() const +{ + QHash roles; + roles.insert(RoleId, "id"); + roles.insert(RoleDisplayName, "displayName"); + roles.insert(RoleEnabled, "enabled"); + return roles; +} + +Repository *Repositories::get(int index) const +{ + if (index >= 0 && index < m_list.count()) { + return m_list.at(index); + } + return nullptr; +} + +Repository *Repositories::getRepository(const QString &id) const +{ + foreach (Repository *repo, m_list) { + if (repo->id() == id) { + return repo; + } + } + return nullptr; +} + +void Repositories::addRepository(Repository *repository) +{ + repository->setParent(this); + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + m_list.append(repository); + connect(repository, &Repository::enabledChanged, this, [this, repository](){ + QModelIndex idx = index(m_list.indexOf(repository)); + emit dataChanged(idx, idx, {RoleEnabled}); + }); + endInsertRows(); + emit countChanged(); +} + +void Repositories::removeRepository(const QString &repositoryId) +{ + int idx = -1; + for (int i = 0; i < m_list.count(); i++) { + if (m_list.at(i)->id() == repositoryId) { + idx = i; + break; + } + } + if (idx < 0) { + return; + } + beginRemoveRows(QModelIndex(), idx, idx); + m_list.takeAt(idx)->deleteLater(); + endRemoveRows(); + emit countChanged(); + +} + +void Repositories::clear() +{ + beginResetModel(); + qDeleteAll(m_list); + m_list.clear(); + endResetModel(); + emit countChanged(); +} diff --git a/libnymea-common/types/repositories.h b/libnymea-common/types/repositories.h new file mode 100644 index 00000000..efe1ad9c --- /dev/null +++ b/libnymea-common/types/repositories.h @@ -0,0 +1,40 @@ +#ifndef REPOSITORIES_H +#define REPOSITORIES_H + +#include + +class Repository; + +class Repositories : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) +public: + enum Roles { + RoleId, + RoleDisplayName, + RoleEnabled + }; + Q_ENUM(Roles) + + explicit Repositories(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + Q_INVOKABLE Repository* get(int index) const; + Q_INVOKABLE Repository* getRepository(const QString &id) const; + + void addRepository(Repository* repository); + void removeRepository(const QString &repositoryId); + void clear(); + +signals: + void countChanged(); + +private: + QList m_list; +}; + +#endif // REPOSITORIES_H diff --git a/libnymea-common/types/repository.cpp b/libnymea-common/types/repository.cpp new file mode 100644 index 00000000..e3df8947 --- /dev/null +++ b/libnymea-common/types/repository.cpp @@ -0,0 +1,32 @@ +#include "repository.h" + +Repository::Repository(const QString &id, const QString &displayName, QObject *parent): + QObject(parent), + m_id(id), + m_displayName(displayName) +{ + +} + +QString Repository::id() const +{ + return m_id; +} + +QString Repository::displayName() const +{ + return m_displayName; +} + +bool Repository::enabled() const +{ + return m_enabled; +} + +void Repository::setEnabled(bool enabled) +{ + if (m_enabled != enabled) { + m_enabled = enabled; + emit enabledChanged(); + } +} diff --git a/libnymea-common/types/repository.h b/libnymea-common/types/repository.h new file mode 100644 index 00000000..43ef8058 --- /dev/null +++ b/libnymea-common/types/repository.h @@ -0,0 +1,31 @@ +#ifndef REPOSITORY_H +#define REPOSITORY_H + +#include + +class Repository : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(QString displayName READ displayName CONSTANT) + Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) + +public: + explicit Repository(const QString &id, const QString &displayName, QObject *parent = nullptr); + + QString id() const; + QString displayName() const; + + bool enabled() const; + void setEnabled(bool enabled); + +signals: + void enabledChanged(); + +private: + QString m_id; + QString m_displayName; + bool m_enabled = false; +}; + +#endif // REPOSITORY_H diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index afb8e2d4..2ce38fb7 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -177,5 +177,9 @@ ui/images/network-wifi-offline.svg ui/images/network-wired-offline.svg ui/images/preferences-look-and-feel.svg + ui/images/sensors/closable.svg + ui/images/lock-closed.svg + ui/images/lock-open.svg + ui/images/system-update.svg diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index a2a18a9b..3dc0cb73 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -180,5 +180,7 @@ ui/appsettings/LookAndFeelSettingsPage.qml ui/appsettings/AppLogPage.qml ui/magic/SelectStatePage.qml + ui/system/SystemUpdatePage.qml + ui/components/UpdateRunningOverlay.qml diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index 96a9211c..9ce24790 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -170,6 +170,8 @@ ApplicationWindow { return Qt.resolvedUrl("images/sensors/light.svg") case "presencesensor": return Qt.resolvedUrl("images/sensors/presence.svg") + case "closablesensor": + return Qt.resolvedUrl("images/sensors/closable.svg") case "media": case "mediacontroller": return Qt.resolvedUrl("images/mediaplayer-app-symbolic.svg") @@ -210,7 +212,7 @@ ApplicationWindow { case "fingerprintreader": return Qt.resolvedUrl("images/fingerprint.svg") case "accesscontrol": - return Qt.resolvedUrl("images/network-secure.svg"); + return Qt.resolvedUrl("images/lock-closed.svg"); case "smartmeter": case "smartmeterconsumer": case "smartmeterproducer": @@ -244,6 +246,7 @@ ApplicationWindow { "co2sensor": "turquoise", "daylightsensor": "gold", "presencesensor": "darkblue", + "closablesensor": "green", "smartmeterproducer": "lightgreen", "smartmeterconsumer": "orange", "extendedsmartmeterproducer": "blue", diff --git a/nymea-app/ui/SettingsPage.qml b/nymea-app/ui/SettingsPage.qml index e779ef65..5a393bc8 100644 --- a/nymea-app/ui/SettingsPage.qml +++ b/nymea-app/ui/SettingsPage.qml @@ -155,6 +155,23 @@ Page { } } + Pane { + Layout.fillWidth: true + Material.elevation: layout.isGrid ? 1 : 0 + visible: engine.jsonRpcClient.ensureServerVersion("2.1") + + padding: 0 + MeaListItemDelegate { + width: parent.width + iconName: "../images/system-update.svg" + text: qsTr("System update") + subText: qsTr("Update your %1:core system").arg(app.systemName) + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("system/SystemUpdatePage.qml")) + } + } + Pane { Layout.fillWidth: true Material.elevation: layout.isGrid ? 1 : 0 diff --git a/nymea-app/ui/components/UpdateRunningOverlay.qml b/nymea-app/ui/components/UpdateRunningOverlay.qml new file mode 100644 index 00000000..a26d18ec --- /dev/null +++ b/nymea-app/ui/components/UpdateRunningOverlay.qml @@ -0,0 +1,44 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.2 + +Rectangle { + anchors.fill: parent + color: "#99000000" + visible: shown + + property bool shown: false + // Event eater + MouseArea { + anchors.fill: parent + } + + ColumnLayout { + anchors.centerIn: parent + width: parent.width + + ColorIcon { + height: app.iconSize * 3 + width: height + Layout.alignment: Qt.AlignHCenter + name: Qt.resolvedUrl("../images/system-update.svg") + color: app.accentColor + PropertyAnimation on rotation { + from: 0; to: 360; + duration: 2000 + loops: Animation.Inifinite + onStopped: start(); // No clue why loops won't work + } + } + + Label { + Layout.fillWidth: true + Layout.margins: app.margins * 2 + text: qsTr("An update operation is currently running.\nPlease wait for it to complete.") + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + font.pixelSize: app.largeFont + color: "white" + } + } +} diff --git a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml index 4c7fd110..8ca43278 100644 --- a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml @@ -84,6 +84,7 @@ DeviceListPageBase { ListElement { interfaceName: "co2sensor"; stateName: "co2" } ListElement { interfaceName: "daylightsensor"; stateName: "daylight" } ListElement { interfaceName: "presencesensor"; stateName: "isPresent" } + ListElement { interfaceName: "closablesensor"; stateName: "closed" } ListElement { interfaceName: "heating"; stateName: "power" } ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" } } @@ -100,24 +101,45 @@ DeviceListPageBase { Layout.preferredHeight: app.iconSize * .8 Layout.preferredWidth: height Layout.alignment: Qt.AlignVCenter - color: app.interfaceToColor(model.interfaceName) - name: app.interfaceToIcon(model.interfaceName) + color: { + switch (model.interfaceName) { + case "closablesensor": + return sensorValueDelegate.stateValue.value === true ? "green" : "red"; + default: + return app.interfaceToColor(model.interfaceName) + } + } + name: { + switch (model.interfaceName) { + case "closablesensor": + return sensorValueDelegate.stateValue.value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg"); + default: + return app.interfaceToIcon(model.interfaceName) + } + } } Label { Layout.fillWidth: true - text: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" - ? sensorValueDelegate.stateType.displayName - : sensorValueDelegate.stateValue - ? "%1 %2".arg(Math.round(sensorValueDelegate.stateValue.value * 100) / 100).arg(sensorValueDelegate.stateType.unitString) - : "" + text: { + switch (model.interfaceName) { + case "closablesensor": + return sensorValueDelegate.stateValue.value === true ? qsTr("is closed") : qsTr("is open"); + default: + return sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" + ? sensorValueDelegate.stateType.displayName + : sensorValueDelegate.stateValue + ? "%1 %2".arg(Math.round(sensorValueDelegate.stateValue.value * 100) / 100).arg(sensorValueDelegate.stateType.unitString) + : "" + } + } elide: Text.ElideRight verticalAlignment: Text.AlignVCenter font.pixelSize: app.smallFont } Led { id: led - visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() == "bool" + visible: ["presencesensor", "daylightsensor"].indexOf(model.interfaceName) >= 0 state: visible && sensorValueDelegate.stateValue.value === true ? "on" : "off" } Item { diff --git a/nymea-app/ui/devicepages/SensorDevicePagePost110.qml b/nymea-app/ui/devicepages/SensorDevicePagePost110.qml index b70b737b..266b8193 100644 --- a/nymea-app/ui/devicepages/SensorDevicePagePost110.qml +++ b/nymea-app/ui/devicepages/SensorDevicePagePost110.qml @@ -11,7 +11,7 @@ ListView { interactive: contentHeight > height model: ListModel { Component.onCompleted: { - var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor"] + var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor", "noisesensor", "co2sensor", "presencesensor", "daylightsensor", "closablesensor"] for (var i = 0; i < supportedInterfaces.length; i++) { if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) { append({name: supportedInterfaces[i]}); @@ -38,7 +38,8 @@ ListView { "noisesensor": "noise", "co2sensor": "co2", "presencesensor": "isPresent", - "daylightsensor": "daylight" + "daylightsensor": "daylight", + "closablesensor": "closed" } } @@ -72,8 +73,22 @@ ListView { anchors.centerIn: parent height: app.iconSize * 4 width: height - name: app.interfaceToIcon(boolView.interfaceName) - color: device.states.getState(boolView.stateType.id).value === true ? app.interfaceToColor(boolView.interfaceName) : keyColor + name: { + switch (boolView.interfaceName) { + case "closablesensor": + return device.states.getState(boolView.stateType.id).value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg") + default: + return app.interfaceToIcon(boolView.interfaceName) + } + } + color: { + switch (boolView.interfaceName) { + case "closablesensor": + return device.states.getState(boolView.stateType.id).value === true ? "green" : "red" + default: + device.states.getState(boolView.stateType.id).value === true ? app.interfaceToColor(boolView.interfaceName) : keyColor + } + } } } Item { diff --git a/nymea-app/ui/images/lock-closed.svg b/nymea-app/ui/images/lock-closed.svg new file mode 100644 index 00000000..d7cf5385 --- /dev/null +++ b/nymea-app/ui/images/lock-closed.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/lock-open.svg b/nymea-app/ui/images/lock-open.svg new file mode 100644 index 00000000..354dada1 --- /dev/null +++ b/nymea-app/ui/images/lock-open.svg @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/sensors/closable.svg b/nymea-app/ui/images/sensors/closable.svg new file mode 100644 index 00000000..d7cf5385 --- /dev/null +++ b/nymea-app/ui/images/sensors/closable.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/images/system-update.svg b/nymea-app/ui/images/system-update.svg new file mode 100644 index 00000000..0d98998c --- /dev/null +++ b/nymea-app/ui/images/system-update.svg @@ -0,0 +1,21 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/nymea-app/ui/mainviews/DevicesPageDelegate.qml b/nymea-app/ui/mainviews/DevicesPageDelegate.qml index 422f9478..88df04e2 100644 --- a/nymea-app/ui/mainviews/DevicesPageDelegate.qml +++ b/nymea-app/ui/mainviews/DevicesPageDelegate.qml @@ -428,6 +428,7 @@ MainPageTile { ListElement { ifaceName: "pressuresensor"; stateName: "pressure" } ListElement { ifaceName: "daylightsensor"; stateName: "daylight" } ListElement { ifaceName: "presencesensor"; stateName: "isPresent" } + ListElement { ifaceName: "closablesensor"; stateName: "closed" } ListElement { ifaceName: "lightsensor"; stateName: "lightIntensity" } ListElement { ifaceName: "co2sensor"; stateName: "co2" } ListElement { ifaceName: "conductivity"; stateName: "conductivity" } @@ -511,7 +512,7 @@ MainPageTile { Led { Layout.preferredHeight: app.iconSize * .5 Layout.preferredWidth: height - state: sensorsRoot.shownStateType && sensorsRoot.device.stateValue(sensorsRoot.shownStateType.id) === true ? "on" : "off" + state: sensorsRoot.shownStateType && sensorsRoot.device.states.getState(sensorsRoot.shownStateType.id).value === true ? "on" : "off" visible: sensorsRoot.shownStateType && sensorsRoot.shownStateType.type.toLowerCase() === "bool" } } diff --git a/nymea-app/ui/system/GeneralSettingsPage.qml b/nymea-app/ui/system/GeneralSettingsPage.qml index bc5cc95f..2fa6afd4 100644 --- a/nymea-app/ui/system/GeneralSettingsPage.qml +++ b/nymea-app/ui/system/GeneralSettingsPage.qml @@ -93,5 +93,22 @@ Page { } } } + + Button { + Layout.fillWidth: true + text: qsTr("Reboot %1:core").arg(app.systemName) + visible: engine.systemController.powerManagementAvailable + onClicked: { + engine.systemController.reboot() + } + } + Button { + Layout.fillWidth: true + text: qsTr("Shutdown %1:core").arg(app.systemName) + visible: engine.systemController.powerManagementAvailable + onClicked: { + engine.systemController.shutdown() + } + } } } diff --git a/nymea-app/ui/system/SystemUpdatePage.qml b/nymea-app/ui/system/SystemUpdatePage.qml new file mode 100644 index 00000000..98ce4ac2 --- /dev/null +++ b/nymea-app/ui/system/SystemUpdatePage.qml @@ -0,0 +1,266 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "../components" +import Nymea 1.0 + +Page { + id: root + header: GuhHeader { + text: qsTr("System update") + backButtonVisible: true + onBackPressed: pageStack.pop() + } + + PackagesFilterModel { + id: updatesModel + packages: engine.systemController.packages + updatesOnly: true + } + + ColumnLayout { + id: contentColumn + anchors.fill: parent + + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("Configure updates") + onClicked: { + pageStack.push(repositoryListComponent) + } + } + + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("Show all packages") + onClicked: { + pageStack.push(packageListComponent, {packages: engine.systemController.packages}) + } + } + + ThinDivider {} + + Label { + Layout.fillWidth: true + Layout.margins: app.margins + elide: Text.ElideRight + text: updatesModel.count === 0 ? qsTr("Your system is up to date.") : qsTr("There are %1 updates available.").arg(updatesModel.count) + } + + Button { + Layout.fillWidth: true + Layout.margins: app.margins + text: qsTr("Update all") + visible: updatesModel.count > 0 + onClicked: { + var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml")); + var text = qsTr("This will start a system update. Note that the update might take several minutes and your %1:core might not be functioning properly during this time and restart during the process.\nDo you want to proceed?").arg(app.systemName) + var popup = dialog.createObject(app, + { + headerIcon: "../images/system-update.svg", + title: qsTr("System update"), + text: text, + standardButtons: Dialog.Ok | Dialog.Cancel + }); + popup.open(); + popup.accepted.connect(function() { + engine.systemController.updatePackages() + }) + + } + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + model: updatesModel + clip: true + delegate: MeaListItemDelegate { + width: parent.width + text: model.displayName + subText: model.candidateVersion + prominentSubText: false + iconName: model.updateAvailable + ? Qt.resolvedUrl("../images/system-update.svg") + : Qt.resolvedUrl("../images/view-" + (model.installedVersion.length > 0 ? "expand" : "collapse") + ".svg") + iconColor: model.updateAvailable + ? "green" + : model.installedVersion.length > 0 ? "blue" : iconKeyColor + onClicked: { + pageStack.push(packageDetailsComponent, {pkg: updatesModel.get(index)}) + } + } + } + } + + Component { + id: repositoryListComponent + Page { + id: repositoryListPage + header: GuhHeader { + text: qsTr("Configure update sources") + onBackPressed: pageStack.pop() + } + ListView { + anchors.fill: parent + model: engine.systemController.repositories + delegate: CheckDelegate { + width: parent.width + text: model.displayName + checked: model.enabled + onClicked: { + if (checked) { + var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml")); + var text = qsTr("Enabling additional software sources allows to install unreleased %1:core packages.\nThis can potentially break your system and lead to problems.\nPlease only use this if you are sure you want this and consider reporting the issues you find when testing unreleased channels.").arg(app.systemName) + var popup = dialog.createObject(app, + { + headerIcon: "../images/dialog-warning-symbolic.svg", + title: qsTr("Enable package source"), + text: text, + standardButtons: Dialog.Ok | Dialog.Cancel + }); + popup.open(); + popup.accepted.connect(function() { + engine.systemController.enableRepository(model.id, true) + }) + popup.rejected.connect(function() { + checked = false + }) + } else { + engine.systemController.enableRepository(model.id, false) + } + } + } + } + UpdateRunningOverlay { + visible: engine.systemController.updateRunning + } + } + } + + Component { + id: packageListComponent + Page { + id: packageListPage + + property var packages: null + + header: GuhHeader { + text: qsTr("All packages") + onBackPressed: pageStack.pop() + } + + ListView { + anchors.fill: parent + model: PackagesFilterModel { + id: filterModel + packages: packageListPage.packages + } + delegate: MeaListItemDelegate { + width: parent.width + text: model.displayName + subText: model.candidateVersion + prominentSubText: false + iconName: model.updateAvailable + ? Qt.resolvedUrl("../images/system-update.svg") + : Qt.resolvedUrl("../images/view-" + (model.installedVersion.length > 0 ? "expand" : "collapse") + ".svg") + iconColor: model.updateAvailable + ? "green" + : model.installedVersion.length > 0 ? "blue" : iconKeyColor + onClicked: { + pageStack.push(packageDetailsComponent, {pkg: filterModel.get(index)}) + } + } + } + UpdateRunningOverlay { + visible: engine.systemController.updateRunning + } + } + } + + Component { + id: packageDetailsComponent + Page { + id: packageDetailsPage + + property Package pkg: null + + header: GuhHeader { + text: pkg.displayName + onBackPressed: pageStack.pop() + } + + GridLayout { + anchors { left: parent.left; top: parent.top; right: parent.right } + columns: app.landscape ? 2 : 1 + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("Installed version:") + subText: packageDetailsPage.pkg.installedVersion + progressive: false + } + + MeaListItemDelegate { + Layout.fillWidth: true + text: qsTr("Candidate version:") + subText: packageDetailsPage.pkg.candidateVersion + visible: packageDetailsPage.pkg.updateAvailable || packageDetailsPage.pkg.installedVersion.length === 0 + progressive: false + } + Button { + Layout.fillWidth: true + Layout.margins: app.margins + visible: packageDetailsPage.pkg.updateAvailable || packageDetailsPage.pkg.installedVersion.length === 0 + text: packageDetailsPage.pkg.updateAvailable ? qsTr("Update") : qsTr("Install") + onClicked: { + var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml")); + var text = qsTr("This will start a system update. Note that the update might take several minutes and your %1:core might not be functioning properly during this time and restart during the process.\nDo you want to proceed?").arg(app.systemName) + var popup = dialog.createObject(app, + { + headerIcon: "../images/system-update.svg", + title: qsTr("Start update"), + text: text, + standardButtons: Dialog.Ok | Dialog.Cancel + }); + popup.open(); + popup.accepted.connect(function() { + engine.systemController.updatePackages(packageDetailsPage.pkg.id) + }) + + } + } + Button { + Layout.fillWidth: true + Layout.margins: app.margins + text: qsTr("Remove") + visible: packageDetailsPage.pkg.canRemove + onClicked: { + var dialog = Qt.createComponent(Qt.resolvedUrl("../components/MeaDialog.qml")); + var text = qsTr("This will start a system update. Note that the update might take several minutes and your %1:core might not be functioning properly during this time and restart during the process.\nDo you want to proceed?").arg(app.systemName) + var popup = dialog.createObject(app, + { + headerIcon: "../images/system-update.svg", + title: qsTr("Remove package"), + text: text, + standardButtons: Dialog.Ok | Dialog.Cancel + }); + popup.open(); + popup.accepted.connect(function() { + engine.systemController.removePackages(packageDetailsPage.pkg.id) + }) + } + } + + } + UpdateRunningOverlay { + visible: engine.systemController.updateRunning + } + } + } + + + UpdateRunningOverlay { + visible: engine.systemController.updateRunning + } +}