276 lines
10 KiB
QML
276 lines
10 KiB
QML
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright (C) 2013 - 2024, nymea GmbH
|
|
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
|
|
*
|
|
* This file is part of nymea-app.
|
|
*
|
|
* nymea-app is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* nymea-app is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with nymea-app. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
import QtQuick.Controls
|
|
import QtQuick.Controls.Material
|
|
import Nymea
|
|
|
|
import "../components"
|
|
import "../delegates"
|
|
|
|
Page {
|
|
id: root
|
|
|
|
property Thing thing: null
|
|
|
|
header: NymeaHeader {
|
|
text: thing ? thing.name : ""
|
|
backButtonVisible: true
|
|
onBackPressed: pageStack.pop()
|
|
}
|
|
|
|
|
|
ListView {
|
|
id: flickable
|
|
anchors.fill: parent
|
|
clip: true
|
|
|
|
SwipeDelegateGroup {}
|
|
|
|
section.property: "type"
|
|
section.delegate: ListSectionHeader {
|
|
text: {
|
|
switch (parseInt(section)) {
|
|
case ThingModel.TypeStateType:
|
|
return qsTr("States")
|
|
case ThingModel.TypeEventType:
|
|
return qsTr("Events")
|
|
}
|
|
}
|
|
}
|
|
|
|
model: ThingModel {
|
|
thing: root.thing
|
|
showActions: false
|
|
}
|
|
delegate: SwipeDelegate {
|
|
id: delegate
|
|
width: parent.width
|
|
|
|
readonly property StateType stateType: model.type === ThingModel.TypeStateType ? root.thing.thingClass.stateTypes.getStateType(model.id) : null
|
|
readonly property EventType eventType: model.type === ThingModel.TypeEventType ? root.thing.thingClass.eventTypes.getEventType(model.id) : null
|
|
|
|
Layout.fillWidth: true
|
|
bottomPadding: 0
|
|
|
|
contentItem: Loader {
|
|
id: inlineLoader
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: Style.smallDelegateHeight
|
|
sourceComponent: {
|
|
switch (model.type) {
|
|
case ThingModel.TypeStateType:
|
|
return stateComponent;
|
|
case ThingModel.TypeEventType:
|
|
return eventComponent;
|
|
}
|
|
}
|
|
|
|
Binding {
|
|
target: inlineLoader.item
|
|
when: model.type === ThingModel.TypeStateType
|
|
property: "stateType"
|
|
value: delegate.stateType
|
|
}
|
|
Binding {
|
|
target: inlineLoader.item
|
|
when: model.type === ThingModel.TypeEventType
|
|
property: "eventType"
|
|
value: delegate.eventType
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: stateComponent
|
|
RowLayout {
|
|
id: stateDelegate
|
|
property StateType stateType: null
|
|
readonly property State thingState: stateType ? root.thing.states.getState(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: text.length > 0 && stateDelegate.stateType.unit !== Types.UnitUnixTime
|
|
text: Types.toUiUnit(stateDelegate.stateType.unit)
|
|
}
|
|
|
|
Component.onCompleted: updateLoader()
|
|
onStateTypeChanged: updateLoader();
|
|
|
|
function updateLoader() {
|
|
if (stateDelegate.stateType == null) {
|
|
return;
|
|
}
|
|
|
|
var sourceComp;
|
|
switch (stateDelegate.stateType.type.toLowerCase()) {
|
|
case "string":
|
|
sourceComp = "LabelDelegate.qml";
|
|
break;
|
|
case "stringlist":
|
|
sourceComp = "ListDelegate.qml";
|
|
break;
|
|
case "bool":
|
|
sourceComp = "LedDelegate.qml";
|
|
break;
|
|
case "int":
|
|
case "uint":
|
|
case "double":
|
|
if (stateDelegate.stateType.unit === Types.UnitUnixTime) {
|
|
sourceComp = "DateTimeDelegate.qml";
|
|
} else {
|
|
sourceComp = "NumberLabelDelegate.qml";
|
|
}
|
|
break;
|
|
case "color":
|
|
sourceComp = "ColorDelegate.qml";
|
|
break;
|
|
}
|
|
if (!sourceComp) {
|
|
sourceComp = "LabelDelegate.qml";
|
|
print("GenericThingPage: 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;
|
|
console.log("pushing delegate for", stateDelegate.stateType.name, "from:", minValue, "to:", maxValue, "possible:", stateDelegate.stateType.possibleValuesDisplayNames)
|
|
stateDelegateLoader.setSource("../delegates/statedelegates/" + sourceComp, {
|
|
value: root.thing.states.getState(stateType.id).value
|
|
})
|
|
|
|
}
|
|
|
|
Binding {
|
|
target: stateDelegateLoader.item
|
|
property: "value"
|
|
value: stateDelegate.thingState.value
|
|
}
|
|
Binding {
|
|
target: stateDelegateLoader.item.hasOwnProperty("from") ? stateDelegateLoader.item : null
|
|
property: "from"
|
|
value: stateDelegate.thingState.minValue
|
|
}
|
|
Binding {
|
|
target: stateDelegateLoader.item.hasOwnProperty("to") ? stateDelegateLoader.item : null
|
|
property: "to"
|
|
value: stateDelegate.thingState.maxValue
|
|
}
|
|
Binding {
|
|
target: stateDelegateLoader.item.hasOwnProperty("possibleValues") ? stateDelegateLoader.item : null
|
|
property: "possibleValues"
|
|
value: stateDelegate.thingState.possibleValues
|
|
}
|
|
Binding {
|
|
target: stateDelegateLoader.item.hasOwnProperty("possibleValuesDisplayNames") ? stateDelegateLoader.item : null
|
|
property: "possibleValuesDisplayNames"
|
|
value: {
|
|
print("updating displayNames", stateDelegate.thingState.possibleValues)
|
|
var ret = []
|
|
for (var i = 0; i < stateDelegate.thingState.possibleValues.length; i++) {
|
|
var possibleValue = stateDelegate.thingState.possibleValues[i]
|
|
var idx = stateDelegate.stateType.possibleValues.indexOf(possibleValue)
|
|
print("value:", possibleValue, idx)
|
|
if (idx >= 0) {
|
|
ret.push(stateDelegate.stateType.possibleValuesDisplayNames[idx])
|
|
} else {
|
|
ret.push(possibleValue)
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
}
|
|
Binding {
|
|
target: stateDelegateLoader.item.hasOwnProperty("unit") ? stateDelegateLoader.item : null
|
|
property: "unit"
|
|
value: stateDelegate.stateType.unit
|
|
}
|
|
Binding {
|
|
target: stateDelegateLoader.item.hasOwnProperty("writable") ? stateDelegateLoader.item : null
|
|
property: "writable"
|
|
value: false
|
|
}
|
|
Binding {
|
|
target: stateDelegateLoader.item.hasOwnProperty("stateType") ? stateDelegateLoader.item : null
|
|
property: "stateType"
|
|
value: stateDelegate.stateType
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: eventComponent
|
|
RowLayout {
|
|
id: eventComponentItem
|
|
property EventType eventType: null
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: eventComponentItem.eventType.displayName
|
|
}
|
|
Rectangle {
|
|
id: flashlight
|
|
Layout.preferredHeight: Style.iconSize * .8
|
|
Layout.preferredWidth: height
|
|
color: "lightgray"
|
|
radius: width / 2
|
|
border.color: Style.foregroundColor
|
|
border.width: 1
|
|
|
|
SequentialAnimation on color {
|
|
id: flashlightAnimation
|
|
running: false
|
|
ColorAnimation { to: "lightgreen"; duration: 100 }
|
|
ColorAnimation { to: "lightgray"; duration: 500 }
|
|
}
|
|
}
|
|
Connections {
|
|
target: root.thing
|
|
onEventTriggered: {
|
|
if (eventTypeId === eventComponentItem.eventType.id) {
|
|
flashlightAnimation.start();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|