diff --git a/libnymea-app-core/connection/awsclient.cpp b/libnymea-app-core/connection/awsclient.cpp index 6b7b98fe..e89dbc29 100644 --- a/libnymea-app-core/connection/awsclient.cpp +++ b/libnymea-app-core/connection/awsclient.cpp @@ -628,7 +628,7 @@ void AWSClient::registerPushNotificationEndpoint(const QString ®istrationId, payload.insert("mobileDeviceUuid", mobileDeviceId); QJsonDocument jsonDoc = QJsonDocument::fromVariant(payload); - qDebug() << "Registering push notification endpoint"; + qDebug() << "Registering push notification endpoint" << mobileDeviceId; // qDebug() << "POST" << url.toString(); // qDebug() << "HEADERS:"; // foreach (const QByteArray &hdr, request.rawHeaderList()) { diff --git a/libnymea-app-core/devicediscovery.cpp b/libnymea-app-core/devicediscovery.cpp index ec3a2e6e..e937f4b1 100644 --- a/libnymea-app-core/devicediscovery.cpp +++ b/libnymea-app-core/devicediscovery.cpp @@ -22,6 +22,8 @@ QVariant DeviceDiscovery::data(const QModelIndex &index, int role) const return m_foundDevices.at(index.row())->name(); case RoleDescription: return m_foundDevices.at(index.row())->description(); + case RoleDeviceId: + return m_foundDevices.at(index.row())->deviceId(); } return QVariant(); @@ -31,6 +33,7 @@ QHash DeviceDiscovery::roleNames() const { QHash roles; roles.insert(RoleId, "id"); + roles.insert(RoleDeviceId, "deviceId"); roles.insert(RoleName, "name"); roles.insert(RoleDescription, "description"); return roles; @@ -104,6 +107,7 @@ void DeviceDiscovery::discoverDevicesResponse(const QVariantMap ¶ms) if (!contains(descriptorVariant.toMap().value("id").toUuid())) { beginInsertRows(QModelIndex(), m_foundDevices.count(), m_foundDevices.count()); DeviceDescriptor *descriptor = new DeviceDescriptor(descriptorVariant.toMap().value("id").toUuid(), + descriptorVariant.toMap().value("deviceId").toString(), descriptorVariant.toMap().value("title").toString(), descriptorVariant.toMap().value("description").toString()); foreach (const QVariant ¶mVariant, descriptorVariant.toMap().value("deviceParams").toList()) { @@ -128,9 +132,10 @@ bool DeviceDiscovery::contains(const QUuid &deviceDescriptorId) const return false; } -DeviceDescriptor::DeviceDescriptor(const QUuid &id, const QString &name, const QString &description, QObject *parent): +DeviceDescriptor::DeviceDescriptor(const QUuid &id, const QUuid &deviceId, const QString &name, const QString &description, QObject *parent): QObject(parent), m_id(id), + m_deviceId(deviceId), m_name(name), m_description(description), m_params(new Params(this)) @@ -143,6 +148,11 @@ QUuid DeviceDescriptor::id() const return m_id; } +QUuid DeviceDescriptor::deviceId() const +{ + return m_deviceId; +} + QString DeviceDescriptor::name() const { return m_name; @@ -157,3 +167,92 @@ Params* DeviceDescriptor::params() const { return m_params; } + +DeviceDiscoveryProxy::DeviceDiscoveryProxy(QObject *parent): + QSortFilterProxyModel (parent) +{ + +} + +DeviceDiscovery *DeviceDiscoveryProxy::deviceDiscovery() const +{ + return m_deviceDiscovery; +} + +void DeviceDiscoveryProxy::setDeviceDiscovery(DeviceDiscovery *deviceDiscovery) +{ + if (m_deviceDiscovery != deviceDiscovery) { + m_deviceDiscovery = deviceDiscovery; + setSourceModel(deviceDiscovery); + emit deviceDiscoveryChanged(); + emit countChanged(); + connect(m_deviceDiscovery, &DeviceDiscovery::countChanged, this, &DeviceDiscoveryProxy::countChanged); + invalidateFilter(); + } +} + +bool DeviceDiscoveryProxy::showAlreadyAdded() const +{ + return m_showAlreadyAdded; +} + +void DeviceDiscoveryProxy::setShowAlreadyAdded(bool showAlreadyAdded) +{ + if (m_showAlreadyAdded != showAlreadyAdded) { + m_showAlreadyAdded = showAlreadyAdded; + emit showAlreadyAddedChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +bool DeviceDiscoveryProxy::showNew() const +{ + return m_showNew; +} + +void DeviceDiscoveryProxy::setShowNew(bool showNew) +{ + if (m_showNew != showNew) { + m_showNew = showNew; + emit showNewChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +QUuid DeviceDiscoveryProxy::filterDeviceId() const +{ + return m_filterDeviceId; +} + +void DeviceDiscoveryProxy::setFilterDeviceId(const QUuid &filterDeviceId) +{ + if (m_filterDeviceId != filterDeviceId) { + m_filterDeviceId = filterDeviceId; + emit filterDeviceIdChanged(); + invalidateFilter(); + emit countChanged(); + } +} + +DeviceDescriptor *DeviceDiscoveryProxy::get(int index) const +{ + return m_deviceDiscovery->get(mapToSource(this->index(index, 0)).row()); +} + +bool DeviceDiscoveryProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + Q_UNUSED(sourceParent) + DeviceDescriptor* dev = m_deviceDiscovery->get(sourceRow); + if (!m_showAlreadyAdded && !dev->deviceId().isNull()) { + return false; + } + if (!m_showNew && dev->deviceId().isNull()) { + return false; + } + if (!m_filterDeviceId.isNull() && dev->deviceId() != m_filterDeviceId) { + return false; + } + return true; +} diff --git a/libnymea-app-core/devicediscovery.h b/libnymea-app-core/devicediscovery.h index 1e486b65..cd99d228 100644 --- a/libnymea-app-core/devicediscovery.h +++ b/libnymea-app-core/devicediscovery.h @@ -9,19 +9,22 @@ class DeviceDescriptor: public QObject { Q_OBJECT Q_PROPERTY(QUuid id READ id CONSTANT) + Q_PROPERTY(QUuid deviceId READ deviceId CONSTANT) Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString description READ description CONSTANT) Q_PROPERTY(Params* params READ params CONSTANT) public: - DeviceDescriptor(const QUuid &id, const QString &name, const QString &description, QObject *parent = nullptr); + DeviceDescriptor(const QUuid &id, const QUuid &deviceId, const QString &name, const QString &description, QObject *parent = nullptr); QUuid id() const; + QUuid deviceId() const; QString name() const; QString description() const; Params* params() const; private: QUuid m_id; + QUuid m_deviceId; QString m_name; QString m_description; Params *m_params = nullptr; @@ -36,6 +39,7 @@ class DeviceDiscovery : public QAbstractListModel public: enum Roles { RoleId, + RoleDeviceId, RoleName, RoleDescription }; @@ -72,4 +76,47 @@ private: QList m_foundDevices; }; +class DeviceDiscoveryProxy: public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(DeviceDiscovery* deviceDiscovery READ deviceDiscovery WRITE setDeviceDiscovery NOTIFY deviceDiscoveryChanged) + Q_PROPERTY(bool showAlreadyAdded READ showAlreadyAdded WRITE setShowAlreadyAdded NOTIFY showAlreadyAddedChanged) + Q_PROPERTY(bool showNew READ showNew WRITE setShowNew NOTIFY showNewChanged) + Q_PROPERTY(QUuid filterDeviceId READ filterDeviceId WRITE setFilterDeviceId NOTIFY filterDeviceIdChanged) + +public: + DeviceDiscoveryProxy(QObject *parent = nullptr); + + DeviceDiscovery* deviceDiscovery() const; + void setDeviceDiscovery(DeviceDiscovery* deviceDiscovery); + + bool showAlreadyAdded() const; + void setShowAlreadyAdded(bool showAlreadyAdded); + + bool showNew() const; + void setShowNew(bool showNew); + + QUuid filterDeviceId() const; + void setFilterDeviceId(const QUuid &filterDeviceId); + + Q_INVOKABLE DeviceDescriptor* get(int index) const; + +signals: + void countChanged(); + void deviceDiscoveryChanged(); + void showAlreadyAddedChanged(); + void showNewChanged(); + void filterDeviceIdChanged(); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + +private: + DeviceDiscovery* m_deviceDiscovery = nullptr; + bool m_showAlreadyAdded = false; + bool m_showNew = true; + QUuid m_filterDeviceId; +}; + #endif // DEVICEDISCOVERY_H diff --git a/libnymea-app-core/devicemanager.cpp b/libnymea-app-core/devicemanager.cpp index 1f581f39..1ce074bd 100644 --- a/libnymea-app-core/devicemanager.cpp +++ b/libnymea-app-core/devicemanager.cpp @@ -302,6 +302,12 @@ void DeviceManager::executeActionResponse(const QVariantMap ¶ms) emit executeActionReply(params); } +void DeviceManager::reconfigureDeviceResponse(const QVariantMap ¶ms) +{ + qDebug() << "Reconfigure device response" << params; + emit reconfigureDeviceReply(params.value("params").toMap()); +} + void DeviceManager::savePluginConfig(const QUuid &pluginId) { Plugin *p = m_plugins->getPlugin(pluginId); @@ -369,6 +375,22 @@ void DeviceManager::editDevice(const QUuid &deviceId, const QString &name) m_jsonClient->sendCommand("Devices.EditDevice", params, this, "editDeviceResponse"); } +void DeviceManager::reconfigureDevice(const QUuid &deviceId, const QVariantList &deviceParams) +{ + QVariantMap params; + params.insert("deviceId", deviceId.toString()); + params.insert("deviceParams", deviceParams); + m_jsonClient->sendCommand("Devices.ReconfigureDevice", params, this, "reconfigureDeviceResponse"); +} + +void DeviceManager::reconfigureDiscoveredDevice(const QUuid &deviceId, const QUuid &deviceDescriptorId) +{ + QVariantMap params; + params.insert("deviceId", deviceId.toString()); + params.insert("deviceDescriptorId", deviceDescriptorId); + m_jsonClient->sendCommand("Devices.ReconfigureDevice", params, this, "reconfigureDeviceResponse"); +} + int DeviceManager::executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList ¶ms) { // qDebug() << "JsonRpc: execute action " << deviceId.toString() << actionTypeId.toString() << params; diff --git a/libnymea-app-core/devicemanager.h b/libnymea-app-core/devicemanager.h index d6b2a343..2b4fa8cd 100644 --- a/libnymea-app-core/devicemanager.h +++ b/libnymea-app-core/devicemanager.h @@ -69,6 +69,8 @@ public: Q_INVOKABLE void confirmPairing(const QUuid &pairingTransactionId, const QString &secret = QString()); Q_INVOKABLE void removeDevice(const QUuid &deviceId, RemovePolicy policy = RemovePolicyNone); Q_INVOKABLE void editDevice(const QUuid &deviceId, const QString &name); + Q_INVOKABLE void reconfigureDevice(const QUuid &deviceId, const QVariantList &deviceParams); + Q_INVOKABLE void reconfigureDiscoveredDevice(const QUuid &deviceId, const QUuid &deviceDescriptorId); Q_INVOKABLE int executeAction(const QUuid &deviceId, const QUuid &actionTypeId, const QVariantList ¶ms = QVariantList()); private: @@ -85,6 +87,7 @@ private: Q_INVOKABLE void setPluginConfigResponse(const QVariantMap ¶ms); Q_INVOKABLE void editDeviceResponse(const QVariantMap ¶ms); Q_INVOKABLE void executeActionResponse(const QVariantMap ¶ms); + Q_INVOKABLE void reconfigureDeviceResponse(const QVariantMap ¶ms); public slots: void savePluginConfig(const QUuid &pluginId); @@ -96,6 +99,7 @@ signals: void removeDeviceReply(const QVariantMap ¶ms); void savePluginConfigReply(const QVariantMap ¶ms); void editDeviceReply(const QVariantMap ¶ms); + void reconfigureDeviceReply(const QVariantMap ¶ms); void executeActionReply(const QVariantMap ¶ms); void fetchingDataChanged(); void notificationReceived(const QString &deviceId, const QString &eventTypeId, const QVariantList ¶ms); diff --git a/libnymea-app-core/libnymea-app-core.h b/libnymea-app-core/libnymea-app-core.h index c710add8..d4402af8 100644 --- a/libnymea-app-core/libnymea-app-core.h +++ b/libnymea-app-core/libnymea-app-core.h @@ -114,6 +114,7 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "DeviceClasses", "Can't create this in QML. Get it from the DeviceManager."); qmlRegisterType(uri, 1, 0, "DeviceClassesProxy"); qmlRegisterType(uri, 1, 0, "DeviceDiscovery"); + qmlRegisterType(uri, 1, 0, "DeviceDiscoveryProxy"); qmlRegisterUncreatableType(uri, 1, 0, "DeviceDescriptor", "Get it from DeviceDiscovery"); qmlRegisterType(uri, 1, 0, "DeviceModel"); diff --git a/libnymea-app-core/vendorsproxy.cpp b/libnymea-app-core/vendorsproxy.cpp index a4ce4568..377de4d2 100644 --- a/libnymea-app-core/vendorsproxy.cpp +++ b/libnymea-app-core/vendorsproxy.cpp @@ -26,7 +26,7 @@ VendorsProxy::VendorsProxy(QObject *parent) : QSortFilterProxyModel(parent) { - + setSortRole(Vendors::RoleDisplayName); } Vendors *VendorsProxy::vendors() @@ -40,10 +40,16 @@ void VendorsProxy::setVendors(Vendors *vendors) m_vendors = vendors; setSourceModel(vendors); emit vendorsChanged(); + connect(m_vendors, &Vendors::countChanged, this, &VendorsProxy::countChanged); sort(0); } } +Vendor *VendorsProxy::get(int index) const +{ + return m_vendors->get(mapToSource(this->index(index, 0)).row()); +} + bool VendorsProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { QVariant leftName = sourceModel()->data(left); diff --git a/libnymea-app-core/vendorsproxy.h b/libnymea-app-core/vendorsproxy.h index 2ddd2bdd..021713c1 100644 --- a/libnymea-app-core/vendorsproxy.h +++ b/libnymea-app-core/vendorsproxy.h @@ -32,6 +32,7 @@ class VendorsProxy : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(Vendors *vendors READ vendors WRITE setVendors NOTIFY vendorsChanged) + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: explicit VendorsProxy(QObject *parent = nullptr); @@ -39,8 +40,11 @@ public: Vendors *vendors(); void setVendors(Vendors *vendors); + Q_INVOKABLE Vendor* get(int index) const; + signals: void vendorsChanged(); + void countChanged(); private: Vendors *m_vendors; diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index ff664b5d..a3083e31 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -78,6 +78,8 @@ int main(int argc, char *argv[]) QLibraryInfo::location(QLibraryInfo::TranslationsPath)); application.installTranslator(&qtTranslator); + qDebug() << "Locale info:" << QLocale() << QLocale().name() << QLocale().language() << QLocale().system(); + QTranslator appTranslator; bool translationResult = appTranslator.load(QLocale(), "nymea-app", "-", ":/translations/", ".qm"); if (translationResult) { diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 8197c0b2..11cbd132 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -1,11 +1,9 @@ ui/Nymea.qml - ui/NewDeviceWizard.qml ui/SettingsPage.qml ui/LoginPage.qml ui/MagicPage.qml - ui/EditDevicesPage.qml ui/PushButtonAuthPage.qml ui/KeyboardLoader.qml ui/MainPage.qml @@ -42,6 +40,7 @@ ui/components/FancyHeader.qml ui/components/MainPageTile.qml ui/components/BusyOverlay.qml + ui/components/SwipeDelegateGroup.qml ui/components/AWSPasswordTextField.qml ui/components/BrightnessSlider.qml ui/components/SegmentedImage.qml @@ -69,7 +68,6 @@ ui/devicepages/SensorDevicePagePre110.qml ui/devicepages/SensorDevicePagePost110.qml ui/devicepages/DevicePageBase.qml - ui/devicepages/ConfigureThingPage.qml ui/devicepages/InputTriggerDevicePage.qml ui/devicepages/StateLogPage.qml ui/devicepages/ShutterDevicePage.qml @@ -144,10 +142,10 @@ ui/system/ServerConfigurationDialog.qml ui/system/MqttPolicyPage.qml ui/devicepages/BoolSensorDevicePage.qml + ui/devicepages/HeatingDevicePage.qml ui/devicepages/PowersocketDevicePage.qml ui/devicelistpages/PowerSocketsDeviceListPage.qml ui/components/GroupedListView.qml - ui/devicepages/HeatingDevicePage.qml ui/delegates/statedelegates/LedDelegate.qml ui/delegates/statedelegates/LabelDelegate.qml ui/delegates/statedelegates/NumberLabelDelegate.qml @@ -161,5 +159,9 @@ ui/delegates/statedelegates/DateTimeDelegate.qml ui/components/Dial.qml ui/delegates/statedelegates/SliderDelegate.qml + ui/thingconfiguration/NewThingPage.qml + ui/thingconfiguration/SetupWizard.qml + ui/thingconfiguration/EditThingsPage.qml + ui/thingconfiguration/ConfigureThingPage.qml diff --git a/nymea-app/ui/MainPage.qml b/nymea-app/ui/MainPage.qml index 01fa5c7a..7426dd95 100644 --- a/nymea-app/ui/MainPage.qml +++ b/nymea-app/ui/MainPage.qml @@ -15,7 +15,7 @@ Page { title: swipeView.currentItem.title model: ListModel { - ListElement { iconSource: "../images/share.svg"; text: qsTr("Configure things"); page: "EditDevicesPage.qml" } + ListElement { iconSource: "../images/share.svg"; text: qsTr("Configure things"); page: "thingconfiguration/EditThingsPage.qml" } ListElement { iconSource: "../images/magic.svg"; text: qsTr("Magic"); page: "MagicPage.qml" } ListElement { iconSource: "../images/stock_application.svg"; text: qsTr("App settings"); page: "appsettings/AppSettingsPage.qml" } ListElement { iconSource: "../images/settings.svg"; text: qsTr("Box settings"); page: "SettingsPage.qml" } @@ -123,7 +123,7 @@ Page { imageSource: "images/starred.svg" buttonVisible: engine.deviceManager.devices.count === 0 buttonText: qsTr("Add a thing") - onButtonClicked: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml")) + onButtonClicked: pageStack.push(Qt.resolvedUrl("thingconfiguration/NewThingPage.qml")) } } @@ -150,7 +150,7 @@ Page { text: qsTr("There are no things set up yet.") + "\n" + qsTr("In order for your %1 box to be useful, go ahead and add some things.").arg(app.systemName) imageSource: "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle) buttonText: qsTr("Add a thing") - onButtonClicked: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml")) + onButtonClicked: pageStack.push(Qt.resolvedUrl("thingconfiguration/NewThingPage.qml")) } } @@ -172,7 +172,7 @@ Page { buttonText: engine.deviceManager.devices.count === 0 ? qsTr("Add a thing") : qsTr("Add a scene") onButtonClicked: { if (engine.deviceManager.devices.count === 0) { - pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml")) + pageStack.push(Qt.resolvedUrl("thingconfiguration/NewThingPage.qml")) } else { var page = pageStack.push(Qt.resolvedUrl("MagicPage.qml")) page.addRule() diff --git a/nymea-app/ui/components/GroupedListView.qml b/nymea-app/ui/components/GroupedListView.qml index 401cf7ed..85fcebbf 100644 --- a/nymea-app/ui/components/GroupedListView.qml +++ b/nymea-app/ui/components/GroupedListView.qml @@ -1,7 +1,8 @@ import QtQuick 2.9 -import QtQuick.Controls 2.1 +import QtQuick.Controls 2.2 ListView { + id: root ScrollBar.vertical: ScrollBar {} @@ -12,4 +13,5 @@ ListView { text: app.interfaceToString(section) } + SwipeDelegateGroup {} } diff --git a/nymea-app/ui/components/SwipeDelegateGroup.qml b/nymea-app/ui/components/SwipeDelegateGroup.qml new file mode 100644 index 00000000..209e945e --- /dev/null +++ b/nymea-app/ui/components/SwipeDelegateGroup.qml @@ -0,0 +1,47 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 + +Item { + id: swipeGroup + + property ListView listView: parent + QtObject { + id: d + property var delegates: swipeGroup.listView.contentItem.children + property var delegateCache: [] + + onDelegatesChanged: { + for (var i = 0; i < d.delegates.length; i++) { + var thisItem = d.delegates[i]; + if (!thisItem.hasOwnProperty("swipe")) { + continue; + } + if (d.delegateCache.indexOf(thisItem) < 0) { + d.delegateCache.push(thisItem); + + print("cache is now", d.delegateCache.length) + + thisItem.Component.destruction.connect(function() { + print("item destroyed", thisItem) + var idx = d.delegateCache.indexOf(thisItem) + d.delegateCache.splice(idx, 1) + print("cache is now", d.delegateCache.length) + }) + + thisItem.swipe.opened.connect(function() { + for (var j = 0; j < d.delegates.length; j++) { + var otherItem = d.delegates[j]; + if (thisItem === otherItem) { + continue; + } + if (!otherItem.hasOwnProperty("swipe")) { + continue; + } + otherItem.swipe.close(); + } + }) + } + } + } + } +} diff --git a/nymea-app/ui/delegates/ThingDelegate.qml b/nymea-app/ui/delegates/ThingDelegate.qml index d38d43d1..8dbfc75b 100644 --- a/nymea-app/ui/delegates/ThingDelegate.qml +++ b/nymea-app/ui/delegates/ThingDelegate.qml @@ -19,5 +19,4 @@ MeaListItemDelegate { readonly property var deviceClass: device ? engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) : null readonly property bool batteryCritical: deviceClass && deviceClass.interfaces.indexOf("battery") >= 0 ? device.stateValue(deviceClass.stateTypes.findByName("batteryCritical").id) === true : false readonly property bool disconnected: deviceClass && deviceClass.interfaces.indexOf("connectable") >= 0 ? device.stateValue(deviceClass.stateTypes.findByName("connected").id) === false : false - } diff --git a/nymea-app/ui/devicepages/ConfigureThingPage.qml b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml similarity index 88% rename from nymea-app/ui/devicepages/ConfigureThingPage.qml rename to nymea-app/ui/thingconfiguration/ConfigureThingPage.qml index 20c68c38..d2ac5435 100644 --- a/nymea-app/ui/devicepages/ConfigureThingPage.qml +++ b/nymea-app/ui/thingconfiguration/ConfigureThingPage.qml @@ -7,8 +7,8 @@ import "../delegates" Page { id: root - property var device: null - readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId) + property Device device: null + readonly property DeviceClass deviceClass: device ? device.deviceClass : null header: GuhHeader { text: root.device.name @@ -37,6 +37,15 @@ Page { popup.open(); } } + IconMenuItem { + iconSource: "../images/configure.svg" + text: qsTr("Reconfigure Thing") + visible: root.device.deviceClass.paramTypes.count > 0 + onTriggered: { + var configPage = pageStack.push(Qt.resolvedUrl("SetupWizard.qml"), {device: root.device}) + configPage.done.connect(function() {pageStack.pop(root)}) + } + } } Connections { @@ -149,4 +158,5 @@ Page { } } + } diff --git a/nymea-app/ui/EditDevicesPage.qml b/nymea-app/ui/thingconfiguration/EditThingsPage.qml similarity index 90% rename from nymea-app/ui/EditDevicesPage.qml rename to nymea-app/ui/thingconfiguration/EditThingsPage.qml index ab62f3b9..280a2a3d 100644 --- a/nymea-app/ui/EditDevicesPage.qml +++ b/nymea-app/ui/thingconfiguration/EditThingsPage.qml @@ -1,8 +1,8 @@ import QtQuick 2.4 import QtQuick.Controls 2.1 import QtQuick.Layouts 1.2 -import "components" -import "delegates" +import "../components" +import "../delegates" import Nymea 1.0 Page { @@ -20,7 +20,7 @@ Page { HeaderButton { imageSource: "../images/add.svg" - onClicked: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml")) + onClicked: pageStack.push(Qt.resolvedUrl("NewThingPage.qml")) } } @@ -77,7 +77,7 @@ Page { canDelete: true onClicked: { print("clicked:", model.id) - pageStack.push(Qt.resolvedUrl("devicepages/ConfigureThingPage.qml"), {device: device}) + pageStack.push(Qt.resolvedUrl("ConfigureThingPage.qml"), {device: device}) } onDeleteClicked: { d.deviceToRemove = device; @@ -96,6 +96,6 @@ Page { text: qsTr("In order for your %1 box to be useful, go ahead and add some things.").arg(app.systemName) imageSource: "qrc:/styles/%1/logo.svg".arg(styleController.currentStyle) buttonText: qsTr("Add a thing") - onButtonClicked: pageStack.push(Qt.resolvedUrl("NewDeviceWizard.qml")) + onButtonClicked: pageStack.push(Qt.resolvedUrl("NewThingPage.qml")) } } diff --git a/nymea-app/ui/thingconfiguration/NewThingPage.qml b/nymea-app/ui/thingconfiguration/NewThingPage.qml new file mode 100644 index 00000000..dddeef8b --- /dev/null +++ b/nymea-app/ui/thingconfiguration/NewThingPage.qml @@ -0,0 +1,130 @@ +import QtQuick 2.5 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.1 +import QtQuick.Controls.Material 2.1 +import Nymea 1.0 +import "../components" +import "../delegates" + +Page { + id: root + + header: GuhHeader { + text: qsTr("Set up new thing") + onBackPressed: { + pageStack.pop(); + } + } + + Pane { + id: filterPane + anchors { left: parent.left; top: parent.top; right: parent.right } + Behavior on height { NumberAnimation { duration: 120; easing.type: Easing.InOutQuad } } + + height: implicitHeight + app.margins * 2 + Material.elevation: 1 + z: 1 + + leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0 + contentItem: Rectangle { + color: app.primaryColor + clip: true + GridLayout { + anchors.fill: parent + anchors.margins: app.margins + columnSpacing: app.margins + columns: 2 + Label { + text: qsTr("Vendor") + } + + ComboBox { + id: vendorFilterComboBox + Layout.fillWidth: true + textRole: "displayName" + VendorsProxy { + id: vendorsProxy + vendors: engine.deviceManager.vendors + } + model: ListModel { + id: vendorsFilterModel + ListElement { displayName: qsTr("All"); vendorId: "" } + + + Component.onCompleted: { + for (var i = 0; i < vendorsProxy.count; i++) { + var vendor = vendorsProxy.get(i); + append({displayName: vendor.displayName, vendorId: vendor.id}) + } + } + } + } + Label { + text: qsTr("Type") + } + + ComboBox { + id: typeFilterComboBox + Layout.fillWidth: true + textRole: "displayName" + InterfacesSortModel { + id: interfacesSortModel + interfacesModel: InterfacesModel { + deviceManager: engine.deviceManager + shownInterfaces: app.supportedInterfaces + onlyConfiguredDevices: false + showUncategorized: false + } + } + model: ListModel { + id: typeFilterModel + ListElement { interfaceName: ""; displayName: qsTr("All") } + + Component.onCompleted: { + for (var i = 0; i < interfacesSortModel.count; i++) { + append({interfaceName: interfacesSortModel.get(i), displayName: app.interfaceToString(interfacesSortModel.get(i))}); + } + } + } + } + } + } + } + + GroupedListView { + anchors { + left: parent.left + top: filterPane.bottom + right: parent.right + bottom: parent.bottom + } + + model: DeviceClassesProxy { + id: deviceClassesProxy + vendorId: vendorsFilterModel.get(vendorFilterComboBox.currentIndex).vendorId + deviceClasses: engine.deviceManager.deviceClasses + filterInterface: typeFilterModel.get(typeFilterComboBox.currentIndex).interfaceName + groupByInterface: true + } + + delegate: MeaListItemDelegate { + id: deviceClassDelegate + width: parent.width + text: model.displayName + subText: engine.deviceManager.vendors.getVendor(model.vendorId).displayName + iconName: app.interfacesToIcon(deviceClass.interfaces) + prominentSubText: false + wrapTexts: false + + property var deviceClass: deviceClassesProxy.get(index) + + onClicked: { + var page = pageStack.push(Qt.resolvedUrl("SetupWizard.qml"), {deviceClass: deviceClassesProxy.get(index)}); + page.done.connect(function() { + pageStack.pop(root, StackView.Immediate); + pageStack.pop(); + }) + } + } + } +} diff --git a/nymea-app/ui/NewDeviceWizard.qml b/nymea-app/ui/thingconfiguration/SetupWizard.qml similarity index 57% rename from nymea-app/ui/NewDeviceWizard.qml rename to nymea-app/ui/thingconfiguration/SetupWizard.qml index ee5e9281..0ec7a0ec 100644 --- a/nymea-app/ui/NewDeviceWizard.qml +++ b/nymea-app/ui/thingconfiguration/SetupWizard.qml @@ -3,29 +3,38 @@ import QtQuick.Layouts 1.1 import QtQuick.Controls 2.1 import QtQuick.Controls.Material 2.1 import Nymea 1.0 -import "components" -import "delegates" +import "../components" +import "../delegates" Page { id: root + property DeviceClass deviceClass: device ? device.deviceClass : null + + // Optional: If set, it will be reconfigred, otherwise a new one will be created + property Device device: null + + signal done(); + header: GuhHeader { - text: qsTr("Set up new thing") - backButtonVisible: internalPageStack.depth > 1 + text: qsTr("Set up thing") onBackPressed: { - internalPageStack.pop(); + if (internalPageStack.depth > 1) { + internalPageStack.pop(); + } else { + pageStack.pop(); + } } HeaderButton { imageSource: "../images/close.svg" - onClicked: pageStack.pop(); + onClicked: root.done(); } } QtObject { id: d property var vendorId: null - property DeviceClass deviceClass: null property DeviceDescriptor deviceDescriptor: null property var discoveryParams: [] property string deviceName: "" @@ -34,6 +43,19 @@ Page { property int addRequestId: 0 } + Component.onCompleted: { + if (root.deviceClass.createMethods.indexOf("CreateMethodDiscovery") !== -1) { + if (deviceClass["discoveryParamTypes"].count > 0) { + internalPageStack.push(discoveryParamsPage) + } else { + discovery.discoverDevices(deviceClass.id) + internalPageStack.push(discoveryPage, {deviceClass: deviceClass}) + } + } else if (deviceClass.createMethods.indexOf("CreateMethodUser") !== -1) { + internalPageStack.push(paramsPage) + } + } + Connections { target: engine.deviceManager onPairDeviceReply: { @@ -50,7 +72,6 @@ Page { break; default: print("Setup method", params["setupMethod"], "not handled"); - } } onConfirmPairingReply: { @@ -61,6 +82,10 @@ Page { busyOverlay.shown = false; internalPageStack.push(resultsPage, {success: params["deviceError"] === "DeviceErrorNoError", deviceId: params["deviceId"]}) } + onReconfigureDeviceReply: { + busyOverlay.shown = false; + internalPageStack.push(resultsPage, {success: params["deviceError"] === "DeviceErrorNoError", deviceId: params["deviceId"]}) + } } DeviceDiscovery { @@ -71,169 +96,6 @@ Page { StackView { id: internalPageStack anchors.fill: parent - Component.onCompleted: push(deviceClassesPage) - } - - Component { - id: vendorsPage - Page { - ListView { - anchors.fill: parent - model: VendorsProxy { - vendors: engine.deviceManager.vendors - } - delegate: MeaListItemDelegate { - width: parent.width - text: model.displayName - - onClicked: { - d.vendorId = model.id - internalPageStack.push(deviceClassesPage) - } - } - } - } - } - - Component { - id: deviceClassesPage - Page { - Pane { - id: filterPane - anchors { left: parent.left; top: parent.top; right: parent.right } - Behavior on height { NumberAnimation { duration: 120; easing.type: Easing.InOutQuad } } - - height: implicitHeight + app.margins * 2 - Material.elevation: 1 - z: 1 - - leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0 - contentItem: Rectangle { - color: app.primaryColor - clip: true - GridLayout { - anchors.fill: parent - anchors.margins: app.margins - columnSpacing: app.margins - columns: 2 - Label { - text: qsTr("Vendor") - } - - ComboBox { - id: vendorFilterComboBox - Layout.fillWidth: true - textRole: "displayName" - model: ListModel { - id: vendorsFilterModel - ListElement { displayName: qsTr("All"); vendorId: "" } - - Component.onCompleted: { - for (var i = 0; i < engine.deviceManager.vendors.count; i++) { - var vendor = engine.deviceManager.vendors.get(i); - append({displayName: vendor.displayName, vendorId: vendor.id}) - } - } - } - } - Label { - text: qsTr("Type") - } - - ComboBox { - id: typeFilterComboBox - Layout.fillWidth: true - textRole: "displayName" - InterfacesSortModel { - id: interfacesSortModel - interfacesModel: InterfacesModel { - deviceManager: engine.deviceManager - shownInterfaces: app.supportedInterfaces - onlyConfiguredDevices: false - showUncategorized: false - } - } - model: ListModel { - id: typeFilterModel - ListElement { interfaceName: ""; displayName: qsTr("All") } - - Component.onCompleted: { - for (var i = 0; i < interfacesSortModel.count; i++) { - append({interfaceName: interfacesSortModel.get(i), displayName: app.interfaceToString(interfacesSortModel.get(i))}); - } - } - } - } - } - } - } - - GroupedListView { - anchors { - left: parent.left - top: filterPane.bottom - right: parent.right - bottom: parent.bottom - } - - model: DeviceClassesProxy { - id: deviceClassesProxy - vendorId: vendorsFilterModel.get(vendorFilterComboBox.currentIndex).vendorId - deviceClasses: engine.deviceManager.deviceClasses - filterInterface: typeFilterModel.get(typeFilterComboBox.currentIndex).interfaceName - groupByInterface: true - } - - delegate: MeaListItemDelegate { - id: deviceClassDelegate - width: parent.width - text: model.displayName - subText: engine.deviceManager.vendors.getVendor(model.vendorId).displayName - iconName: app.interfacesToIcon(deviceClass.interfaces) - prominentSubText: false - wrapTexts: false - - property var deviceClass: deviceClassesProxy.get(index) - - onClicked: { - d.deviceClass = deviceClass - if (deviceClass.createMethods.indexOf("CreateMethodDiscovery") !== -1) { - if (deviceClass["discoveryParamTypes"].count > 0) { - internalPageStack.push(discoveryParamsPage) - } else { - discovery.discoverDevices(deviceClass.id) - internalPageStack.push(discoveryPage, {deviceClass: deviceClass}) - } - } else if (deviceClass.createMethods.indexOf("CreateMethodUser") !== -1) { - internalPageStack.push(paramsPage) - } - - print("should setup", deviceClass.name, deviceClass.setupMethod, deviceClass.createMethods, deviceClass["discoveryParamTypes"].count) - } - - swipe.enabled: deviceClass.createMethods.indexOf("CreateMethodUser") !== -1 - swipe.right: MouseArea { - height: deviceClassDelegate.height - width: height - anchors.right: parent.right - Rectangle { - anchors.fill: parent - color: "transparent" - } - - ColorIcon { - anchors.fill: parent - anchors.margins: app.margins - name: "../images/add.svg" - } - onClicked: { - d.deviceClass = deviceClass - internalPageStack.push(paramsPage) - } - } - } - } - } } Component { @@ -254,7 +116,7 @@ Page { Repeater { id: paramRepeater - model: d.deviceClass ? d.deviceClass["discoveryParamTypes"] : null + model: root.deviceClass ? root.deviceClass["discoveryParamTypes"] : null Loader { Layout.fillWidth: true sourceComponent: searchStringEntryComponent @@ -266,7 +128,7 @@ Page { Layout.fillWidth: true text: "Next" onClicked: { - var paramTypes = d.deviceClass["discoveryParamTypes"]; + var paramTypes = root.deviceClass["discoveryParamTypes"]; d.discoveryParams = []; for (var i = 0; i < paramTypes.count; i++) { var param = {}; @@ -274,8 +136,8 @@ Page { param["value"] = paramRepeater.itemAt(i).value d.discoveryParams.push(param); } - discovery.discoverDevices(d.deviceClass.id, d.discoveryParams) - internalPageStack.push(discoveryPage, {deviceClass: d.deviceClass}) + discovery.discoverDevices(root.deviceClass.id, d.discoveryParams) + internalPageStack.push(discoveryPage, {deviceClass: root.deviceClass}) } } } @@ -313,7 +175,13 @@ Page { ListView { Layout.fillWidth: true Layout.fillHeight: true - model: discovery + model: DeviceDiscoveryProxy { + id: discoveryProxy + deviceDiscovery: discovery + showAlreadyAdded: root.device !== null + showNew: root.device === null + filterDeviceId: root.device !== null ? root.device.id : null + } delegate: MeaListItemDelegate { width: parent.width height: app.delegateHeight @@ -321,8 +189,29 @@ Page { subText: model.description iconName: app.interfacesToIcon(discoveryView.deviceClass.interfaces) onClicked: { - d.deviceDescriptor = discovery.get(index); + d.deviceDescriptor = discoveryProxy.get(index); d.deviceName = model.name; + + // Overriding params for reconfiguring discovered devices not supported by core yet + // So if we are reconfiguring and discovering, go straight to end + + if (root.device && d.deviceDescriptor) { + busyOverlay.shown = true; + + switch (root.deviceClass.setupMethod) { + case 0: + engine.deviceManager.reconfigureDiscoveredDevice(root.device.id, d.deviceDescriptor.id); + break; + case 1: + case 2: + case 3: + engine.deviceManager.pairDevice(root.deviceClass.id, d.deviceDescriptor.id, root.device.name); + break; + } + + return; + } + internalPageStack.push(paramsPage) } } @@ -332,15 +221,15 @@ Page { Layout.fillWidth: true Layout.leftMargin: app.margins; Layout.rightMargin: app.margins text: qsTr("Search again") - onClicked: discovery.discoverDevices(d.deviceClass.id, d.discoveryParams) - visible: !discovery.busy && discovery.count > 0 + onClicked: discovery.discoverDevices(root.deviceClass.id, d.discoveryParams) + visible: !discovery.busy && discoveryProxy.count > 0 } Button { id: manualAddButton Layout.fillWidth: true Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.bottomMargin: app.margins - visible: d.deviceClass.createMethods.indexOf("CreateMethodUser") >= 0 + visible: root.deviceClass.createMethods.indexOf("CreateMethodUser") >= 0 text: qsTr("Add thing manually") onClicked: internalPageStack.push(paramsPage) } @@ -367,7 +256,7 @@ Page { ColumnLayout { anchors.centerIn: parent width: parent.width - app.margins * 2 - visible: !discovery.busy && discovery.count == 0 + visible: !discovery.busy && discoveryProxy.count === 0 spacing: app.margins * 2 Label { text: qsTr("Too bad...") @@ -389,7 +278,7 @@ Page { text: qsTr("Try again!") Layout.fillWidth: true onClicked: { - discovery.discoverDevices(d.deviceClass.id, d.discoveryParams) + discovery.discoverDevices(root.deviceClass.id, d.discoveryParams) } } } @@ -409,35 +298,38 @@ Page { id: paramsColumn width: parent.width - Label { - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - Layout.topMargin: app.margins - Layout.fillWidth: true - text: qsTr("Name the thing:") - } - TextField { - id: nameTextField - text: d.deviceName ? d.deviceName : "" - Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - } + ColumnLayout { + visible: root.device === null + Label { + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + Layout.topMargin: app.margins + Layout.fillWidth: true + text: qsTr("Name the thing:") + } + TextField { + id: nameTextField + text: d.deviceName ? d.deviceName : "" + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + } - ThinDivider { - visible: paramRepeater.count > 0 + ThinDivider { + visible: paramRepeater.count > 0 + } } Repeater { id: paramRepeater - model: engine.jsonRpcClient.ensureServerVersion("1.12") || d.deviceDescriptor == null ? d.deviceClass.paramTypes : null + model: engine.jsonRpcClient.ensureServerVersion("1.12") || d.deviceDescriptor == null ? root.deviceClass.paramTypes : null delegate: ParamDelegate { // Layout.preferredHeight: 60 Layout.fillWidth: true - paramType: d.deviceClass.paramTypes.get(index) + paramType: root.deviceClass.paramTypes.get(index) value: d.deviceDescriptor && d.deviceDescriptor.params.getParam(paramType.id) ? d.deviceDescriptor.params.getParam(paramType.id).value : - d.deviceClass.paramTypes.get(index).defaultValue + root.deviceClass.paramTypes.get(index).defaultValue } } @@ -448,7 +340,7 @@ Page { text: "OK" onClicked: { - print("setupMethod", d.deviceClass.setupMethod) + print("setupMethod", root.deviceClass.setupMethod) var params = [] for (var i = 0; i < paramRepeater.count; i++) { @@ -459,20 +351,26 @@ Page { params.push(param) } - switch (d.deviceClass.setupMethod) { + switch (root.deviceClass.setupMethod) { case 0: - if (d.deviceDescriptor) { - engine.deviceManager.addDiscoveredDevice(d.deviceClass.id, d.deviceDescriptor.id, nameTextField.text, params); + if (root.device !== null) { + if (d.deviceDescriptor) { + engine.deviceManager.reconfigureDiscoveredDevice(root.device.id, d.deviceDescriptor.id); + } else { + engine.deviceManager.reconfigureDevice(root.device.id, params); + } } else { - - engine.deviceManager.addDevice(d.deviceClass.id, nameTextField.text, params); + if (d.deviceDescriptor) { + engine.deviceManager.addDiscoveredDevice(root.deviceClass.id, d.deviceDescriptor.id, nameTextField.text, params); + } else { + engine.deviceManager.addDevice(root.deviceClass.id, nameTextField.text, params); + } } - break; case 1: case 2: case 3: - engine.deviceManager.pairDevice(d.deviceClass.id, d.deviceDescriptor.id, nameTextField.text); + engine.deviceManager.pairDevice(root.deviceClass.id, d.deviceDescriptor.id, nameTextField.text); break; } @@ -539,7 +437,7 @@ Page { property bool success property string deviceId - readonly property var device: engine.deviceManager.devices.getDevice(deviceId) + readonly property var device: root.device ? root.device : engine.deviceManager.devices.getDevice(deviceId) ColumnLayout { width: parent.width - app.margins * 2 @@ -549,7 +447,7 @@ Page { Layout.fillWidth: true wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter - text: resultsView.success ? qsTr("Thing added!") : qsTr("Uh oh") + text: resultsView.success ? root.device ? qsTr("Thing reconfigured!") : qsTr("Thing added!") : qsTr("Uh oh") font.pixelSize: app.largeFont color: app.accentColor } @@ -563,7 +461,9 @@ Page { Button { Layout.fillWidth: true text: qsTr("Ok") - onClicked: pageStack.pop(); + onClicked: { + root.done(); + } } } }