diff --git a/libnymea-app-core/connection/nymeaconnection.cpp b/libnymea-app-core/connection/nymeaconnection.cpp index 8a89783f..8142ec09 100644 --- a/libnymea-app-core/connection/nymeaconnection.cpp +++ b/libnymea-app-core/connection/nymeaconnection.cpp @@ -137,7 +137,7 @@ Connection *NymeaConnection::currentConnection() const void NymeaConnection::sendData(const QByteArray &data) { if (connected()) { - qDebug() << "sending data:" << data; +// qDebug() << "sending data:" << data; m_currentTransport->sendData(data); } else { qWarning() << "Connection: Not connected. Cannot send."; @@ -382,7 +382,7 @@ void NymeaConnection::updateActiveBearers() QList configs = m_networkConfigManager->allConfigurations(QNetworkConfiguration::Active); // qDebug() << "Network configuations:" << configs.count(); foreach (const QNetworkConfiguration &config, configs) { - qDebug() << "Candidate network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName(); +// qDebug() << "Candidate network config:" << config.name() << config.bearerTypeFamily() << config.bearerTypeName(); // NOTE: iOS doesn't correctly report bearer types. It'll be Unknown all the time. Let's hardcode it to WiFi for that... #if defined(Q_OS_IOS) diff --git a/libnymea-app-core/devicesproxy.cpp b/libnymea-app-core/devicesproxy.cpp index 0e9c7368..b3a08153 100644 --- a/libnymea-app-core/devicesproxy.cpp +++ b/libnymea-app-core/devicesproxy.cpp @@ -101,6 +101,20 @@ void DevicesProxy::setFilterTagId(const QString &filterTag) } } +QString DevicesProxy::filterDeviceClassId() const +{ + return m_filterDeviceClassId; +} + +void DevicesProxy::setFilterDeviceClassId(const QString &filterDeviceClassId) +{ + if (m_filterDeviceClassId != filterDeviceClassId) { + m_filterDeviceClassId = filterDeviceClassId; + emit filterDeviceClassIdChanged(); + invalidateFilter(); + } +} + QStringList DevicesProxy::shownInterfaces() const { return m_shownInterfaces; @@ -231,6 +245,11 @@ bool DevicesProxy::filterAcceptsRow(int source_row, const QModelIndex &source_pa return false; } } + if (!m_filterDeviceClassId.isEmpty()) { + if (device->deviceClassId() != m_filterDeviceClassId) { + return false; + } + } DeviceClass *deviceClass = m_engine->deviceManager()->deviceClasses()->getDeviceClass(device->deviceClassId()); if (!m_shownInterfaces.isEmpty()) { bool foundMatch = false; diff --git a/libnymea-app-core/devicesproxy.h b/libnymea-app-core/devicesproxy.h index 63dfca95..4ab74807 100644 --- a/libnymea-app-core/devicesproxy.h +++ b/libnymea-app-core/devicesproxy.h @@ -38,6 +38,7 @@ class DevicesProxy : public QSortFilterProxyModel Q_PROPERTY(Engine* engine READ engine WRITE setEngine NOTIFY engineChanged) Q_PROPERTY(DevicesProxy *parentProxy READ parentProxy WRITE setParentProxy NOTIFY parentProxyChanged) Q_PROPERTY(QString filterTagId READ filterTagId WRITE setFilterTagId NOTIFY filterTagIdChanged) + Q_PROPERTY(QString filterDeviceClassId READ filterDeviceClassId WRITE setFilterDeviceClassId NOTIFY filterDeviceClassIdChanged) Q_PROPERTY(QStringList shownInterfaces READ shownInterfaces WRITE setShownInterfaces NOTIFY shownInterfacesChanged) Q_PROPERTY(QStringList hiddenInterfaces READ hiddenInterfaces WRITE setHiddenInterfaces NOTIFY hiddenInterfacesChanged) Q_PROPERTY(QString nameFilter READ nameFilter WRITE setNameFilter NOTIFY nameFilterChanged) @@ -62,6 +63,9 @@ public: QString filterTagId() const; void setFilterTagId(const QString &filterTag); + QString filterDeviceClassId() const; + void setFilterDeviceClassId(const QString &filterDeviceClassId); + QStringList shownInterfaces() const; void setShownInterfaces(const QStringList &shownInterfaces); @@ -86,6 +90,7 @@ signals: void engineChanged(); void parentProxyChanged(); void filterTagIdChanged(); + void filterDeviceClassIdChanged(); void shownInterfacesChanged(); void hiddenInterfacesChanged(); void nameFilterChanged(); @@ -100,6 +105,7 @@ private: Engine *m_engine = nullptr; DevicesProxy *m_parentProxy = nullptr; QString m_filterTagId; + QString m_filterDeviceClassId; QStringList m_shownInterfaces; QStringList m_hiddenInterfaces; QString m_nameFilter; diff --git a/libnymea-common/types/states.h b/libnymea-common/types/states.h index 51b7794b..c54ee50b 100644 --- a/libnymea-common/types/states.h +++ b/libnymea-common/types/states.h @@ -37,7 +37,7 @@ public: StateTypeIdRole }; - explicit States(QObject *parent = 0); + explicit States(QObject *parent = nullptr); QList states(); diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index 68f4fa09..effa074f 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -170,5 +170,6 @@ ui/images/sensors/presence.svg ui/images/powersocket.svg ui/images/dial.svg + ui/images/ventilation.svg diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index 54ff0894..d51b0f8f 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -77,7 +77,7 @@ int main(int argc, char *argv[]) } QFont applicationFont; - applicationFont.setFamily("Ubuntu"); +// applicationFont.setFamily("Oswald"); applicationFont.setCapitalization(QFont::MixedCase); applicationFont.setPixelSize(16); applicationFont.setWeight(QFont::Normal); diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index a994706d..a4bcf29c 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -164,5 +164,13 @@ ui/thingconfiguration/EditThingsPage.qml ui/thingconfiguration/ConfigureThingPage.qml ui/connection/CertificateDialog.qml + ui/experiences/garagegates/Main.qml + ui/experiences/heating/Main.qml + ui/fonts/Oswald-Bold.ttf + ui/fonts/Oswald-ExtraLight.ttf + ui/fonts/Oswald-Light.ttf + ui/fonts/Oswald-Medium.ttf + ui/fonts/Oswald-Regular.ttf + ui/fonts/Oswald-SemiBold.ttf diff --git a/nymea-app/stylecontroller.cpp b/nymea-app/stylecontroller.cpp index 7c64363d..eac65c3d 100644 --- a/nymea-app/stylecontroller.cpp +++ b/nymea-app/stylecontroller.cpp @@ -43,3 +43,25 @@ QStringList StyleController::allStyles() const // qDebug() << "styles:" << dir.entryList(); return dir.entryList(QDir::Dirs); } + +QString StyleController::currentExperience() const +{ + QSettings settings; + return settings.value("experience", "Default").toString(); +} + +void StyleController::setCurrentExperience(const QString ¤tExperience) +{ + QSettings settings; + if (settings.value("experience").toString() != currentExperience) { + settings.setValue("experience", currentExperience); + emit currentExperienceChanged(); + } +} + +QStringList StyleController::allExperiences() const +{ + QDir dir(":/ui/experiences"); + qDebug() << "experiences:" << dir.entryList(); + return QStringList() << "Default" << dir.entryList(); +} diff --git a/nymea-app/stylecontroller.h b/nymea-app/stylecontroller.h index 4c34ae36..524b59b0 100644 --- a/nymea-app/stylecontroller.h +++ b/nymea-app/stylecontroller.h @@ -9,6 +9,9 @@ class StyleController : public QObject Q_PROPERTY(QString currentStyle READ currentStyle WRITE setCurrentStyle NOTIFY currentStyleChanged) Q_PROPERTY(QStringList allStyles READ allStyles CONSTANT) + Q_PROPERTY(QString currentExperience READ currentExperience WRITE setCurrentExperience NOTIFY currentExperienceChanged) + Q_PROPERTY(QStringList allExperiences READ allExperiences CONSTANT) + public: explicit StyleController(QObject *parent = nullptr); @@ -17,8 +20,14 @@ public: QStringList allStyles() const; + QString currentExperience() const; + void setCurrentExperience(const QString ¤tExperience); + + QStringList allExperiences() const; + signals: void currentStyleChanged(); + void currentExperienceChanged(); }; diff --git a/nymea-app/styles.qrc b/nymea-app/styles.qrc index 347c12f5..f6b0e151 100644 --- a/nymea-app/styles.qrc +++ b/nymea-app/styles.qrc @@ -3,10 +3,14 @@ styles/light/ApplicationWindow.qml styles/light/Page.qml styles/light/logo.svg + styles/light/Button.qml styles/dark/ApplicationWindow.qml styles/dark/Page.qml styles/dark/logo.svg styles/dark/Button.qml - styles/light/Button.qml + styles/noir/ApplicationWindow.qml + styles/noir/Page.qml + styles/noir/logo.svg + styles/noir/Button.qml diff --git a/nymea-app/styles/noir/ApplicationWindow.qml b/nymea-app/styles/noir/ApplicationWindow.qml new file mode 100644 index 00000000..e67e8f8d --- /dev/null +++ b/nymea-app/styles/noir/ApplicationWindow.qml @@ -0,0 +1,36 @@ +import QtQuick 2.0 +import QtQuick.Templates 2.2 +import QtQuick.Controls.Material 2.2 + +ApplicationWindow { + // The app style + Material.theme: Material.Dark + + // Main background color + Material.background: "#50514f" + + font.family: "Oswald" + + // The header background color + property color primaryColor: Qt.darker("#50514f", 1.1) + + + // Header font color + property color headerForegroundColor: "#ebebeb" + + // The font color + property color foregroundColor: "#ebebeb" + + // The color of selected/highlighted things + property color accentColor: "#f45b69" + + // colors for interfaces, e.g. icons + property var interfaceColors: { + "temperaturesensor": "#FF0000", + "humiditysensor": "#00BFFF", + "moisturesensor":"#0000FF", + "lightsensor": "#FFA500", + "conductivitysensor": "#008000", + "pressuresensor": "#808080" + } +} diff --git a/nymea-app/styles/noir/Button.qml b/nymea-app/styles/noir/Button.qml new file mode 100644 index 00000000..35779675 --- /dev/null +++ b/nymea-app/styles/noir/Button.qml @@ -0,0 +1,77 @@ +import QtQuick 2.9 +import QtQuick.Templates 2.2 as T +import QtQuick.Controls 2.2 +import QtQuick.Controls.impl 2.2 +import QtQuick.Controls.Material 2.2 +import QtQuick.Controls.Material.impl 2.2 + +T.Button { + id: control + + implicitWidth: Math.max(background ? background.implicitWidth : 0, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max(background ? background.implicitHeight : 0, + contentItem.implicitHeight + topPadding + bottomPadding) + baselineOffset: contentItem.y + contentItem.baselineOffset + + // external vertical padding is 6 (to increase touch area) + padding: 12 + leftPadding: padding - 4 + rightPadding: padding - 4 + + Material.elevation: flat ? control.down || control.hovered ? 2 : 0 + : control.down ? 8 : 2 + Material.background: flat ? "transparent" : undefined + + contentItem: Text { + text: control.text + font: control.font + color: app.primaryColor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } + + // TODO: Add a proper ripple/ink effect for mouse/touch input and focus state + background: Rectangle { + implicitWidth: 64 + implicitHeight: 40 + + // external vertical padding is 6 (to increase touch area) + y: 6 + width: parent.width + height: parent.height - 12 + radius: height / 2 + color: !control.enabled ? control.Material.buttonDisabledColor : + control.highlighted ? control.Material.highlightedButtonColor : control.Material.accentColor + + PaddedRectangle { + y: parent.height - 4 + width: parent.width + height: 4 + radius: 2 + topPadding: -2 + clip: true + visible: control.checkable && (!control.highlighted || control.flat) + color: control.checked && control.enabled ? control.Material.accentColor : control.Material.secondaryTextColor + } + + // The layer is disabled when the button color is transparent so you can do + // Material.background: "transparent" and get a proper flat button without needing + // to set Material.elevation as well + layer.enabled: control.enabled && control.Material.buttonColor.a > 0 + layer.effect: ElevationEffect { + elevation: control.Material.elevation + } + + Ripple { + clipRadius: 2 + width: parent.width + height: parent.height + pressed: control.pressed + anchor: control + active: control.down || control.visualFocus || control.hovered + color: control.Material.rippleColor + } + } +} diff --git a/nymea-app/styles/noir/Page.qml b/nymea-app/styles/noir/Page.qml new file mode 100644 index 00000000..43c4f49e --- /dev/null +++ b/nymea-app/styles/noir/Page.qml @@ -0,0 +1,10 @@ +import QtQuick 2.0 +import QtQuick.Templates 2.2 +import QtQuick.Controls.Material 2.2 + +Page { + + background: Rectangle { + color: Material.background + } +} diff --git a/nymea-app/styles/noir/logo.svg b/nymea-app/styles/noir/logo.svg new file mode 100644 index 00000000..9d1907c3 --- /dev/null +++ b/nymea-app/styles/noir/logo.svg @@ -0,0 +1,603 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml index 439d7fbd..2a626d12 100644 --- a/nymea-app/ui/MainPage.qml +++ b/nymea-app/ui/MainPage.qml @@ -31,6 +31,34 @@ Page { property bool swipeViewReady: false property bool tabsReady: false + // FIXME: All this can go away when we require Controls 2.3 (Qt 5.10) or greater as TabBar got a major rework there. + // Ideally we'd just list the 3 items and set visible to false if the server version isn't good enough but TabBar + // has troubles dealing with that. For now, let's manually fill it and use a timer to initialize the currentIndex. + Component.onCompleted: { + // Fill SwipeView (The 2 static views things and scenes will already be there). + if (engine.jsonRpcClient.ensureServerVersion(1.6)) { + swipeView.insertItem(0, favoritesViewComponent.createObject(swipeView)) + } + var experienceView = null; + if (styleController.currentExperience != "Default") { + experienceView = experienceViewComponent.createObject(swipeView, {source: "experiences/" + styleController.currentExperience + "/Main.qml" }); + swipeView.insertItem(0, experienceView) + } + root.swipeViewReady = true; + + + var pi = 0; + if (experienceView) { + tabEntryComponent.createObject(tabBar, {text: experienceView.title, iconSource: experienceView.icon, pageIndex: pi++}) + } + if (engine.jsonRpcClient.ensureServerVersion(1.6)) { + tabEntryComponent.createObject(tabBar, {text: qsTr("Favorites"), iconSource: "../images/starred.svg", pageIndex: pi++}) + } + tabEntryComponent.createObject(tabBar, {text: qsTr("Things"), iconSource: "../images/share.svg", pageIndex: pi++}) + tabEntryComponent.createObject(tabBar, {text: qsTr("Scenes"), iconSource: "../images/slideshow.svg", pageIndex: pi++}) + root.tabsReady = true + } + readonly property bool viewReady: swipeViewReady && tabsReady onViewReadyChanged: { if (tabSettings.currentMainViewIndex > swipeView.count) { @@ -90,16 +118,21 @@ Page { anchors.rightMargin: anchors.leftMargin currentIndex: root.currentViewIndex - Component.onCompleted: { - if (engine.jsonRpcClient.ensureServerVersion(1.6)) { - swipeView.insertItem(0, favoritesViewComponent.createObject(swipeView)) - } - root.swipeViewReady = true; - } onCurrentIndexChanged: { root.currentViewIndex = currentIndex } + Component { + id: experienceViewComponent + Loader { + width: swipeView.width + height: swipeView.height + clip: true + readonly property string title: item ? item.title : "" + readonly property string icon: item ? item.icon : "" + } + } + Component { id: favoritesViewComponent FavoritesView { @@ -206,20 +239,6 @@ Page { ((systemProductType === "ios" && Screen.height === 375) ? -10 : -20) : (systemProductType === "ios" && Screen.height === 812) ? 14 : 0) - - // FIXME: All this can go away when we require Controls 2.3 (Qt 5.10) or greater as TabBar got a major rework there. - // Ideally we'd just list the 3 items and set visible to false if the server version isn't good enough but TabBar - // has troubles dealing with that. For now, let's manually fill it and use a timer to initialize the currentIndex. - Component.onCompleted: { - var pi = 0; - if (engine.jsonRpcClient.ensureServerVersion(1.6)) { - tabEntryComponent.createObject(tabBar, {text: qsTr("Favorites"), iconSource: "../images/starred.svg", pageIndex: pi++}) - } - tabEntryComponent.createObject(tabBar, {text: qsTr("Things"), iconSource: "../images/share.svg", pageIndex: pi++}) - tabEntryComponent.createObject(tabBar, {text: qsTr("Scenes"), iconSource: "../images/slideshow.svg", pageIndex: pi++}) - root.tabsReady = true - } - Component { id: tabEntryComponent MainPageTabButton { diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index b1eacadd..e0250a5f 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -14,7 +14,7 @@ ApplicationWindow { minimumWidth: 360 minimumHeight: 480 visibility: ApplicationWindow.AutomaticVisibility - font: Qt.application.font +// font: Qt.application.font // Those variables must be present in the Style @@ -50,7 +50,6 @@ ApplicationWindow { RootItem { id: rootItem anchors.fill: parent - anchors.topMargin: PlatformHelper.getSafeAreaMargins(app) } NymeaDiscovery { diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index 0e023fdd..403b97fb 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -98,10 +98,9 @@ Item { } Binding { - target: _discovey + target: _discovery property: "discovering" - when: engine.connection.currentHost === null - value: true + value: engine.connection.currentHost === null } Component.onCompleted: { diff --git a/nymea-app/ui/appsettings/DeveloperOptionsPage.qml b/nymea-app/ui/appsettings/DeveloperOptionsPage.qml index 0c5eb735..a4d5bb2b 100644 --- a/nymea-app/ui/appsettings/DeveloperOptionsPage.qml +++ b/nymea-app/ui/appsettings/DeveloperOptionsPage.qml @@ -46,6 +46,23 @@ Page { onClicked: pageStack.push(appLogComponent) enabled: AppLogController.enabled } + + RowLayout { + Layout.leftMargin: app.margins; Layout.rightMargin: app.margins + + Label { + Layout.fillWidth: true + text: qsTr("Experience mode") + } + + ComboBox { + currentIndex: model.indexOf(styleController.currentExperience) + model: styleController.allExperiences + onActivated: { + styleController.currentExperience = model[index] + } + } + } } Component { diff --git a/nymea-app/ui/components/Dial.qml b/nymea-app/ui/components/Dial.qml index ff3f55f6..56395aac 100644 --- a/nymea-app/ui/components/Dial.qml +++ b/nymea-app/ui/components/Dial.qml @@ -24,8 +24,8 @@ ColumnLayout { } readonly property State deviceState: device && stateType ? device.states.getState(stateType.id) : null - readonly property double from: dial.stateType.minValue - readonly property double to: dial.stateType.maxValue + readonly property double from: dial.stateType ? dial.stateType.minValue : 0 + readonly property double to: dial.stateType ? dial.stateType.maxValue : 100 readonly property double anglePerStep: maxAngle / dial.steps readonly property double startAngle: -(dial.steps * dial.anglePerStep) / 2 @@ -90,7 +90,7 @@ ColumnLayout { Label { id: topLabel Layout.fillWidth: true - text: rotateMouseArea.currentValue + dial.stateType.unitString + text: rotateMouseArea.currentValue + (dial.stateType ? dial.stateType.unitString : "") font.pixelSize: app.largeFont * 1.5 horizontalAlignment: Text.AlignHCenter visible: dial.showValueLabel && dial.stateType !== null @@ -101,7 +101,6 @@ ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true - Item { id: innerDial @@ -110,7 +109,6 @@ ColumnLayout { anchors.centerIn: parent rotation: dial.startAngle - Rectangle { anchors.fill: rotationButton radius: height / 2 @@ -167,7 +165,7 @@ ColumnLayout { width: parent.width height: width radius: width / 2 - color: dial.angleToValue(parent.rotation) <= dial.deviceState.value ? d.poweredColor : d.offColor + color: dial.deviceState && dial.angleToValue(parent.rotation) <= dial.deviceState.value ? d.poweredColor : d.offColor Behavior on color { ColorAnimation { duration: 200 } } } } @@ -247,8 +245,8 @@ ColumnLayout { dragging = false; } - readonly property int decimals: dial.stateType.type.toLowerCase() === "int" ? 0 : 1 - property var currentValue: dial.deviceState.value.toFixed(decimals) + readonly property int decimals: dial.stateType && dial.stateType.type.toLowerCase() === "int" ? 0 : 1 + property var currentValue: dial.deviceState ? dial.deviceState.value.toFixed(decimals) : 0 property date lastVibration: new Date() property int startX property int startY diff --git a/nymea-app/ui/components/Led.qml b/nymea-app/ui/components/Led.qml index 7ca8b7ae..d48c4791 100644 --- a/nymea-app/ui/components/Led.qml +++ b/nymea-app/ui/components/Led.qml @@ -5,13 +5,28 @@ Item { implicitHeight: app.iconSize * .8 implicitWidth: height - property bool on: false + // TODO: Convert to enum once we have Qt 5.10 + // on, off, green, orange, red + property string state: "off" Rectangle { height: Math.min(parent.height, parent.height) width: height radius: width / 2 - color: root.on ? "lightgreen" : "lightgray" + color: { + switch (root.state) { + case "on": + return app.accentColor + case "green": + return "#73c0b3"; + case "off": + return "lightgray"; + case "orange": + return "#fedf6f"; + case "red": + return "#dd7777" + } + } border.width: 1 border.color: app.foregroundColor } diff --git a/nymea-app/ui/components/ShutterControls.qml b/nymea-app/ui/components/ShutterControls.qml index e14cd60e..bb62ac7d 100644 --- a/nymea-app/ui/components/ShutterControls.qml +++ b/nymea-app/ui/components/ShutterControls.qml @@ -6,7 +6,7 @@ import Nymea 1.0 RowLayout { id: root - spacing: (parent.width - app.iconSize*2*children.length) / 4 + spacing: (parent.width - app.iconSize*2*children.length) / (children.length - 1) // implicitWidth: app.iconSize * 2 * children.length + spacing * (children.length - 1) implicitWidth: childrenRect.width diff --git a/nymea-app/ui/components/ThrottledSlider.qml b/nymea-app/ui/components/ThrottledSlider.qml index 434889a2..8bcc5598 100644 --- a/nymea-app/ui/components/ThrottledSlider.qml +++ b/nymea-app/ui/components/ThrottledSlider.qml @@ -10,6 +10,7 @@ Item { property alias from: slider.from property alias to: slider.to property alias stepSize: slider.stepSize + property alias snapMode: slider.snapMode readonly property real rawValue: slider.value diff --git a/nymea-app/ui/customviews/GenericTypeGraph.qml b/nymea-app/ui/customviews/GenericTypeGraph.qml index d6ec772e..a5d69c03 100644 --- a/nymea-app/ui/customviews/GenericTypeGraph.qml +++ b/nymea-app/ui/customviews/GenericTypeGraph.qml @@ -57,7 +57,7 @@ Item { Led { visible: root.stateType.type.toLowerCase() === "bool" - on: root.valueState.value === true + state: root.valueState.value === true ? "on" : "off" } Label { diff --git a/nymea-app/ui/delegates/statedelegates/LedDelegate.qml b/nymea-app/ui/delegates/statedelegates/LedDelegate.qml index 22fb94a5..8eb5163b 100644 --- a/nymea-app/ui/delegates/statedelegates/LedDelegate.qml +++ b/nymea-app/ui/delegates/statedelegates/LedDelegate.qml @@ -7,5 +7,5 @@ import "../../components" Led { property bool value - on: value === true + state: value === true ? "on" : "off" } diff --git a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml index 541e1044..4c7fd110 100644 --- a/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml +++ b/nymea-app/ui/devicelistpages/SensorsDeviceListPage.qml @@ -118,7 +118,7 @@ DeviceListPageBase { Led { id: led visible: sensorValueDelegate.stateType && sensorValueDelegate.stateType.type.toLowerCase() == "bool" - on: visible && sensorValueDelegate.stateValue.value === true + state: visible && sensorValueDelegate.stateValue.value === true ? "on" : "off" } Item { Layout.preferredWidth: led.width diff --git a/nymea-app/ui/devicepages/DeviceLogPage.qml b/nymea-app/ui/devicepages/DeviceLogPage.qml index 8481e246..01b00219 100644 --- a/nymea-app/ui/devicepages/DeviceLogPage.qml +++ b/nymea-app/ui/devicepages/DeviceLogPage.qml @@ -268,7 +268,7 @@ Page { property var value Led { implicitHeight: app.smallFont - on: boolLed.value === "true" + state: boolLed.value === "true" ? "on" : "off" } } } diff --git a/nymea-app/ui/devicepages/HeatingDevicePage.qml b/nymea-app/ui/devicepages/HeatingDevicePage.qml index 5171c499..d0b137f6 100644 --- a/nymea-app/ui/devicepages/HeatingDevicePage.qml +++ b/nymea-app/ui/devicepages/HeatingDevicePage.qml @@ -23,6 +23,9 @@ DevicePageBase { readonly property StateType boostStateType: device.deviceClass.stateTypes.findByName("boost") readonly property State boostState: boostStateType ? device.states.getState(boostStateType.id) : null + Component.onCompleted: { + print("d:", root.device, root.targetTemperatureStateType, root.percentageStateType) + } GridLayout { anchors.fill: parent @@ -33,10 +36,11 @@ DevicePageBase { id: dial Layout.fillWidth: true Layout.fillHeight: true - visible: root.targetTemperatureStateType || root.percentageStateType +// visible: root.targetTemperatureStateType || root.percentageStateType device: root.device stateType: root.targetTemperatureStateType ? root.targetTemperatureStateType : root.percentageStateType + } Rectangle { diff --git a/nymea-app/ui/experiences/garagegates/Main.qml b/nymea-app/ui/experiences/garagegates/Main.qml new file mode 100644 index 00000000..73b62137 --- /dev/null +++ b/nymea-app/ui/experiences/garagegates/Main.qml @@ -0,0 +1,97 @@ +import QtQuick 2.3 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.2 +import "qrc:/ui/components" +import Nymea 1.0 + +Item { + id: root + readonly property string title: qsTr("Garage gates") + readonly property string icon: Qt.resolvedUrl("qrc:/ui/images/shutter/shutter-050.svg") + + DevicesProxy { + id: garagesFilterModel + engine: _engine + shownInterfaces: ["garagegate"] + } + + EmptyViewPlaceholder { + anchors.centerIn: parent + width: parent.width - app.margins * 2 + text: qsTr("There are no garage gates set up yet.") + imageSource: "qrc:/ui/images/shutter/shutter-050.svg" + buttonText: qsTr("Set up now") + visible: garagesFilterModel.count === 0 + } + + SwipeView { + id: swipeView + anchors.fill: parent + + Repeater { + model: garagesFilterModel + + Item { + id: garageGateView + width: swipeView.width + height: swipeView.height + + readonly property Device device: garagesFilterModel.get(index) + + readonly property StateType openStateType: device.deviceClass.stateTypes.findByName("state") + readonly property State openState: openStateType ? device.states.getState(openStateType.id) : null + + readonly property StateType intermediatePositionStateType: device.deviceClass.stateTypes.findByName("intermediatePosition") + readonly property State intermediatePositionState: intermediatePositionStateType ? device.states.getState(intermediatePositionStateType.id) : null + + GridLayout { + id: layout + anchors.fill: parent + anchors.margins: app.margins + columns: app.landscape ? 2 : 1 + + Label { + id: label + text: garageGateView.device.name + font.pixelSize: app.largeFont + Layout.preferredWidth: layout.width + Layout.columnSpan: parent.columns + horizontalAlignment: Text.AlignHCenter + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + Layout.minimumWidth: app.landscape ? layout.width / 2 : layout.width + + ColorIcon { + height: Math.min(parent.height, parent.width) + width: height + anchors.centerIn: parent + name: "qrc:/ui/images/shutter/shutter-" + currentImage + ".svg" + property string currentImage: garageGateView.openState.value === "closed" ? "100" : + garageGateView.openState.value === "open" && garageGateView.intermediatePositionState.value === false ? "000" : "050" + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: controls.implicitHeight + Layout.minimumWidth: app.landscape ? layout.width / 2 : layout.width + + ShutterControls { + id: controls + device: garageGateView.device + } + } + } + } + } + } + + PageIndicator { + anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter } + count: garagesFilterModel.count + currentIndex: swipeView.currentIndex + } +} diff --git a/nymea-app/ui/experiences/heating/Main.qml b/nymea-app/ui/experiences/heating/Main.qml new file mode 100644 index 00000000..30d40ac6 --- /dev/null +++ b/nymea-app/ui/experiences/heating/Main.qml @@ -0,0 +1,432 @@ +import QtQuick 2.3 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import "qrc:/ui/components" +import Nymea 1.0 +import QtGraphicalEffects 1.0 + +Item { + id: root + readonly property string title: qsTr("Celsi°s") + readonly property string icon: Qt.resolvedUrl("qrc:/ui/images/radiator.svg") + + readonly property Device duwWpDevice: duwWpFilterModel.count > 0 ? duwWpFilterModel.get(0) : null + readonly property Device duwLuDevice: duwLuFilterModel.count > 0 ? duwLuFilterModel.get(0) : null + + readonly property State temperatureState: duwWpDevice ? duwWpDevice.states.getState(duwWpDevice.deviceClass.stateTypes.findByName("temperature").id) : null + readonly property State targetTemperatureState: duwWpDevice ? duwWpDevice.states.getState(duwWpDevice.deviceClass.stateTypes.findByName("targetTemperature").id) : null + readonly property State co2LevelState: duwLuDevice ? duwLuDevice.states.getState(duwLuDevice.deviceClass.stateTypes.findByName("co2").id) : null + readonly property State ventilationModeState: duwLuDevice ? duwLuDevice.states.getState(duwLuDevice.deviceClass.stateTypes.findByName("ventilationMode").id) : null + readonly property State ventilationLevelState: duwLuDevice ? duwLuDevice.states.getState(duwLuDevice.deviceClass.stateTypes.findByName("activeVentilationLevel").id) : null + + function ventilationModeToSliderValue(ventilationMode) { + switch (ventilationMode) { + case "Automatic": + case "Party": + return 0 + case "Manual level 0": + return 0; + case "Manual level 1": + return 1; + case "Manual level 2": + return 2; + case "Manual level 3": + return 3; + } + return 0; + } + function ventilationModeToUiMode(ventilationMode) { + switch (ventilationMode) { + case "Automatic": + return 0 + case "Party": + return 1; + case "Manual level 0": + case "Manual level 1": + case "Manual level 2": + case "Manual level 3": + return 2; + } + } + + function uiModeToVentilationMode(uiMode, sliderValue) { + switch (uiMode) { + case 0: + return "Automatic"; + case 1: + return "Party"; + case 2: + return "Manual level " + Math.floor(sliderValue) + } + } + + function setVentilationMode(uiModeIndex, sliderIndex) { + var params =[]; + var param = {}; + param["paramTypeId"] = root.ventilationModeState.stateTypeId + param["value"] = root.uiModeToVentilationMode(uiModeIndex, sliderIndex) + params.push(param) + engine.deviceManager.executeAction(root.duwLuDevice.id, root.ventilationModeState.stateTypeId, params) + } + + function setTargetTemp(targetTemp) { + // We don't want to spam with set value calls so we're going to queue them up and only send one at a time + d.queuedTargetTemp = targetTemp; + if (d.pendingCallId != -1) { + d.setTempPending = true; + return; + } + var params = [] + var param = {} + param["paramTypeId"] = root.targetTemperatureState.stateTypeId + param["value"] = targetTemp + params.push(param) + d.pendingCallId = engine.deviceManager.executeAction(root.duwWpDevice.id, root.targetTemperatureState.stateTypeId, params) + d.setTempPending = false; + } + + Connections { + target: engine.deviceManager + onExecuteActionReply: { + print("executeActionReply:", params["id"]) + if (params["id"] === d.pendingCallId) { + d.pendingCallId = -1; + if (d.setTempPending) { + setTargetTemp(d.queuedTargetTemp) + } + } + } + } + + QtObject { + id: d + property int pendingCallId: -1 + property bool setTempPending: false + property real queuedTargetTemp: 0 + } + + DevicesProxy { + id: duwWpFilterModel + engine: _engine + filterDeviceClassId: "e548f962-92db-4110-8279-10fbcde35f93" + } + + DevicesProxy { + id: duwLuFilterModel + engine: _engine + filterDeviceClassId: "0de8e21e-392a-4790-a78a-b1a7eaa7571b" + } + + EmptyViewPlaceholder { + anchors.centerIn: parent + width: parent.width - app.margins * 2 + text: qsTr("There is no drexel und weiss heating system set up yet.") + imageSource: "qrc:/ui/images/radiator.svg" + buttonVisible: false + buttonText: qsTr("Set up now") + visible: duwWpFilterModel.count === 0 && !engine.deviceManager.fetchingData + } + + + Item { + id: mainView + anchors.fill: parent + visible: root.duwWpDevice !== null + + ColumnLayout { + anchors.fill: parent + anchors.margins: app.margins + + ColumnLayout { + + Label { + text: qsTr("Current air quality") + font.pixelSize: app.smallFont + } + + RowLayout { + spacing: app.margins + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + name: "qrc:/ui/images/weathericons/wind.svg" + color: app.accentColor + } + Led { + state: { + if (!root.co2LevelState) { + return "off" + } + if (root.co2LevelState.value < 600) { + return "green" + } + if (root.co2LevelState.value < 1200) { + return "orange" + } + return "red" + } + } + } + + Label { + text: qsTr("Current temperature") + font.pixelSize: app.smallFont + } + + RowLayout { + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + name: "qrc:/ui/images/sensors/temperature.svg" + color: app.accentColor + } + Label { + text: root.temperatureState ? root.temperatureState.value.toFixed(1) + "°C" : "N/A" + Layout.fillWidth: true + font.pixelSize: app.largeFont * 1.5 + } + } + } + + ColumnLayout { + + Label { + text: qsTr("Temperature, °C") + font.pixelSize: app.largeFont + } + Label { + text: (d.pendingCallId !== -1 || d.setTempPending) ? d.queuedTargetTemp.toFixed(1) : + root.targetTemperatureState ? root.targetTemperatureState.value.toFixed(1) : "N/A" + font.pixelSize: app.largeFont * 3 + } + } + + + ColumnLayout { + Layout.fillWidth: false + Layout.bottomMargin: app.margins + ColorIcon { + Layout.preferredHeight: app.iconSize //* 1.5 + Layout.preferredWidth: height + Layout.alignment: Qt.AlignHCenter + color: app.accentColor + name: "qrc:/ui/images/magic.svg" + MouseArea { + anchors.fill: parent + onClicked: pageStack.push("qrc:/ui/magic/DeviceRulesPage.qml", {device: root.duwWpDevice}) + } + } + Label { + text: qsTr("Automate this thing") + color: app.accentColor + font.pixelSize: app.smallFont + } + } + + ColumnLayout { + + RowLayout { + Layout.leftMargin: parent.width * .05 + Layout.rightMargin: parent.width * .2 + spacing: app.margins + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + color: app.accentColor + name: "qrc:/ui/images/ventilation.svg" + PropertyAnimation on rotation { + running: root.ventilationLevelState !== null + duration: root.ventilationLevelState !== null && root.ventilationLevelState.value > 0 + ? 2000 / root.ventilationLevelState.value + : 0 + from: 0 + to: 360 + loops: Animation.Infinite + onDurationChanged: { + running = false; + running = true; + } + } + } + + RowLayout { + Layout.fillWidth: true + Layout.maximumHeight: app.iconSize + spacing: 0 + + Repeater { + model: ListModel { + ListElement { text: qsTr("Auto") } + ListElement { text: qsTr("Party") } + ListElement { text: qsTr("Manual") } + } + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + border.width: 1 + border.color: app.accentColor + color: root.ventilationModeState && root.ventilationModeToUiMode(root.ventilationModeState.value) === index ? app.accentColor : "transparent" + Label { + anchors.centerIn: parent + text: model.text + font.pixelSize: app.smallFont + } + MouseArea { + anchors.fill: parent + onClicked: { + root.setVentilationMode(index, ventilationSlider.value) + } + } + } + } + } + } + + Slider { + id: ventilationSlider + Layout.fillWidth: true + Layout.leftMargin: parent.width * .05 + Layout.rightMargin: parent.width * .05 + from: 0 + to: 3 + stepSize: 1 + live: false + snapMode: Slider.SnapAlways + enabled: root.ventilationModeState && root.ventilationModeToUiMode(root.ventilationModeState.value) === 2 + opacity: enabled ? 1 : .2 + value: root.ventilationModeState ? root.ventilationModeToSliderValue(root.ventilationModeState.value) : 0 + onMoved: root.setVentilationMode(2, valueAt(visualPosition)) + } + } + + +// ProgressButton { +// imageSource: "qrc:/ui/images/system-shutdown.svg" +// Layout.preferredHeight: app.iconSize * 1.5 +// Layout.preferredWidth: height +// Layout.alignment: Qt.AlignHCenter +// } + +// Label { +// text: qsTr("Hold to turn off") +// font.pixelSize: app.smallFont +// Layout.fillWidth: true +// horizontalAlignment: Text.AlignHCenter +// } + } + + Item { + height: parent.height * .85 + width: height + anchors.left: parent.right + anchors.leftMargin: -width * .25 + anchors.top: parent.top + anchors.topMargin: -height * .05 + z: -1 + + Rectangle { + id: outerRadius + anchors.fill: parent + radius: width / 2 + border.width: 3 + color: "transparent" + border.color: app.accentColor + } + + Glow { + anchors.fill: parent + source: outerRadius +// color: "#f45b69" + color: Qt.rgba(app.accentColor.r, app.accentColor.g, app.accentColor.b, .5) + radius: 8 + samples: 17 + spread: 0.5 + } + + Rectangle { + id: innerRadius + anchors.fill: parent + anchors.margins: parent.width * .02 + radius: width / 2 + border.width: 2 + color: "transparent" + border.color: app.accentColor + + Repeater { + id: ticksRepeater + model: 180 + + Item { + height: isBold ? 3 : 2 + width: parent.width - 2 + anchors.centerIn: parent + rotation: index * 360 / ticksRepeater.count + readonly property int isBold: index % 10 === 0 +// Rectangle { anchors.fill: parent; color: "blue" } + + Rectangle { height: parent.height; width: parent.isBold ? 20 : 10; color: app.accentColor } + + } + } + + } + + MouseArea { + anchors.fill: parent + preventStealing: true + + property real startAnglePress + property real startAngleDial + property real startTemp + + property real lastValue + + onPressed: { + startAnglePress = calculateAngle(mouseX, mouseY) + startAngleDial = innerRadius.rotation + startTemp = root.targetTemperatureState.value + lastValue = startTemp + + print("angle:", calculateAngle(mouseX, mouseY)) + } + + onPositionChanged: { + var currentAngle = calculateAngle(mouseX, mouseY) + var angleDiff = currentAngle - startAnglePress + + var tempDiff = Math.round(angleDiff / 2) / 10 + var newTemp = startTemp + tempDiff + + innerRadius.rotation = startAngleDial + angleDiff + + if (lastValue.toFixed(1) === newTemp.toFixed(1)) { + return; + } + lastValue = newTemp + + print("degree value changed", newTemp, lastValue) + PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection) + root.setTargetTemp(newTemp); + } + + function calculateAngle(mouseX, mouseY) { + // transform coords to center of dial + mouseX -= width / 2 + mouseY -= height / 2 + + var rad = Math.atan(mouseY / mouseX); + var angle = rad * 180 / Math.PI + + angle += 90; + + if (mouseX < 0 && mouseY >= 0) angle = 180 + angle; + if (mouseX < 0 && mouseY < 0) angle = 180 + angle; + + return angle; + } + } + } + } +} diff --git a/nymea-app/ui/fonts/Oswald-Bold.ttf b/nymea-app/ui/fonts/Oswald-Bold.ttf new file mode 100644 index 00000000..c7307b0b Binary files /dev/null and b/nymea-app/ui/fonts/Oswald-Bold.ttf differ diff --git a/nymea-app/ui/fonts/Oswald-ExtraLight.ttf b/nymea-app/ui/fonts/Oswald-ExtraLight.ttf new file mode 100644 index 00000000..5662aad7 Binary files /dev/null and b/nymea-app/ui/fonts/Oswald-ExtraLight.ttf differ diff --git a/nymea-app/ui/fonts/Oswald-Light.ttf b/nymea-app/ui/fonts/Oswald-Light.ttf new file mode 100644 index 00000000..264872d2 Binary files /dev/null and b/nymea-app/ui/fonts/Oswald-Light.ttf differ diff --git a/nymea-app/ui/fonts/Oswald-Medium.ttf b/nymea-app/ui/fonts/Oswald-Medium.ttf new file mode 100644 index 00000000..d060d7ea Binary files /dev/null and b/nymea-app/ui/fonts/Oswald-Medium.ttf differ diff --git a/nymea-app/ui/fonts/Oswald-Regular.ttf b/nymea-app/ui/fonts/Oswald-Regular.ttf new file mode 100644 index 00000000..e9976cc3 Binary files /dev/null and b/nymea-app/ui/fonts/Oswald-Regular.ttf differ diff --git a/nymea-app/ui/fonts/Oswald-SemiBold.ttf b/nymea-app/ui/fonts/Oswald-SemiBold.ttf new file mode 100644 index 00000000..9183e926 Binary files /dev/null and b/nymea-app/ui/fonts/Oswald-SemiBold.ttf differ diff --git a/nymea-app/ui/images/ventilation.svg b/nymea-app/ui/images/ventilation.svg new file mode 100644 index 00000000..ee42d644 --- /dev/null +++ b/nymea-app/ui/images/ventilation.svg @@ -0,0 +1,28 @@ + + + + +settings + + + diff --git a/nymea-app/ui/mainviews/DevicesPageDelegate.qml b/nymea-app/ui/mainviews/DevicesPageDelegate.qml index bcc4e168..5528ebe4 100644 --- a/nymea-app/ui/mainviews/DevicesPageDelegate.qml +++ b/nymea-app/ui/mainviews/DevicesPageDelegate.qml @@ -511,7 +511,7 @@ MainPageTile { Led { Layout.preferredHeight: app.iconSize * .5 Layout.preferredWidth: height - on: sensorsRoot.shownStateType && sensorsRoot.device.stateValue(sensorsRoot.shownStateType.id) === true + state: sensorsRoot.shownStateType && sensorsRoot.device.stateValue(sensorsRoot.shownStateType.id) === true ? "on" : "off" visible: sensorsRoot.shownStateType && sensorsRoot.shownStateType.type.toLowerCase() === "bool" } } diff --git a/nymea-app/ui/mainviews/FavoritesView.qml b/nymea-app/ui/mainviews/FavoritesView.qml index eb51c485..f6507c65 100644 --- a/nymea-app/ui/mainviews/FavoritesView.qml +++ b/nymea-app/ui/mainviews/FavoritesView.qml @@ -347,7 +347,7 @@ Item { } } Led { - on: sensorsRoot.currentState.value === true + state: sensorsRoot.currentState.value === true ? "on" : "off" visible: sensorsRoot.currentStateType.type.toLowerCase() === "bool" }