diff --git a/libnymea-app/devicesproxy.cpp b/libnymea-app/devicesproxy.cpp index e18ded4e..4da19364 100644 --- a/libnymea-app/devicesproxy.cpp +++ b/libnymea-app/devicesproxy.cpp @@ -308,6 +308,21 @@ void DevicesProxy::setFilterSetupFailed(bool filterSetupFailed) } } +bool DevicesProxy::filterUpdates() const +{ + return m_filterUpdates; +} + +void DevicesProxy::setFilterUpdates(bool filterUpdates) +{ + if (m_filterUpdates != filterUpdates) { + m_filterUpdates = filterUpdates; + emit filterUpdatesChanged(); + invalidateFilter(); + emit countChanged(); + } +} + bool DevicesProxy::groupByInterface() const { return m_groupByInterface; @@ -371,7 +386,15 @@ bool DevicesProxy::lessThan(const QModelIndex &left, const QModelIndex &right) c QString leftName = sourceModel()->data(left, Devices::RoleName).toString(); QString rightName = sourceModel()->data(right, Devices::RoleName).toString(); - return QString::localeAwareCompare(leftName, rightName) < 0; + int comparison = QString::localeAwareCompare(leftName, rightName); + if (comparison == 0) { + // If there are 2 identically named things we don't want undefined behavor as it may cause items + // to reorder randomly. Use something static like thingId as fallback + QString leftThingId = sourceModel()->data(left, Devices::RoleId).toString(); + QString rightThingId = sourceModel()->data(right, Devices::RoleId).toString(); + comparison = QString::localeAwareCompare(leftThingId, rightThingId); + } + return comparison < 0; } bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const @@ -452,6 +475,15 @@ bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_pa } } + if (m_filterUpdates) { + if (!deviceClass->interfaces().contains("update")) { + return false; + } + if (device->stateValue(deviceClass->stateTypes()->findByName("updateStatus")->id()).toString() == "idle") { + return false; + } + } + if (!m_nameFilter.isEmpty()) { if (!device->name().toLower().contains(m_nameFilter.toLower().trimmed())) { return false; diff --git a/libnymea-app/devicesproxy.h b/libnymea-app/devicesproxy.h index 7ade3750..d8a74cf6 100644 --- a/libnymea-app/devicesproxy.h +++ b/libnymea-app/devicesproxy.h @@ -67,6 +67,8 @@ class DevicesProxy : public QSortFilterProxyModel Q_PROPERTY(bool filterSetupFailed READ filterSetupFailed WRITE setFilterSetupFailed NOTIFY filterSetupFailedChanged) + Q_PROPERTY(bool filterUpdates READ filterUpdates WRITE setFilterUpdates NOTIFY filterUpdatesChanged) + Q_PROPERTY(bool groupByInterface READ groupByInterface WRITE setGroupByInterface NOTIFY groupByInterfaceChanged) public: @@ -120,12 +122,15 @@ public: bool filterSetupFailed() const; void setFilterSetupFailed(bool filterSetupFailed); - bool groupByInterface() const; - void setGroupByInterface(bool groupByInterface); + bool filterUpdates() const; + void setFilterUpdates(bool filterUpdates); + + bool groupByInterface() const; + void setGroupByInterface(bool groupByInterface); Q_INVOKABLE Device *get(int index) const; - Q_INVOKABLE Device *getDevice(const QUuid &deviceId) const; - Q_INVOKABLE Device *getThing(const QUuid &thingId) const; + Q_INVOKABLE Device *getDevice(const QUuid &deviceId) const; + Q_INVOKABLE Device *getThing(const QUuid &thingId) const; signals: void engineChanged(); @@ -144,6 +149,7 @@ signals: void filterBatteryCriticalChanged(); void filterDisconnectedChanged(); void filterSetupFailedChanged(); + void filterUpdatesChanged(); void groupByInterfaceChanged(); void countChanged(); @@ -168,6 +174,7 @@ private: bool m_filterBatteryCritical = false; bool m_filterDisconnected = false; bool m_filterSetupFailed = false; + bool m_filterUpdates = false; bool m_groupByInterface = false; diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index e91ed782..bea8e76d 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -47,9 +47,7 @@ ui/components/Led.qml ui/components/ProgressButton.qml ui/customviews/GenericTypeLogView.qml - ui/customviews/CustomViewBase.qml ui/customviews/WeatherView.qml - ui/customviews/NotificationsView.qml ui/devicepages/MediaThingPage.qml ui/devicepages/ButtonDevicePage.qml ui/devicepages/GenericDevicePage.qml @@ -223,5 +221,9 @@ ui/magic/WriteNfcTagPage.qml ui/components/MediaPlayer.qml ui/components/InfoPane.qml + ui/components/BigTile.qml + ui/components/ThingStatusIcons.qml + ui/components/InfoPaneBase.qml + ui/components/ThingInfoPane.qml diff --git a/nymea-app/styles.qrc b/nymea-app/styles.qrc index ae4308a9..cab2e807 100644 --- a/nymea-app/styles.qrc +++ b/nymea-app/styles.qrc @@ -20,6 +20,5 @@ styles/energize/Button.qml styles/energize/logo.svg styles/energize/Page.qml - styles/energize/Pane.qml diff --git a/nymea-app/styles/energize/ApplicationWindow.qml b/nymea-app/styles/energize/ApplicationWindow.qml index fb87969c..30e63822 100644 --- a/nymea-app/styles/energize/ApplicationWindow.qml +++ b/nymea-app/styles/energize/ApplicationWindow.qml @@ -58,7 +58,7 @@ ApplicationWindow { property color headerForegroundColor: "#79a79f" // The font color - property color foregroundColor: "#69938c" + property color foregroundColor: "#638B87" // The color of selected/highlighted things property color accentColor: "#8cc1b6" diff --git a/nymea-app/styles/energize/Button.qml b/nymea-app/styles/energize/Button.qml index e65426b7..975ba9ac 100644 --- a/nymea-app/styles/energize/Button.qml +++ b/nymea-app/styles/energize/Button.qml @@ -64,7 +64,7 @@ T.Button { font.overline: control.font.overline font.pixelSize: app.smallFont font.weight: Font.Bold - color: app.foregroundColor + color: app.backgroundColor horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight diff --git a/nymea-app/styles/energize/Pane.qml b/nymea-app/styles/energize/Pane.qml deleted file mode 100644 index 4a283139..00000000 --- a/nymea-app/styles/energize/Pane.qml +++ /dev/null @@ -1,76 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU General Public License as published by the Free Software -* Foundation, GNU version 3. This project 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 -* this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -import QtQuick 2.9 -import QtQuick.Templates 2.2 as T -import QtQuick.Controls.Material 2.2 -import QtQuick.Controls.Material.impl 2.2 -import QtGraphicalEffects 1.0 - -T.Pane { - id: control - - implicitWidth: Math.max(background ? background.implicitWidth : 0, contentWidth + leftPadding + rightPadding) - implicitHeight: Math.max(background ? background.implicitHeight : 0, contentHeight + topPadding + bottomPadding) - - contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0) - contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0) - - padding: 12 - - background: Rectangle { - color: control.Material.backgroundColor - radius: 5 - - layer.enabled: control.enabled && control.Material.elevation > 0 - layer.effect: ElevationEffect { - elevation: control.Material.elevation - } - } - - layer.effect: Item { - width: control.width - height: control.height - Rectangle { - id: mask - anchors.fill: parent - color: "#FFFFFFFF" - radius: 5 - } - OpacityMask { - width: control.width - height: control.height - source: control - maskSource: mask - } - } - layer.enabled: true - -} diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml index 6289f612..c88e870c 100644 --- a/nymea-app/ui/MainPage.qml +++ b/nymea-app/ui/MainPage.qml @@ -202,7 +202,7 @@ Page { text: engine.systemController.updateRunning ? qsTr("System update in progress...") : qsTr("%n system update(s) available", "", updatesModel.count) imageSource: "../images/system-update.svg" rotatingIcon: engine.systemController.updateRunning - onPaneClicked: pageStack.push(Qt.resolvedUrl("system/SystemUpdatePage.qml")) + onClicked: pageStack.push(Qt.resolvedUrl("system/SystemUpdatePage.qml")) PackagesFilterModel { id: updatesModel diff --git a/nymea-app/ui/components/BigTile.qml b/nymea-app/ui/components/BigTile.qml new file mode 100644 index 00000000..b66c13d2 --- /dev/null +++ b/nymea-app/ui/components/BigTile.qml @@ -0,0 +1,69 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.1 +import Nymea 1.0 + +Item { + id: root + implicitHeight: layout.implicitHeight + app.margins + + property Thing thing: null + + property bool showHeader: true + + property alias contentItem: content.contentItem + + property alias leftPadding: content.leftPadding + property alias rightPadding: content.rightPadding + property alias topPadding: content.topPadding + property alias bottomPadding: content.bottomPadding + + signal clicked(); + + Rectangle { + id: background + anchors.fill: parent + anchors.margins: app.margins / 2 + radius: 6 + + gradient: Gradient { + GradientStop { + position: (headerRow.height + app.margins) / background.height + color: Qt.tint(app.backgroundColor, Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05)) + } + GradientStop { + position: (headerRow.height + app.margins) / background.height + color:root.showHeader ? + Qt.tint(app.backgroundColor, Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .1)) + : Qt.tint(app.backgroundColor, Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05)) + } + } + } + + ColumnLayout { + id: layout + spacing: 0 + anchors { left: parent.left; top: parent.top; right: parent.right; margins: app.margins / 2 } + + RowLayout { + id: headerRow + visible: root.showHeader + Layout.margins: app.margins / 2 + Label { + Layout.fillWidth: true + text: root.thing.name + elide: Text.ElideRight + } + ThingStatusIcons { + thing: root.thing + } + } + + ItemDelegate { + id: content + Layout.fillWidth: true + height: contentItem.implicitHeight + onClicked: root.clicked() + } + } +} diff --git a/nymea-app/ui/components/InfoPane.qml b/nymea-app/ui/components/InfoPane.qml index 1acea6a0..882f5184 100644 --- a/nymea-app/ui/components/InfoPane.qml +++ b/nymea-app/ui/components/InfoPane.qml @@ -3,89 +3,53 @@ import QtQuick.Controls 2.2 import QtQuick.Controls.Material 2.2 import QtQuick.Layouts 1.2 -Item { +InfoPaneBase { id: root - implicitHeight: d.shownHeight - visible: d.shownHeight > 0 property alias text: textLabel.text property alias imageSource: icon.name property alias buttonText: button.text - property alias color: background.color - property alias textColor: textLabel.color + property color textColor: "white" property bool rotatingIcon: false - property bool shown: false - - function show() { - shown = true; - } - function hide() { - shown = false; - } - - signal paneClicked(); signal buttonClicked(); - QtObject { - id: d - property int shownHeight: shown ? contentRow.implicitHeight : 0 - Behavior on shownHeight { NumberAnimation { easing.type: Easing.InOutQuad; duration: 150 } } - } + contentItem: RowLayout { + id: contentRow + anchors { left: parent.left; top: parent.top; right: parent.right } - Pane { - anchors { left: parent.left; bottom: parent.bottom; right: parent.right } - Material.elevation: 2 - padding: 0 - height: contentRow.implicitHeight + Label { + id: textLabel + color: root.textColor + font.pixelSize: app.smallFont + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + wrapMode: Text.WordWrap + } + ColorIcon { + id: icon + Layout.preferredHeight: app.smallIconSize + Layout.preferredWidth: height + color: root.textColor + visible: name.length > 0 - MouseArea { - anchors.fill: parent - onClicked: root.paneClicked() + RotationAnimation on rotation { + from: 0 + to: 360 + duration: 2000 + loops: Animation.Infinite + running: root.rotatingIcon + onStopped: icon.rotation = 0; + } } - Rectangle { - id: background - color: app.accentColor - anchors.fill: parent - - RowLayout { - id: contentRow - anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: app.margins; rightMargin: app.margins } - Label { - id: textLabel - color: "white" - font.pixelSize: app.smallFont - Layout.fillWidth: true - Layout.margins: app.margins * .4 - horizontalAlignment: Text.AlignRight - wrapMode: Text.WordWrap - } - ColorIcon { - id: icon - height: app.smallIconSize - width: height - color: "white" - visible: name.length > 0 - - RotationAnimation on rotation { - from: 0 - to: 360 - duration: 2000 - loops: Animation.Infinite - running: root.rotatingIcon - onStopped: icon.rotation = 0; - } - } - - Button { - id: button - visible: text.length > 0 - onClicked: root.buttonClicked() - } - } + Button { + id: button + Layout.leftMargin: app.margins + visible: text.length > 0 + onClicked: root.buttonClicked() } } } diff --git a/nymea-app/ui/components/InfoPaneBase.qml b/nymea-app/ui/components/InfoPaneBase.qml new file mode 100644 index 00000000..1664484f --- /dev/null +++ b/nymea-app/ui/components/InfoPaneBase.qml @@ -0,0 +1,59 @@ +import QtQuick 2.0 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import QtQuick.Layouts 1.2 + +Item { + id: root + implicitHeight: d.shownHeight + visible: d.shownHeight > 0 + + property alias color: background.color + property bool shown: false + + property alias contentItem: content.data + + function show() { + shown = true; + } + function hide() { + shown = false; + } + + signal clicked(); + + QtObject { + id: d + property int shownHeight: shown ? content.implicitHeight : 0 + Behavior on shownHeight { NumberAnimation { easing.type: Easing.InOutQuad; duration: 150 } } + } + + Pane { + anchors { left: parent.left; bottom: parent.bottom; right: parent.right } + Material.elevation: 2 + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + topPadding: 0 + height: content.implicitHeight + + MouseArea { + anchors.fill: parent + onClicked: root.clicked() + } + + Rectangle { + id: background + color: app.accentColor + anchors.fill: parent + } + + Item { + id: content + anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: app.margins; rightMargin: app.margins; topMargin: app.margins / 2 } + implicitHeight: childrenRect.height + app.margins + } + } +} + + diff --git a/nymea-app/ui/components/MainPageTile.qml b/nymea-app/ui/components/MainPageTile.qml index 3305c9b4..8c466eba 100644 --- a/nymea-app/ui/components/MainPageTile.qml +++ b/nymea-app/ui/components/MainPageTile.qml @@ -48,6 +48,7 @@ Item { property int signalStrength: 0 property int setupStatus: Thing.ThingSetupStatusNone property bool batteryCritical: false + property bool updateStatus: false property alias contentItem: innerContent.children @@ -119,7 +120,10 @@ Item { spacing: 0 ColumnLayout { Layout.fillHeight: true - Layout.margins: app.margins + Layout.topMargin: app.margins + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + Layout.bottomMargin: app.margins / 2 Item { visible: backgroundImg.status !== Image.Ready Layout.fillWidth: true @@ -171,10 +175,17 @@ Item { } } - Row { + RowLayout { id: quickAlertPane anchors { top: parent.top; right: parent.right; margins: app.margins } - spacing: app.margins / 2 + ColorIcon { + height: app.smallIconSize + width: height + name: "../images/system-update.svg" + color: app.accentColor + visible: root.updateStatus + } + ColorIcon { height: app.smallIconSize width: height diff --git a/nymea-app/ui/components/MainViewBase.qml b/nymea-app/ui/components/MainViewBase.qml index baf58b3a..421655eb 100644 --- a/nymea-app/ui/components/MainViewBase.qml +++ b/nymea-app/ui/components/MainViewBase.qml @@ -36,13 +36,15 @@ import Nymea 1.0 import "../components" import "../delegates" -MouseArea { +Item { id: root - // Prevent scroll events to swipe left/right in case they fall through the grid - preventStealing: true - onWheel: wheel.accepted = true - property string title: "" + // Prevent scroll events to swipe left/right in case they fall through the grid + MouseArea { + anchors.fill: parent + preventStealing: true + onWheel: wheel.accepted = true + } } diff --git a/nymea-app/ui/components/NymeaListItemDelegate.qml b/nymea-app/ui/components/NymeaListItemDelegate.qml index 7ddfc47e..577a2eec 100644 --- a/nymea-app/ui/components/NymeaListItemDelegate.qml +++ b/nymea-app/ui/components/NymeaListItemDelegate.qml @@ -151,7 +151,7 @@ SwipeDelegate { ColorIcon { id: secondaryIcon - Layout.preferredHeight: app.iconSize * .5 + Layout.preferredHeight: app.smallIconSize Layout.preferredWidth: height visible: name.length > 0 MouseArea { @@ -165,7 +165,7 @@ SwipeDelegate { ColorIcon { id: tertiaryIcon - Layout.preferredHeight: app.iconSize * .5 + Layout.preferredHeight: app.smallIconSize Layout.preferredWidth: height visible: name.length > 0 MouseArea { @@ -179,7 +179,7 @@ SwipeDelegate { ColorIcon { id: progressionIcon - Layout.preferredHeight: app.iconSize * .6 + Layout.preferredHeight: app.smallIconSize Layout.preferredWidth: height name: "../images/next.svg" visible: root.progressive diff --git a/nymea-app/ui/components/ThingInfoPane.qml b/nymea-app/ui/components/ThingInfoPane.qml new file mode 100644 index 00000000..66b2945d --- /dev/null +++ b/nymea-app/ui/components/ThingInfoPane.qml @@ -0,0 +1,68 @@ +import QtQuick 2.0 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import QtQuick.Layouts 1.2 +import Nymea 1.0 + +InfoPaneBase { + id: root + + property Thing thing: null + + readonly property bool setupInProgress: root.thing.setupStatus == Thing.ThingSetupStatusInProgress + 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.stateByName("connected") + readonly property State signalStrengthState: root.thing.stateByName("signalStrength") + readonly property State updateStatusState: root.thing.stateByName("updateStatus") + readonly property bool updateAvailable: updateStatusState && updateStatusState.value === "available" + readonly property bool updateRunning: updateStatusState && updateStatusState.value === "updating" + readonly property bool isWireless: root.thing.thingClass.interfaces.indexOf("wirelessconnectable") >= 0 + readonly property bool alertState: setupFailure || + (connectedState !== null && connectedState.value === false) || + (batteryCriticalState !== null && batteryCriticalState.value === true) + readonly property bool batteryCritical: batteryCriticalState && batteryCriticalState.value === true + readonly property bool highlightState: updateAvailable || updateRunning + + shown: setupInProgress || setupFailure || batteryState !== null || (connectedState !== null && connectedState.value === false) || signalStrengthState !== null || updateAvailable + + color: alertState ? "red" + : highlightState ? app.accentColor : "transparent" + + contentItem: RowLayout { + id: contentRow + anchors { left: parent.left; top: parent.top; right: parent.right } + + Label { + id: textLabel + color: root.alertState || root.highlightState ? "white" : app.foregroundColor + font.pixelSize: app.smallFont + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + wrapMode: Text.WordWrap + text: root.setupInProgress ? + qsTr("Thing is being set up...") + : root.setupFailure ? + (root.thing.setupDisplayMessage.length > 0 ? root.thing.setupDisplayMessage : qsTr("Thing setup failed!")) + : (root.connectedState !== null && root.connectedState.value === false) ? + qsTr("Thing is not connected!") + : root.updateAvailable ? + qsTr("Update available!") + : root.updateRunning ? + qsTr("Updating...") + : root.batteryCritical ? + qsTr("Thing runs out of battery!") + : "" + + } + + ThingStatusIcons { + thing: root.thing + color: root.alertState || root.highlightState ? "white" : keyColor + + } + } +} + + diff --git a/nymea-app/ui/components/ThingStatusIcons.qml b/nymea-app/ui/components/ThingStatusIcons.qml new file mode 100644 index 00000000..5bd1d225 --- /dev/null +++ b/nymea-app/ui/components/ThingStatusIcons.qml @@ -0,0 +1,46 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.1 +import Nymea 1.0 + +RowLayout { + id: root + Layout.fillWidth: false + + property Thing thing: null + + property color color: keyColor + readonly property color keyColor: updateStatusIcon.keyColor + + UpdateStatusIcon { + id: updateStatusIcon + Layout.preferredHeight: app.smallIconSize + Layout.preferredWidth: height + thing: root.thing + visible: updateAvailable || updateRunning + Binding { target: updateStatusIcon; property: "color"; value: root.color; when: root.color !== root.keyColor } + } + BatteryStatusIcon { + id: batteryStatusIcon + Layout.preferredHeight: app.smallIconSize + Layout.preferredWidth: height + thing: root.thing + visible: root.thing.setupStatus == Thing.ThingSetupStatusComplete && (hasBatteryLevel || isCritical) + Binding { target: batteryStatusIcon; property: "color"; value: root.color; when: root.color !== root.keyColor } + } + ConnectionStatusIcon { + id: connectionStatusIcon + Layout.preferredHeight: app.smallIconSize + Layout.preferredWidth: height + thing: root.thing + visible: root.thing.setupStatus == Thing.ThingSetupStatusComplete && (hasSignalStrength || !isConnected) + Binding { target: connectionStatusIcon; property: "color"; value: root.color; when: root.color !== root.keyColor } + } + SetupStatusIcon { + id: setupStatusIcon + Layout.preferredHeight: app.smallIconSize + Layout.preferredWidth: height + thing: root.thing + visible: setupFailed || setupInProgress + Binding { target: setupStatusIcon; property: "color"; value: root.color; when: root.color !== root.keyColor } + } +} diff --git a/nymea-app/ui/components/UpdateStatusIcon.qml b/nymea-app/ui/components/UpdateStatusIcon.qml index 24d65b89..f59e90b5 100644 --- a/nymea-app/ui/components/UpdateStatusIcon.qml +++ b/nymea-app/ui/components/UpdateStatusIcon.qml @@ -11,6 +11,7 @@ ColorIcon { readonly property bool updateRunning: updateStatusState && updateStatusState.value === "updating" name: "../images/system-update.svg" + color: app.accentColor RotationAnimation on rotation { from: 0; to: 360 diff --git a/nymea-app/ui/customviews/CustomViewBase.qml b/nymea-app/ui/customviews/CustomViewBase.qml deleted file mode 100644 index 798a9547..00000000 --- a/nymea-app/ui/customviews/CustomViewBase.qml +++ /dev/null @@ -1,39 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU General Public License as published by the Free Software -* Foundation, GNU version 3. This project 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 -* this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -import QtQuick 2.5 - -Item { - - property var device: null - property var deviceClass: null - property string interfaceName - -} diff --git a/nymea-app/ui/customviews/GenericTypeGraph.qml b/nymea-app/ui/customviews/GenericTypeGraph.qml index 0ffacb48..0bfe093e 100644 --- a/nymea-app/ui/customviews/GenericTypeGraph.qml +++ b/nymea-app/ui/customviews/GenericTypeGraph.qml @@ -259,7 +259,7 @@ Item { id: connectedLineSeries onPointAdded: { var newPoint = connectedLineSeries.at(index) - print("pointadded", newPoint.x, newPoint.y) +// print("pointadded", newPoint.x, newPoint.y) } } @@ -284,7 +284,7 @@ Item { id: lineSeries1 onPointAdded: { var newPoint = lineSeries1.at(index) - print("pointadded", newPoint.x, newPoint.y) +// print("pointadded", newPoint.x, newPoint.y) if (newPoint.x > lineSeries0.at(0).x) { lineSeries0.replace(0, newPoint.x, 0) diff --git a/nymea-app/ui/customviews/NotificationsView.qml b/nymea-app/ui/customviews/NotificationsView.qml deleted file mode 100644 index e8362174..00000000 --- a/nymea-app/ui/customviews/NotificationsView.qml +++ /dev/null @@ -1,79 +0,0 @@ -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* -* Copyright 2013 - 2020, nymea GmbH -* Contact: contact@nymea.io -* -* This file is part of nymea. -* This project including source code and documentation is protected by -* copyright law, and remains the property of nymea GmbH. All rights, including -* reproduction, publication, editing and translation, are reserved. The use of -* this project is subject to the terms of a license agreement to be concluded -* with nymea GmbH in accordance with the terms of use of nymea GmbH, available -* under https://nymea.io/license -* -* GNU General Public License Usage -* Alternatively, this project may be redistributed and/or modified under the -* terms of the GNU General Public License as published by the Free Software -* Foundation, GNU version 3. This project 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 -* this project. If not, see . -* -* For any further details and any questions please contact us under -* contact@nymea.io or see our FAQ/Licensing Information on -* https://nymea.io/license/faq -* -* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -import QtQuick 2.5 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 2.1 -import "../components" -import Nymea 1.0 - -CustomViewBase { - id: root - height: grid.implicitHeight + app.margins * 2 - - ColumnLayout { - id: grid - anchors { left: parent.left; top: parent.top; right: parent.right; margins: app.margins } - Label { - Layout.fillWidth: true - text: qsTr("Send a notification now:") - } - TextArea { - id: titleTextArea - placeholderText: qsTr("Title") - Layout.fillWidth: true - } - TextArea { - id: bodyTextArea - placeholderText: qsTr("Text") - Layout.fillWidth: true - } - Button { - Layout.fillWidth: true - text: qsTr("Send") - onClicked: { - - var params = [] - var param1 = {} - print("bla:", root.deviceClass.actionTypes.findByName("notify").paramTypes) - var paramTypeId = root.deviceClass.actionTypes.findByName("notify").paramTypes.findByName("title").id - param1.paramTypeId = paramTypeId - param1.value = titleTextArea.text - params.push(param1) - var param2 = {} - paramTypeId = root.deviceClass.actionTypes.findByName("notify").paramTypes.findByName("body").id - param2.paramTypeId = paramTypeId - param2.value = bodyTextArea.text - params.push(param2) - engine.deviceManager.executeAction(root.device.id, root.deviceClass.actionTypes.findByName("notify").id, params) - } - } - } -} diff --git a/nymea-app/ui/customviews/WeatherView.qml b/nymea-app/ui/customviews/WeatherView.qml index ccc4a86c..a35b8ad5 100644 --- a/nymea-app/ui/customviews/WeatherView.qml +++ b/nymea-app/ui/customviews/WeatherView.qml @@ -34,30 +34,21 @@ import QtQuick.Controls 2.1 import "../components" import Nymea 1.0 -CustomViewBase { +Item { id: root - height: grid.implicitHeight + app.margins * 2 + implicitHeight: grid.implicitHeight + app.margins * 2 - readonly property StateType weatherConditionStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("weatherCondition") : null - readonly property State weatherConditionState: weatherConditionStateType && device.states ? device.states.getState(weatherConditionStateType.id) : null + property Thing thing: null - readonly property StateType weatherDescriptionStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("weatherDescription") : null - readonly property State weatherDescriptionState: weatherDescriptionStateType && device.states ? device.states.getState(weatherDescriptionStateType.id) : null + readonly property State weatherConditionState: thing.stateByName("weatherCondition") + readonly property State weatherDescriptionState: thing.stateByName("weatherDescription") + readonly property State temperatureState: thing.stateByName("temperature") + readonly property State humidityState: thing.stateByName("humidity") + readonly property State pressureState: thing.stateByName("pressure") + readonly property State windDirectionState: thing.stateByName("windDirection") + readonly property State windSpeedState: thing.stateByName("windSpeed") - readonly property StateType temperatureStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("temperature") : null - readonly property State temperatureState: temperatureStateType && device.states ? device.states.getState(temperatureStateType.id) : null - - readonly property StateType humidityStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("humidity") : null - readonly property State humidityState: humidityStateType && device.states ? device.states.getState(humidityStateType.id) : null - - readonly property StateType pressureStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("pressure") : null - readonly property State pressureState: pressureStateType && device.states ? device.states.getState(pressureStateType.id) : null - - readonly property StateType windDirectionStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("windDirection") : null - readonly property State windDirectionState: windDirectionStateType && device.states ? device.states.getState(windDirectionStateType.id) : null - - readonly property StateType windSpeedStateType: deviceClass.stateTypes ? deviceClass.stateTypes.findByName("windSpeed") : null - readonly property State windSpeedState: windSpeedStateType && device.states ? device.states.getState(windSpeedStateType.id) : null + readonly property StateType temperatureStateType: thing.thingClass.stateTypes.findByName("temperature") ColumnLayout { id: grid @@ -86,7 +77,6 @@ CustomViewBase { Label { text: (temperatureState ? Math.round(Types.toUiValue(temperatureState.value, temperatureStateType.unit) * 10) / 10 : "N/A") + " °" Layout.fillWidth: true - font.pixelSize: app.largeFont horizontalAlignment: Text.AlignHCenter } ColorIcon { diff --git a/nymea-app/ui/delegates/InterfaceTile.qml b/nymea-app/ui/delegates/InterfaceTile.qml index 6c730d93..bb2ee7f8 100644 --- a/nymea-app/ui/delegates/InterfaceTile.qml +++ b/nymea-app/ui/delegates/InterfaceTile.qml @@ -44,6 +44,7 @@ MainPageTile { isWireless: devicesSubProxyConnectables.count > 0 && devicesSubProxyConnectables.get(0).thingClass.interfaces.indexOf("wirelessconnectable") >= 0 batteryCritical: devicesSubProxyBattery.count > 0 setupStatus: thingsSubProxySetupFailure.count > 0 ? Thing.ThingSetupStatusFailed : Thing.ThingSetupStatusComplete + updateStatus: thingsSubProxyUpdates.count > 0 property Interface iface: null property alias filterTagId: devicesProxy.filterTagId @@ -139,6 +140,12 @@ MainPageTile { parentProxy: devicesProxy filterSetupFailed: true } + ThingsProxy { + id: thingsSubProxyUpdates + engine: _engine + parentProxy: devicesProxy + filterUpdates: true + } property int currentDeviceIndex: 0 readonly property Device currentDevice: devicesProxy.get(currentDeviceIndex) @@ -220,7 +227,7 @@ MainPageTile { var d = devicesProxy.get(i); var st = d.deviceClass.stateTypes.findByName("playbackStatus") var s = d.states.getState(st.id) - s.valueChanged.connect(function() {updateTile()}) + s.valueChanged.connect(function() {inlineMediaControl.updateTile()}) } updateTile(); } diff --git a/nymea-app/ui/delegates/ThingTile.qml b/nymea-app/ui/delegates/ThingTile.qml index 930ac767..07f76191 100644 --- a/nymea-app/ui/delegates/ThingTile.qml +++ b/nymea-app/ui/delegates/ThingTile.qml @@ -45,6 +45,7 @@ MainPageTile { disconnected: connectedState && connectedState.value === false signalStrength: signalStrengthState ? signalStrengthState.value : -1 setupStatus: device.setupStatus + updateStatus: updateStatusState && updateStatusState.value !== "idle" backgroundImage: artworkState && artworkState.value.length > 0 ? artworkState.value : "" @@ -54,6 +55,7 @@ MainPageTile { readonly property State signalStrengthState: device.stateByName("signalStrength") readonly property State batteryCriticalState: deviceClass.interfaces.indexOf("battery") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("batteryCritical").id) : null readonly property State artworkState: deviceClass.interfaces.indexOf("mediametadataprovider") >= 0 ? device.states.getState(deviceClass.stateTypes.findByName("artwork").id) : null + readonly property State updateStatusState: device.stateByName("updateStatus") contentItem: Loader { id: loader diff --git a/nymea-app/ui/devicelistpages/ClosablesDeviceListPage.qml b/nymea-app/ui/devicelistpages/ClosablesDeviceListPage.qml index bb442646..3ca3b42e 100644 --- a/nymea-app/ui/devicelistpages/ClosablesDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/ClosablesDeviceListPage.qml @@ -83,88 +83,69 @@ DeviceListPageBase { } } - ListView { + Flickable { anchors.fill: parent - model: devicesProxy - spacing: app.margins + contentHeight: contentGrid.implicitHeight + topMargin: app.margins / 2 - delegate: Pane { - id: itemDelegate - width: parent.width + GridLayout { + id: contentGrid + width: parent.width - app.margins + anchors.horizontalCenter: parent.horizontalCenter + columns: Math.ceil(width / 600) + rowSpacing: 0 + columnSpacing: 0 + Repeater { + model: root.thingsProxy - property bool inline: width > 500 + delegate: BigTile { + id: itemDelegate + Layout.preferredWidth: contentGrid.width / contentGrid.columns + thing: root.thingsProxy.getThing(model.id) + showHeader: false + enabled: connectedState == null || connectedState.value === true + topPadding: 0 + bottomPadding: 0 - property Device device: devicesProxy.getDevice(model.id) - property DeviceClass deviceClass: device.deviceClass + onClicked: root.enterPage(index) - property var connectedStateType: deviceClass.stateTypes.findByName("connected"); - property var connectedState: connectedStateType ? device.states.getState(connectedStateType.id) : null + property State connectedState: thing.stateByName("connected") + property State movingState: thing.stateByName("moving") + property State percentageState: thing.stateByName("percentage") - property StateType percentageStateType: deviceClass.stateTypes.findByName("percentage"); - property ActionType percentageActionType: deviceClass.actionTypes.findByName("percentage"); - property State percentageState: percentageStateType ? device.states.getState(percentageStateType.id) : null - - property StateType movingStateType: deviceClass.stateTypes.findByName("moving"); - property ActionType movingActionType: deviceClass.actionTypes.findByName("moving"); - property State movingState: movingStateType ? device.states.getState(movingStateType.id) : null - - Material.elevation: 1 - topPadding: 0 - bottomPadding: 0 - leftPadding: 0 - rightPadding: 0 - contentItem: ItemDelegate { - id: contentItem - implicitHeight: nameRow.implicitHeight - - topPadding: 0 - - contentItem: ColumnLayout { - spacing: 0 - RowLayout { - enabled: itemDelegate.connectedState === null || itemDelegate.connectedState.value === true - id: nameRow - z: 2 // make sure the switch in here is on top of the slider, given we cheated a bit and made them overlap + contentItem: RowLayout { spacing: app.margins - Item { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - Layout.alignment: Qt.AlignVCenter - ColorIcon { - id: icon - anchors.fill: parent - color: itemDelegate.movingStateType && itemDelegate.movingState.value === true - ? app.accentColor - : keyColor - name: itemDelegate.percentageStateType - ? root.iconBasename + "-" + app.pad(Math.round(itemDelegate.percentageState.value / 10) * 10, 3) + ".svg" - : root.iconBasename + "-050.svg" - } + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + color: itemDelegate.movingState && itemDelegate.movingState.value === true + ? app.accentColor + : keyColor + name: itemDelegate.percentageState + ? root.iconBasename + "-" + app.pad(Math.round(itemDelegate.percentageState.value / 10) * 10, 3) + ".svg" + : root.iconBasename + "-050.svg" } Label { Layout.fillWidth: true - text: itemDelegate.device.name + text: itemDelegate.thing.name elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter } - Item { - Layout.preferredWidth: shutterControls.implicitWidth - Layout.preferredHeight: app.iconSize * 2 - ShutterControls { - id: shutterControls - height: parent.height - device: itemDelegate.device - invert: root.invertControls - } + ThingStatusIcons { + thing: itemDelegate.thing + } + + ShutterControls { + id: shutterControls + Layout.fillWidth: false + height: parent.height + device: itemDelegate.thing + invert: root.invertControls } } } - onClicked: { - enterPage(index) - } } } } diff --git a/nymea-app/ui/devicelistpages/GarageThingListPage.qml b/nymea-app/ui/devicelistpages/GarageThingListPage.qml index c6eaa856..2d9b730c 100644 --- a/nymea-app/ui/devicelistpages/GarageThingListPage.qml +++ b/nymea-app/ui/devicelistpages/GarageThingListPage.qml @@ -44,242 +44,86 @@ DeviceListPageBase { header: NymeaHeader { id: header onBackPressed: pageStack.pop() - text: root.title + text: qsTr("Garage doors") } - ListView { + Flickable { anchors.fill: parent - model: devicesProxy - spacing: app.margins + contentHeight: contentGrid.implicitHeight + topMargin: app.margins / 2 - delegate: Pane { - id: itemDelegate - width: parent.width + GridLayout { + id: contentGrid + width: parent.width - app.margins + anchors.horizontalCenter: parent.horizontalCenter + columns: Math.ceil(width / 600) + rowSpacing: 0 + columnSpacing: 0 + Repeater { + model: root.thingsProxy + delegate: BigTile { + id: itemDelegate + Layout.preferredWidth: contentGrid.width / contentGrid.columns + thing: root.thingsProxy.getThing(model.id) + showHeader: false + enabled: connectedState == null || connectedState.value === true + topPadding: 0 + bottomPadding: 0 - property bool inline: width > 500 + onClicked: root.enterPage(index) - property Device device: devicesProxy.getDevice(model.id) - property Device thing: device - property DeviceClass deviceClass: device.deviceClass + property State connectedState: thing.stateByName("connected") + property State movingState: thing.stateByName("moving") + property State percentageState: thing.stateByName("percentage") - readonly property bool isImpulseBased: device.deviceClass.interfaces.indexOf("impulsegaragedoor") >= 0 - readonly property bool isStateful: device.deviceClass.interfaces.indexOf("statefulgaragedoor") >= 0 - || device.deviceClass.interfaces.indexOf("garagegate") >= 0 // garagegate did not inherit garagedoor before 0.23 - readonly property bool isExtended: device.deviceClass.interfaces.indexOf("extendedstatefulgaragedoor") >= 0 + readonly property bool isImpulseBased: thing.thingClass.interfaces.indexOf("impulsegaragedoor") >= 0 - property var connectedStateType: deviceClass.stateTypes.findByName("connected"); - property var connectedState: connectedStateType ? device.states.getState(connectedStateType.id) : null + contentItem: RowLayout { + spacing: app.margins + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + color: itemDelegate.movingState && itemDelegate.movingState.value === true + ? app.accentColor + : keyColor + name: itemDelegate.percentageState + ? root.iconBasename + "-" + app.pad(Math.round(itemDelegate.percentageState.value / 10) * 10, 3) + ".svg" + : root.iconBasename + "-050.svg" + } - property StateType movingStateType: deviceClass.stateTypes.findByName("moving"); - property ActionType movingActionType: deviceClass.actionTypes.findByName("moving"); - property State movingState: movingStateType ? device.states.getState(movingStateType.id) : null + Label { + Layout.fillWidth: true + text: itemDelegate.thing.name + elide: Text.ElideRight + } - Material.elevation: 1 - topPadding: 0 - bottomPadding: 0 - leftPadding: 0 - rightPadding: 0 - contentItem: ItemDelegate { - id: contentItem - implicitHeight: contentLoader.item.implicitHeight + ThingStatusIcons { + thing: itemDelegate.thing + } - topPadding: 0 - - contentItem: Loader { - id: contentLoader - enabled: itemDelegate.connectedState === null || itemDelegate.connectedState.value === true - sourceComponent: isImpulseBased ? impulseGaragedoor - : isExtended ? extendedStatefulGaragedoor - : isStateful ? garagedoor - : simpleGaragedoor - Binding { target: contentLoader.item; property: "device"; value: itemDelegate.device } - } - onClicked: { - enterPage(index) - } - } - } - } - - Component { - id: impulseGaragedoor - - RowLayout { - id: contentItem - property Device device: null - - spacing: app.margins - Item { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - Layout.alignment: Qt.AlignVCenter - - ColorIcon { - id: icon - anchors.fill: parent - name: root.iconBasename + "-100.svg" - } - } - - Label { - Layout.fillWidth: true - text: contentItem.device.name - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - - ItemDelegate { - Layout.preferredHeight: app.iconSize * 2 - Layout.preferredWidth: height - ColorIcon { - anchors.centerIn: parent - height: app.iconSize - width: height - name: "../images/closable-move.svg" - } - onClicked: { - var actionTypeId = device.thingClass.actionTypes.findByName("triggerImpulse").id - print("Triggering impulse", actionTypeId) - engine.thingManager.executeAction(device.id, actionTypeId) - } - } - } - } - - Component { - id: simpleGaragedoor - - RowLayout { - id: contentItem - spacing: app.margins - property Device device: null - - Item { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - Layout.alignment: Qt.AlignVCenter - - ColorIcon { - id: icon - anchors.fill: parent - color: keyColor - name: root.iconBasename + "-100.svg" - } - } - - Label { - Layout.fillWidth: true - text: contentItem.device.name - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - - Item { - Layout.preferredWidth: shutterControls.implicitWidth - Layout.preferredHeight: app.iconSize * 2 - ShutterControls { - id: shutterControls - height: parent.height - device: contentItem.device - } - } - } - } - - - Component { - id: garagedoor - - RowLayout { - id: contentItem - spacing: app.margins - property Device device: null - - property StateType stateStateType: device.deviceClass.stateTypes.findByName("state") - property State stateState: stateStateType ? device.states.getState(stateStateType.id) : null - Item { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - Layout.alignment: Qt.AlignVCenter - - ColorIcon { - id: icon - anchors.fill: parent - color: ["opening", "closing"].indexOf(contentItem.stateState.value) >= 0 - ? app.accentColor - : keyColor - name: root.iconBasename + "-100.svg" - } - } - - Label { - Layout.fillWidth: true - text: contentItem.device.name - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - - Item { - Layout.preferredWidth: shutterControls.implicitWidth - Layout.preferredHeight: app.iconSize * 2 - ShutterControls { - id: shutterControls - height: parent.height - device: contentItem.device - } - } - } - } - - - Component { - id: extendedStatefulGaragedoor - - RowLayout { - id: contentItem - spacing: app.margins - property Device device: null - - property StateType stateStateType: device.deviceClass.stateTypes.findByName("state") - property State stateState: stateStateType ? device.states.getState(stateStateType.id) : null - - property StateType percentageStateType: device.deviceClass.stateTypes.findByName("percentage"); - property ActionType percentageActionType: device.deviceClass.actionTypes.findByName("percentage"); - property State percentageState: percentageStateType ? device.states.getState(percentageStateType.id) : null - - Item { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - Layout.alignment: Qt.AlignVCenter - - ColorIcon { - id: icon - anchors.fill: parent - color: ["opening", "closing"].indexOf(contentItem.stateState.value) >= 0 - ? app.accentColor - : keyColor - name: contentItem.percentageStateType - ? root.iconBasename + "-" + app.pad(Math.round(contentItem.percentageState.value / 10) * 10, 3) + ".svg" - : root.iconBasename + "-050.svg" - } - } - - Label { - Layout.fillWidth: true - text: contentItem.device.name - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - - Item { - Layout.preferredWidth: shutterControls.implicitWidth - Layout.preferredHeight: app.iconSize * 2 - ShutterControls { - id: shutterControls - height: parent.height - device: contentItem.device + ShutterControls { + id: shutterControls + Layout.fillWidth: false + height: parent.height + device: itemDelegate.thing + invert: root.invertControls + visible: !itemDelegate.isImpulseBased + } + ProgressButton { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: height + longpressEnabled: false + imageSource: "../images/closable-move.svg" + visible: itemDelegate.isImpulseBased + onClicked: { + var actionTypeId = itemDelegate.thing.thingClass.actionTypes.findByName("triggerImpulse").id + print("Triggering impulse", actionTypeId) + engine.thingManager.executeAction(itemDelegate.thing.id, actionTypeId) + } + } + } } } } diff --git a/nymea-app/ui/devicelistpages/GenericDeviceListPage.qml b/nymea-app/ui/devicelistpages/GenericDeviceListPage.qml index 0efbf889..88575567 100644 --- a/nymea-app/ui/devicelistpages/GenericDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/GenericDeviceListPage.qml @@ -53,15 +53,69 @@ DeviceListPageBase { } } - ListView { + Flickable { anchors.fill: parent - model: root.devicesProxy + contentHeight: contentGrid.implicitHeight + topMargin: app.margins / 2 - delegate: ThingDelegate { - width: parent.width - device: engine.deviceManager.devices.getDevice(model.id); - onClicked: { - enterPage(index) + GridLayout { + id: contentGrid + width: parent.width - app.margins + anchors.horizontalCenter: parent.horizontalCenter + columns: Math.ceil(width / 600) + rowSpacing: 0 + columnSpacing: 0 + Repeater { + model: root.thingsProxy + + delegate: BigTile { + id: itemDelegate + Layout.preferredWidth: contentGrid.width / contentGrid.columns + thing: root.thingsProxy.getThing(model.id) + showHeader: false + enabled: connectedState == null || connectedState.value === true + topPadding: 0 + bottomPadding: 0 + + onClicked: root.enterPage(index) + + property State connectedState: thing.stateByName("connected") + property State powerState: thing.stateByName("power") + + contentItem: RowLayout { + spacing: app.margins + + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + name: app.interfacesToIcon(itemDelegate.thing.thingClass.interfaces) + color: itemDelegate.powerState && itemDelegate.powerState.value === true ? app.accentColor : keyColor + } + + Label { + Layout.fillWidth: true + text: itemDelegate.thing.name + elide: Text.ElideRight + } + + ThingStatusIcons { + thing: itemDelegate.thing + } + + Switch { + visible: itemDelegate.powerState !== null + checked: itemDelegate.powerState.value === true + onClicked: { + var params = []; + var param1 = {}; + param1["paramTypeId"] = itemDelegate.powerState.stateTypeId; + param1["value"] = checked; + params.push(param1) + engine.deviceManager.executeAction(itemDelegate.thing.id, itemDelegate.powerState.stateTypeId, params) + } + } + } + } } } } diff --git a/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml b/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml index 20cf2b8f..22ca1f05 100644 --- a/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/LightsDeviceListPage.qml @@ -37,6 +37,7 @@ import Nymea 1.0 import "../components" DeviceListPageBase { + id: root header: NymeaHeader { text: qsTr("Lights") @@ -70,125 +71,151 @@ DeviceListPageBase { } } - ListView { + Flickable { anchors.fill: parent - model: devicesProxy - spacing: app.margins + contentHeight: contentGrid.implicitHeight + topMargin: app.margins / 2 - delegate: Pane { - id: itemDelegate - width: parent.width + GridLayout { + id: contentGrid + width: parent.width - app.margins + anchors.horizontalCenter: parent.horizontalCenter + columns: Math.ceil(width / 600) + rowSpacing: 0 + columnSpacing: 0 + Repeater { + model: root.thingsProxy - property bool inline: width > 500 + delegate: BigTile { + id: itemDelegate + Layout.preferredWidth: contentGrid.width / contentGrid.columns + thing: root.thingsProxy.getThing(model.id) + showHeader: false + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + enabled: connectedState == null || connectedState.value === true - property Device device: devicesProxy.getDevice(model.id) - property DeviceClass deviceClass: device.deviceClass + property State connectedState: thing.stateByName("connected") + property State powerState: thing.stateByName("power") + property State brightnessState: thing.stateByName("brightness") + property State colorState: thing.stateByName("color") - property StateType connectedStateType: deviceClass.stateTypes.findByName("connected"); - property State connectedState: connectedStateType ? device.states.getState(connectedStateType.id) : null + onClicked: root.enterPage(index) - property StateType powerStateType: deviceClass.stateTypes.findByName("power"); - property ActionType powerActionType: deviceClass.actionTypes.findByName("power"); - property State powerState: device.states.getState(powerStateType.id) + property int pendingCommand: -1 + property var pendingValue: null - property StateType brightnessStateType: deviceClass.stateTypes.findByName("brightness"); - property ActionType brightnessActionType: deviceClass.actionTypes.findByName("brightness"); - property State brightnessState: brightnessStateType ? device.states.getState(brightnessStateType.id) : null - - property StateType colorStateType: deviceClass.stateTypes.findByName("color"); - property State colorState: colorStateType ? device.states.getState(colorStateType.id) : null - - Material.elevation: 1 - topPadding: 0 - bottomPadding: 0 - leftPadding: 0 - rightPadding: 0 - contentItem: ItemDelegate { - id: contentItem - implicitHeight: nameRow.implicitHeight - // gradient: Gradient { - // GradientStop { position: 0.0; color: "transparent" } - // GradientStop { position: 1.0; color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, 0.05) } - // } - - - topPadding: 0 - - Rectangle { - anchors { left: parent.left; top: parent.top; bottom: parent.bottom } - width: app.margins / 2 - color: itemDelegate.colorStateType ? itemDelegate.colorState.value : "#00000000" - } - - contentItem: ColumnLayout { - spacing: 0 - RowLayout { - enabled: itemDelegate.connectedState === null || itemDelegate.connectedState.value === true - id: nameRow - z: 2 // make sure the switch in here is on top of the slider, given we cheated a bit and made them overlap - spacing: app.margins - Item { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - Layout.alignment: Qt.AlignVCenter - -// DropShadow { -// anchors.fill: icon -// horizontalOffset: 0 -// verticalOffset: 0 -// radius: 2.0 -// samples: 17 -// color: app.foregroundColor -// source: icon -// } - - ColorIcon { - id: icon - anchors.fill: parent - color: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false - ? "red" - : app.accentColor - name: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false ? - "../images/dialog-warning-symbolic.svg" - : itemDelegate.powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg" - } + function adjustBrightness(value) { + if (pendingCommand != -1) { + // busy, cache value + pendingValue = value; + return; } + pendingCommand = engine.thingManager.executeAction(itemDelegate.thing.id, + itemDelegate.brightnessState.stateTypeId, + [{ + paramTypeId: itemDelegate.brightnessState.stateTypeId, + value: value + }]) + pendingValue = null; + } - Label { - Layout.fillWidth: true - text: model.name - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - ThrottledSlider { - id: inlineSlider - visible: contentItem.enabled && itemDelegate.brightnessStateType && itemDelegate.inline - from: 0; to: 100 - value: itemDelegate.brightnessState ? itemDelegate.brightnessState.value : 0 - onMoved: { - var params = []; - var param1 = {}; - param1["paramTypeId"] = itemDelegate.brightnessActionType.paramTypes.get(0).id; - param1["value"] = value; - params.push(param1) - engine.deviceManager.executeAction(itemDelegate.device.id, itemDelegate.brightnessActionType.id, params) - } - } - Switch { - checked: itemDelegate.powerState.value === true - onClicked: { - var params = []; - var param1 = {}; - param1["paramTypeId"] = itemDelegate.powerActionType.paramTypes.get(0).id; - param1["value"] = checked; - params.push(param1) - engine.deviceManager.executeAction(device.id, itemDelegate.powerActionType.id, params) + Connections { + target: engine.thingManager + onExecuteActionReply: { + if (itemDelegate.pendingCommand == commandId) { + itemDelegate.pendingCommand = -1; + if (itemDelegate.pendingValue != null) { + itemDelegate.adjustBrightness(pendingValue) + } + } + } + } + + contentItem: Rectangle { + color: itemDelegate.powerState.value === true && itemDelegate.colorState ? itemDelegate.colorState.value : "#00000000" + implicitHeight: contentColumn.implicitHeight + Behavior on implicitHeight { NumberAnimation { duration: 100 } } + radius: 6 + + ColumnLayout { + id: contentColumn + anchors { left: parent.left; right: parent.right } + spacing: 0 + + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + spacing: app.margins + + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + name: itemDelegate.powerState.value === true ? "../images/light-on.svg" : "../images/light-off.svg" + color: itemDelegate.powerState.value === true ? app.accentColor : keyColor + } + + Label { + id: nameLabel + Layout.fillWidth: true + text: itemDelegate.thing.name + elide: Text.ElideRight + + Binding { + target: nameLabel + property: "color" + value: itemDelegate.colorState && NymeaUtils.isDark(app.foregroundColor) === NymeaUtils.isDark(itemDelegate.colorState.value) ? + app.backgroundColor : app.foregroundColor + when: nameLabel.enabled && itemDelegate.colorState !== null && itemDelegate.powerState.value === true + } + } + + ThingStatusIcons { + thing: itemDelegate.thing + } + + Switch { + checked: itemDelegate.powerState.value === true + onClicked: { + var params = []; + var param1 = {}; + param1["paramTypeId"] = itemDelegate.powerState.stateTypeId; + param1["value"] = checked; + params.push(param1) + print("executing for thing:", itemDelegate.thing.id) + engine.deviceManager.executeAction(itemDelegate.thing.id, itemDelegate.powerState.stateTypeId, params) + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 12 + visible: itemDelegate.powerState.value === true && itemDelegate.brightnessState != null + radius: 6 + color: Qt.tint(app.backgroundColor, Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .1)) + + Rectangle { + id: knob + height: 12 + width: 12 + radius: 8 + color: app.accentColor + anchors.verticalCenter: parent.verticalCenter + x: itemDelegate.brightnessState ? itemDelegate.brightnessState.value * (parent.width - width) / 100 : 0 + } + + MouseArea { + anchors.fill: parent + preventStealing: true + onMouseXChanged: { + itemDelegate.adjustBrightness(Math.max(1, Math.min(100, mouseX / width * 100))) + } + } } } } - } - onClicked: { - enterPage(index) } } } diff --git a/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml b/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml index 72d1f6f5..ac08337d 100644 --- a/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/MediaDeviceListPage.qml @@ -57,16 +57,20 @@ DeviceListPageBase { rowSpacing: 0 columnSpacing: 0 Repeater { - model: root.devicesProxy + model: root.thingsProxy - delegate: Item { + delegate: BigTile { id: itemDelegate Layout.preferredWidth: contentGrid.width / contentGrid.columns - height: contentItem.implicitHeight + app.margins + thing: thingsProxy.getThing(model.id) - property bool inline: width > 500 + topPadding: 0 + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + enabled: connectedState == null || connectedState.value === true - property Thing thing: thingsProxy.getThing(model.id) + property State connectedState: thing.stateByName("connected") readonly property StateType playbackStateType: thing.thingClass.stateTypes.findByName("playbackStatus") readonly property State playbackState: thing.stateByName("playbackStatus") @@ -74,162 +78,112 @@ DeviceListPageBase { readonly property StateType playerTypeStateType: thing.thingClass.stateTypes.findByName("playerType") readonly property State playerTypeState: thing.stateByName("playerType") - Pane { - id: contentItem - width: parent.width - app.margins - anchors.centerIn: parent - Material.elevation: 2 - leftPadding: 0 - rightPadding: 0 - topPadding: 0 - bottomPadding: 0 + onClicked: { + enterPage(index) + } - contentItem: ItemDelegate { - leftPadding: 0 - rightPadding: 0 - topPadding: 0 - bottomPadding: 0 - contentItem: ColumnLayout { - spacing: 0 - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: app.mediumFont + app.margins - color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05) - RowLayout { - anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right; margins: app.margins } - Label { - Layout.fillWidth: true - text: model.name - elide: Text.ElideRight - } - BatteryStatusIcon { - Layout.preferredHeight: app.iconSize * .5 - Layout.preferredWidth: height - thing: itemDelegate.thing - visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (hasBatteryLevel || isCritical) - } - ConnectionStatusIcon { - Layout.preferredHeight: app.iconSize * .5 - Layout.preferredWidth: height - thing: itemDelegate.thing - visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (hasSignalStrength || !isConnected) - } - SetupStatusIcon { - Layout.preferredHeight: app.iconSize * .5 - Layout.preferredWidth: height - thing: itemDelegate.thing - visible: setupFailed || setupInProgress - } - } + contentItem: RowLayout { + ColumnLayout { + id: leftColummn + Layout.margins: app.margins + Label { + Layout.fillWidth: true + text: itemDelegate.playbackState.value === "Stopped" ? + qsTr("No playback") + : itemDelegate.thing.stateByName("title").value + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + } + Label { + Layout.fillWidth: true + text: itemDelegate.thing.stateByName("artist").value + font.pixelSize: app.smallFont + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + } + Label { + Layout.fillWidth: true + text: itemDelegate.thing.stateByName("collection").value + horizontalAlignment: Text.AlignHCenter + font.pixelSize: app.smallFont + elide: Text.ElideRight + } + MediaControls { + visible: itemDelegate.thing.thingClass.interfaces.indexOf("mediacontroller") >= 0 + thing: itemDelegate.thing + } + } + Item { + Layout.preferredHeight: leftColummn.height + app.margins * 2 + Layout.preferredWidth: height * .7 + Item { + id: artworkContainer + anchors.fill: parent + Image { + id: artworkImage + width: artworkImage.sourceSize.width * height / artworkImage.sourceSize.height + anchors { top: parent.top; right: parent.right; bottom: parent.bottom } + readonly property State artworkState: thing.stateByName("artwork") + source: artworkState ? artworkState.value : "" + } + Rectangle { + visible: artworkImage.status !== Image.Ready || artworkImage.source === "" + anchors.fill: parent + color: "black" } - RowLayout { - ColumnLayout { - id: leftColummn - Layout.margins: app.margins - Label { - Layout.fillWidth: true - text: itemDelegate.playbackState.value === "Stopped" ? - qsTr("No playback") - : itemDelegate.thing.stateByName("title").value - horizontalAlignment: Text.AlignHCenter - // font.pixelSize: app.largeFont - elide: Text.ElideRight - } - Label { - Layout.fillWidth: true - text: itemDelegate.thing.stateByName("artist").value - font.pixelSize: app.smallFont - horizontalAlignment: Text.AlignHCenter - elide: Text.ElideRight - } - Label { - Layout.fillWidth: true - text: itemDelegate.thing.stateByName("collection").value - horizontalAlignment: Text.AlignHCenter - font.pixelSize: app.smallFont - elide: Text.ElideRight - } - MediaControls { - visible: itemDelegate.thing.thingClass.interfaces.indexOf("mediacontroller") >= 0 - thing: itemDelegate.thing - } - } - Item { - Layout.preferredHeight: leftColummn.height + app.margins * 2 - Layout.preferredWidth: height * .7 - Item { - id: artworkContainer - anchors.fill: parent - Image { - id: artworkImage - width: artworkImage.sourceSize.width * height / artworkImage.sourceSize.height - anchors { top: parent.top; right: parent.right; bottom: parent.bottom } - readonly property State artworkState: thing.stateByName("artwork") - source: artworkState ? artworkState.value : "" - } - Rectangle { - visible: artworkImage.status !== Image.Ready || artworkImage.source === "" - anchors.fill: parent - color: "black" - } - - ColorIcon { - width: Math.min(parent.width, parent.height) - app.margins * 2 - height: width - anchors.centerIn: parent - name: itemDelegate.playerTypeState && itemDelegate.playerTypeState.value === "video" ? "../images/stock_video.svg" : "../images/stock_music.svg" - visible: artworkImage.status !== Image.Ready || artworkImage.source === "" - color: "white" - } - } - - Rectangle { - id: maskRect - anchors.centerIn: parent - height: parent.width - width: parent.height - gradient: Gradient { - GradientStop { position: 0; color: "#00FF0000" } - GradientStop { position: 0.2; color: "#15FF0000" } - GradientStop { position: 1; color: "#FFFF0000" } - } - } - - ShaderEffect { - anchors.fill: parent - property variant source: ShaderEffectSource { - sourceItem: artworkContainer - hideSource: true - } - property variant mask: ShaderEffectSource { - sourceItem: maskRect - hideSource: true - } - - fragmentShader: " - varying highp vec2 qt_TexCoord0; - uniform sampler2D source; - uniform sampler2D mask; - void main(void) - { - highp vec4 sourceColor = texture2D(source, qt_TexCoord0); - highp float alpha = texture2D(mask, vec2(qt_TexCoord0.y, qt_TexCoord0.x)).a; - sourceColor *= alpha; - gl_FragColor = sourceColor; - } - " - } - } + ColorIcon { + width: Math.min(parent.width, parent.height) - app.margins * 2 + height: width + anchors.centerIn: parent + name: itemDelegate.playerTypeState && itemDelegate.playerTypeState.value === "video" ? "../images/stock_video.svg" : "../images/stock_music.svg" + visible: artworkImage.status !== Image.Ready || artworkImage.source === "" + color: "white" } } - onClicked: { - enterPage(index) + + Rectangle { + id: maskRect + anchors.centerIn: parent + height: parent.width + width: parent.height + radius: 6 + gradient: Gradient { + GradientStop { position: 0; color: "#00FF0000" } + GradientStop { position: 0.2; color: "#15FF0000" } + GradientStop { position: 1; color: "#FFFF0000" } + } + } + + ShaderEffect { + anchors.fill: parent + property variant source: ShaderEffectSource { + sourceItem: artworkContainer + hideSource: true + } + property variant mask: ShaderEffectSource { + sourceItem: maskRect + hideSource: true + } + + fragmentShader: " + varying highp vec2 qt_TexCoord0; + uniform sampler2D source; + uniform sampler2D mask; + void main(void) + { + highp vec4 sourceColor = texture2D(source, qt_TexCoord0); + highp float alpha = texture2D(mask, vec2(qt_TexCoord0.y, qt_TexCoord0.x)).a; + sourceColor *= alpha; + gl_FragColor = sourceColor; + } + " } } } } + } } } diff --git a/nymea-app/ui/devicelistpages/PowerSocketsDeviceListPage.qml b/nymea-app/ui/devicelistpages/PowerSocketsDeviceListPage.qml index 5503f786..d96671f3 100644 --- a/nymea-app/ui/devicelistpages/PowerSocketsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/PowerSocketsDeviceListPage.qml @@ -47,81 +47,68 @@ DeviceListPageBase { } } - ListView { + Flickable { anchors.fill: parent - model: devicesProxy - spacing: app.margins + contentHeight: contentGrid.implicitHeight + topMargin: app.margins / 2 - delegate: Pane { - id: itemDelegate - width: parent.width + GridLayout { + id: contentGrid + width: parent.width - app.margins + anchors.horizontalCenter: parent.horizontalCenter + columns: Math.ceil(width / 600) + rowSpacing: 0 + columnSpacing: 0 + Repeater { + model: root.thingsProxy - property Device device: devicesProxy.getDevice(model.id) - property DeviceClass deviceClass: device.deviceClass + delegate: BigTile { + id: itemDelegate + Layout.preferredWidth: contentGrid.width / contentGrid.columns + thing: root.thingsProxy.getThing(model.id) + showHeader: false + enabled: connectedState == null || connectedState.value === true + topPadding: 0 + bottomPadding: 0 - property var connectedStateType: deviceClass.stateTypes.findByName("connected"); - property var connectedState: connectedStateType ? device.states.getState(connectedStateType.id) : null + onClicked: root.enterPage(index) - property var powerStateType: deviceClass.stateTypes.findByName("power"); - property var powerActionType: deviceClass.actionTypes.findByName("power"); - property var powerState: device.states.getState(powerStateType.id) + property State connectedState: thing.stateByName("connected") + property State powerState: thing.stateByName("power") - Material.elevation: 1 - topPadding: 0 - bottomPadding: 0 - leftPadding: 0 - rightPadding: 0 - contentItem: ItemDelegate { - id: contentItem - implicitHeight: nameRow.implicitHeight - topPadding: 0 - - contentItem: ColumnLayout { - spacing: 0 - RowLayout { - enabled: itemDelegate.connectedState === null || itemDelegate.connectedState.value === true - id: nameRow - z: 2 // make sure the switch in here is on top of the slider, given we cheated a bit and made them overlap + contentItem: RowLayout { spacing: app.margins - Item { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: height - Layout.alignment: Qt.AlignVCenter - ColorIcon { - id: icon - anchors.fill: parent - color: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false - ? "red" - : itemDelegate.powerState.value === true ? app.accentColor : keyColor - name: itemDelegate.connectedState !== null && itemDelegate.connectedState.value === false ? - "../images/dialog-warning-symbolic.svg" - : app.interfaceToIcon("powersocket") - } + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + name: app.interfaceToIcon("powersocket") + color: itemDelegate.powerState.value === true ? app.accentColor : keyColor } Label { Layout.fillWidth: true - text: model.name + text: itemDelegate.thing.name elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter } + + ThingStatusIcons { + thing: itemDelegate.thing + } + Switch { checked: itemDelegate.powerState.value === true onClicked: { var params = []; var param1 = {}; - param1["paramTypeId"] = itemDelegate.powerActionType.paramTypes.get(0).id; + param1["paramTypeId"] = itemDelegate.powerState.stateTypeId; param1["value"] = checked; params.push(param1) - engine.deviceManager.executeAction(device.id, itemDelegate.powerActionType.id, params) + engine.deviceManager.executeAction(itemDelegate.thing.id, itemDelegate.powerState.stateTypeId, params) } } } } - onClicked: { - enterPage(index) - } } } } diff --git a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml index a9654bcb..ebe38b54 100644 --- a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml @@ -43,154 +43,119 @@ DeviceListPageBase { onBackPressed: pageStack.pop() } - ListView { + Flickable { anchors.fill: parent - model: root.thingsProxy + contentHeight: contentGrid.implicitHeight topMargin: app.margins / 2 - delegate: Item { - id: itemDelegate + GridLayout { + id: contentGrid width: parent.width - app.margins anchors.horizontalCenter: parent.horizontalCenter - height: contentItem.implicitHeight + app.margins + columns: Math.ceil(width / 600) + rowSpacing: 0 + columnSpacing: 0 - property bool inline: width > 500 + Repeater { + model: root.thingsProxy - property Thing thing: thingsProxy.getThing(model.id) + delegate: BigTile { + id: itemDelegate + Layout.preferredWidth: contentGrid.width / contentGrid.columns + thing: root.thingsProxy.getThing(model.id) - Pane { - id: contentItem - width: parent.width - app.margins - anchors.centerIn: parent - Material.elevation: 2 - leftPadding: 0 - rightPadding: 0 - topPadding: 0 - bottomPadding: 0 - - contentItem: ItemDelegate { - leftPadding: 0 - rightPadding: 0 - topPadding: 0 - bottomPadding: 0 - contentItem: ColumnLayout { - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: app.mediumFont + app.margins - color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05) - RowLayout { - anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right; margins: app.margins } - Label { - Layout.fillWidth: true - text: model.name - elide: Text.ElideRight - } - BatteryStatusIcon { - Layout.preferredHeight: app.iconSize * .5 - Layout.preferredWidth: height - thing: itemDelegate.thing - visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (hasBatteryLevel || isCritical) - } - ConnectionStatusIcon { - Layout.preferredHeight: app.iconSize * .5 - Layout.preferredWidth: height - thing: itemDelegate.thing - visible: itemDelegate.thing.setupStatus == Thing.ThingSetupStatusComplete && (isWireless || !isConnected) - } - SetupStatusIcon { - Layout.preferredHeight: app.iconSize * .5 - Layout.preferredWidth: height - thing: itemDelegate.thing - visible: setupFailed || setupInProgress - } - } - } - GridLayout { - id: dataGrid - columns: Math.floor(contentItem.width / 120) - Layout.margins: app.margins - Repeater { - model: ListModel { - ListElement { interfaceName: "temperaturesensor"; stateName: "temperature" } - ListElement { interfaceName: "humiditysensor"; stateName: "humidity" } - ListElement { interfaceName: "moisturesensor"; stateName: "moisture" } - ListElement { interfaceName: "pressuresensor"; stateName: "pressure" } - ListElement { interfaceName: "lightsensor"; stateName: "lightIntensity" } - ListElement { interfaceName: "conductivitysensor"; stateName: "conductivity" } - ListElement { interfaceName: "noisesensor"; stateName: "noise" } - ListElement { interfaceName: "co2sensor"; stateName: "co2" } - ListElement { interfaceName: "daylightsensor"; stateName: "daylight" } - ListElement { interfaceName: "presencesensor"; stateName: "isPresent" } - ListElement { interfaceName: "closablesensor"; stateName: "closed" } - ListElement { interfaceName: "heating"; stateName: "power" } - ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" } - } - - delegate: RowLayout { - id: sensorValueDelegate - visible: itemDelegate.thing.thingClass.interfaces.indexOf(model.interfaceName) >= 0 - Layout.preferredWidth: contentItem.width / dataGrid.columns - - property StateType stateType: itemDelegate.thing.thingClass.stateTypes.findByName(model.stateName) - property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null - - ColorIcon { - Layout.preferredHeight: app.iconSize * .8 - Layout.preferredWidth: height - Layout.alignment: Qt.AlignVCenter - color: { - switch (model.interfaceName) { - case "closablesensor": - return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? "green" : "red"; - default: - return app.interfaceToColor(model.interfaceName) - } - } - name: { - switch (model.interfaceName) { - case "closablesensor": - return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg"); - default: - return app.interfaceToIcon(model.interfaceName) - } - } - } - - Label { - Layout.fillWidth: true - property var unit: sensorValueDelegate.stateType ? sensorValueDelegate.stateType.unit : Types.UnitNone - text: { - switch (model.interfaceName) { - case "closablesensor": - return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("is closed") : qsTr("is open"); - default: - return sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" - ? sensorValueDelegate.stateType.displayName - : sensorValueDelegate.stateValue - ? "%1 %2".arg(Math.round(Types.toUiValue(sensorValueDelegate.stateValue.value, unit) * 100) / 100).arg(Types.toUiUnit(unit)) - : "" - } - } - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - font.pixelSize: app.smallFont - } - Led { - id: led - visible: ["presencesensor", "daylightsensor", "heating"].indexOf(model.interfaceName) >= 0 - state: visible && sensorValueDelegate.stateValue.value === true ? "on" : "off" - } - Item { - Layout.preferredWidth: led.width - visible: led.visible - } - } - } - } - } onClicked: { enterPage(index) } + + contentItem: GridLayout { + id: dataGrid + columns: Math.floor(contentItem.width / 120) + + Repeater { + model: ListModel { + ListElement { interfaceName: "temperaturesensor"; stateName: "temperature" } + ListElement { interfaceName: "humiditysensor"; stateName: "humidity" } + ListElement { interfaceName: "moisturesensor"; stateName: "moisture" } + ListElement { interfaceName: "pressuresensor"; stateName: "pressure" } + ListElement { interfaceName: "lightsensor"; stateName: "lightIntensity" } + ListElement { interfaceName: "conductivitysensor"; stateName: "conductivity" } + ListElement { interfaceName: "noisesensor"; stateName: "noise" } + ListElement { interfaceName: "co2sensor"; stateName: "co2" } + ListElement { interfaceName: "daylightsensor"; stateName: "daylight" } + ListElement { interfaceName: "presencesensor"; stateName: "isPresent" } + ListElement { interfaceName: "closablesensor"; stateName: "closed" } + ListElement { interfaceName: "heating"; stateName: "power" } + ListElement { interfaceName: "thermostat"; stateName: "targetTemperature" } + } + + delegate: RowLayout { + id: sensorValueDelegate + visible: itemDelegate.thing.thingClass.interfaces.indexOf(model.interfaceName) >= 0 + Layout.preferredWidth: contentItem.width / dataGrid.columns + + property StateType stateType: itemDelegate.thing.thingClass.stateTypes.findByName(model.stateName) + property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null + + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: height + Layout.alignment: Qt.AlignVCenter + color: { + switch (model.interfaceName) { + case "closablesensor": + return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? "green" : "red"; + default: + return app.interfaceToColor(model.interfaceName) + } + } + name: { + switch (model.interfaceName) { + case "closablesensor": + return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? Qt.resolvedUrl("../images/lock-closed.svg") : Qt.resolvedUrl("../images/lock-open.svg"); + default: + return app.interfaceToIcon(model.interfaceName) + } + } + } + + Label { + Layout.fillWidth: true + property var unit: sensorValueDelegate.stateType ? sensorValueDelegate.stateType.unit : Types.UnitNone + text: { + switch (model.interfaceName) { + case "closablesensor": + return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Closed") : qsTr("Open"); + case "preencesensor": + return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Presence") : qsTr("Vacant"); + case "daylightsensor": + return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("Daytime") : qsTr("Nighttime"); + case "heating": + return sensorValueDelegate.stateValue && sensorValueDelegate.stateValue.value === true ? qsTr("On") : qsTr("Off"); + default: + return sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" + ? sensorValueDelegate.stateType.displayName + : sensorValueDelegate.stateValue + ? "%1 %2".arg(Math.round(Types.toUiValue(sensorValueDelegate.stateValue.value, unit) * 100) / 100).arg(Types.toUiUnit(unit)) + : "" + } + } + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + font.pixelSize: app.smallFont + } + Led { + id: led + visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() === "bool" && ["presencesensor", "daylightsensor", "heating", "closablesensor"].indexOf(model.interfaceName) < 0 + state: visible && sensorValueDelegate.stateValue.value === true ? "on" : "off" + } + Item { + Layout.preferredWidth: led.width + visible: led.visible + } + } + } + } } } } diff --git a/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml index 4ba056e8..31ff38bb 100644 --- a/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/SmartMeterDeviceListPage.qml @@ -43,124 +43,82 @@ DeviceListPageBase { onBackPressed: pageStack.pop() } - ListView { + Flickable { anchors.fill: parent - model: root.devicesProxy + contentHeight: contentGrid.implicitHeight topMargin: app.margins / 2 - delegate: Item { - id: itemDelegate + GridLayout { + id: contentGrid width: parent.width - app.margins anchors.horizontalCenter: parent.horizontalCenter - height: contentItem.height + app.margins + columns: Math.ceil(width / 600) + rowSpacing: 0 + columnSpacing: 0 - property bool inline: width > 500 + Repeater { + model: root.thingsProxy - property Thing thing: thingsProxy.getThing(model.id) + delegate: BigTile { + id: itemDelegate + Layout.preferredWidth: contentGrid.width / contentGrid.columns + thing: root.thingsProxy.getThing(model.id) - Pane { - id: contentItem - width: parent.width - app.margins - anchors.centerIn: parent - Material.elevation: 2 - leftPadding: 0 - rightPadding: 0 - topPadding: 0 - bottomPadding: 0 - - contentItem: ItemDelegate { - leftPadding: 0 - rightPadding: 0 - topPadding: 0 - bottomPadding: 0 - contentItem: ColumnLayout { - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: app.mediumFont + app.margins - color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05) - RowLayout { - anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right; margins: app.margins } - Label { - Layout.fillWidth: true - text: model.name - elide: Text.ElideRight - } - BatteryStatusIcon { - Layout.preferredHeight: app.iconSize * .5 - Layout.preferredWidth: height - thing: itemDelegate.thing - visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (isCritical || hasBatteryLevel) - } - ConnectionStatusIcon { - Layout.preferredHeight: app.iconSize * .5 - Layout.preferredWidth: height - thing: itemDelegate.thing - visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (isWireless || !isConnected) - } - SetupStatusIcon { - Layout.preferredHeight: app.iconSize * .5 - Layout.preferredWidth: height - thing: itemDelegate.thing - visible: setupFailed || setupInProgress - } - } - - } - GridLayout { - id: dataGrid - columns: Math.floor(contentItem.width / 120) - Layout.margins: app.margins - Repeater { - model: ListModel { - Component.onCompleted: { - if (itemDelegate.thing.thingClass.interfaces.indexOf("smartmeterproducer") >= 0) { - append( {interfaceName: "smartmeterproducer", stateName: "totalEnergyProduced" }) - } - if (itemDelegate.thing.thingClass.interfaces.indexOf("smartmeterconsumer") >= 0) { - append( {interfaceName: "smartmeterconsumer", stateName: "totalEnergyConsumed" }) - } - var added = false; - if (itemDelegate.thing.thingClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0) { - append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"}); - added = true; - } - if (!added && itemDelegate.thing.thingClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) { - append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"}); - } - } - } - - delegate: RowLayout { - id: sensorValueDelegate - Layout.preferredWidth: contentItem.width / dataGrid.columns - - property StateType stateType: itemDelegate.thing.thingClass.stateTypes.findByName(model.stateName) - property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null - - ColorIcon { - Layout.preferredHeight: app.iconSize * .8 - Layout.preferredWidth: height - Layout.alignment: Qt.AlignVCenter - color: app.interfaceToColor(model.interfaceName) - name: app.interfaceToIcon(model.interfaceName) - } - - Label { - Layout.fillWidth: true - text: sensorValueDelegate.stateValue - ? "%1 %2".arg(1.0 * Math.round(Types.toUiValue(sensorValueDelegate.stateValue.value, sensorValueDelegate.stateType.unit) * 100000) / 100000).arg(Types.toUiUnit(sensorValueDelegate.stateType.unit)) - : "" - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - font.pixelSize: app.smallFont - } - } - } - } - } onClicked: { enterPage(index) } + contentItem: GridLayout { + id: dataGrid + columns: Math.floor(contentItem.width / 120) + + Repeater { + model: ListModel { + Component.onCompleted: { + if (itemDelegate.thing.thingClass.interfaces.indexOf("smartmeterproducer") >= 0) { + append( {interfaceName: "smartmeterproducer", stateName: "totalEnergyProduced" }) + } + if (itemDelegate.thing.thingClass.interfaces.indexOf("smartmeterconsumer") >= 0) { + append( {interfaceName: "smartmeterconsumer", stateName: "totalEnergyConsumed" }) + } + var added = false; + if (itemDelegate.thing.thingClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0) { + append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"}); + added = true; + } + if (!added && itemDelegate.thing.thingClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) { + append({interfaceName: "extendedsmartmeterconsumer", stateName: "currentPower"}); + } + } + } + + delegate: RowLayout { + id: sensorValueDelegate + visible: itemDelegate.thing.thingClass.interfaces.indexOf(model.interfaceName) >= 0 + Layout.preferredWidth: contentItem.width / dataGrid.columns + + property StateType stateType: itemDelegate.thing.thingClass.stateTypes.findByName(model.stateName) + property State stateValue: stateType ? itemDelegate.thing.states.getState(stateType.id) : null + + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: height + Layout.alignment: Qt.AlignVCenter + color: app.interfaceToColor(model.interfaceName) + name: app.interfaceToIcon(model.interfaceName) + } + + Label { + Layout.fillWidth: true + text: sensorValueDelegate.stateValue + ? "%1 %2".arg(1.0 * Math.round(Types.toUiValue(sensorValueDelegate.stateValue.value, sensorValueDelegate.stateType.unit) * 100000) / 100000).arg(Types.toUiUnit(sensorValueDelegate.stateType.unit)) + : "" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + font.pixelSize: app.smallFont + } + } + } + } } } } diff --git a/nymea-app/ui/devicelistpages/WeatherDeviceListPage.qml b/nymea-app/ui/devicelistpages/WeatherDeviceListPage.qml index 910a4d5f..5c2ee465 100644 --- a/nymea-app/ui/devicelistpages/WeatherDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/WeatherDeviceListPage.qml @@ -44,69 +44,31 @@ DeviceListPageBase { onBackPressed: pageStack.pop() } - ListView { + Flickable { anchors.fill: parent - model: root.devicesProxy + contentHeight: contentGrid.implicitHeight topMargin: app.margins / 2 - delegate: Item { - id: itemDelegate + GridLayout { + id: contentGrid width: parent.width - app.margins - height: contentItem.height + app.margins anchors.horizontalCenter: parent.horizontalCenter + columns: Math.ceil(width / 600) + rowSpacing: 0 + columnSpacing: 0 - property bool inline: width > 500 + Repeater { + model: root.thingsProxy - property Device device: devicesProxy.getDevice(model.id) - property DeviceClass deviceClass: device.deviceClass + delegate: BigTile { + id: itemDelegate + Layout.preferredWidth: contentGrid.width / contentGrid.columns + thing: root.thingsProxy.get(model.id) - Pane { - id: contentItem - width: parent.width - app.margins - anchors.centerIn: parent - Material.elevation: 2 - leftPadding: 0 - rightPadding: 0 - topPadding: 0 - bottomPadding: 0 + onClicked: enterPage(index) - contentItem: ItemDelegate { - leftPadding: 0 - rightPadding: 0 - topPadding: 0 - bottomPadding: 0 - contentItem: ColumnLayout { - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: app.mediumFont + app.margins - color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05) - RowLayout { - anchors { verticalCenter: parent.verticalCenter; left: parent.left; right: parent.right; margins: app.margins } - Label { - Layout.fillWidth: true - text: model.name - elide: Text.ElideRight - } - ColorIcon { - Layout.preferredHeight: app.iconSize * .5 - Layout.preferredWidth: height - name: "../images/dialog-warning-symbolic.svg" - visible: itemDelegate.deviceClass.interfaces.indexOf("connectable") >= 0 && itemDelegate.device.states.getState(itemDelegate.deviceClass.stateTypes.findByName("connected").id).value === false - color: "red" - } - } - } - - WeatherView { - Layout.fillWidth: true - device: itemDelegate.device - deviceClass: itemDelegate.deviceClass - } - } - onClicked: { - var device = devicesProxy.get(index); - var deviceClass = engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) - pageStack.push(Qt.resolvedUrl("../devicepages/WeatherDevicePage.qml"), {device: devicesProxy.get(index)}) + contentItem: WeatherView { + thing: itemDelegate.thing } } } diff --git a/nymea-app/ui/devicepages/DevicePageBase.qml b/nymea-app/ui/devicepages/DevicePageBase.qml index 10dfcb3a..0c732ac2 100644 --- a/nymea-app/ui/devicepages/DevicePageBase.qml +++ b/nymea-app/ui/devicepages/DevicePageBase.qml @@ -242,86 +242,10 @@ Page { } } - Rectangle { + ThingInfoPane { id: infoPane - visible: setupInProgress || setupFailure || batteryState !== null || (connectedState !== null && connectedState.value === false) || isWireless || updateAvailable - height: visible ? contentRow.implicitHeight : 0 anchors { left: parent.left; top: parent.top; right: parent.right } - property bool setupInProgress: root.thing.setupStatus == Thing.ThingSetupStatusInProgress - property bool setupFailure: root.thing.setupStatus == Thing.ThingSetupStatusFailed - property State batteryState: root.thing.stateByName("batteryLevel") - property State batteryCriticalState: root.thing.stateByName("batteryCritical") - property State connectedState: root.thing.stateByName("connected") - property State signalStrengthState: root.thing.stateByName("signalStrength") - property State updateStatusState: root.thing.stateByName("updateStatus") - property bool updateAvailable: updateStatusState && updateStatusState.value === "available" - property bool updateRunning: updateStatusState && updateStatusState.value === "updating" - property bool isWireless: root.thingClass.interfaces.indexOf("wirelessconnectable") >= 0 - property bool alertState: setupFailure || - (connectedState !== null && connectedState.value === false) || - (batteryCriticalState !== null && batteryCriticalState.value === true) - property bool highlightState: updateAvailable || updateRunning - color: alertState ? "red" - : infoPane.highlightState ? app.accentColor : "transparent" - z: 1000 - - RowLayout { - id: contentRow - anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: app.margins; rightMargin: app.margins } - Item { - Layout.fillWidth: true - height: app.iconSize - } - - Label { - text: infoPane.setupInProgress ? - qsTr("Thing is being set up...") - : infoPane.setupFailure ? - (root.device.setupDisplayMessage.length > 0 ? root.device.setupDisplayMessage : qsTr("Thing setup failed!")) - : (infoPane.connectedState !== null && infoPane.connectedState.value === false) ? - qsTr("Thing is not connected!") - : infoPane.updateAvailable ? - qsTr("Update available!") - : infoPane.updateRunning ? - qsTr("Updating...") - : qsTr("Thing runs out of battery!") - visible: infoPane.alertState || infoPane.updateAvailable || infoPane.updateRunning - font.pixelSize: app.smallFont - color: "white" - } - - UpdateStatusIcon { - height: app.iconSize / 2 - width: height - thing: root.thing - color: infoPane.alertState || infoPane.highlightState ? "white" : keyColor - visible: updateAvailable || updateRunning - } - - BatteryStatusIcon { - height: app.iconSize / 2 - width: height * 1.23 - thing: root.thing - color: infoPane.alertState || infoPane.highlightState ? "white" : keyColor - visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (hasBatteryLevel || isCritical) - } - - ConnectionStatusIcon { - height: app.iconSize / 2 - width: height - thing: root.thing - color: infoPane.alertState || infoPane.highlightState ? "white" : keyColor - visible: thing.setupStatus == Thing.ThingSetupStatusComplete && (hasSignalStrength || !isConnected) - } - - SetupStatusIcon { - height: app.iconSize / 2 - width: height - thing: root.thing - color: infoPane.alertState || infoPane.highlightState ? "white" : keyColor - visible: setupFailed || setupInProgress - } - } + thing: root.thing } Item { diff --git a/nymea-app/ui/devicepages/SensorDevicePage.qml b/nymea-app/ui/devicepages/SensorDevicePage.qml index df75c0c4..6f36c459 100644 --- a/nymea-app/ui/devicepages/SensorDevicePage.qml +++ b/nymea-app/ui/devicepages/SensorDevicePage.qml @@ -47,7 +47,7 @@ DevicePageBase { GridLayout { id: contentGrid width: parent.width - columns: width / 300 + columns: Math.ceil(width / 600) Repeater { model: ListModel { diff --git a/nymea-app/ui/devicepages/WeatherDevicePage.qml b/nymea-app/ui/devicepages/WeatherDevicePage.qml index 5120933f..a4232c92 100644 --- a/nymea-app/ui/devicepages/WeatherDevicePage.qml +++ b/nymea-app/ui/devicepages/WeatherDevicePage.qml @@ -49,8 +49,7 @@ DevicePageBase { WeatherView { Layout.fillWidth: true - device: root.device - deviceClass: root.deviceClass + thing: root.thing } GridLayout { diff --git a/nymea-app/ui/mainviews/GroupsView.qml b/nymea-app/ui/mainviews/GroupsView.qml index 52ee0172..89c9467e 100644 --- a/nymea-app/ui/mainviews/GroupsView.qml +++ b/nymea-app/ui/mainviews/GroupsView.qml @@ -65,203 +65,6 @@ MainViewBase { onClicked: { pageStack.push(Qt.resolvedUrl("../grouping/GroupInterfacesPage.qml"), {groupTag: model.tagId}) } - -// delegate: Item { -// id: groupDelegate -// width: groupsGridView.cellWidth -// height: groupsGridView.cellHeight - -// Pane { -// anchors.fill: parent -// anchors.margins: app.margins / 2 -// Material.elevation: 2 -// padding: 0 - -// DevicesProxy { -// id: devicesInGroup -// engine: _engine -// filterTagId: model.tagId -// filterTagValue: model.value -// } - -// contentItem: ColumnLayout { - -// ColorIcon { -// name: "../images/view-grid-symbolic.svg" -// } - -// Label { -// text: model -// } -// } - - -// InterfacesProxy { -// id: controlsInGroup -// shownInterfaces: ["light", "simpleclosable", "mediacontroller", "powersocket"] -// devicesProxyFilter: devicesInGroup -// showStates: true -// showActions: true -// } -// InterfacesProxy { -// id: sensorsInGroup -// shownInterfaces: ["temperaturesensor", "lightsensor", "presencesensor"] -// devicesProxyFilter: devicesInGroup -// showStates: true -// } - - -// contentItem: ItemDelegate { -// leftPadding: 0 -// topPadding: 0 -// rightPadding: 0 -// bottomPadding: 0 - -// onClicked: { -// pageStack.push(Qt.resolvedUrl("../grouping/GroupInterfacesPage.qml"), {groupTag: model.tagId}) -// } - -// contentItem: ColumnLayout { -// Rectangle { -// Layout.fillWidth: true -// Layout.preferredHeight: 30 -// color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, .05) -// Label { -// anchors.fill: parent -// verticalAlignment: Text.AlignVCenter -// anchors { leftMargin: app.margins; rightMargin: app.margins } -// text: model.tagId.substring(6) -// elide: Text.ElideRight -// } -// } -// Item { -// Layout.fillHeight: true -// Layout.fillWidth: true - -// ColorIcon { -// anchors.centerIn: parent -// height: app.iconSize * 2 -// width: height -// visible: controlsInGroup.count == 0 -// color: app.accentColor -// name: "../images/view-grid-symbolic.svg" -// } - -// ColumnLayout { -// anchors.fill: parent - -// Repeater { -//// model: Math.min(controlsInGroup.count, parent.height / 50) -// model: controlsInGroup.count -// delegate: Loader { -// id: controlLoader -// Layout.fillWidth: true -// Layout.leftMargin: app.margins / 2 -// Layout.rightMargin: app.margins / 2 -// property string interfaceName: controlsInGroup.get(index).name -// sourceComponent: { -// switch (interfaceName) { -// case "simpleclosable": -// return closableDelegate -// case "light": -// return lightDelegate -// case "mediacontroller": -// return mediaControllerDelegate -// case "powersocket": -// return powerSocketDelegate -// } -// } -// Binding { -// target: controlLoader.item -// property: "devices" -// value: devicesInGroup -// } -// } -// } -// Item { -// Layout.fillHeight: true -// Layout.fillWidth: true -// } -// } - -// } -// Rectangle { -// Layout.fillWidth: true -// Layout.preferredHeight: app.iconSize * 1.2 -// color: Qt.rgba(app.foregroundColor.r, app.foregroundColor.g, app.foregroundColor.b, 0.05) - -// RowLayout { -// anchors.fill: parent - -// Repeater { -// model: sensorsInGroup -// delegate: Row { -// height: parent.height - -// ColorIcon { -// height: app.iconSize * .8 -// width: height -// name: app.interfaceToIcon(model.name) -// color: app.interfaceToColor(model.name) -// } -// DevicesProxy { -// id: innerProxy -// engine: _engine -// parentProxy: devicesInGroup -// shownInterfaces: [model.name] -// } - -// Led { -// visible: ["presencesensor"].indexOf(model.name) >= 0 -// state: { -// var stateName = null -// switch (model.name) { -// case "presencesensor": -// stateName = "isPresent" -// break; -// } -// if (!stateName) { -// return "off"; -// } -// var ret = false; -// for (var i = 0; i < innerProxy.count; i++) { -// ret |= innerProxy.get(i).states.getState(innerProxy.get(i).deviceClass.stateTypes.findByName(stateName).id).value -// } -// return ret ? "on" : "off"; -// } -// } - -// Label { -// height: parent.height -// verticalAlignment: Text.AlignVCenter -// text: { -// var stateName = null; -// switch (model.name) { -// case "temperaturesensor": -// stateName = "temperature"; -// break; -// case "lightsensor": -// stateName = "lightIntensity" -// break; -// } -// if (!stateName) { -// return ""; -// } - -// var ret = 0 -// for (var i = 0; i < innerProxy.count; i++) { -// ret += innerProxy.get(i).states.getState(innerProxy.get(i).deviceClass.stateTypes.findByName(stateName).id).value -// } -// return (ret / innerProxy.count).toFixed(1) -// } -// } -// } -// } -// } -// } -// } -// } -// } } } diff --git a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml index 323bbc84..0966dc98 100644 --- a/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml +++ b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml @@ -37,7 +37,8 @@ import "../delegates" SettingsPageBase { id: root - property Device device: null + property Thing thing: null + property alias device: root.thing readonly property DeviceClass deviceClass: device ? device.deviceClass : null header: NymeaHeader { @@ -50,6 +51,12 @@ SettingsPageBase { } } + ThingInfoPane { + id: infoPane + anchors { left: parent.left; top: parent.top; right: parent.right } + thing: root.thing + } + Menu { id: deviceMenu width: implicitWidth + app.margins diff --git a/nymea-app/ui/utils/NymeaUtils.qml b/nymea-app/ui/utils/NymeaUtils.qml index d3a774f6..0d17436e 100644 --- a/nymea-app/ui/utils/NymeaUtils.qml +++ b/nymea-app/ui/utils/NymeaUtils.qml @@ -65,6 +65,18 @@ Item { } function isDark(color) { - return ((color.r() * 299 + color.g() * 587 + color.b() * 114) / 1000) > 123 + var r, g, b; + if (color.constructor.name === "Object") { + r = color.r * 255; + g = color.g * 255; + b = color.b * 255; + } else if (color.constructor.name === "String") { + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color); + r = parseInt(result[1], 16) + g = parseInt(result[2], 16) + b = parseInt(result[3], 16) + } + + return ((r * 299 + g * 587 + b * 114) / 1000) < 128 } }