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" Item { id: root property bool editMode: false readonly property int count: tagsProxy.count TagsProxyModel { id: tagsProxy tags: engine.tagsManager.tags filterTagId: "favorites" } GridView { id: gridView anchors.fill: parent anchors.margins: app.margins / 2 readonly property int minTileWidth: 180 readonly property int minTileHeight: 240 readonly property int tilesPerRow: root.width / minTileWidth cellWidth: gridView.width / tilesPerRow cellHeight: cellWidth model: tagsProxy delegate: MainPageTile { 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 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 alwaysRunToEnd: true NumberAnimation { from: 0; to: 3; target: delegateRoot; duration: 75; property: "rotation" } NumberAnimation { from: 3; to: -3; target: delegateRoot; duration: 150; property: "rotation" } NumberAnimation { from: -3; to: 0; target: delegateRoot; duration: 75; property: "rotation" } } } MouseArea { id: dragArea anchors.fill: parent enabled: root.editMode propagateComposedEvents: true property var dragOffset: ({}) property var draggedItem: null onPressed: { var item = gridView.itemAt(mouseX, mouseY) draggedItem = item; dragOffset = mapToItem(item, mouseX, mouseY) fakeDragItem.x = mouseX - dragOffset.x; fakeDragItem.y = mouseY - dragOffset.y; fakeDragItem.text = item.text fakeDragItem.iconName = item.iconName fakeDragItem.iconColor = item.iconColor; fakeDragItem.deviceId = item.device.id fakeDragItem.batteryCritical = item.batteryCritical fakeDragItem.disconnected = item.disconnected drag.target = fakeDragItem } onReleased: { drag.target = null draggedItem = null } onClicked: { root.editMode = false } } MainPageTile { id: fakeDragItem width: gridView.cellWidth height: gridView.cellHeight Drag.active: dragArea.drag.active visible: Drag.active property var deviceId } DropArea { id: dropArea anchors.fill: gridView property int from: -1 property int to: -1 onEntered: { var index = gridView.indexAt(drag.x + dragArea.dragOffset.x, drag.y + dragArea.dragOffset.y); from = index; to = index; } onPositionChanged: { var index = gridView.indexAt(drag.x + dragArea.dragOffset.x, drag.y + dragArea.dragOffset.y); if (to !== index && from !== index && index >= 0 && index <= tagsProxy.count) { to = index; print("should move", from, "to", to) for (var i = 0; i < tagsProxy.count; i++) { if (i < Math.min(from, to) || i > Math.max(from, to)) { // outside the range... don't touch continue; } var newIdx; if (i == from) { newIdx = to; } else { if (from < to) { // item is moved down the list newIdx = i - 1; } else { newIdx = i + 1; } } var tag = tagsProxy.get(i); engine.tagsManager.tagDevice(tag.deviceId, tag.tagId, newIdx); } from = index; } } } } 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 } } } }