From 336e21aabce60f2d3e142df6d8233502fd727a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 6 Nov 2025 09:46:18 +0100 Subject: [PATCH] Add EV Dash experience --- experiences/evdash/evdash.pro | 29 +++ experiences/evdash/evdashmanager.cpp | 167 +++++++++++++++++ experiences/evdash/evdashmanager.h | 89 +++++++++ experiences/evdash/evdashusers.cpp | 108 +++++++++++ experiences/evdash/evdashusers.h | 55 ++++++ experiences/evdash/libnymea-app-evdash.h | 46 +++++ experiences/experiences.pro | 2 +- libnymea-app/jsonrpc/jsonrpcclient.cpp | 5 +- libnymea-app/usermanager.cpp | 2 +- nymea-app/main.cpp | 2 + nymea-app/nymea-app.pro | 30 +-- nymea-app/resources.qrc | 1 + nymea-app/ui/SettingsPage.qml | 11 ++ nymea-app/ui/system/EvDashSettingsPage.qml | 203 +++++++++++++++++++++ 14 files changed, 735 insertions(+), 15 deletions(-) create mode 100644 experiences/evdash/evdash.pro create mode 100644 experiences/evdash/evdashmanager.cpp create mode 100644 experiences/evdash/evdashmanager.h create mode 100644 experiences/evdash/evdashusers.cpp create mode 100644 experiences/evdash/evdashusers.h create mode 100644 experiences/evdash/libnymea-app-evdash.h create mode 100644 nymea-app/ui/system/EvDashSettingsPage.qml diff --git a/experiences/evdash/evdash.pro b/experiences/evdash/evdash.pro new file mode 100644 index 00000000..05d898d5 --- /dev/null +++ b/experiences/evdash/evdash.pro @@ -0,0 +1,29 @@ +TEMPLATE = lib +CONFIG += staticlib +TARGET = nymea-app-evdash + +QT -= gui +QT += network websockets quick + +include(../../shared.pri) + +LIBS += -L$${top_builddir}/libnymea-app/ -lnymea-app +INCLUDEPATH += $${top_srcdir}/libnymea-app/ + +android: { + LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH} + PRE_TARGETDEPS += $$top_builddir/libnymea-app/$${ANDROID_TARGET_ARCH}/libnymea-app_$${ANDROID_TARGET_ARCH}.a +} + +HEADERS += \ + evdashmanager.h \ + evdashusers.h \ + libnymea-app-evdash.h + +SOURCES += \ + evdashmanager.cpp \ + evdashusers.cpp + +android: { + DESTDIR = $${ANDROID_TARGET_ARCH} +} diff --git a/experiences/evdash/evdashmanager.cpp b/experiences/evdash/evdashmanager.cpp new file mode 100644 index 00000000..cb960f2b --- /dev/null +++ b/experiences/evdash/evdashmanager.cpp @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2013 - 2024, nymea GmbH +* Copyright (C) 2024 - 2025, chargebyte austria GmbH +* +* This file is part of nymea-app. +* +* nymea-app is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* nymea-app is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with nymea-app. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "evdashmanager.h" + +#include + +#include + +NYMEA_LOGGING_CATEGORY(dcEvDashExperience, "EvDashExperience") + +EvDashManager::EvDashManager(QObject *parent) + :QObject{parent}, + m_users{new EvDashUsers(this)} +{ + +} + +EvDashManager::~EvDashManager() +{ + if (m_engine) { + m_engine->jsonRpcClient()->unregisterNotificationHandler(this); + } +} + +EvDashUsers *EvDashManager::users() const +{ + return m_users; +} + +Engine *EvDashManager::engine() const +{ + return m_engine; +} + +void EvDashManager::setEngine(Engine *engine) +{ + if (m_engine == engine) + return; + + if (m_engine) + m_engine->jsonRpcClient()->unregisterNotificationHandler(this); + + m_engine = engine; + emit engineChanged(); + + if (m_engine) { + connect(engine, &Engine::destroyed, this, [engine, this]{ if (m_engine == engine) m_engine = nullptr; }); + + m_engine->jsonRpcClient()->registerNotificationHandler(this, "EvDash", "notificationReceived"); + m_engine->jsonRpcClient()->sendCommand("EvDash.GetEnabled", QVariantMap(), this, "getEnabledResponse"); + m_engine->jsonRpcClient()->sendCommand("EvDash.GetUsers", QVariantMap(), this, "getUsersResponse"); + } +} + +bool EvDashManager::enabled() const +{ + return m_enabled; +} + +int EvDashManager::setEnabled(bool enabled) +{ + QVariantMap params; + params.insert("enabled", enabled); + return m_engine->jsonRpcClient()->sendCommand("EvDash.SetEnabled", params, this, "setEnabledResponse"); +} + +int EvDashManager::addUser(const QString &username, const QString &password) +{ + QVariantMap params; + params.insert("username", username); + params.insert("password", password); + return m_engine->jsonRpcClient()->sendCommand("EvDash.AddUser", params, this, "addUserResponse"); +} + +int EvDashManager::removeUser(const QString &username) +{ + QVariantMap params; + params.insert("username", username); + return m_engine->jsonRpcClient()->sendCommand("EvDash.RemoveUser", params, this, "removeUserResponse"); +} + +void EvDashManager::notificationReceived(const QVariantMap &data) +{ + QString notification = data.value("notification").toString(); + QVariantMap params = data.value("params").toMap(); + + if (notification == "EvDash.EnabledChanged") { + bool enabled = params.value("enabled").toBool(); + if (m_enabled != enabled) { + m_enabled = enabled; + emit enabledChanged(); + } + } else if (notification == "EvDash.UserAdded") { + m_users->addUser(params.value("username").toString()); + } else if (notification == "EvDash.UserRemoved") { + m_users->removeUser(params.value("username").toString()); + } else { + qCDebug(dcEvDashExperience()) << "Unhandled notification received" << data; + } +} + +void EvDashManager::getEnabledResponse(int commandId, const QVariantMap ¶ms) +{ + Q_UNUSED(commandId) + qCDebug(dcEvDashExperience()) << "Response for GetEnabled request" << commandId << params; + + bool enabled = params.value("enabled").toBool(); + if (m_enabled != enabled) { + m_enabled = enabled; + emit enabledChanged(); + } +} + +void EvDashManager::setEnabledResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcEvDashExperience()) << "Response for SetEnabled request" << commandId << params; + QMetaEnum metaEnum = QMetaEnum::fromType(); + EvDashError error = static_cast(metaEnum.keyToValue(params.value("evDashError").toByteArray().data())); + emit setEnabledReply(commandId, error); +} + +void EvDashManager::getUsersResponse(int commandId, const QVariantMap ¶ms) +{ + Q_UNUSED(commandId) + qCDebug(dcEvDashExperience()) << "Response for GetEnabled request" << commandId << params; + m_users->setUsers(params.value("usernames").toStringList()); +} + +void EvDashManager::addUserResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcEvDashExperience()) << "Response for AddUser request" << commandId << params; + QMetaEnum metaEnum = QMetaEnum::fromType(); + EvDashError error = static_cast(metaEnum.keyToValue(params.value("evDashError").toByteArray().data())); + emit addUserReply(commandId, error); +} + +void EvDashManager::removeUserResponse(int commandId, const QVariantMap ¶ms) +{ + qCDebug(dcEvDashExperience()) << "Response for RemoveUser request" << commandId << params; + QMetaEnum metaEnum = QMetaEnum::fromType(); + EvDashError error = static_cast(metaEnum.keyToValue(params.value("evDashError").toByteArray().data())); + emit removeUserReply(commandId, error); +} + + diff --git a/experiences/evdash/evdashmanager.h b/experiences/evdash/evdashmanager.h new file mode 100644 index 00000000..01ba0332 --- /dev/null +++ b/experiences/evdash/evdashmanager.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2013 - 2024, nymea GmbH +* Copyright (C) 2024 - 2025, chargebyte austria GmbH +* +* This file is part of nymea-app. +* +* nymea-app is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* nymea-app is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with nymea-app. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef EVDASHMANAGER_H +#define EVDASHMANAGER_H + +#include +#include + +#include "evdashusers.h" + +class EvDashManager : public QObject +{ + Q_OBJECT + Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL) + Q_PROPERTY(EvDashUsers *users READ users CONSTANT FINAL) + +public: + enum EvDashError { + EvDashErrorNoError = 0, + EvDashErrorBackendError, + EvDashErrorDuplicateUser, + EvDashErrorUserNotFound, + EvDashErrorBadPassword + }; + Q_ENUM(EvDashError) + + explicit EvDashManager(QObject *parent = nullptr); + ~EvDashManager(); + + EvDashUsers *users() const; + + Engine* engine() const; + void setEngine(Engine *engine); + + bool enabled() const; + int setEnabled(bool enabled); + + Q_INVOKABLE int addUser(const QString &username, const QString &password); + Q_INVOKABLE int removeUser(const QString &username); + +signals: + void engineChanged(); + void enabledChanged(); + + void setEnabledReply(int commandId, EvDashManager::EvDashError error); + void addUserReply(int commandId, EvDashManager::EvDashError error); + void removeUserReply(int commandId, EvDashManager::EvDashError error); + +private slots: + void notificationReceived(const QVariantMap &data); + + void getEnabledResponse(int commandId, const QVariantMap ¶ms); + void setEnabledResponse(int commandId, const QVariantMap ¶ms); + + void getUsersResponse(int commandId, const QVariantMap ¶ms); + void addUserResponse(int commandId, const QVariantMap ¶ms); + void removeUserResponse(int commandId, const QVariantMap ¶ms); + +private: + Engine *m_engine = nullptr; + bool m_enabled = false; + EvDashUsers *m_users = nullptr; + +}; + +#endif // EVDASHMANAGER_H diff --git a/experiences/evdash/evdashusers.cpp b/experiences/evdash/evdashusers.cpp new file mode 100644 index 00000000..e3492804 --- /dev/null +++ b/experiences/evdash/evdashusers.cpp @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2013 - 2024, nymea GmbH +* Copyright (C) 2024 - 2025, chargebyte austria GmbH +* +* This file is part of nymea-app. +* +* nymea-app is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* nymea-app is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with nymea-app. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "evdashusers.h" + +#include + +namespace { +inline bool localeLess(const QString &lhs, const QString &rhs) +{ + return QString::localeAwareCompare(lhs, rhs) < 0; +} +} + +EvDashUsers::EvDashUsers(QObject *parent) + : QAbstractListModel(parent) +{ + +} + +EvDashUsers::EvDashUsers(const QStringList &data, QObject *parent) + : QAbstractListModel(parent), m_data(data) +{ + +} + +int EvDashUsers::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_data.size(); +} + +QVariant EvDashUsers::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_data.size()) + return QVariant(); + + if (role == Qt::DisplayRole || role == NameRole) + return m_data.at(index.row()); + + return QVariant(); +} + +QHash EvDashUsers::roleNames() const +{ + QHash roles; + roles[NameRole] = "name"; + return roles; +} + +void EvDashUsers::setUsers(const QStringList &users) +{ + QStringList sortedUsers = users; + sortedUsers.removeDuplicates(); + std::sort(sortedUsers.begin(), sortedUsers.end(), localeLess); + + if (sortedUsers == m_data) + return; + + beginResetModel(); + m_data = sortedUsers; + endResetModel(); +} + +void EvDashUsers::addUser(const QString &user) +{ + if (user.isEmpty() || m_data.contains(user)) + return; + + const auto insertIt = std::lower_bound(m_data.begin(), m_data.end(), user, localeLess); + const int insertIndex = std::distance(m_data.begin(), insertIt); + + beginInsertRows(QModelIndex(), insertIndex, insertIndex); + m_data.insert(insertIndex, user); + endInsertRows(); +} + +void EvDashUsers::removeUser(const QString &user) +{ + int index = m_data.indexOf(user); + if (index < 0) + return; + + beginRemoveRows(QModelIndex(), index, index); + m_data.removeAt(index); + endRemoveRows(); +} diff --git a/experiences/evdash/evdashusers.h b/experiences/evdash/evdashusers.h new file mode 100644 index 00000000..991294fe --- /dev/null +++ b/experiences/evdash/evdashusers.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2013 - 2024, nymea GmbH +* Copyright (C) 2024 - 2025, chargebyte austria GmbH +* +* This file is part of nymea-app. +* +* nymea-app is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* nymea-app is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with nymea-app. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef EVDASHUSERS_H +#define EVDASHUSERS_H + +#include +#include +#include + +class EvDashUsers : public QAbstractListModel +{ + Q_OBJECT +public: + enum Roles { DisplayRole = Qt::UserRole + 1, NameRole }; + + explicit EvDashUsers(QObject *parent = nullptr); + explicit EvDashUsers(const QStringList &data, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + + void setUsers(const QStringList &users); + void addUser(const QString &username); + void removeUser(const QString &username); + +private: + QStringList m_data; + +}; + + +#endif // EVDASHUSERS_H diff --git a/experiences/evdash/libnymea-app-evdash.h b/experiences/evdash/libnymea-app-evdash.h new file mode 100644 index 00000000..040cf3e0 --- /dev/null +++ b/experiences/evdash/libnymea-app-evdash.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright (C) 2013 - 2024, nymea GmbH +* Copyright (C) 2024 - 2025, chargebyte austria GmbH +* +* This file is part of nymea-app. +* +* nymea-app is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* nymea-app is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with nymea-app. If not, see . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef LIBNYMEA_APP_EVDASH_H +#define LIBNYMEA_APP_EVDASH_H + +#include "evdashmanager.h" +#include "evdashusers.h" + +#include + +namespace Nymea { + +namespace EvDash { + +void registerQmlTypes() { + qmlRegisterType("Nymea.EvDash", 1, 0, "EvDashManager"); + qmlRegisterUncreatableType("Nymea.EvDash", 1, 0, "EvDashUsers", "Get if from the EvDash Manager"); +} + +} + +} + +#endif // LIBNYMEA_APP_EVDASH_H diff --git a/experiences/experiences.pro b/experiences/experiences.pro index 298fcbc4..794d26d4 100644 --- a/experiences/experiences.pro +++ b/experiences/experiences.pro @@ -1,3 +1,3 @@ TEMPLATE = subdirs -SUBDIRS += airconditioning +SUBDIRS += airconditioning evdash diff --git a/libnymea-app/jsonrpc/jsonrpcclient.cpp b/libnymea-app/jsonrpc/jsonrpcclient.cpp index 76e15985..0687bd0c 100644 --- a/libnymea-app/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app/jsonrpc/jsonrpcclient.cpp @@ -668,7 +668,10 @@ void JsonRpcClient::helloReply(int /*commandId*/, const QVariantMap ¶ms) QString name = params.value("name").toString(); m_experiences.clear(); foreach (const QVariant &experience, params.value("experiences").toList()) { - m_experiences.insert(experience.toMap().value("name").toString(), experience.toMap().value("version").toString()); + QString experienceName = experience.toMap().value("name").toString(); + QString experienceVersion = experience.toMap().value("version").toString(); + m_experiences.insert(experienceName, experienceVersion); + qCInfo(dcJsonRpc()) << "Experience available:" << experienceName << experienceVersion; } QString protoVersionString = params.value("protocol version").toString(); diff --git a/libnymea-app/usermanager.cpp b/libnymea-app/usermanager.cpp index 828cdfbe..c9cab670 100644 --- a/libnymea-app/usermanager.cpp +++ b/libnymea-app/usermanager.cpp @@ -163,7 +163,7 @@ int UserManager::setUserScopes(const QString &username, int scopes, const QList< if (m_engine->jsonRpcClient()->ensureServerVersion("8.4")) { QVariantList thingIds; foreach (const QUuid &thingId, allowedThingIds) - thingIds.append(thingId); + thingIds.append(thingId.toString()); params.insert("allowedThingIds", thingIds); } diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index 1acb1832..740bf417 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -48,6 +48,7 @@ #include "libnymea-app-core.h" #include "libnymea-app-airconditioning.h" +#include "libnymea-app-evdash.h" #include "stylecontroller.h" #include "pushnotifications.h" @@ -145,6 +146,7 @@ int main(int argc, char *argv[]) Nymea::Core::registerQmlTypes(); Nymea::AirConditioning::registerQmlTypes(); + Nymea::EvDash::registerQmlTypes(); QQmlApplicationEngine *engine = new QQmlApplicationEngine(); diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index a16746fa..fa60ddf3 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -13,25 +13,29 @@ qtHaveModule(webview) { } INCLUDEPATH += $$top_srcdir/libnymea-app \ - $$top_srcdir/experiences/airconditioning + $$top_srcdir/experiences/airconditioning \ + $$top_srcdir/experiences/evdash + +linux:!android:LIBS += -L$$top_builddir/libnymea-app/ -lnymea-app \ + -L$$top_builddir/experiences/airconditioning -lnymea-app-airconditioning \ + -L$$top_builddir/experiences/evdash -lnymea-app-evdash -linux:!android: LIBS += -L$$top_builddir/libnymea-app/ -lnymea-app -L$$top_builddir/experiences/airconditioning -lnymea-app-airconditioning win32:Debug:LIBS += -L$$top_builddir/libnymea-app/debug -lnymea-app \ - -L$$top_builddir/experiences/airconditioning/debug -lnymea-app-airconditioning -win32:Release:LIBS += -L$$top_builddir/libnymea-app/release -lnymea-app \ - -L$$top_builddir/experiences/airconditioning/release -lnymea-app-airconditioning + -L$$top_builddir/experiences/airconditioning/debug -lnymea-app-airconditioning \ + -L$$top_builddir/experiences/evdash/debug -lnymea-app-evdash + +win32:Release:LIBS += -L$$top_builddir/libnymea-app/release -lnymea-app \ + -L$$top_builddir/experiences/airconditioning/release -lnymea-app-airconditioning \ + -L$$top_builddir/experiences/evdash/release -lnymea-app-evdash -win32:Debug:PRE_TARGETDEPS += $$top_builddir/libnymea-app/debug/nymea-app.lib \ - $$top_builddir/experiences/airconditioning/debug/nymea-app-airconditioning.lib -win32:Release:PRE_TARGETDEPS += $$top_builddir/libnymea-app/release/nymea-app.lib \ - $$top_builddir/experiences/airconditioning/release/nymea-app-airconditioning.lib win32:CXX_FLAGS += /w linux:!android:!nozeroconf:LIBS += -lavahi-client -lavahi-common linux:!android:PRE_TARGETDEPS += $$top_builddir/libnymea-app/libnymea-app.a \ - $$top_builddir/experiences/airconditioning/libnymea-app-airconditioning.a + $$top_builddir/experiences/airconditioning/libnymea-app-airconditioning.a \ + $$top_builddir/experiences/evdash/libnymea-app-evdash.a HEADERS += \ configuredhostsmodel.h \ @@ -94,7 +98,7 @@ android { include(../3rdParty/android/android_openssl/openssl.pri) ANDROID_MIN_SDK_VERSION = 21 - ANDROID_TARGET_SDK_VERSION = 35 + ANDROID_TARGET_SDK_VERSION = 36 HEADERS += platformintegration/android/platformhelperandroid.h \ platformintegration/android/platformpermissionsandroid.h \ @@ -104,9 +108,11 @@ android { CORE_LIBS += -L$${top_builddir}/libnymea-app/$${ANDROID_TARGET_ARCH} AIRCONDITIONING_LIBS += -L$${top_builddir}/experiences/airconditioning/$${ANDROID_TARGET_ARCH} + EVDASH_LIBS += -L$${top_builddir}/experiences/evdash/$${ANDROID_TARGET_ARCH} LIBS += $${CORE_LIBS} -lnymea-app_$${ANDROID_TARGET_ARCH} \ - $${AIRCONDITIONING_LIBS} -lnymea-app-airconditioning_$${ANDROID_TARGET_ARCH} + $${AIRCONDITIONING_LIBS} -lnymea-app-airconditioning_$${ANDROID_TARGET_ARCH} \ + $${EVDASH_LIBS} -lnymea-app-evdash_$${ANDROID_TARGET_ARCH} versioninfo.files = ../version.txt versioninfo.path = / diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 4a7c56a7..7900a598 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -325,5 +325,6 @@ ui/shaders/coloricon.frag.qsb ui/shaders/brightnesscircle.frag.qsb ui/shaders/colorizedimage.frag.qsb + ui/system/EvDashSettingsPage.qml diff --git a/nymea-app/ui/SettingsPage.qml b/nymea-app/ui/SettingsPage.qml index 5f113aa8..c5489b13 100644 --- a/nymea-app/ui/SettingsPage.qml +++ b/nymea-app/ui/SettingsPage.qml @@ -143,6 +143,17 @@ Page { onClicked:pageStack.push(Qt.resolvedUrl("system/PluginsPage.qml")) } + + SettingsTile { + Layout.fillWidth: true + iconSource: "qrc:/icons/dashboard.svg" + text: qsTr("EV Dash") + subText: qsTr("Dashboard settings") + visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && + (engine.jsonRpcClient.experiences.hasOwnProperty("EvDash")) + onClicked:pageStack.push(Qt.resolvedUrl("system/EvDashSettingsPage.qml")) + } + SettingsTile { Layout.fillWidth: true iconSource: "qrc:/icons/sdk.svg" diff --git a/nymea-app/ui/system/EvDashSettingsPage.qml b/nymea-app/ui/system/EvDashSettingsPage.qml new file mode 100644 index 00000000..af8b67a2 --- /dev/null +++ b/nymea-app/ui/system/EvDashSettingsPage.qml @@ -0,0 +1,203 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2025, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU General Public License as published by the Free Software +* Foundation, GNU version 3. This project is distributed in the hope that it +* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +* Public License for more details. +* +* You should have received a copy of the GNU General Public License along with +* this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +import Nymea 1.0 +import Nymea.EvDash 1.0 + +import "../components" + +SettingsPageBase { + id: root + + header: NymeaHeader { + text: qsTr("EV Dash") + onBackPressed: pageStack.pop() + + HeaderButton { + imageSource: Qt.resolvedUrl("qrc:/icons/add.svg") + onClicked: { + var popup = addUserPopup.createObject(app); + popup.open(); + } + } + } + + EvDashManager { + id: evDashManager + engine: _engine + } + + Component { + id: errorDialog + ErrorDialog {} + } + + + Component { + id: removeUserPopup + NymeaDialog { + id: removeUserDialog + + property string username + + headerIcon: "qrc:/icons/dialog-warning-symbolic.svg" + title: qsTr("Remove user") + text: qsTr("Are you sure you want to remove \"%1\"?").arg(username) + + onAccepted: { + evDashManager.removeUser(username); + popup.close(); + } + } + } + + Connections { + target: evDashManager + onAddUserReply: { + if (error === EvDashManager.EvDashErrorNoError) + return + + var text; + switch (error) { + case EvDashManager.EvDashErrorDuplicateUser: + text = qsTr("The given username is already in use. Please choose a different username."); + break; + case EvDashManager.EvDashErrorBadPassword: + text = qsTr("The given password is not valid."); + break; + default: + text = qsTr("Un unexpected error happened when creating the user. We're sorry for this. (Error code: %1)").arg(error); + break; + } + + var popup = errorDialog.createObject(app, {text: text}); + popup.open() + } + + onRemoveUserReply: { + if (error === EvDashManager.EvDashErrorNoError) + return + + var text; + switch (error) { + case EvDashManager.EvDashErrorDuplicateUser: + text = qsTr("The given username is already in use. Please choose a different username."); + break; + case EvDashManager.EvDashErrorBadPassword: + text = qsTr("The given password is not valid."); + break; + default: + text = qsTr("Un unexpected error happened when creating the user. We're sorry for this. (Error code: %1)").arg(error); + break; + } + + var popup = errorDialog.createObject(app, {text: text}); + popup.open() + } + } + + Component { + id: addUserPopup + + NymeaDialog { + id: addUserDialog + + title: qsTr("Create new user") + standardButtons: Dialog.NoButton + + Label { text: qsTr("Username") } + + NymeaTextField { + id: usernameTextField + Layout.fillWidth: true + } + + Label { text: qsTr("Password") } + + PasswordTextField { + id: passwordTextField + + Layout.fillWidth: true + minPasswordLength: 4 + requireSpecialChar: false + requireNumber: false + requireUpperCaseLetter: false + requireLowerCaseLetter: false + } + + Button { + Layout.fillWidth: true + text: qsTr("Add user") + onClicked: { + evDashManager.addUser(usernameTextField.text, passwordTextField.password) + addUserDialog.close() + } + } + + Button { + Layout.fillWidth: true + text: qsTr("Cancel") + onClicked: { + addUserDialog.close() + } + } + } + } + + + SwitchDelegate { + text: qsTr("Dashboard enabled") + checked: evDashManager.enabled + onCheckedChanged: evDashManager.enabled = checked + Layout.fillWidth: true + } + + SettingsPageSectionHeader { + text: qsTr("Manage users") + } + + Repeater { + id: usersList + model: evDashManager.users + + delegate: NymeaItemDelegate { + Layout.fillWidth: true + text: model.name + onClicked: { + var popup = removeUserPopup.createObject(app, {username: model.name}); + popup.open() + } + } + } +}