From 5b0397ba2b199087fe9e060db6928c97722f27f2 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 5 Sep 2020 00:04:52 +0200 Subject: [PATCH 1/2] Make plugin issues more visible to the user --- libnymea-app/devicemanager.cpp | 2 +- libnymea-app/devicesproxy.cpp | 21 +++++++++++++++++ libnymea-app/devicesproxy.h | 7 ++++++ libnymea-app/jsonrpc/jsontypes.cpp | 10 ++++---- libnymea-app/thinggroup.cpp | 6 ++--- libnymea-app/thinggroup.h | 2 +- libnymea-app/types/device.cpp | 10 ++++---- libnymea-app/types/device.h | 20 +++++++++++----- nymea-app/ui/components/MainPageTile.qml | 18 ++++++++++++--- nymea-app/ui/delegates/InterfaceTile.qml | 8 +++++++ nymea-app/ui/delegates/ThingDelegate.qml | 29 ++++++++++++++++-------- nymea-app/ui/delegates/ThingTile.qml | 4 ++++ 12 files changed, 103 insertions(+), 34 deletions(-) diff --git a/libnymea-app/devicemanager.cpp b/libnymea-app/devicemanager.cpp index 7b46a240..7021b7ca 100644 --- a/libnymea-app/devicemanager.cpp +++ b/libnymea-app/devicemanager.cpp @@ -457,7 +457,7 @@ void DeviceManager::savePluginConfig(const QUuid &pluginId) ThingGroup *DeviceManager::createGroup(Interface *interface, DevicesProxy *things) { ThingGroup* group = new ThingGroup(this, interface->createDeviceClass(), things, this); - group->setSetupStatus(Device::DeviceSetupStatusComplete, QString()); + group->setSetupStatus(Device::ThingSetupStatusComplete, QString()); return group; } diff --git a/libnymea-app/devicesproxy.cpp b/libnymea-app/devicesproxy.cpp index bc77975a..e73d192a 100644 --- a/libnymea-app/devicesproxy.cpp +++ b/libnymea-app/devicesproxy.cpp @@ -293,6 +293,21 @@ void DevicesProxy::setFilterDisconnected(bool filterDisconnected) } } +bool DevicesProxy::filterSetupFailed() const +{ + return m_filterSetupFailed; +} + +void DevicesProxy::setFilterSetupFailed(bool filterSetupFailed) +{ + if (m_filterSetupFailed != filterSetupFailed) { + m_filterSetupFailed = filterSetupFailed; + emit filterSetupFailedChanged(); + invalidateFilter(); + emit countChanged(); + } +} + bool DevicesProxy::groupByInterface() const { return m_groupByInterface; @@ -426,6 +441,12 @@ bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_pa } } + if (m_filterSetupFailed) { + if (device->setupStatus() != Device::DeviceSetupStatusFailed) { + return false; + } + } + if (!m_nameFilter.isEmpty()) { if (!device->name().toLower().contains(m_nameFilter.toLower().trimmed())) { return false; diff --git a/libnymea-app/devicesproxy.h b/libnymea-app/devicesproxy.h index c5f11c65..a3ed9ef2 100644 --- a/libnymea-app/devicesproxy.h +++ b/libnymea-app/devicesproxy.h @@ -65,6 +65,8 @@ class DevicesProxy : public QSortFilterProxyModel // Setting this to true will imply filtering for "connectable" interface Q_PROPERTY(bool filterDisconnected READ filterDisconnected WRITE setFilterDisconnected NOTIFY filterDisconnectedChanged) + Q_PROPERTY(bool filterSetupFailed READ filterSetupFailed WRITE setFilterSetupFailed NOTIFY filterSetupFailedChanged) + Q_PROPERTY(bool groupByInterface READ groupByInterface WRITE setGroupByInterface NOTIFY groupByInterfaceChanged) public: @@ -115,6 +117,9 @@ public: bool filterDisconnected() const; void setFilterDisconnected(bool filterDisconnected); + bool filterSetupFailed() const; + void setFilterSetupFailed(bool filterSetupFailed); + bool groupByInterface() const; void setGroupByInterface(bool groupByInterface); @@ -137,6 +142,7 @@ signals: void showAnalogOutputsChanged(); void filterBatteryCriticalChanged(); void filterDisconnectedChanged(); + void filterSetupFailedChanged(); void groupByInterfaceChanged(); void countChanged(); @@ -160,6 +166,7 @@ private: bool m_filterBatteryCritical = false; bool m_filterDisconnected = false; + bool m_filterSetupFailed = false; bool m_groupByInterface = false; diff --git a/libnymea-app/jsonrpc/jsontypes.cpp b/libnymea-app/jsonrpc/jsontypes.cpp index a890a807..c825b2cc 100644 --- a/libnymea-app/jsonrpc/jsontypes.cpp +++ b/libnymea-app/jsonrpc/jsontypes.cpp @@ -250,16 +250,16 @@ Device* JsonTypes::unpackDevice(DeviceManager *deviceManager, const QVariantMap QString setupStatus = deviceMap.value("setupStatus").toString(); QString setupDisplayMessage = deviceMap.value("setupDisplayMessage").toString(); if (setupStatus == "DeviceSetupStatusNone" || setupStatus == "ThingSetupStatusNone") { - device->setSetupStatus(Device::DeviceSetupStatusNone, setupDisplayMessage); + device->setSetupStatus(Device::ThingSetupStatusNone, setupDisplayMessage); } else if (setupStatus == "DeviceSetupStatusInProgress" || setupStatus == "ThingSetupStatusInProgress") { - device->setSetupStatus(Device::DeviceSetupStatusInProgress, setupDisplayMessage); + device->setSetupStatus(Device::ThingSetupStatusInProgress, setupDisplayMessage); } else if (setupStatus == "DeviceSetupStatusComplete" || setupStatus == "ThingSetupStatusComplete") { - device->setSetupStatus(Device::DeviceSetupStatusComplete, setupDisplayMessage); + device->setSetupStatus(Device::ThingSetupStatusComplete, setupDisplayMessage); } else if (setupStatus == "DeviceSetupStatusFailed" || setupStatus == "ThingSetupStatusFailed") { - device->setSetupStatus(Device::DeviceSetupStatusFailed, setupDisplayMessage); + device->setSetupStatus(Device::ThingSetupStatusFailed, setupDisplayMessage); } } else { - device->setSetupStatus(deviceMap.value("setupComplete").toBool() ? Device::DeviceSetupStatusComplete : Device::DeviceSetupStatusNone, QString()); + device->setSetupStatus(deviceMap.value("setupComplete").toBool() ? Device::ThingSetupStatusComplete : Device::ThingSetupStatusNone, QString()); } Params *params = device->params(); diff --git a/libnymea-app/thinggroup.cpp b/libnymea-app/thinggroup.cpp index 492a4837..b3d78801 100644 --- a/libnymea-app/thinggroup.cpp +++ b/libnymea-app/thinggroup.cpp @@ -36,7 +36,7 @@ ThingGroup::ThingGroup(DeviceManager *deviceManager, DeviceClass *deviceClass, DevicesProxy *devices, QObject *parent): Device(deviceManager, deviceClass, QUuid::createUuid(), parent), - m_deviceManager(deviceManager), + m_thingManager(deviceManager), m_devices(devices) { deviceClass->setParent(this); @@ -56,7 +56,7 @@ ThingGroup::ThingGroup(DeviceManager *deviceManager, DeviceClass *deviceClass, D syncStates(); }); - connect(m_deviceManager, &DeviceManager::executeActionReply, this, [this](const QVariantMap ¶ms){ + connect(m_thingManager, &DeviceManager::executeActionReply, this, [this](const QVariantMap ¶ms){ int returningId = params.value("id").toInt(); foreach (int id, m_pendingActions.keys()) { if (m_pendingActions.value(id).contains(returningId)) { @@ -101,7 +101,7 @@ int ThingGroup::executeAction(const QString &actionName, const QVariantList &par qDebug() << "Initial params" << params; qDebug() << "Executing" << device->id() << actionType->name() << finalParams; - int id = m_deviceManager->executeAction(device->id(), actionType->id(), finalParams); + int id = m_thingManager->executeAction(device->id(), actionType->id(), finalParams); pendingIds.append(id); } m_pendingActions.insert(++m_idCounter, pendingIds); diff --git a/libnymea-app/thinggroup.h b/libnymea-app/thinggroup.h index f076d513..59473b59 100644 --- a/libnymea-app/thinggroup.h +++ b/libnymea-app/thinggroup.h @@ -53,7 +53,7 @@ signals: void actionExecutionFinished(int id, const QString &status); private: - DeviceManager* m_deviceManager = nullptr; + DeviceManager* m_thingManager = nullptr; DevicesProxy* m_devices = nullptr; int m_idCounter = 0; diff --git a/libnymea-app/types/device.cpp b/libnymea-app/types/device.cpp index 989f547f..f33b4ca5 100644 --- a/libnymea-app/types/device.cpp +++ b/libnymea-app/types/device.cpp @@ -34,9 +34,9 @@ #include -Device::Device(DeviceManager *deviceManager, DeviceClass *thingClass, const QUuid &parentId, QObject *parent) : +Device::Device(DeviceManager *thingManager, DeviceClass *thingClass, const QUuid &parentId, QObject *parent) : QObject(parent), - m_deviceManager(deviceManager), + m_thingManager(thingManager), m_parentId(parentId), m_thingClass(thingClass) { @@ -83,7 +83,7 @@ bool Device::isChild() const return !m_parentId.isNull(); } -Device::DeviceSetupStatus Device::setupStatus() const +Device::ThingSetupStatus Device::setupStatus() const { return m_setupStatus; } @@ -93,7 +93,7 @@ QString Device::setupDisplayMessage() const return m_setupDisplayMessage; } -void Device::setSetupStatus(Device::DeviceSetupStatus setupStatus, const QString &displayMessage) +void Device::setSetupStatus(Device::ThingSetupStatus setupStatus, const QString &displayMessage) { if (m_setupStatus != setupStatus || m_setupDisplayMessage != displayMessage) { m_setupStatus = setupStatus; @@ -215,7 +215,7 @@ int Device::executeAction(const QString &actionName, const QVariantList ¶ms) } finalParams.append(param); } - return m_deviceManager->executeAction(m_id, actionType->id(), finalParams); + return m_thingManager->executeAction(m_id, actionType->id(), finalParams); } QDebug operator<<(QDebug &dbg, Device *thing) diff --git a/libnymea-app/types/device.h b/libnymea-app/types/device.h index 1f60b3fb..d0189832 100644 --- a/libnymea-app/types/device.h +++ b/libnymea-app/types/device.h @@ -50,7 +50,7 @@ class Device : public QObject Q_PROPERTY(QUuid parentDeviceId READ parentDeviceId CONSTANT) Q_PROPERTY(bool isChild READ isChild CONSTANT) Q_PROPERTY(QString name READ name NOTIFY nameChanged) - Q_PROPERTY(DeviceSetupStatus setupStatus READ setupStatus NOTIFY setupStatusChanged) + Q_PROPERTY(ThingSetupStatus setupStatus READ setupStatus NOTIFY setupStatusChanged) Q_PROPERTY(QString setupDisplayMessage READ setupDisplayMessage NOTIFY setupStatusChanged) Q_PROPERTY(Params *params READ params NOTIFY paramsChanged) Q_PROPERTY(Params *settings READ settings NOTIFY settingsChanged) @@ -67,7 +67,15 @@ public: }; Q_ENUM(DeviceSetupStatus) - explicit Device(DeviceManager *deviceManager, DeviceClass *thingClass, const QUuid &parentId = QUuid(), QObject *parent = nullptr); + enum ThingSetupStatus { + ThingSetupStatusNone, + ThingSetupStatusInProgress, + ThingSetupStatusComplete, + ThingSetupStatusFailed + }; + Q_ENUM(ThingSetupStatus) + + explicit Device(DeviceManager *thingManager, DeviceClass *thingClass, const QUuid &parentId = QUuid(), QObject *parent = nullptr); QUuid id() const; void setId(const QUuid &id); @@ -80,9 +88,9 @@ public: QUuid parentDeviceId() const; bool isChild() const; - DeviceSetupStatus setupStatus() const; + Device::ThingSetupStatus setupStatus() const; QString setupDisplayMessage() const; - void setSetupStatus(DeviceSetupStatus setupStatus, const QString &displayMessage); + void setSetupStatus(Device::ThingSetupStatus setupStatus, const QString &displayMessage); Params *params() const; void setParams(Params *params); @@ -115,11 +123,11 @@ signals: private: protected: - DeviceManager *m_deviceManager = nullptr; + DeviceManager *m_thingManager = nullptr; QString m_name; QUuid m_id; QUuid m_parentId; - DeviceSetupStatus m_setupStatus = DeviceSetupStatusNone; + ThingSetupStatus m_setupStatus = ThingSetupStatusNone; QString m_setupDisplayMessage; Params *m_params = nullptr; Params *m_settings = nullptr; diff --git a/nymea-app/ui/components/MainPageTile.qml b/nymea-app/ui/components/MainPageTile.qml index 5a93f687..0f1f708f 100644 --- a/nymea-app/ui/components/MainPageTile.qml +++ b/nymea-app/ui/components/MainPageTile.qml @@ -32,6 +32,7 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Controls.Material 2.2 import QtQuick.Layouts 1.3 +import Nymea 1.0 Item { id: root @@ -42,6 +43,9 @@ Item { property alias backgroundImage: background.source property string text property bool disconnected: false + property bool isWireless: false + property int signalStrength: -1 + property int setupStatus: Device.DeviceSetupStatusNone property bool batteryCritical: false property alias contentItem: innerContent.children @@ -143,12 +147,20 @@ Item { Row { id: quickAlertPane anchors { top: parent.top; right: parent.right; margins: app.margins } + spacing: app.margins / 2 ColorIcon { height: app.iconSize / 2 width: height - name: "../images/dialog-warning-symbolic.svg" - color: "red" - visible: root.disconnected + name: root.isWireless ? "../images/network-wifi-offline.svg" : "../images/network-wired-offline.svg" + color: root.disconnected ? "red" : "orange" + visible: root.setupStatus == Device.DeviceSetupStatusComplete && (root.disconnected || (root.signalStrength >= 0 && root.signalStrength < 10)) + } + ColorIcon { + height: app.iconSize / 2 + width: height + name: root.setupStatus === Device.DeviceSetupStatusFailed ? "../images/dialog-warning-symbolic.svg" : "../images/settings.svg" + color: root.setupStatus === Device.DeviceSetupStatusFailed ? "red" : keyColor + visible: root.setupStatus === Device.DeviceSetupStatusFailed || root.setupStatus === Device.DeviceSetupStatusInProgress } ColorIcon { height: app.iconSize / 2 diff --git a/nymea-app/ui/delegates/InterfaceTile.qml b/nymea-app/ui/delegates/InterfaceTile.qml index 9d936ca5..888c8b94 100644 --- a/nymea-app/ui/delegates/InterfaceTile.qml +++ b/nymea-app/ui/delegates/InterfaceTile.qml @@ -41,7 +41,9 @@ MainPageTile { iconName: iface ? interfaceToIcon(iface.name) : interfaceToIcon("uncategorized") iconColor: app.accentColor disconnected: devicesSubProxyConnectables.count > 0 + isWireless: devicesSubProxyConnectables.count > 0 && devicesSubProxyConnectables.get(0).thingClass.interfaces.indexOf("wirelessconnectable") >= 0 batteryCritical: devicesSubProxyBattery.count > 0 + setupStatus: thingsSubProxySetupFailure.count > 0 ? Device.DeviceSetupStatusFailed : Device.DeviceSetupStatusComplete property Interface iface: null property alias filterTagId: devicesProxy.filterTagId @@ -118,6 +120,12 @@ MainPageTile { parentProxy: devicesProxy filterBatteryCritical: true } + DevicesProxy { + id: thingsSubProxySetupFailure + engine: _engine + parentProxy: devicesProxy + filterSetupFailed: true + } property int currentDeviceIndex: 0 readonly property Device currentDevice: devicesProxy.get(currentDeviceIndex) diff --git a/nymea-app/ui/delegates/ThingDelegate.qml b/nymea-app/ui/delegates/ThingDelegate.qml index 60c67374..812b40bd 100644 --- a/nymea-app/ui/delegates/ThingDelegate.qml +++ b/nymea-app/ui/delegates/ThingDelegate.qml @@ -37,22 +37,31 @@ import Nymea 1.0 NymeaListItemDelegate { id: root width: parent.width - iconName: device && device.deviceClass ? app.interfacesToIcon(device.deviceClass.interfaces) : "" - text: device ? device.name : "" + iconName: thing && thing.thingClass ? app.interfacesToIcon(thing.thingClass.interfaces) : "" + text: thing ? thing.name : "" progressive: true secondaryIconName: batteryCritical ? "../images/battery/battery-010.svg" : "" - tertiaryIconName: disconnected ? "../images/dialog-warning-symbolic.svg" : "" - tertiaryIconColor: "red" + tertiaryIconName: thing.setupStatus == Thing.ThingSetupStatusFailed + ? "../images/dialog-warning-symbolic.svg" + : thing.setupStatus == Thing.ThingSetupStatusInProgress + ? "../images/settings.svg" + : disconnected + ? "../images/dialog-warning-symbolic.svg" + : "" + tertiaryIconColor: thing.setupStatus == Thing.ThingSetupStatusInProgress ? iconKeyColor : "red" property Device device: null + property Thing thing: device - readonly property bool hasBatteryInterface: device && device.deviceClass.interfaces.indexOf("battery") > 0 - readonly property StateType batteryCriticalStateType: hasBatteryInterface ? device.deviceClass.stateTypes.findByName("batteryCritical") : null - readonly property State batteryCriticalState: batteryCriticalStateType ? device.states.getState(batteryCriticalStateType.id) : null + readonly property bool hasBatteryInterface: thing && thing.thingClass.interfaces.indexOf("battery") > 0 + readonly property StateType batteryCriticalStateType: hasBatteryInterface ? thing.thingClass.stateTypes.findByName("batteryCritical") : null + readonly property State batteryCriticalState: batteryCriticalStateType ? thing.states.getState(batteryCriticalStateType.id) : null readonly property bool batteryCritical: batteryCriticalState && batteryCriticalState.value === true - readonly property bool hasConnectableInterface: device && device.deviceClass.interfaces.indexOf("connectable") > 0 - readonly property StateType connectedStateType: hasConnectableInterface ? device.deviceClass.stateTypes.findByName("connected") : null - readonly property State connectedState: connectedStateType ? device.states.getState(connectedStateType.id) : null + readonly property bool hasConnectableInterface: thing && thing.thingClass.interfaces.indexOf("connectable") > 0 + readonly property StateType connectedStateType: hasConnectableInterface ? thing.thingClass.stateTypes.findByName("connected") : null + readonly property State connectedState: connectedStateType ? thing.states.getState(connectedStateType.id) : null readonly property bool disconnected: connectedState && connectedState.value === false ? true : false + + } diff --git a/nymea-app/ui/delegates/ThingTile.qml b/nymea-app/ui/delegates/ThingTile.qml index 6f61e988..bd0e7c89 100644 --- a/nymea-app/ui/delegates/ThingTile.qml +++ b/nymea-app/ui/delegates/ThingTile.qml @@ -40,14 +40,18 @@ MainPageTile { text: device.name.toUpperCase() iconName: app.interfacesToIcon(deviceClass.interfaces) iconColor: app.accentColor + isWireless: deviceClass.interfaces.indexOf("wirelessconnectable") >= 0 batteryCritical: batteryCriticalState && batteryCriticalState.value === true disconnected: connectedState && connectedState.value === false + signalStrength: signalStrengthState ? signalStrengthState.value : -1 + setupStatus: device.setupStatus backgroundImage: artworkState && artworkState.value.length > 0 ? artworkState.value : "" property Device device: null readonly property DeviceClass deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null readonly property State connectedState: deviceClass.interfaces.indexOf("connectable") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("connected").id) : null + readonly property State signalStrengthState: device.stateByName("signalStrength") readonly property State batteryCriticalState: deviceClass.interfaces.indexOf("battery") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("batteryCritical").id) : null readonly property State artworkState: deviceClass.interfaces.indexOf("mediametadataprovider") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("artwork").id) : null From 0e61ad05653fa32029c3a3659933c028148baadb Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 5 Sep 2020 22:35:04 +0200 Subject: [PATCH 2/2] More work --- libnymea-app/devices.cpp | 47 +++++++++------- libnymea-app/devices.h | 4 +- libnymea-app/devicesproxy.cpp | 11 ++-- libnymea-app/devicesproxy.h | 3 +- libnymea-app/libnymea-app-core.h | 2 + libnymea-app/thinggroup.cpp | 14 ++--- libnymea-app/thinggroup.h | 2 +- libnymea-app/types/device.h | 8 --- nymea-app/main.cpp | 2 +- nymea-app/nymea-app.pro | 5 ++ nymea-app/resources.qrc | 4 ++ nymea-app/ui/components/BatteryStatusIcon.qml | 28 ++++++++++ .../ui/components/ConnectionStatusIcon.qml | 35 ++++++++++++ nymea-app/ui/components/MainPageTile.qml | 14 ++--- nymea-app/ui/components/MediaControls.qml | 9 ++-- nymea-app/ui/components/SetupStatusIcon.qml | 16 ++++++ nymea-app/ui/delegates/InterfaceTile.qml | 2 +- nymea-app/ui/delegates/ThingDelegate.qml | 5 +- .../ui/devicelistpages/DeviceListPageBase.qml | 20 +++---- .../devicelistpages/MediaDeviceListPage.qml | 43 ++++++++------- .../devicelistpages/SensorsDeviceListPage.qml | 31 ++++++----- .../SmartMeterDeviceListPage.qml | 34 ++++++------ nymea-app/ui/devicepages/DevicePageBase.qml | 53 +++++++++++-------- nymea-app/ui/utils/NymeaUtils.qml | 18 +++++++ 24 files changed, 272 insertions(+), 138 deletions(-) create mode 100644 nymea-app/ui/components/BatteryStatusIcon.qml create mode 100644 nymea-app/ui/components/ConnectionStatusIcon.qml create mode 100644 nymea-app/ui/components/SetupStatusIcon.qml create mode 100644 nymea-app/ui/utils/NymeaUtils.qml diff --git a/libnymea-app/devices.cpp b/libnymea-app/devices.cpp index 0d8225cf..6839e11b 100644 --- a/libnymea-app/devices.cpp +++ b/libnymea-app/devices.cpp @@ -40,46 +40,52 @@ Devices::Devices(QObject *parent) : QList Devices::devices() { - return m_devices; + return m_things; } Device *Devices::get(int index) const { - if (index < 0 || index >= m_devices.count()) { + if (index < 0 || index >= m_things.count()) { return nullptr; } - return m_devices.at(index); + return m_things.at(index); } -Device *Devices::getDevice(const QUuid &deviceId) const +Device *Devices::getThing(const QUuid &thingId) const { - foreach (Device *device, m_devices) { - if (device->id() == deviceId) { - return device; + foreach (Device *thing, m_things) { + if (thing->id() == thingId) { + return thing; } } return nullptr; } +Device *Devices::getDevice(const QUuid &deviceId) const +{ + return getThing(deviceId); +} + int Devices::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) - return m_devices.count(); + return m_things.count(); } QVariant Devices::data(const QModelIndex &index, int role) const { - if (index.row() < 0 || index.row() >= m_devices.count()) + if (index.row() < 0 || index.row() >= m_things.count()) return QVariant(); - Device *thing = m_devices.at(index.row()); + Device *thing = m_things.at(index.row()); switch (role) { case RoleName: return thing->name(); case RoleId: return thing->id().toString(); case RoleDeviceClass: - return thing->deviceClassId().toString(); + case RoleThingClass: + return thing->thingClassId().toString(); case RoleParentDeviceId: return thing->parentDeviceId().toString(); case RoleSetupStatus: @@ -97,22 +103,22 @@ QVariant Devices::data(const QModelIndex &index, int role) const void Devices::addDevice(Device *device) { device->setParent(this); - beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count()); + beginInsertRows(QModelIndex(), m_things.count(), m_things.count()); // qDebug() << "Devices: add device" << device->name(); - m_devices.append(device); + m_things.append(device); endInsertRows(); connect(device, &Device::nameChanged, this, [device, this]() { - int idx = m_devices.indexOf(device); + int idx = m_things.indexOf(device); if (idx < 0) return; emit dataChanged(index(idx), index(idx), {RoleName}); }); connect(device, &Device::setupStatusChanged, this, [device, this]() { - int idx = m_devices.indexOf(device); + int idx = m_things.indexOf(device); if (idx < 0) return; emit dataChanged(index(idx), index(idx), {RoleSetupStatus, RoleSetupDisplayMessage}); }); connect(device->states(), &States::dataChanged, this, [device, this]() { - int idx = m_devices.indexOf(device); + int idx = m_things.indexOf(device); if (idx < 0) return; emit dataChanged(index(idx), index(idx)); }); @@ -122,10 +128,10 @@ void Devices::addDevice(Device *device) void Devices::removeDevice(Device *device) { - int index = m_devices.indexOf(device); + int index = m_things.indexOf(device); beginRemoveRows(QModelIndex(), index, index); qDebug() << "Devices: removed device" << device->name(); - m_devices.takeAt(index)->deleteLater(); + m_things.takeAt(index)->deleteLater(); endRemoveRows(); emit countChanged(); emit thingRemoved(device); @@ -134,8 +140,8 @@ void Devices::removeDevice(Device *device) void Devices::clearModel() { beginResetModel(); - qDeleteAll(m_devices); - m_devices.clear(); + qDeleteAll(m_things); + m_things.clear(); endResetModel(); emit countChanged(); } @@ -146,6 +152,7 @@ QHash Devices::roleNames() const roles[RoleName] = "name"; roles[RoleId] = "id"; roles[RoleDeviceClass] = "deviceClassId"; + roles[RoleThingClass] = "thingClassId"; roles[RoleParentDeviceId] = "parentDeviceId"; roles[RoleSetupStatus] = "setupStatus"; roles[RoleSetupDisplayMessage] = "setupDisplayMessage"; diff --git a/libnymea-app/devices.h b/libnymea-app/devices.h index 578d15e3..7c4cf08b 100644 --- a/libnymea-app/devices.h +++ b/libnymea-app/devices.h @@ -46,6 +46,7 @@ public: RoleId, RoleParentDeviceId, RoleDeviceClass, + RoleThingClass, RoleSetupStatus, RoleSetupDisplayMessage, RoleInterfaces, @@ -58,6 +59,7 @@ public: QList devices(); Q_INVOKABLE Device *get(int index) const; + Q_INVOKABLE Device *getThing(const QUuid &thingId) const; Q_INVOKABLE Device *getDevice(const QUuid &deviceId) const; int rowCount(const QModelIndex & parent = QModelIndex()) const; @@ -77,7 +79,7 @@ signals: void thingRemoved(Device *device); private: - QList m_devices; + QList m_things; }; diff --git a/libnymea-app/devicesproxy.cpp b/libnymea-app/devicesproxy.cpp index e73d192a..e18ded4e 100644 --- a/libnymea-app/devicesproxy.cpp +++ b/libnymea-app/devicesproxy.cpp @@ -329,14 +329,19 @@ Device *DevicesProxy::get(int index) const } Device *DevicesProxy::getDevice(const QUuid &deviceId) const +{ + return getThing(deviceId); +} + +Device *DevicesProxy::getThing(const QUuid &thingId) const { Devices *d = qobject_cast(sourceModel()); if (d) { - return d->getDevice(deviceId); + return d->getThing(thingId); } DevicesProxy *dp = qobject_cast(sourceModel()); if (dp) { - return dp->getDevice(deviceId); + return dp->getThing(thingId); } return nullptr; } @@ -442,7 +447,7 @@ bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_pa } if (m_filterSetupFailed) { - if (device->setupStatus() != Device::DeviceSetupStatusFailed) { + if (device->setupStatus() != Device::ThingSetupStatusFailed) { return false; } } diff --git a/libnymea-app/devicesproxy.h b/libnymea-app/devicesproxy.h index a3ed9ef2..7ade3750 100644 --- a/libnymea-app/devicesproxy.h +++ b/libnymea-app/devicesproxy.h @@ -124,7 +124,8 @@ public: void setGroupByInterface(bool groupByInterface); Q_INVOKABLE Device *get(int index) const; - Q_INVOKABLE Device *getDevice(const QUuid &deviceId) const; + Q_INVOKABLE Device *getDevice(const QUuid &deviceId) const; + Q_INVOKABLE Device *getThing(const QUuid &thingId) const; signals: void engineChanged(); diff --git a/libnymea-app/libnymea-app-core.h b/libnymea-app/libnymea-app-core.h index 66518bca..4345c008 100644 --- a/libnymea-app/libnymea-app-core.h +++ b/libnymea-app/libnymea-app-core.h @@ -187,6 +187,8 @@ void registerQmlTypes() { qmlRegisterType(uri, 1, 0, "InterfacesModel"); qmlRegisterType(uri, 1, 0, "InterfacesSortModel"); + qmlRegisterUncreatableType(uri, 1, 0, "ThingClass", "Can't create this in QML. Get it from the ThingClasses."); + qmlRegisterUncreatableType(uri, 1, 0, "ThingClasses", "Can't create this in QML. Get it from the ThingManager."); qmlRegisterUncreatableType(uri, 1, 0, "DeviceClass", "Can't create this in QML. Get it from the DeviceClasses."); qmlRegisterUncreatableType(uri, 1, 0, "DeviceClasses", "Can't create this in QML. Get it from the DeviceManager."); qmlRegisterType(uri, 1, 0, "DeviceClassesProxy"); diff --git a/libnymea-app/thinggroup.cpp b/libnymea-app/thinggroup.cpp index b3d78801..6659ed4b 100644 --- a/libnymea-app/thinggroup.cpp +++ b/libnymea-app/thinggroup.cpp @@ -37,7 +37,7 @@ ThingGroup::ThingGroup(DeviceManager *deviceManager, DeviceClass *deviceClass, DevicesProxy *devices, QObject *parent): Device(deviceManager, deviceClass, QUuid::createUuid(), parent), m_thingManager(deviceManager), - m_devices(devices) + m_things(devices) { deviceClass->setParent(this); @@ -52,7 +52,7 @@ ThingGroup::ThingGroup(DeviceManager *deviceManager, DeviceClass *deviceClass, D syncStates(); setName(deviceClass->displayName()); - connect(devices, &DevicesProxy::dataChanged, this, [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles){ + connect(devices, &DevicesProxy::dataChanged, this, [this](const QModelIndex &/*topLeft*/, const QModelIndex &/*bottomRight*/, const QVector &/*roles*/){ syncStates(); }); @@ -77,9 +77,9 @@ int ThingGroup::executeAction(const QString &actionName, const QVariantList &par QList pendingIds; qDebug() << "Execute action for group:" << this; - for (int i = 0; i < m_devices->rowCount(); i++) { - Device *device = m_devices->get(i); - if (device->setupStatus() != Device::DeviceSetupStatusComplete) { + for (int i = 0; i < m_things->rowCount(); i++) { + Device *device = m_things->get(i); + if (device->setupStatus() != Device::ThingSetupStatusComplete) { continue; } ActionType *actionType = device->thingClass()->actionTypes()->findByName(actionName); @@ -118,8 +118,8 @@ void ThingGroup::syncStates() QVariant value; int count = 0; - for (int j = 0; j < m_devices->rowCount(); j++) { - Device *d = m_devices->get(j); + for (int j = 0; j < m_things->rowCount(); j++) { + Device *d = m_things->get(j); // Skip things that don't have the required state StateType *ds = d->thingClass()->stateTypes()->findByName(stateType->name()); if (!ds) { diff --git a/libnymea-app/thinggroup.h b/libnymea-app/thinggroup.h index 59473b59..1da38cdd 100644 --- a/libnymea-app/thinggroup.h +++ b/libnymea-app/thinggroup.h @@ -54,7 +54,7 @@ signals: private: DeviceManager* m_thingManager = nullptr; - DevicesProxy* m_devices = nullptr; + DevicesProxy* m_things = nullptr; int m_idCounter = 0; QHash> m_pendingActions; diff --git a/libnymea-app/types/device.h b/libnymea-app/types/device.h index d0189832..d6bc3f33 100644 --- a/libnymea-app/types/device.h +++ b/libnymea-app/types/device.h @@ -59,14 +59,6 @@ class Device : public QObject Q_PROPERTY(DeviceClass *thingClass READ thingClass CONSTANT) public: - enum DeviceSetupStatus { - DeviceSetupStatusNone, - DeviceSetupStatusInProgress, - DeviceSetupStatusComplete, - DeviceSetupStatusFailed - }; - Q_ENUM(DeviceSetupStatus) - enum ThingSetupStatus { ThingSetupStatusNone, ThingSetupStatusInProgress, diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index e021a099..d9586b38 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -125,8 +125,8 @@ int main(int argc, char *argv[]) PushNotifications::instance()->connectClient(); qmlRegisterSingletonType("Nymea", 1, 0, "PushNotifications", PushNotifications::pushNotificationsProvider); - qmlRegisterSingletonType("Nymea", 1, 0, "AppLogController", AppLogController::appLogControllerProvider); + qmlRegisterSingletonType(QUrl("qrc:///ui/utils/NymeaUtils.qml"), "Nymea", 1, 0, "NymeaUtils" ); #ifdef BRANDING engine->rootContext()->setContextProperty("appBranding", BRANDING); diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 4be2cba9..801fa05c 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -160,3 +160,8 @@ BR=$$BRANDING target.path = /usr/bin INSTALLS += target + +contains(ANDROID_TARGET_ARCH,) { + ANDROID_ABIS = \ + armeabi-v7a +} diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index f1d2d196..8ff704a5 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -218,5 +218,9 @@ ui/mainviews/MediaView.qml ui/components/ShuffleRepeatVolumeControl.qml ui/components/MediaBrowser.qml + ui/utils/NymeaUtils.qml + ui/components/ConnectionStatusIcon.qml + ui/components/BatteryStatusIcon.qml + ui/components/SetupStatusIcon.qml diff --git a/nymea-app/ui/components/BatteryStatusIcon.qml b/nymea-app/ui/components/BatteryStatusIcon.qml new file mode 100644 index 00000000..a3e8af5c --- /dev/null +++ b/nymea-app/ui/components/BatteryStatusIcon.qml @@ -0,0 +1,28 @@ +import QtQuick 2.9 +import Nymea 1.0 + +ColorIcon { + id: root + + property Thing thing: null + + readonly property bool hasBattery: batteryCriticalState !== null + readonly property bool hasBatteryLevel: batteryLevelState !== null + readonly property bool isCritical: batteryCriticalState && batteryCriticalState.value === true + readonly property int batteryLevel: batteryLevelState ? batteryLevelState.value : 0 + + readonly property State batteryCriticalState: thing.stateByName("batteryCritical") + readonly property State batteryLevelState: thing.stateByName("batteryLevel") + + name: { + if (!hasBatteryLevel) { + if (isCritical) { + return "../images/battery/battery-020.svg" + } + return "../images/battery/battery-100.svg" + } + + var rounded = Math.round(batteryLevel / 10) * 10 + return "../images/battery/battery-" + NymeaUtils.pad(rounded, 3) + } +} diff --git a/nymea-app/ui/components/ConnectionStatusIcon.qml b/nymea-app/ui/components/ConnectionStatusIcon.qml new file mode 100644 index 00000000..925f734b --- /dev/null +++ b/nymea-app/ui/components/ConnectionStatusIcon.qml @@ -0,0 +1,35 @@ +import QtQuick 2.9 +import Nymea 1.0 + +ColorIcon { + id: root + + property Thing thing: null + + readonly property bool isConnected: connectedState === null || connectedState.value === true + readonly property bool isWireless: thing.thingClass.interfaces.indexOf("wirelessconnectable") >= 0 + readonly property bool hasSignalStrength: signalStrengthState !== null + + readonly property State connectedState: thing.stateByName("connected") + readonly property State signalStrengthState: thing.stateByName("signalStrength") + + name: { + if (!isWireless) { + return connectedState && connectedState.value === true ? "../images/network-wired.svg" : "../images/network-wired-offline.svg" + } + if (connectedState && connectedState.value === false) { + return "../images/network-wifi-offline.svg" + } + + if (signalStrengthState && signalStrengthState.value === -1) { + return "../images/network-wifi.svg" + } + + return "../images/nm-signal-" + NymeaUtils.pad(Math.round(signalStrengthState.value * 4 / 100) * 25, 2) + ".svg" + } + + color: connectedState && connectedState.value === false + ? "red" + : signalStrengthState && signalStrengthState.value < 20 + ? "orange" : keyColor +} diff --git a/nymea-app/ui/components/MainPageTile.qml b/nymea-app/ui/components/MainPageTile.qml index 0f1f708f..17b9ebf7 100644 --- a/nymea-app/ui/components/MainPageTile.qml +++ b/nymea-app/ui/components/MainPageTile.qml @@ -44,8 +44,8 @@ Item { property string text property bool disconnected: false property bool isWireless: false - property int signalStrength: -1 - property int setupStatus: Device.DeviceSetupStatusNone + property int signalStrength: 0 + property int setupStatus: Thing.ThingSetupStatusNone property bool batteryCritical: false property alias contentItem: innerContent.children @@ -153,20 +153,20 @@ Item { width: height name: root.isWireless ? "../images/network-wifi-offline.svg" : "../images/network-wired-offline.svg" color: root.disconnected ? "red" : "orange" - visible: root.setupStatus == Device.DeviceSetupStatusComplete && (root.disconnected || (root.signalStrength >= 0 && root.signalStrength < 10)) + visible: root.setupStatus == Thing.ThingSetupStatusComplete && (root.disconnected || (root.isWireless && root.signalStrength < 20)) } ColorIcon { height: app.iconSize / 2 width: height - name: root.setupStatus === Device.DeviceSetupStatusFailed ? "../images/dialog-warning-symbolic.svg" : "../images/settings.svg" - color: root.setupStatus === Device.DeviceSetupStatusFailed ? "red" : keyColor - visible: root.setupStatus === Device.DeviceSetupStatusFailed || root.setupStatus === Device.DeviceSetupStatusInProgress + name: root.setupStatus === Thing.ThingSetupStatusFailed ? "../images/dialog-warning-symbolic.svg" : "../images/settings.svg" + color: root.setupStatus === Thing.ThingSetupStatusFailed ? "red" : keyColor + visible: root.setupStatus === Thing.ThingSetupStatusFailed || root.setupStatus === Thing.ThingSetupStatusInProgress } ColorIcon { height: app.iconSize / 2 width: height name: "../images/battery/battery-010.svg" - visible: root.batteryCritical + visible: root.setupStatus == Thing.ThingSetupStatusComplete && root.batteryCritical } } } diff --git a/nymea-app/ui/components/MediaControls.qml b/nymea-app/ui/components/MediaControls.qml index 27eaa01b..3f1dcff6 100644 --- a/nymea-app/ui/components/MediaControls.qml +++ b/nymea-app/ui/components/MediaControls.qml @@ -41,8 +41,7 @@ RowLayout { property Thing thing: null property int iconSize: app.iconSize * 1.5 - readonly property StateType playbackStateType: thing ? thing.thingClass.stateTypes.findByName("playbackStatus") : null - readonly property State playbackState: playbackStateType ? thing.states.getState(playbackStateType.id) : null + readonly property State playbackState: thing.stateByName("playbackStatus") function executeAction(actionName, params) { if (params === undefined) { @@ -58,7 +57,7 @@ RowLayout { Layout.preferredWidth: height imageSource: "../images/media-skip-backward.svg" longpressImageSource: "../images/media-seek-backward.svg" - enabled: root.playbackState.value !== "Stopped" + enabled: root.playbackState && root.playbackState.value !== "Stopped" opacity: enabled ? 1 : .5 repeat: true @@ -75,7 +74,7 @@ RowLayout { Layout.preferredWidth: height imageSource: root.playbackState && root.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg" longpressImageSource: "../images/media-playback-stop.svg" - longpressEnabled: root.playbackState.value !== "Stopped" + longpressEnabled: root.playbackState && root.playbackState.value !== "Stopped" onClicked: { if (root.playbackState.value === "Playing") { @@ -95,7 +94,7 @@ RowLayout { Layout.preferredWidth: height imageSource: "../images/media-skip-forward.svg" longpressImageSource: "../images/media-seek-forward.svg" - enabled: root.playbackState.value !== "Stopped" + enabled: root.playbackState && root.playbackState.value !== "Stopped" opacity: enabled ? 1 : .5 repeat: true onClicked: { diff --git a/nymea-app/ui/components/SetupStatusIcon.qml b/nymea-app/ui/components/SetupStatusIcon.qml new file mode 100644 index 00000000..9d780de3 --- /dev/null +++ b/nymea-app/ui/components/SetupStatusIcon.qml @@ -0,0 +1,16 @@ +import QtQuick 2.9 +import Nymea 1.0 + +ColorIcon { + id: root + + property Thing thing: null + + readonly property int setupStatus: thing.setupStatus + readonly property bool setupInProgress: setupStatus == Thing.ThingSetupStatusInProgress + readonly property bool setupFailed: setupStatus == Thing.ThingSetupStatusFailed + + name: setupFailed ? "../images/dialog-warning-symbolic.svg" + : setupInProgress ? "../images/settings.svg" : "../images/tick.svg" + color: setupFailed ? "red" : keyColor +} diff --git a/nymea-app/ui/delegates/InterfaceTile.qml b/nymea-app/ui/delegates/InterfaceTile.qml index 888c8b94..f1ed5c0f 100644 --- a/nymea-app/ui/delegates/InterfaceTile.qml +++ b/nymea-app/ui/delegates/InterfaceTile.qml @@ -43,7 +43,7 @@ MainPageTile { disconnected: devicesSubProxyConnectables.count > 0 isWireless: devicesSubProxyConnectables.count > 0 && devicesSubProxyConnectables.get(0).thingClass.interfaces.indexOf("wirelessconnectable") >= 0 batteryCritical: devicesSubProxyBattery.count > 0 - setupStatus: thingsSubProxySetupFailure.count > 0 ? Device.DeviceSetupStatusFailed : Device.DeviceSetupStatusComplete + setupStatus: thingsSubProxySetupFailure.count > 0 ? Thing.ThingSetupStatusFailed : Thing.ThingSetupStatusComplete property Interface iface: null property alias filterTagId: devicesProxy.filterTagId diff --git a/nymea-app/ui/delegates/ThingDelegate.qml b/nymea-app/ui/delegates/ThingDelegate.qml index 812b40bd..adfa6ae5 100644 --- a/nymea-app/ui/delegates/ThingDelegate.qml +++ b/nymea-app/ui/delegates/ThingDelegate.qml @@ -46,7 +46,8 @@ NymeaListItemDelegate { : thing.setupStatus == Thing.ThingSetupStatusInProgress ? "../images/settings.svg" : disconnected - ? "../images/dialog-warning-symbolic.svg" + ? isWireless + ? "../images/network-wifi-offline.svg" : "../images/network-wired-offline.svg" : "" tertiaryIconColor: thing.setupStatus == Thing.ThingSetupStatusInProgress ? iconKeyColor : "red" @@ -63,5 +64,7 @@ NymeaListItemDelegate { readonly property State connectedState: connectedStateType ? thing.states.getState(connectedStateType.id) : null readonly property bool disconnected: connectedState && connectedState.value === false ? true : false + readonly property bool isWireless: root.thing.thingClass.interfaces.indexOf("wirelessconnectable") >= 0 + } diff --git a/nymea-app/ui/devicelistpages/DeviceListPageBase.qml b/nymea-app/ui/devicelistpages/DeviceListPageBase.qml index edde3bb6..0cd7853a 100644 --- a/nymea-app/ui/devicelistpages/DeviceListPageBase.qml +++ b/nymea-app/ui/devicelistpages/DeviceListPageBase.qml @@ -38,32 +38,32 @@ import "../components" Page { id: root - property alias shownInterfaces: devicesProxyInternal.shownInterfaces - property alias hiddenInterfaces: devicesProxyInternal.hiddenInterfaces - property alias filterTagId: devicesProxyInternal.filterTagId + property alias shownInterfaces: thingsProxyInternal.shownInterfaces + property alias hiddenInterfaces: thingsProxyInternal.hiddenInterfaces + property alias filterTagId: thingsProxyInternal.filterTagId Component.onCompleted: { - if (devicesProxyInternal.count === 1) { + if (thingsProxyInternal.count === 1) { enterPage(0, true) } } - property var devicesProxy: devicesProxyInternal + property var devicesProxy: thingsProxyInternal + property var thingsProxy: thingsProxyInternal function enterPage(index, replace) { - var device = devicesProxy.get(index); - var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + var thing = thingsProxy.get(index); var page = app.interfaceListToDevicePage(root.shownInterfaces); // var page = "GenericDevicePage.qml"; if (replace) { - pageStack.replace(Qt.resolvedUrl("../devicepages/" + page), {device: devicesProxy.get(index)}) + pageStack.replace(Qt.resolvedUrl("../devicepages/" + page), {thing: thingsProxy.get(index)}) } else { - pageStack.push(Qt.resolvedUrl("../devicepages/" + page), {device: devicesProxy.get(index)}) + pageStack.push(Qt.resolvedUrl("../devicepages/" + page), {thing: thingsProxy.get(index)}) } } DevicesProxy { - id: devicesProxyInternal + id: thingsProxyInternal engine: _engine } } diff --git a/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml b/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml index abc251aa..987e2735 100644 --- a/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml @@ -64,14 +64,13 @@ DeviceListPageBase { property bool inline: width > 500 - property Device device: devicesProxy.getDevice(model.id) - property DeviceClass deviceClass: device.deviceClass + property Thing thing: thingsProxy.getThing(model.id) - readonly property StateType playbackStateType: deviceClass.stateTypes.findByName("playbackStatus") - readonly property State playbackState: playbackStateType ? device.states.getState(playbackStateType.id) : null + readonly property StateType playbackStateType: thing.thingClass.stateTypes.findByName("playbackStatus") + readonly property State playbackState: thing.stateByName("playbackStatus") - readonly property StateType playerTypeStateType: deviceClass.stateTypes.findByName("playerType") - readonly property State playerTypeState: playerTypeStateType ? device.states.getState(playerTypeStateType.id) : null + readonly property StateType playerTypeStateType: thing.thingClass.stateTypes.findByName("playerType") + readonly property State playerTypeState: thing.stateByName("playerType") bottomPadding: index === root.devicesProxy.count - 1 ? topPadding : 0 contentItem: Pane { @@ -100,18 +99,23 @@ DeviceListPageBase { text: model.name elide: Text.ElideRight } - ColorIcon { + BatteryStatusIcon { Layout.preferredHeight: app.iconSize * .5 Layout.preferredWidth: height - name: "../images/battery/battery-020.svg" - visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true + thing: itemDelegate.thing + visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (hasBatteryLevel || isCritical) } - ColorIcon { + ConnectionStatusIcon { Layout.preferredHeight: app.iconSize * .5 Layout.preferredWidth: height - name: "../images/dialog-warning-symbolic.svg" - visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false - color: "red" + thing: itemDelegate.thing + visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (hasSignalStrength || !isConnected) + } + SetupStatusIcon { + Layout.preferredHeight: app.iconSize * .5 + Layout.preferredWidth: height + thing: itemDelegate.thing + visible: setupFailed || setupInProgress } } @@ -124,28 +128,28 @@ DeviceListPageBase { Layout.fillWidth: true text: itemDelegate.playbackState.value === "Stopped" ? qsTr("No playback") - : itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("title").id).value + : itemDelegate.thing.stateByName("title").value horizontalAlignment: Text.AlignHCenter // font.pixelSize: app.largeFont elide: Text.ElideRight } Label { Layout.fillWidth: true - text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("artist").id).value + text: itemDelegate.thing.stateByName("artist").value font.pixelSize: app.smallFont horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight } Label { Layout.fillWidth: true - text: itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("collection").id).value + text: itemDelegate.thing.stateByName("collection").value horizontalAlignment: Text.AlignHCenter font.pixelSize: app.smallFont elide: Text.ElideRight } MediaControls { - visible: itemDelegate.deviceClass.interfaces.indexOf("mediacontroller") >= 0 - thing: itemDelegate.device + visible: itemDelegate.thing.thingClass.interfaces.indexOf("mediacontroller") >= 0 + thing: itemDelegate.thing } } Item { @@ -159,8 +163,7 @@ DeviceListPageBase { id: artworkImage width: artworkImage.sourceSize.width * height / artworkImage.sourceSize.height anchors { top: parent.top; right: parent.right; bottom: parent.bottom } - readonly property StateType artworkStateType: device ? device.deviceClass.stateTypes.findByName("artwork") : null - readonly property State artworkState: artworkStateType ? device.states.getState(artworkStateType.id) : null + readonly property State artworkState: thing.stateByName("artwork") source: artworkState ? artworkState.value : "" } Rectangle { diff --git a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml index d5b1e946..e87b31ab 100644 --- a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml @@ -45,7 +45,7 @@ DeviceListPageBase { ListView { anchors.fill: parent - model: root.devicesProxy + model: root.thingsProxy delegate: ItemDelegate { id: itemDelegate @@ -53,8 +53,7 @@ DeviceListPageBase { property bool inline: width > 500 - property Device device: devicesProxy.getDevice(model.id) - property DeviceClass deviceClass: device.deviceClass + property Thing thing: thingsProxy.getThing(model.id) bottomPadding: index === ListView.view.count - 1 ? topPadding : 0 contentItem: Pane { @@ -82,21 +81,25 @@ DeviceListPageBase { text: model.name elide: Text.ElideRight } - ColorIcon { + BatteryStatusIcon { Layout.preferredHeight: app.iconSize * .5 Layout.preferredWidth: height - name: "../images/battery/battery-020.svg" - visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true + thing: itemDelegate.thing + visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (hasBatteryLevel || isCritical) } - ColorIcon { + ConnectionStatusIcon { Layout.preferredHeight: app.iconSize * .5 Layout.preferredWidth: height - name: "../images/dialog-warning-symbolic.svg" - visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false - color: "red" + thing: itemDelegate.thing + visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (isWireless || !isConnected) + } + SetupStatusIcon { + Layout.preferredHeight: app.iconSize * .5 + Layout.preferredWidth: height + thing: itemDelegate.thing + visible: setupFailed || setupInProgress } } - } GridLayout { id: dataGrid @@ -121,11 +124,11 @@ DeviceListPageBase { delegate: RowLayout { id: sensorValueDelegate - visible: itemDelegate.deviceClass.interfaces.indexOf(model.interfaceName) >= 0 + visible: itemDelegate.thing.thingClass.interfaces.indexOf(model.interfaceName) >= 0 Layout.preferredWidth: contentItem.width / dataGrid.columns - property StateType stateType: itemDelegate.deviceClass.stateTypes.findByName(model.stateName) - property State stateValue: stateType ? itemDelegate.device.states.getState(stateType.id) : null + property StateType stateType: itemDelegate.thing.thingClass.stateTypes.findByName(model.stateName) + property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null ColorIcon { Layout.preferredHeight: app.iconSize * .8 diff --git a/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml index c192d706..8cba58c8 100644 --- a/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml @@ -53,8 +53,7 @@ DeviceListPageBase { property bool inline: width > 500 - property Device device: devicesProxy.getDevice(model.id) - property DeviceClass deviceClass: device.deviceClass + property Thing thing: thingsProxy.getThing(model.id) bottomPadding: index === ListView.view.count - 1 ? topPadding : 0 contentItem: Pane { @@ -82,18 +81,23 @@ DeviceListPageBase { text: model.name elide: Text.ElideRight } - ColorIcon { + BatteryStatusIcon { Layout.preferredHeight: app.iconSize * .5 Layout.preferredWidth: height - name: "../images/battery/battery-020.svg" - visible: itemDelegate.deviceClass.interfaces.indexOf("battery") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("batteryCritical").id).value === true + thing: itemDelegate.thing + visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (isCritical || hasBatteryLevel) } - ColorIcon { + ConnectionStatusIcon { Layout.preferredHeight: app.iconSize * .5 Layout.preferredWidth: height - name: "../images/dialog-warning-symbolic.svg" - visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false - color: "red" + thing: itemDelegate.thing + visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (isWireless || !isConnected) + } + SetupStatusIcon { + Layout.preferredHeight: app.iconSize * .5 + Layout.preferredWidth: height + thing: itemDelegate.thing + visible: setupFailed || setupInProgress } } @@ -105,18 +109,18 @@ DeviceListPageBase { Repeater { model: ListModel { Component.onCompleted: { - if (itemDelegate.deviceClass.interfaces.indexOf("smartmeterproducer") >= 0) { + if (itemDelegate.thing.thingClass.interfaces.indexOf("smartmeterproducer") >= 0) { append( {interfaceName: "smartmeterproducer", stateName: "totalEnergyProduced" }) } - if (itemDelegate.deviceClass.interfaces.indexOf("smartmeterconsumer") >= 0) { + if (itemDelegate.thing.thingClass.interfaces.indexOf("smartmeterconsumer") >= 0) { append( {interfaceName: "smartmeterconsumer", stateName: "totalEnergyConsumed" }) } var added = false; - if (itemDelegate.deviceClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0) { + if (itemDelegate.thing.thingClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0) { append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"}); added = true; } - if (!added && itemDelegate.deviceClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) { + if (!added && itemDelegate.thing.thingClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) { append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"}); } } @@ -126,8 +130,8 @@ DeviceListPageBase { id: sensorValueDelegate Layout.preferredWidth: contentItem.width / dataGrid.columns - property var stateType: itemDelegate.deviceClass.stateTypes.findByName(model.stateName) - property var stateValue: stateType ? itemDelegate.device.states.getState(stateType.id) : null + property StateType stateType: itemDelegate.thing.thingClass.stateTypes.findByName(model.stateName) + property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null ColorIcon { Layout.preferredHeight: app.iconSize * .8 diff --git a/nymea-app/ui/devicepages/DevicePageBase.qml b/nymea-app/ui/devicepages/DevicePageBase.qml index b6be9119..b4253f75 100644 --- a/nymea-app/ui/devicepages/DevicePageBase.qml +++ b/nymea-app/ui/devicepages/DevicePageBase.qml @@ -36,10 +36,11 @@ import "../components" Page { id: root - property Device device: null - readonly property DeviceClass deviceClass: device.deviceClass + property Thing thing: null + readonly property ThingClass thingClass: thing.thingClass - readonly property Device thing: device + property alias device: root.thing + property alias deviceClass: root.thingClass property bool showLogsButton: true property bool showDetailsButton: true @@ -51,7 +52,7 @@ Page { signal backPressed() header: NymeaHeader { - text: device.name + text: root.thing.name onBackPressed: { root.backPressed(); if (root.popStackOnBackButton) { @@ -61,7 +62,7 @@ Page { HeaderButton { imageSource: "../images/folder-symbolic.svg" - visible: root.deviceClass.browsable && root.showBrowserButton + visible: root.thingClass.browsable && root.showBrowserButton onClicked: { pageStack.push(Qt.resolvedUrl("DeviceBrowserPage.qml"), {device: root.device}) } @@ -229,11 +230,11 @@ Page { visible: setupInProgress || setupFailure || batteryState !== null || (connectedState !== null && connectedState.value === false) height: visible ? contentRow.implicitHeight : 0 anchors { left: parent.left; top: parent.top; right: parent.right } - property bool setupInProgress: device.setupStatus == Device.DeviceSetupStatusInProgress - property bool setupFailure: device.setupStatus == Device.DeviceSetupStatusFailed - property var batteryState: deviceClass.interfaces.indexOf("battery") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("batteryLevel").id) : null - property var batteryCriticalState: deviceClass.interfaces.indexOf("battery") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("batteryCritical").id) : null - property var connectedState: deviceClass.interfaces.indexOf("connectable") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("connected").id) : null + property bool setupInProgress: root.thing.setupStatus == Thing.ThingSetupStatusInProgress + property bool setupFailure: root.thing.setupStatus == Thing.ThingSetupStatusFailed + property State batteryState: root.thing.stateByName("batteryLevel") + property State batteryCriticalState: root.thing.stateByName("batteryCritical") + property State connectedState: root.thing.stateByName("connected") property bool alertState: setupFailure || (connectedState !== null && connectedState.value === false) || (batteryCriticalState !== null && batteryCriticalState.value === true) @@ -261,22 +262,28 @@ Page { color: "white" } - ColorIcon { - height: app.iconSize / 2 - width: height - visible: infoPane.setupInProgress || infoPane.setupFailure || (infoPane.connectedState !== null && infoPane.connectedState.value === false) - color: "white" - name: infoPane.setupInProgress ? - "../images/settings.svg" - : "../images/dialog-warning-symbolic.svg" - } - - ColorIcon { + BatteryStatusIcon { height: app.iconSize / 2 width: height * 1.23 - name: infoPane.batteryState !== null ? "../images/battery/battery-" + ("00" + (Math.floor(infoPane.batteryState.value / 10) * 10)).slice(-3) + ".svg" : "" - visible: infoPane.batteryState !== null + thing: root.thing color: infoPane.alertState ? "white" : keyColor + visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (hasBatteryLevel || isCritical) + } + + ConnectionStatusIcon { + height: app.iconSize / 2 + width: height + thing: root.thing + color: infoPane.alertState ? "white" : keyColor + visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (hasSignalStrength || !isConnected) + } + + SetupStatusIcon { + height: app.iconSize / 2 + width: height + thing: root.thing + color: infoPane.alertState ? "white" : keyColor + visible: setupFailed || setupInProgress } } } diff --git a/nymea-app/ui/utils/NymeaUtils.qml b/nymea-app/ui/utils/NymeaUtils.qml new file mode 100644 index 00000000..2ed6f93d --- /dev/null +++ b/nymea-app/ui/utils/NymeaUtils.qml @@ -0,0 +1,18 @@ +pragma Singleton +import QtQuick 2.9 + +Item { + id: root + + function pad(num, size) { + var trimmedNum = Math.floor(num) + var decimals = num - trimmedNum + var trimmedStr = "" + trimmedNum + var str = "000000000" + trimmedNum; + str = str.substr(str.length - Math.max(size, trimmedStr.length)); + if (decimals !== 0) { + str += "." + (num - trimmedNum); + } + return str; + } +}