diff --git a/libnymea-common/types/paramdescriptors.h b/libnymea-common/types/paramdescriptors.h index bfc9c68b..b3d2d161 100644 --- a/libnymea-common/types/paramdescriptors.h +++ b/libnymea-common/types/paramdescriptors.h @@ -33,7 +33,7 @@ public: QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; - ParamDescriptor* get(int index) const; + Q_INVOKABLE ParamDescriptor* get(int index) const; ParamDescriptor* createNewParamDescriptor() const; void addParamDescriptor(ParamDescriptor* paramDescriptor); diff --git a/mea/devicesproxy.cpp b/mea/devicesproxy.cpp index a8f4ec45..1e5be80c 100644 --- a/mea/devicesproxy.cpp +++ b/mea/devicesproxy.cpp @@ -40,6 +40,7 @@ void DevicesProxy::setDevices(Devices *devices) m_devices = devices; setSourceModel(devices); sort(0); + connect(devices, &Devices::countChanged, this, &DevicesProxy::countChanged); emit devicesChanged(); emit countChanged(); } @@ -104,119 +105,3 @@ bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_pa } return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } - - -DevicesBasicTagsModel::DevicesBasicTagsModel(QObject *parent): - QAbstractListModel(parent) -{ - -} - -int DevicesBasicTagsModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - return m_tags.count(); -} - -QVariant DevicesBasicTagsModel::data(const QModelIndex &index, int role) const -{ - switch (role) { - case RoleTag: - return m_tags.at(index.row()); - case RoleTagLabel: - return DeviceClass::basicTagToString(m_tags.at(index.row())); - } - return QVariant(); -} - -QHash DevicesBasicTagsModel::roleNames() const -{ - QHash roles; - roles.insert(RoleTag, "tag"); - roles.insert(RoleTagLabel, "tagLabel"); - return roles; -} - -Devices *DevicesBasicTagsModel::devices() const -{ - return m_devices; -} - -void DevicesBasicTagsModel::setDevices(Devices *devices) -{ - if (m_devices != devices) { - m_devices = devices; - syncTags(); - - connect(devices, &Devices::rowsInserted, this, &DevicesBasicTagsModel::rowsChanged); - connect(devices, &Devices::rowsRemoved, this, &DevicesBasicTagsModel::rowsChanged); - } -} - -bool DevicesBasicTagsModel::hideSystemTags() const -{ - return m_hideSystemTags; -} - -void DevicesBasicTagsModel::setHideSystemTags(bool hideSystemTags) -{ - if (m_hideSystemTags != hideSystemTags) { - m_hideSystemTags = hideSystemTags; - emit hideSystemTagsChanged(); - syncTags(); - } -} - -QString DevicesBasicTagsModel::basicTagToString(DeviceClass::BasicTag basicTag) const -{ - return DeviceClass::basicTagToString(basicTag); -} - -void DevicesBasicTagsModel::rowsChanged(const QModelIndex &index, int first, int last) -{ - Q_UNUSED(index) - Q_UNUSED(first) - Q_UNUSED(last) - - syncTags(); -} - -void DevicesBasicTagsModel::syncTags() -{ - if (!m_devices) { - return; - } - - QList tagsInSource; - for (int i = 0; i < m_devices->rowCount(); i++) { - DeviceClass *dc = Engine::instance()->deviceManager()->deviceClasses()->getDeviceClass(m_devices->get(i)->deviceClassId()); - foreach (DeviceClass::BasicTag tag, dc->basicTags()) { - if (!tagsInSource.contains(tag)) { - tagsInSource.append(tag); - } - } - } - QList tagsToAdd = tagsInSource; - if (m_hideSystemTags) { - tagsToAdd.removeAll(DeviceClass::BasicTagActuator); - tagsToAdd.removeAll(DeviceClass::BasicTagDevice); - tagsToAdd.removeAll(DeviceClass::BasicTagService); - } - - for (QList::iterator i = m_tags.begin(); i != m_tags.end();) { - if (!tagsInSource.contains(*i)) { - int idx = m_tags.indexOf(*i); - beginRemoveRows(QModelIndex(), idx, idx); - m_tags.takeAt(idx); - endRemoveRows(); - continue; - } - tagsToAdd.removeAll(*i); - ++i; - } - if (!tagsToAdd.isEmpty()) { - beginInsertRows(QModelIndex(), m_tags.count(), m_tags.count() + tagsToAdd.count() - 1); - m_tags.append(tagsToAdd); - endInsertRows(); - } -} diff --git a/mea/devicesproxy.h b/mea/devicesproxy.h index b5d23031..4fe7dcfa 100644 --- a/mea/devicesproxy.h +++ b/mea/devicesproxy.h @@ -29,45 +29,6 @@ #include "devices.h" -class DevicesBasicTagsModel: public QAbstractListModel -{ - Q_OBJECT - Q_PROPERTY(Devices* devices READ devices WRITE setDevices NOTIFY devicesChanged) - Q_PROPERTY(bool hideSystemTags READ hideSystemTags WRITE setHideSystemTags NOTIFY hideSystemTagsChanged) -public: - enum Roles { - RoleTag, - RoleTagLabel - }; - - DevicesBasicTagsModel(QObject *parent = nullptr); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - QHash roleNames() const override; - - Devices* devices() const; - void setDevices(Devices *devices); - - bool hideSystemTags() const; - void setHideSystemTags(bool hideSystemTags); - - Q_INVOKABLE QString basicTagToString(DeviceClass::BasicTag basicTag) const; - -signals: - void devicesChanged(); - void hideSystemTagsChanged(); - -private: - void rowsChanged(const QModelIndex &index, int first, int last); - void syncTags(); - -private: - Devices* m_devices = nullptr; - QList m_tags; - bool m_hideSystemTags = false; -}; - class DevicesProxy : public QSortFilterProxyModel { Q_OBJECT diff --git a/mea/jsonrpc/jsonrpcclient.cpp b/mea/jsonrpc/jsonrpcclient.cpp index 3d54550e..db4b78f2 100644 --- a/mea/jsonrpc/jsonrpcclient.cpp +++ b/mea/jsonrpc/jsonrpcclient.cpp @@ -309,7 +309,7 @@ void JsonRpcClient::dataReceived(const QByteArray &data) int commandId = dataMap.value("id").toInt(); JsonRpcReply *reply = m_replies.take(commandId); if (reply) { - qDebug() << QString("JsonRpc: got response for %1.%2: %3").arg(reply->nameSpace(), reply->method(), QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented))) << reply->callback() << reply->callback(); +// qDebug() << QString("JsonRpc: got response for %1.%2: %3").arg(reply->nameSpace(), reply->method(), QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented))) << reply->callback() << reply->callback(); if (dataMap.value("status").toString() == "unauthorized") { qWarning() << "Something's off with the token"; diff --git a/mea/main.cpp b/mea/main.cpp index 2fa7a3f2..b6f640de 100644 --- a/mea/main.cpp +++ b/mea/main.cpp @@ -115,7 +115,6 @@ int main(int argc, char *argv[]) qmlRegisterUncreatableType(uri, 1, 0, "Device", "Can't create this in QML. Get it from the Devices."); qmlRegisterUncreatableType(uri, 1, 0, "Devices", "Can't create this in QML. Get it from the DeviceManager."); qmlRegisterType(uri, 1, 0, "DevicesProxy"); - qmlRegisterType(uri, 1, 0, "DevicesBasicTagsModel"); qmlRegisterType(uri, 1, 0, "InterfacesModel"); qmlRegisterUncreatableType(uri, 1, 0, "DeviceClass", "Can't create this in QML. Get it from the DeviceClasses."); diff --git a/mea/models/logsmodel.cpp b/mea/models/logsmodel.cpp index 6c7d6973..46cfd08c 100644 --- a/mea/models/logsmodel.cpp +++ b/mea/models/logsmodel.cpp @@ -187,11 +187,19 @@ void LogsModel::newLogEntryReceived(const QVariantMap &data) return; } - beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); QVariantMap entryMap = data; - QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong()); QString deviceId = entryMap.value("deviceId").toString(); + if (!m_deviceId.isNull() && deviceId != m_deviceId) { + return; + } + QString typeId = entryMap.value("typeId").toString(); + if (!m_typeId.isNull() && typeId != m_typeId) { + return; + } + + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(entryMap.value("timestamp").toLongLong()); QMetaEnum sourceEnum = QMetaEnum::fromType(); LogEntry::LoggingSource loggingSource = (LogEntry::LoggingSource)sourceEnum.keyToValue(entryMap.value("source").toByteArray()); QMetaEnum loggingEventTypeEnum = QMetaEnum::fromType(); diff --git a/mea/models/rulesfiltermodel.cpp b/mea/models/rulesfiltermodel.cpp index 94bc3055..97ab2b7c 100644 --- a/mea/models/rulesfiltermodel.cpp +++ b/mea/models/rulesfiltermodel.cpp @@ -26,6 +26,7 @@ void RulesFilterModel::setRules(Rules *rules) setSourceModel(rules); emit rulesChanged(); invalidateFilter(); + emit countChanged(); } } @@ -40,6 +41,7 @@ void RulesFilterModel::setFilterDeviceId(const QString &filterDeviceId) m_filterDeviceId = filterDeviceId; emit filterDeviceIdChanged(); invalidateFilter(); + emit countChanged(); } } diff --git a/mea/models/rulesfiltermodel.h b/mea/models/rulesfiltermodel.h index 645b7a2d..acae1e1d 100644 --- a/mea/models/rulesfiltermodel.h +++ b/mea/models/rulesfiltermodel.h @@ -10,6 +10,7 @@ class Rule; class RulesFilterModel : public QSortFilterProxyModel { Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(Rules* rules READ rules WRITE setRules NOTIFY rulesChanged) Q_PROPERTY(QString filterDeviceId READ filterDeviceId WRITE setFilterDeviceId NOTIFY filterDeviceIdChanged) @@ -27,6 +28,7 @@ public: signals: void rulesChanged(); void filterDeviceIdChanged(); + void countChanged(); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; diff --git a/mea/resources.qrc b/mea/resources.qrc index 53e952ce..2c0dd9ae 100644 --- a/mea/resources.qrc +++ b/mea/resources.qrc @@ -144,5 +144,11 @@ ui/images/edit.svg ui/PushButtonAuthPage.qml ui/images/dialog-error-symbolic.svg + ui/images/send.svg + ui/images/mail-mark-important.svg + ui/devicepages/InputTriggerDevicePage.qml + ui/images/clock-app-symbolic.svg + ui/devicepages/StateLogPage.qml + ui/customviews/GenericTypeLogView.qml diff --git a/mea/rulemanager.cpp b/mea/rulemanager.cpp index e6a3edd3..3c3d5c72 100644 --- a/mea/rulemanager.cpp +++ b/mea/rulemanager.cpp @@ -140,7 +140,7 @@ void RuleManager::getRuleDetailsReply(const QVariantMap ¶ms) qDebug() << "Got rule details for a rule we don't know"; return; } - qDebug() << "got rule details for rule" << ruleMap; +// qDebug() << "got rule details for rule" << ruleMap; parseEventDescriptors(ruleMap.value("eventDescriptors").toList(), rule); parseRuleActions(ruleMap.value("actions").toList(), rule); parseRuleExitActions(ruleMap.value("exitActions").toList(), rule); diff --git a/mea/ui/DevicesPage.qml b/mea/ui/DevicesPage.qml index 846d034e..383463e2 100644 --- a/mea/ui/DevicesPage.qml +++ b/mea/ui/DevicesPage.qml @@ -1,59 +1,238 @@ import QtQuick 2.8 import QtQuick.Controls 2.1 import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.2 import Mea 1.0 import "components" -GridView { - id: interfacesGridView +Item { + id: root + property alias count: interfacesGridView.count + property alias model: interfacesGridView.model - property alias shownInterfaces: interfacesModel.shownInterfaces - property int tilesPerRow: Math.ceil(Math.sqrt(interfacesGridView.count)) + GridView { + id: interfacesGridView + anchors.fill: parent + anchors.margins: app.margins / 2 - model: InterfacesModel { - id: interfacesModel - devices: Engine.deviceManager.devices - } - cellWidth: width / tilesPerRow - cellHeight: height / tilesPerRow - delegate: Item { - width: interfacesGridView.cellWidth - height: interfacesGridView.cellHeight - Pane { - anchors.fill: parent - anchors.margins: app.margins - Material.elevation: 1 - Column { - anchors.centerIn: parent - spacing: app.margins - ColorIcon { - height: app.iconSize * 2 - width: height - color: app.guhAccent - anchors.horizontalCenter: parent.horizontalCenter - name: interfaceToIcon(model.name) - } + readonly property int minTileWidth: 180 + readonly property int minTileHeight: 240 + readonly property int tilesPerRow: root.width / minTileWidth - Label { - text: interfaceToString(model.name).toUpperCase() - anchors.horizontalCenter: parent.horizontalCenter - } - - } - - MouseArea { + model: InterfacesModel { + id: interfacesModel + devices: Engine.deviceManager.devices + } + cellWidth: width / tilesPerRow + cellHeight: Math.max(cellWidth, minTileHeight) + delegate: Item { + width: interfacesGridView.cellWidth + height: interfacesGridView.cellHeight + Pane { anchors.fill: parent - onClicked: { - var page; - switch (model.name) { - case "light": - page = "LightsDeviceListPage.qml" - break; - default: - page = "GenericDeviceListPage.qml" + anchors.margins: app.margins / 2 + Material.elevation: 1 + + Column { + width: parent.width + anchors.centerIn: parent + anchors.verticalCenterOffset: -app.iconSize + spacing: app.margins + ColorIcon { + height: app.iconSize * 2 + width: height + color: app.guhAccent + anchors.horizontalCenter: parent.horizontalCenter + name: interfaceToIcon(model.name) } - pageStack.push(Qt.resolvedUrl("devicelistpages/" + page), {filterInterface: model.name}) + Label { + text: interfaceToString(model.name).toUpperCase() + width: parent.width + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WordWrap + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + var page; + switch (model.name) { + case "light": + page = "LightsDeviceListPage.qml" + break; + default: + page = "GenericDeviceListPage.qml" + } + + pageStack.push(Qt.resolvedUrl("devicelistpages/" + page), {filterInterface: model.name}) + } + } + + DevicesProxy { + id: devicesProxy + devices: Engine.deviceManager.devices + filterInterface: model.name + } + } + + Item { + id: inlineControlPane + anchors { left: parent.left; bottom: parent.bottom; right: parent.right; margins: app.margins / 2 } + height: app.iconSize + app.margins * 2 + Rectangle { + anchors.fill: parent + // color: app.guhAccent + color: "black" + opacity: .05 + } + + Loader { + id: inlineControlLoader + anchors { + fill: parent + leftMargin: app.margins + rightMargin: app.margins + topMargin: app.margins / 2 + bottomMargin: app.margins / 2 + } + sourceComponent: { + switch (model.name) { + case "sensor": + case "weather": + return labelComponent; + + case "light": + case "media": + return buttonComponent + } + } + } + } + + Component { + id: buttonComponent + MouseArea { + onClicked: { + switch (model.name) { + case "light": + for (var i = 0; i < devicesProxy.count; i++) { + var device = devicesProxy.get(i); + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + var actionType = deviceClass.actionTypes.findByName("power"); + + var params = []; + var param1 = {}; + param1["paramTypeId"] = actionType.paramTypes.get(0).id; + param1["value"] = false; + params.push(param1) + Engine.deviceManager.executeAction(device.id, actionType.id, params) + } + break; + case "media": + var device = devicesProxy.get(0) + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + var stateType = deviceClass.stateTypes.findByName("playbackStatus"); + var state = device.states.getState(stateType.id) + + var actionName + switch (state.value) { + case "PLAYING": + actionName = "pause"; + break; + case "PAUSED": + actionName = "play"; + break; + } + var actionTypeId = deviceClass.actionTypes.findByName(actionName).id; + + print("executing", device, device.id, actionTypeId, actionName, deviceClass.actionTypes) + + Engine.deviceManager.executeAction(device.id, actionTypeId) + } + } + + ColumnLayout { + anchors.fill: parent + + Label { + id: label + Layout.fillWidth: true + text: { + switch (model.name) { + case "media": + return devicesProxy.get(0).name; + case "light": + var count = 0; + for (var i = 0; i < devicesProxy.count; i++) { + var device = devicesProxy.get(i); + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + var stateType = deviceClass.stateTypes.findByName("power") + if (device.states.getState(stateType.id).value === true) { + count++; + } + } + return count === 0 ? qsTr("All off") : qsTr("%1 on").arg(count) + } + } + font.pixelSize: app.smallFont + elide: Text.ElideRight + } + ColorIcon { + id: icon + width: app.largeFont + height: width + color: app.guhAccent + anchors.right: parent.right + name: { + switch (model.name) { + case "media": + var device = devicesProxy.get(0) + var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + var stateType = deviceClass.stateTypes.findByName("playbackStatus"); + var state = device.states.getState(stateType.id) + return state.value === "PLAYING" ? "../images/media-playback-pause.svg" : + state.value === "PAUSED" ? "../images/media-playback-start.svg" : + "" + case "light": + return "../images/system-shutdown.svg" + } + } + } + } + } + } + + Component { + id: labelComponent + ColumnLayout { + property var device: devicesProxy.get(0) + property var deviceClass: device ? Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null + property var state: deviceClass ? device.states.getState(deviceClass.stateTypes.findByName("temperature").id) : null + + Label { + text: parent.device.name + font.pixelSize: app.smallFont + Layout.fillWidth: true + elide: Text.ElideRight + } + + Label { + font.pixelSize: app.largeFont + color: app.guhAccent + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + text: { + if (devicesProxy.count > 0) { + var stateName; + // switch (model.name) { + // case "sensor": + // } + return parent.state.value + "°C"; + } + } + } } } } diff --git a/mea/ui/MainPage.qml b/mea/ui/MainPage.qml index 61ac8d8b..9d89f5ac 100644 --- a/mea/ui/MainPage.qml +++ b/mea/ui/MainPage.qml @@ -58,7 +58,7 @@ Page { InterfacesModel { id: page2Model devices: Engine.deviceManager.devices - shownInterfaces: ["gateway", "button", "notifications"] + shownInterfaces: ["gateway", "button", "notifications", "inputtrigger", "outputtrigger"] property var view: null onCountChanged: buildView() } @@ -69,7 +69,6 @@ Page { width: swipeView.width height: swipeView.height visible: count > 0 - shownInterfaces: ["light", "weather", "sensor", "media", "garagegate"] } } diff --git a/mea/ui/actiondelegates-ng/ActionDelegate.qml b/mea/ui/actiondelegates-ng/ActionDelegate.qml index d4136a34..32dc1ba8 100644 --- a/mea/ui/actiondelegates-ng/ActionDelegate.qml +++ b/mea/ui/actiondelegates-ng/ActionDelegate.qml @@ -39,7 +39,8 @@ ItemDelegate { if (paramType.allowedValues.length > 0) { return comboBoxComponent; } - return textFieldComponent; + return buttonComponent; +// return textFieldComponent; case "color": return colorPreviewComponent; } @@ -78,8 +79,11 @@ ItemDelegate { case "color": return colorPickerComponent case "string": - return paramType.allowedValues.length === 0 ? textFieldComponent : null + return paramType.allowedValues.length === 0 ? textFieldComponent : + root.actionType.paramTypes.count === 1 ? null : comboBoxComponent + } + console.warn("WARNING", root.actionType.paramTypes.get(index).name, "not implemented") return null; } @@ -170,6 +174,7 @@ ItemDelegate { Component { id: textFieldComponent RowLayout { + id: textFieldRow property alias value: textField.text property var paramType: null spacing: app.margins @@ -271,9 +276,11 @@ ItemDelegate { text: "Do it" onClicked: { var params = []; + print("fooo", root.actionType.paramTypes.count) for (var i = 0; i < root.actionType.paramTypes.count; i++) { var param = new Object(); param["paramTypeId"] = root.actionType.paramTypes.get(i).id; + print("bla", paramRepeater.itemAt(i), root.actionType.paramTypes.get(i).name) param["value"] = paramRepeater.itemAt(i).item.value; params.push(param) } diff --git a/mea/ui/customviews/GenericTypeLogView.qml b/mea/ui/customviews/GenericTypeLogView.qml new file mode 100644 index 00000000..b5006173 --- /dev/null +++ b/mea/ui/customviews/GenericTypeLogView.qml @@ -0,0 +1,110 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Mea 1.0 +import "../components" + +Item { + id: root + + property var device: null + property alias typeId: logs.typeId + + // %1 will be replaced with count + property string text + + signal addRuleClicked(var value) + + readonly property var deviceClass: Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) + + LogsModel { + id: logs + deviceId: root.device.id + live: true + Component.onCompleted: update() + } + + ColumnLayout { + anchors.fill: parent + + Label { + id: titleLabel + Layout.fillWidth: true + Layout.margins: app.margins + wrapMode: Text.WordWrap + text: root.text.arg(logs.count) + } + + ThinDivider {} + + RulesFilterModel { + id: rulesFilterModel + rules: Engine.ruleManager.rules + filterDeviceId: root.device.id + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + model: logs + clip: true + delegate: ItemDelegate { + width: parent.width + contentItem: RowLayout { + ColumnLayout { + Layout.fillWidth: true + RowLayout { + Layout.fillWidth: true + + ColorIcon { + Layout.preferredHeight: timeStampLabel.height + Layout.preferredWidth: height + name: "../images/clock-app-symbolic.svg" + } + + Label { + id: timeStampLabel + Layout.fillWidth: true + text: Qt.formatDateTime(model.timestamp,"dd.MM.yy - hh:mm:ss") + } + } + RowLayout { + Layout.fillWidth: true + Label { + text: qsTr("Data:") + } + Label { + Layout.fillWidth: true + text: model.value.trim() + elide: Text.ElideRight + } + } + } + HeaderButton { + imageSource: "../images/magic.svg" + color: { + for (var i = 0; i < rulesFilterModel.count; i++) { + var rule = rulesFilterModel.get(i); + for (var j = 0; j < rule.eventDescriptors.count; j++) { + var eventDescriptor = rule.eventDescriptors.get(j); + if (eventDescriptor.eventTypeId === root.deviceClass.eventTypes.findByName("triggered").id) { + var matching = true; + for (var k = 0; k < eventDescriptor.paramDescriptors.count; k++) { + var paramDescriptor = eventDescriptor.paramDescriptors.get(k); + if (paramDescriptor.value == model.value) { + return app.guhAccent; + } + } + } + } + } + return keyColor; + } + + onClicked: root.addRuleClicked(model.value) + } + } + } + } + } +} diff --git a/mea/ui/devicelistpages/GenericDeviceListPage.qml b/mea/ui/devicelistpages/GenericDeviceListPage.qml index 9ff8e900..7e2e78b2 100644 --- a/mea/ui/devicelistpages/GenericDeviceListPage.qml +++ b/mea/ui/devicelistpages/GenericDeviceListPage.qml @@ -6,7 +6,6 @@ import "../components" Page { id: subPage - property alias filterTag: devicesProxy.filterTag property alias filterInterface: devicesProxy.filterInterface Component.onCompleted: { @@ -17,9 +16,7 @@ Page { header: GuhHeader { text: { - if (subPage.filterTag != DeviceClass.BasicTagNone) { - return qsTr("My %1 things").arg(devicesBasicTagsModel.basicTagToString(subPage.filterTag)) - } else if (subPage.filterInterface.length > 0) { + if (subPage.filterInterface.length > 0) { return qsTr("My %1 things").arg(interfaceToString(subPage.filterInterface)) } return qsTr("All my things") @@ -35,14 +32,16 @@ Page { var device = devicesProxy.get(index); var deviceClass = Engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); var page; -/* if (deviceClass.interfaces.indexOf("media") >= 0) { + if (deviceClass.interfaces.indexOf("media") >= 0) { page = "MediaDevicePage.qml"; - } else */if (deviceClass.interfaces.indexOf("button") >= 0) { + } else if (deviceClass.interfaces.indexOf("button") >= 0) { page = "ButtonDevicePage.qml"; } else if (deviceClass.interfaces.indexOf("weather") >= 0) { page = "WeatherDevicePage.qml"; } else if (deviceClass.interfaces.indexOf("sensor") >= 0) { page = "SensorDevicePage.qml"; + } else if (deviceClass.interfaces.indexOf("inputtrigger") >= 0) { + page = "InputTriggerDevicePage.qml"; } else { page = "GenericDevicePage.qml"; } diff --git a/mea/ui/devicelistpages/LightsDeviceListPage.qml b/mea/ui/devicelistpages/LightsDeviceListPage.qml index 770ec45b..536839fd 100644 --- a/mea/ui/devicelistpages/LightsDeviceListPage.qml +++ b/mea/ui/devicelistpages/LightsDeviceListPage.qml @@ -30,7 +30,7 @@ Page { var params = []; var param1 = {}; param1["paramTypeId"] = actionType.paramTypes.get(0).id; - param1["value"] = checked; + param1["value"] = false; params.push(param1) Engine.deviceManager.executeAction(device.id, actionType.id, params) } diff --git a/mea/ui/devicepages/DevicePageBase.qml b/mea/ui/devicepages/DevicePageBase.qml index aa3dcb58..d3f7cdba 100644 --- a/mea/ui/devicepages/DevicePageBase.qml +++ b/mea/ui/devicepages/DevicePageBase.qml @@ -26,40 +26,51 @@ Page { } } - Pane { + Rectangle { id: infoPane visible: batteryState !== null || (connectedState !== null && connectedState.value === false) - height: visible ? implicitHeight : 0 + height: visible ? contentRow.implicitHeight : 0 anchors { left: parent.left; top: parent.top; right: parent.right } 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 var connectedState: deviceClass.interfaces.indexOf("connectable") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("connected").id) : null + property bool alertState: (connectedState !== null && connectedState.value === false) || + (batteryCriticalState !== null && batteryCriticalState.value === true) + color: alertState ? "red" : "transparent" + z: 1000 RowLayout { - anchors { left: parent.left; top: parent.top; right: parent.right } + id: contentRow + anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: app.margins; rightMargin: app.margins } Item { Layout.fillWidth: true height: app.iconSize } Label { - text: qsTr("Thing is not connected!") - visible: infoPane.connectedState !== null && infoPane.connectedState.value === false + text: (infoPane.connectedState !== null && infoPane.connectedState.value === false) ? + qsTr("Thing is not connected!") + : qsTr("Thing runs out of battery!") + visible: infoPane.alertState + font.pixelSize: app.smallFont + color: "white" } ColorIcon { - height: app.iconSize + height: app.iconSize / 2 width: height visible: infoPane.connectedState !== null && infoPane.connectedState.value === false - color: "red" + color: "white" name: "../images/dialog-warning-symbolic.svg" } ColorIcon { - height: app.iconSize + 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 + color: infoPane.alertState ? "white" : keyColor } } } @@ -68,5 +79,6 @@ Page { id: contentItem anchors.fill: parent anchors.topMargin: infoPane.height + clip: true } } diff --git a/mea/ui/devicepages/GenericDeviceStateDetailsPage.qml b/mea/ui/devicepages/GenericDeviceStateDetailsPage.qml index 3ebd947c..c5feb8cc 100644 --- a/mea/ui/devicepages/GenericDeviceStateDetailsPage.qml +++ b/mea/ui/devicepages/GenericDeviceStateDetailsPage.qml @@ -67,6 +67,18 @@ Page { } } + ColorIcon { + Layout.fillHeight: true + Layout.preferredWidth: height + name: "../images/info.svg" + + MouseArea { + anchors.fill: parent + onClicked: pageStack.push(Qt.resolvedUrl("StateLogPage.qml"), + {device: root.device, stateType: stateType}) + } + } + Binding { target: placeHolder.item when: placeHolder.item diff --git a/mea/ui/devicepages/InputTriggerDevicePage.qml b/mea/ui/devicepages/InputTriggerDevicePage.qml new file mode 100644 index 00000000..08c38248 --- /dev/null +++ b/mea/ui/devicepages/InputTriggerDevicePage.qml @@ -0,0 +1,33 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Mea 1.0 +import "../components" +import "../customviews" + +GenericDevicePage { + id: root + + + GenericTypeLogView { + anchors.fill: parent + text: qsTr("This event has appeared %1 times in the last 24 hours.") + + onAddRuleClicked: { + var rule = Engine.ruleManager.createNewRule(); + var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); + eventDescriptor.deviceId = device.id; + var eventType = root.deviceClass.eventTypes.findByName("triggered"); + eventDescriptor.eventTypeId = eventType.id; + rule.name = root.device.name + " - " + eventType.displayName; + if (eventType.paramTypes.count === 1) { + var paramType = eventType.paramTypes.get(0); + eventDescriptor.paramDescriptors.setParamDescriptor(paramType.id, value, ParamDescriptor.ValueOperatorEquals); + rule.eventDescriptors.addEventDescriptor(eventDescriptor); + rule.name = rule.name + " - " + value + } + var rulePage = pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device}); + rulePage.addRule(rule); + } + } +} diff --git a/mea/ui/devicepages/StateLogPage.qml b/mea/ui/devicepages/StateLogPage.qml new file mode 100644 index 00000000..8ae7d365 --- /dev/null +++ b/mea/ui/devicepages/StateLogPage.qml @@ -0,0 +1,40 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Mea 1.0 +import "../components" +import "../customviews" + +Page { + id: root + + property var device: null + property var stateType: null + + header: GuhHeader { + text: qsTr("History") + onBackPressed: pageStack.pop() + } + + GenericTypeLogView { + anchors.fill: parent + device: root.device + typeId: root.stateType.id + text: qsTr("%1, %2 has changed %3 times in the last 24h").arg(device.name).arg(stateType.displayName) + + onAddRuleClicked: { + var rule = Engine.ruleManager.createNewRule(); + rule.createStateEvaluator(); + rule.stateEvaluator.stateDescriptor.deviceId = device.id; + rule.stateEvaluator.stateDescriptor.stateTypeId = root.stateType.id; + rule.stateEvaluator.stateDescriptor.value = value; + rule.stateEvaluator.stateDescriptor.valueOperator = StateDescriptor.ValueOperatorEquals; + rule.name = root.device.name + " - " + stateType.displayName + " = " + value; + + var rulePage = pageStack.push(Qt.resolvedUrl("../magic/DeviceRulesPage.qml"), {device: root.device}); + rulePage.addRule(rule); + } + + } +} + diff --git a/mea/ui/images/clock-app-symbolic.svg b/mea/ui/images/clock-app-symbolic.svg new file mode 100644 index 00000000..acd85abe --- /dev/null +++ b/mea/ui/images/clock-app-symbolic.svg @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/mea/ui/images/mail-mark-important.svg b/mea/ui/images/mail-mark-important.svg new file mode 100644 index 00000000..d2427184 --- /dev/null +++ b/mea/ui/images/mail-mark-important.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/mea/ui/images/send.svg b/mea/ui/images/send.svg new file mode 100644 index 00000000..0f6aba13 --- /dev/null +++ b/mea/ui/images/send.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/mea/ui/magic/DeviceRulesPage.qml b/mea/ui/magic/DeviceRulesPage.qml index 46544dea..4147a8b2 100644 --- a/mea/ui/magic/DeviceRulesPage.qml +++ b/mea/ui/magic/DeviceRulesPage.qml @@ -20,17 +20,22 @@ Page { } } - function addRule() { -// pageStack.push(Qt.resolvedUrl("NewThingMagicPage.qml"), {device: root.device, text: "Add magic"}) - var rule = Engine.ruleManager.createNewRule(); + // Rule is optional and might be initialized with anything wanted. A new, empty one will be created if null + function addRule(rule) { + if (rule === null || rule === undefined) { + rule = Engine.ruleManager.createNewRule(); + } var page = pageStack.push(Qt.resolvedUrl("EditRulePage.qml"), {rule: rule}); - var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); - eventDescriptor.deviceId = device.id; - page.selectEventDescriptorData(eventDescriptor); page.onAccept.connect(function() { Engine.ruleManager.addRule(page.rule); }) +// if (rule.eventDescriptors.count === 0) { +// var eventDescriptor = rule.eventDescriptors.createNewEventDescriptor(); +// eventDescriptor.deviceId = device.id; +// page.selectEventDescriptorData(eventDescriptor); +// } + } Connections { diff --git a/mea/ui/main.qml b/mea/ui/main.qml index 028ac7b3..8d1dfe8a 100644 --- a/mea/ui/main.qml +++ b/mea/ui/main.qml @@ -8,8 +8,8 @@ import Mea 1.0 ApplicationWindow { id: app visible: true - width: 270 * 1.5 - height: 480 * 1.5 + width: 360 + height: 580 visibility: settings.viewMode font: Qt.application.font @@ -112,8 +112,10 @@ ApplicationWindow { onClosing: { if (Qt.platform.os == "android") { - close.accepted = false; - if (pageStack.depth > 1) pageStack.pop(); + if (pageStack.depth > 1) { + close.accepted = false; + pageStack.pop(); + } } } @@ -152,6 +154,10 @@ ApplicationWindow { return "Temperature"; case "humiditysensor": return "Humidity"; + case "inputtrigger": + return "Incoming Events"; + case "outputtrigger": + return "Events"; } } @@ -195,6 +201,10 @@ ApplicationWindow { return Qt.resolvedUrl("images/notification.svg") case "connectable": return Qt.resolvedUrl("images/stock_link.svg") + case "inputtrigger": + return Qt.resolvedUrl("images/mail-mark-important.svg") + case "outputtrigger": + return Qt.resolvedUrl("images/send.svg") } return ""; }