From 0e61ad05653fa32029c3a3659933c028148baadb Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 5 Sep 2020 22:35:04 +0200 Subject: [PATCH] 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; + } +}