diff --git a/libnymea-app-core/engine.cpp b/libnymea-app-core/engine.cpp index 2c521b9f..568cf884 100644 --- a/libnymea-app-core/engine.cpp +++ b/libnymea-app-core/engine.cpp @@ -137,6 +137,7 @@ void Engine::onConnectedChanged() qDebug() << "Engine: connected changed:" << m_jsonRpcClient->connected(); m_deviceManager->clear(); m_ruleManager->clear(); + m_tagsManager->clear(); if (m_jsonRpcClient->connected()) { qDebug() << "Engine: inital setup required:" << m_jsonRpcClient->initialSetupRequired() << "auth required:" << m_jsonRpcClient->authenticationRequired(); if (!m_jsonRpcClient->initialSetupRequired() && !m_jsonRpcClient->authenticationRequired()) { diff --git a/libnymea-app-core/tagsmanager.cpp b/libnymea-app-core/tagsmanager.cpp index 0912937b..1c361721 100644 --- a/libnymea-app-core/tagsmanager.cpp +++ b/libnymea-app-core/tagsmanager.cpp @@ -23,6 +23,11 @@ void TagsManager::init() m_jsonClient->sendCommand("Tags.GetTags", this, "getTagsReply"); } +void TagsManager::clear() +{ + m_tags->clear(); +} + bool TagsManager::busy() const { return m_busy; @@ -123,7 +128,6 @@ void TagsManager::getTagsReply(const QVariantMap ¶ms) { QList tags; foreach (const QVariant &tagVariant, params.value("params").toMap().value("tags").toList()) { - qDebug() << "aDDING TAG"; Tag *tag = unpackTag(tagVariant.toMap()); if (tag) { tags.append(tag); diff --git a/libnymea-app-core/tagsmanager.h b/libnymea-app-core/tagsmanager.h index 28b7cdcd..ca7e6ba8 100644 --- a/libnymea-app-core/tagsmanager.h +++ b/libnymea-app-core/tagsmanager.h @@ -17,6 +17,7 @@ public: QString nameSpace() const override; void init(); + void clear(); bool busy() const; Tags* tags() const; diff --git a/libnymea-common/types/interfaces.cpp b/libnymea-common/types/interfaces.cpp index d0d3f41a..1466c8bd 100644 --- a/libnymea-common/types/interfaces.cpp +++ b/libnymea-common/types/interfaces.cpp @@ -120,6 +120,13 @@ Interfaces::Interfaces(QObject *parent) : QAbstractListModel(parent) tr("Playback status"), tr("Playback status changed"), tr("Set playback status")); + + addInterface("mediacontroller", tr("Media controllers")); + addActionType("mediacontroller", "play", tr("Start playback"), new ParamTypes()); + addActionType("mediacontroller", "stop", tr("Stop playback"), new ParamTypes()); + addActionType("mediacontroller", "pause", tr("Pause playback"), new ParamTypes()); + addActionType("mediacontroller", "skipBack", tr("Skip back"), new ParamTypes()); + addActionType("mediacontroller", "skipNext", tr("Skip next"), new ParamTypes()); } int Interfaces::rowCount(const QModelIndex &parent) const @@ -149,6 +156,9 @@ QHash Interfaces::roleNames() const Interface *Interfaces::get(int index) const { + if (index < 0 || index >= m_list.count()) { + return nullptr; + } return m_list.at(index); } diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 8f386481..05d5e5b8 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -199,5 +199,6 @@ ui/magic/NewScenePage.qml ui/mainviews/GroupsView.qml ui/grouping/GroupPage.qml + ui/delegates/ThingTile.qml diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml index 499ce009..19c96df4 100644 --- a/nymea-app/ui/MainPage.qml +++ b/nymea-app/ui/MainPage.qml @@ -299,7 +299,7 @@ Page { GroupsView { id: groupsView - property string title: qsTr("My groups" + count); + property string title: qsTr("My groups"); width: swipeView.width height: swipeView.height } diff --git a/nymea-app/ui/delegates/ThingTile.qml b/nymea-app/ui/delegates/ThingTile.qml new file mode 100644 index 00000000..47dbb6d8 --- /dev/null +++ b/nymea-app/ui/delegates/ThingTile.qml @@ -0,0 +1,376 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 +import Nymea 1.0 +import "../components" + +MainPageTile { + id: root + text: device.name.toUpperCase() + iconName: app.interfacesToIcon(deviceClass.interfaces) + iconColor: app.accentColor + batteryCritical: batteryCriticalState && batteryCriticalState.value === true + disconnected: connectedState && connectedState.value === false + + 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 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 + + contentItem: Loader { + id: loader + anchors.fill: parent + sourceComponent: { + if (root.deviceClass.interfaces.indexOf("closable") >= 0) { + return closableComponent; + } + if (root.deviceClass.interfaces.indexOf("power") >= 0) { + return lightsComponent; + } + if (root.deviceClass.interfaces.indexOf("sensor") >= 0) { + return sensorsComponent; + } + if (root.deviceClass.interfaces.indexOf("weather") >= 0) { + return sensorsComponent; + } + if (root.deviceClass.interfaces.indexOf("smartmeter") >= 0) { + return sensorsComponent; + } + if (root.deviceClass.interfaces.indexOf("mediacontroller") >= 0) { + return mediaComponent; + } + } + Binding { target: loader.item ? loader.item : null; property: "deviceClass"; value: root.deviceClass } + Binding { target: loader.item ? loader.item : null; property: "device"; value: root.device } + } + + Component { + id: lightsComponent + RowLayout { + property var device: null + property var deviceClass: null + + readonly property var powerStateType: deviceClass.stateTypes.findByName("power"); + readonly property var powerState: device.states.getState(powerStateType.id) + + readonly property var brightnessStateType: deviceClass.stateTypes.findByName("brightness"); + readonly property var brightnessState: brightnessStateType ? device.states.getState(brightnessStateType.id) : null + + ThrottledSlider { + Layout.fillWidth: true + Layout.leftMargin: app.margins / 2 + Layout.alignment: Qt.AlignVCenter + opacity: deviceClass.interfaces.indexOf("dimmablelight") >= 0 ? 1 : 0 + enabled: opacity > 0 + from: 0 + to: 100 + value: brightnessState ? brightnessState.value : 0 + onMoved: { + var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + var actionType = deviceClass.actionTypes.findByName("brightness"); + var params = []; + var powerParam = {} + powerParam["paramTypeId"] = actionType.paramTypes.get(0).id; + powerParam["value"] = value; + params.push(powerParam) + engine.deviceManager.executeAction(device.id, actionType.id, params); + } + } + + ItemDelegate { + Layout.preferredWidth: app.iconSize + Layout.preferredHeight: width + Layout.rightMargin: app.margins / 2 + Layout.alignment: Qt.AlignVCenter + padding: 0; topPadding: 0; bottomPadding: 0 + + contentItem: ColorIcon { + name: deviceClass.interfaces.indexOf("light") >= 0 + ? (powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg") + : app.interfacesToIcon(deviceClass.interfaces) + color: powerState.value === true ? app.accentColor : keyColor + } + onClicked: { + var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + var actionType = deviceClass.actionTypes.findByName("power"); + var params = []; + var powerParam = {} + powerParam["paramTypeId"] = actionType.paramTypes.get(0).id; + powerParam["value"] = !powerState.value; + params.push(powerParam) + engine.deviceManager.executeAction(device.id, actionType.id, params); + } + } + } + } + + Component { + id: sensorsComponent + RowLayout { + id: sensorsRoot + property var device: null + property var deviceClass: null + spacing: 0 + + property var shownInterfaces: [] + property int currentStateIndex: -1 + property var currentStateType: deviceClass ? deviceClass.stateTypes.findByName(shownInterfaces[currentStateIndex].state) : null + property var currentState: currentStateType ? device.states.getState(currentStateType.id) : null + + onDeviceClassChanged: { + if (deviceClass == null) { + return; + } + + var tmp = [] + if (deviceClass.interfaces.indexOf("temperaturesensor") >= 0) { + tmp.push({iface: "temperaturesensor", state: "temperature"}); + } + if (deviceClass.interfaces.indexOf("humiditysensor") >= 0) { + tmp.push({iface: "humiditysensor", state: "humidity"}); + } + if (deviceClass.interfaces.indexOf("moisturesensor") >= 0) { + tmp.push({iface: "moisturesensor", state: "moisture"}); + } + if (deviceClass.interfaces.indexOf("pressuresensor") >= 0) { + tmp.push({iface: "pressuresensor", state: "pressure"}); + } + if (deviceClass.interfaces.indexOf("lightsensor") >= 0) { + tmp.push({iface: "lightsensor", state: "lightIntensity"}); + } + if (deviceClass.interfaces.indexOf("conductivitysensor") >= 0) { + tmp.push({iface: "conductivitysensor", state: "conductivity"}); + } + if (deviceClass.interfaces.indexOf("noisesensor") >= 0) { + tmp.push({iface: "noisesensor", state: "noise"}); + } + if (deviceClass.interfaces.indexOf("co2sensor") >= 0) { + tmp.push({iface: "co2sensor", state: "co2"}); + } + if (deviceClass.interfaces.indexOf("smartmeterconsumer") >= 0) { + tmp.push({iface: "smartmeterconsumer", state: "totalEnergyConsumed"}); + } + if (deviceClass.interfaces.indexOf("smartmeterproducer") >= 0) { + tmp.push({iface: "smartmeterproducer", state: "totalEnergyProduced"}); + } + if (deviceClass.interfaces.indexOf("daylightsensor") >= 0) { + tmp.push({iface: "daylightsensor", state: "daylight"}); + } + if (deviceClass.interfaces.indexOf("presencesensor") >= 0) { + tmp.push({iface: "presencesensor", state: "isPresent"}); + } + + if (deviceClass.interfaces.indexOf("weather") >= 0) { + tmp.push({iface: "temperaturesensor", state: "temperature"}); + tmp.push({iface: "humiditysensor", state: "humidity"}); + tmp.push({iface: "pressuresensor", state: "pressure"}); + } + + shownInterfaces = tmp + currentStateIndex = 0 + } + + ItemDelegate { + Layout.preferredWidth: app.iconSize + Layout.preferredHeight: width + Layout.leftMargin: app.margins / 2 + Layout.alignment: Qt.AlignVCenter + padding: 0; topPadding: 0; bottomPadding: 0 + visible: sensorsRoot.shownInterfaces.length > 1 + contentItem: ColorIcon { + name: "../images/back.svg" + } + onClicked: { + var newIndex = sensorsRoot.currentStateIndex - 1; + if (newIndex < 0) newIndex = sensorsRoot.shownInterfaces.length - 1 + sensorsRoot.currentStateIndex = newIndex; + } + } + + Item { Layout.fillHeight: true; Layout.fillWidth: true } + ColorIcon { + Layout.preferredWidth: app.iconSize + Layout.preferredHeight: width + Layout.alignment: Qt.AlignVCenter + color: app.interfaceToColor(sensorsRoot.shownInterfaces[sensorsRoot.currentStateIndex].iface) + name: app.interfaceToIcon(sensorsRoot.shownInterfaces[sensorsRoot.currentStateIndex].iface) + } + + Item { Layout.fillHeight: true; Layout.fillWidth: true } + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + visible: sensorsRoot.currentStateType.type.toLowerCase() !== "bool" + + Label { + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + text: sensorsRoot.currentStateType.unitString + font.pixelSize: app.smallFont + } + Label { + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + text: sensorsRoot.currentState.value// + " " + sensorsRoot.currentStateType.unitString + elide: Text.ElideRight + } + } + Led { + state: sensorsRoot.currentState.value === true ? "on" : "off" + visible: sensorsRoot.currentStateType.type.toLowerCase() === "bool" + } + + Item { Layout.fillHeight: true; Layout.fillWidth: true } + + ItemDelegate { + Layout.preferredWidth: app.iconSize + Layout.preferredHeight: width + Layout.rightMargin: app.margins / 2 + Layout.alignment: Qt.AlignVCenter + padding: 0; topPadding: 0; bottomPadding: 0 + visible: sensorsRoot.shownInterfaces.length > 1 + contentItem: ColorIcon { + name: "../images/next.svg" + } + onClicked: { + var newIndex = sensorsRoot.currentStateIndex + 1; + if (newIndex >= sensorsRoot.shownInterfaces.length) newIndex = 0; + sensorsRoot.currentStateIndex = newIndex; + } + } + } + } + + Component { + id: closableComponent + RowLayout { + property var device: null + property var deviceClass: null + + ItemDelegate { + Layout.preferredWidth: app.iconSize + Layout.preferredHeight: width + Layout.leftMargin: app.margins / 2 + Layout.alignment: Qt.AlignVCenter + padding: 0; topPadding: 0; bottomPadding: 0 + contentItem: ColorIcon { + name: "../images/up.svg" + color: app.accentColor + } + onClicked: { + var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + var actionType = deviceClass.actionTypes.findByName("open"); + engine.deviceManager.executeAction(device.id, actionType.id); + } + } + + Slider { + id: closableSlider + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + visible: deviceClass.interfaces.indexOf("extendedclosable") >= 0 + readonly property var percentageStateType: deviceClass.stateTypes.findByName("percentage"); + readonly property var percentateState: percentageStateType ? device.states.getState(percentageStateType.id) : null + from: 0 + to: 100 + value: percentateState ? percentateState.value : 0 + } + Item { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + visible: !closableSlider.visible + } + + ItemDelegate { + Layout.preferredWidth: app.iconSize + Layout.preferredHeight: width + Layout.rightMargin: app.margins / 2 + Layout.alignment: Qt.AlignVCenter + padding: 0; topPadding: 0; bottomPadding: 0 + contentItem: ColorIcon { + name: "../images/down.svg" + color: app.accentColor + } + onClicked: { + var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); + var actionType = deviceClass.actionTypes.findByName("close"); + engine.deviceManager.executeAction(device.id, actionType.id); + } + } + } + } + + Component { + id: mediaComponent + RowLayout { + id: mediaRoot + + property Device device: null + property DeviceClass deviceClass: null + + readonly property State playbackState: device.states.getState(deviceClass.stateTypes.findByName("playbackStatus").id) + + function executeAction(actionName, params) { + var actionTypeId = deviceClass.actionTypes.findByName(actionName).id; + engine.deviceManager.executeAction(device.id, actionTypeId, params) + } + Item { Layout.fillWidth: true } + + ProgressButton { + Layout.preferredHeight: app.iconSize * .9 + Layout.preferredWidth: height + imageSource: "../images/media-skip-backward.svg" + longpressImageSource: "../images/media-seek-backward.svg" + repeat: true + + onClicked: { + mediaRoot.executeAction("skipBack") + } + onLongpressed: { + mediaRoot.executeAction("fastRewind") + } + } + Item { Layout.fillWidth: true } + + ProgressButton { + Layout.preferredHeight: app.iconSize * 1.3 + Layout.preferredWidth: height + imageSource: mediaRoot.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg" + longpressImageSource: "../images/media-playback-stop.svg" + longpressEnabled: mediaRoot.playbackState.value !== "Stopped" + + onClicked: { + if (mediaRoot.playbackState.value === "Playing") { + mediaRoot.executeAction("pause") + } else { + mediaRoot.executeAction("play") + } + } + + onLongpressed: { + mediaRoot.executeAction("stop") + } + } + + Item { Layout.fillWidth: true } + ProgressButton { + Layout.preferredHeight: app.iconSize * .9 + Layout.preferredWidth: height + imageSource: "../images/media-skip-forward.svg" + longpressImageSource: "../images/media-seek-forward.svg" + repeat: true + onClicked: { + mediaRoot.executeAction("skipNext") + } + onLongpressed: { + mediaRoot.executeAction("fastForward") + } + } + Item { Layout.fillWidth: true } + } + } +} diff --git a/nymea-app/ui/devicepages/DevicePageBase.qml b/nymea-app/ui/devicepages/DevicePageBase.qml index f9eda37f..3366e7cd 100644 --- a/nymea-app/ui/devicepages/DevicePageBase.qml +++ b/nymea-app/ui/devicepages/DevicePageBase.qml @@ -93,7 +93,6 @@ Page { } } function addToGroup() { -// engine.tagsManager.tagDevice(root.device.id, "group", "My group 1") var dialog = addToGroupDialog.createObject(root) dialog.open(); } @@ -122,6 +121,9 @@ Page { headerIcon: "../images/view-grid-symbolic.svg" RowLayout { + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + spacing: app.margins TextField { id: newGroupdTextField Layout.fillWidth: true @@ -141,6 +143,8 @@ Page { ListView { Layout.fillWidth: true height: 200 + clip: true + ScrollIndicator.vertical: ScrollIndicator {} model: TagListModel { id: groupTags diff --git a/nymea-app/ui/grouping/GroupPage.qml b/nymea-app/ui/grouping/GroupPage.qml index d1053f0b..0e9494da 100644 --- a/nymea-app/ui/grouping/GroupPage.qml +++ b/nymea-app/ui/grouping/GroupPage.qml @@ -4,6 +4,7 @@ import QtQuick.Controls.Material 2.1 import QtQuick.Layouts 1.1 import Nymea 1.0 import "../components" +import "../delegates" Page { id: root @@ -27,11 +28,28 @@ Page { showStates: true } - ListView { + GridView { + id: gridView anchors.fill: parent + anchors.margins: app.margins / 2 + model: devicesInGroup - delegate: Label { - text: model.name + + readonly property int minTileWidth: 180 + readonly property int minTileHeight: 240 + readonly property int tilesPerRow: root.width / minTileWidth + + cellWidth: gridView.width / tilesPerRow + cellHeight: cellWidth + + delegate: ThingTile { + width: gridView.cellWidth + height: gridView.cellHeight + + device: devicesInGroup.get(index) + + onClicked: pageStack.push(Qt.resolvedUrl("../devicepages/" + app.interfaceListToDevicePage(deviceClass.interfaces)), {device: device}) + } } diff --git a/nymea-app/ui/mainviews/DevicesPage.qml b/nymea-app/ui/mainviews/DevicesPage.qml index c2700ec8..dde2e21f 100644 --- a/nymea-app/ui/mainviews/DevicesPage.qml +++ b/nymea-app/ui/mainviews/DevicesPage.qml @@ -5,11 +5,15 @@ import QtQuick.Layouts 1.2 import Nymea 1.0 import "../components" -Item { +MouseArea { id: root property alias count: interfacesGridView.count property alias model: interfacesGridView.model + // Prevent scroll events to swipe left/right in case they fall through the grid + preventStealing: true + onWheel: wheel.accepted = true + GridView { id: interfacesGridView anchors.fill: parent diff --git a/nymea-app/ui/mainviews/FavoritesView.qml b/nymea-app/ui/mainviews/FavoritesView.qml index f6507c65..c2c4c2a7 100644 --- a/nymea-app/ui/mainviews/FavoritesView.qml +++ b/nymea-app/ui/mainviews/FavoritesView.qml @@ -4,13 +4,18 @@ import QtQuick.Layouts 1.3 import QtQuick.Controls.Material 2.2 import Nymea 1.0 import "../components" +import "../delegates" -Item { +MouseArea { id: root property bool editMode: false readonly property int count: tagsProxy.count + // Prevent scroll events to swipe left/right in case they fall through the grid + preventStealing: true + onWheel: wheel.accepted = true + TagsProxyModel { id: tagsProxy tags: engine.tagsManager.tags @@ -29,57 +34,16 @@ Item { cellHeight: cellWidth model: tagsProxy - delegate: MainPageTile { + delegate: ThingTile { id: delegateRoot width: gridView.cellWidth height: gridView.cellHeight - text: device.name.toUpperCase() - iconName: app.interfacesToIcon(deviceClass.interfaces) - iconColor: app.accentColor - visible: !fakeDragItem.visible || fakeDragItem.deviceId !== device.id - batteryCritical: batteryCriticalState && batteryCriticalState.value === true - disconnected: connectedState && connectedState.value === false - - property var modelIndex: index - - property string deviceId: model.deviceId - property string ruleId: model.ruleId - readonly property Device device: engine.deviceManager.devices.getDevice(deviceId) - 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 batteryCriticalState: deviceClass.interfaces.indexOf("battery") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("batteryCritical").id) : null + device: engine.deviceManager.devices.getDevice(deviceId) onClicked: pageStack.push(Qt.resolvedUrl("../devicepages/" + app.interfaceListToDevicePage(deviceClass.interfaces)), {device: device}) onPressAndHold: root.editMode = true - contentItem: Loader { - id: loader - anchors.fill: parent - sourceComponent: { - if (delegateRoot.deviceClass.interfaces.indexOf("closable") >= 0) { - return closableComponent; - } - if (delegateRoot.deviceClass.interfaces.indexOf("power") >= 0) { - return lightsComponent; - } - if (delegateRoot.deviceClass.interfaces.indexOf("sensor") >= 0) { - return sensorsComponent; - } - if (delegateRoot.deviceClass.interfaces.indexOf("weather") >= 0) { - return sensorsComponent; - } - if (delegateRoot.deviceClass.interfaces.indexOf("smartmeter") >= 0) { - return sensorsComponent; - } - if (delegateRoot.deviceClass.interfaces.indexOf("mediacontroller") >= 0) { - return mediaComponent; - } - } - Binding { target: loader.item ? loader.item : null; property: "deviceClass"; value: delegateRoot.deviceClass } - Binding { target: loader.item ? loader.item : null; property: "device"; value: delegateRoot.device } - } - SequentialAnimation { loops: Animation.Infinite running: root.editMode @@ -174,330 +138,4 @@ Item { } } } - - Component { - id: lightsComponent - RowLayout { - property var device: null - property var deviceClass: null - - readonly property var powerStateType: deviceClass.stateTypes.findByName("power"); - readonly property var powerState: device.states.getState(powerStateType.id) - - readonly property var brightnessStateType: deviceClass.stateTypes.findByName("brightness"); - readonly property var brightnessState: brightnessStateType ? device.states.getState(brightnessStateType.id) : null - - ThrottledSlider { - Layout.fillWidth: true - Layout.leftMargin: app.margins / 2 - Layout.alignment: Qt.AlignVCenter - opacity: deviceClass.interfaces.indexOf("dimmablelight") >= 0 ? 1 : 0 - enabled: opacity > 0 - from: 0 - to: 100 - value: brightnessState ? brightnessState.value : 0 - onMoved: { - var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - var actionType = deviceClass.actionTypes.findByName("brightness"); - var params = []; - var powerParam = {} - powerParam["paramTypeId"] = actionType.paramTypes.get(0).id; - powerParam["value"] = value; - params.push(powerParam) - engine.deviceManager.executeAction(device.id, actionType.id, params); - } - } - - ItemDelegate { - Layout.preferredWidth: app.iconSize - Layout.preferredHeight: width - Layout.rightMargin: app.margins / 2 - Layout.alignment: Qt.AlignVCenter - padding: 0; topPadding: 0; bottomPadding: 0 - - contentItem: ColorIcon { - name: deviceClass.interfaces.indexOf("light") >= 0 - ? (powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg") - : app.interfacesToIcon(deviceClass.interfaces) - color: powerState.value === true ? app.accentColor : keyColor - } - onClicked: { - var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - var actionType = deviceClass.actionTypes.findByName("power"); - var params = []; - var powerParam = {} - powerParam["paramTypeId"] = actionType.paramTypes.get(0).id; - powerParam["value"] = !powerState.value; - params.push(powerParam) - engine.deviceManager.executeAction(device.id, actionType.id, params); - } - } - } - } - - Component { - id: sensorsComponent - RowLayout { - id: sensorsRoot - property var device: null - property var deviceClass: null - spacing: 0 - - property var shownInterfaces: [] - property int currentStateIndex: -1 - property var currentStateType: deviceClass ? deviceClass.stateTypes.findByName(shownInterfaces[currentStateIndex].state) : null - property var currentState: currentStateType ? device.states.getState(currentStateType.id) : null - - onDeviceClassChanged: { - if (deviceClass == null) { - return; - } - - var tmp = [] - if (deviceClass.interfaces.indexOf("temperaturesensor") >= 0) { - tmp.push({iface: "temperaturesensor", state: "temperature"}); - } - if (deviceClass.interfaces.indexOf("humiditysensor") >= 0) { - tmp.push({iface: "humiditysensor", state: "humidity"}); - } - if (deviceClass.interfaces.indexOf("moisturesensor") >= 0) { - tmp.push({iface: "moisturesensor", state: "moisture"}); - } - if (deviceClass.interfaces.indexOf("pressuresensor") >= 0) { - tmp.push({iface: "pressuresensor", state: "pressure"}); - } - if (deviceClass.interfaces.indexOf("lightsensor") >= 0) { - tmp.push({iface: "lightsensor", state: "lightIntensity"}); - } - if (deviceClass.interfaces.indexOf("conductivitysensor") >= 0) { - tmp.push({iface: "conductivitysensor", state: "conductivity"}); - } - if (deviceClass.interfaces.indexOf("noisesensor") >= 0) { - tmp.push({iface: "noisesensor", state: "noise"}); - } - if (deviceClass.interfaces.indexOf("co2sensor") >= 0) { - tmp.push({iface: "co2sensor", state: "co2"}); - } - if (deviceClass.interfaces.indexOf("smartmeterconsumer") >= 0) { - tmp.push({iface: "smartmeterconsumer", state: "totalEnergyConsumed"}); - } - if (deviceClass.interfaces.indexOf("smartmeterproducer") >= 0) { - tmp.push({iface: "smartmeterproducer", state: "totalEnergyProduced"}); - } - if (deviceClass.interfaces.indexOf("daylightsensor") >= 0) { - tmp.push({iface: "daylightsensor", state: "daylight"}); - } - if (deviceClass.interfaces.indexOf("presencesensor") >= 0) { - tmp.push({iface: "presencesensor", state: "isPresent"}); - } - - if (deviceClass.interfaces.indexOf("weather") >= 0) { - tmp.push({iface: "temperaturesensor", state: "temperature"}); - tmp.push({iface: "humiditysensor", state: "humidity"}); - tmp.push({iface: "pressuresensor", state: "pressure"}); - } - - shownInterfaces = tmp - currentStateIndex = 0 - } - - ItemDelegate { - Layout.preferredWidth: app.iconSize - Layout.preferredHeight: width - Layout.leftMargin: app.margins / 2 - Layout.alignment: Qt.AlignVCenter - padding: 0; topPadding: 0; bottomPadding: 0 - visible: sensorsRoot.shownInterfaces.length > 1 - contentItem: ColorIcon { - name: "../images/back.svg" - } - onClicked: { - var newIndex = sensorsRoot.currentStateIndex - 1; - if (newIndex < 0) newIndex = sensorsRoot.shownInterfaces.length - 1 - sensorsRoot.currentStateIndex = newIndex; - } - } - - Item { Layout.fillHeight: true; Layout.fillWidth: true } - ColorIcon { - Layout.preferredWidth: app.iconSize - Layout.preferredHeight: width - Layout.alignment: Qt.AlignVCenter - color: app.interfaceToColor(sensorsRoot.shownInterfaces[sensorsRoot.currentStateIndex].iface) - name: app.interfaceToIcon(sensorsRoot.shownInterfaces[sensorsRoot.currentStateIndex].iface) - } - - Item { Layout.fillHeight: true; Layout.fillWidth: true } - ColumnLayout { - Layout.fillWidth: true - spacing: 0 - visible: sensorsRoot.currentStateType.type.toLowerCase() !== "bool" - - Label { - Layout.fillWidth: true - horizontalAlignment: Text.AlignRight - text: sensorsRoot.currentStateType.unitString - font.pixelSize: app.smallFont - } - Label { - Layout.fillWidth: true - horizontalAlignment: Text.AlignRight - text: sensorsRoot.currentState.value// + " " + sensorsRoot.currentStateType.unitString - elide: Text.ElideRight - } - } - Led { - state: sensorsRoot.currentState.value === true ? "on" : "off" - visible: sensorsRoot.currentStateType.type.toLowerCase() === "bool" - } - - Item { Layout.fillHeight: true; Layout.fillWidth: true } - - ItemDelegate { - Layout.preferredWidth: app.iconSize - Layout.preferredHeight: width - Layout.rightMargin: app.margins / 2 - Layout.alignment: Qt.AlignVCenter - padding: 0; topPadding: 0; bottomPadding: 0 - visible: sensorsRoot.shownInterfaces.length > 1 - contentItem: ColorIcon { - name: "../images/next.svg" - } - onClicked: { - var newIndex = sensorsRoot.currentStateIndex + 1; - if (newIndex >= sensorsRoot.shownInterfaces.length) newIndex = 0; - sensorsRoot.currentStateIndex = newIndex; - } - } - } - } - - Component { - id: closableComponent - RowLayout { - property var device: null - property var deviceClass: null - - ItemDelegate { - Layout.preferredWidth: app.iconSize - Layout.preferredHeight: width - Layout.leftMargin: app.margins / 2 - Layout.alignment: Qt.AlignVCenter - padding: 0; topPadding: 0; bottomPadding: 0 - contentItem: ColorIcon { - name: "../images/up.svg" - color: app.accentColor - } - onClicked: { - var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - var actionType = deviceClass.actionTypes.findByName("open"); - engine.deviceManager.executeAction(device.id, actionType.id); - } - } - - Slider { - id: closableSlider - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - visible: deviceClass.interfaces.indexOf("extendedclosable") >= 0 - readonly property var percentageStateType: deviceClass.stateTypes.findByName("percentage"); - readonly property var percentateState: device.states.getState(percentageStateType.id) - from: 0 - to: 100 - value: percentateState.value - } - Item { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - visible: !closableSlider.visible - } - - ItemDelegate { - Layout.preferredWidth: app.iconSize - Layout.preferredHeight: width - Layout.rightMargin: app.margins / 2 - Layout.alignment: Qt.AlignVCenter - padding: 0; topPadding: 0; bottomPadding: 0 - contentItem: ColorIcon { - name: "../images/down.svg" - color: app.accentColor - } - onClicked: { - var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); - var actionType = deviceClass.actionTypes.findByName("close"); - engine.deviceManager.executeAction(device.id, actionType.id); - } - } - } - } - - Component { - id: mediaComponent - RowLayout { - id: mediaRoot - - property Device device: null - property DeviceClass deviceClass: null - - readonly property State playbackState: device.states.getState(deviceClass.stateTypes.findByName("playbackStatus").id) - - function executeAction(actionName, params) { - var actionTypeId = deviceClass.actionTypes.findByName(actionName).id; - engine.deviceManager.executeAction(device.id, actionTypeId, params) - } - Item { Layout.fillWidth: true } - - ProgressButton { - Layout.preferredHeight: app.iconSize * .9 - Layout.preferredWidth: height - imageSource: "../images/media-skip-backward.svg" - longpressImageSource: "../images/media-seek-backward.svg" - repeat: true - - onClicked: { - mediaRoot.executeAction("skipBack") - } - onLongpressed: { - mediaRoot.executeAction("fastRewind") - } - } - Item { Layout.fillWidth: true } - - ProgressButton { - Layout.preferredHeight: app.iconSize * 1.3 - Layout.preferredWidth: height - imageSource: mediaRoot.playbackState.value === "Playing" ? "../images/media-playback-pause.svg" : "../images/media-playback-start.svg" - longpressImageSource: "../images/media-playback-stop.svg" - longpressEnabled: mediaRoot.playbackState.value !== "Stopped" - - onClicked: { - if (mediaRoot.playbackState.value === "Playing") { - mediaRoot.executeAction("pause") - } else { - mediaRoot.executeAction("play") - } - } - - onLongpressed: { - mediaRoot.executeAction("stop") - } - } - - Item { Layout.fillWidth: true } - ProgressButton { - Layout.preferredHeight: app.iconSize * .9 - Layout.preferredWidth: height - imageSource: "../images/media-skip-forward.svg" - longpressImageSource: "../images/media-seek-forward.svg" - repeat: true - onClicked: { - mediaRoot.executeAction("skipNext") - } - onLongpressed: { - mediaRoot.executeAction("fastForward") - } - } - Item { Layout.fillWidth: true } - } - } } diff --git a/nymea-app/ui/mainviews/GroupsView.qml b/nymea-app/ui/mainviews/GroupsView.qml index a2762a0e..014b27de 100644 --- a/nymea-app/ui/mainviews/GroupsView.qml +++ b/nymea-app/ui/mainviews/GroupsView.qml @@ -5,8 +5,10 @@ import Nymea 1.0 import QtQuick.Controls.Material 2.2 import "../components" -Item { +MouseArea { id: root + preventStealing: true + onWheel: wheel.accepted = true readonly property int count: groupsGridView.count @@ -15,6 +17,7 @@ Item { anchors.fill: parent anchors.margins: app.margins / 2 + readonly property int minTileWidth: 180 readonly property int minTileHeight: 180 readonly property int tilesPerRow: root.width / minTileWidth @@ -48,7 +51,7 @@ Item { InterfacesProxy { id: controlsInGroup - shownInterfaces: ["light", "simpleclosable"] + shownInterfaces: ["light", "simpleclosable", "mediacontroller"] devicesProxyFilter: devicesInGroup showStates: true showActions: true @@ -61,7 +64,10 @@ Item { } contentItem: ItemDelegate { - padding: 0 + leftPadding: 0 + topPadding: 0 + rightPadding: 0 + bottomPadding: 0 onClicked: { pageStack.push(Qt.resolvedUrl("../grouping/GroupPage.qml"), {groupTag: model.tagId}) @@ -74,6 +80,7 @@ Item { color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05) Label { anchors.fill: parent + verticalAlignment: Text.AlignVCenter anchors { leftMargin: app.margins; rightMargin: app.margins } text: model.tagId.substring(6) elide: Text.ElideRight @@ -82,22 +89,35 @@ Item { Item { Layout.fillHeight: true Layout.fillWidth: true + + ColorIcon { + anchors.centerIn: parent + height: app.iconSize * 2 + width: height + visible: controlsInGroup.count == 0 + color: app.accentColor + name: "../images/view-grid-symbolic.svg" + } + ColumnLayout { anchors.fill: parent Repeater { - model: controlsInGroup + model: Math.min(controlsInGroup.count, parent.height / 50) delegate: Loader { id: controlLoader Layout.fillWidth: true Layout.leftMargin: app.margins / 2 Layout.rightMargin: app.margins / 2 + property string interfaceName: controlsInGroup.get(index).name sourceComponent: { - switch (model.name) { + switch (interfaceName) { case "simpleclosable": return closableDelegate case "light": return lightDelegate + case "mediacontroller": + return mediaControllerDelegate } } Binding { @@ -111,74 +131,77 @@ Item { Layout.fillHeight: true Layout.fillWidth: true } + } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: app.iconSize * 1.2 - Layout.alignment: Qt.AlignRight - color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, 0.05) + } + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: app.iconSize * 1.2 + color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, 0.05) - RowLayout { - anchors.fill: parent + RowLayout { + anchors.fill: parent - Repeater { - model: sensorsInGroup - delegate: Row { - ColorIcon { - height: app.iconSize * .8 - width: height - name: app.interfaceToIcon(model.name) - color: app.interfaceToColor(model.name) + Repeater { + model: sensorsInGroup + delegate: Row { + height: parent.height + + ColorIcon { + height: app.iconSize * .8 + width: height + name: app.interfaceToIcon(model.name) + color: app.interfaceToColor(model.name) + } + DevicesProxy { + id: innerProxy + engine: _engine + parentProxy: devicesInGroup + shownInterfaces: [model.name] + } + + Led { + visible: ["presencesensor"].indexOf(model.name) >= 0 + state: { + var stateName = null + switch (model.name) { + case "presencesensor": + stateName = "isPresent" + break; } - DevicesProxy { - id: innerProxy - engine: _engine - parentProxy: devicesInGroup - shownInterfaces: [model.name] + if (!stateName) { + return "off"; + } + var ret = false; + for (var i = 0; i < innerProxy.count; i++) { + ret |= innerProxy.get(i).states.getState(innerProxy.get(i).deviceClass.stateTypes.findByName(stateName).id).value + } + return ret ? "on" : "off"; + } + } + + Label { + height: parent.height + verticalAlignment: Text.AlignVCenter + text: { + var stateName = null; + switch (model.name) { + case "temperaturesensor": + stateName = "temperature"; + break; + case "lightsensor": + stateName = "lightIntensity" + break; + } + if (!stateName) { + return ""; } - Led { - visible: ["presencesensor"].indexOf(model.name) >= 0 - state: { - var stateName = null - switch (model.name) { - case "presencesensor": - stateName = "isPresent" - break; - } - if (!stateName) { - return "off"; - } - var ret = false; - for (var i = 0; i < innerProxy.count; i++) { - ret |= innerProxy.get(i).states.getState(innerProxy.get(i).deviceClass.stateTypes.findByName(stateName).id).value - } - return ret ? "on" : "off"; - } - } - - Label { - text: { - var stateName = null; - switch (model.name) { - case "temperaturesensor": - stateName = "temperature"; - break; - case "lightsensor": - stateName = "lightIntensity" - break; - } - if (!stateName) { - return ""; - } - - var ret = 0 - for (var i = 0; i < innerProxy.count; i++) { - ret += innerProxy.get(i).states.getState(innerProxy.get(i).deviceClass.stateTypes.findByName(stateName).id).value - } - return (ret / innerProxy.count).toFixed(1) - } + var ret = 0 + for (var i = 0; i < innerProxy.count; i++) { + ret += innerProxy.get(i).states.getState(innerProxy.get(i).deviceClass.stateTypes.findByName(stateName).id).value } + return (ret / innerProxy.count).toFixed(1) } } } @@ -351,4 +374,19 @@ Item { } } } + + Component { + id: mediaControllerDelegate + MediaControls { + property var devices: null + DevicesProxy { + id: mediaControllers + engine: _engine + parentProxy: devices + shownInterfaces: ["mediacontroller"] + } + + device: mediaControllers.parentProxy ? mediaControllers.get(0) : null + } + } } diff --git a/nymea-app/ui/mainviews/ScenesView.qml b/nymea-app/ui/mainviews/ScenesView.qml index 8bac0639..f036af43 100644 --- a/nymea-app/ui/mainviews/ScenesView.qml +++ b/nymea-app/ui/mainviews/ScenesView.qml @@ -5,11 +5,15 @@ import Nymea 1.0 import QtQuick.Controls.Material 2.2 import "../components" -Item { +MouseArea { id: root readonly property int count: interfacesGridView.count + // Prevent scroll events to swipe left/right in case they fall through the grid + preventStealing: true + onWheel: wheel.accepted = true + GridView { id: interfacesGridView anchors.fill: parent