This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
powersync-app/nymea-app/ui/devicepages/GenericDevicePage.qml
2020-01-21 14:55:24 +01:00

411 lines
16 KiB
QML

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"
import "../delegates"
DevicePageBase {
id: root
showDetailsButton: false
function executeAction(actionTypeId, params) {
print("executing", actionTypeId)
return engine.deviceManager.executeAction(root.device.id, actionTypeId, params)
}
ListView {
id: flickable
anchors.fill: parent
clip: true
SwipeDelegateGroup {}
section.property: "type"
section.delegate: ListSectionHeader {
text: {
switch (parseInt(section)) {
case DeviceModel.TypeStateType:
return qsTr("States")
case DeviceModel.TypeActionType:
return qsTr("Actions")
case DeviceModel.TypeEventType:
return qsTr("Events")
}
}
}
model: DeviceModel {
device: root.device
}
delegate: SwipeDelegate {
id: delegate
width: parent.width
readonly property StateType stateType: model.type === DeviceModel.TypeStateType ? root.deviceClass.stateTypes.getStateType(model.id) : null
readonly property ActionType actionType: model.writable ? root.deviceClass.actionTypes.getActionType(model.id) : null
readonly property EventType eventType: model.type === DeviceModel.TypeEventType ? root.deviceClass.eventTypes.getEventType(model.id) : null
Layout.fillWidth: true
topPadding: model.type === DeviceModel.TypeActionType ? app.margins / 2 : 0
bottomPadding: 0
contentItem: Loader {
id: inlineLoader
sourceComponent: {
switch (model.type) {
case DeviceModel.TypeStateType:
return stateComponent;
case DeviceModel.TypeActionType:
return actionComponent;
case DeviceModel.TypeEventType:
return eventComponent;
}
}
Binding {
target: inlineLoader.item
when: model.type === DeviceModel.TypeStateType
property: "stateType"
value: delegate.stateType
}
Binding {
target: inlineLoader.item
when: model.type === DeviceModel.TypeActionType
property: "actionType"
value: delegate.actionType
}
Binding {
target: inlineLoader.item
when: model.type === DeviceModel.TypeEventType
property: "eventType"
value: delegate.eventType
}
}
onClicked: swipe.close()
swipe.right: RowLayout {
height: delegate.height
anchors.right: parent.right
MouseArea {
Layout.fillHeight: true
Layout.preferredWidth: height
ColorIcon {
anchors.fill: parent
anchors.margins: app.margins
name: "../images/logs.svg"
}
onClicked: {
swipe.close();
pageStack.push(Qt.resolvedUrl("DeviceLogPage.qml"), {device: root.device, filterTypeIds: [model.id]})
}
}
}
}
}
Component {
id: stateComponent
RowLayout {
id: stateDelegate
property StateType stateType: null
readonly property State deviceState: stateType ? root.device.states.getState(stateType.id) : null
readonly property bool writable: root.deviceClass.actionTypes.getActionType(stateType.id) !== null
Label {
Layout.fillWidth: true
Layout.minimumWidth: parent.width / 2
text: stateDelegate.stateType.displayName
elide: Text.ElideRight
}
Loader {
id: stateDelegateLoader
Layout.fillWidth: true
}
Label {
visible: stateDelegate.stateType.unit !== Types.UnitUnixTime && stateDelegate.stateType.unit !== Types.UnitUnixTime
text: Types.toUiUnit(stateDelegate.stateType.unit)
}
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.length > 0) {
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 "uint":
case "double":
if (stateDelegate.stateType.unit === Types.UnitUnixTime) {
sourceComp = "DateTimeDelegate.qml";
} else if (isWritable) {
if (stateDelegate.stateType.minValue !== undefined && stateDelegate.stateType.maxValue !== undefined) {
sourceComp = "SliderDelegate.qml";
} else {
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)
}
var minValue = stateDelegate.stateType.minValue !== undefined
? stateDelegate.stateType.minValue
: stateDelegate.stateType.type.toLowerCase() === "uint"
? 0
: -2000000000; // As per QML spec
var maxValue = stateDelegate.stateType.maxValue !== undefined
? stateDelegate.stateType.maxValue
: 2000000000;
print(stateDelegate.stateType.minValue)
print("pushing delegate for", stateDelegate.stateType.name, "from:", minValue, "to:", maxValue)
stateDelegateLoader.setSource("../delegates/statedelegates/" + sourceComp,
{
// value: root.device.states.getState(stateType.id).value,
possibleValues: stateDelegate.stateType.allowedValues,
from: minValue,
to: maxValue,
unit: stateDelegate.stateType.unit,
writable: isWritable,
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: stateDelegate.deviceState.value
when: !stateDelegate.valueCacheDirty && stateDelegate.pendingActionId === -1
}
Binding {
target: stateDelegateLoader.item.hasOwnProperty("unit") ? stateDelegateLoader.item : null
property: "unit"
value: stateDelegate.stateType.unit
}
Connections {
target: stateDelegateLoader.item && stateDelegateLoader.item.hasOwnProperty("changed") ? stateDelegateLoader.item : null
onChanged: {
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;
}
}
}
}
}
}
Component {
id: actionComponent
RowLayout {
id: actionDelegate
property ActionType actionType: null
property int pendingActionId: -1
property bool lastSuccess: false
Connections {
target: engine.deviceManager
onExecuteActionReply: {
if (params["id"] === actionDelegate.pendingActionId) {
pendingTimer.start();
actionDelegate.lastSuccess = params["params"]["deviceError"] === "DeviceErrorNoError"
actionDelegate.pendingActionId = -1
}
}
}
Timer { id: pendingTimer; interval: 1000; repeat: false; running: false }
Button {
text: actionType.displayName
Layout.fillWidth: true
onClicked: {
if (actionDelegate.actionType.paramTypes.count === 0) {
actionDelegate.pendingActionId = root.executeAction(actionDelegate.actionType.id, [])
} else {
var dialog = paramsDialogComponent.createObject(root, { actionType: actionDelegate.actionType })
dialog.open()
}
}
Component {
id: paramsDialogComponent
Dialog {
id: paramsDialog
modal: true
width: parent.width - app.margins * 2
x: (parent.width - width) / 2
y: (parent.height - height) / 2
padding: 0
property ActionType actionType: null
contentItem: ColumnLayout {
Repeater {
id: paramsRepeater
model: paramsDialog.actionType.paramTypes
delegate: ParamDelegate {
Layout.fillWidth: true
paramType: paramsDialog.actionType.paramTypes.get(index)
}
}
RowLayout {
Layout.margins: app.margins
spacing: app.margins
Button {
text: qsTr("Cancel")
Layout.fillWidth: true
onClicked: paramsDialog.close()
}
Button {
text: qsTr("OK")
Layout.fillWidth: true
onClicked: {
var params = []
for (var i = 0; i < paramsRepeater.count; i++) {
var param = {}
param["paramTypeId"] = paramsRepeater.itemAt(i).paramType.id
param["value"] = paramsRepeater.itemAt(i).value
params.push(param)
}
actionDelegate.pendingActionId = root.executeAction(paramsDialog.actionType.id, params);
paramsDialog.close();
}
}
}
}
}
}
}
Item {
Layout.preferredHeight: preferredSize
Layout.preferredWidth: preferredSize
property int preferredSize: actionDelegate.pendingActionId !== -1 || pendingTimer.running ? app.iconSize : 0
Behavior on preferredSize { NumberAnimation { duration: 100 } }
BusyIndicator {
anchors.fill: parent
visible: actionDelegate.pendingActionId !== -1
}
ColorIcon {
anchors.fill: parent
visible: actionDelegate.pendingActionId === -1
name: actionDelegate.lastSuccess ? "../images/tick.svg" : "../images/close.svg"
color: actionDelegate.lastSuccess ? "green" : "red"
}
}
}
}
Component {
id: eventComponent
RowLayout {
id: eventComponentItem
property EventType eventType: null
Label {
Layout.fillWidth: true
text: eventComponentItem.eventType.displayName
}
Rectangle {
id: flashlight
Layout.preferredHeight: app.iconSize * .8
Layout.preferredWidth: height
color: "lightgray"
radius: width / 2
border.color: app.foregroundColor
border.width: 1
SequentialAnimation on color {
id: flashlightAnimation
running: false
ColorAnimation { to: "lightgreen"; duration: 100 }
ColorAnimation { to: "lightgray"; duration: 500 }
}
}
Connections {
target: root.device
onEventTriggered: {
flashlightAnimation.start();
}
}
}
}
}