diff --git a/libnymea-app/jsonrpc/jsonrpcclient.cpp b/libnymea-app/jsonrpc/jsonrpcclient.cpp index aa3ac5df..c491ad0a 100644 --- a/libnymea-app/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app/jsonrpc/jsonrpcclient.cpp @@ -142,7 +142,14 @@ NymeaConnection::ConnectionStatus JsonRpcClient::connectionStatus() const void JsonRpcClient::connectToHost(NymeaHost *host, Connection *connection) { + if (m_connection->currentHost()) { + disconnect(m_connection->currentHost(), &NymeaHost::nameChanged, this, &JsonRpcClient::serverNameChanged); + } + m_connection->connectToHost(host, connection); + + connect(host, &NymeaHost::nameChanged, this, &JsonRpcClient::serverNameChanged); + emit serverNameChanged(); } void JsonRpcClient::disconnectFromHost() @@ -339,6 +346,11 @@ QString JsonRpcClient::serverUuid() const return m_serverUuid; } +QString JsonRpcClient::serverName() const +{ + return m_connection->currentHost() ? m_connection->currentHost()->name() : ""; +} + QString JsonRpcClient::serverQtVersion() { if (!m_serverQtVersion.isEmpty()) { diff --git a/libnymea-app/jsonrpc/jsonrpcclient.h b/libnymea-app/jsonrpc/jsonrpcclient.h index 557eae0d..55748341 100644 --- a/libnymea-app/jsonrpc/jsonrpcclient.h +++ b/libnymea-app/jsonrpc/jsonrpcclient.h @@ -58,6 +58,7 @@ class JsonRpcClient : public QObject Q_PROPERTY(QString serverVersion READ serverVersion NOTIFY handshakeReceived) Q_PROPERTY(QString jsonRpcVersion READ jsonRpcVersion NOTIFY handshakeReceived) Q_PROPERTY(QString serverUuid READ serverUuid NOTIFY handshakeReceived) + Q_PROPERTY(QString serverName READ serverName NOTIFY serverNameChanged) Q_PROPERTY(QString serverQtVersion READ serverQtVersion NOTIFY serverQtVersionChanged) Q_PROPERTY(QString serverQtBuildVersion READ serverQtBuildVersion NOTIFY serverQtVersionChanged) Q_PROPERTY(QVariantMap certificateIssuerInfo READ certificateIssuerInfo NOTIFY currentConnectionChanged) @@ -97,6 +98,7 @@ public: QString serverVersion() const; QString jsonRpcVersion() const; QString serverUuid() const; + QString serverName() const; QString serverQtVersion(); QString serverQtBuildVersion(); QVariantMap experiences() const; @@ -137,6 +139,7 @@ signals: void createUserFailed(const QString &error); void cloudConnectionStateChanged(); void serverQtVersionChanged(); + void serverNameChanged(); void responseReceived(const int &commandId, const QVariantMap &response); diff --git a/nymea-app/configuredhostsmodel.cpp b/nymea-app/configuredhostsmodel.cpp new file mode 100644 index 00000000..cc562150 --- /dev/null +++ b/nymea-app/configuredhostsmodel.cpp @@ -0,0 +1,263 @@ +#include "configuredhostsmodel.h" + +#include + +#include +Q_DECLARE_LOGGING_CATEGORY(dcApplication) + +ConfiguredHostsModel::ConfiguredHostsModel(QObject *parent) : QAbstractListModel(parent) +{ + QSettings settings; + settings.beginGroup("ConfiguredHosts"); + foreach (const QString &childGroup, settings.childGroups()) { + settings.beginGroup(childGroup); + QUuid uuid = settings.value("uuid").toUuid(); + QString cachedName = settings.value("cachedName").toString(); + ConfiguredHost *host = new ConfiguredHost(uuid, this); + host->setName(cachedName); + addHost(host); + settings.endGroup(); + m_currentIndex = settings.value("currentIndex", 0).toInt(); + } + settings.endGroup(); + + // If there aren't any in the config, try migrating settings from old tab model + if (m_list.isEmpty() && settings.contains("tabCount")) { + qCInfo(dcApplication()) << "Migrating tab settings to mainmenumodel"; + int tabCount = settings.value("tabCount", 0).toInt(); + qCDebug(dcApplication()) << "Tab count:" << tabCount; + + for (int i = 0; i < tabCount; i++) { + settings.beginGroup(QString("tabSettings%1").arg(i)); + QUuid uuid = settings.value("lastConnectedHost").toUuid(); + ConfiguredHost *host = new ConfiguredHost(uuid, this); + addHost(host); + settings.endGroup(); + } + + settings.remove("tabCount"); + } + + // There must be always 1 at least + if (m_list.isEmpty()) { + createHost(); + } +} + +int ConfiguredHostsModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_list.count(); +} + +QVariant ConfiguredHostsModel::data(const QModelIndex &index, int role) const +{ + switch (role) { + case RoleUuid: + return m_list.at(index.row())->uuid(); + case RoleName: + if (!m_list.at(index.row())->name().isEmpty()) { + return m_list.at(index.row())->name(); + } + return m_list.at(index.row())->uuid(); + } + return QVariant(); +} + +QHash ConfiguredHostsModel::roleNames() const +{ + QHash roles; + roles.insert(RoleUuid, "uuid"); + roles.insert(RoleName, "name"); + return roles; +} + +ConfiguredHost *ConfiguredHostsModel::get(int index) const +{ + if (index < 0 || index >= m_list.count()) { + return nullptr; + } + return m_list.at(index); +} + +int ConfiguredHostsModel::currentIndex() const +{ + return m_currentIndex; +} + +void ConfiguredHostsModel::setCurrentIndex(int currentIndex) +{ + if (m_currentIndex != currentIndex) { + m_currentIndex = currentIndex; + emit currentIndexChanged(); + + QSettings settings; + settings.beginGroup("ConfiguredHosts"); + settings.setValue("currentIndex", currentIndex); + settings.endGroup(); + } +} + +ConfiguredHost *ConfiguredHostsModel::createHost() +{ + ConfiguredHost *host = new ConfiguredHost(); + addHost(host); + return host; +} + +void ConfiguredHostsModel::removeHost(int index) +{ + if (index < 0 || index >= m_list.count()) { + qCWarning(dcApplication()) << "Cannot remove connection at index" << index; + return; + } + beginRemoveRows(QModelIndex(), index, index); + m_list.takeAt(index)->deleteLater(); + saveToDisk(); + endRemoveRows(); + emit countChanged(); + + if (m_list.isEmpty()) { + createHost(); + } +} + +int ConfiguredHostsModel::indexOf(ConfiguredHost *host) const +{ + return m_list.indexOf(host); +} + +void ConfiguredHostsModel::addHost(ConfiguredHost *host) +{ + host->setParent(this); + beginInsertRows(QModelIndex(), m_list.count(), m_list.count()); + connect(host->engine()->jsonRpcClient(), &JsonRpcClient::currentHostChanged, this, [=]{ + if (host->engine()->jsonRpcClient()->currentHost()) { + host->setUuid(host->engine()->jsonRpcClient()->currentHost()->uuid()); + } else { + host->setUuid(QUuid()); + host->setName(QString()); + } + saveToDisk(); + }); + connect(host->engine()->jsonRpcClient(), &JsonRpcClient::serverNameChanged, this, [=]{ + host->setName(host->engine()->jsonRpcClient()->serverName()); + saveToDisk(); + }); + connect(host, &ConfiguredHost::nameChanged, this, [=](){ + QModelIndex idx = index(m_list.indexOf(host)); + emit dataChanged(idx, idx, {RoleName}); + }); + m_list.append(host); + endInsertRows(); + emit countChanged(); +} + +void ConfiguredHostsModel::saveToDisk() +{ + QSettings settings; + settings.beginGroup("ConfiguredHosts"); + settings.remove(""); + for (int i = 0; i < m_list.count(); i++) { + settings.beginGroup(QString::number(i)); + settings.setValue("uuid", m_list.at(i)->uuid()); + settings.setValue("cachedName", m_list.at(i)->name()); + settings.endGroup(); + } + settings.endGroup(); +} + +ConfiguredHost::ConfiguredHost(const QUuid &uuid, QObject *parent): + QObject(parent), + m_uuid(uuid), + m_engine(new Engine(this)) +{ + +} + +QUuid ConfiguredHost::uuid() const +{ + return m_uuid; +} + +void ConfiguredHost::setUuid(const QUuid &uuid) +{ + if (m_uuid != uuid) { + m_uuid = uuid; + emit uuidChanged(); + } +} + +Engine *ConfiguredHost::engine() const +{ + return m_engine; +} + +QString ConfiguredHost::name() const +{ + return m_name; +} + +void ConfiguredHost::setName(const QString &name) +{ + if (m_name != name) { + m_name = name; + emit nameChanged(); + } +} + +ConfiguredHostsProxyModel::ConfiguredHostsProxyModel(QObject *parent): + QSortFilterProxyModel(parent) +{ + +} + +ConfiguredHostsModel *ConfiguredHostsProxyModel::model() const +{ + return m_model; +} + +void ConfiguredHostsProxyModel::setModel(ConfiguredHostsModel *model) +{ + if (m_model != model) { + m_model = model; + emit modelChanged(); + + setSourceModel(model); + sort(0); + } +} + +QUuid ConfiguredHostsProxyModel::currentHost() const +{ + return m_currentHost; +} + +void ConfiguredHostsProxyModel::setCurrentHost(const QUuid ¤tHost) +{ + if (m_currentHost != currentHost) { + m_currentHost = currentHost; + emit currentHostChanged(); + + invalidate(); + } +} + +ConfiguredHost *ConfiguredHostsProxyModel::get(int index) const +{ + return m_model->get(mapToSource(this->index(index, 0)).row()); +} + +bool ConfiguredHostsProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + ConfiguredHost *left = m_model->get(source_left.row()); + ConfiguredHost *right = m_model->get(source_right.row()); + + if (left->uuid() == m_currentHost) { + return true; + } + if (right->uuid() == m_currentHost) { + return false; + } + return source_left.row() < source_right.row(); +} diff --git a/nymea-app/configuredhostsmodel.h b/nymea-app/configuredhostsmodel.h new file mode 100644 index 00000000..64890775 --- /dev/null +++ b/nymea-app/configuredhostsmodel.h @@ -0,0 +1,107 @@ +#ifndef CONFIGUREDHOSTSMODEL_H +#define CONFIGUREDHOSTSMODEL_H + +#include +#include +#include + +#include "engine.h" + +class ConfiguredHost: public QObject +{ + Q_OBJECT + Q_PROPERTY(QUuid uuid READ uuid WRITE setUuid NOTIFY uuidChanged) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(Engine* engine READ engine CONSTANT) + +public: + ConfiguredHost(const QUuid &uuid = QUuid(), QObject *parent = nullptr); + + QUuid uuid() const; + void setUuid(const QUuid &uuid); + + QString name() const; + void setName(const QString &name); + + Engine* engine() const; + +signals: + void uuidChanged(); + void nameChanged(); + +private: + QUuid m_uuid; + Engine *m_engine = nullptr; + QString m_name; +}; + +class ConfiguredHostsModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) +public: + enum Roles { + RoleUuid, + RoleEngine, + RoleName, + }; + Q_ENUM(Roles) + explicit ConfiguredHostsModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + int currentIndex() const; + void setCurrentIndex(int currentIndex); + + Q_INVOKABLE int indexOf(ConfiguredHost *host) const; + Q_INVOKABLE ConfiguredHost* get(int index) const; + Q_INVOKABLE ConfiguredHost* createHost(); + Q_INVOKABLE void removeHost(int index); + +signals: + void countChanged(); + void currentIndexChanged(); + +private: + void addHost(ConfiguredHost *host); + + void saveToDisk(); + +private: + QList m_list; + int m_currentIndex = 0; +}; + +class ConfiguredHostsProxyModel: public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(ConfiguredHostsModel* model READ model WRITE setModel NOTIFY modelChanged) + + Q_PROPERTY(QUuid currentHost READ currentHost WRITE setCurrentHost NOTIFY currentHostChanged) +public: + ConfiguredHostsProxyModel(QObject *parent = nullptr); + + ConfiguredHostsModel* model() const; + void setModel(ConfiguredHostsModel *model); + + QUuid currentHost() const; + void setCurrentHost(const QUuid ¤tHost); + + Q_INVOKABLE ConfiguredHost* get(int index) const; + +signals: + void modelChanged(); + void currentHostChanged(); + +protected: + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; + +private: + ConfiguredHostsModel *m_model = nullptr; + QUuid m_currentHost; +}; + +#endif // CONFIGUREDHOSTSMODEL_H diff --git a/nymea-app/main.cpp b/nymea-app/main.cpp index 70025ba2..a48121e1 100644 --- a/nymea-app/main.cpp +++ b/nymea-app/main.cpp @@ -49,6 +49,7 @@ #include "dashboard/dashboardmodel.h" #include "dashboard/dashboarditem.h" #include "mouseobserver.h" +#include "configuredhostsmodel.h" #include "../config.h" #include "logging.h" @@ -168,6 +169,10 @@ int main(int argc, char *argv[]) qmlRegisterType("Nymea", 1, 0, "MouseObserver"); + qmlRegisterType("Nymea", 1, 0, "ConfiguredHostsModel"); + qmlRegisterType("Nymea", 1, 0, "ConfiguredHostsProxyModel"); + qmlRegisterUncreatableType("Nymea", 1, 0, "ConfiguredHost", "Get them from ConfiguredHostsModel"); + #ifdef OVERLAY_QMLTYPES registerOverlayTypes("Nymea", 1, 0); #endif diff --git a/nymea-app/mainmenumodel.cpp b/nymea-app/mainmenumodel.cpp deleted file mode 100644 index d18cc578..00000000 --- a/nymea-app/mainmenumodel.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "mainmenumodel.h" - -MainMenuModel::MainMenuModel(QObject *parent) : QAbstractListModel(parent) -{ - -} - -int MainMenuModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent) - return m_list.count(); -} diff --git a/nymea-app/mainmenumodel.h b/nymea-app/mainmenumodel.h deleted file mode 100644 index f90cd3de..00000000 --- a/nymea-app/mainmenumodel.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef MAINMENUMODEL_H -#define MAINMENUMODEL_H - -#include - -class MainMenuItem: public QObject -{ - Q_OBJECT - -}; - -class MainMenuModel : public QAbstractListModel -{ - Q_OBJECT -public: - explicit MainMenuModel(QObject *parent = nullptr); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - -private: - QList m_list; -}; - -#endif // MAINMENUMODEL_H diff --git a/nymea-app/nymea-app.pro b/nymea-app/nymea-app.pro index 0bbb7c8e..372bf025 100644 --- a/nymea-app/nymea-app.pro +++ b/nymea-app/nymea-app.pro @@ -19,9 +19,9 @@ PRE_TARGETDEPS += ../libnymea-app linux:!android:PRE_TARGETDEPS += $$top_builddir/libnymea-app/libnymea-app.a HEADERS += \ + configuredhostsmodel.h \ dashboard/dashboarditem.h \ dashboard/dashboardmodel.h \ - mainmenumodel.h \ mouseobserver.h \ nfchelper.h \ nfcthingactionwriter.h \ @@ -34,9 +34,9 @@ HEADERS += \ ruletemplates/messages.h SOURCES += main.cpp \ + configuredhostsmodel.cpp \ dashboard/dashboarditem.cpp \ dashboard/dashboardmodel.cpp \ - mainmenumodel.cpp \ mouseobserver.cpp \ nfchelper.cpp \ nfcthingactionwriter.cpp \ diff --git a/nymea-app/ui/MainMenu.qml b/nymea-app/ui/MainMenu.qml index dc026fc0..e45953e9 100644 --- a/nymea-app/ui/MainMenu.qml +++ b/nymea-app/ui/MainMenu.qml @@ -7,7 +7,8 @@ import Nymea 1.0 Drawer { id: root - property Engine currentEngine: null + property ConfiguredHostsModel configuredHosts: null + readonly property Engine currentEngine: configuredHosts.get(configuredHosts.currentIndex).engine signal openThingSettings(); signal openMagicSettings(); @@ -17,12 +18,12 @@ Drawer { signal startWirelessSetup(); signal startManualConnection(); - signal startDemoMode(); background: Rectangle { color: Style.backgroundColor } + onClosed: topSectionLayout.configureConnections = false; ColumnLayout { anchors.fill: parent @@ -30,38 +31,88 @@ Drawer { Rectangle { Layout.fillWidth: true - Layout.preferredHeight: topSectionLayout.implicitHeight + app.margins * 2 + Layout.preferredHeight: topSectionLayout.implicitHeight color: Qt.tint(Style.backgroundColor, Qt.rgba(Style.foregroundColor.r, Style.foregroundColor.g, Style.foregroundColor.b, 0.05)) + ColumnLayout { id: topSectionLayout - anchors { left: parent.left; top: parent.top; right: parent.right; margins: app.margins } - spacing: app.margins + anchors { left: parent.left; top: parent.top; right: parent.right } + spacing: 0 - Image { - Layout.preferredHeight: Style.hugeIconSize - sourceSize.height: Style.hugeIconSize - source: "qrc:/styles/%1/logo-wide.svg".arg(styleController.currentStyle) - } + property bool configureConnections: false RowLayout { - visible: root.currentEngine && root.currentEngine.jsonRpcClient.currentHost - ColumnLayout { - Label { - Layout.fillWidth: true - text: root.currentEngine && root.currentEngine.jsonRpcClient.currentHost ? root.currentEngine.jsonRpcClient.currentHost.name : Configuration.systemName - } - Label { - Layout.fillWidth: true - text: root.currentEngine.jsonRpcClient.currentConnection ? root.currentEngine.jsonRpcClient.currentConnection.url : "" - font.pixelSize: app.extraSmallFont - enabled: false - } + Layout.margins: Style.margins + Image { + Layout.preferredHeight: Style.hugeIconSize + sourceSize.height: Style.hugeIconSize + Layout.fillWidth: true + fillMode: Image.PreserveAspectFit + horizontalAlignment: Image.AlignLeft + source: "qrc:/styles/%1/logo-wide.svg".arg(styleController.currentStyle) } ProgressButton { + imageSource: "/ui/images/configure.svg" longpressEnabled: false - imageSource: "../images/close.svg" + Layout.alignment: Qt.AlignBottom + color: topSectionLayout.configureConnections ? Style.accentColor : Style.iconColor onClicked: { - root.currentEngine.jsonRpcClient.disconnectFromHost(); + topSectionLayout.configureConnections = !topSectionLayout.configureConnections + } + } + } + + + Repeater { + model: root.configuredHosts + delegate: NymeaItemDelegate { + + readonly property ConfiguredHost configuredHost: root.configuredHosts.get(index) + + Layout.fillWidth: true + text: model.name.length > 0 ? model.name : qsTr("New connection") + subText: configuredHost.engine.jsonRpcClient.currentConnection ? configuredHost.engine.jsonRpcClient.currentConnection.url : "" + prominentSubText: false + progressive: false + additionalItem: RowLayout { + anchors.verticalCenter: parent.verticalCenter + Rectangle { + height: Style.smallIconSize + width: height + radius: height / 2 + color: Style.accentColor + Layout.alignment: Qt.AlignVCenter + visible: index === configuredHostsModel.currentIndex && !topSectionLayout.configureConnections + } + ProgressButton { + imageSource: "/ui/images/close.svg" + visible: topSectionLayout.configureConnections + longpressEnabled: false + onClicked: { + configuredHostsModel.removeHost(index) + } + } + } + onClicked: { + configuredHostsModel.currentIndex = index + root.close() + } + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: topSectionLayout.configureConnections ? childrenRect.height : 0 + Behavior on Layout.preferredHeight { NumberAnimation { duration: Style.animationDuration; easing.type: Easing.InOutQuad }} + clip: true + NymeaItemDelegate { + width: parent.width + text: qsTr("Set up another...") + iconName: "add" + progressive: false + onClicked: { + var host = configuredHostsModel.createHost() + configuredHostsModel.currentIndex = configuredHosts.indexOf(host) root.close(); } } @@ -83,40 +134,6 @@ Drawer { width: parent.width spacing: 0 - NymeaItemDelegate { - Layout.fillWidth: true - text: qsTr("Wireless setup") - iconName: "../images/connections/bluetooth.svg" - progressive: false - visible: root.currentEngine && root.currentEngine.jsonRpcClient.currentHost == null - onClicked: { - root.startWirelessSetup(); - root.close(); - } - } - NymeaItemDelegate { - Layout.fillWidth: true - text: qsTr("Manual connection") - iconName: "../images/connections/network-vpn.svg" - progressive: false - visible: root.currentEngine && root.currentEngine.jsonRpcClient.currentHost == null - onClicked: { - root.startManualConnection(); - root.close(); - } - } - NymeaItemDelegate { - Layout.fillWidth: true - text: qsTr("Demo mode") - iconName: "../images/private-browsing.svg" - progressive: false - visible: root.currentEngine && root.currentEngine.jsonRpcClient.currentHost == null - onClicked: { - root.startDemoMode(); - root.close(); - } - } - NymeaItemDelegate { Layout.fillWidth: true text: qsTr("Configure things") diff --git a/nymea-app/ui/Nymea.qml b/nymea-app/ui/Nymea.qml index d74231fd..14425051 100644 --- a/nymea-app/ui/Nymea.qml +++ b/nymea-app/ui/Nymea.qml @@ -81,7 +81,6 @@ ApplicationWindow { property bool showHiddenOptions: false property string cloudEnvironment: "Community" property bool showConnectionTabs: false - property int tabCount: 1 // FIXME: This shouldn't be needed... we should probably only use the system locale and not even provide a setting // However, the topic is more complex, and in the long run we'd probably want to allow the user selecting the // desired unit for particular interfaces/things/views. See https://github.com/nymea/nymea/issues/386 @@ -108,13 +107,17 @@ ApplicationWindow { value: "cloudEnvironment" in app ? app.cloudEnvironment : settings.cloudEnvironment } + ConfiguredHostsModel { + id: configuredHostsModel + } + property alias mainMenu: m MainMenu { id: m height: app.height width: Math.min(300, app.width) // z: 1000 - currentEngine: rootItem.currentEngine + configuredHosts: configuredHostsModel onOpenThingSettings: rootItem.openThingSettings(); onOpenMagicSettings: rootItem.openMagicSettings(); onOpenAppSettings: rootItem.openAppSettings(); @@ -122,7 +125,6 @@ ApplicationWindow { onConfigureMainView: rootItem.configureMainView(); onStartManualConnection: rootItem.startManualConnection(); onStartWirelessSetup: rootItem.startWirelessSetup(); - onStartDemoMode: rootItem.startDemoMode(); } RootItem { diff --git a/nymea-app/ui/RootItem.qml b/nymea-app/ui/RootItem.qml index 15e797fe..d6e27334 100644 --- a/nymea-app/ui/RootItem.qml +++ b/nymea-app/ui/RootItem.qml @@ -40,8 +40,6 @@ import "connection" Item { id: root - readonly property Engine currentEngine: swipeView.currentItem ? swipeView.currentItem.engine : null - function handleAndroidBackButton() { return swipeView.currentItem.handleAndroidBackButton() } @@ -77,53 +75,6 @@ Item { swipeView.currentItem.pageStack.currentItem.configureViews() } - function startManualConnection() { - d.pushSettingsPage("connection/ManualConnectPage.qml") - } - function startWirelessSetup() { - d.pushSettingsPage("connection/wifisetup/BluetoothDiscoveryPage.qml"); - } - function startDemoMode() { - var host = nymeaDiscovery.nymeaHosts.createWanHost("Demo server", "nymea://nymea.nymea.io:2222") - root.currentEngine.jsonRpcClient.connectToHost(host) - } - - ListModel { - id: tabModel - - Component.onCompleted: { - for (var i = 0; i < settings.tabCount; i++) { - tabModel.append({}) - } - } - - function addTab() { - tabModel.append({}) - settings.tabCount++; - swipeView.currentIndex = settings.tabCount - 1 - tabbar.currentIndex = swipeView.currentIndex - } - function removeTab(index) { - if (swipeView.currentIndex === index) { - if (swipeView.currentIndex > 0) { - swipeView.currentIndex--; - } else { - swipeView.currentIndex++; - } - } - - remove(index); - settings.tabCount--; - tabbar.currentIndex = swipeView.currentIndex - orphanedSettings.lastConnectedHost = "" - } - } - Settings { - id: orphanedSettings - category: "tabSettings" + tabModel.count - property string lastConnectedHost - } - ColumnLayout { anchors.fill: parent spacing: 0 @@ -133,29 +84,26 @@ Item { Layout.fillHeight: true Layout.fillWidth: true interactive: false + currentIndex: configuredHostsModel.currentIndex Repeater { id: mainRepeater - model: tabModel + model: configuredHostsModel delegate: Item { height: swipeView.height width: swipeView.width clip: true + readonly property ConfiguredHost configuredHost: configuredHostsModel.get(index) + property var tabSettings: Settings { category: "tabSettings" + index - property string lastConnectedHost property int currentMainViewIndex: -1 } - Engine { - id: engineObject - } - readonly property Engine engine: engineObject - readonly property Engine _engine: engineObject // In case a child cannot use "engine" - property int connectionTabIndex: index -// onConnectionTabIndexChanged: tabSettings.lastConnectedHost = engine.jsonRpcClient.url + readonly property Engine engine: configuredHost.engine + readonly property Engine _engine: configuredHost.engine // In case a child cannot use "engine" Binding { target: nymeaDiscovery @@ -173,9 +121,9 @@ Item { Component.onCompleted: { setupPushNotifications(); - if (tabSettings.lastConnectedHost.length > 0) { - print("Last connected host was", tabSettings.lastConnectedHost) - var cachedHost = nymeaDiscovery.nymeaHosts.find(tabSettings.lastConnectedHost); + if (configuredHost.uuid.toString() !== "{00000000-0000-0000-0000-000000000000}") { + print("Configured host id is", configuredHost.uuid) + var cachedHost = nymeaDiscovery.nymeaHosts.find(configuredHost.uuid); if (cachedHost) { engine.jsonRpcClient.connectToHost(cachedHost) return; @@ -201,7 +149,6 @@ Item { pageStack.clear() if (!engine.jsonRpcClient.currentHost) { print("pushing ConnectPage") - tabSettings.lastConnectedHost = "" pageStack.push(Configuration.connectionWizard) PlatformHelper.hideSplashScreen(); return; @@ -213,7 +160,7 @@ Item { print("opening push button auth") var page = pageStack.push(Qt.resolvedUrl("PushButtonAuthPage.qml")) page.backPressed.connect(function() { - tabSettings.lastConnectedHost = ""; +// tabSettings.lastConnectedHost = ""; engine.jsonRpcClient.disconnectFromHost(); init(); }) @@ -222,7 +169,7 @@ Item { if (engine.jsonRpcClient.initialSetupRequired) { var page = pageStack.push(Qt.resolvedUrl("connection/SetupWizard.qml")); page.backPressed.connect(function() { - tabSettings.lastConnectedHost = ""; +// tabSettings.lastConnectedHost = ""; engine.jsonRpcClient.disconnectFromHost() init(); }) @@ -231,7 +178,7 @@ Item { var page = pageStack.push(Qt.resolvedUrl("connection/LoginPage.qml")); page.backPressed.connect(function() { - tabSettings.lastConnectedHost = ""; +// tabSettings.lastConnectedHost = ""; engine.jsonRpcClient.disconnectFromHost() init(); }) @@ -240,6 +187,7 @@ Item { } if (engine.jsonRpcClient.connected) { + print("Connected to", engine.jsonRpcClient.currentHost.uuid, engine.jsonRpcClient.currentHost.name) pageStack.push(Qt.resolvedUrl("MainPage.qml")) return; } @@ -350,7 +298,8 @@ Item { print("json client connected changed", engine.jsonRpcClient.connected) if (engine.jsonRpcClient.connected) { nymeaDiscovery.cacheHost(engine.jsonRpcClient.currentHost) - tabSettings.lastConnectedHost = engine.jsonRpcClient.serverUuid + configuredHost.uuid = engine.jsonRpcClient.serverUuid +// tabSettings.lastConnectedHost = engine.jsonRpcClient.serverUuid } init(); } @@ -369,14 +318,14 @@ Item { popup.actualVersion = actualVersion; popup.minVersion = minVersion; popup.open() - tabSettings.lastConnectedHost = "" +// tabSettings.lastConnectedHost = "" } onInvalidMaximumVersion: { var popup = invalidVersionComponent.createObject(app.contentItem); popup.actualVersion = actualVersion; popup.maxVersion = maxVersion; popup.open() - tabSettings.lastConnectedHost = "" +// tabSettings.lastConnectedHost = "" } } @@ -477,16 +426,16 @@ Item { Layout.fillWidth: true Material.elevation: 2 position: TabBar.Footer + currentIndex: configuredHostsModel.currentIndex Repeater { - model: tabModel.count + model: configuredHostsModel delegate: TabButton { id: hostTabButton - property var engine: mainRepeater.itemAt(index)._engine - property string serverName: engine.nymeaConfiguration.serverName + readonly property ConfiguredHost configuredHost: configuredHostsModel.get(index) Material.elevation: index - width: Math.max(150, tabbar.width / tabModel.count) + width: Math.max(150, tabbar.width / configuredHostsModel.count) Rectangle { anchors.fill: parent @@ -494,49 +443,19 @@ Item { opacity: 0.06 } - contentItem: RowLayout { - Label { - Layout.fillWidth: true - text: hostTabButton.serverName !== "" ? hostTabButton.serverName : qsTr("New connection") - elide: Text.ElideRight - } - ColorIcon { - Layout.fillHeight: true - Layout.preferredWidth: height - visible: tabModel.count > 1 - name: "../images/close.svg" - MouseArea { - anchors.fill: parent - onClicked: tabModel.removeTab(index) - } - } + contentItem: Label { + Layout.fillWidth: true + text: hostTabButton.configuredHost.name !== "" ? hostTabButton.configuredHost.name : qsTr("New connection") + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight } onClicked: { - swipeView.currentIndex = index + configuredHostsModel.currentIndex = index } } } } - - Pane { - Layout.preferredHeight: tabbar.height - Layout.preferredWidth: height - Material.elevation: 2 - padding: 0 - - TabButton { - anchors.fill: parent - contentItem: ColorIcon { - height: parent.height - width: parent.width - name: "../images/tab-new.svg" - } - onClicked: { - tabModel.addTab() - } - } - } } } } diff --git a/nymea-app/ui/StyleBase.qml b/nymea-app/ui/StyleBase.qml index cf165efb..da596a0c 100644 --- a/nymea-app/ui/StyleBase.qml +++ b/nymea-app/ui/StyleBase.qml @@ -98,4 +98,8 @@ Item { readonly property color red: "#952727" readonly property color white: "white" + + readonly property int fastAnimationDuration: 100 + readonly property int animationDuration: 150 + readonly property int slowAnimationDuration: 300 } diff --git a/nymea-app/ui/components/ProgressButton.qml b/nymea-app/ui/components/ProgressButton.qml index 0bff8f1b..6da0c092 100644 --- a/nymea-app/ui/components/ProgressButton.qml +++ b/nymea-app/ui/components/ProgressButton.qml @@ -65,9 +65,7 @@ Item { buttonDelegate.longpressed = false } onReleased: { - print("onReleased!") if (!containsMouse) { - print("cancelled") buttonDelegate.longpressed = false; return; } @@ -80,7 +78,6 @@ Item { root.clicked(); } buttonDelegate.longpressed = false - print("released end") } } diff --git a/nymea-app/ui/components/WizardPageBase.qml b/nymea-app/ui/components/WizardPageBase.qml index ffff1aaf..24e8601e 100644 --- a/nymea-app/ui/components/WizardPageBase.qml +++ b/nymea-app/ui/components/WizardPageBase.qml @@ -25,15 +25,28 @@ Page { anchors.fill: parent spacing: Style.margins - Label { - id: titleLabel - Layout.fillWidth: true - Layout.margins: Style.margins - text: root.title - font: Style.largeFont - horizontalAlignment: Text.AlignHCenter + RowLayout { + ProgressButton { + imageSource: "/ui/images/navigation-menu.svg" + longpressEnabled: false + onClicked: mainMenu.open() + } + + Label { + id: titleLabel + Layout.fillWidth: true + Layout.margins: Style.margins + text: root.title + font: Style.largeFont + horizontalAlignment: Text.AlignHCenter + } + Item { + Layout.preferredHeight: Style.iconSize + Style.smallMargins * 2 + Layout.preferredWidth: Style.iconSize + Style.smallMargins * 2 + } } + Label { id: textLabel Layout.fillWidth: true diff --git a/nymea-app/ui/connection/NewConnectionWizard.qml b/nymea-app/ui/connection/NewConnectionWizard.qml index 3b84dea4..5f7016af 100644 --- a/nymea-app/ui/connection/NewConnectionWizard.qml +++ b/nymea-app/ui/connection/NewConnectionWizard.qml @@ -146,7 +146,8 @@ WizardPageBase { WizardPageBase { title: qsTr("Connection") text: qsTr("Connecting to the nymea system.") - showNextButton: false + nextButtonText: qsTr("Manual connection") + onNext: pageStack.push(manualConnectionComponent) onBack: pageStack.pop() @@ -248,6 +249,95 @@ WizardPageBase { } } + Component { + id: manualConnectionComponent + WizardPageBase { + title: qsTr("Manual connection") + text: qsTr("Please enter the connection information for your nymea system") + onBack: pageStack.pop() + + onNext: { + var rpcUrl + var hostAddress + var port + + // Set default to placeholder + if (addressTextInput.text === "") { + hostAddress = addressTextInput.placeholderText + } else { + hostAddress = addressTextInput.text + } + + if (portTextInput.text === "") { + port = portTextInput.placeholderText + } else { + port = portTextInput.text + } + + if (connectionTypeComboBox.currentIndex == 0) { + if (secureCheckBox.checked) { + rpcUrl = "nymeas://" + hostAddress + ":" + port + } else { + rpcUrl = "nymea://" + hostAddress + ":" + port + } + } else if (connectionTypeComboBox.currentIndex == 1) { + if (secureCheckBox.checked) { + rpcUrl = "wss://" + hostAddress + ":" + port + } else { + rpcUrl = "ws://" + hostAddress + ":" + port + } + } + + print("Try to connect ", rpcUrl) + var host = nymeaDiscovery.nymeaHosts.createWanHost("Manual connection", rpcUrl); + engine.jsonRpcClient.connectToHost(host) + } + + content: ColumnLayout { + anchors.fill: parent + anchors.margins: Style.margins + GridLayout { + columns: 2 + + Label { + text: qsTr("Protocol") + } + + ComboBox { + id: connectionTypeComboBox + Layout.fillWidth: true + model: [ qsTr("TCP"), qsTr("Websocket") ] + } + + Label { text: qsTr("Address:") } + TextField { + id: addressTextInput + objectName: "addressTextInput" + Layout.fillWidth: true + placeholderText: "127.0.0.1" + } + + Label { text: qsTr("Port:") } + TextField { + id: portTextInput + Layout.fillWidth: true + placeholderText: connectionTypeComboBox.currentIndex === 0 ? "2222" : "4444" + validator: IntValidator{bottom: 1; top: 65535;} + } + + Label { + Layout.fillWidth: true + text: qsTr("Encrypted connection:") + } + CheckBox { + id: secureCheckBox + checked: true + } + } + } + } + } + Component { id: wiredInstructionsComponent WizardPageBase {