diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc
index 3667d88c..a31fc172 100644
--- a/nymea-app/resources.qrc
+++ b/nymea-app/resources.qrc
@@ -318,5 +318,6 @@
ui/mainviews/dashboard/DashboardSensorDelegate.qml
ui/devicepages/ThingStatusPage.qml
ui/customviews/MultiStateChart.qml
+ ui/devicepages/DeviceDetailsPage.qml
diff --git a/nymea-app/ui/components/ThingInfoPane.qml b/nymea-app/ui/components/ThingInfoPane.qml
index 725a95c9..58126561 100644
--- a/nymea-app/ui/components/ThingInfoPane.qml
+++ b/nymea-app/ui/components/ThingInfoPane.qml
@@ -13,7 +13,7 @@ InfoPaneBase {
readonly property bool setupFailure: root.thing.setupStatus == Thing.ThingSetupStatusFailed
readonly property State batteryState: root.thing.stateByName("batteryLevel")
readonly property State batteryCriticalState: root.thing.stateByName("batteryCritical")
- readonly property State connectedState: root.thing.thingClass.interfaces.indexOf("connectable") >= 0 && root.thing.stateByName("connected")
+ readonly property bool connectedState: root.thing.thingClass.interfaces.indexOf("connectable") >= 0 && root.thing.stateByName("connected")
readonly property State signalStrengthState: root.thing.stateByName("signalStrength")
readonly property State updateStatusState: root.thing.stateByName("updateStatus")
readonly property State childLockState: root.thing.stateByName("childLock")
diff --git a/nymea-app/ui/devicepages/DeviceDetailsPage.qml b/nymea-app/ui/devicepages/DeviceDetailsPage.qml
new file mode 100644
index 00000000..d3ad2d9f
--- /dev/null
+++ b/nymea-app/ui/devicepages/DeviceDetailsPage.qml
@@ -0,0 +1,238 @@
+import QtQuick 2.5
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.2
+import QtQuick.Controls.Material 2.2
+import Nymea 1.0
+import "../components"
+import "../delegates"
+
+ThingPageBase {
+ id: root
+
+ 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
+ }
+ 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;
+ print("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,
+ possibleValues: stateDelegate.stateType.possibleValues,
+ possibleValuesDisplayNames: stateDelegate.stateType.possibleValuesDisplayNames,
+ from: minValue,
+ to: maxValue,
+ unit: stateDelegate.stateType.unit,
+ writable: false,
+ stateType: stateDelegate.stateType
+ })
+
+ }
+
+ 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
+ }
+ }
+ }
+
+ 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();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml
index 10c33f8c..40c634d4 100644
--- a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml
+++ b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml
@@ -73,6 +73,8 @@ SettingsPageBase {
if (!root.thing.isChild || root.thingClass.createMethods.indexOf("CreateMethodAuto") < 0) {
deviceMenu.addItem(menuEntryComponent.createObject(deviceMenu, {text: qsTr("Reconfigure"), iconSource: "../images/configure.svg", functionName: "reconfigureThing"}))
}
+
+ deviceMenu.addItem(menuEntryComponent.createObject(deviceMenu, {text: qsTr("Details"), iconSource: "../images/info.svg", functionName: "thingDetails"}))
}
function renameThing() {
@@ -91,6 +93,10 @@ SettingsPageBase {
configPage.aborted.connect(function() {pageStack.pop(root)})
}
+ function thingDetails() {
+ var detailsPage = pageStack.push(Qt.resolvedUrl("qrc:/ui/devicepages/DeviceDetailsPage.qml"), {thing: root.thing})
+ }
+
Component {
id: menuEntryComponent
IconMenuItem {