diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index f855504b..ee60f6c4 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -61,8 +61,12 @@ ui/devicepages/ButtonDevicePage.qml ui/devicepages/GenericDeviceStateDetailsPage.qml ui/devicepages/GenericDevicePage.qml + ui/devicepages/WeatherDevicePagePre110.qml + ui/devicepages/WeatherDevicePagePost110.qml ui/devicepages/WeatherDevicePage.qml ui/devicepages/SensorDevicePage.qml + ui/devicepages/SensorDevicePagePre110.qml + ui/devicepages/SensorDevicePagePost110.qml ui/devicepages/DevicePageBase.qml ui/devicepages/ConfigureThingPage.qml ui/devicepages/InputTriggerDevicePage.qml @@ -129,5 +133,6 @@ ../LICENSE ui/customviews/GenericTypeGraphPre110.qml ui/customviews/GenericTypeGraph.qml + ui/customviews/SensorChart.qml diff --git a/nymea-app/ui/NewDeviceWizard.qml b/nymea-app/ui/NewDeviceWizard.qml index c4e90538..121683c7 100644 --- a/nymea-app/ui/NewDeviceWizard.qml +++ b/nymea-app/ui/NewDeviceWizard.qml @@ -36,6 +36,7 @@ Page { Connections { target: engine.deviceManager onPairDeviceReply: { + busyOverlay.shown = false switch (params["setupMethod"]) { case "SetupMethodPushButton": d.pairingTransactionId = params["pairingTransactionId"]; @@ -52,9 +53,11 @@ Page { } } onConfirmPairingReply: { + busyOverlay.shown = false internalPageStack.push(resultsPage, {success: params["deviceError"] === "DeviceErrorNoError", deviceId: params["deviceId"]}) } onAddDeviceReply: { + busyOverlay.shown = false; internalPageStack.push(resultsPage, {success: params["deviceError"] === "DeviceErrorNoError", deviceId: params["deviceId"]}) } } @@ -358,6 +361,8 @@ Page { break; } + busyOverlay.shown = true; + } } } @@ -448,4 +453,8 @@ Page { } } } + + BusyOverlay { + id: busyOverlay + } } diff --git a/nymea-app/ui/components/HeaderButton.qml b/nymea-app/ui/components/HeaderButton.qml index 74d46cdf..d38d58d3 100644 --- a/nymea-app/ui/components/HeaderButton.qml +++ b/nymea-app/ui/components/HeaderButton.qml @@ -13,6 +13,7 @@ ToolButton { id: image anchors.fill: parent anchors.margins: app.margins / 2 + opacity: enabled ? 1 : .5 } } } diff --git a/nymea-app/ui/customviews/GenericTypeGraph.qml b/nymea-app/ui/customviews/GenericTypeGraph.qml index c5eeebdc..f138505c 100644 --- a/nymea-app/ui/customviews/GenericTypeGraph.qml +++ b/nymea-app/ui/customviews/GenericTypeGraph.qml @@ -12,10 +12,14 @@ Item { property var device: null property var stateType: null + property var valueState: device.states.getState(stateType.id) readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId); readonly property bool hasConnectable: deviceClass.interfaces.indexOf("connectable") >= 0 readonly property var connectedStateType: hasConnectable ? deviceClass.stateTypes.findByName("connected") : null + property color color: app.accentColor + property string iconSource: "" + LogsModelNg { id: logsModelNg engine: _engine @@ -40,54 +44,34 @@ Item { anchors.fill: parent spacing: 0 RowLayout { - Layout.alignment: Qt.AlignHCenter - HeaderButton { - imageSource: "../images/zoom-out.svg" - onClicked: { - var diff = xAxis.max.getTime() - xAxis.min.getTime() - var newTime = new Date(xAxis.min.getTime() - (diff / 4)) - xAxis.min = newTime; - } + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + name: root.iconSource + visible: root.iconSource.length > 0 + color: root.color } Label { - Layout.preferredWidth: 100 - horizontalAlignment: Text.AlignHCenter - text: { - var timeDiff = (xAxis.max.getTime() - xAxis.min.getTime()) / 1000; - if (timeDiff < 60) { - return qsTr("%1 seconds").arg(Math.round(timeDiff)); - } - timeDiff = timeDiff / 60 - if (timeDiff < 60) { - return qsTr("%1 minutes").arg(Math.round(timeDiff)); - } - timeDiff = timeDiff / 60 - if (timeDiff < 48) { - return qsTr("%1 hours").arg(Math.round(timeDiff)); - } - timeDiff = timeDiff / 24; - if (timeDiff < 14) { - return qsTr("%1 days").arg(Math.round(timeDiff)); - } - timeDiff = timeDiff / 7 - if (timeDiff < 5) { - return qsTr("%1 weeks").arg(Math.round(timeDiff)); - } - timeDiff * timeDiff * 7 / 30 - if (timeDiff < 24) { - return qsTr("%1 months").arg(Math.round(timeDiff)); - } - timeDiff = timeDiff * 30 / 356 - return qsTr("%1 years").arg(Math.round(timeDiff)) + Layout.fillWidth: true + text: root.valueState.value + " " + root.stateType.unitString + font.pixelSize: app.largeFont + } + + HeaderButton { + imageSource: "../images/zoom-out.svg" + onClicked: { + var newTime = new Date(xAxis.min.getTime() - (xAxis.timeDiff / 4)) + xAxis.min = newTime; } } HeaderButton { imageSource: "../images/zoom-in.svg" + enabled: xAxis.timeDiff > (1000 * 60 * 30) onClicked: { - var diff = xAxis.max.getTime() - xAxis.min.getTime() - var newTime = new Date(xAxis.min.getTime() + (diff / 4)) + var newTime = new Date(Math.min(xAxis.min.getTime() + (xAxis.timeDiff / 4), xAxis.max.getTime() - (1000 * 60 * 30))) xAxis.min = newTime; } } @@ -102,6 +86,7 @@ Item { margins.left: 0 margins.right: 0 backgroundColor: Material.background + legend.visible: false legend.labelColor: app.foregroundColor animationDuration: 300 @@ -109,8 +94,8 @@ Item { ValueAxis { id: yAxis - min: logsModelNg.minValue - max: logsModelNg.maxValue + min: logsModelNg.minValue - logsModelNg.minValue * .01 + max: logsModelNg.maxValue + logsModelNg.maxValue * .01 labelsFont.pixelSize: app.smallFont labelsColor: app.foregroundColor tickCount: chartView.height / 40 @@ -132,30 +117,60 @@ Item { tickCount: chartView.width / 70 labelsFont.pixelSize: app.smallFont labelsColor: app.foregroundColor + property int timeDiff: xAxis.max.getTime() - xAxis.min.getTime() + + function getTimeSpanString() { + var td = timeDiff / 1000 + if (td < 60) { + return qsTr("%1 seconds").arg(Math.round(td)); + } + td = td / 60 + if (td < 60) { + return qsTr("%1 minutes").arg(Math.round(td)); + } + td = td / 60 + if (td < 48) { + return qsTr("%1 hours").arg(Math.round(td)); + } + td = td / 24; + if (td < 14) { + return qsTr("%1 days").arg(Math.round(td)); + } + td = td / 7 + if (td < 9) { + return qsTr("%1 weeks").arg(Math.round(td)); + } + td = td * 7 / 30 + if (td < 24) { + return qsTr("%1 months").arg(Math.round(td)); + } + td = td * 30 / 356 + return qsTr("%1 years").arg(Math.round(td)) + } + titleText: { if (xAxis.min.getYear() === xAxis.max.getYear() && xAxis.min.getMonth() === xAxis.max.getMonth() && xAxis.min.getDate() === xAxis.max.getDate()) { - return Qt.formatDate(xAxis.min) + return Qt.formatDate(xAxis.min) + " (" + getTimeSpanString() + ")" } - return Qt.formatDate(xAxis.min) + " - " + Qt.formatDate(xAxis.max) + return Qt.formatDate(xAxis.min) + " - " + Qt.formatDate(xAxis.max) + " (" + getTimeSpanString() + ")" } titleBrush: app.foregroundColor format: { - var timeDiff = (xAxis.max.getTime() - xAxis.min.getTime()) / 1000 - if (timeDiff < 60) { // one minute + if (timeDiff < 1000 * 60) { // one minute return "mm:ss" } - if (timeDiff < 60 * 60) { // one hour + if (timeDiff < 1000 * 60 * 60) { // one hour return "hh:mm" } - if (timeDiff < 60 * 60 * 24 * 2) { // two day + if (timeDiff < 1000 * 60 * 60 * 24 * 2) { // two day return "hh:mm" } - if (timeDiff < 60 * 60 * 24 * 7) { // one week + if (timeDiff < 1000 * 60 * 60 * 24 * 7) { // one week return "ddd hh:mm" } - if (timeDiff < 60 * 60 * 24 * 7 * 30) { // one month + if (timeDiff < 1000 * 60 * 60 * 24 * 7 * 30) { // one month return "dd.MM." } return "MMM yy" @@ -186,17 +201,62 @@ Item { } AreaSeries { + id: mainSeries axisX: xAxis axisY: yAxis name: root.stateType.displayName - borderColor: app.accentColor + borderColor: root.color borderWidth: 4 upperSeries: LineSeries { id: lineSeries1 } - color: Qt.rgba(app.accentColor.r, app.accentColor.g, app.accentColor.b, .3) + color: Qt.rgba(root.color.r, root.color.g, root.color.b, .3) + onHovered: { + markClosestPoint(point) + } + + function markClosestPoint(point) { + var found = false; + if (lineSeries1.count == 1) { + selectedHighlights.removePoints(0, selectedHighlights.count) + selectedHighlights.append(lineSeries1.at(0).x, lineSeries1.at(1).y) + return; + } + + var searchIndex = Math.floor(lineSeries1.count / 2) + var previousIndex = 0; + var nextIndex = lineSeries1.count - 1; + + while (previousIndex + 1 != nextIndex) { + if (point.x < lineSeries1.at(searchIndex).x) { + previousIndex = searchIndex; + } else if (point.x > lineSeries1.at(searchIndex).x) { + nextIndex = searchIndex; + } + searchIndex = previousIndex + Math.floor((nextIndex - previousIndex) / 2); + } + var diffToPrevious = Math.abs(point.x - lineSeries1.at(previousIndex).x) + var diffToNext = Math.abs(point.x - lineSeries1.at(nextIndex).x) + var closestPoint = diffToPrevious < diffToNext ? lineSeries1.at(previousIndex) : lineSeries1.at(nextIndex); + + selectedHighlights.removePoints(0, selectedHighlights.count) + selectedHighlights.append(closestPoint.x, closestPoint.y) + } } + ScatterSeries { + id: selectedHighlights + color: root.color + markerSize: 10 + borderWidth: 2 + borderColor: root.color + axisX: xAxis + axisY: yAxis + pointLabelsVisible: true + pointLabelsColor: app.foregroundColor + pointLabelsFont.pixelSize: app.smallFont + pointLabelsFormat: "@yPoint" + } MouseArea { anchors.fill: parent @@ -225,19 +285,20 @@ Item { chartView.animationOptions = ChartView.NoAnimation var oldMax = xAxis.max; chartView.scrollRight(dy); - var timeDiff = xAxis.max.getTime() - oldMax.getTime() - xAxis.min = new Date(xAxis.min.getTime() - timeDiff * 2) + xAxis.min = new Date(xAxis.min.getTime() - xAxis.timeDiff * 2) chartView.animationOptions = ChartView.SeriesAnimations } onPressed: { lastX = mouse.x lastY = mouse.y + var pt = chartView.mapToValue(Qt.point(mouse.x, mouse.y), mainSeries) + mainSeries.markClosestPoint(pt) } onWheel: { scrollRightLimited(-wheel.pixelDelta.x) -// zoomInLimited(wheel.pixelDelta.y) +// zoomInLimited(wheel.pixelDelta.y) } onPositionChanged: { diff --git a/nymea-app/ui/customviews/SensorChart.qml b/nymea-app/ui/customviews/SensorChart.qml new file mode 100644 index 00000000..d2cadaf8 --- /dev/null +++ b/nymea-app/ui/customviews/SensorChart.qml @@ -0,0 +1,28 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "../components" +import Nymea 1.0 + +CustomViewBase { + id: root + implicitHeight: width * .6 + property string interfaceName + + readonly property string stateTypeName: { + switch (interfaceName) { + case "lightsensor": + return "lightIntensity"; + default: + return interfaceName.replace("sensor", ""); + } + } + GenericTypeGraph { + anchors { left: parent.left; top: parent.top; right: parent.right; bottom: parent.bottom } + device: root.device + stateType: root.deviceClass.stateTypes.findByName(root.stateTypeName) + color: app.interfaceToColor(root.interfaceName) + iconSource: app.interfaceToIcon(root.interfaceName) + } +} diff --git a/nymea-app/ui/devicepages/SensorDevicePage.qml b/nymea-app/ui/devicepages/SensorDevicePage.qml index 65998a15..e50610dc 100644 --- a/nymea-app/ui/devicepages/SensorDevicePage.qml +++ b/nymea-app/ui/devicepages/SensorDevicePage.qml @@ -8,24 +8,16 @@ import "../customviews" DevicePageBase { id: root - ListView { - anchors { fill: parent } - model: ListModel { - Component.onCompleted: { - var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor"] - for (var i = 0; i < supportedInterfaces.length; i++) { - print("checking", root.deviceClass.name, root.deviceClass.interfaces) - if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) { - append({name: supportedInterfaces[i]}); - } - } + Loader { + anchors.fill: parent + Component.onCompleted: { + var src + if (engine.jsonRpcClient.ensureServerVersion("1.10")) { + src = "SensorDevicePagePost110.qml" + } else { + src = "SensorDevicePagePre110.qml" } - } - delegate: SensorView { - width: parent.width - interfaceName: modelData - device: root.device - deviceClass: root.deviceClass + setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass}) } } } diff --git a/nymea-app/ui/devicepages/SensorDevicePagePost110.qml b/nymea-app/ui/devicepages/SensorDevicePagePost110.qml new file mode 100644 index 00000000..135bef73 --- /dev/null +++ b/nymea-app/ui/devicepages/SensorDevicePagePost110.qml @@ -0,0 +1,27 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + +ListView { + anchors { fill: parent } + model: ListModel { + Component.onCompleted: { + var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor"] + for (var i = 0; i < supportedInterfaces.length; i++) { + print("checking", root.deviceClass.name, root.deviceClass.interfaces) + if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) { + append({name: supportedInterfaces[i]}); + } + } + } + } + delegate: SensorChart { + width: parent.width + interfaceName: modelData + device: root.device + deviceClass: root.deviceClass + } +} diff --git a/nymea-app/ui/devicepages/SensorDevicePagePre110.qml b/nymea-app/ui/devicepages/SensorDevicePagePre110.qml new file mode 100644 index 00000000..3b15028f --- /dev/null +++ b/nymea-app/ui/devicepages/SensorDevicePagePre110.qml @@ -0,0 +1,31 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + +ListView { + anchors { fill: parent } + + property var device + property var deviceClass + + model: ListModel { + Component.onCompleted: { + var supportedInterfaces = ["temperaturesensor", "humiditysensor", "pressuresensor", "moisturesensor", "lightsensor", "conductivitysensor"] + for (var i = 0; i < supportedInterfaces.length; i++) { + print("checking", root.deviceClass.name, root.deviceClass.interfaces) + if (root.deviceClass.interfaces.indexOf(supportedInterfaces[i]) >= 0) { + append({name: supportedInterfaces[i]}); + } + } + } + } + delegate: SensorView { + width: parent.width + interfaceName: modelData + device: root.device + deviceClass: root.deviceClass + } +} diff --git a/nymea-app/ui/devicepages/WeatherDevicePage.qml b/nymea-app/ui/devicepages/WeatherDevicePage.qml index 6a7c7543..363faf3b 100644 --- a/nymea-app/ui/devicepages/WeatherDevicePage.qml +++ b/nymea-app/ui/devicepages/WeatherDevicePage.qml @@ -8,36 +8,16 @@ import "../customviews" DevicePageBase { id: root - Flickable { + Loader { anchors.fill: parent - clip: true - contentHeight: content.implicitHeight - ColumnLayout { - id: content - width: parent.width - WeatherView { - Layout.fillWidth: true - device: root.device - deviceClass: root.deviceClass - } - SensorView { - Layout.fillWidth: true - device: root.device - deviceClass: root.deviceClass - interfaceName: "temperaturesensor" - } - SensorView { - Layout.fillWidth: true - device: root.device - deviceClass: root.deviceClass - interfaceName: "humiditysensor" - } - SensorView { - Layout.fillWidth: true - device: root.device - deviceClass: root.deviceClass - interfaceName: "pressuresensor" + Component.onCompleted: { + var src + if (engine.jsonRpcClient.ensureServerVersion("1.10")) { + src = "WeatherDevicePagePost110.qml" + } else { + src = "WeatherDevicePagePre110.qml" } + setSource(Qt.resolvedUrl(src), {device: root.device, deviceClass: root.deviceClass}) } } } diff --git a/nymea-app/ui/devicepages/WeatherDevicePagePost110.qml b/nymea-app/ui/devicepages/WeatherDevicePagePost110.qml new file mode 100644 index 00000000..04f08ca3 --- /dev/null +++ b/nymea-app/ui/devicepages/WeatherDevicePagePost110.qml @@ -0,0 +1,43 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + +Flickable { + anchors.fill: parent + clip: true + contentHeight: content.implicitHeight + + property var device + property var deviceClass + + ColumnLayout { + id: content + width: parent.width + WeatherView { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + } + SensorChart { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "temperaturesensor" + } + SensorChart { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "humiditysensor" + } + SensorChart { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "pressuresensor" + } + } +} diff --git a/nymea-app/ui/devicepages/WeatherDevicePagePre110.qml b/nymea-app/ui/devicepages/WeatherDevicePagePre110.qml new file mode 100644 index 00000000..3feaa8e5 --- /dev/null +++ b/nymea-app/ui/devicepages/WeatherDevicePagePre110.qml @@ -0,0 +1,44 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.1 +import Nymea 1.0 +import "../components" +import "../customviews" + + +Flickable { + anchors.fill: parent + clip: true + contentHeight: content.implicitHeight + + property var device + property var deviceClass + + ColumnLayout { + id: content + width: parent.width + WeatherView { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + } + SensorView { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "temperaturesensor" + } + SensorView { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "humiditysensor" + } + SensorView { + Layout.fillWidth: true + device: root.device + deviceClass: root.deviceClass + interfaceName: "pressuresensor" + } + } +}