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
}
}