diff --git a/AppSettingsPage.qml b/AppSettingsPage.qml
index ce4d8e8..88a08b5 100644
--- a/AppSettingsPage.qml
+++ b/AppSettingsPage.qml
@@ -14,7 +14,7 @@ NavigationPage {
anchors.fill: parent
columns: 2
- Label {
+ Text {
text: qsTr("Number of app instances:")
font.bold: true
wrapMode: Text.Wrap
@@ -35,7 +35,7 @@ NavigationPage {
anchors.fill: parent
columns: 2
- Label {
+ Text {
text: qsTr("solalaweb key:")
font.bold: true
wrapMode: Text.Wrap
@@ -52,7 +52,7 @@ NavigationPage {
}
}
- Label {
+ Text {
text: qsTr("solalaweb cert:")
font.bold: true
wrapMode: Text.Wrap
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5a69d04..0140ed5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -103,13 +103,19 @@ qt_add_qml_module(evcharger-app
PvSurplusPage.qml
RebootPage.qml
RequestStatusText.qml
+ SchedulerDayPage.qml
SchedulerPage.qml
SecurityPage.qml
SelectLogicModeItem.qml
SensorsConfigurationPage.qml
SetPriceLimitPage.qml
SettingsTabPage.qml
+ SimpleNavigationItem.qml
SwitchLanguagePage.qml
+ TimeComponentLabel.qml
+ TimePickerDialog.qml
+ TimePickerLabel.qml
+ TimePicker.qml
VerticalTabButton.qml
WhiteBox.qml
WiFiErrorsPage.qml
diff --git a/ChargerTabPage.qml b/ChargerTabPage.qml
index 9e82e6d..07e99ea 100644
--- a/ChargerTabPage.qml
+++ b/ChargerTabPage.qml
@@ -28,7 +28,7 @@ StackView {
RowLayout {
anchors.fill: parent
- Label {
+ Text {
Layout.fillWidth: true
Layout.fillHeight: true
diff --git a/CloudPage.qml b/CloudPage.qml
index 661082e..85fa8a9 100644
--- a/CloudPage.qml
+++ b/CloudPage.qml
@@ -25,7 +25,7 @@ NavigationPage {
anchors.fill: parent
columns: 2
- Label {
+ Text {
text: qsTr("Trying to connect:")
font.bold: true
}
@@ -34,7 +34,7 @@ NavigationPage {
apiKey: "cws"
}
- Label {
+ Text {
text: qsTr("Is connected:")
font.bold: true
}
@@ -45,7 +45,7 @@ NavigationPage {
// TODO cwsca
- Label {
+ Text {
text: qsTr("Hello received:")
font.bold: true
}
@@ -54,7 +54,7 @@ NavigationPage {
apiKey: "chr"
}
- Label {
+ Text {
text: qsTr("Queue size cloud:")
font.bold: true
}
@@ -63,7 +63,7 @@ NavigationPage {
apiKey: "qsc"
}
- Label {
+ Text {
text: qsTr("Last error:")
font.bold: true
}
diff --git a/ConnectingScreen.qml b/ConnectingScreen.qml
index c634324..917cc89 100644
--- a/ConnectingScreen.qml
+++ b/ConnectingScreen.qml
@@ -37,7 +37,7 @@ ColumnLayout {
model: collectedMessages
clip: true
- delegate: Label {
+ delegate: Text {
text: message
}
diff --git a/ControllerTabPage.qml b/ControllerTabPage.qml
index 79dd524..70a7214 100644
--- a/ControllerTabPage.qml
+++ b/ControllerTabPage.qml
@@ -18,7 +18,7 @@ Page {
RowLayout {
anchors.fill: parent
- Label {
+ Text {
Layout.fillWidth: true
Layout.fillHeight: true
diff --git a/DeviceListScreen.qml b/DeviceListScreen.qml
index 5be7782..5c49292 100644
--- a/DeviceListScreen.qml
+++ b/DeviceListScreen.qml
@@ -190,14 +190,14 @@ StackView {
Layout.fillWidth: true
Layout.fillHeight: true
- Label {
+ Text {
Layout.fillWidth: true
text: delegate.friendlyName
font.bold: true
elide: Text.ElideRight
}
- Label {
+ Text {
Layout.fillWidth: true
text: qsTr("Serial Number %0").arg(delegate.serial);
}
@@ -212,48 +212,48 @@ StackView {
rowSpacing: 10
columnSpacing: 10
- Label {
+ Text {
text: qsTr("Manufacturer:")
Layout.leftMargin: 60
}
- Label {
+ Text {
text: delegate.manufacturer
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
- Label {
+ Text {
text: qsTr("Device Type:")
Layout.leftMargin: 60
}
- Label {
+ Text {
text: delegate.deviceType
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
- Label {
+ Text {
text: qsTr("Host Name:")
Layout.leftMargin: 60
}
- Label {
+ Text {
text: delegate.hostName
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
- Label {
+ Text {
text: qsTr("Ip:")
Layout.leftMargin: 60
}
- Label {
+ Text {
text: delegate.ip
font.bold: true
elide: Text.ElideRight
diff --git a/DeviceScreen.qml b/DeviceScreen.qml
index 25c6fa0..8ed7d38 100644
--- a/DeviceScreen.qml
+++ b/DeviceScreen.qml
@@ -92,7 +92,7 @@ Loader {
color: "red"
}
- Label {
+ Text {
text: qsTr("Password:")
Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline
}
diff --git a/EcoTabPage.qml b/EcoTabPage.qml
index 8c37cfe..6259b6d 100644
--- a/EcoTabPage.qml
+++ b/EcoTabPage.qml
@@ -43,7 +43,7 @@ StackView {
fillMode: Image.PreserveAspectFit
}
- Label {
+ Text {
Layout.fillWidth: true
font.pointSize: 20
font.bold: true
diff --git a/FirmwarePage.qml b/FirmwarePage.qml
index 0307e34..dbbcaf8 100644
--- a/FirmwarePage.qml
+++ b/FirmwarePage.qml
@@ -13,7 +13,7 @@ NavigationPage {
anchors.fill: parent
columns: 2
- Label {
+ Text {
text: qsTr("Running version:")
font.bold: true
}
@@ -23,7 +23,7 @@ NavigationPage {
id: runningVersion
}
- Label {
+ Text {
text: qsTr("Details:")
font.bold: true
}
@@ -40,7 +40,7 @@ NavigationPage {
text: JSON.stringify(runningVersionDetails.value, null, 4)
}
- Label {
+ Text {
text: qsTr("Recommended version:")
font.bold: true
}
@@ -61,7 +61,7 @@ NavigationPage {
RowLayout {
Layout.fillWidth: true
- Label {
+ Text {
text: qsTr("Update:")
}
@@ -163,7 +163,7 @@ NavigationPage {
Layout.fillWidth: true
columns: 2
- Label {
+ Text {
text: qsTr("Update status:")
font.bold: true
}
@@ -191,7 +191,7 @@ NavigationPage {
}
}
- Label {
+ Text {
text: qsTr("Update progress:")
font.bold: true
}
@@ -222,7 +222,7 @@ NavigationPage {
}
}
- Label {
+ Text {
text: qsTr("Update message:")
font.bold: true
}
diff --git a/HardwareInformationPage.qml b/HardwareInformationPage.qml
index 4212b1c..457ee4a 100644
--- a/HardwareInformationPage.qml
+++ b/HardwareInformationPage.qml
@@ -13,7 +13,7 @@ NavigationPage {
columns: 2
anchors.fill: parent
- Label {
+ Text {
text: qsTr("Serial Number:")
}
@@ -22,7 +22,7 @@ NavigationPage {
apiKey: "sse"
}
- Label {
+ Text {
visible: variant.exists
text: qsTr("Variant:")
}
@@ -34,7 +34,7 @@ NavigationPage {
apiKey: "var"
}
- Label {
+ Text {
visible: rssi.exists
text: qsTr("RSSI:")
}
diff --git a/MqttPage.qml b/MqttPage.qml
index c9fcb31..82c4a9c 100644
--- a/MqttPage.qml
+++ b/MqttPage.qml
@@ -17,7 +17,7 @@ NavigationPage {
anchors.fill: parent
columns: 2
- Label {
+ Text {
text: qsTr("Trying to connect:")
font.bold: true
}
@@ -26,7 +26,7 @@ NavigationPage {
apiKey: "mcs"
}
- Label {
+ Text {
text: qsTr("Is connected:")
font.bold: true
}
@@ -35,7 +35,7 @@ NavigationPage {
apiKey: "mcc"
}
- Label {
+ Text {
text: qsTr("Connected since:")
font.bold: true
}
@@ -50,7 +50,7 @@ NavigationPage {
text: connectedSince.value ? formatDuration(connectedSince.value - rebootTime.value) : ""
}
- Label {
+ Text {
text: qsTr("Command Counter:")
font.bold: true
}
@@ -59,7 +59,7 @@ NavigationPage {
apiKey: "mcsl"
}
- Label {
+ Text {
text: qsTr("Last error:")
font.bold: true
}
@@ -69,7 +69,7 @@ NavigationPage {
wrapMode: Text.Wrap
}
- Label {
+ Text {
text: qsTr("Last error age:")
font.bold: true
}
diff --git a/OcppPage.qml b/OcppPage.qml
index c4bb62a..91c64f0 100644
--- a/OcppPage.qml
+++ b/OcppPage.qml
@@ -17,7 +17,7 @@ NavigationPage {
anchors.fill: parent
columns: 2
- Label {
+ Text {
text: qsTr("Trying to connect:")
font.bold: true
}
@@ -26,7 +26,7 @@ NavigationPage {
apiKey: "ocpps"
}
- Label {
+ Text {
text: qsTr("Is connected:")
font.bold: true
}
@@ -35,7 +35,7 @@ NavigationPage {
apiKey: "ocppc"
}
- Label {
+ Text {
text: qsTr("Connected since:")
font.bold: true
}
@@ -50,7 +50,7 @@ NavigationPage {
text: connectedSince.value ? formatDuration(connectedSince.value - rebootTime.value) : ""
}
- Label {
+ Text {
text: qsTr("Is accepted:")
font.bold: true
}
@@ -59,7 +59,7 @@ NavigationPage {
apiKey: "ocppa"
}
- Label {
+ Text {
text: qsTr("Accepted since:")
font.bold: true
}
@@ -74,7 +74,7 @@ NavigationPage {
text: acceptedSince.value ? formatDuration(acceptedSince.value - rebootTime.value) : ""
}
- Label {
+ Text {
text: qsTr("Last error:")
font.bold: true
}
@@ -84,7 +84,7 @@ NavigationPage {
wrapMode: Text.Wrap
}
- Label {
+ Text {
text: qsTr("Last error age:")
font.bold: true
}
@@ -99,7 +99,7 @@ NavigationPage {
text: lastErrorAge.value ? formatDuration(lastErrorAge.value - rebootTime.value) : ""
}
- Label {
+ Text {
text: qsTr("Chargepoint status:")
font.bold: true
}
diff --git a/SchedulerDayPage.qml b/SchedulerDayPage.qml
new file mode 100644
index 0000000..03f3879
--- /dev/null
+++ b/SchedulerDayPage.qml
@@ -0,0 +1,47 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+NavigationPage {
+ title: qsTr("Scheduler day")
+
+ TimeComponentLabel {
+ id: openDialogLabel
+ width: parent.width - 80
+ anchors.centerIn: parent
+ font.pixelSize: Qt.application.font.pixelSize * 8
+ renderTypeQuality: Text.VeryHighRenderTypeQuality
+ interactive: !timePickerDialog.opened
+
+ text: Qt.formatTime(new Date(1970, 1, 1, timePickerDialog.hours, timePickerDialog.minutes), "hh:mm")
+
+ onTapped: timePickerDialog.openWithMode(TimePicker.Mode.Hours)
+ }
+
+ ColumnLayout {
+ // We always want the openDialogLabel to be centered in the window, not us.
+ // For that reason, we use anchors rather than putting the root items into a ColumnLayout.
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: openDialogLabel.bottom
+ anchors.topMargin: 24
+ spacing: 12
+
+ Switch {
+ id: is24HourSwitch
+ text: qsTr("24 Hour")
+ checked: timePickerDialog.is24Hour
+ }
+ // Switch {
+ // id: darkThemeSwitch
+ // text: qsTr("Dark")
+ // }
+ }
+
+ TimePickerDialog {
+ id: timePickerDialog
+ anchors.centerIn: parent
+ is24Hour: is24HourSwitch.checked
+
+ onTimeAccepted: print("A time was chosen - do something here!")
+ }
+}
diff --git a/SchedulerPage.qml b/SchedulerPage.qml
index 47d2815..43b4f73 100644
--- a/SchedulerPage.qml
+++ b/SchedulerPage.qml
@@ -5,9 +5,138 @@ import QtQuick.Layouts
NavigationPage {
title: qsTr("Scheduler")
- Text {
- text: "TODO"
+ WhiteBox {
+ Layout.fillWidth: true
- Layout.fillHeight: true
+ ColumnLayout {
+ anchors.fill: parent
+
+ Image {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 50
+ source: "material-icons/grid_guides.svg"
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Text {
+ Layout.fillWidth: true
+ font.pointSize: 20
+ font.bold: true
+ text: qsTr("About Schedule")
+ wrapMode: Text.Wrap
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Text {
+ Layout.fillWidth: true
+ text: qsTr("Customize your charging routine on workdays, Saturdays, and Sundays. Define specific time slots for charging or blocking to perfectly align with your weekly schedule. Schedule can only start charging if all other functions like eco or access settings allow charging.")
+ wrapMode: Text.Wrap
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Image {
+ Layout.preferredWidth: 50
+ Layout.fillHeight: true
+ source: "material-icons/grid_guides.svg"
+ fillMode: Image.PreserveAspectFit
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+
+ Text {
+ Layout.fillWidth: true
+
+ font.bold: true
+ text: qsTr("Charging schedule")
+ wrapMode: Text.Wrap
+ }
+
+ Text {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("go-e Charger will exclusively charge your car during the time slots you set, and won't charge for the rest of the time.")
+ wrapMode: Text.Wrap
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Image {
+ Layout.preferredWidth: 50
+ Layout.fillHeight: true
+ source: "material-icons/grid_guides.svg"
+ fillMode: Image.PreserveAspectFit
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+
+ Text {
+ Layout.fillWidth: true
+
+ font.bold: true
+ text: qsTr("Blocking schedule")
+ wrapMode: Text.Wrap
+ }
+
+ Text {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("go-e Charger will block charging your car during the time slots you set, but it will charge your car for the rest of the time.")
+ wrapMode: Text.Wrap
+ }
+ }
+ }
+ }
+ }
+
+ Text {
+ text: qsTr("Workdays")
+ font.bold: true
+ font.pointSize: 15
+ height: implicitHeight + 30
+ }
+
+ SimpleNavigationItem {
+ text: qsTr("Create workdays schedule")
+ color: "blue"
+ component: "SchedulerDayPage.qml"
+ componentArgs: { day: 0 }
+ }
+
+ Text {
+ text: qsTr("Saturdays")
+ font.bold: true
+ font.pointSize: 15
+ height: implicitHeight + 30
+ }
+
+ SimpleNavigationItem {
+ text: qsTr("Create saturdays schedule")
+ color: "blue"
+ component: "SchedulerDayPage.qml"
+ componentArgs: { day: 1 }
+ }
+
+ Text {
+ text: qsTr("Sundays")
+ font.bold: true
+ font.pointSize: 15
+ height: implicitHeight + 30
+ }
+
+ SimpleNavigationItem {
+ text: qsTr("Create sundays schedule")
+ color: "blue"
+ component: "SchedulerDayPage.qml"
+ componentArgs: { day: 2 }
}
}
diff --git a/SimpleNavigationItem.qml b/SimpleNavigationItem.qml
new file mode 100644
index 0000000..e9c250d
--- /dev/null
+++ b/SimpleNavigationItem.qml
@@ -0,0 +1,21 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+ItemDelegate {
+ id: navigationItem
+
+ property color color
+ property string component
+ property var componentArgs: undefined
+
+ Layout.fillWidth: true
+
+ Component.onCompleted: {
+ background.radius = 5
+ background.color = 'white'
+ contentItem.children[0].color = navigationItem.color
+ }
+
+ onClicked: stackView.push(navigationItem.component, navigationItem.componentArgs)
+}
diff --git a/TimeComponentLabel.qml b/TimeComponentLabel.qml
new file mode 100644
index 0000000..3496a60
--- /dev/null
+++ b/TimeComponentLabel.qml
@@ -0,0 +1,26 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls.Material
+
+Label {
+ id: root
+ fontSizeMode: Label.Fit
+ horizontalAlignment: Label.AlignHCenter
+ verticalAlignment: Label.AlignVCenter
+
+ Material.foreground: Material.theme === Material.Light
+ ? Material.color(Material.Indigo, !dim ? Material.Shade500 : Material.Shade100)
+ : Material.color(Material.Indigo, dim ? Material.Shade300 : Material.Shade100)
+
+ Layout.fillHeight: true
+
+ property bool dim: false
+ property alias interactive: tapHandler.enabled
+
+ signal tapped
+
+ TapHandler {
+ id: tapHandler
+ onTapped: root.tapped()
+ }
+}
diff --git a/TimePicker.qml b/TimePicker.qml
new file mode 100644
index 0000000..63ba952
--- /dev/null
+++ b/TimePicker.qml
@@ -0,0 +1,316 @@
+import QtQuick
+import QtQuick.Controls.Material
+
+Item {
+ id: root
+ implicitWidth: 250
+ implicitHeight: 250
+
+ enum Mode {
+ Hours,
+ Minutes
+ }
+
+ property int mode: TimePicker.Mode.Hours
+ property int hours
+ property int minutes
+ property bool is24Hour
+ property bool interactive: true
+
+ // The mode that the label delegates see, so that we can
+ // animate their opacity before their text changes.
+ property int __effectiveMode: TimePicker.Mode.Hours
+ // For 12 hour pickers, we can use 0 to 60 to represent all values.
+ property int __value: 0
+ // For 24 hour pickers, we need to store this extra flag.
+ property bool __is24HourValueSelected
+ // How many values the arm should snap to at a time.
+ readonly property int __stepSize: __getStepSize(mode)
+ readonly property int __to: 60
+ readonly property int __labelAngleStepSize: 360 / 12
+ property bool __switchingModes
+
+ // This signal could be used if TimePicker is used as a standalone component.
+ // It's emitted when the minute is selected.
+ signal accepted()
+
+ // Convenience for setting each property individually, but also
+ // ensures that the selector arm is properly rotated when there is
+ // a programmatic change in hours or minutes but not mode.
+ function openWith(mode, hours, minutes) {
+ root.mode = mode
+ root.hours = hours
+ root.minutes = minutes
+ __updateAfterModeOrTimeChange()
+ }
+
+ // Until QML gets private properties (QTBUG-11984), use the traditional
+ // double-underscore convention.
+ function __angleForValue(value: int): real {
+ return (value / __to) * 360
+ }
+
+ function __getStepSize(mode) {
+ return mode === TimePicker.Mode.Hours ? 5 : 1
+ }
+
+ function __updateAfterModeOrTimeChange() {
+ // We use a function for this rather than a binding, because we could be called before the
+ // __stepSize binding is evaluated.
+ if (mode === TimePicker.Mode.Hours) {
+ // modulo the hours value by __to because we want 60 (12) to be 0.
+ __value = hours * __getStepSize(mode) % __to
+ } else {
+ __value = minutes
+ }
+
+ __is24HourValueSelected = mode === TimePicker.Mode.Hours && hours >= 13
+ }
+
+ onModeChanged: __updateAfterModeOrTimeChange()
+
+ onIs24HourChanged: {
+ // Don't allow 24-hour values when we're not a 24-hour picker.
+ if (!is24Hour && hours > 12)
+ hours = 12
+ }
+
+ // Center dot.
+ Rectangle {
+ width: 6
+ height: 6
+ radius: width / 2
+ color: Material.primary
+ anchors.centerIn: parent
+ z: 1
+ }
+
+ Rectangle {
+ id: contentContainer
+ objectName: "contentContainer"
+ width: Math.min(parent.width, parent.height)
+ height: width
+ radius: width / 2
+ anchors.centerIn: parent
+ color: Material.theme === Material.Light ? "#eeeeee" : "#626262"
+
+ // Animate this so that we don't need an intermediate parent item for the
+ // labels to animate the opacity of that instead. That item would be required
+ // because we don't want to change the opacity of the contentContainer Rectangle.
+ property real labelOpacity: 1
+
+ function updateValueAfterPressPointChange() {
+ const y1 = height / 2
+ const x1 = width / 2
+ const y2 = tapHandler.point.position.y
+ const x2 = tapHandler.point.position.x
+ const yDistance = y2 - y1
+ const xDistance = x2 - x1
+ const angle = Math.atan2(yDistance, xDistance)
+
+ let angleInDegrees = (angle * (180 / Math.PI)) + 90.0
+ if (angleInDegrees < 0)
+ angleInDegrees = 360 + angleInDegrees
+
+ const normalisedAngle = angleInDegrees / 360.0
+ const rawValue = normalisedAngle * __to
+ // Snap to each step.
+ const steppedValue = Math.round(rawValue / __stepSize) * __stepSize
+ root.__value = steppedValue
+ // Account for the area where the angle wraps around from 360 to 0,
+ // otherwise values from 59.5 to 59.999[...] will register as 60 instead of 0.
+ if (rawValue > __to - __stepSize / 2)
+ root.__value = 0
+
+ const distanceFromCenter = Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2))
+ // Only allow selecting 24 hour values when it's in the correct mode.
+ root.__is24HourValueSelected = root.is24Hour && root.__effectiveMode === TimePicker.Mode.Hours
+ && distanceFromCenter < distanceFromCenterForLabels(true) + selectionIndicator.height * 0.5
+ }
+
+ // Returns the distance from our center at which a label should be centered over given is24Hour.
+ function distanceFromCenterForLabels(is24Hour) {
+ return contentContainer.radius - (is24Hour
+ ? selectionIndicator.height * 1.5 : selectionIndicator.height * 0.5)
+ }
+
+ states: [
+ State {
+ name: "hours"
+ when: root.mode === TimePicker.Mode.Hours
+ },
+ State {
+ name: "minutes"
+ when: root.mode === TimePicker.Mode.Minutes
+ }
+ ]
+
+ transitions: [
+ Transition {
+ // When the picker isn't interactive (e.g. when a dialog is opening),
+ // we shouldn't animate the opacity of the labels, as it looks wrong,
+ // and should only happen when switching between modes while the
+ // picker was already visible.
+ enabled: root.interactive
+
+ SequentialAnimation {
+ NumberAnimation {
+ target: contentContainer
+ property: "labelOpacity"
+ from: 1
+ to: 0
+ duration: 100
+ }
+
+ ScriptAction {
+ script: root.__effectiveMode = root.mode
+ }
+
+ NumberAnimation {
+ target: contentContainer
+ property: "labelOpacity"
+ from: 0
+ to: 1
+ duration: 100
+ }
+ }
+ },
+ Transition {
+ enabled: !root.interactive
+
+ // Since the transition above doesn't run when we're not interactive,
+ // we need to do the immediate property change here.
+ // See QTBUG-13268 for why we use a ScriptAction and not PropertyAction.
+ ScriptAction {
+ script: root.__effectiveMode = root.mode
+ }
+ }
+
+ ]
+
+ TapHandler {
+ id: tapHandler
+ gesturePolicy: TapHandler.ReleaseWithinBounds
+ // Don't allow input while switching modes, or a click on an hour could go through to a minute.
+ enabled: root.interactive && root.__effectiveMode === root.mode
+
+ onPointChanged: {
+ if (pressed) {
+ // Don't call this when not pressed, as the position will be invalid.
+ contentContainer.updateValueAfterPressPointChange()
+
+ // Update the value (like a "live" Slider) while the pointer position changes.
+ if (mode === TimePicker.Mode.Hours) {
+ root.hours = root.__value / root.__stepSize
+
+ if (root.hours === 0) {
+ // A value of 0 (when it's not a 24-hour picker) is 12.
+ // When it is a 24-hour picker, it's 0.
+ if (!root.__is24HourValueSelected)
+ root.hours = 12
+ } else if (root.__is24HourValueSelected) {
+ root.hours += 12
+ }
+ } else {
+ root.minutes = root.__value
+ }
+ } else {
+ // Select the value that was chosen in the press code above.
+ if (mode === TimePicker.Mode.Hours) {
+ mode = TimePicker.Mode.Minutes
+ } else {
+ // Does nothing in our example, but could be used to hide the dialog
+ // if it didn't have an OK button to accept it.
+ root.accepted()
+ }
+ }
+ }
+ }
+
+ // The line connecting the center dot to the selection indicator.
+ Rectangle {
+ id: selectionArm
+ objectName: "selectionArm"
+ width: 2
+ height: contentContainer.distanceFromCenterForLabels(root.__is24HourValueSelected)
+ color: Material.primary
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.verticalCenter
+ rotation: root.__angleForValue(root.__value)
+ transformOrigin: Item.Bottom
+ antialiasing: true
+
+ Rectangle {
+ id: selectionIndicator
+ objectName: "selectionIndicator"
+ width: 40
+ height: 40
+ radius: width / 2
+ color: Material.primary
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.top
+
+ Rectangle {
+ width: 4
+ height: 4
+ radius: width / 2
+ color: Material.color(Material.Indigo, Material.Shade100)
+ anchors.centerIn: parent
+ // Only show the circle within the indicator between minute labels.
+ visible: root.__effectiveMode === TimePicker.Mode.Minutes
+ && root.__value % 5 !== 0
+ }
+ }
+ }
+
+ Repeater {
+ id: labelRepeater
+ model: root.mode === TimePicker.Mode.Hours && root.is24Hour ? 24 : 12
+ delegate: Label {
+ id: labelDelegate
+ text: displayValue
+ font.pixelSize: Qt.application.font.pixelSize * (is24HourValue ? 0.85 : 1)
+ rotation: -rotationTransform.angle
+ opacity: contentContainer.labelOpacity
+ anchors.centerIn: parent
+
+ required property int index
+ // From 0 to 60.
+ readonly property int value: (index * 5) % root.__to
+ property int displayValue: root.__effectiveMode === TimePicker.Mode.Hours
+ ? index === 0
+ ? 12
+ : index === 12
+ ? 0
+ : index
+ : value
+ // The picker's current value can equal ours but we still might not be current -
+ // it depends on whether it's a 24 hour value or not.
+ readonly property bool current: root.__value === value && root.__is24HourValueSelected == is24HourValue
+ readonly property bool is24HourValue: index >= 12
+
+ Material.foreground: current
+ // When the selection arm is over us, invert our color so it's legible.
+ ? Material.color(Material.Indigo, Material.Shade100)
+ : root.Material.theme === Material.Light
+ ? is24HourValue ? "#686868" : "#484848"
+ : Material.color(Material.Indigo, is24HourValue ? Material.Shade300 : Material.Shade100)
+
+ transform: [
+ Translate {
+ // We're already centered in our parent, so we can use this function
+ // to determine our position, which doesn't need to be aware of our height -
+ // it just needs to tell us where our center position should be.
+ y: -contentContainer.distanceFromCenterForLabels(labelDelegate.is24HourValue)
+ },
+ Rotation {
+ id: rotationTransform
+ angle: root.__angleForValue(labelDelegate.value)
+ origin.x: labelDelegate.width / 2
+ origin.y: labelDelegate.height / 2
+ }
+ ]
+ }
+ }
+ }
+}
diff --git a/TimePickerDialog.qml b/TimePickerDialog.qml
new file mode 100644
index 0000000..56eda85
--- /dev/null
+++ b/TimePickerDialog.qml
@@ -0,0 +1,83 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls.Material
+
+Dialog {
+ id: root
+ standardButtons: Dialog.Ok | Dialog.Cancel
+ closePolicy: Dialog.NoAutoClose
+ // We don't want so much space between the picker and dialog buttons.
+ bottomPadding: 8
+
+ property int hours: 12
+ property int minutes: 0
+ property alias is24Hour: timePicker.is24Hour
+
+ property int __initialHours
+ property int __initialMinutes
+
+ signal timeAccepted
+ signal timeRejected
+
+ function openWithMode(mode) {
+ timePicker.openWith(mode !== undefined ? mode : TimePicker.Mode.Hours, hours, minutes)
+
+ __initialHours = hours
+ __initialMinutes = minutes
+
+ open()
+ }
+
+ onAccepted: {
+ root.hours = timePicker.hours
+ root.minutes = timePicker.minutes
+ root.timeAccepted()
+ }
+ onRejected: {
+ hours = __initialHours
+ minutes = __initialMinutes
+ // Also reset the picker's time so that the onIs24HourChanged handler below works as expected.
+ timePicker.hours = __initialHours
+ timePicker.minutes = __initialMinutes
+ root.timeRejected()
+ }
+
+ // If is24Hour changes programmatically (only while we're not open),
+ // make sure we adapt to any possible clamping it did in the transition from 24 hours to 12.
+ onIs24HourChanged: {
+ if (!opened)
+ root.hours = timePicker.hours
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 12
+
+ TimePickerLabel {
+ id: timeLabel
+ // Use TimePicker's time, because that is updated live, whereas our values
+ // are only changed once we've been accepted.
+ time: new Date(1970, 1, 1, timePicker.hours, timePicker.minutes)
+ hoursActive: timePicker.mode === TimePicker.Mode.Hours
+ showAmPm: !timePicker.is24Hour
+
+ Layout.fillWidth: true
+ // Push us down a bit so we're not so close to the top of the dialog.
+ Layout.topMargin: 8
+
+ onHoursSelected: timePicker.mode = TimePicker.Mode.Hours
+ onMinutesSelected: timePicker.mode = TimePicker.Mode.Minutes
+ }
+
+ TimePicker {
+ id: timePicker
+ objectName: "timePicker"
+ // Our TapHandler may handle the click event on the Label if we don't do this,
+ // causing an hour to be inadvertently selected.
+ interactive: root.opened
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ }
+}
diff --git a/TimePickerLabel.qml b/TimePickerLabel.qml
new file mode 100644
index 0000000..c3aeceb
--- /dev/null
+++ b/TimePickerLabel.qml
@@ -0,0 +1,121 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls.Material
+
+Item {
+ id: root
+ // Use TextMetrics' boundingRect.width rather than a RowLayout's implicitWidth
+ // because the latter can cause TimePickerDialog to jump around when the label text changes.
+ implicitWidth: fullTextMetrics.boundingRect.width + amPmLayout.implicitWidth + 80
+ implicitHeight: fullTextMetrics.boundingRect.height
+
+ property var time
+ property bool am: true
+ property bool showAmPm: true
+
+ property bool hoursActive: true
+
+ property int fontPixelSize: Qt.application.font.pixelSize * 4
+
+ signal hoursSelected
+ signal minutesSelected
+ signal amSelected
+ signal pmSelected
+
+ TextMetrics {
+ id: fullTextMetrics
+ font: hoursLabel.font
+ text: "99:99"
+ }
+
+ TextMetrics {
+ id: hourTextMetrics
+ font.family: hoursLabel.font.family
+ font.pixelSize: hoursLabel.fontInfo.pixelSize
+ text: "99"
+ }
+
+ TimeComponentLabel {
+ id: hoursLabel
+ objectName: "hoursLabel"
+ text: Qt.formatTime(root.time, "hh")
+ font.pixelSize: root.fontPixelSize
+ // Avoid any jumping around by using a dedicated TextMetrics object
+ // for our label too, and position ourselves based on its width.
+ x: colonLabel.x - hourTextMetrics.boundingRect.width - 4
+ anchors.verticalCenter: parent.verticalCenter
+ dim: !root.hoursActive
+
+ onTapped: root.hoursSelected()
+ }
+
+ TimeComponentLabel {
+ id: colonLabel
+ text: ":"
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ font.pixelSize: root.fontPixelSize
+ dim: true
+ }
+
+ TimeComponentLabel {
+ id: minutesLabel
+ objectName: "minutesLabel"
+ text: Qt.formatTime(root.time, "mm")
+ font.pixelSize: root.fontPixelSize
+ anchors.left: colonLabel.right
+ anchors.leftMargin: 4
+ anchors.verticalCenter: parent.verticalCenter
+ dim: root.hoursActive
+
+ onTapped: root.minutesSelected()
+ }
+
+ ColumnLayout {
+ id: amPmLayout
+ visible: root.showAmPm
+ // We also need to avoid the AM/PM label jumping around,
+ // so rather than anchor it to the right of the minutes label,
+ // we use colonLabel's horizontal center (which never changes), and fullTextMetrics.
+ anchors.left: colonLabel.horizontalCenter
+ anchors.leftMargin: fullTextMetrics.boundingRect.width / 2 + 12
+ anchors.verticalCenter: minutesLabel.verticalCenter
+
+ spacing: 0
+
+ ToolButton {
+ objectName: "amButton"
+ text: qsTr("AM")
+ autoExclusive: true
+
+ Material.foreground: Material.color(Material.Indigo,
+ root.am ? Material.Shade500 : Material.Shade100)
+
+ // Set the size explicitly to ensure that the buttons aren't too large.
+ // We also add 1 to the width because we want square ripple effects, not round.
+ Layout.preferredWidth: (implicitWidth * 0.7) + 1
+ Layout.preferredHeight: (implicitHeight * 0.7)
+
+ onClicked: {
+ root.am = true
+ root.amSelected()
+ }
+ }
+ ToolButton {
+ objectName: "pmButton"
+ text: qsTr("PM")
+ autoExclusive: true
+
+ Material.foreground: Material.color(Material.Indigo,
+ !root.am ? Material.Shade500 : Material.Shade100)
+
+ Layout.preferredWidth: (implicitWidth * 0.7) + 1
+ Layout.preferredHeight: (implicitHeight * 0.7)
+
+ onClicked: {
+ root.am = false
+ root.pmSelected()
+ }
+ }
+ }
+}
diff --git a/WiFiPage.qml b/WiFiPage.qml
index e3aab35..6b0c19a 100644
--- a/WiFiPage.qml
+++ b/WiFiPage.qml
@@ -13,7 +13,7 @@ NavigationPage {
GridLayout {
columns: 2
- Label {
+ Text {
text: qsTr("Status:")
}
diff --git a/i18n/qml_de.ts b/i18n/qml_de.ts
index 8459d94..b02bba6 100644
--- a/i18n/qml_de.ts
+++ b/i18n/qml_de.ts
@@ -544,20 +544,26 @@
Ethernet
-
-
+
+
OCPP
OCPP
-
-
+
+
Cloud
Cloud
+ MQTT
+ MQTT
+
+
+
+
API Settings
API Einstellungen
@@ -1379,6 +1385,51 @@
Einstellungen
+
+ MqttPage
+
+
+
+ MQTT
+ MQTT
+
+
+
+
+ Trying to connect:
+ Versuche zu verbinden:
+
+
+
+
+ Is connected:
+ Ist verbunden:
+
+
+
+
+ Connected since:
+ Verbunden seit:
+
+
+
+
+ Command Counter:
+ Befehlszähler:
+
+
+
+
+ Last error:
+ Letzer Fehler:
+
+
+
+
+ Last error age:
+ Alter des letzten Fehlers:
+
+
NamePage
@@ -1407,11 +1458,113 @@
OcppPage
-
-
+
+
OCPP
OCPP
+
+
+
+ Trying to connect:
+ Versuche zu verbinden:
+
+
+
+
+ Is connected:
+ Ist verbunden:
+
+
+
+
+ Connected since:
+ Verbunden seit:
+
+
+
+
+ Is accepted:
+ Ist akzeptiert:
+
+
+
+
+ Accepted since:
+ Akzeptiert seit:
+
+
+
+
+ Last error:
+ Letzer Fehler:
+
+
+
+
+ Last error age:
+ Alter des letzten Fehlers:
+
+
+
+
+ Chargepoint status:
+ Chargepoint Status:
+
+
+
+
+ Available
+
+
+
+
+
+ Preparing
+
+
+
+
+
+ Charging
+
+
+
+
+
+ SuspendedEVSE
+
+
+
+
+
+ SuspendedEV
+
+
+
+
+
+ Finishing
+
+
+
+
+
+ Reserved
+
+
+
+
+
+ Unavailable
+
+
+
+
+
+ Faulted
+
+
PasswordPage
@@ -1447,6 +1600,21 @@
Gerät %0
+
+ SchedulerDayPage
+
+
+
+ Scheduler day
+
+
+
+
+
+ 24 Hour
+
+
+
SchedulerPage
@@ -1455,6 +1623,98 @@
Scheduler
Ladetimer
+
+
+
+ About Schedule
+ Über den Ladetimer
+
+
+
+
+ Customize your charging routine on workdays, Saturdays, and Sundays. Define specific time slots for charging or blocking to perfectly align with your weekly schedule. Schedule can only start charging if all other functions like eco or access settings allow charging.
+ Passe deine Laderoutine an Werktagen, Samstagen und Sonntagen an. Lege bestimmte Zeitfenster für das Aufladen oder Blockieren fest, um deinen Wochenplan perfekt abzustimmen. Der Ladevorgang kann nur gestartet werden, wenn alle anderen Funktionen wie Eco Modus oder Zugangskontrolle das Laden zulassen.
+
+
+
+
+ Charging schedule
+ Laden erlauben
+
+
+
+
+ go-e Charger will exclusively charge your car during the time slots you set, and won't charge for the rest of the time.
+ go-e Charger wird das Fahrzeug ausschließlich in den von dir eingestellten Zeitfenstern aufladen und in der restlichen Zeit nicht laden.
+
+
+
+
+ Blocking schedule
+ Laden blockieren
+
+
+
+
+ go-e Charger will block charging your car during the time slots you set, but it will charge your car for the rest of the time.
+ go-e Charger wird das Aufladen deines Fahrzeugs während des von dir eingestellten Zeitfensters verhindern. In der restlichen Zeit kann dein Auto geladen werden.
+
+
+
+
+ Workdays
+
+
+
+
+
+ Create workdays schedule
+
+
+
+
+
+ Saturdays
+
+
+
+
+
+ Create saturdays schedule
+
+
+
+
+
+ Sundays
+
+
+
+
+
+ Create sundays schedule
+
+
+
+ About eco
+ Über Eco
+
+
+ Flexible energy tariff
+ Flexibler Energietarif
+
+
+ We offer to charge your car during periods of low electricity price, If you have an electricity contract based on flexible energy tariffs.
+ Du kannst dein Auto in Zeiten niedriger Strompreise aufladen, sofern du einen Stromvertrag mit flexiblen Tarifen hast.
+
+
+ PV surplus energy
+ PV-Überschuss
+
+
+ By connecting a PV system and a controller to your charger, you can charge your EV particularly cheaply and use clean energy at the same time.
+ Wenn du eine PV-Anlage und einen Controller an deinen Charger anschließt, kannst du dein Elektroauto besonders günstig aufladen und gleichzeitig grüne Energie nutzen.
+
SecurityPage
@@ -1665,98 +1925,104 @@
Kategorien
-
-
+
+
Wi-Fi
WLAN
-
-
+
+
Hotspot
Hotspot
-
-
+
+
Ethernet
Ethernet
-
-
+
+
OCPP
OCPP
-
-
+
+
Cloud
Cloud
-
-
+
+
+ MQTT
+ MQTT
+
+
+
+
API Settings
API Einstellungen
-
-
+
+
Name
Name
-
-
+
+
Switch Language
Sprache ändern
-
-
+
+
Notifications
Benachrichtigungen
-
-
+
+
Date and time
Datum und Uhrzeit
-
-
+
+
LED
LED
-
-
+
+
Controller
Controller
-
-
+
+
Display settings
Display
-
-
+
+
Firmware
Firmware
-
-
+
+
Hardware information
Hardwareinformationen
-
-
+
+
Licenses
Lizenzen
@@ -1785,8 +2051,8 @@
Sensoren • Kategorien
-
-
+
+
Connection
Verbindung
@@ -1795,8 +2061,8 @@
WLAN • Hotspot • OCPP • API Einstellungen
-
-
+
+
General
Allgemein
@@ -1805,8 +2071,8 @@
Name • Sprache ändern • Benachrichtigungen • Datum und Uhrzeit • LED • Controller
-
-
+
+
About
Über
@@ -1824,6 +2090,21 @@
Sprache ändern
+
+ TimePickerLabel
+
+
+
+ AM
+
+
+
+
+
+ PM
+
+
+
WiFiErrorsPage
diff --git a/qmldir b/qmldir
index 782a4e0..c0d4666 100644
--- a/qmldir
+++ b/qmldir
@@ -53,13 +53,19 @@ EVChargerApp 1.0 PasswordPage.qml
EVChargerApp 1.0 PvSurplusPage.qml
EVChargerApp 1.0 RebootPage.qml
EVChargerApp 1.0 RequestStatusText.qml
+EVChargerApp 1.0 SchedulerDayPage.qml
EVChargerApp 1.0 SchedulerPage.qml
EVChargerApp 1.0 SecurityPage.qml
EVChargerApp 1.0 SelectLogicModeItem.qml
EVChargerApp 1.0 SensorsConfigurationPage.qml
EVChargerApp 1.0 SetPriceLimitPage.qml
EVChargerApp 1.0 SettingsTabPage.qml
+EVChargerApp 1.0 SimpleNavigationItem.qml
EVChargerApp 1.0 SwitchLanguagePage.qml
+EVChargerApp 1.0 TimeComponentLabel.qml
+EVChargerApp 1.0 TimePickerDialog.qml
+EVChargerApp 1.0 TimePickerLabel.qml
+EVChargerApp 1.0 TimePicker.qml
EVChargerApp 1.0 VerticalTabButton.qml
EVChargerApp 1.0 WhiteBox.qml
EVChargerApp 1.0 WiFiErrorsPage.qml