From 187d306a0b329aa0454893968bfe89643956a7d4 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sun, 15 Dec 2019 12:43:14 +0100 Subject: [PATCH] Implement new system time api --- .../configuration/nymeaconfiguration.cpp | 58 +--- .../configuration/nymeaconfiguration.h | 17 -- libnymea-app-core/system/systemcontroller.cpp | 105 +++++++ libnymea-app-core/system/systemcontroller.h | 38 ++- .../android/platformhelperandroid.cpp | 2 +- nymea-app/resources.qrc | 2 + nymea-app/ui/components/DatePicker.qml | 134 +++++++++ nymea-app/ui/components/TimePicker.qml | 261 ++++++++++++++++++ nymea-app/ui/system/GeneralSettingsPage.qml | 179 +++++++++--- 9 files changed, 676 insertions(+), 120 deletions(-) create mode 100644 nymea-app/ui/components/DatePicker.qml create mode 100644 nymea-app/ui/components/TimePicker.qml diff --git a/libnymea-app-core/configuration/nymeaconfiguration.cpp b/libnymea-app-core/configuration/nymeaconfiguration.cpp index ad593fd3..b403d3cb 100644 --- a/libnymea-app-core/configuration/nymeaconfiguration.cpp +++ b/libnymea-app-core/configuration/nymeaconfiguration.cpp @@ -32,8 +32,6 @@ void NymeaConfiguration::init() m_webSocketServerConfigurations->clear(); m_mqttServerConfigurations->clear(); m_client->sendCommand("Configuration.GetConfigurations", this, "getConfigurationsResponse"); - m_client->sendCommand("Configuration.GetAvailableLanguages", this, "getAvailableLanguagesResponse"); - m_client->sendCommand("Configuration.GetTimeZones", this, "getTimezonesResponse"); m_client->sendCommand("Configuration.GetMqttServerConfigurations", this, "getMqttServerConfigsReply"); m_client->sendCommand("Configuration.GetMqttPolicies", this, "getMqttPoliciesReply"); } @@ -50,38 +48,11 @@ void NymeaConfiguration::setServerName(const QString &serverName) m_client->sendCommand("Configuration.SetServerName", params, this, "setServerNameResponse"); } -QString NymeaConfiguration::language() const -{ - return m_language; -} - -void NymeaConfiguration::setLanguage(const QString &language) -{ - QVariantMap params; - params.insert("language", language); - m_client->sendCommand("Configuration.SetLanguage", params); -} - -QStringList NymeaConfiguration::availableLanguages() const -{ - return m_availableLanguages; -} - -QString NymeaConfiguration::timezone() const -{ - return m_timezone; -} - void NymeaConfiguration::setTimezone(const QString &timezone) { QVariantMap params; params.insert("timeZone", timezone); - m_client->sendCommand("Configuration.SetTimeZone", params, this, "setTimezoneResponse"); -} - -QStringList NymeaConfiguration::timezones() const -{ - return m_timezones; + m_client->sendCommand("System.SetTimeZone", params, this, "setTimezoneResponse"); } bool NymeaConfiguration::debugServerEnabled() const @@ -259,10 +230,6 @@ void NymeaConfiguration::getConfigurationsResponse(const QVariantMap ¶ms) emit debugServerEnabledChanged(); m_serverName = basicConfig.value("serverName").toString(); emit serverNameChanged(); - m_language = basicConfig.value("language").toString(); - emit languageChanged(); - m_timezone = basicConfig.value("timeZone").toString(); - emit timezoneChanged(); QVariantMap cloudConfig = params.value("params").toMap().value("cloud").toMap(); m_cloudEnabled = cloudConfig.value("enabled").toBool(); emit cloudEnabledChanged(); @@ -290,25 +257,6 @@ void NymeaConfiguration::getConfigurationsResponse(const QVariantMap ¶ms) } } -void NymeaConfiguration::getAvailableLanguagesResponse(const QVariantMap ¶ms) -{ -// qDebug() << "available languages" << params; - m_availableLanguages = params.value("params").toMap().value("languages").toStringList(); - emit availableLanguagesChanged(); -} - -void NymeaConfiguration::getTimezonesResponse(const QVariantMap ¶ms) -{ -// qDebug() << "Get timezones response" << params; - m_timezones = params.value("params").toMap().value("timeZones").toStringList(); - emit timezonesChanged(); -} - -void NymeaConfiguration::setTimezoneResponse(const QVariantMap ¶ms) -{ - qDebug() << "Set timezones response" << params; -} - void NymeaConfiguration::setServerNameResponse(const QVariantMap ¶ms) { qDebug() << "Server name set:" << params; @@ -416,10 +364,6 @@ void NymeaConfiguration::notificationReceived(const QVariantMap ¬ification) emit debugServerEnabledChanged(); m_serverName = params.value("serverName").toString(); emit serverNameChanged(); - m_language = params.value("language").toString(); - emit languageChanged(); - m_timezone = params.value("timeZone").toString(); - emit timezoneChanged(); return; } if (notif == "Configuration.CloudConfigurationChanged") { diff --git a/libnymea-app-core/configuration/nymeaconfiguration.h b/libnymea-app-core/configuration/nymeaconfiguration.h index 07c3e50d..b348f380 100644 --- a/libnymea-app-core/configuration/nymeaconfiguration.h +++ b/libnymea-app-core/configuration/nymeaconfiguration.h @@ -19,12 +19,6 @@ class NymeaConfiguration : public JsonHandler Q_PROPERTY(QString serverName READ serverName WRITE setServerName NOTIFY serverNameChanged) - Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged) - Q_PROPERTY(QStringList availableLanguages READ availableLanguages NOTIFY availableLanguagesChanged) - - Q_PROPERTY(QString timezone READ timezone WRITE setTimezone NOTIFY timezoneChanged) - Q_PROPERTY(QStringList timezones READ timezones NOTIFY timezonesChanged) - Q_PROPERTY(bool cloudEnabled READ cloudEnabled WRITE setCloudEnabled NOTIFY cloudEnabledChanged) Q_PROPERTY(bool debugServerEnabled READ debugServerEnabled WRITE setDebugServerEnabled NOTIFY debugServerEnabledChanged) @@ -87,9 +81,6 @@ private: Q_INVOKABLE void setDebugServerEnabledResponse(const QVariantMap ¶ms); Q_INVOKABLE void setServerNameResponse(const QVariantMap ¶ms); Q_INVOKABLE void setCloudEnabledResponse(const QVariantMap ¶ms); - Q_INVOKABLE void getAvailableLanguagesResponse(const QVariantMap ¶ms); - Q_INVOKABLE void getTimezonesResponse(const QVariantMap ¶ms); - Q_INVOKABLE void setTimezoneResponse(const QVariantMap ¶ms); Q_INVOKABLE void setTcpConfigReply(const QVariantMap ¶ms); Q_INVOKABLE void deleteTcpConfigReply(const QVariantMap ¶ms); Q_INVOKABLE void setWebSocketConfigReply(const QVariantMap ¶ms); @@ -108,10 +99,6 @@ private: signals: void debugServerEnabledChanged(); void serverNameChanged(); - void languageChanged(); - void availableLanguagesChanged(); - void timezoneChanged(); - void timezonesChanged(); void cloudEnabledChanged(); private: @@ -119,10 +106,6 @@ private: bool m_debugServerEnabled = false; QString m_serverName; - QString m_language; - QStringList m_availableLanguages; - QString m_timezone; - QStringList m_timezones; bool m_cloudEnabled = false; ServerConfigurations *m_tcpServerConfigurations = nullptr; diff --git a/libnymea-app-core/system/systemcontroller.cpp b/libnymea-app-core/system/systemcontroller.cpp index 5a835cd8..55e4dd61 100644 --- a/libnymea-app-core/system/systemcontroller.cpp +++ b/libnymea-app-core/system/systemcontroller.cpp @@ -5,6 +5,8 @@ #include "types/packages.h" #include "types/repositories.h" +#include + SystemController::SystemController(JsonRpcClient *jsonRpcClient, QObject *parent): JsonHandler(parent), m_jsonRpcClient(jsonRpcClient) @@ -12,6 +14,8 @@ SystemController::SystemController(JsonRpcClient *jsonRpcClient, QObject *parent m_jsonRpcClient->registerNotificationHandler(this, "notificationReceived"); m_packages = new Packages(this); m_repositories = new Repositories(this); + + startTimer(1000, Qt::VeryCoarseTimer); } void SystemController::init() @@ -22,6 +26,8 @@ void SystemController::init() m_jsonRpcClient->sendCommand("System.GetCapabilities", this, "getCapabilitiesResponse"); } else { m_powerManagementAvailable = false; + m_updateManagementAvailable = false; + m_timeManagementAvailable = false; } } @@ -101,6 +107,63 @@ int SystemController::enableRepository(const QString &id, bool enabled) return m_jsonRpcClient->sendCommand("System.EnableRepository", params, this, "enableRepositoryResponse"); } +bool SystemController::timeManagementAvailable() const +{ + return m_timeManagementAvailable; +} + +QDateTime SystemController::serverTime() const +{ + return m_serverTime; +} + +void SystemController::setServerTime(const QDateTime &serverTime) +{ + QVariantMap params; + params.insert("automaticTime", false); + params.insert("time", serverTime.toSecsSinceEpoch()); + params.insert("timeZone", serverTime.timeZone().id()); + m_jsonRpcClient->sendCommand("System.SetTime", params, this, "setTimeResponse"); +} + +QStringList SystemController::timeZones() const +{ + QStringList ret; + foreach (const QByteArray &tzId, QTimeZone::availableTimeZoneIds()) { + ret << tzId; + } + return ret; +} + +QString SystemController::serverTimeZone() const +{ + return m_serverTime.timeZone().id(); +} + +void SystemController::setServerTimeZone(const QString &serverTimeZone) +{ + QVariantMap params; + params.insert("timeZone", serverTimeZone); + m_jsonRpcClient->sendCommand("System.SetTime", params, this, "setTimeResponse"); +} + +bool SystemController::automaticTimeAvailable() const +{ + return m_automaticTimeAvailable; +} + +bool SystemController::automaticTime() const +{ + return m_automaticTime; +} + +void SystemController::setAutomaticTime(bool automaticTime) +{ + QVariantMap params; + params.insert("automaticTime", automaticTime); + m_jsonRpcClient->sendCommand("System.SetTime", params, this, "setTimeResponse"); +} + void SystemController::getCapabilitiesResponse(const QVariantMap &data) { qDebug() << "capabilities received" << data; @@ -110,11 +173,18 @@ void SystemController::getCapabilitiesResponse(const QVariantMap &data) m_updateManagementAvailable = data.value("params").toMap().value("updateManagement").toBool(); emit updateManagementAvailableChanged(); + m_timeManagementAvailable = data.value("params").toMap().value("timeManagement").toBool(); + emit timeManagementAvailableChanged(); + if (m_updateManagementAvailable) { m_jsonRpcClient->sendCommand("System.GetUpdateStatus", this, "getUpdateStatusResponse"); m_jsonRpcClient->sendCommand("System.GetPackages", this, "getPackagesResponse"); m_jsonRpcClient->sendCommand("System.GetRepositories", this, "getRepositoriesResponse"); } + +// if (m_jsonRpcClient->ensureServerVersion("4.1")) { + m_jsonRpcClient->sendCommand("System.GetTime", this, "getServerTimeResponse"); +// } } void SystemController::getUpdateStatusResponse(const QVariantMap &data) @@ -164,6 +234,24 @@ void SystemController::enableRepositoryResponse(const QVariantMap ¶ms) emit enableRepositoryFinished(params.value("id").toInt(), params.value("params").toMap().value("success").toBool()); } +void SystemController::getServerTimeResponse(const QVariantMap ¶ms) +{ + qDebug() << "Server time" << params; + m_serverTime = QDateTime::fromSecsSinceEpoch(params.value("params").toMap().value("time").toUInt()); + m_serverTime.setTimeZone(QTimeZone(params.value("params").toMap().value("timeZone").toString().toUtf8())); + emit serverTimeChanged(); + emit serverTimeZoneChanged(); + m_automaticTimeAvailable = params.value("params").toMap().value("automaticTimeAvailable").toBool(); + emit automaticTimeAvailableChanged(); + m_automaticTime = params.value("params").toMap().value("automaticTime").toBool(); + emit automaticTimeChanged(); +} + +void SystemController::setTimeResponse(const QVariantMap ¶ms) +{ + qDebug() << "set time response" << params; +} + void SystemController::notificationReceived(const QVariantMap &data) { QString notification = data.value("notification").toString(); @@ -233,7 +321,24 @@ void SystemController::notificationReceived(const QVariantMap &data) qWarning() << "System capabilites changed: power management:" << m_powerManagementAvailable << "update management:" << m_updateManagementAvailable; emit powerManagementAvailableChanged(); emit updateManagementAvailableChanged(); + } else if (notification == "System.TimeConfigurationChanged") { + qDebug() << "System time configuration changed"; + m_serverTime = QDateTime::fromSecsSinceEpoch(data.value("params").toMap().value("time").toUInt()); + m_serverTime.setTimeZone(QTimeZone(data.value("params").toMap().value("timeZone").toByteArray())); + emit serverTimeChanged(); + emit serverTimeZoneChanged(); + m_automaticTimeAvailable = data.value("params").toMap().value("automaticTimeAvailable").toBool(); + emit automaticTimeAvailableChanged(); + m_automaticTime = data.value("params").toMap().value("automaticTime").toBool(); + emit automaticTimeChanged(); } else { qWarning() << "Unhandled System Notification" << data.value("notification"); } } + +void SystemController::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event) + m_serverTime = m_serverTime.addSecs(1); + emit serverTimeChanged(); +} diff --git a/libnymea-app-core/system/systemcontroller.h b/libnymea-app-core/system/systemcontroller.h index 072eba42..8524aba7 100644 --- a/libnymea-app-core/system/systemcontroller.h +++ b/libnymea-app-core/system/systemcontroller.h @@ -14,12 +14,19 @@ class SystemController : public JsonHandler Q_PROPERTY(bool powerManagementAvailable READ powerManagementAvailable NOTIFY powerManagementAvailableChanged) // Whether the update mechanism is available in the connected core Q_PROPERTY(bool updateManagementAvailable READ updateManagementAvailable NOTIFY updateManagementAvailableChanged) + Q_PROPERTY(bool timeManagementAvailable READ timeManagementAvailable NOTIFY timeManagementAvailableChanged) Q_PROPERTY(bool updateManagementBusy READ updateManagementBusy NOTIFY updateManagementBusyChanged) Q_PROPERTY(bool updateRunning READ updateRunning NOTIFY updateRunningChanged) Q_PROPERTY(Packages* packages READ packages CONSTANT) Q_PROPERTY(Repositories* repositories READ repositories CONSTANT) + Q_PROPERTY(QDateTime serverTime READ serverTime WRITE setServerTime NOTIFY serverTimeChanged) + Q_PROPERTY(QString serverTimeZone READ serverTimeZone WRITE setServerTimeZone NOTIFY serverTimeZoneChanged) + Q_PROPERTY(QStringList timeZones READ timeZones CONSTANT) + Q_PROPERTY(bool automaticTimeAvailable READ automaticTimeAvailable NOTIFY automaticTimeAvailableChanged) + Q_PROPERTY(bool automaticTime READ automaticTime WRITE setAutomaticTime NOTIFY automaticTimeChanged) + public: explicit SystemController(JsonRpcClient *jsonRpcClient, QObject *parent = nullptr); @@ -27,29 +34,40 @@ public: QString nameSpace() const override; bool powerManagementAvailable() const; - bool updateManagementAvailable() const; - Q_INVOKABLE void reboot(); Q_INVOKABLE void shutdown(); + bool updateManagementAvailable() const; bool updateManagementBusy() const; bool updateRunning() const; - Q_INVOKABLE void checkForUpdates(); Packages* packages() const; Q_INVOKABLE void updatePackages(const QString packageId = QString()); Q_INVOKABLE void removePackages(const QString packageId = QString()); - Repositories* repositories() const; Q_INVOKABLE int enableRepository(const QString &id, bool enabled); + bool timeManagementAvailable() const; + QDateTime serverTime() const; + void setServerTime(const QDateTime &serverTime); + QStringList timeZones() const; + QString serverTimeZone() const; + void setServerTimeZone(const QString &serverTimeZone); + bool automaticTimeAvailable() const; + bool automaticTime() const; + void setAutomaticTime(bool automaticTime); signals: void powerManagementAvailableChanged(); void updateManagementAvailableChanged(); + void timeManagementAvailableChanged(); void updateManagementBusyChanged(); void updateRunningChanged(); void enableRepositoryFinished(int id, bool success); + void serverTimeChanged(); + void serverTimeZoneChanged(); + void automaticTimeAvailableChanged(); + void automaticTimeChanged(); private slots: void getCapabilitiesResponse(const QVariantMap &data); @@ -58,19 +76,31 @@ private slots: void getRepositoriesResponse(const QVariantMap &data); void removePackageResponse(const QVariantMap ¶ms); void enableRepositoryResponse(const QVariantMap ¶ms); + void getServerTimeResponse(const QVariantMap ¶ms); + void setTimeResponse(const QVariantMap ¶ms); void notificationReceived(const QVariantMap &data); + +protected: + void timerEvent(QTimerEvent *event) override; + private: JsonRpcClient *m_jsonRpcClient = nullptr; bool m_powerManagementAvailable = false; bool m_updateManagementAvailable = false; + bool m_timeManagementAvailable = false; bool m_updateManagementBusy = false; bool m_updateRunning = false; Packages *m_packages = nullptr; Repositories *m_repositories = nullptr; + + QDateTime m_serverTime; + QStringList m_timeZones; + bool m_automaticTimeAvailable = false; + bool m_automaticTime = false; }; #endif // SYSTEMCONTROLLER_H diff --git a/nymea-app/platformintegration/android/platformhelperandroid.cpp b/nymea-app/platformintegration/android/platformhelperandroid.cpp index 33df72c2..af5fec78 100644 --- a/nymea-app/platformintegration/android/platformhelperandroid.cpp +++ b/nymea-app/platformintegration/android/platformhelperandroid.cpp @@ -80,7 +80,7 @@ void PlatformHelperAndroid::vibrate(PlatformHelper::HapticsFeedback feedbackType int duration; switch (feedbackType) { case HapticsFeedbackSelection: - duration = 20; + duration = 10; break; case HapticsFeedbackImpact: duration = 30; diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index 4e82fbcc..9a03a723 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -201,5 +201,7 @@ ui/mainviews/GroupsView.qml ui/grouping/GroupPage.qml ui/delegates/ThingTile.qml + ui/components/TimePicker.qml + ui/components/DatePicker.qml diff --git a/nymea-app/ui/components/DatePicker.qml b/nymea-app/ui/components/DatePicker.qml new file mode 100644 index 00000000..c3fa0460 --- /dev/null +++ b/nymea-app/ui/components/DatePicker.qml @@ -0,0 +1,134 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 +import Nymea 1.0 + +ColumnLayout { + id: root + + property date date + + RowLayout { + Layout.fillWidth: true + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + name: "../images/back.svg" + MouseArea { + anchors.fill: parent + onClicked: { + var newDate = new Date(root.date) + newDate.setMonth(root.date.getMonth() - 1) + root.date = newDate + } + } + } + Label { + text: root.date.toLocaleDateString() + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + ColorIcon { + Layout.preferredHeight: app.iconSize + Layout.preferredWidth: app.iconSize + name: "../images/next.svg" + MouseArea { + anchors.fill: parent + onClicked: { + var newDate = new Date(root.date) + newDate.setMonth(root.date.getMonth() + 1) + root.date = newDate + } + } + } + } + + ThinDivider {} + + ListModel { + id: monthModel + ListElement { text: qsTr("January"); days: 31; leapYearDays: 31 } + ListElement { text: qsTr("February"); days: 28; leapYearDays: 29 } + ListElement { text: qsTr("March"); days: 31; leapYearDays: 31 } + ListElement { text: qsTr("April"); days: 30; leapYearDays: 30 } + ListElement { text: qsTr("May"); days: 31; leapYearDays: 31 } + ListElement { text: qsTr("June"); days: 30; leapYearDays: 30 } + ListElement { text: qsTr("July"); days: 31; leapYearDays: 31 } + ListElement { text: qsTr("August"); days: 31; leapYearDays: 31 } + ListElement { text: qsTr("September"); days: 30; leapYearDays: 30 } + ListElement { text: qsTr("October"); days: 31; leapYearDays: 31 } + ListElement { text: qsTr("November"); days: 30; leapYearDays: 30 } + ListElement { text: qsTr("December"); days: 31; leapYearDays: 31 } + } + + ListModel { + id: weekModel + ListElement { text: qsTr("Mon") } + ListElement { text: qsTr("Tue") } + ListElement { text: qsTr("Wed") } + ListElement { text: qsTr("Thu") } + ListElement { text: qsTr("Fri") } + ListElement { text: qsTr("Sat") } + ListElement { text: qsTr("Sun") } + } + + RowLayout { + Repeater { + model: weekModel + Item { + Layout.fillWidth: true + Layout.preferredHeight: width + Label { + anchors.centerIn: parent + text: model.text + } + } + } + } + + GridLayout { + id: daysGrid + Layout.fillWidth: true + Layout.fillHeight: true + columns: 7 + columnSpacing: 0 + rowSpacing: 0 + + property date firstOfMonth: new Date(root.date.getFullYear(), root.date.getMonth(), 1) + property int offset: ((firstOfMonth.getDay() - 1) % 7 + 7) % 7 + property bool isLeapYear: false + property int daysInMonth: isLeapYear ? monthModel.get(root.date.getMonth()).leapDays : monthModel.get(root.date.getMonth()).days + property int daysInPreviousMonth: isLeapYear ? monthModel.get((root.date.getMonth() + 11) % 12).leapDays : monthModel.get((root.date.getMonth() + 11) % 12).days + + Repeater { + model: 6 * 7 + + delegate: Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: width + radius: width / 2 + property int dayOfMonth: index - daysGrid.offset + 1 + property bool isPreviousMonth: dayOfMonth < 1 + property bool isNextMonth: dayOfMonth > daysGrid.daysInMonth + property int correctedDayOfMonth: isPreviousMonth ? daysGrid.daysInPreviousMonth + dayOfMonth + : isNextMonth ? dayOfMonth - daysGrid.daysInMonth : dayOfMonth + color: !isPreviousMonth && !isNextMonth && correctedDayOfMonth == root.date.getDate() ? app.accentColor : "transparent" + Label { + anchors.centerIn: parent + opacity: isPreviousMonth || isNextMonth ? 0.6 : 1 + + text: correctedDayOfMonth + } + + MouseArea { + anchors.fill: parent + onClicked: { + var newDate = new Date(root.date) + newDate.setDate(dayOfMonth) + root.date = newDate + } + } + } + } + } +} diff --git a/nymea-app/ui/components/TimePicker.qml b/nymea-app/ui/components/TimePicker.qml new file mode 100644 index 00000000..f5e6199d --- /dev/null +++ b/nymea-app/ui/components/TimePicker.qml @@ -0,0 +1,261 @@ +import QtQuick 2.3 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 +import Nymea 1.0 + +ColumnLayout { + id: root + spacing: app.margins + + property int hour: 0 + property int minute: 0 + + function selectHours() { + d.mode = "hours" + } + function selectMinutes() { + d.mode = "minutes" + } + Component.onCompleted: { + initTimer.start(); + } + Timer { + id: initTimer + interval: 1 + onTriggered: selectHours(); + } + + Row { + Layout.alignment: Qt.AlignHCenter + Label { + text: app.pad(root.hour, 2) + font.pixelSize: app.largeFont * 2 + opacity: d.mode == "hours" ? 1 : .6 + Behavior on opacity { NumberAnimation {duration: 250 } } + MouseArea { + anchors.fill: parent + onClicked: selectHours() + } + } + Label { + text: ":" + font.pixelSize: app.largeFont * 2 + } + Label { + text: app.pad(root.minute, 2) + font.pixelSize: app.largeFont * 2 + opacity: d.mode == "minutes" ? 1 : .6 + Behavior on opacity { NumberAnimation {duration: 250 } } + MouseArea { + anchors.fill: parent + onClicked: selectMinutes() + } + } + } + + Item { + id: d + Layout.fillHeight: true + Layout.fillWidth: true + Layout.preferredHeight: width + + property string mode: "none" + + Item { + id: dial + height: Math.min(parent.height, parent.width) + width: height + anchors.centerIn: parent + + Repeater { + id: hours12 + + model: 12 + delegate: Item { + id: delegate + visible: d.mode == "hours" + anchors.centerIn: parent + height: parent.height + rotation: (360 / 12) * (index + 1) + width: 30 + + property alias field: fieldItem + Item { + id: fieldItem + anchors { left: parent.left; top: parent.top; right: parent.right } + height: width + + Label { + anchors.centerIn: parent + text: index + 1 + rotation: -delegate.rotation + } + } + } + } + + Repeater { + id: hours24 + + property int selectedIndex: root.hour - 12 + + model: 12 + delegate: Item { + id: delegate + visible: d.mode == "hours" + anchors.centerIn: parent + height: parent.height - 80 + rotation: (360 / 12) * (index + 12) + width: 30 + + property alias field: fieldItem + Item { + id: fieldItem + anchors { left: parent.left; top: parent.top; right: parent.right } + height: width + + Label { + anchors.centerIn: parent + text: index + 12 == 12 ? "00" : index + 12 + rotation: -delegate.rotation + opacity: .8 + } + } + } + } + + Repeater { + id: minutes + + property int selectedIndex: 0 + readonly property int selectedMinute: selectedIndex + + model: 60 + delegate: Item { + id: delegate + visible: d.mode == "minutes" + anchors.centerIn: parent + height: parent.height + rotation: (360 / 60) * (index) + width: 30 + + property alias field: fieldItem + Item { + id: fieldItem + anchors { left: parent.left; top: parent.top; right: parent.right } + height: width + + Label { + anchors.centerIn: parent + text: index + rotation: -delegate.rotation + visible: index % 5 == 0 + } + } + } + } + + Item { + id: newDot + height: (parent.height + 10) - (d.mode == "hours" && (root.hour == 0 || root.hour > 12) ? 80 : 0) + width: 40 + anchors.centerIn: parent + z: -1 + rotation: { + if (d.mode == "hours") { + if (root.hour > 0 && root.hour < 13) { + return root.hour * 360 / 12 + } + return (root.hour - 12 % 12) * 360 / 12 + } + return root.minute * 360 / 60 + } + Behavior on height { NumberAnimation { duration: 100 } } + Behavior on rotation { RotationAnimation { duration: 100; direction: RotationAnimation.Shortest } } + + Rectangle { + anchors { left: parent.left; top: parent.top; right: parent.right } + height: width + color: app.accentColor + radius: width / 2 + } + Rectangle { + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + width: 5 + height: parent.height / 2 + color: app.accentColor + radius: width / 2 + } + } + + MouseArea { + anchors.fill: parent + + onPressed: { + update(); + } + onPositionChanged: update(); + onReleased: { + if (d.mode == "hours") { + selectMinutes(); + } + } + + function update() { + var angle = calculateAngle(mouseX, mouseY); + + var items = d.mode == "hours" ? 12 : 60 + + // angle : 360 = num : 12 + var selected = Math.round(angle * items / 360) % items; + + if (d.mode == "hours") { + if (calculateDistanceToCenter(mouseX, mouseY) < (width / 2) - 40) { + selected = selected + 12 + } + // swap 12 and 00 + if (selected === 12) { + selected = 0 + } else if (selected === 0) { + selected = 12 + } + if (root.hour !== selected) { + root.hour = selected + PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection) + } + } else { + if (root.minute !== selected) { + root.minute = selected + PlatformHelper.vibrate(PlatformHelper.HapticsFeedbackSelection) + } + } + + } + + 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; + } + + function calculateDistanceToCenter(mouseX, mouseY) { + var a = mouseY - (height / 2) + var b = mouseX - (width / 2) + var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)) + return c; + } + } + } + } +} diff --git a/nymea-app/ui/system/GeneralSettingsPage.qml b/nymea-app/ui/system/GeneralSettingsPage.qml index c81b757e..c25c81f3 100644 --- a/nymea-app/ui/system/GeneralSettingsPage.qml +++ b/nymea-app/ui/system/GeneralSettingsPage.qml @@ -8,20 +8,18 @@ import "../components" Page { id: root header: NymeaHeader { - text: qsTr("Box settings") + text: qsTr("General settings") backButtonVisible: true onBackPressed: pageStack.pop() } ColumnLayout { - id: settingsColumn - anchors { left: parent.left; right: parent.right; top: parent.top } + id: settingsGrid + anchors { horizontalCenter: parent.horizontalCenter; top: parent.top; margins: app.margins } + width: Math.min(500, parent.width - app.margins * 2) RowLayout { Layout.fillWidth: true - Layout.topMargin: app.margins - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins spacing: app.margins Label { text: qsTr("Name") @@ -40,63 +38,119 @@ Page { RowLayout { Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins - spacing: app.margins - + visible: engine.systemController.automaticTimeAvailable Label { + text: qsTr("Set date and time automatically") Layout.fillWidth: true - text: qsTr("Language") } - ComboBox { - id: languageBox - Layout.fillWidth: true - model: engine.nymeaConfiguration.availableLanguages - currentIndex: model.indexOf(engine.nymeaConfiguration.language) - contentItem: Label { - leftPadding: app.margins / 2 - text: Qt.locale(languageBox.displayText).nativeLanguageName + " (" + Qt.locale(languageBox.displayText).nativeCountryName + ")" - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - - delegate: ItemDelegate { - width: languageBox.width - contentItem: Label { - text: Qt.locale(modelData).nativeLanguageName + " (" + Qt.locale(modelData).nativeCountryName + ")" - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - highlighted: languageBox.highlightedIndex === index - } - onActivated: { - engine.nymeaConfiguration.language = currentText; + CheckBox { + checked: engine.systemController.automaticTime + onClicked: { + engine.systemController.automaticTime = checked } } } RowLayout { Layout.fillWidth: true - Layout.leftMargin: app.margins - Layout.rightMargin: app.margins spacing: app.margins + Layout.preferredHeight: dateButton.implicitHeight + visible: engine.jsonRpcClient.ensureServerVersion("4.1") + Label { + text: qsTr("Date") + Layout.fillWidth: true + } + Label { + text: engine.systemController.serverTime.toLocaleDateString() + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + } + Button { + id: dateButton + visible: !engine.systemController.automaticTime && engine.systemController.timeManagementAvailable + contentItem: Item { + ColorIcon { + name: "../images/edit.svg" + color: app.foregroundColor + anchors.centerIn: parent + height: parent.height + width: height + } + } + + onClicked: { + var popup = datePickerComponent.createObject(root, {dateTime: engine.systemController.serverTime}) + popup.accepted.connect(function() { + print("setting new date", popup.dateTime) + engine.systemController.serverTime = popup.dateTime + }) + popup.open(); + + } + } + } + RowLayout { + Layout.fillWidth: true + spacing: app.margins + Layout.preferredHeight: timeButton.implicitHeight + visible: engine.jsonRpcClient.ensureServerVersion("4.1") + Label { + text: qsTr("Time") + Layout.fillWidth: true + } + Label { + text: engine.systemController.serverTime.toLocaleTimeString(/*Locale.ShortTimeString*/) + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + } + Button { + id: timeButton + visible: !engine.systemController.automaticTime && engine.systemController.timeManagementAvailable + contentItem: Item { + ColorIcon { + name: "../images/edit.svg" + color: app.foregroundColor + anchors.centerIn: parent + height: parent.height + width: height + } + } + + onClicked: { + var popup = timePickerComponent.createObject(root, {hour: engine.systemController.serverTime.getHours(), minute: engine.systemController.serverTime.getMinutes()}) + popup.accepted.connect(function() { + var date = new Date(engine.systemController.serverTime) + date.setHours(popup.hour); + date.setMinutes(popup.minute) + engine.systemController.serverTime = date; + }) + popup.open(); + + } + } + } + + + RowLayout { + Layout.fillWidth: true + spacing: app.margins + visible: engine.jsonRpcClient.ensureServerVersion("4.1") Label { Layout.fillWidth: true text: qsTr("Time zone") } ComboBox { Layout.minimumWidth: 200 - model: engine.nymeaConfiguration.timezones - currentIndex: model.indexOf(engine.nymeaConfiguration.timezone) + model: engine.systemController.timeZones + currentIndex: model.indexOf(engine.systemController.serverTimeZone) onActivated: { - engine.nymeaConfiguration.timezone = currentText; + engine.systemController.serverTimeZone = currentText; } } } Button { Layout.fillWidth: true - Layout.margins: app.margins text: qsTr("Reboot %1:core").arg(app.systemName) visible: engine.systemController.powerManagementAvailable onClicked: { @@ -117,7 +171,6 @@ Page { } Button { Layout.fillWidth: true - Layout.margins: app.margins text: qsTr("Shutdown %1:core").arg(app.systemName) visible: engine.systemController.powerManagementAvailable onClicked: { @@ -137,4 +190,48 @@ Page { } } } + + Component { + id: timePickerComponent + Dialog { + id: timePicker + property int maxSize: Math.min(parent.width, parent.height) + property int size: Math.min(maxSize, 500) + property alias hour: p.hour + property alias minute: p.minute + width: size - 80 + height: size + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + + TimePicker { + id: p + width: parent.width + height: parent.height + } + standardButtons: Dialog.Ok | Dialog.Cancel + } + } + + Component { + id: datePickerComponent + Dialog { + id: datePicker + property int maxSize: Math.min(parent.width, parent.height) + property int size: Math.min(maxSize, 500) + property alias dateTime: p.date + width: size - 80 + height: size + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + + DatePicker { + id: p + width: parent.width + height: parent.height + date: datePicker.dateTime + } + standardButtons: Dialog.Ok | Dialog.Cancel + } + } }