Merge PR #132: Add support for the thermostat interface
This commit is contained in:
commit
ba9cac9b1b
@ -298,7 +298,7 @@ void DeviceManager::editDeviceResponse(const QVariantMap ¶ms)
|
|||||||
|
|
||||||
void DeviceManager::executeActionResponse(const QVariantMap ¶ms)
|
void DeviceManager::executeActionResponse(const QVariantMap ¶ms)
|
||||||
{
|
{
|
||||||
qDebug() << "Execute Action response" << params;
|
// qDebug() << "Execute Action response" << params;
|
||||||
emit executeActionReply(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)
|
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;
|
QVariantMap p;
|
||||||
p.insert("deviceId", deviceId.toString());
|
p.insert("deviceId", deviceId.toString());
|
||||||
p.insert("actionTypeId", actionTypeId.toString());
|
p.insert("actionTypeId", actionTypeId.toString());
|
||||||
@ -379,6 +379,5 @@ int DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionTypeI
|
|||||||
p.insert("params", params);
|
p.insert("params", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Params:" << p;
|
|
||||||
return m_jsonClient->sendCommand("Actions.ExecuteAction", p, this, "executeActionResponse");
|
return m_jsonClient->sendCommand("Actions.ExecuteAction", p, this, "executeActionResponse");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -169,5 +169,6 @@
|
|||||||
<file>ui/images/stock_video.svg</file>
|
<file>ui/images/stock_video.svg</file>
|
||||||
<file>ui/images/sensors/presence.svg</file>
|
<file>ui/images/sensors/presence.svg</file>
|
||||||
<file>ui/images/powersocket.svg</file>
|
<file>ui/images/powersocket.svg</file>
|
||||||
|
<file>ui/images/dial.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@ -5,3 +5,4 @@ PlatformHelper::PlatformHelper(QObject *parent) : QObject(parent)
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,13 @@ class PlatformHelper : public QObject
|
|||||||
Q_PROPERTY(QString machineHostname READ machineHostname CONSTANT)
|
Q_PROPERTY(QString machineHostname READ machineHostname CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
enum HapticsFeedback {
|
||||||
|
HapticsFeedbackSelection,
|
||||||
|
HapticsFeedbackImpact,
|
||||||
|
HapticsFeedbackNotification
|
||||||
|
};
|
||||||
|
Q_ENUM(HapticsFeedback)
|
||||||
|
|
||||||
explicit PlatformHelper(QObject *parent = nullptr);
|
explicit PlatformHelper(QObject *parent = nullptr);
|
||||||
virtual ~PlatformHelper() = default;
|
virtual ~PlatformHelper() = default;
|
||||||
|
|
||||||
@ -24,6 +31,8 @@ public:
|
|||||||
virtual QString deviceModel() const = 0;
|
virtual QString deviceModel() const = 0;
|
||||||
virtual QString deviceManufacturer() const = 0;
|
virtual QString deviceManufacturer() const = 0;
|
||||||
|
|
||||||
|
Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbackType) = 0;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void permissionsRequestFinished();
|
void permissionsRequestFinished();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -44,6 +44,24 @@ QString PlatformHelperAndroid::deviceManufacturer() const
|
|||||||
return QAndroidJniObject::callStaticObjectMethod<jstring>("io/guh/nymeaapp/NymeaAppActivity","deviceManufacturer").toString();
|
return QAndroidJniObject::callStaticObjectMethod<jstring>("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<void>("vibrate","(I)V", duration);
|
||||||
|
}
|
||||||
|
|
||||||
void PlatformHelperAndroid::permissionRequestFinished(const QtAndroid::PermissionResultMap &result)
|
void PlatformHelperAndroid::permissionRequestFinished(const QtAndroid::PermissionResultMap &result)
|
||||||
{
|
{
|
||||||
foreach (const QString &key, result.keys()) {
|
foreach (const QString &key, result.keys()) {
|
||||||
|
|||||||
@ -19,6 +19,8 @@ public:
|
|||||||
QString deviceModel() const override;
|
QString deviceModel() const override;
|
||||||
QString deviceManufacturer() const override;
|
QString deviceManufacturer() const override;
|
||||||
|
|
||||||
|
Q_INVOKABLE void vibrate(HapticsFeedback feedbackType) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void permissionRequestFinished(const QtAndroid::PermissionResultMap &);
|
static void permissionRequestFinished(const QtAndroid::PermissionResultMap &);
|
||||||
|
|
||||||
|
|||||||
@ -38,3 +38,8 @@ QString PlatformHelperGeneric::deviceManufacturer() const
|
|||||||
{
|
{
|
||||||
return QSysInfo::productType();
|
return QSysInfo::productType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformHelperGeneric::vibrate(PlatformHelper::HapticsFeedback feedbyckType)
|
||||||
|
{
|
||||||
|
Q_UNUSED(feedbyckType)
|
||||||
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ public:
|
|||||||
virtual QString deviceModel() const override;
|
virtual QString deviceModel() const override;
|
||||||
virtual QString deviceManufacturer() const override;
|
virtual QString deviceManufacturer() const override;
|
||||||
|
|
||||||
|
Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbyckType) override;
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|||||||
@ -47,3 +47,19 @@ QString PlatformHelperIOS::deviceManufacturer() const
|
|||||||
{
|
{
|
||||||
return QString("iPhone");
|
return QString("iPhone");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PlatformHelperIOS::vibrate(PlatformHelper::HapticsFeedback feedbackType)
|
||||||
|
{
|
||||||
|
switch (feedbackType) {
|
||||||
|
case HapticsFeedbackSelection:
|
||||||
|
generateSelectionFeedback();
|
||||||
|
break;
|
||||||
|
case HapticsFeedbackImpact:
|
||||||
|
generateImpactFeedback();
|
||||||
|
break;
|
||||||
|
case HapticsFeedbackNotification:
|
||||||
|
generateNotificationFeedback();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,10 +19,16 @@ public:
|
|||||||
virtual QString deviceModel() const override;
|
virtual QString deviceModel() const override;
|
||||||
virtual QString deviceManufacturer() const override;
|
virtual QString deviceManufacturer() const override;
|
||||||
|
|
||||||
|
Q_INVOKABLE virtual void vibrate(HapticsFeedback feedbackType) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// defined in platformhelperios.mm
|
// defined in platformhelperios.mm
|
||||||
QString readKeyChainEntry(const QString &service, const QString &key);
|
QString readKeyChainEntry(const QString &service, const QString &key);
|
||||||
void writeKeyChainEntry(const QString &service, const QString &key, const QString &value);
|
void writeKeyChainEntry(const QString &service, const QString &key, const QString &value);
|
||||||
|
|
||||||
|
void generateSelectionFeedback();
|
||||||
|
void generateImpactFeedback();
|
||||||
|
void generateNotificationFeedback();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PLATFORMHELPERIOS_H
|
#endif // PLATFORMHELPERIOS_H
|
||||||
|
|||||||
@ -147,5 +147,19 @@
|
|||||||
<file>ui/devicepages/PowersocketDevicePage.qml</file>
|
<file>ui/devicepages/PowersocketDevicePage.qml</file>
|
||||||
<file>ui/devicelistpages/PowerSocketsDeviceListPage.qml</file>
|
<file>ui/devicelistpages/PowerSocketsDeviceListPage.qml</file>
|
||||||
<file>ui/components/GroupedListView.qml</file>
|
<file>ui/components/GroupedListView.qml</file>
|
||||||
|
<file>ui/devicepages/HeatingDevicePage.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/LedDelegate.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/LabelDelegate.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/NumberLabelDelegate.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/TextFieldDelegate.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/ListDelegate.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/CheckboxDelegate.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/SwitchDelegate.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/SpinBoxDelegate.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/ComboBoxDelegate.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/ColorDelegate.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/DateTimeDelegate.qml</file>
|
||||||
|
<file>ui/components/Dial.qml</file>
|
||||||
|
<file>ui/delegates/statedelegates/SliderDelegate.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@ -54,7 +54,7 @@ ApplicationWindow {
|
|||||||
rootItem.handleCloseEvent(close)
|
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) {
|
function interfaceToString(name) {
|
||||||
switch(name) {
|
switch(name) {
|
||||||
case "light":
|
case "light":
|
||||||
@ -206,6 +206,8 @@ ApplicationWindow {
|
|||||||
case "heating":
|
case "heating":
|
||||||
case "extendedheating":
|
case "extendedheating":
|
||||||
return Qt.resolvedUrl("images/radiator.svg")
|
return Qt.resolvedUrl("images/radiator.svg")
|
||||||
|
case "thermostat":
|
||||||
|
return Qt.resolvedUrl("images/dial.svg")
|
||||||
case "evcharger":
|
case "evcharger":
|
||||||
case "extendedevcharger":
|
case "extendedevcharger":
|
||||||
return Qt.resolvedUrl("images/ev-charger.svg")
|
return Qt.resolvedUrl("images/ev-charger.svg")
|
||||||
@ -231,7 +233,9 @@ ApplicationWindow {
|
|||||||
"smartmeterproducer": "lightgreen",
|
"smartmeterproducer": "lightgreen",
|
||||||
"smartmeterconsumer": "orange",
|
"smartmeterconsumer": "orange",
|
||||||
"extendedsmartmeterproducer": "blue",
|
"extendedsmartmeterproducer": "blue",
|
||||||
"extendedsmartmeterconsumer": "blue"
|
"extendedsmartmeterconsumer": "blue",
|
||||||
|
"heating" : "gainsboro",
|
||||||
|
"thermostat": "dodgerblue"
|
||||||
}
|
}
|
||||||
|
|
||||||
function interfaceToColor(name) {
|
function interfaceToColor(name) {
|
||||||
@ -278,6 +282,8 @@ ApplicationWindow {
|
|||||||
page = "ButtonDevicePage.qml";
|
page = "ButtonDevicePage.qml";
|
||||||
} else if (interfaceList.indexOf("weather") >= 0) {
|
} else if (interfaceList.indexOf("weather") >= 0) {
|
||||||
page = "WeatherDevicePage.qml";
|
page = "WeatherDevicePage.qml";
|
||||||
|
} else if (interfaceList.indexOf("heating") >= 0 || interfaceList.indexOf("thermostat") >= 0) {
|
||||||
|
page = "HeatingDevicePage.qml";
|
||||||
} else if (interfaceList.indexOf("sensor") >= 0) {
|
} else if (interfaceList.indexOf("sensor") >= 0) {
|
||||||
page = "SensorDevicePage.qml";
|
page = "SensorDevicePage.qml";
|
||||||
} else if (interfaceList.indexOf("inputtrigger") >= 0) {
|
} else if (interfaceList.indexOf("inputtrigger") >= 0) {
|
||||||
|
|||||||
296
nymea-app/ui/components/Dial.qml
Normal file
296
nymea-app/ui/components/Dial.qml
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -10,6 +10,9 @@ Item {
|
|||||||
property alias from: slider.from
|
property alias from: slider.from
|
||||||
property alias to: slider.to
|
property alias to: slider.to
|
||||||
property alias stepSize: slider.stepSize
|
property alias stepSize: slider.stepSize
|
||||||
|
|
||||||
|
readonly property real rawValue: slider.value
|
||||||
|
|
||||||
signal moved(real value);
|
signal moved(real value);
|
||||||
|
|
||||||
Slider {
|
Slider {
|
||||||
|
|||||||
@ -31,6 +31,7 @@ ItemDelegate {
|
|||||||
id: loader
|
id: loader
|
||||||
Layout.fillWidth: sourceComponent === textFieldComponent
|
Layout.fillWidth: sourceComponent === textFieldComponent
|
||||||
sourceComponent: {
|
sourceComponent: {
|
||||||
|
print("loading paramdelegate:", root.writable, root.paramType.type)
|
||||||
if (!root.writable) {
|
if (!root.writable) {
|
||||||
return stringComponent;
|
return stringComponent;
|
||||||
}
|
}
|
||||||
@ -39,7 +40,8 @@ ItemDelegate {
|
|||||||
case "bool":
|
case "bool":
|
||||||
return boolComponent;
|
return boolComponent;
|
||||||
case "int":
|
case "int":
|
||||||
return spinnerComponent;
|
case "double":
|
||||||
|
return sliderComponent;
|
||||||
case "string":
|
case "string":
|
||||||
case "qstring":
|
case "qstring":
|
||||||
if (root.paramType.allowedValues.length > 0) {
|
if (root.paramType.allowedValues.length > 0) {
|
||||||
@ -104,9 +106,6 @@ ItemDelegate {
|
|||||||
id: sliderComponent
|
id: sliderComponent
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: app.margins
|
spacing: app.margins
|
||||||
Label {
|
|
||||||
text: root.paramType.minValue
|
|
||||||
}
|
|
||||||
Slider {
|
Slider {
|
||||||
id: slider
|
id: slider
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@ -114,12 +113,13 @@ ItemDelegate {
|
|||||||
to: root.paramType.maxValue
|
to: root.paramType.maxValue
|
||||||
value: root.param.value
|
value: root.param.value
|
||||||
stepSize: {
|
stepSize: {
|
||||||
switch (root.paramType.type.toLowerCase()) {
|
var ret = 1
|
||||||
case "int":
|
for (var i = 0; i < decimals; i++) {
|
||||||
return 1;
|
ret /= 10;
|
||||||
}
|
}
|
||||||
return 0.01;
|
return ret;
|
||||||
}
|
}
|
||||||
|
property int decimals: root.paramType.type.toLocaleLowerCase() === "int" ? 0 : 1
|
||||||
|
|
||||||
onMoved: {
|
onMoved: {
|
||||||
var newValue
|
var newValue
|
||||||
@ -134,7 +134,7 @@ ItemDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
text: root.paramType.maxValue
|
text: root.param.value.toFixed(slider.decimals) + root.paramType.unitString
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
nymea-app/ui/delegates/statedelegates/CheckboxDelegate.qml
Normal file
12
nymea-app/ui/delegates/statedelegates/CheckboxDelegate.qml
Normal file
@ -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
|
||||||
|
}
|
||||||
61
nymea-app/ui/delegates/statedelegates/ColorDelegate.qml
Normal file
61
nymea-app/ui/delegates/statedelegates/ColorDelegate.qml
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
nymea-app/ui/delegates/statedelegates/ComboBoxDelegate.qml
Normal file
17
nymea-app/ui/delegates/statedelegates/ComboBoxDelegate.qml
Normal file
@ -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)
|
||||||
|
}
|
||||||
12
nymea-app/ui/delegates/statedelegates/DateTimeDelegate.qml
Normal file
12
nymea-app/ui/delegates/statedelegates/DateTimeDelegate.qml
Normal file
@ -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
|
||||||
|
}
|
||||||
12
nymea-app/ui/delegates/statedelegates/LabelDelegate.qml
Normal file
12
nymea-app/ui/delegates/statedelegates/LabelDelegate.qml
Normal file
@ -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
|
||||||
|
}
|
||||||
11
nymea-app/ui/delegates/statedelegates/LedDelegate.qml
Normal file
11
nymea-app/ui/delegates/statedelegates/LedDelegate.qml
Normal file
@ -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
|
||||||
|
}
|
||||||
11
nymea-app/ui/delegates/statedelegates/ListDelegate.qml
Normal file
11
nymea-app/ui/delegates/statedelegates/ListDelegate.qml
Normal file
@ -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(", ")
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
}
|
||||||
57
nymea-app/ui/delegates/statedelegates/SliderDelegate.qml
Normal file
57
nymea-app/ui/delegates/statedelegates/SliderDelegate.qml
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
16
nymea-app/ui/delegates/statedelegates/SpinBoxDelegate.qml
Normal file
16
nymea-app/ui/delegates/statedelegates/SpinBoxDelegate.qml
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
nymea-app/ui/delegates/statedelegates/SwitchDelegate.qml
Normal file
15
nymea-app/ui/delegates/statedelegates/SwitchDelegate.qml
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
11
nymea-app/ui/delegates/statedelegates/TextFieldDelegate.qml
Normal file
11
nymea-app/ui/delegates/statedelegates/TextFieldDelegate.qml
Normal file
@ -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
|
||||||
|
}
|
||||||
@ -84,6 +84,8 @@ DeviceListPageBase {
|
|||||||
ListElement { interfaceName: "co2sensor"; stateName: "co2" }
|
ListElement { interfaceName: "co2sensor"; stateName: "co2" }
|
||||||
ListElement { interfaceName: "daylightsensor"; stateName: "daylight" }
|
ListElement { interfaceName: "daylightsensor"; stateName: "daylight" }
|
||||||
ListElement { interfaceName: "presencesensor"; stateName: "isPresent" }
|
ListElement { interfaceName: "presencesensor"; stateName: "isPresent" }
|
||||||
|
ListElement { interfaceName: "heating"; stateName: "power" }
|
||||||
|
ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" }
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: RowLayout {
|
delegate: RowLayout {
|
||||||
@ -114,9 +116,14 @@ DeviceListPageBase {
|
|||||||
font.pixelSize: app.smallFont
|
font.pixelSize: app.smallFont
|
||||||
}
|
}
|
||||||
Led {
|
Led {
|
||||||
|
id: led
|
||||||
visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() == "bool"
|
visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() == "bool"
|
||||||
on: visible && sensorValueDelegate.stateValue.value === true
|
on: visible && sensorValueDelegate.stateValue.value === true
|
||||||
}
|
}
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: led.width
|
||||||
|
visible: led.visible
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -113,89 +113,129 @@ DevicePageBase {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.minimumWidth: parent.width / 2
|
||||||
text: stateDelegate.stateType.displayName
|
text: stateDelegate.stateType.displayName
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
Loader {
|
Loader {
|
||||||
id: stateDelegateLoader
|
id: stateDelegateLoader
|
||||||
sourceComponent: {
|
Layout.fillWidth: true
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
visible: stateDelegate.stateType.unit !== Types.UnitUnixTime && stateDelegate.stateType.unitString.length > 0
|
visible: stateDelegate.stateType.unit !== Types.UnitUnixTime && stateDelegate.stateType.unitString.length > 0
|
||||||
text: stateDelegate.stateType.unitString
|
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 {
|
Binding {
|
||||||
target: stateDelegateLoader.item
|
target: stateDelegateLoader.item
|
||||||
property: "value"
|
property: "value"
|
||||||
value: root.device.states.getState(stateDelegate.stateType.id).value
|
value: stateDelegate.deviceState.value
|
||||||
}
|
when: !stateDelegate.valueCacheDirty && stateDelegate.pendingActionId === -1
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("changed") ? stateDelegateLoader.item : null
|
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("changed") ? stateDelegateLoader.item : null
|
||||||
onChanged: {
|
onChanged: {
|
||||||
var params = []
|
stateDelegate.enqueueSetValue(value)
|
||||||
var param1 = {}
|
}
|
||||||
param1["paramTypeId"] = stateDelegate.stateType.id
|
}
|
||||||
param1["value"] = value;
|
Connections {
|
||||||
params.push(param1)
|
target: engine.deviceManager
|
||||||
root.executeAction(stateDelegate.stateType.id, params);
|
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
|
target: engine.deviceManager
|
||||||
onExecuteActionReply: {
|
onExecuteActionReply: {
|
||||||
if (params["id"] === actionDelegate.pendingActionId) {
|
if (params["id"] === actionDelegate.pendingActionId) {
|
||||||
print("blubb!")
|
|
||||||
pendingTimer.start();
|
pendingTimer.start();
|
||||||
actionDelegate.lastSuccess = params["params"]["deviceError"] === "DeviceErrorNoError"
|
actionDelegate.lastSuccess = params["params"]["deviceError"] === "DeviceErrorNoError"
|
||||||
actionDelegate.pendingActionId = -1
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
81
nymea-app/ui/devicepages/HeatingDevicePage.qml
Normal file
81
nymea-app/ui/devicepages/HeatingDevicePage.qml
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,45 +33,59 @@ DevicePageBase {
|
|||||||
columnSpacing: app.margins
|
columnSpacing: app.margins
|
||||||
Layout.alignment: Qt.AlignCenter
|
Layout.alignment: Qt.AlignCenter
|
||||||
|
|
||||||
Item {
|
Dial {
|
||||||
Layout.preferredWidth: Math.max(app.iconSize * 4, parent.width / 5)
|
Layout.minimumWidth: app.landscape ? parent.width / 3 :app.iconSize * 4
|
||||||
Layout.preferredHeight: width
|
Layout.preferredHeight: width
|
||||||
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: app.margins
|
Layout.topMargin: app.margins
|
||||||
Layout.bottomMargin: app.landscape ? app.margins : 0
|
Layout.bottomMargin: app.landscape ? app.margins : 0
|
||||||
Layout.alignment: Qt.AlignCenter
|
Layout.alignment: Qt.AlignCenter
|
||||||
Layout.rowSpan: app.landscape ? 4 : 1
|
Layout.rowSpan: app.landscape ? 3 : 1
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
device: root.device
|
||||||
AbstractButton {
|
stateType: root.brightnessStateType
|
||||||
height: Math.min(parent.height, parent.width)
|
showValueLabel: false
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
RowLayout {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
@ -121,34 +135,34 @@ DevicePageBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
// Rectangle {
|
||||||
color: "blue"
|
// color: "blue"
|
||||||
Layout.fillWidth: true
|
// Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
// Layout.fillHeight: true
|
||||||
Layout.minimumHeight: 20
|
// Layout.minimumHeight: 20
|
||||||
Layout.preferredHeight: 20
|
// Layout.preferredHeight: 20
|
||||||
visible: root.brightnessStateType
|
// visible: root.brightnessStateType
|
||||||
|
|
||||||
Pane {
|
// Pane {
|
||||||
anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter }
|
// anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter }
|
||||||
height: parent.height
|
// height: parent.height
|
||||||
Material.elevation: 1
|
// Material.elevation: 1
|
||||||
padding: 0
|
// padding: 0
|
||||||
|
|
||||||
BrightnessSlider {
|
// BrightnessSlider {
|
||||||
anchors.fill: parent
|
// anchors.fill: parent
|
||||||
brightness: root.brightnessState ? root.brightnessState.value : 0
|
// brightness: root.brightnessState ? root.brightnessState.value : 0
|
||||||
onMoved: {
|
// onMoved: {
|
||||||
var params = []
|
// var params = []
|
||||||
var param = {}
|
// var param = {}
|
||||||
param["paramTypeId"] = root.brightnessActionType.paramTypes.get(0).id;
|
// param["paramTypeId"] = root.brightnessActionType.paramTypes.get(0).id;
|
||||||
param["value"] = brightness;
|
// param["value"] = brightness;
|
||||||
params.push(param)
|
// params.push(param)
|
||||||
engine.deviceManager.executeAction(root.device.id, root.brightnessActionType.id, params);
|
// engine.deviceManager.executeAction(root.device.id, root.brightnessActionType.id, params);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
170
nymea-app/ui/images/dial.svg
Normal file
170
nymea-app/ui/images/dial.svg
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
viewBox="0 0 96 96.000001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg4874"
|
||||||
|
height="96"
|
||||||
|
width="96"
|
||||||
|
sodipodi:docname="dial.svg"
|
||||||
|
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="2792"
|
||||||
|
inkscape:window-height="1698"
|
||||||
|
id="namedview842"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="6.9947917"
|
||||||
|
inkscape:cx="77.052782"
|
||||||
|
inkscape:cy="81.297134"
|
||||||
|
inkscape:window-x="88"
|
||||||
|
inkscape:window-y="44"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg4874">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid844" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs4876" />
|
||||||
|
<metadata
|
||||||
|
id="metadata4879">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<rect
|
||||||
|
transform="rotate(90)"
|
||||||
|
y="-95.999985"
|
||||||
|
x="8.5593265e-06"
|
||||||
|
height="96"
|
||||||
|
width="96"
|
||||||
|
id="rect4782"
|
||||||
|
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:3.99920893;marker:none;enable-background:accumulate" />
|
||||||
|
<circle
|
||||||
|
cy="48"
|
||||||
|
cx="48"
|
||||||
|
id="path833"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:3.99999976;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
r="34.642204" />
|
||||||
|
<circle
|
||||||
|
r="2.5"
|
||||||
|
cy="-28.688278"
|
||||||
|
cx="19.022213"
|
||||||
|
id="circle944"
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="rotate(70)" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="circle950"
|
||||||
|
cx="25.123938"
|
||||||
|
cy="-5.916328"
|
||||||
|
r="2.5"
|
||||||
|
transform="rotate(50)" />
|
||||||
|
<circle
|
||||||
|
r="2.5"
|
||||||
|
cy="17.56922"
|
||||||
|
cx="23.06922"
|
||||||
|
id="circle956"
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="rotate(30)" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="circle962"
|
||||||
|
cx="13.105885"
|
||||||
|
cy="38.935661"
|
||||||
|
r="2.5"
|
||||||
|
transform="rotate(10)" />
|
||||||
|
<circle
|
||||||
|
r="2.5"
|
||||||
|
cy="55.605885"
|
||||||
|
cx="-3.5643404"
|
||||||
|
id="circle968"
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="rotate(-10)" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="circle974"
|
||||||
|
cx="-24.93078"
|
||||||
|
cy="65.569221"
|
||||||
|
r="2.5"
|
||||||
|
transform="rotate(-30)" />
|
||||||
|
<circle
|
||||||
|
r="2.5"
|
||||||
|
cy="-17.56922"
|
||||||
|
cx="-108.06922"
|
||||||
|
id="circle980"
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="rotate(-150)" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="circle986"
|
||||||
|
cx="-98.105888"
|
||||||
|
cy="-38.935661"
|
||||||
|
r="2.5"
|
||||||
|
transform="rotate(-170)" />
|
||||||
|
<circle
|
||||||
|
r="2.5"
|
||||||
|
cy="-55.605885"
|
||||||
|
cx="-81.435661"
|
||||||
|
id="circle992"
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="rotate(170)" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="circle998"
|
||||||
|
cx="-60.069218"
|
||||||
|
cy="-65.569221"
|
||||||
|
r="2.5"
|
||||||
|
transform="rotate(150)" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="circle1010"
|
||||||
|
cx="-13.811721"
|
||||||
|
cy="-61.522213"
|
||||||
|
r="2.5"
|
||||||
|
transform="rotate(110)" />
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path936"
|
||||||
|
cx="5.5"
|
||||||
|
cy="-48"
|
||||||
|
r="2.5"
|
||||||
|
transform="rotate(90)" />
|
||||||
|
<circle
|
||||||
|
r="2.5"
|
||||||
|
cy="-67.62394"
|
||||||
|
cx="-36.583672"
|
||||||
|
id="circle1024"
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="rotate(130)" />
|
||||||
|
<rect
|
||||||
|
y="-25.083672"
|
||||||
|
x="65.12394"
|
||||||
|
height="17"
|
||||||
|
width="5"
|
||||||
|
id="rect1028"
|
||||||
|
style="opacity:1;fill:#808080;fill-opacity:1;stroke:none;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="rotate(40)"
|
||||||
|
rx="3"
|
||||||
|
ry="3" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 5.5 KiB |
@ -16,6 +16,7 @@ MainPageTile {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
var page;
|
var page;
|
||||||
switch (model.name) {
|
switch (model.name) {
|
||||||
|
case "heating":
|
||||||
case "sensor":
|
case "sensor":
|
||||||
page = "SensorsDeviceListPage.qml"
|
page = "SensorsDeviceListPage.qml"
|
||||||
break;
|
break;
|
||||||
@ -91,6 +92,7 @@ MainPageTile {
|
|||||||
case "smartmeterproducer":
|
case "smartmeterproducer":
|
||||||
case "extendedsmartmeterconsumer":
|
case "extendedsmartmeterconsumer":
|
||||||
case "extendedsmartmeterproducer":
|
case "extendedsmartmeterproducer":
|
||||||
|
case "heating":
|
||||||
return sensorComponent;
|
return sensorComponent;
|
||||||
// return labelComponent;
|
// return labelComponent;
|
||||||
|
|
||||||
@ -430,6 +432,7 @@ MainPageTile {
|
|||||||
ListElement { ifaceName: "noisesensor"; stateName: "noise" }
|
ListElement { ifaceName: "noisesensor"; stateName: "noise" }
|
||||||
ListElement { ifaceName: "smartmeterconsumer"; stateName: "totalEnergyConsumed" }
|
ListElement { ifaceName: "smartmeterconsumer"; stateName: "totalEnergyConsumed" }
|
||||||
ListElement { ifaceName: "smartmeterproducer"; stateName: "totalEnergyProduced" }
|
ListElement { ifaceName: "smartmeterproducer"; stateName: "totalEnergyProduced" }
|
||||||
|
ListElement { ifaceName: "thermostat"; stateName: "targetTemperature" }
|
||||||
}
|
}
|
||||||
function findSensors(deviceClass) {
|
function findSensors(deviceClass) {
|
||||||
var ret = []
|
var ret = []
|
||||||
|
|||||||
@ -85,4 +85,5 @@
|
|||||||
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
|
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
|
||||||
Remove the comment if you do not require these default features. -->
|
Remove the comment if you do not require these default features. -->
|
||||||
<!-- %%INSERT_FEATURES -->
|
<!-- %%INSERT_FEATURES -->
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import android.os.Bundle;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.provider.Settings.Secure;
|
import android.provider.Settings.Secure;
|
||||||
|
import android.os.Vibrator;
|
||||||
|
|
||||||
//import com.google.firebase.messaging.MessageForwardingService;
|
//import com.google.firebase.messaging.MessageForwardingService;
|
||||||
|
|
||||||
@ -27,6 +28,12 @@ public class NymeaAppActivity extends org.qtproject.qt5.android.bindings.QtActiv
|
|||||||
return Build.MODEL;
|
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 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
|
// // the server, GmsCore sends EXTRA_MESSAGE_ID_KEY below. Server can't send that as it would get
|
||||||
// // stripped by the client.
|
// // stripped by the client.
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <Security/Security.h>
|
#import <Security/Security.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#include "platformintegration/ios/platformhelperios.h"
|
#include "platformintegration/ios/platformhelperios.h"
|
||||||
|
|
||||||
|
|
||||||
QString PlatformHelperIOS::readKeyChainEntry(const QString &service, const QString &key)
|
QString PlatformHelperIOS::readKeyChainEntry(const QString &service, const QString &key)
|
||||||
{
|
{
|
||||||
NSDictionary *const query = @{
|
NSDictionary *const query = @{
|
||||||
@ -65,3 +67,35 @@ void PlatformHelperIOS::writeKeyChainEntry(const QString &service, const QString
|
|||||||
qWarning() << "Error storing value in keycahin" << status;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user