diff --git a/libnymea-app-core/devicemanager.cpp b/libnymea-app-core/devicemanager.cpp
index 626d01dc..1f581f39 100644
--- a/libnymea-app-core/devicemanager.cpp
+++ b/libnymea-app-core/devicemanager.cpp
@@ -298,7 +298,7 @@ void DeviceManager::editDeviceResponse(const QVariantMap ¶ms)
void DeviceManager::executeActionResponse(const QVariantMap ¶ms)
{
- qDebug() << "Execute Action response" << params;
+// qDebug() << "Execute Action response" << params;
emit executeActionReply(params);
}
@@ -371,7 +371,7 @@ void DeviceManager::editDevice(const QUuid &deviceId, const QString &name)
int DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList ¶ms)
{
- qDebug() << "JsonRpc: execute action " << deviceId.toString() << actionTypeId.toString() << params;
+// qDebug() << "JsonRpc: execute action " << deviceId.toString() << actionTypeId.toString() << params;
QVariantMap p;
p.insert("deviceId", deviceId.toString());
p.insert("actionTypeId", actionTypeId.toString());
@@ -379,6 +379,5 @@ int DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionTypeI
p.insert("params", params);
}
- qDebug() << "Params:" << p;
return m_jsonClient->sendCommand("Actions.ExecuteAction", p, this, "executeActionResponse");
}
diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc
index e83d1940..68f4fa09 100644
--- a/nymea-app/images.qrc
+++ b/nymea-app/images.qrc
@@ -169,5 +169,6 @@
ui/images/stock_video.svg
ui/images/sensors/presence.svg
ui/images/powersocket.svg
+ ui/images/dial.svg
diff --git a/nymea-app/platformhelper.cpp b/nymea-app/platformhelper.cpp
index 2158ef74..de4fd04f 100644
--- a/nymea-app/platformhelper.cpp
+++ b/nymea-app/platformhelper.cpp
@@ -5,3 +5,4 @@ PlatformHelper::PlatformHelper(QObject *parent) : QObject(parent)
{
}
+
diff --git a/nymea-app/platformhelper.h b/nymea-app/platformhelper.h
index c936e9c8..a5a4a22e 100644
--- a/nymea-app/platformhelper.h
+++ b/nymea-app/platformhelper.h
@@ -13,6 +13,13 @@ class PlatformHelper : public QObject
Q_PROPERTY(QString machineHostname READ machineHostname CONSTANT)
public:
+ enum HapticsFeedback {
+ HapticsFeedbackSelection,
+ HapticsFeedbackImpact,
+ HapticsFeedbackNotification
+ };
+ Q_ENUM(HapticsFeedback)
+
explicit PlatformHelper(QObject *parent = nullptr);
virtual ~PlatformHelper() = default;
@@ -24,6 +31,8 @@ public:
virtual QString deviceModel() const = 0;
virtual QString deviceManufacturer() const = 0;
+ Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbackType) = 0;
+
signals:
void permissionsRequestFinished();
};
diff --git a/nymea-app/platformintegration/android/platformhelperandroid.cpp b/nymea-app/platformintegration/android/platformhelperandroid.cpp
index d9f6d4d1..cde0ece6 100644
--- a/nymea-app/platformintegration/android/platformhelperandroid.cpp
+++ b/nymea-app/platformintegration/android/platformhelperandroid.cpp
@@ -44,6 +44,24 @@ QString PlatformHelperAndroid::deviceManufacturer() const
return QAndroidJniObject::callStaticObjectMethod("io/guh/nymeaapp/NymeaAppActivity","deviceManufacturer").toString();
}
+void PlatformHelperAndroid::vibrate(PlatformHelper::HapticsFeedback feedbackType)
+{
+ int duration;
+ switch (feedbackType) {
+ case HapticsFeedbackSelection:
+ duration = 15;
+ break;
+ case HapticsFeedbackImpact:
+ duration = 25;
+ break;
+ case HapticsFeedbackNotification:
+ duration = 500;
+ break;
+ }
+
+ QtAndroid::androidActivity().callMethod("vibrate","(I)V", duration);
+}
+
void PlatformHelperAndroid::permissionRequestFinished(const QtAndroid::PermissionResultMap &result)
{
foreach (const QString &key, result.keys()) {
diff --git a/nymea-app/platformintegration/android/platformhelperandroid.h b/nymea-app/platformintegration/android/platformhelperandroid.h
index f35e7eb3..06dc5053 100644
--- a/nymea-app/platformintegration/android/platformhelperandroid.h
+++ b/nymea-app/platformintegration/android/platformhelperandroid.h
@@ -19,6 +19,8 @@ public:
QString deviceModel() const override;
QString deviceManufacturer() const override;
+ Q_INVOKABLE void vibrate(HapticsFeedback feedbackType) override;
+
private:
static void permissionRequestFinished(const QtAndroid::PermissionResultMap &);
diff --git a/nymea-app/platformintegration/generic/platformhelpergeneric.cpp b/nymea-app/platformintegration/generic/platformhelpergeneric.cpp
index 51d35d8b..9c9c7145 100644
--- a/nymea-app/platformintegration/generic/platformhelpergeneric.cpp
+++ b/nymea-app/platformintegration/generic/platformhelpergeneric.cpp
@@ -38,3 +38,8 @@ QString PlatformHelperGeneric::deviceManufacturer() const
{
return QSysInfo::productType();
}
+
+void PlatformHelperGeneric::vibrate(PlatformHelper::HapticsFeedback feedbyckType)
+{
+ Q_UNUSED(feedbyckType)
+}
diff --git a/nymea-app/platformintegration/generic/platformhelpergeneric.h b/nymea-app/platformintegration/generic/platformhelpergeneric.h
index c0583a2d..82bb4461 100644
--- a/nymea-app/platformintegration/generic/platformhelpergeneric.h
+++ b/nymea-app/platformintegration/generic/platformhelpergeneric.h
@@ -18,6 +18,7 @@ public:
virtual QString deviceModel() const override;
virtual QString deviceManufacturer() const override;
+ Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbyckType) override;
signals:
public slots:
diff --git a/nymea-app/platformintegration/ios/platformhelperios.cpp b/nymea-app/platformintegration/ios/platformhelperios.cpp
index 19c0bbca..3885d710 100644
--- a/nymea-app/platformintegration/ios/platformhelperios.cpp
+++ b/nymea-app/platformintegration/ios/platformhelperios.cpp
@@ -47,3 +47,19 @@ QString PlatformHelperIOS::deviceManufacturer() const
{
return QString("iPhone");
}
+
+void PlatformHelperIOS::vibrate(PlatformHelper::HapticsFeedback feedbackType)
+{
+ switch (feedbackType) {
+ case HapticsFeedbackSelection:
+ generateSelectionFeedback();
+ break;
+ case HapticsFeedbackImpact:
+ generateImpactFeedback();
+ break;
+ case HapticsFeedbackNotification:
+ generateNotificationFeedback();
+ break;
+ }
+}
+
diff --git a/nymea-app/platformintegration/ios/platformhelperios.h b/nymea-app/platformintegration/ios/platformhelperios.h
index 26813f40..b8af806d 100644
--- a/nymea-app/platformintegration/ios/platformhelperios.h
+++ b/nymea-app/platformintegration/ios/platformhelperios.h
@@ -19,10 +19,16 @@ public:
virtual QString deviceModel() const override;
virtual QString deviceManufacturer() const override;
+ Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbackType) override;
+
private:
// defined in platformhelperios.mm
QString readKeyChainEntry(const QString &service, const QString &key);
void writeKeyChainEntry(const QString &service, const QString &key, const QString &value);
+
+ void generateSelectionFeedback();
+ void generateImpactFeedback();
+ void generateNotificationFeedback();
};
#endif // PLATFORMHELPERIOS_H
diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc
index a83a2858..8197c0b2 100644
--- a/nymea-app/resources.qrc
+++ b/nymea-app/resources.qrc
@@ -147,5 +147,19 @@
ui/devicepages/PowersocketDevicePage.qml
ui/devicelistpages/PowerSocketsDeviceListPage.qml
ui/components/GroupedListView.qml
+ ui/devicepages/HeatingDevicePage.qml
+ ui/delegates/statedelegates/LedDelegate.qml
+ ui/delegates/statedelegates/LabelDelegate.qml
+ ui/delegates/statedelegates/NumberLabelDelegate.qml
+ ui/delegates/statedelegates/TextFieldDelegate.qml
+ ui/delegates/statedelegates/ListDelegate.qml
+ ui/delegates/statedelegates/CheckboxDelegate.qml
+ ui/delegates/statedelegates/SwitchDelegate.qml
+ ui/delegates/statedelegates/SpinBoxDelegate.qml
+ ui/delegates/statedelegates/ComboBoxDelegate.qml
+ ui/delegates/statedelegates/ColorDelegate.qml
+ ui/delegates/statedelegates/DateTimeDelegate.qml
+ ui/components/Dial.qml
+ ui/delegates/statedelegates/SliderDelegate.qml
diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml
index 003f012e..8883c19e 100644
--- a/nymea-app/ui/Nymea.qml
+++ b/nymea-app/ui/Nymea.qml
@@ -54,7 +54,7 @@ ApplicationWindow {
rootItem.handleCloseEvent(close)
}
- property var supportedInterfaces: ["light", "weather", "media", "garagegate", "awning", "shutter", "blind", "heating", "powersocket", "sensor", "smartmeter", "evcharger", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
+ property var supportedInterfaces: ["light", "weather", "media", "garagegate", "awning", "shutter", "blind", "powersocket", "heating", "sensor", "smartmeter", "evcharger", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
function interfaceToString(name) {
switch(name) {
case "light":
@@ -206,6 +206,8 @@ ApplicationWindow {
case "heating":
case "extendedheating":
return Qt.resolvedUrl("images/radiator.svg")
+ case "thermostat":
+ return Qt.resolvedUrl("images/dial.svg")
case "evcharger":
case "extendedevcharger":
return Qt.resolvedUrl("images/ev-charger.svg")
@@ -231,7 +233,9 @@ ApplicationWindow {
"smartmeterproducer": "lightgreen",
"smartmeterconsumer": "orange",
"extendedsmartmeterproducer": "blue",
- "extendedsmartmeterconsumer": "blue"
+ "extendedsmartmeterconsumer": "blue",
+ "heating" : "gainsboro",
+ "thermostat": "dodgerblue"
}
function interfaceToColor(name) {
@@ -278,6 +282,8 @@ ApplicationWindow {
page = "ButtonDevicePage.qml";
} else if (interfaceList.indexOf("weather") >= 0) {
page = "WeatherDevicePage.qml";
+ } else if (interfaceList.indexOf("heating") >= 0 || interfaceList.indexOf("thermostat") >= 0) {
+ page = "HeatingDevicePage.qml";
} else if (interfaceList.indexOf("sensor") >= 0) {
page = "SensorDevicePage.qml";
} else if (interfaceList.indexOf("inputtrigger") >= 0) {
diff --git a/nymea-app/ui/components/Dial.qml b/nymea-app/ui/components/Dial.qml
new file mode 100644
index 00000000..d9fc343e
--- /dev/null
+++ b/nymea-app/ui/components/Dial.qml
@@ -0,0 +1,296 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import Nymea 1.0
+import QtQuick.Layouts 1.2
+import QtQuick.Controls.Material 2.2
+
+ColumnLayout {
+ id: dial
+
+ property Device device: null
+ property StateType stateType: null
+
+ property bool showValueLabel: true
+ property int steps: 10
+ property color color: app.accentColor
+ property int maxAngle: 235
+
+ // value : max = angle : maxAngle
+ function valueToAngle(value) {
+ return (value - from) * maxAngle / (to - from)
+ }
+ function angleToValue(angle) {
+ return (to - from) * angle / maxAngle + from
+ }
+
+ readonly property State deviceState: device && stateType ? device.states.getState(stateType.id) : null
+ readonly property double from: dial.stateType.minValue
+ readonly property double to: dial.stateType.maxValue
+ readonly property double anglePerStep: maxAngle / dial.steps
+ readonly property double startAngle: -(dial.steps * dial.anglePerStep) / 2
+
+ readonly property StateType powerStateType: dial.device.deviceClass.stateTypes.findByName("power")
+ readonly property State powerState: powerStateType ? dial.device.states.getState(powerStateType.id) : null
+
+ QtObject {
+ id: d
+ property int pendingActionId: -1
+ property real valueCache: 0
+ property bool valueCacheDirty: false
+
+ property bool busy: rotateMouseArea.pressed || pendingActionId != -1 || valueCacheDirty
+
+ property color onColor: dial.color
+ property color offColor: "#808080"
+ property color poweredColor: dial.powerStateType
+ ? (dial.powerState.value === true ? onColor : offColor)
+ : onColor
+
+
+ function enqueueSetValue(value) {
+ if (d.pendingActionId == -1) {
+ executeAction(value);
+ return;
+ } else {
+ valueCache = value
+ valueCacheDirty = true;
+ }
+ }
+
+ function executeAction(value) {
+ var params = []
+ var param = {}
+ param["paramTypeId"] = dial.stateType.id
+ param["value"] = value
+ params.push(param)
+ d.pendingActionId = engine.deviceManager.executeAction(dial.device.id, dial.stateType.id, params)
+ }
+ }
+ Connections {
+ target: engine.deviceManager
+ onExecuteActionReply: {
+ d.pendingActionId = -1
+ if (d.valueCacheDirty) {
+ d.executeAction(d.valueCache)
+ d.valueCacheDirty = false;
+ }
+ }
+ }
+
+ Component.onCompleted: rotationButton.rotation = dial.valueToAngle(dial.deviceState.value)
+ Connections {
+ target: dial.deviceState
+ onValueChanged: {
+ if (!d.busy) {
+ rotationButton.rotation = dial.valueToAngle(dial.deviceState.value)
+ }
+ }
+ }
+
+ Label {
+ id: topLabel
+ Layout.fillWidth: true
+ text: rotateMouseArea.currentValue + dial.stateType.unitString
+ font.pixelSize: app.largeFont * 1.5
+ horizontalAlignment: Text.AlignHCenter
+ visible: dial.showValueLabel && dial.stateType !== null
+ }
+
+ Item {
+ id: buttonContainer
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ Item {
+ id: innerDial
+
+ height: Math.min(parent.height, parent.width) * .9
+ width: height
+ anchors.centerIn: parent
+ rotation: dial.startAngle
+
+ Rectangle {
+ height: parent.height * .8
+ width: height
+ anchors.centerIn: parent
+ radius: height / 2
+ border.color: app.foregroundColor
+ opacity: .3
+ border.width: width * .025
+ color: "transparent"
+ }
+
+ Rectangle {
+ anchors.fill: rotationButton
+ radius: height / 2
+ border.color: app.foregroundColor
+ border.width: 2
+ color: "transparent"
+ opacity: rotateMouseArea.pressed && !rotateMouseArea.grabbed ? .7 : 1
+ }
+
+ Item {
+ id: rotationButton
+ height: parent.height * .75
+ width: height
+ anchors.centerIn: parent
+ visible: dial.stateType !== null
+ Behavior on rotation {
+ NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
+ enabled: !rotateMouseArea.pressed && !d.busy
+ }
+
+ Item {
+ id: handle
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: parent.height * .3
+ width: height
+
+ Rectangle {
+ height: parent.height * .6
+ width: innerDial.width * 0.02
+ radius: width / 2
+ anchors.centerIn: parent
+ color: d.poweredColor
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+ }
+ }
+
+ Repeater {
+ id: indexLEDs
+ model: dial.steps + 1
+
+ Item {
+ height: parent.height
+ width: parent.width * .04
+ anchors.centerIn: parent
+ rotation: dial.anglePerStep * index
+ visible: dial.stateType !== null
+
+ Rectangle {
+ width: parent.width
+ height: width
+ radius: width / 2
+ color: dial.angleToValue(parent.rotation) <= dial.deviceState.value ? d.poweredColor : d.offColor
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+ }
+ }
+ }
+
+ Label {
+ anchors { left: innerDial.left; bottom: innerDial.bottom; bottomMargin: innerDial.height * .1 }
+ text: "MIN"
+ font.pixelSize: innerDial.height * .06
+ visible: dial.stateType !== null
+ }
+
+ Label {
+ anchors { right: innerDial.right; bottom: innerDial.bottom; bottomMargin: innerDial.height * .1 }
+ text: "MAX"
+ font.pixelSize: innerDial.height * .06
+ visible: dial.stateType !== null
+ }
+
+ ColorIcon {
+ anchors.centerIn: innerDial
+ height: innerDial.height * .2
+ width: height
+ name: "../images/system-shutdown.svg"
+ visible: dial.powerStateType !== null
+ color: d.poweredColor
+ Behavior on color { ColorAnimation { duration: 200 } }
+ }
+
+ MouseArea {
+ id: rotateMouseArea
+ anchors.fill: innerDial
+ onPressedChanged: PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackImpact)
+
+ property bool grabbed: false
+ onPressed: {
+ var mappedToHandle = mapToItem(handle, mouseX, mouseY);
+ if (mappedToHandle.x >= 0
+ && mappedToHandle.x < handle.width
+ && mappedToHandle.y >= 0
+ && mappedToHandle.y < handle.height
+ ) {
+ grabbed = true;
+ return;
+ }
+ }
+ onCanceled: grabbed = false;
+
+ property bool dragging: false
+ onReleased: {
+ grabbed = false;
+ if (dial.powerStateType && !dragging) {
+ var params = []
+ var param = {}
+ param["paramTypeId"] = dial.powerStateType.id
+ param["value"] = !dial.powerState.value
+ params.push(param)
+ engine.deviceManager.executeAction(dial.device.id, dial.powerStateType.id, params)
+ }
+ dragging = false;
+ }
+
+ readonly property int decimals: dial.stateType.type.toLowerCase() === "int" ? 0 : 1
+ property var currentValue: dial.deviceState.value.toFixed(decimals)
+ property date lastVibration: new Date()
+ onPositionChanged: {
+ if (!grabbed) {
+ return;
+ }
+ var angle = calculateAngle(mouseX, mouseY)
+ angle = (360 + angle - dial.startAngle) % 360;
+
+ if (angle > 360 - ((360 - dial.maxAngle) / 2)) {
+ angle = 0;
+ } else if (angle > dial.maxAngle) {
+ angle = dial.maxAngle
+ }
+
+ var newValue = Math.round(dial.angleToValue(angle) * 2) / 2;
+ rotationButton.rotation = angle;
+ newValue = newValue.toFixed(decimals)
+
+ if (newValue != currentValue) {
+ if (!dragging) {
+ dragging = true;
+ }
+
+ currentValue = newValue;
+ if (newValue <= dial.stateType.minValue || newValue >= dial.stateType.maxValue) {
+ PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackImpact)
+ } else {
+ if (lastVibration.getTime() + 35 < new Date()) {
+ PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
+ }
+ lastVibration = new Date()
+ }
+ d.enqueueSetValue(newValue);
+ }
+ }
+
+ function calculateAngle(mouseX, mouseY) {
+ // transform coords to center of dial
+ mouseX -= innerDial.width / 2
+ mouseY -= innerDial.height / 2
+
+ var rad = Math.atan(mouseY / mouseX);
+ var angle = rad * 180 / Math.PI
+
+ angle += 90;
+
+ if (mouseX < 0 && mouseY >= 0) angle = 180 + angle;
+ if (mouseX < 0 && mouseY < 0) angle = 180 + angle;
+
+ return angle;
+ }
+ }
+ }
+
+
+}
diff --git a/nymea-app/ui/components/ThrottledSlider.qml b/nymea-app/ui/components/ThrottledSlider.qml
index 80e45af6..434889a2 100644
--- a/nymea-app/ui/components/ThrottledSlider.qml
+++ b/nymea-app/ui/components/ThrottledSlider.qml
@@ -10,6 +10,9 @@ Item {
property alias from: slider.from
property alias to: slider.to
property alias stepSize: slider.stepSize
+
+ readonly property real rawValue: slider.value
+
signal moved(real value);
Slider {
diff --git a/nymea-app/ui/delegates/ParamDelegate.qml b/nymea-app/ui/delegates/ParamDelegate.qml
index c488f9f9..a76cf1a9 100644
--- a/nymea-app/ui/delegates/ParamDelegate.qml
+++ b/nymea-app/ui/delegates/ParamDelegate.qml
@@ -31,6 +31,7 @@ ItemDelegate {
id: loader
Layout.fillWidth: sourceComponent === textFieldComponent
sourceComponent: {
+ print("loading paramdelegate:", root.writable, root.paramType.type)
if (!root.writable) {
return stringComponent;
}
@@ -39,7 +40,8 @@ ItemDelegate {
case "bool":
return boolComponent;
case "int":
- return spinnerComponent;
+ case "double":
+ return sliderComponent;
case "string":
case "qstring":
if (root.paramType.allowedValues.length > 0) {
@@ -104,9 +106,6 @@ ItemDelegate {
id: sliderComponent
RowLayout {
spacing: app.margins
- Label {
- text: root.paramType.minValue
- }
Slider {
id: slider
Layout.fillWidth: true
@@ -114,12 +113,13 @@ ItemDelegate {
to: root.paramType.maxValue
value: root.param.value
stepSize: {
- switch (root.paramType.type.toLowerCase()) {
- case "int":
- return 1;
+ var ret = 1
+ for (var i = 0; i < decimals; i++) {
+ ret /= 10;
}
- return 0.01;
+ return ret;
}
+ property int decimals: root.paramType.type.toLocaleLowerCase() === "int" ? 0 : 1
onMoved: {
var newValue
@@ -134,7 +134,7 @@ ItemDelegate {
}
}
Label {
- text: root.paramType.maxValue
+ text: root.param.value.toFixed(slider.decimals) + root.paramType.unitString
}
}
diff --git a/nymea-app/ui/delegates/statedelegates/CheckboxDelegate.qml b/nymea-app/ui/delegates/statedelegates/CheckboxDelegate.qml
new file mode 100644
index 00000000..1942a410
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/CheckboxDelegate.qml
@@ -0,0 +1,12 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+CheckBox {
+ property var value
+ checked: value === true
+ enabled: false
+}
diff --git a/nymea-app/ui/delegates/statedelegates/ColorDelegate.qml b/nymea-app/ui/delegates/statedelegates/ColorDelegate.qml
new file mode 100644
index 00000000..18083220
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/ColorDelegate.qml
@@ -0,0 +1,61 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+Item {
+ id: colorComponentItem
+ implicitWidth: app.iconSize * 2
+ implicitHeight: app.iconSize
+ property var value
+ signal changed(var value)
+
+ Pane {
+ anchors.fill: parent
+ topPadding: 0
+ bottomPadding: 0
+ leftPadding: 0
+ rightPadding: 0
+ Material.elevation: 1
+ contentItem: Rectangle {
+ color: colorComponentItem.value
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ var pos = colorComponentItem.mapToItem(root, 0, colorComponentItem.height)
+ print("opening", colorComponentItem.value)
+ var colorPicker = colorPickerComponent.createObject(root, {preferredY: pos.y, colorValue: colorComponentItem.value })
+ colorPicker.open()
+ }
+ }
+ }
+ }
+
+ Component {
+ id: colorPickerComponent
+ Dialog {
+ id: colorPickerDialog
+ modal: true
+ x: (parent.width - width) / 2
+ y: Math.min(preferredY, parent.height - height)
+ width: parent.width - app.margins * 2
+ height: 200
+ padding: app.margins
+ property var colorValue
+ property int preferredY: 0
+ contentItem: ColorPicker {
+ color: colorPickerDialog.colorValue
+ property var lastSentTime: new Date()
+ onColorChanged: {
+ var currentTime = new Date();
+ if (pressed && currentTime - lastSentTime > 200) {
+ colorComponentItem.changed(color);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/nymea-app/ui/delegates/statedelegates/ComboBoxDelegate.qml b/nymea-app/ui/delegates/statedelegates/ComboBoxDelegate.qml
new file mode 100644
index 00000000..0b5662d7
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/ComboBoxDelegate.qml
@@ -0,0 +1,17 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+ComboBox {
+ property var value
+ property var possibleValues
+
+ signal changed(var value)
+ model: possibleValues
+ currentIndex: possibleValues.indexOf(value)
+ onActivated: changed(model[index])
+ Component.onCompleted: print("completed. values", possibleValues, "value", value)
+}
diff --git a/nymea-app/ui/delegates/statedelegates/DateTimeDelegate.qml b/nymea-app/ui/delegates/statedelegates/DateTimeDelegate.qml
new file mode 100644
index 00000000..ca427599
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/DateTimeDelegate.qml
@@ -0,0 +1,12 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+Label {
+ property var value
+ text: Qt.formatDateTime(new Date(value * 1000), Qt.DefaultLocaleShortDate)
+ horizontalAlignment: Text.AlignRight
+}
diff --git a/nymea-app/ui/delegates/statedelegates/LabelDelegate.qml b/nymea-app/ui/delegates/statedelegates/LabelDelegate.qml
new file mode 100644
index 00000000..8ddf0103
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/LabelDelegate.qml
@@ -0,0 +1,12 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+Label {
+ property var value
+ text: value
+ horizontalAlignment: Text.AlignRight
+}
diff --git a/nymea-app/ui/delegates/statedelegates/LedDelegate.qml b/nymea-app/ui/delegates/statedelegates/LedDelegate.qml
new file mode 100644
index 00000000..22fb94a5
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/LedDelegate.qml
@@ -0,0 +1,11 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+Led {
+ property bool value
+ on: value === true
+}
diff --git a/nymea-app/ui/delegates/statedelegates/ListDelegate.qml b/nymea-app/ui/delegates/statedelegates/ListDelegate.qml
new file mode 100644
index 00000000..66c659e5
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/ListDelegate.qml
@@ -0,0 +1,11 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+Label {
+ property var value
+ text: value.join(", ")
+}
diff --git a/nymea-app/ui/delegates/statedelegates/NumberLabelDelegate.qml b/nymea-app/ui/delegates/statedelegates/NumberLabelDelegate.qml
new file mode 100644
index 00000000..2ac99a57
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/NumberLabelDelegate.qml
@@ -0,0 +1,12 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+Label {
+ property var value
+ text: Math.round(value * 100) / 100
+ horizontalAlignment: Text.AlignRight
+}
diff --git a/nymea-app/ui/delegates/statedelegates/SliderDelegate.qml b/nymea-app/ui/delegates/statedelegates/SliderDelegate.qml
new file mode 100644
index 00000000..54dc2848
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/SliderDelegate.qml
@@ -0,0 +1,57 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+RowLayout {
+ id: root
+ width: 150
+ signal changed(var value)
+
+ property var value
+ property alias from: slider.from
+ property alias to: slider.to
+
+ property StateType stateType
+
+ readonly property int decimals: root.stateType.type.toLowerCase() === "int" ? 0 : 1
+
+ Slider {
+ id: slider
+ Layout.fillWidth: true
+ value: root.value
+ stepSize: {
+ var ret = 1
+ for (var i = 0; i < root.decimals; i++) {
+ ret /= 10;
+ }
+ return ret;
+ }
+ property var lastVibration: new Date()
+ property var lastChange: root.value
+ onMoved: {
+ // Emits moved more often than stepsize, we only want to act when we actually emitted value change
+ if (value === lastChange) {
+ return;
+ }
+ lastChange = value;
+
+ if (value === from || value === to) {
+ PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackImpact)
+ } else {
+ if (lastVibration.getTime() + 35 < new Date()) {
+ PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection)
+ }
+ lastVibration = new Date()
+ }
+
+
+ root.changed(value)
+ }
+ }
+ Label {
+ text: slider.value.toFixed(root.decimals)
+ }
+}
diff --git a/nymea-app/ui/delegates/statedelegates/SpinBoxDelegate.qml b/nymea-app/ui/delegates/statedelegates/SpinBoxDelegate.qml
new file mode 100644
index 00000000..9b6e47fb
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/SpinBoxDelegate.qml
@@ -0,0 +1,16 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+SpinBox {
+ width: 150
+ signal changed(var value)
+ stepSize: (to - from) / 10
+ editable: true
+ onValueModified: {
+ changed(value)
+ }
+}
diff --git a/nymea-app/ui/delegates/statedelegates/SwitchDelegate.qml b/nymea-app/ui/delegates/statedelegates/SwitchDelegate.qml
new file mode 100644
index 00000000..f4ccbab1
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/SwitchDelegate.qml
@@ -0,0 +1,15 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+Switch {
+ property var value
+ signal changed(var value)
+ checked: value === true
+ onClicked: {
+ changed(checked)
+ }
+}
diff --git a/nymea-app/ui/delegates/statedelegates/TextFieldDelegate.qml b/nymea-app/ui/delegates/statedelegates/TextFieldDelegate.qml
new file mode 100644
index 00000000..35fc098e
--- /dev/null
+++ b/nymea-app/ui/delegates/statedelegates/TextFieldDelegate.qml
@@ -0,0 +1,11 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../../components"
+
+TextField {
+ property var value
+ text: value
+}
diff --git a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml
index 4f0d60d3..541e1044 100644
--- a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml
+++ b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml
@@ -84,6 +84,8 @@ DeviceListPageBase {
ListElement { interfaceName: "co2sensor"; stateName: "co2" }
ListElement { interfaceName: "daylightsensor"; stateName: "daylight" }
ListElement { interfaceName: "presencesensor"; stateName: "isPresent" }
+ ListElement { interfaceName: "heating"; stateName: "power" }
+ ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" }
}
delegate: RowLayout {
@@ -114,9 +116,14 @@ DeviceListPageBase {
font.pixelSize: app.smallFont
}
Led {
+ id: led
visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() == "bool"
on: visible && sensorValueDelegate.stateValue.value === true
}
+ Item {
+ Layout.preferredWidth: led.width
+ visible: led.visible
+ }
}
}
}
diff --git a/nymea-app/ui/devicepages/GenericDevicePage.qml b/nymea-app/ui/devicepages/GenericDevicePage.qml
index 7bf37dba..f2d0aae7 100644
--- a/nymea-app/ui/devicepages/GenericDevicePage.qml
+++ b/nymea-app/ui/devicepages/GenericDevicePage.qml
@@ -113,89 +113,129 @@ DevicePageBase {
Label {
Layout.fillWidth: true
+ Layout.minimumWidth: parent.width / 2
text: stateDelegate.stateType.displayName
elide: Text.ElideRight
}
Loader {
id: stateDelegateLoader
- sourceComponent: {
- switch (stateType.type.toLowerCase()) {
- case "string":
- if (stateDelegate.writable) {
- if (stateDelegate.stateType.allowedValues !== undefined) {
- return comboBoxComponent
- }
- return textFieldComponent;
- } else {
- return labelComponent;
- }
- case "stringlist":
- return listComponent;
- case "bool":
- if (stateDelegate.writable) {
- return switchComponent;
- } else {
- return ledComponent;
- }
- case "int":
- case "double":
- if (stateDelegate.stateType.unit === Types.UnitUnixTime) {
- return dateTimeComponent;
- }
-
- if (stateDelegate.writable) {
- return spinBoxComponent;
- }
- return numberLabelComponent;
- case "color":
- return colorComponent;
- default:
- print("Unhandled state type", stateType.displayName, stateType.type)
- }
-
- print("GenericDevicePage: unhandled entry", stateDelegate.stateType.displayName)
- }
+ Layout.fillWidth: true
}
-
Label {
visible: stateDelegate.stateType.unit !== Types.UnitUnixTime && stateDelegate.stateType.unitString.length > 0
text: stateDelegate.stateType.unitString
}
+ Component.onCompleted: updateLoader()
+ onStateTypeChanged: updateLoader();
+
+ function updateLoader() {
+ if (stateDelegate.stateType == null) {
+ return;
+ }
+
+ var isWritable = root.deviceClass.actionTypes.getActionType(stateType.id) !== null;
+
+ var sourceComp;
+ switch (stateDelegate.stateType.type.toLowerCase()) {
+ case "string":
+ if (isWritable) {
+ if (stateDelegate.stateType.allowedValues !== undefined) {
+ sourceComp = "ComboBoxDelegate.qml"
+ } else {
+ sourceComp = "TextFieldDelegate.qml";
+ }
+ } else {
+ sourceComp = "LabelDelegate.qml";
+ }
+ break;
+ case "stringlist":
+ sourceComp = "ListDelegate.qml";
+ break;
+ case "bool":
+ if (isWritable) {
+ sourceComp = "SwitchDelegate.qml";
+ } else {
+ sourceComp = "LedDelegate.qml";
+ }
+ break;
+ case "int":
+ case "double":
+ if (stateDelegate.stateType.unit === Types.UnitUnixTime) {
+ sourceComp = "DateTimeDelegate.qml";
+ } else if (isWritable) {
+ sourceComp = "SliderDelegate.qml";
+// sourceComp = "SpinBoxDelegate.qml";
+ } else {
+ sourceComp = "NumberLabelDelegate.qml";
+ }
+ break;
+ case "color":
+ sourceComp = "ColorDelegate.qml";
+ break;
+ }
+ if (!sourceComp) {
+ sourceComp = "LabelDelegate.qml";
+ print("GenericDevicePage: unhandled entry", stateDelegate.stateType.displayName)
+ }
+
+ stateDelegateLoader.setSource("../delegates/statedelegates/" + sourceComp,
+ {
+// value: root.device.states.getState(stateType.id).value,
+ possibleValues: stateDelegate.stateType.allowedValues,
+ from: stateDelegate.stateType.minValue,
+ to: stateDelegate.stateType.maxValue,
+ stateType: stateDelegate.stateType
+ })
+ }
+
+ property int pendingActionId: -1
+ property real valueCache: 0
+ property bool valueCacheDirty: false
+
+ function enqueueSetValue(value) {
+ if (pendingActionId == -1) {
+ executeAction(value);
+ return;
+ } else {
+ valueCache = value
+ valueCacheDirty = true;
+ }
+ }
+
+ function executeAction(value) {
+ var params = []
+ var param1 = {}
+ param1["paramTypeId"] = stateDelegate.stateType.id
+ param1["value"] = value;
+ params.push(param1)
+ var actionId = root.executeAction(stateDelegate.stateType.id, params);
+ stateDelegate.pendingActionId = actionId
+ }
+
Binding {
target: stateDelegateLoader.item
property: "value"
- value: root.device.states.getState(stateDelegate.stateType.id).value
- }
- Binding {
- target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("possibleValues") ? stateDelegateLoader.item : null
- property: "possibleValues"
- value: stateDelegate.stateType.allowedValues
- }
- Binding {
- target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("from") ? stateDelegateLoader.item : null
- property: "from"
- value: stateDelegate.stateType.minValue !== undefined ? stateDelegate.stateType.minValue : -999999999999
- }
- Binding {
- target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("to") ? stateDelegateLoader.item : null
- property: "to"
- value: stateDelegate.stateType.maxValue !== undefined ? stateDelegate.stateType.maxValue : 999999999999
- }
- Binding {
- target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("actionTypeId") ? stateDelegateLoader.item : null
- property: "actionTypeId"
- value: stateDelegate.stateType.id
+ value: stateDelegate.deviceState.value
+ when: !stateDelegate.valueCacheDirty && stateDelegate.pendingActionId === -1
}
+
Connections {
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("changed") ? stateDelegateLoader.item : null
onChanged: {
- var params = []
- var param1 = {}
- param1["paramTypeId"] = stateDelegate.stateType.id
- param1["value"] = value;
- params.push(param1)
- root.executeAction(stateDelegate.stateType.id, params);
+ stateDelegate.enqueueSetValue(value)
+ }
+ }
+ Connections {
+ target: engine.deviceManager
+ onExecuteActionReply: {
+ if (stateDelegate.pendingActionId === params.id) {
+ stateDelegate.pendingActionId = -1
+ if (stateDelegate.valueCacheDirty) {
+ stateDelegate.executeAction(stateDelegate.valueCache)
+ stateDelegate.valueCacheDirty = false;
+ }
+ }
}
}
}
@@ -215,7 +255,6 @@ DevicePageBase {
target: engine.deviceManager
onExecuteActionReply: {
if (params["id"] === actionDelegate.pendingActionId) {
- print("blubb!")
pendingTimer.start();
actionDelegate.lastSuccess = params["params"]["deviceError"] === "DeviceErrorNoError"
actionDelegate.pendingActionId = -1
@@ -347,154 +386,4 @@ DevicePageBase {
}
}
}
-
- Component {
- id: ledComponent
- Led {
- property bool value
- on: value === true
- }
- }
-
- Component {
- id: labelComponent
- Label {
- property var value
- text: value
- }
- }
- Component {
- id: numberLabelComponent
- Label {
- property var value
- text: Math.round(value * 100) / 100
- }
- }
- Component {
- id: textFieldComponent
- TextField {
- property var value
- text: value
- }
- }
-
- Component {
- id: listComponent
- Label {
- property var value
- text: value.join(", ")
- }
- }
-
- Component {
- id: checkBoxComponent
- CheckBox {
- property var value
- checked: value === true
- enabled: false
- }
- }
-
- Component {
- id: switchComponent
- Switch {
- property var value
- signal changed(var value)
- checked: value === true
- onClicked: {
- changed(checked)
- }
- }
- }
-
- Component {
- id: spinBoxComponent
- SpinBox {
- width: 150
- signal changed(var value)
- stepSize: (to - from) / 10
- editable: true
- onValueModified: {
- changed(value)
- }
- }
- }
-
- Component {
- id: comboBoxComponent
- ComboBox {
- property var value
- property var possibleValues
-
- signal changed(var value)
- model: possibleValues
- onActivated: changed(model[index])
- }
- }
-
- Component {
- id: colorComponent
- Item {
- id: colorComponentItem
- implicitWidth: app.iconSize * 2
- implicitHeight: app.iconSize
- property var value
- signal changed(var value)
-
- Pane {
- anchors.fill: parent
- topPadding: 0
- bottomPadding: 0
- leftPadding: 0
- rightPadding: 0
- Material.elevation: 1
- contentItem: Rectangle {
- color: colorComponentItem.value
-
- MouseArea {
- anchors.fill: parent
- onClicked: {
- var pos = colorComponentItem.mapToItem(root, 0, colorComponentItem.height)
- print("opening", colorComponentItem.value)
- var colorPicker = colorPickerComponent.createObject(root, {preferredY: pos.y, colorValue: colorComponentItem.value })
- colorPicker.open()
- }
- }
- }
- }
-
- Component {
- id: colorPickerComponent
- Dialog {
- id: colorPickerDialog
- modal: true
- x: (parent.width - width) / 2
- y: Math.min(preferredY, parent.height - height)
- width: parent.width - app.margins * 2
- height: 200
- padding: app.margins
- property var colorValue
- property int preferredY: 0
- contentItem: ColorPicker {
- color: colorPickerDialog.colorValue
- property var lastSentTime: new Date()
- onColorChanged: {
- var currentTime = new Date();
- if (pressed && currentTime - lastSentTime > 200) {
- colorComponentItem.changed(color);
- }
- }
- }
- }
- }
- }
- }
-
- Component {
- id: dateTimeComponent
- Label {
- property var value
- text: Qt.formatDateTime(new Date(value * 1000), Qt.DefaultLocaleShortDate)
- }
- }
}
diff --git a/nymea-app/ui/devicepages/HeatingDevicePage.qml b/nymea-app/ui/devicepages/HeatingDevicePage.qml
new file mode 100644
index 00000000..64afdbd7
--- /dev/null
+++ b/nymea-app/ui/devicepages/HeatingDevicePage.qml
@@ -0,0 +1,81 @@
+import QtQuick 2.5
+import QtQuick.Controls 2.1
+import QtQuick.Controls.Material 2.2
+import QtQuick.Layouts 1.1
+import Nymea 1.0
+import "../components"
+import "../customviews"
+
+DevicePageBase {
+ id: root
+
+ readonly property bool landscape: width > height
+
+ readonly property StateType targetTemperatureStateType: device.deviceClass.stateTypes.findByName("targetTemperature")
+ readonly property State targetTemperatureState: targetTemperatureStateType ? device.states.getState(targetTemperatureStateType.id) : null
+ readonly property StateType powerStateType: deviceClass.stateTypes.findByName("power")
+ readonly property State powerState: powerStateType ? device.states.getState(powerStateType.id) : null
+ readonly property StateType temperatureStateType: device.deviceClass.stateTypes.findByName("temperature")
+ readonly property State temperatureState: temperatureStateType ? device.states.getState(temperatureStateType.id) : null
+ readonly property StateType percentageStateType: device.deviceClass.stateTypes.findByName("percentage")
+ readonly property State percentageState: percentageStateType ? device.states.getState(percentageStateType.id) : null
+ // TODO: should this be an interface? e.g. extendedthermostat
+ readonly property StateType boostStateType: device.deviceClass.stateTypes.findByName("boost")
+ readonly property State boostState: boostStateType ? device.states.getState(boostStateType.id) : null
+
+
+ GridLayout {
+ anchors.fill: parent
+ anchors.margins: app.margins
+ columns: app.landscape ? 2 : 1
+
+ Dial {
+ id: dial
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ visible: root.targetTemperatureStateType || root.percentageStateType
+
+ device: root.device
+ stateType: root.targetTemperatureStateType ? root.targetTemperatureStateType : root.percentageStateType
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 50
+ visible: root.boostStateType
+ border.color: boostMouseArea.pressed || root.boostStateType && root.boostState.value === true ? app.accentColor : app.foregroundColor
+ border.width: 1
+ radius: height / 2
+ color: root.boostStateType && root.boostState.value === true ? app.accentColor : "transparent"
+
+ Row {
+ anchors.centerIn: parent
+ spacing: app.margins / 2
+ ColorIcon {
+ height: app.iconSize
+ width: app.iconSize
+ name: "../images/sensors/temperature.svg"
+ color: root.boostStateType && root.boostState.value === true ? "red" : keyColor
+ }
+
+ Label {
+ text: qsTr("Boost")
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+ MouseArea {
+ id: boostMouseArea
+ anchors.fill: parent
+ onPressedChanged: PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackImpact)
+ onClicked: {
+ var params = []
+ var param = {}
+ param["paramTypeId"] = root.boostStateType.id
+ param["value"] = !root.boostState.value
+ params.push(param)
+ engine.deviceManager.executeAction(root.device.id, root.boostStateType.id, params);
+ }
+ }
+ }
+ }
+}
diff --git a/nymea-app/ui/devicepages/LightDevicePage.qml b/nymea-app/ui/devicepages/LightDevicePage.qml
index 612efc36..41f63742 100644
--- a/nymea-app/ui/devicepages/LightDevicePage.qml
+++ b/nymea-app/ui/devicepages/LightDevicePage.qml
@@ -33,45 +33,59 @@ DevicePageBase {
columnSpacing: app.margins
Layout.alignment: Qt.AlignCenter
- Item {
- Layout.preferredWidth: Math.max(app.iconSize * 4, parent.width / 5)
+ Dial {
+ Layout.minimumWidth: app.landscape ? parent.width / 3 :app.iconSize * 4
Layout.preferredHeight: width
+ Layout.fillWidth: true
Layout.topMargin: app.margins
Layout.bottomMargin: app.landscape ? app.margins : 0
Layout.alignment: Qt.AlignCenter
- Layout.rowSpan: app.landscape ? 4 : 1
+ Layout.rowSpan: app.landscape ? 3 : 1
Layout.fillHeight: true
-
- AbstractButton {
- height: Math.min(parent.height, parent.width)
- width: height
- anchors.centerIn: parent
- Rectangle {
- anchors.fill: parent
- color: "white"
- border.color: root.powerState.value === true ? app.accentColor : bulbIcon.keyColor
- border.width: 4
- radius: width / 2
- }
-
- ColorIcon {
- id: bulbIcon
- anchors.fill: parent
- anchors.margins: app.margins * 1.5
- name: root.powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg"
- color: root.powerState.value === true ? app.accentColor : keyColor
- }
- onClicked: {
- var params = []
- var param = {}
- param["paramTypeId"] = root.powerActionType.paramTypes.get(0).id;
- param["value"] = !root.powerState.value;
- params.push(param)
- engine.deviceManager.executeAction(root.device.id, root.powerStateType.id, params);
- }
- }
+ device: root.device
+ stateType: root.brightnessStateType
+ showValueLabel: false
}
+// Item {
+// Layout.preferredWidth: Math.max(app.iconSize * 4, parent.width / 5)
+// Layout.preferredHeight: width
+// Layout.topMargin: app.margins
+// Layout.bottomMargin: app.landscape ? app.margins : 0
+// Layout.alignment: Qt.AlignCenter
+// Layout.rowSpan: app.landscape ? 4 : 1
+// Layout.fillHeight: true
+
+// AbstractButton {
+// height: Math.min(parent.height, parent.width)
+// width: height
+// anchors.centerIn: parent
+// Rectangle {
+// anchors.fill: parent
+// color: "white"
+// border.color: root.powerState.value === true ? app.accentColor : bulbIcon.keyColor
+// border.width: 4
+// radius: width / 2
+// }
+
+// ColorIcon {
+// id: bulbIcon
+// anchors.fill: parent
+// anchors.margins: app.margins * 1.5
+// name: root.powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg"
+// color: root.powerState.value === true ? app.accentColor : keyColor
+// }
+// onClicked: {
+// var params = []
+// var param = {}
+// param["paramTypeId"] = root.powerActionType.paramTypes.get(0).id;
+// param["value"] = !root.powerState.value;
+// params.push(param)
+// engine.deviceManager.executeAction(root.device.id, root.powerStateType.id, params);
+// }
+// }
+// }
+
RowLayout {
Layout.fillHeight: true
@@ -121,34 +135,34 @@ DevicePageBase {
}
}
- Rectangle {
- color: "blue"
- Layout.fillWidth: true
- Layout.fillHeight: true
- Layout.minimumHeight: 20
- Layout.preferredHeight: 20
- visible: root.brightnessStateType
+// Rectangle {
+// color: "blue"
+// Layout.fillWidth: true
+// Layout.fillHeight: true
+// Layout.minimumHeight: 20
+// Layout.preferredHeight: 20
+// visible: root.brightnessStateType
- Pane {
- anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter }
- height: parent.height
- Material.elevation: 1
- padding: 0
+// Pane {
+// anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter }
+// height: parent.height
+// Material.elevation: 1
+// padding: 0
- BrightnessSlider {
- anchors.fill: parent
- brightness: root.brightnessState ? root.brightnessState.value : 0
- onMoved: {
- var params = []
- var param = {}
- param["paramTypeId"] = root.brightnessActionType.paramTypes.get(0).id;
- param["value"] = brightness;
- params.push(param)
- engine.deviceManager.executeAction(root.device.id, root.brightnessActionType.id, params);
- }
- }
- }
- }
+// BrightnessSlider {
+// anchors.fill: parent
+// brightness: root.brightnessState ? root.brightnessState.value : 0
+// onMoved: {
+// var params = []
+// var param = {}
+// param["paramTypeId"] = root.brightnessActionType.paramTypes.get(0).id;
+// param["value"] = brightness;
+// params.push(param)
+// engine.deviceManager.executeAction(root.device.id, root.brightnessActionType.id, params);
+// }
+// }
+// }
+// }
Rectangle {
diff --git a/nymea-app/ui/images/dial.svg b/nymea-app/ui/images/dial.svg
new file mode 100644
index 00000000..d73f77d6
--- /dev/null
+++ b/nymea-app/ui/images/dial.svg
@@ -0,0 +1,170 @@
+
+
diff --git a/nymea-app/ui/mainviews/DevicesPageDelegate.qml b/nymea-app/ui/mainviews/DevicesPageDelegate.qml
index 23cb44fe..df8cda1e 100644
--- a/nymea-app/ui/mainviews/DevicesPageDelegate.qml
+++ b/nymea-app/ui/mainviews/DevicesPageDelegate.qml
@@ -16,6 +16,7 @@ MainPageTile {
onClicked: {
var page;
switch (model.name) {
+ case "heating":
case "sensor":
page = "SensorsDeviceListPage.qml"
break;
@@ -91,6 +92,7 @@ MainPageTile {
case "smartmeterproducer":
case "extendedsmartmeterconsumer":
case "extendedsmartmeterproducer":
+ case "heating":
return sensorComponent;
// return labelComponent;
@@ -430,6 +432,7 @@ MainPageTile {
ListElement { ifaceName: "noisesensor"; stateName: "noise" }
ListElement { ifaceName: "smartmeterconsumer"; stateName: "totalEnergyConsumed" }
ListElement { ifaceName: "smartmeterproducer"; stateName: "totalEnergyProduced" }
+ ListElement { ifaceName: "thermostat"; stateName: "targetTemperature" }
}
function findSensors(deviceClass) {
var ret = []
diff --git a/packaging/android/AndroidManifest.xml b/packaging/android/AndroidManifest.xml
index d911e010..f59d1f71 100644
--- a/packaging/android/AndroidManifest.xml
+++ b/packaging/android/AndroidManifest.xml
@@ -85,4 +85,5 @@
+
diff --git a/packaging/android/src/io/guh/nymeaapp/NymeaAppActivity.java b/packaging/android/src/io/guh/nymeaapp/NymeaAppActivity.java
index c1f57fe8..6b6785ae 100644
--- a/packaging/android/src/io/guh/nymeaapp/NymeaAppActivity.java
+++ b/packaging/android/src/io/guh/nymeaapp/NymeaAppActivity.java
@@ -6,6 +6,7 @@ import android.os.Bundle;
import android.os.Build;
import android.telephony.TelephonyManager;
import android.provider.Settings.Secure;
+import android.os.Vibrator;
//import com.google.firebase.messaging.MessageForwardingService;
@@ -27,6 +28,12 @@ public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActiv
return Build.MODEL;
}
+ public void vibrate(int duration)
+ {
+ Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+ v.vibrate(duration);
+ }
+
// // The key in the intent's extras that maps to the incoming message's message ID. Only sent by
// // the server, GmsCore sends EXTRA_MESSAGE_ID_KEY below. Server can't send that as it would get
// // stripped by the client.
diff --git a/packaging/ios/platformhelperios.mm b/packaging/ios/platformhelperios.mm
index d7d58519..f7f5c919 100644
--- a/packaging/ios/platformhelperios.mm
+++ b/packaging/ios/platformhelperios.mm
@@ -1,10 +1,12 @@
#import
#import
+#import
#include
#include "platformintegration/ios/platformhelperios.h"
+
QString PlatformHelperIOS::readKeyChainEntry(const QString &service, const QString &key)
{
NSDictionary *const query = @{
@@ -65,3 +67,35 @@ void PlatformHelperIOS::writeKeyChainEntry(const QString &service, const QString
qWarning() << "Error storing value in keycahin" << status;
}
}
+
+
+void PlatformHelperIOS::generateSelectionFeedback()
+{
+ UISelectionFeedbackGenerator *generator = [[UISelectionFeedbackGenerator alloc] init];
+ [generator prepare];
+ [generator selectionChanged];
+ generator = nil;
+}
+
+void PlatformHelperIOS::generateImpactFeedback()
+{
+ // UIImpactFeedbackStyleLight
+ // UIImpactFeedbackStyleMedium
+ // UIImpactFeedbackStyleHeavy
+ UIImpactFeedbackGenerator *generator = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
+ [generator prepare];
+ [generator impactOccurred];
+ generator = nil;
+}
+
+void PlatformHelperIOS::generateNotificationFeedback()
+{
+// UINotificationFeedbackTypeSuccess
+// UINotificationFeedbackTypeWarning
+// UINotificationFeedbackTypeError
+
+ UINotificationFeedbackGenerator *generator = [[UINotificationFeedbackGenerator alloc] init];
+ [generator prepare];
+ [generator notificationOccurred:UINotificationFeedbackTypeSuccess];
+ generator = nil;
+}