From 82454fc5546b20614e7f644cc6078f4325b55a47 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 14 Dec 2020 16:28:56 +0100 Subject: [PATCH] Finishing touches --- libnymea-app/models/logsmodel.cpp | 8 +- libnymea-app/models/logsmodel.h | 3 +- libnymea-app/models/logsmodelng.cpp | 4 +- libnymea-app/types/device.cpp | 18 +- libnymea-app/types/device.h | 10 +- libnymea-app/types/logentry.cpp | 10 +- libnymea-app/types/logentry.h | 5 +- libnymea-app/types/params.cpp | 4 +- libnymea-app/types/params.h | 2 +- nymea-app/main.cpp | 27 +- nymea-app/platformhelper.cpp | 36 +++ nymea-app/platformhelper.h | 11 +- .../ubports/platformhelperubports.cpp | 9 + .../ubports/platformhelperubports.h | 1 + nymea-app/pushnotifications.cpp | 22 ++ nymea-app/pushnotifications.h | 4 + nymea-app/resources.qrc | 1 + .../ruletemplates/notificationtemplates.json | 4 +- nymea-app/ui/RootItem.qml | 52 +++- nymea-app/ui/components/BigThingTile.qml | 36 +++ nymea-app/ui/components/BigTile.qml | 37 +-- .../ClosablesDeviceListPage.qml | 2 +- .../devicelistpages/GarageThingListPage.qml | 2 +- .../devicelistpages/GenericDeviceListPage.qml | 2 +- .../devicelistpages/LightsDeviceListPage.qml | 3 +- .../devicelistpages/MediaDeviceListPage.qml | 2 +- .../PowerSocketsDeviceListPage.qml | 2 +- .../devicelistpages/SensorsDeviceListPage.qml | 2 +- .../SmartMeterDeviceListPage.qml | 2 +- .../devicelistpages/WeatherDeviceListPage.qml | 2 +- .../devicepages/NotificationsDevicePage.qml | 263 ++++++++++++------ .../ui/thingconfiguration/SetupWizard.qml | 12 +- nymea-app/ui/utils/NymeaUtils.qml | 2 + 33 files changed, 420 insertions(+), 180 deletions(-) create mode 100644 nymea-app/ui/components/BigThingTile.qml diff --git a/libnymea-app/models/logsmodel.cpp b/libnymea-app/models/logsmodel.cpp index 606539ce..89d53d23 100644 --- a/libnymea-app/models/logsmodel.cpp +++ b/libnymea-app/models/logsmodel.cpp @@ -79,6 +79,8 @@ QVariant LogsModel::data(const QModelIndex &index, int role) const return m_list.at(index.row())->source(); case RoleLoggingEventType: return m_list.at(index.row())->loggingEventType(); + case RoleErrorCode: + return m_list.at(index.row())->errorCode(); } return QVariant(); } @@ -93,6 +95,7 @@ QHash LogsModel::roleNames() const roles.insert(RoleTypeId, "typeId"); roles.insert(RoleSource, "source"); roles.insert(RoleLoggingEventType, "loggingEventType"); + roles.insert(RoleErrorCode, "errorCode"); return roles; } @@ -229,7 +232,8 @@ void LogsModel::logsReply(int /*commandId*/, const QVariantMap &data) QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType(); LogEntry::LoggingEventType loggingEventType = static_cast(loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray())); QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); - LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, this); + QString errorCode = entryMap.value("errorCode").toString(); + LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, errorCode, this); newBlock.append(entry); } @@ -371,7 +375,7 @@ void LogsModel::newLogEntryReceived(const QVariantMap &data) QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType(); LogEntry::LoggingEventType loggingEventType = static_cast(loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray())); QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); - LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, this); + LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, entryMap.value("errorCode").toString(), this); m_list.prepend(entry); endInsertRows(); emit countChanged(); diff --git a/libnymea-app/models/logsmodel.h b/libnymea-app/models/logsmodel.h index 0326de9b..98388b5d 100644 --- a/libnymea-app/models/logsmodel.h +++ b/libnymea-app/models/logsmodel.h @@ -61,7 +61,8 @@ public: RoleDeviceId, // < JSONRPC 5.0 RoleTypeId, RoleSource, - RoleLoggingEventType + RoleLoggingEventType, + RoleErrorCode }; explicit LogsModel(QObject *parent = nullptr); diff --git a/libnymea-app/models/logsmodelng.cpp b/libnymea-app/models/logsmodelng.cpp index bdc3261d..d9585380 100644 --- a/libnymea-app/models/logsmodelng.cpp +++ b/libnymea-app/models/logsmodelng.cpp @@ -261,7 +261,7 @@ void LogsModelNg::logsReply(int commandId, const QVariantMap &data) QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType(); LogEntry::LoggingEventType loggingEventType = static_cast(loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray())); QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); - LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, this); + LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, entryMap.value("errorCode").toString(), this); newBlock.append(entry); } @@ -461,7 +461,7 @@ void LogsModelNg::newLogEntryReceived(const QVariantMap &data) QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType(); LogEntry::LoggingEventType loggingEventType = static_cast(loggingEventTypeEnum.keyToValue(entryMap.value("eventType").toByteArray())); QVariant value = loggingEventType == LogEntry::LoggingEventTypeActiveChange ? entryMap.value("active").toBool() : entryMap.value("value"); - LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, this); + LogEntry *entry = new LogEntry(timeStamp, value, thingId, typeId, loggingSource, loggingEventType, entryMap.value("errorCode").toString(), this); m_list.prepend(entry); if (m_graphSeries) { diff --git a/libnymea-app/types/device.cpp b/libnymea-app/types/device.cpp index f33b4ca5..038f0e6c 100644 --- a/libnymea-app/types/device.cpp +++ b/libnymea-app/types/device.cpp @@ -167,12 +167,26 @@ State *Device::stateByName(const QString &stateName) const return m_states->getState(st->id()); } +Param *Device::param(const QUuid ¶mTypeId) const +{ + return m_params->getParam(paramTypeId); +} + +Param *Device::paramByName(const QString ¶mName) const +{ + ParamType *paramType = m_thingClass->paramTypes()->findByName(paramName); + if (!paramType) { + return nullptr; + } + return m_params->getParam(paramType->id()); +} + DeviceClass *Device::thingClass() const { return m_thingClass; } -bool Device::hasState(const QUuid &stateTypeId) +bool Device::hasState(const QUuid &stateTypeId) const { foreach (State *state, states()->states()) { if (state->stateTypeId() == stateTypeId) { @@ -182,7 +196,7 @@ bool Device::hasState(const QUuid &stateTypeId) return false; } -QVariant Device::stateValue(const QUuid &stateTypeId) +QVariant Device::stateValue(const QUuid &stateTypeId) const { foreach (State *state, states()->states()) { if (state->stateTypeId() == stateTypeId) { diff --git a/libnymea-app/types/device.h b/libnymea-app/types/device.h index d6bc3f33..e9c81c5f 100644 --- a/libnymea-app/types/device.h +++ b/libnymea-app/types/device.h @@ -92,15 +92,17 @@ public: States *states() const; void setStates(States *states); + void setStateValue(const QUuid &stateTypeId, const QVariant &value); DeviceClass *thingClass() const; - Q_INVOKABLE bool hasState(const QUuid &stateTypeId); + Q_INVOKABLE bool hasState(const QUuid &stateTypeId) const; Q_INVOKABLE State *state(const QUuid &stateTypeId) const; Q_INVOKABLE State *stateByName(const QString &stateName) const; + Q_INVOKABLE QVariant stateValue(const QUuid &stateTypeId) const; - Q_INVOKABLE QVariant stateValue(const QUuid &stateTypeId); - void setStateValue(const QUuid &stateTypeId, const QVariant &value); + Q_INVOKABLE Param *param(const QUuid ¶mTypeId) const; + Q_INVOKABLE Param *paramByName(const QString ¶mName) const; Q_INVOKABLE virtual int executeAction(const QString &actionName, const QVariantList ¶ms); @@ -112,8 +114,6 @@ signals: void statesChanged(); void eventTriggered(const QUuid &eventTypeId, const QVariantMap ¶ms); -private: - protected: DeviceManager *m_thingManager = nullptr; QString m_name; diff --git a/libnymea-app/types/logentry.cpp b/libnymea-app/types/logentry.cpp index 896472ad..ac7b4e7e 100644 --- a/libnymea-app/types/logentry.cpp +++ b/libnymea-app/types/logentry.cpp @@ -32,14 +32,15 @@ #include -LogEntry::LogEntry(const QDateTime ×tamp, const QVariant &value, const QUuid &thingId, const QUuid &typeId, LoggingSource source, LoggingEventType loggingEventType, QObject *parent): +LogEntry::LogEntry(const QDateTime ×tamp, const QVariant &value, const QUuid &thingId, const QUuid &typeId, LoggingSource source, LoggingEventType loggingEventType, const QString &errorCode, QObject *parent): QObject(parent), m_value(value), m_timeStamp(timestamp), m_thingId(thingId), m_typeId(typeId), m_source(source), - m_loggingEventType(loggingEventType) + m_loggingEventType(loggingEventType), + m_errorCode(errorCode) { } @@ -104,3 +105,8 @@ QString LogEntry::dateString() const { return m_timeStamp.date().toString("dd.MM."); } + +QString LogEntry::errorCode() const +{ + return m_errorCode; +} diff --git a/libnymea-app/types/logentry.h b/libnymea-app/types/logentry.h index 4977755c..6201f5a0 100644 --- a/libnymea-app/types/logentry.h +++ b/libnymea-app/types/logentry.h @@ -50,6 +50,7 @@ class LogEntry : public QObject Q_PROPERTY(QString timeString READ timeString CONSTANT) Q_PROPERTY(QString dayString READ dayString CONSTANT) Q_PROPERTY(QString dateString READ dateString CONSTANT) + Q_PROPERTY(QString errorCode READ errorCode CONSTANT) public: enum LoggingSource { @@ -71,7 +72,7 @@ public: }; Q_ENUM(LoggingEventType) - explicit LogEntry(const QDateTime ×tamp, const QVariant &value, const QUuid &thingId = QUuid(), const QUuid &typeId = QUuid(), LoggingSource source = LoggingSourceSystem, LoggingEventType loggingEventType = LoggingEventTypeTrigger, QObject *parent = nullptr); + explicit LogEntry(const QDateTime ×tamp, const QVariant &value, const QUuid &thingId = QUuid(), const QUuid &typeId = QUuid(), LoggingSource source = LoggingSourceSystem, LoggingEventType loggingEventType = LoggingEventTypeTrigger, const QString &errorCode = QString(), QObject *parent = nullptr); QVariant value() const; QDateTime timestamp() const; @@ -83,6 +84,7 @@ public: QString timeString() const; QString dayString() const; QString dateString() const; + QString errorCode() const; private: QVariant m_value; @@ -91,6 +93,7 @@ private: QUuid m_typeId; LoggingSource m_source; LoggingEventType m_loggingEventType; + QString m_errorCode; }; #endif // LOGENTRY_H diff --git a/libnymea-app/types/params.cpp b/libnymea-app/types/params.cpp index 50e0b508..e569913e 100644 --- a/libnymea-app/types/params.cpp +++ b/libnymea-app/types/params.cpp @@ -56,10 +56,10 @@ Param *Params::get(int index) const return m_params.at(index); } -Param *Params::getParam(QString paramTypeId) const +Param *Params::getParam(const QUuid ¶mTypeId) const { foreach (Param *param, m_params) { - if (QUuid(param->paramTypeId()) == QUuid(paramTypeId)) { + if (param->paramTypeId() == paramTypeId) { return param; } } diff --git a/libnymea-app/types/params.h b/libnymea-app/types/params.h index c2d4d0cc..8a98ad5e 100644 --- a/libnymea-app/types/params.h +++ b/libnymea-app/types/params.h @@ -51,7 +51,7 @@ public: Q_INVOKABLE int count() const; Q_INVOKABLE Param *get(int index) const; - Q_INVOKABLE Param *getParam(QString paramTypeId) const; + Q_INVOKABLE Param *getParam(const QUuid ¶mTypeId) const; Q_INVOKABLE int paramCount() const; diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index 0149a717..6b271538 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -37,16 +37,6 @@ #include #include -#ifdef Q_OS_ANDROID -#include -#include "platformintegration/android/platformhelperandroid.h" -#elif defined(Q_OS_IOS) -#include "platformintegration/ios/platformhelperios.h" -#elif defined UBPORTS -#include "platformintegration/ubports/platformhelperubports.h" -#else -#include "platformintegration/generic/platformhelpergeneric.h" -#endif #include "libnymea-app-core.h" @@ -56,21 +46,8 @@ #include "ruletemplates/messages.h" #include "nfchelper.h" #include "nfcthingactionwriter.h" +#include "platformhelper.h" -QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine) -{ - Q_UNUSED(engine) - Q_UNUSED(scriptEngine) -#ifdef Q_OS_ANDROID - return new PlatformHelperAndroid(); -#elif defined(Q_OS_IOS) - return new PlatformHelperIOS(); -#elif defined UBPORTS - return new PlatformHelperUBPorts(); -#else - return new PlatformHelperGeneric(); -#endif -} int main(int argc, char *argv[]) { @@ -159,7 +136,7 @@ int main(int argc, char *argv[]) engine->rootContext()->setContextProperty("styleController", &styleController); - qmlRegisterSingletonType("Nymea", 1, 0, "PlatformHelper", platformHelperProvider); + qmlRegisterSingletonType("Nymea", 1, 0, "PlatformHelper", PlatformHelper::platformHelperProvider); qmlRegisterSingletonType("Nymea", 1, 0, "NfcHelper", NfcHelper::nfcHelperProvider); qmlRegisterType("Nymea", 1, 0, "NfcThingActionWriter"); diff --git a/nymea-app/platformhelper.cpp b/nymea-app/platformhelper.cpp index daee679d..ea76316b 100644 --- a/nymea-app/platformhelper.cpp +++ b/nymea-app/platformhelper.cpp @@ -33,11 +33,40 @@ #include #include +#if defined Q_OS_ANDROID +#include +#include "platformintegration/android/platformhelperandroid.h" +#elif defined Q_OS_IOS +#include "platformintegration/ios/platformhelperios.h" +#elif defined UBPORTS +#include "platformintegration/ubports/platformhelperubports.h" +#else +#include "platformintegration/generic/platformhelpergeneric.h" +#endif + +PlatformHelper* PlatformHelper::s_instance = nullptr; + PlatformHelper::PlatformHelper(QObject *parent) : QObject(parent) { } +PlatformHelper *PlatformHelper::instance() +{ + if (!s_instance) { +#ifdef Q_OS_ANDROID + s_instance = new PlatformHelperAndroid(); +#elif defined(Q_OS_IOS) + s_instance = new PlatformHelperIOS(); +#elif defined UBPORTS + s_instance = new PlatformHelperUBPorts(); +#else + s_instance = new PlatformHelperGeneric(); +#endif + } + return s_instance; +} + bool PlatformHelper::hasPermissions() const { return true; @@ -153,3 +182,10 @@ QString PlatformHelper::fromClipBoard() { return QApplication::clipboard()->text(); } + +QObject *PlatformHelper::platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + return instance(); +} diff --git a/nymea-app/platformhelper.h b/nymea-app/platformhelper.h index d9e7ce74..72e18ccd 100644 --- a/nymea-app/platformhelper.h +++ b/nymea-app/platformhelper.h @@ -34,6 +34,9 @@ #include #include +class QQmlEngine; +class QJSEngine; + class PlatformHelper : public QObject { Q_OBJECT @@ -58,7 +61,7 @@ public: }; Q_ENUM(HapticsFeedback) - explicit PlatformHelper(QObject *parent = nullptr); + static PlatformHelper* instance(); virtual ~PlatformHelper() = default; virtual bool hasPermissions() const; @@ -88,6 +91,7 @@ public: Q_INVOKABLE virtual void toClipBoard(const QString &text); Q_INVOKABLE virtual QString fromClipBoard(); + static QObject *platformHelperProvider(QQmlEngine *engine, QJSEngine *scriptEngine); signals: void permissionsRequestFinished(); void screenTimeoutChanged(); @@ -95,7 +99,12 @@ signals: void topPanelColorChanged(); void bottomPanelColorChanged(); +protected: + explicit PlatformHelper(QObject *parent = nullptr); + private: + static PlatformHelper *s_instance; + QColor m_topPanelColor = QColor("black"); QColor m_bottomPanelColor = QColor("black"); }; diff --git a/nymea-app/platformintegration/ubports/platformhelperubports.cpp b/nymea-app/platformintegration/ubports/platformhelperubports.cpp index 1ede877e..8471e304 100644 --- a/nymea-app/platformintegration/ubports/platformhelperubports.cpp +++ b/nymea-app/platformintegration/ubports/platformhelperubports.cpp @@ -1,5 +1,8 @@ #include "platformhelperubports.h" +#include +#include + PlatformHelperUBPorts::PlatformHelperUBPorts(QObject *parent) : PlatformHelper(parent) { @@ -9,3 +12,9 @@ QString PlatformHelperUBPorts::platform() const { return "ubports"; } + +QString PlatformHelperUBPorts::deviceSerial() const +{ + QSettings s; + return s.value("deviceSerial", QUuid::createUuid()).toString(); +} diff --git a/nymea-app/platformintegration/ubports/platformhelperubports.h b/nymea-app/platformintegration/ubports/platformhelperubports.h index eb307007..10606e58 100644 --- a/nymea-app/platformintegration/ubports/platformhelperubports.h +++ b/nymea-app/platformintegration/ubports/platformhelperubports.h @@ -12,6 +12,7 @@ public: explicit PlatformHelperUBPorts(QObject *parent = nullptr); QString platform() const override; + QString deviceSerial() const override; signals: diff --git a/nymea-app/pushnotifications.cpp b/nymea-app/pushnotifications.cpp index 6fddca54..f96b0d24 100644 --- a/nymea-app/pushnotifications.cpp +++ b/nymea-app/pushnotifications.cpp @@ -29,6 +29,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "pushnotifications.h" +#include "platformhelper.h" #include @@ -87,6 +88,27 @@ PushNotifications *PushNotifications::instance() return pushNotifications; } +QString PushNotifications::service() const +{ +#if defined Q_OS_ANDROID + return "FB-GCM"; +#elif defined Q_OS_IOS + return "FB-APNs"; +#elif defined UBPORTS + return "ubports"; +#endif + return QString(); +} + +QString PushNotifications::clientId() const +{ + QString branding; +#if defined BRANDING + branding = "-" + BRANDING; +#endif + return PlatformHelper::instance()->deviceSerial() + "+io.guh.nymeaapp" + branding; +} + QString PushNotifications::token() const { return m_token; diff --git a/nymea-app/pushnotifications.h b/nymea-app/pushnotifications.h index db38e9c7..1456bee6 100644 --- a/nymea-app/pushnotifications.h +++ b/nymea-app/pushnotifications.h @@ -51,6 +51,8 @@ class PushNotifications : public QObject #endif { Q_OBJECT + Q_PROPERTY(QString service READ service CONSTANT) + Q_PROPERTY(QString clientId READ clientId CONSTANT) Q_PROPERTY(QString token READ token NOTIFY tokenChanged) public: @@ -60,6 +62,8 @@ public: static QObject* pushNotificationsProvider(QQmlEngine *engine, QJSEngine *scriptEngine); static PushNotifications* instance(); + QString service() const; + QString clientId() const; QString token() const; // Called by Objective-C++ diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 740daa7d..8bc52351 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -232,5 +232,6 @@ ui/StyleBase.qml ui/customviews/ThermostatController.qml ui/devicepages/ThermostatDevicePage.qml + ui/components/BigThingTile.qml diff --git a/nymea-app/ruletemplates/notificationtemplates.json b/nymea-app/ruletemplates/notificationtemplates.json index 1a7e0e62..f408bf06 100644 --- a/nymea-app/ruletemplates/notificationtemplates.json +++ b/nymea-app/ruletemplates/notificationtemplates.json @@ -32,8 +32,8 @@ ] }, { - "description": "Notify me when something runs dry", - "ruleNameTemplate": "Notify %1 when %0 runs dry", + "description": "Notify me when something dries ou", + "ruleNameTemplate": "Notify %1 when %0 dries out", "stateEvaluatorTemplate": { "stateDescriptorTemplate": { "interfaceName": "moisturesensor", diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index 5c994eb5..baf640d1 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -262,6 +262,7 @@ Item { return false; } + // Old nymea:cloud based push notifications... function setupPushNotifications(askForPermissions) { if (askForPermissions === undefined) { askForPermissions = true; @@ -296,6 +297,46 @@ Item { } } + // New, nymea thing based push notifactions + function updatePushNotificationThings() { + if (PushNotifications.service == "") { + print("This platform does not support push notifications") + return; + } + if (!PushNotifications.token) { + print("No push notification token available at this time. Not updating..."); + return; + } + + print("Updating push notifications") + print("Own push service:", PushNotifications.service); + print("Own client ID:", PushNotifications.clientId); + print("Current token:", PushNotifications.token); + + for (var i = 0; i < engine.thingManager.things.count; i++) { + var thing = engine.thingManager.things.get(i); + if (thing.thingClass.id.toString().match(/\{?f0dd4c03-0aca-42cc-8f34-9902457b05de\}?/)) { + var serviceParam = thing.paramByName("service"); + var clientIdParam = thing.paramByName("clientId") + var tokenParam = thing.paramByName("token") + print("Found a push notification thing for client id:", clientIdParam.value) + if (clientIdParam.value === PushNotifications.clientId) { + if (tokenParam.value !== PushNotifications.token) { + var params = [ + { "paramTypeId": serviceParam.paramTypeId, "value": PushNotifications.service }, + { "paramTypeId": clientIdParam.paramTypeId, "value": PushNotifications.clientId }, + { "paramTypeId": tokenParam.paramTypeId, "value": PushNotifications.token } + ]; + print("Reconfiguring PushNotifications for", thing.name) + engine.thingManager.reconfigureDevice(thing.id, params); + } else { + print("Push notifications don't need to be updated. Token is valid.") + } + } + } + } + } + Connections { target: engine.jsonRpcClient onCurrentHostChanged: { @@ -370,13 +411,22 @@ Item { } } + Connections { + target: engine.thingManager + onFetchingDataChanged: { + if (!engine.thingManager.fetchingData) { + updatePushNotificationThings() + } + } + } + Component { id: invalidVersionComponent Popup { id: popup property string actualVersion: "0.0" - property string minimumVersion: "1.0" + property string minimumVersion: "1.10" width: app.width * .8 height: col.childrenRect.height + app.margins * 2 diff --git a/nymea-app/ui/components/BigThingTile.qml b/nymea-app/ui/components/BigThingTile.qml new file mode 100644 index 00000000..7042540b --- /dev/null +++ b/nymea-app/ui/components/BigThingTile.qml @@ -0,0 +1,36 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.1 +import Nymea 1.0 + +BigTile { + id: root + + property Thing thing: null + + readonly property State connectedState: thing.stateByName("connected") + readonly property bool isConnected: connectedState === null || connectedState.value === true + readonly property bool isEnabled: thing.setupStatus == Thing.ThingSetupStatusComplete && isConnected + + onPressAndHold: { + var contextMenuComponent = Qt.createComponent("../components/ThingContextMenu.qml"); + var contextMenu = contextMenuComponent.createObject(root, { thing: root.thing }) + contextMenu.x = Qt.binding(function() { return (root.width - contextMenu.width) / 2 }) + contextMenu.open() + } + + header: RowLayout { + id: headerRow + visible: root.showHeader + width: parent.width + Layout.margins: app.margins / 2 + Label { + Layout.fillWidth: true + text: root.thing.name + elide: Text.ElideRight + } + ThingStatusIcons { + thing: root.thing + } + } +} diff --git a/nymea-app/ui/components/BigTile.qml b/nymea-app/ui/components/BigTile.qml index 63ad4ea3..05dd08b6 100644 --- a/nymea-app/ui/components/BigTile.qml +++ b/nymea-app/ui/components/BigTile.qml @@ -4,25 +4,20 @@ import QtQuick.Controls 2.1 import QtQuick.Controls.Material 2.1 import Nymea 1.0 - Item { +Item { id: root implicitHeight: layout.implicitHeight + app.margins - property Thing thing: null - - property bool showHeader: true - + property alias header: headerContainer.children property alias contentItem: content.contentItem + property alias showHeader: headerContainer.visible + property alias leftPadding: content.leftPadding property alias rightPadding: content.rightPadding property alias topPadding: content.topPadding property alias bottomPadding: content.bottomPadding - readonly property State connectedState: thing.stateByName("connected") - readonly property bool isConnected: connectedState === null || connectedState.value === true - readonly property bool isEnabled: thing.setupStatus == Thing.ThingSetupStatusComplete && isConnected - signal clicked(); signal pressAndHold(); @@ -73,12 +68,12 @@ import Nymea 1.0 gradient: Gradient { GradientStop { - position: (headerRow.height + app.margins) / background.height + position: (headerContainer.height + app.margins) / background.height color: Style.tileBackgroundColor } GradientStop { - position: (headerRow.height + app.margins) / background.height - color:root.showHeader ? + position: (headerContainer.height + app.margins) / background.height + color: headerContainer.visible ? Style.tileOverlayColor : Style.tileBackgroundColor } @@ -90,20 +85,12 @@ import Nymea 1.0 spacing: 0 anchors { left: parent.left; top: parent.top; right: parent.right; margins: app.margins / 2 } - RowLayout { - id: headerRow - visible: root.showHeader + Item { + id: headerContainer + Layout.fillWidth: true Layout.margins: app.margins / 2 - Label { - Layout.fillWidth: true - text: root.thing.name - elide: Text.ElideRight - color: Style.tileOverlayForegroundColor - } - ThingStatusIcons { - thing: root.thing -// color: Style.tileOverlayForegroundColor - } + visible: children.length > 0 + height: childrenRect.height } ItemDelegate { diff --git a/nymea-app/ui/devicelistpages/ClosablesDeviceListPage.qml b/nymea-app/ui/devicelistpages/ClosablesDeviceListPage.qml index 4c42ff95..7243ee8c 100644 --- a/nymea-app/ui/devicelistpages/ClosablesDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/ClosablesDeviceListPage.qml @@ -98,7 +98,7 @@ DeviceListPageBase { Repeater { model: root.thingsProxy - delegate: BigTile { + delegate: BigThingTile { id: itemDelegate Layout.preferredWidth: contentGrid.width / contentGrid.columns thing: root.thingsProxy.getThing(model.id) diff --git a/nymea-app/ui/devicelistpages/GarageThingListPage.qml b/nymea-app/ui/devicelistpages/GarageThingListPage.qml index 1d0898ac..8c20ebbc 100644 --- a/nymea-app/ui/devicelistpages/GarageThingListPage.qml +++ b/nymea-app/ui/devicelistpages/GarageThingListPage.qml @@ -62,7 +62,7 @@ DeviceListPageBase { Repeater { model: root.thingsProxy - delegate: BigTile { + delegate: BigThingTile { id: itemDelegate Layout.preferredWidth: contentGrid.width / contentGrid.columns thing: root.thingsProxy.getThing(model.id) diff --git a/nymea-app/ui/devicelistpages/GenericDeviceListPage.qml b/nymea-app/ui/devicelistpages/GenericDeviceListPage.qml index 1a2bfea6..9ebea896 100644 --- a/nymea-app/ui/devicelistpages/GenericDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/GenericDeviceListPage.qml @@ -68,7 +68,7 @@ DeviceListPageBase { Repeater { model: root.thingsProxy - delegate: BigTile { + delegate: BigThingTile { id: itemDelegate Layout.preferredWidth: contentGrid.width / contentGrid.columns thing: root.thingsProxy.getThing(model.id) diff --git a/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml b/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml index c0a83849..55da90af 100644 --- a/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml @@ -87,7 +87,7 @@ DeviceListPageBase { Repeater { model: root.thingsProxy - delegate: BigTile { + delegate: BigThingTile { id: itemDelegate Layout.preferredWidth: contentGrid.width / contentGrid.columns thing: root.thingsProxy.getThing(model.id) @@ -97,6 +97,7 @@ DeviceListPageBase { leftPadding: 0 rightPadding: 0 + property State powerState: thing.stateByName("power") property State brightnessState: thing.stateByName("brightness") property State colorState: thing.stateByName("color") diff --git a/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml b/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml index 9f31566d..05c91def 100644 --- a/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml @@ -59,7 +59,7 @@ DeviceListPageBase { Repeater { model: root.thingsProxy - delegate: BigTile { + delegate: BigThingTile { id: itemDelegate Layout.preferredWidth: contentGrid.width / contentGrid.columns thing: thingsProxy.getThing(model.id) diff --git a/nymea-app/ui/devicelistpages/PowerSocketsDeviceListPage.qml b/nymea-app/ui/devicelistpages/PowerSocketsDeviceListPage.qml index 7cbecc2c..f5f46367 100644 --- a/nymea-app/ui/devicelistpages/PowerSocketsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/PowerSocketsDeviceListPage.qml @@ -62,7 +62,7 @@ DeviceListPageBase { Repeater { model: root.thingsProxy - delegate: BigTile { + delegate: BigThingTile { id: itemDelegate Layout.preferredWidth: contentGrid.width / contentGrid.columns thing: root.thingsProxy.getThing(model.id) diff --git a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml index bdd50a49..6be739f9 100644 --- a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml @@ -59,7 +59,7 @@ DeviceListPageBase { Repeater { model: root.thingsProxy - delegate: BigTile { + delegate: BigThingTile { id: itemDelegate Layout.preferredWidth: contentGrid.width / contentGrid.columns thing: root.thingsProxy.getThing(model.id) diff --git a/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml index 31ff38bb..df5688af 100644 --- a/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml @@ -59,7 +59,7 @@ DeviceListPageBase { Repeater { model: root.thingsProxy - delegate: BigTile { + delegate: BigThingTile { id: itemDelegate Layout.preferredWidth: contentGrid.width / contentGrid.columns thing: root.thingsProxy.getThing(model.id) diff --git a/nymea-app/ui/devicelistpages/WeatherDeviceListPage.qml b/nymea-app/ui/devicelistpages/WeatherDeviceListPage.qml index 5c2ee465..e7c60386 100644 --- a/nymea-app/ui/devicelistpages/WeatherDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/WeatherDeviceListPage.qml @@ -60,7 +60,7 @@ DeviceListPageBase { Repeater { model: root.thingsProxy - delegate: BigTile { + delegate: BigThingTile { id: itemDelegate Layout.preferredWidth: contentGrid.width / contentGrid.columns thing: root.thingsProxy.get(model.id) diff --git a/nymea-app/ui/devicepages/NotificationsDevicePage.qml b/nymea-app/ui/devicepages/NotificationsDevicePage.qml index 609fb56e..3d3e797e 100644 --- a/nymea-app/ui/devicepages/NotificationsDevicePage.qml +++ b/nymea-app/ui/devicepages/NotificationsDevicePage.qml @@ -28,8 +28,8 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ -import QtQuick 2.5 -import QtQuick.Controls 2.1 +import QtQuick 2.9 +import QtQuick.Controls 2.2 import QtQuick.Layouts 1.1 import Nymea 1.0 import "../components" @@ -52,19 +52,92 @@ DevicePageBase { } } + function sendMessage(title, text) { + print("sending message", title, text) + var actionType = root.deviceClass.actionTypes.findByName("notify") + var params = [] + var titleParam = {} + titleParam["paramTypeId"] = actionType.paramTypes.findByName("title").id + titleParam["value"] = title + params.push(titleParam) + var bodyParam = {} + bodyParam["paramTypeId"] = actionType.paramTypes.findByName("body").id + bodyParam["value"] = text + params.push(bodyParam) + d.pendingAction = engine.deviceManager.executeAction(root.device.id, actionType.id, params) + titleTextField.clear(); + bodyTextField.clear(); + } + ColumnLayout { + id: content anchors.fill: parent - Label { + RowLayout { + id: inputPane Layout.fillWidth: true Layout.margins: app.margins - wrapMode: Text.WordWrap - text: qsTr("Sent notifications:") - visible: logsModel.count > 0 && !logsModel.busy + spacing: app.margins + + ColumnLayout { + id: inputColumn + + TextField { + id: titleTextField + Layout.fillWidth: true + placeholderText: qsTr("Title") + } + + ScrollView { + Layout.preferredWidth: inputPane.width - app.iconSize - inputPane.spacing + Layout.maximumHeight: content.height - y - app.margins + contentWidth: width + + TextArea { + id: bodyTextField + placeholderText: qsTr("Text") + wrapMode: TextArea.WrapAtWordBoundaryOrAnywhere + } + } + } + + Item { + id: sendButton + Layout.preferredWidth: app.iconSize + Layout.preferredHeight: inputColumn.height + ColorIcon { + anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom; margins: app.margins } + height: app.iconSize + width: app.iconSize + name: "../images/send.svg" + color: titleTextField.displayText.length > 0 ? Style.accentColor : Style.iconColor + visible: d.pendingAction == -1 + } + + BusyIndicator { + anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom; margins: app.margins } + visible: d.pendingAction != -1 + running: visible + } + + MouseArea { + anchors.fill: parent + onClicked: { + print("clicked!") + bodyTextField.focus = false + if (titleTextField.displayText.length > 0) { + root.sendMessage(titleTextField.displayText, bodyTextField.text) + titleTextField.clear(); + bodyTextField.clear(); + } + } + } + } } GenericTypeLogView { + id: logView Layout.fillHeight: true Layout.fillWidth: true @@ -76,16 +149,65 @@ DevicePageBase { typeIds: [root.deviceClass.actionTypes.findByName("notify").id]; } - delegate: NymeaSwipeDelegate { - width: parent.width - iconName: app.interfaceToIcon("notifications") - text: model.value.trim() - subText: Qt.formatDateTime(model.timestamp) - progressive: false + delegate: BigTile { + id: itemDelegate + showHeader: false + width: logView.width - app.margins + anchors.horizontalCenter: parent.horizontalCenter + + // Note: This will go wrong if the title contains ", ". Known shortcoming of the log db + readonly property string title: model.value.trim().replace(/, ?.*/, "") + readonly property string text: model.value.trim().replace(/.*, ?/, "") + + contentItem: RowLayout { + ColumnLayout { + Label { + Layout.fillWidth: true + text: itemDelegate.title + elide: Text.ElideRight + } + GridLayout { + Layout.fillWidth: true + columns: textLabel.implicitWidth + dateLayout.implicitWidth < width ? 2 : 1 + + Label { + id: textLabel + Layout.fillWidth: true + text: itemDelegate.text + font.pixelSize: app.smallFont + wrapMode: Text.WordWrap + } + + RowLayout { + id: dateLayout + Layout.fillWidth: true + spacing: app.margins / 2 + Label { + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + text: Qt.formatDateTime(model.timestamp) + font.pixelSize: app.extraSmallFont + } + ColorIcon { + Layout.preferredWidth: app.smallIconSize + Layout.preferredHeight: app.smallIconSize + name: "../images/dialog-warning-symbolic.svg" + color: "red" + visible: model.errorCode !== "" + } + } + } + } + } onClicked: { - var parts = model.value.trim().split(', ') - var popup = detailsPopup.createObject(root, {timestamp: model.timestamp, notificationTitle: parts[0], notificationBody: parts[1]}); + var popup = detailsPopup.createObject(root, + { + timestamp: model.timestamp, + notificationTitle: itemDelegate.title, + notificationBody: itemDelegate.text, + errorCode: model.errorCode + }); popup.open(); } } @@ -100,73 +222,6 @@ DevicePageBase { visible: logsModel.count == 0 && !logsModel.busy } } - - ThinDivider {} - - RowLayout { - Layout.fillWidth: true - Layout.margins: app.margins - spacing: app.margins - - ColumnLayout { - id: inputColumn - anchors { left: parent.left; bottom: parent.bottom; right: parent.right } - - TextField { - id: titleTextField - Layout.fillWidth: true - placeholderText: qsTr("Title") - } - - TextArea { - id: bodyTextField - Layout.fillWidth: true - placeholderText: qsTr("Text") - wrapMode: Text.WordWrap - } - } - - Item { - Layout.preferredWidth: app.iconSize - Layout.preferredHeight: inputColumn.height - ColorIcon { - anchors.centerIn: parent - height: app.iconSize - width: app.iconSize - name: "../images/send.svg" - color: titleTextField.displayText.length > 0 ? Style.accentColor : Style.iconColor - visible: d.pendingAction == -1 - } - - BusyIndicator { - anchors.centerIn: parent - visible: d.pendingAction != -1 - running: visible - } - - MouseArea { - anchors.fill: parent - onClicked: { - print("clicked!") - if (titleTextField.displayText.length > 0) { - var actionType = root.deviceClass.actionTypes.findByName("notify") - var params = [] - var titleParam = {} - titleParam["paramTypeId"] = actionType.paramTypes.findByName("title").id - titleParam["value"] = titleTextField.displayText - params.push(titleParam) - var bodyParam = {} - bodyParam["paramTypeId"] = actionType.paramTypes.findByName("body").id - bodyParam["value"] = bodyTextField.text - params.push(bodyParam) - d.pendingAction = engine.deviceManager.executeAction(root.device.id, actionType.id, params) - titleTextField.clear(); - bodyTextField.clear(); - } - } - } - } - } } BusyIndicator { @@ -177,22 +232,38 @@ DevicePageBase { Component { id: detailsPopup + MeaDialog { id: detailsDialog + standardButtons: Dialog.NoButton property string timestamp property string notificationTitle property string notificationBody + property string errorCode title: qsTr("Notification details") - Label { - Layout.fillWidth: true - text: qsTr("Date sent") - font.bold: true + headerIcon: "../images/messaging-app-symbolic.svg" + RowLayout { + ColumnLayout { + + Label { + Layout.fillWidth: true + text: detailsDialog.errorCode == "" ? qsTr("Date sent") : qsTr("Sending failed") + font.bold: true + } + Label { + Layout.fillWidth: true + text: Qt.formatDateTime(detailsDialog.timestamp) + } + } + ColorIcon { + Layout.preferredWidth: app.largeIconSize + Layout.preferredHeight: app.largeIconSize + name: "../images/dialog-warning-symbolic.svg" + color: "red" + visible: detailsDialog.errorCode !== "" + } } - Label { - Layout.fillWidth: true - text: Qt.formatDateTime(detailsDialog.timestamp) - } Label { Layout.topMargin: app.margins Layout.fillWidth: true @@ -217,6 +288,20 @@ DevicePageBase { text: detailsDialog.notificationBody wrapMode: Text.WordWrap } + + RowLayout { + Item { + Layout.fillWidth: true + } + Button { + text: qsTr("Resend") + onClicked: root.sendMessage(detailsDialog.notificationTitle, detailsDialog.notificationBody) + } + Button { + text: qsTr("Close") + onClicked: detailsDialog.close() + } + } } } } diff --git a/nymea-app/ui/thingconfiguration/SetupWizard.qml b/nymea-app/ui/thingconfiguration/SetupWizard.qml index ac30f72f..1fdbdd96 100644 --- a/nymea-app/ui/thingconfiguration/SetupWizard.qml +++ b/nymea-app/ui/thingconfiguration/SetupWizard.qml @@ -401,21 +401,13 @@ Page { print("Setting up params for thing class:", root.thingClass.id, root.thingClass.name) if (root.thingClass.id.toString().match(/\{?f0dd4c03-0aca-42cc-8f34-9902457b05de\}?/)) { if (paramType.id.toString().match(/\{?3cb8e30e-2ec5-4b4b-8c8c-03eaf7876839\}?/)) { - if (PlatformHelper.platform === "android") { - return "FB-GCM"; - } else if (PlatformHelper.platform === "ios") { - return "FB-APNs" - } else if (PlatformHelper.platform === "ubports") { - return "UBPorts"; - } else { - print("Unsupported platform for push notifications:", PlatformHelper.platform) - } + return PushNotifications.service; } if (paramType.id.toString().match(/\{?12ec06b2-44e7-486a-9169-31c684b91c8f\}?/)) { return PushNotifications.token; } if (paramType.id.toString().match(/\{?d76da367-64e3-4b7d-aa84-c96b3acfb65e\}?/)) { - return PlatformHelper.deviceSerial + "+io.guh.nymeaapp" + (appBranding.length > 0 ? "-" + appBranding : ""); + return PushNotifications.clientId; } } diff --git a/nymea-app/ui/utils/NymeaUtils.qml b/nymea-app/ui/utils/NymeaUtils.qml index c08bccf7..e10e8531 100644 --- a/nymea-app/ui/utils/NymeaUtils.qml +++ b/nymea-app/ui/utils/NymeaUtils.qml @@ -1,5 +1,6 @@ pragma Singleton import QtQuick 2.9 +import Nymea 1.0 Item { id: root @@ -81,4 +82,5 @@ Item { return ((r * 299 + g * 587 + b * 114) / 1000) < 128 } + }