From 6a329e94373b6fa39a533a864c7401e9f391a17a Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 29 Nov 2020 14:28:02 +0100 Subject: [PATCH] Fix garage views --- nymea-app/resources.qrc | 1 + nymea-app/ui/customviews/GarageController.qml | 133 +++++++++++++ nymea-app/ui/devicepages/GarageThingPage.qml | 122 +----------- nymea-app/ui/mainviews/GaragesView.qml | 174 +----------------- 4 files changed, 145 insertions(+), 285 deletions(-) create mode 100644 nymea-app/ui/customviews/GarageController.qml diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 0b796e7a..520a053f 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -231,5 +231,6 @@ ui/MainMenu.qml ui/components/NymeaItemDelegate.qml ui/components/NymeaSwipeDelegate.qml + ui/customviews/GarageController.qml diff --git a/nymea-app/ui/customviews/GarageController.qml b/nymea-app/ui/customviews/GarageController.qml new file mode 100644 index 00000000..5f77fa2c --- /dev/null +++ b/nymea-app/ui/customviews/GarageController.qml @@ -0,0 +1,133 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.1 +import "../components" +import Nymea 1.0 + +Item { + id: root + property Thing thing: null + + readonly property bool isImpulseBased: thing.thingClass.interfaces.indexOf("impulsegaragedoor") >= 0 + readonly property bool isStateful: thing.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0 + readonly property bool isExtended: thing.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0 + + // Stateful garagedoor + readonly property StateType stateStateType: thing.thingClass.stateTypes.findByName("state") + readonly property State stateState: stateStateType ? thing.states.getState(stateStateType.id) : null + + // Extended stateful garagedoor + readonly property StateType percentageStateType: thing.thingClass.stateTypes.findByName("percentage") + readonly property State percentageState: percentageStateType ? thing.states.getState(percentageStateType.id) : null + + + // Backward compatiblity with old garagegate interface + readonly property StateType intermediatePositionStateType: thing.thingClass.stateTypes.findByName("intermediatePosition") + readonly property var intermediatePositionState: intermediatePositionStateType ? thing.states.getState(intermediatePositionStateType.id) : null + + Component.onCompleted: { + print("Creating garage page. Impulse based:", isImpulseBased, "stateful:", isStateful, "extended:", isExtended, "legacy:", intermediatePositionState !== null) + } + + GridLayout { + anchors.fill: parent + columns: app.landscape ? 2 : 1 + columnSpacing: 0 + rowSpacing: 0 + + ColorIcon { + id: shutterImage + Layout.preferredWidth: app.landscape ? + Math.min(parent.width - shutterControlsContainer.minimumWidth, parent.height) - app.margins + : Math.min(Math.min(parent.width, 500), parent.height - shutterControlsContainer.minimumHeight) + Layout.preferredHeight: width + Layout.alignment: Qt.AlignHCenter + property string currentImage: { + if (root.isExtended) { + return NymeaUtils.pad(Math.round(root.percentageState.value / 10), 2) + "0" + } + if (root.intermediatePositionStateType) { + return root.stateState.value === "closed" ? "100" + : root.intermediatePositionState.value === false ? "000" : "050" + } + return "100" + } + name: "../images/garage/garage-" + currentImage + ".svg" + + Item { + id: arrows + anchors.centerIn: parent + width: app.iconSize * 2 + height: parent.height * .6 + clip: true + visible: root.stateStateType && (root.stateState.value === "opening" || root.stateState.value === "closing") + property bool up: root.stateState && root.stateState.value === "opening" + + // NumberAnimation doesn't reload to/from while it's running. If we switch from closing to opening or vice versa + // we need to somehow stop and start the animation + property bool animationHack: true + onAnimationHackChanged: { + if (!animationHack) hackTimer.start(); + } + Timer { id: hackTimer; interval: 1; onTriggered: arrows.animationHack = true } + Connections { target: root.stateState; onValueChanged: arrows.animationHack = false } + + NumberAnimation { + target: arrowColumn + property: "y" + duration: 500 + easing.type: Easing.Linear + from: arrows.up ? app.iconSize : -app.iconSize + to: arrows.up ? -app.iconSize : app.iconSize + loops: Animation.Infinite + running: arrows.animationHack && root.stateState && (root.stateState.value === "opening" || root.stateState.value === "closing") + } + + Column { + id: arrowColumn + width: parent.width + + Repeater { + model: arrows.height / app.iconSize + 1 + ColorIcon { + name: arrows.up ? "../images/up.svg" : "../images/down.svg" + width: parent.width + height: width + color: app.accentColor + } + } + } + } + } + + Item { + id: shutterControlsContainer + Layout.fillWidth: true + Layout.minimumWidth: minimumWidth + Layout.fillHeight: true + property int minimumWidth: app.iconSize * 10 + property int minimumHeight: app.iconSize * 2.5 + + ProgressButton { + anchors.centerIn: parent + visible: root.isImpulseBased + longpressEnabled: false + imageSource: "../images/closable-move.svg" + onClicked: { + var actionTypeId = root.thing.thingClass.actionTypes.findByName("triggerImpulse").id + print("Triggering impulse", actionTypeId) + engine.thingManager.executeAction(root.thing.id, actionTypeId) + } + } + + ShutterControls { + id: shutterControls + thing: root.thing + width: parent.width + anchors.centerIn: parent + spacing: (parent.width - app.iconSize*2*children.length) / (children.length - 1) + visible: !root.isImpulseBased + } + } + } +} diff --git a/nymea-app/ui/devicepages/GarageThingPage.qml b/nymea-app/ui/devicepages/GarageThingPage.qml index 0a16ba3c..5308b05d 100644 --- a/nymea-app/ui/devicepages/GarageThingPage.qml +++ b/nymea-app/ui/devicepages/GarageThingPage.qml @@ -39,126 +39,8 @@ import "../customviews" DevicePageBase { id: root - readonly property bool landscape: width > height - - readonly property bool isImpulseBased: thing.thingClass.interfaces.indexOf("impulsegaragedoor") >= 0 - readonly property bool isStateful: thing.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0 - readonly property bool isExtended: thing.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0 - - // Stateful garagedoor - readonly property StateType stateStateType: thing.thingClass.stateTypes.findByName("state") - readonly property State stateState: stateStateType ? thing.states.getState(stateStateType.id) : null - - // Extended stateful garagedoor - readonly property StateType percentageStateType: thing.thingClass.stateTypes.findByName("percentage") - readonly property State percentageState: percentageStateType ? thing.states.getState(percentageStateType.id) : null - - - // Backward compatiblity with old garagegate interface - readonly property StateType intermediatePositionStateType: thing.thingClass.stateTypes.findByName("intermediatePosition") - readonly property var intermediatePositionState: intermediatePositionStateType ? device.states.getState(intermediatePositionStateType.id) : null - - Component.onCompleted: { - print("Creating garage page. Impulse based:", isImpulseBased, "stateful:", isStateful, "extended:", isExtended, "legacy:", intermediatePositionState !== null) - } - - GridLayout { + GarageController { anchors.fill: parent - columns: root.landscape ? 2 : 1 - - ColorIcon { - id: shutterImage - Layout.preferredWidth: root.landscape ? - Math.min(parent.width - shutterControlsContainer.minimumWidth, parent.height) - app.margins - : Math.min(Math.min(parent.width, 500), parent.height - shutterControlsContainer.minimumHeight) - Layout.preferredHeight: width - Layout.alignment: Qt.AlignHCenter - property string currentImage: { - if (root.isExtended) { - return NymeaUtils.pad(Math.round(root.percentageState.value / 10), 2) + "0" - } - if (root.intermediatePositionStateType) { - return root.stateState.value === "closed" ? "100" - : root.intermediatePositionState.value === false ? "000" : "050" - } - return "100" - } - name: "../images/garage/garage-" + currentImage + ".svg" - - Item { - id: arrows - anchors.centerIn: parent - width: app.iconSize * 2 - height: parent.height * .6 - clip: true - visible: root.stateStateType && (root.stateState.value === "opening" || root.stateState.value === "closing") - property bool up: root.stateState && root.stateState.value === "opening" - - // NumberAnimation doesn't reload to/from while it's running. If we switch from closing to opening or vice versa - // we need to somehow stop and start the animation - property bool animationHack: true - onAnimationHackChanged: { - if (!animationHack) hackTimer.start(); - } - Timer { id: hackTimer; interval: 1; onTriggered: arrows.animationHack = true } - Connections { target: root.stateState; onValueChanged: arrows.animationHack = false } - - NumberAnimation { - target: arrowColumn - property: "y" - duration: 500 - easing.type: Easing.Linear - from: arrows.up ? app.iconSize : -app.iconSize - to: arrows.up ? -app.iconSize : app.iconSize - loops: Animation.Infinite - running: arrows.animationHack && root.stateState && (root.stateState.value === "opening" || root.stateState.value === "closing") - } - - Column { - id: arrowColumn - width: parent.width - - Repeater { - model: arrows.height / app.iconSize + 1 - ColorIcon { - name: arrows.up ? "../images/up.svg" : "../images/down.svg" - width: parent.width - height: width - color: app.accentColor - } - } - } - } - } - - Item { - id: shutterControlsContainer - Layout.fillWidth: true - Layout.margins: app.margins * 2 - Layout.fillHeight: true - property int minimumWidth: app.iconSize * 2.5 - property int minimumHeight: app.iconSize * 2.5 - - ProgressButton { - anchors.centerIn: parent - visible: root.isImpulseBased - longpressEnabled: false - imageSource: "../images/closable-move.svg" - onClicked: { - var actionTypeId = root.thing.thingClass.actionTypes.findByName("triggerImpulse").id - print("Triggering impulse", actionTypeId) - engine.thingManager.executeAction(root.thing.id, actionTypeId) - } - } - - ShutterControls { - id: shutterControls - device: root.device - width: parent.width - anchors.centerIn: parent - spacing: (parent.width - app.iconSize*2*children.length) / (children.length - 1) - visible: !root.isImpulseBased - } - } + thing: root.thing } } diff --git a/nymea-app/ui/mainviews/GaragesView.qml b/nymea-app/ui/mainviews/GaragesView.qml index 7b1f530c..ba97a708 100644 --- a/nymea-app/ui/mainviews/GaragesView.qml +++ b/nymea-app/ui/mainviews/GaragesView.qml @@ -32,10 +32,12 @@ import QtQuick 2.3 import QtQuick.Layouts 1.2 import QtQuick.Controls 2.2 import "../components" +import "../customviews" import Nymea 1.0 MainViewBase { id: root + title: swipeView.currentItem ? swipeView.currentItem.thing.name : "" readonly property bool landscape: width > height @@ -48,177 +50,17 @@ MainViewBase { SwipeView { id: swipeView anchors.fill: parent + anchors.bottomMargin: pageIndicator.visible ? pageIndicator.height : 0 Repeater { model: garagesFilterModel - Item { - id: garageGateView - width: swipeView.width + delegate: GarageController { height: swipeView.height - - readonly property Device thing: garagesFilterModel.get(index) - - - readonly property bool isImpulseBased: thing.thingClass.interfaces.indexOf("impulsegaragedoor") >= 0 - readonly property bool isStateful: thing.thingClass.interfaces.indexOf("statefulgaragedoor") >= 0 - readonly property bool isExtended: thing.thingClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0 - - // Stateful garagedoor - readonly property StateType stateStateType: thing.thingClass.stateTypes.findByName("state") - readonly property State stateState: stateStateType ? thing.states.getState(stateStateType.id) : null - - // Extended stateful garagedoor - readonly property StateType percentageStateType: thing.thingClass.stateTypes.findByName("percentage") - readonly property State percentageState: percentageStateType ? thing.states.getState(percentageStateType.id) : null - - - // Backward compatiblity with old garagegate interface - readonly property StateType intermediatePositionStateType: thing.thingClass.stateTypes.findByName("intermediatePosition") - readonly property var intermediatePositionState: intermediatePositionStateType ? device.states.getState(intermediatePositionStateType.id) : null - - // Some garages may also implement the light interface - readonly property var lightStateType: thing.thingClass.stateTypes.findByName("power") - readonly property var lightState: lightStateType ? thing.states.getState(lightStateType.id) : null - - ColumnLayout { - anchors.fill: parent - anchors.topMargin: app.margins - anchors.bottomMargin: app.margins - - Label { - Layout.fillWidth: true - font.pixelSize: app.largeFont - text: garageGateView.thing.name - horizontalAlignment: Text.AlignHCenter - elide: Text.ElideRight - } - - GridLayout { - columns: root.landscape ? 2 : 1 - - ColorIcon { - id: shutterImage - Layout.preferredWidth: root.landscape ? - Math.min(parent.width - shutterControlsContainer.minimumWidth, parent.height) - app.margins - : Math.min(Math.min(parent.width, 500), parent.height - shutterControlsContainer.minimumHeight) - Layout.preferredHeight: width - Layout.alignment: Qt.AlignHCenter - property string currentImage: { - if (garageGateView.isExtended) { - return app.pad(Math.round(garageGateView.percentageState.value / 10), 2) + "0" - } - if (garageGateView.intermediatePositionStateType) { - return garageGateView.stateState.value === "closed" ? "100" - : garageGateView.intermediatePositionState.value === false ? "000" : "050" - } - return "100" - } - name: "../images/garage/garage-" + currentImage + ".svg" - - Item { - id: arrows - anchors.centerIn: parent - width: app.iconSize * 2 - height: parent.height * .6 - clip: true - visible: garageGateView.stateStateType && (garageGateView.stateState.value === "opening" || garageGateView.stateState.value === "closing") - property bool up: garageGateView.stateState && garageGateView.stateState.value === "opening" - - // NumberAnimation doesn't reload to/from while it's running. If we switch from closing to opening or vice versa - // we need to somehow stop and start the animation - property bool animationHack: true - onAnimationHackChanged: { - if (!animationHack) hackTimer.start(); - } - Timer { id: hackTimer; interval: 1; onTriggered: arrows.animationHack = true } - Connections { target: garageGateView.stateState; onValueChanged: arrows.animationHack = false } - - NumberAnimation { - target: arrowColumn - property: "y" - duration: 500 - easing.type: Easing.Linear - from: arrows.up ? app.iconSize : -app.iconSize - to: arrows.up ? -app.iconSize : app.iconSize - loops: Animation.Infinite - running: arrows.animationHack && garageGateView.stateState && (garageGateView.stateState.value === "opening" || garageGateView.stateState.value === "closing") - } - - Column { - id: arrowColumn - width: parent.width - - Repeater { - model: arrows.height / app.iconSize + 1 - ColorIcon { - name: arrows.up ? "../images/up.svg" : "../images/down.svg" - width: parent.width - height: width - color: app.accentColor - } - } - } - } - } - - Item { - id: shutterControlsContainer - Layout.fillWidth: true - Layout.margins: app.margins * 2 - Layout.fillHeight: true - property int minimumWidth: app.iconSize * 2.5 * (garageGateView.lightState ? 4 : 3) - property int minimumHeight: app.iconSize * 2.5 - - ItemDelegate { - height: app.iconSize * 2 - width: height - anchors.centerIn: parent - visible: garageGateView.isImpulseBased - ColorIcon { - anchors.fill: parent - name: "../images/closable-move.svg" - anchors.margins: app.margins - } - onClicked: { - var actionTypeId = garageGateView.thing.thingClass.actionTypes.findByName("triggerImpulse").id - print("Triggering impulse", actionTypeId) - engine.thingManager.executeAction(garageGateView.thing.id, actionTypeId) - } - } - - ShutterControls { - id: shutterControls - device: garageGateView.thing - anchors.centerIn: parent - spacing: (parent.width - app.iconSize*2*children.length) / (children.length - 1) - visible: !garageGateView.isImpulseBased - - ItemDelegate { - width: app.iconSize * 2 - height: width - visible: garageGateView.lightStateType !== null - - ColorIcon { - anchors.fill: parent - anchors.margins: app.margins - name: "../images/light-" + (garageGateView.lightState && garageGateView.lightState.value === true ? "on" : "off") + ".svg" - color: garageGateView.lightState && garageGateView.lightState.value === true ? Material.accent : keyColor - } - onClicked: { - var params = []; - var param = {}; - param["paramTypeId"] = garageGateView.lightStateType.id; - param["value"] = !garageGateView.lightState.value; - params.push(param) - engine.deviceManager.executeAction(garageGateView.device.id, garageGateView.lightStateType.id, params) - } - } - } - } - } - } + width: swipeView.width + thing: garagesFilterModel.get(index) } + } } @@ -234,8 +76,10 @@ MainViewBase { } PageIndicator { + id: pageIndicator anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter } count: garagesFilterModel.count currentIndex: swipeView.currentIndex + visible: count > 1 } }