General design overhaul and added lots of new languages (finnish swedish norwegian turkish)
Build Linux and Android / build (push) Successful in 2m28s

This commit is contained in:
2026-08-16 17:06:27 +02:00
parent cb7bb7751a
commit 0ee784cf47
39 changed files with 16751 additions and 1210 deletions
+16 -3
View File
@@ -1,6 +1,6 @@
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Quick WebSockets LinguistTools Charts)
qt_standard_project_setup(REQUIRES 6.6 I18N_TRANSLATED_LANGUAGES de)
qt_standard_project_setup(REQUIRES 6.6 I18N_TRANSLATED_LANGUAGES de sv fi nb tr)
qt_add_executable(evcharger-app WIN32 MACOSX_BUNDLE
appsettings.cpp
@@ -50,6 +50,8 @@ if (ANDROID)
)
endif()
set_source_files_properties(qml/AppTheme.qml qml/Constants.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
qt_add_qml_module(evcharger-app
URI EVChargerApp
VERSION 1.0
@@ -65,6 +67,7 @@ qt_add_qml_module(evcharger-app
qml/ApiSettingsPage.qml
qml/AppInstance.qml
qml/AppSettingsPage.qml
qml/AppTheme.qml
qml/BaseNavigationPage.qml
qml/CablePage.qml
qml/CarPage.qml
@@ -106,6 +109,7 @@ qt_add_qml_module(evcharger-app
qml/InformationsTabPage.qml
qml/InformationChartPanel.qml
qml/KwhLimitPage.qml
qml/LanguageSelector.qml
qml/LedPage.qml
qml/LicensesPage.qml
qml/LoadBalancingPage.qml
@@ -131,6 +135,9 @@ qt_add_qml_module(evcharger-app
qml/SelectPhaseSwitchModeItem.qml
qml/SendMessageButton.qml
qml/SensorsConfigurationPage.qml
qml/SettingsCard.qml
qml/SettingsLabel.qml
qml/SettingsSectionLabel.qml
qml/SetPriceLimitPage.qml
qml/SettingsTabPage.qml
qml/SetValueHelper.qml
@@ -166,14 +173,20 @@ qt_add_qml_module(evcharger-app
qml/images/geminiFix.png
qml/js/qrcode.min.js
qml/material-icons/add.svg
qml/material-icons/arrow_back.svg
qml/material-icons/grid_guides.svg
qml/material-icons/open_in_new.svg
qml/material-icons/settings.svg
qml/flags/de.svg
qml/flags/fi.svg
qml/flags/gb.svg
qml/flags/no.svg
qml/flags/se.svg
qml/flags/system.svg
qml/flags/tr.svg
qml/ui-icons/MaterialIcons-Regular.ttf
)
set_source_files_properties(qml/Constants.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
target_link_libraries(evcharger-app PUBLIC
Qt6::Core
Qt6::Gui
+43
View File
@@ -3,6 +3,49 @@
#include <QFile>
#include <QQmlFile>
QLocale::Language AppSettings::uiLanguage() const
{
const QVariant storedLanguage = value("uiLanguage");
bool isNumber = false;
const int language = storedLanguage.toInt(&isNumber);
if (isNumber && language >= QLocale::AnyLanguage && language <= QLocale::LastLanguage)
return static_cast<QLocale::Language>(language);
// Also understand string values written by development versions.
if (storedLanguage.toString() == QStringLiteral("de"))
return QLocale::German;
if (storedLanguage.toString() == QStringLiteral("en"))
return QLocale::English;
if (storedLanguage.toString() == QStringLiteral("sv"))
return QLocale::Swedish;
if (storedLanguage.toString() == QStringLiteral("fi"))
return QLocale::Finnish;
if (storedLanguage.toString() == QStringLiteral("nb")
|| storedLanguage.toString() == QStringLiteral("no"))
return QLocale::NorwegianBokmal;
if (storedLanguage.toString() == QStringLiteral("tr"))
return QLocale::Turkish;
return QLocale::AnyLanguage;
}
void AppSettings::setUiLanguage(QLocale::Language uiLanguage)
{
if (uiLanguage < QLocale::AnyLanguage || uiLanguage > QLocale::LastLanguage)
return;
if (uiLanguage == this->uiLanguage())
return;
if (uiLanguage == QLocale::AnyLanguage)
remove("uiLanguage");
else
setValue("uiLanguage", static_cast<int>(uiLanguage));
emit uiLanguageChanged(uiLanguage);
}
bool AppSettings::loadSolalawebKey(const QString &url)
{
QFile file{QQmlFile::urlToLocalFileOrQrc(url)};
+19
View File
@@ -1,5 +1,6 @@
#pragma once
#include <QLocale>
#include <QtQml/qqmlregistration.h>
#include "goesettings.h"
@@ -8,8 +9,26 @@ class AppSettings : public GoeSettings
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QLocale::Language uiLanguage READ uiLanguage WRITE setUiLanguage NOTIFY uiLanguageChanged FINAL)
public:
enum UiLanguageValue {
SystemLanguage = QLocale::AnyLanguage,
GermanLanguage = QLocale::German,
EnglishLanguage = QLocale::English,
SwedishLanguage = QLocale::Swedish,
FinnishLanguage = QLocale::Finnish,
NorwegianLanguage = QLocale::NorwegianBokmal,
TurkishLanguage = QLocale::Turkish,
};
Q_ENUM(UiLanguageValue)
QLocale::Language uiLanguage() const;
void setUiLanguage(QLocale::Language uiLanguage);
Q_INVOKABLE bool loadSolalawebKey(const QString &url);
Q_INVOKABLE bool loadSolalawebCert(const QString &url);
signals:
void uiLanguageChanged(QLocale::Language uiLanguage);
};
+1627 -797
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+39 -13
View File
@@ -1,7 +1,10 @@
#include <QApplication>
#include <QTranslator>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QApplication>
#include <QLocale>
#include <QTranslator>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "appsettings.h"
int main(int argc, char *argv[])
{
@@ -20,15 +23,38 @@ int main(int argc, char *argv[])
QCoreApplication::setOrganizationDomain("brunner.ninja");
QCoreApplication::setApplicationName("evcharger-app");
QApplication app(argc, argv);
QTranslator translator;
if (!translator.load(QLocale(), "qml", "_", ":/EVChargerApp/i18n/"))
qWarning("could not load translations!");
app.installTranslator(&translator);
QQmlApplicationEngine engine;
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
QApplication app(argc, argv);
AppSettings settings;
QTranslator translator;
QQmlApplicationEngine engine;
const auto applyUiLanguage = [&](QLocale::Language language) {
app.removeTranslator(&translator);
const QLocale translationLocale = language == QLocale::AnyLanguage
? QLocale::system()
: QLocale(language);
// English is the source language. Missing catalogs naturally fall back
// to English. QLocale's formatting locale remains untouched.
if (translationLocale.language() != QLocale::English
&& translator.load(translationLocale,
QStringLiteral("qml"),
QStringLiteral("_"),
QStringLiteral(":/EVChargerApp/i18n/")))
app.installTranslator(&translator);
if (!engine.rootObjects().isEmpty())
engine.retranslate();
};
applyUiLanguage(settings.uiLanguage());
QObject::connect(&settings, &AppSettings::uiLanguageChanged,
&engine, applyUiLanguage);
engine.rootContext()->setContextProperty(QStringLiteral("theSettings"), &settings);
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
[](const QUrl &url){ qFatal("object creation failed: %s", qPrintable(url.toString()));
});
+3 -5
View File
@@ -28,12 +28,10 @@ RowLayout {
apiKey: row.apiKey
}
Label {
Layout.preferredWidth: 125
Layout.maximumWidth: 125
SettingsLabel {
Layout.preferredWidth: Math.min(125, row.width * 0.42)
Layout.maximumWidth: Math.min(125, row.width * 0.42)
text: row.label
font.bold: true
wrapMode: Text.Wrap
}
Text {
+7
View File
@@ -11,6 +11,13 @@ NavigationPage {
return false
}
SettingsSectionLabel {
text: qsTr("Language")
}
LanguageSelector {
}
WhiteBox {
Layout.fillWidth: true
+11
View File
@@ -0,0 +1,11 @@
pragma Singleton
import QtQuick
QtObject {
readonly property bool settingsLabelBold: true
readonly property int cardRadius: 8
readonly property color cardColor: "white"
readonly property color cardPressedColor: "#f1f1f1"
readonly property color sectionLabelColor: "#5f6368"
}
+12 -1
View File
@@ -18,15 +18,26 @@ Item {
id: headerLayout
Layout.fillWidth: true
spacing: 0
Button {
ToolButton {
text: qsTr("Back")
icon.source: "material-icons/arrow_back.svg"
display: AbstractButton.IconOnly
visible: stackView.depth > 1
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Accessible.name: text
Accessible.description: qsTr("Return to the previous screen")
ToolTip.visible: hovered
ToolTip.text: text
onClicked: stackView.pop()
}
TitleText {
Layout.fillWidth: true
leftPadding: 8
rightPadding: 8
id: titleText
}
+27 -50
View File
@@ -10,69 +10,46 @@ NavigationPage {
apiKey: "cwe"
text: qsTr("Enable cloud connection")
dialogTitle: qsTr("Are you sure?")
dialogText: qsTr("Disabling could connection could make your device unreachable from other places than home!")
dialogText: qsTr("Disabling the cloud connection could make your device unreachable from outside your home network!")
}
Text {
Layout.fillWidth: true
wrapMode: Text.Wrap
text: qsTr("Features like flexible energy tariffs, time sync and app connection are unavilable when \"Enable cloud connection\" is disabled")
text: qsTr("Features such as flexible energy tariffs, time synchronization, and remote app access are unavailable when the cloud connection is disabled.")
}
WhiteBox {
Layout.fillWidth: true
SettingsCard {
ApiStatusRow {
apiKey: "cws"
label: qsTr("Trying to connect")
}
GridLayout {
anchors.fill: parent
columns: 2
ApiStatusRow {
apiKey: "cwsc"
label: qsTr("Connected")
}
Text {
text: qsTr("Trying to connect:")
font.bold: true
}
// TODO cwsca
ApiKeyValueItem {
apiKey: "cws"
}
ApiStatusRow {
apiKey: "chr"
label: qsTr("Hello received")
}
Text {
text: qsTr("Is connected:")
font.bold: true
}
ApiStatusRow {
apiKey: "qsc"
label: qsTr("Cloud queue size")
}
ApiKeyValueItem {
apiKey: "cwsc"
}
SettingsLabel {
Layout.fillWidth: true
text: qsTr("Last error")
}
// TODO cwsca
Text {
text: qsTr("Hello received:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "chr"
}
Text {
text: qsTr("Queue size cloud:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "qsc"
}
Text {
text: qsTr("Last error:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "cle"
wrapMode: Text.Wrap
}
ApiKeyValueItem {
apiKey: "cle"
wrapMode: Text.Wrap
}
}
}
@@ -12,10 +12,6 @@ WhiteCheckDelegate {
Layout.fillWidth: true
Component.onCompleted: {
contentItem.children[0].wrapMode = Text.Wrap
}
ApiKeyValueHelper {
id: apiKeyValueHelper
deviceConnection: theDeviceConnection
+137 -26
View File
@@ -1,7 +1,6 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import EVChargerApp
import QtQuick
import QtQuick.Controls.Material
import EVChargerApp
ApplicationWindow {
id: window
@@ -12,11 +11,7 @@ ApplicationWindow {
title: qsTr("EVcharger App")
color: "#F3F2F7"
AppSettings {
id: theSettings
}
DevicesModel {
DevicesModel {
id: devicesModel
settings: theSettings
@@ -29,12 +24,13 @@ ApplicationWindow {
source: "ui-icons/MaterialIcons-Regular.ttf"
}
SwipeView {
id: view
anchors.fill: parent
currentIndex: indicator.currentIndex
SwipeView {
id: view
anchors.fill: parent
// App instances are switched explicitly with three fingers. This keeps
// one-finger horizontal gestures available to controls inside them.
interactive: false
Repeater {
model: theSettings.numberOfAppInstances
@@ -46,8 +42,8 @@ ApplicationWindow {
}
}
PageIndicator {
id: indicator
PageIndicator {
id: indicator
visible: view.count > 1
@@ -55,18 +51,133 @@ ApplicationWindow {
currentIndex: view.currentIndex
anchors.top: view.top
anchors.horizontalCenter: parent.horizontalCenter
}
function backPressed() {
anchors.horizontalCenter: parent.horizontalCenter
}
// Two fingers switch the UI language; three fingers switch app instances.
// Exact point counts keep one-finger gestures available to page content.
Item {
anchors.fill: parent
z: 1000
DragHandler {
id: languageSwipe
target: null
acceptedDevices: PointerDevice.TouchScreen
minimumPointCount: 2
maximumPointCount: 2
property real lastHorizontalDistance: 0
property real lastVerticalDistance: 0
property bool gestureCanceled: false
onActiveTranslationChanged: {
if (active) {
lastHorizontalDistance = activeTranslation.x
lastVerticalDistance = activeTranslation.y
}
}
onActiveChanged: {
if (active) {
lastHorizontalDistance = 0
lastVerticalDistance = 0
gestureCanceled = false
return
}
if (!gestureCanceled
&& Math.abs(lastHorizontalDistance) >= 72
&& Math.abs(lastHorizontalDistance) > Math.abs(lastVerticalDistance) * 1.5)
window.cycleUiLanguage(lastHorizontalDistance)
}
onCanceled: (point) => {
gestureCanceled = true
}
}
DragHandler {
id: appInstanceSwipe
target: null
enabled: view.count > 1
acceptedDevices: PointerDevice.TouchScreen
minimumPointCount: 3
maximumPointCount: 3
property real lastHorizontalDistance: 0
property real lastVerticalDistance: 0
property bool gestureCanceled: false
onActiveTranslationChanged: {
if (active) {
lastHorizontalDistance = activeTranslation.x
lastVerticalDistance = activeTranslation.y
}
}
onActiveChanged: {
if (active) {
lastHorizontalDistance = 0
lastVerticalDistance = 0
gestureCanceled = false
return
}
if (!gestureCanceled
&& Math.abs(lastHorizontalDistance) >= 72
&& Math.abs(lastHorizontalDistance) > Math.abs(lastVerticalDistance) * 1.5)
window.switchAppInstance(lastHorizontalDistance)
}
onCanceled: (point) => {
gestureCanceled = true
}
}
}
function effectiveUiLanguage() {
if (theSettings.uiLanguage !== AppSettings.SystemLanguage)
return theSettings.uiLanguage
const languageCode = Qt.locale().name.substring(0, 2).toLowerCase()
switch (languageCode) {
case "de": return AppSettings.GermanLanguage
case "sv": return AppSettings.SwedishLanguage
case "fi": return AppSettings.FinnishLanguage
case "nb":
case "nn":
case "no": return AppSettings.NorwegianLanguage
case "tr": return AppSettings.TurkishLanguage
default: return AppSettings.EnglishLanguage
}
}
function cycleUiLanguage(horizontalDistance) {
const languages = [AppSettings.GermanLanguage, AppSettings.EnglishLanguage,
AppSettings.SwedishLanguage, AppSettings.FinnishLanguage,
AppSettings.NorwegianLanguage, AppSettings.TurkishLanguage]
const currentIndex = languages.indexOf(effectiveUiLanguage())
const direction = horizontalDistance < 0 ? 1 : -1
const nextIndex = (currentIndex + direction + languages.length) % languages.length
theSettings.uiLanguage = languages[nextIndex]
}
function switchAppInstance(horizontalDistance) {
const direction = horizontalDistance < 0 ? 1 : -1
view.currentIndex = Math.max(0, Math.min(view.count - 1,
view.currentIndex + direction))
}
function backPressed() {
if (view.currentItem.backPressed())
return true
if (view.currentIndex > 0) {
view.currentIndex = 0
view.currentIndex = Qt.binding(() => indicator.currentIndex)
return true
}
if (view.currentIndex > 0) {
view.currentIndex = 0
return true
}
return false
}
+1 -2
View File
@@ -29,10 +29,9 @@ WhiteItemDelegate {
}
contentItem: RowLayout {
Text {
SettingsLabel {
wrapMode: Text.Wrap
Layout.fillWidth: true
font.bold: true
text: itemDelegate.text
}
+40 -62
View File
@@ -41,47 +41,34 @@ NavigationPage {
text: checked ? qsTr("Ethernet on") : qsTr("Ethernet off")
}
WhiteBox {
Layout.fillWidth: true
implicitHeight: connectionStatus.implicitHeight + 20
SettingsSectionLabel {
text: qsTr("Connection")
}
ColumnLayout {
id: connectionStatus
anchors.fill: parent
anchors.margins: 10
SettingsCard {
ApiStatusRow {
apiKey: "cce"
label: qsTr("Address")
valueFormatter: function(value) { return connectionText(value) }
}
Text {
Layout.fillWidth: true
text: qsTr("Connection")
font.bold: true
}
ApiStatusRow {
apiKey: "cce"
label: qsTr("Link")
valueFormatter: function(value) { return linkText(value) }
}
ApiStatusRow {
apiKey: "cce"
label: qsTr("Address")
valueFormatter: function(value) { return connectionText(value) }
}
ApiStatusRow {
apiKey: "cce"
label: qsTr("Link")
valueFormatter: function(value) { return linkText(value) }
}
ApiStatusRow {
apiKey: "cce"
label: qsTr("Gateway")
valueFormatter: function(value) {
return value && value.gw ? value.gw : "—"
}
ApiStatusRow {
apiKey: "cce"
label: qsTr("Gateway")
valueFormatter: function(value) {
return value && value.gw ? value.gw : "—"
}
}
}
Text {
Layout.fillWidth: true
SettingsSectionLabel {
text: qsTr("IPv4 configuration")
font.bold: true
}
GeneralOnOffSwitch {
@@ -171,39 +158,30 @@ NavigationPage {
}
}
WhiteBox {
Layout.fillWidth: true
implicitHeight: ethernetDetails.implicitHeight + 20
SettingsSectionLabel {
visible: ethernetHostname.exists || ethernetMac.exists || ethernetInit.exists
text: qsTr("Interface details")
}
SettingsCard {
visible: ethernetHostname.exists || ethernetMac.exists || ethernetInit.exists
ColumnLayout {
id: ethernetDetails
anchors.fill: parent
anchors.margins: 10
ApiStatusRow {
id: ethernetHostname
apiKey: "hste"
label: qsTr("Hostname")
}
Text {
Layout.fillWidth: true
text: qsTr("Interface details")
font.bold: true
}
ApiStatusRow {
id: ethernetMac
apiKey: "mace"
label: qsTr("MAC address")
}
ApiStatusRow {
id: ethernetHostname
apiKey: "hste"
label: qsTr("Hostname")
}
ApiStatusRow {
id: ethernetMac
apiKey: "mace"
label: qsTr("MAC address")
}
ApiStatusRow {
id: ethernetInit
apiKey: "eis"
label: qsTr("Initialization")
}
ApiStatusRow {
id: ethernetInit
apiKey: "eis"
label: qsTr("Initialization")
}
}
-4
View File
@@ -12,10 +12,6 @@ WhiteCheckDelegate {
Layout.fillWidth: true
Component.onCompleted: {
contentItem.children[0].wrapMode = Text.Wrap
}
ApiKeyValueHelper {
id: valueHelper
deviceConnection: theDeviceConnection
+16 -27
View File
@@ -12,10 +12,8 @@ NavigationPage {
text: checked ? qsTr("Hotspot on") : qsTr("Hotspot off")
}
Text {
Layout.fillWidth: true
SettingsSectionLabel {
text: qsTr("Configuration")
font.bold: true
}
GeneralOnOffSwitch {
@@ -77,33 +75,24 @@ NavigationPage {
}
}
WhiteBox {
Layout.fillWidth: true
implicitHeight: hotspotStatus.implicitHeight + 20
SettingsSectionLabel {
visible: hotspotHostname.exists || hotspotMac.exists
text: qsTr("Hotspot identity")
}
SettingsCard {
visible: hotspotHostname.exists || hotspotMac.exists
ColumnLayout {
id: hotspotStatus
anchors.fill: parent
anchors.margins: 10
ApiStatusRow {
id: hotspotHostname
apiKey: "hsta"
label: qsTr("Hostname")
}
Text {
Layout.fillWidth: true
text: qsTr("Hotspot identity")
font.bold: true
}
ApiStatusRow {
id: hotspotHostname
apiKey: "hsta"
label: qsTr("Hostname")
}
ApiStatusRow {
id: hotspotMac
apiKey: "maca"
label: qsTr("MAC address")
}
ApiStatusRow {
id: hotspotMac
apiKey: "maca"
label: qsTr("MAC address")
}
}
}
+130
View File
@@ -0,0 +1,130 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
import EVChargerApp as App
SettingsCard {
id: selector
function languageForLocale(localeName) {
const languageCode = localeName.substring(0, 2).toLowerCase()
switch (languageCode) {
case "de": return AppSettings.GermanLanguage
case "sv": return AppSettings.SwedishLanguage
case "fi": return AppSettings.FinnishLanguage
case "nb":
case "nn":
case "no": return AppSettings.NorwegianLanguage
case "tr": return AppSettings.TurkishLanguage
default: return AppSettings.EnglishLanguage
}
}
function languageName(language) {
switch (language) {
case AppSettings.GermanLanguage: return qsTr("German")
case AppSettings.SwedishLanguage: return qsTr("Swedish")
case AppSettings.FinnishLanguage: return qsTr("Finnish")
case AppSettings.NorwegianLanguage: return qsTr("Norwegian")
case AppSettings.TurkishLanguage: return qsTr("Turkish")
default: return qsTr("English")
}
}
readonly property int systemLanguage: languageForLocale(Qt.locale().name)
readonly property var languageChoices: [
{
value: AppSettings.SystemLanguage,
text: qsTr("System default (%1)").arg(languageName(systemLanguage)),
icon: "flags/system.svg"
},
{ value: AppSettings.GermanLanguage, text: qsTr("German"), icon: "flags/de.svg" },
{ value: AppSettings.EnglishLanguage, text: qsTr("English"), icon: "flags/gb.svg" },
{ value: AppSettings.SwedishLanguage, text: qsTr("Swedish"), icon: "flags/se.svg" },
{ value: AppSettings.FinnishLanguage, text: qsTr("Finnish"), icon: "flags/fi.svg" },
{ value: AppSettings.NorwegianLanguage, text: qsTr("Norwegian"), icon: "flags/no.svg" },
{ value: AppSettings.TurkishLanguage, text: qsTr("Turkish"), icon: "flags/tr.svg" }
]
SettingsLabel {
text: qsTr("App language")
}
ComboBox {
id: languageCombo
Layout.fillWidth: true
model: selector.languageChoices
textRole: "text"
valueRole: "value"
function synchronizeLanguage() {
const selectedIndex = indexOfValue(theSettings.uiLanguage)
if (selectedIndex >= 0)
currentIndex = selectedIndex
}
Component.onCompleted: synchronizeLanguage()
onModelChanged: synchronizeLanguage()
onActivated: theSettings.uiLanguage = currentValue
delegate: ItemDelegate {
id: languageDelegate
required property int index
required property var modelData
width: languageCombo.width
highlighted: languageCombo.highlightedIndex === index
contentItem: RowLayout {
spacing: 10
Image {
Layout.preferredWidth: 28
Layout.preferredHeight: 20
source: languageDelegate.modelData.icon
fillMode: Image.PreserveAspectFit
}
Label {
Layout.fillWidth: true
text: languageDelegate.modelData.text
elide: Text.ElideRight
}
}
}
contentItem: RowLayout {
spacing: 10
Image {
Layout.preferredWidth: 28
Layout.preferredHeight: 20
source: languageCombo.currentIndex >= 0
? selector.languageChoices[languageCombo.currentIndex].icon : ""
fillMode: Image.PreserveAspectFit
}
Label {
Layout.fillWidth: true
text: languageCombo.displayText
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}
Connections {
target: theSettings
function onUiLanguageChanged() { languageCombo.synchronizeLanguage() }
}
}
Text {
Layout.fillWidth: true
text: qsTr("Numbers, dates, and times continue to use your system settings.")
color: App.AppTheme.sectionLabelColor
wrapMode: Text.Wrap
}
}
+39 -72
View File
@@ -11,7 +11,7 @@ NavigationPage {
}
EditValueItem {
text: qsTr("Broker url")
text: qsTr("Broker URL")
apiKey: "mcu"
fullWidth: true
editableItem: TextField {
@@ -25,95 +25,62 @@ NavigationPage {
url: qsTr("https://github.com/goecharger/go-eCharger-API-v2/blob/main/mqtt-en.md")
}
WhiteBox {
Layout.fillWidth: true
SettingsCard {
ApiStatusRow {
apiKey: "mcs"
label: qsTr("Trying to connect")
}
GridLayout {
anchors.fill: parent
columns: 2
ApiStatusRow {
apiKey: "mcc"
label: qsTr("Connected")
}
Text {
text: qsTr("Trying to connect:")
font.bold: true
ApiStatusRow {
apiKey: "mcca"
label: qsTr("Connected since")
valueFormatter: function(value) {
return value ? formatDuration(value - rebootTime.value) : "—"
}
}
ApiKeyValueItem {
apiKey: "mcs"
}
ApiStatusRow {
apiKey: "mcsl"
label: qsTr("Command counter")
}
Text {
text: qsTr("Is connected:")
font.bold: true
}
SettingsLabel {
Layout.fillWidth: true
text: qsTr("Last error")
}
ApiKeyValueItem {
apiKey: "mcc"
}
ApiKeyValueItem {
apiKey: "mlr"
wrapMode: Text.Wrap
}
Text {
text: qsTr("Connected since:")
font.bold: true
}
ApiKeyValueHelper {
id: connectedSince
deviceConnection: theDeviceConnection
apiKey: "mcca"
}
Text {
text: connectedSince.value ? formatDuration(connectedSince.value - rebootTime.value) : ""
}
Text {
text: qsTr("Command Counter:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "mcsl"
}
Text {
text: qsTr("Last error:")
font.bold: true
}
ApiKeyValueItem {
apiKey: "mlr"
wrapMode: Text.Wrap
}
Text {
text: qsTr("Last error age:")
font.bold: true
}
ApiKeyValueHelper {
id: lastErrorAge
deviceConnection: theDeviceConnection
apiKey: "mlra"
}
Text {
text: lastErrorAge.value ? formatDuration(lastErrorAge.value - rebootTime.value) : ""
ApiStatusRow {
apiKey: "mlra"
label: qsTr("Last error age")
valueFormatter: function(value) {
return value ? formatDuration(value - rebootTime.value) : "—"
}
}
}
GeneralOnOffSwitch {
apiKey: "mqg"
text: qsTr("Use global CA store")
text: qsTr("Use global CA certificate store")
}
GeneralOnOffSwitch {
apiKey: "mqcn"
text: qsTr("Skip cert commonName check")
text: qsTr("Skip certificate common name check")
}
GeneralOnOffSwitch {
apiKey: "mqss"
text: qsTr("Skip server cert verification")
text: qsTr("Skip server certificate verification")
}
// TODO clientKey
@@ -124,7 +91,7 @@ NavigationPage {
GeneralOnOffSwitch {
apiKey: "mcr"
text: qsTr("MQTT readonly")
text: qsTr("MQTT read-only")
}
EditValueItem {
@@ -141,13 +108,13 @@ NavigationPage {
GeneralOnOffSwitch {
id: homeassistant
apiKey: "mhe"
text: qsTr("Homeassistant autodiscover")
text: qsTr("Home Assistant auto-discovery")
}
EditValueItem {
visible: homeassistant.checked
// TODO allow set to null to reset to default
text: qsTr("Homeassistant topic prefix")
text: qsTr("Home Assistant topic prefix")
apiKey: "mht"
fullWidth: true
editableItem: TextField {
+14
View File
@@ -0,0 +1,14 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
WhiteBox {
default property alias contents: contentLayout.data
Layout.fillWidth: true
contentItem: ColumnLayout {
id: contentLayout
spacing: 6
}
}
+8
View File
@@ -0,0 +1,8 @@
import QtQuick
import QtQuick.Controls
import EVChargerApp as App
Label {
font.bold: App.AppTheme.settingsLabelBold
wrapMode: Text.Wrap
}
@@ -0,0 +1,12 @@
import QtQuick
import QtQuick.Layouts
import EVChargerApp
import EVChargerApp as App
SettingsLabel {
Layout.fillWidth: true
Layout.topMargin: 8
leftPadding: 4
rightPadding: 4
color: App.AppTheme.sectionLabelColor
}
+1 -4
View File
@@ -5,9 +5,6 @@ import QtQuick.Layouts
NavigationPage {
title: qsTr("Switch Language")
Text {
text: "TODO"
Layout.fillHeight: true
LanguageSelector {
}
}
+4 -5
View File
@@ -1,11 +1,10 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
import EVChargerApp as App
Pane {
Component.onCompleted: {
background.color = "white"
background.radius = 5
background: Rectangle {
color: App.AppTheme.cardColor
radius: App.AppTheme.cardRadius
}
}
+15 -3
View File
@@ -1,10 +1,22 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp as App
CheckDelegate {
id: control
font.bold: App.AppTheme.settingsLabelBold
Component.onCompleted: {
background.color = "white"
background.radius = 5
// The style-owned IconLabel does not expose its wrapping mode. Keep the
// workaround here so every derived settings switch behaves alike.
if (contentItem.children.length > 0)
contentItem.children[0].wrapMode = Text.Wrap
}
background: Rectangle {
implicitHeight: 48
color: control.down ? App.AppTheme.cardPressedColor : App.AppTheme.cardColor
radius: App.AppTheme.cardRadius
}
}
+9 -4
View File
@@ -1,10 +1,15 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp as App
ItemDelegate {
Component.onCompleted: {
background.radius = 5
background.color = "white"
id: control
font.bold: App.AppTheme.settingsLabelBold
background: Rectangle {
implicitHeight: 48
color: control.down ? App.AppTheme.cardPressedColor : App.AppTheme.cardColor
radius: App.AppTheme.cardRadius
}
}
+6 -4
View File
@@ -1,10 +1,12 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp as App
SwipeDelegate {
Component.onCompleted: {
background.color = "white"
background.radius = 5
id: control
background: Rectangle {
color: control.down ? App.AppTheme.cardPressedColor : App.AppTheme.cardColor
radius: App.AppTheme.cardRadius
}
}
+95 -124
View File
@@ -216,62 +216,51 @@ NavigationPage {
dialogText: qsTr("Disabling Wi-Fi could make your device unreachable from your local homenetwork or the cloud!")
}
WhiteBox {
Layout.fillWidth: true
implicitHeight: connectionStatusColumn.implicitHeight + 20
SettingsSectionLabel {
text: qsTr("Connection")
}
ColumnLayout {
id: connectionStatusColumn
anchors.fill: parent
anchors.margins: 10
SettingsCard {
ApiStatusRow {
apiKey: "wst"
label: qsTr("Status")
valueFormatter: function(value) { return stationStatusName(value) }
}
Text {
Layout.fillWidth: true
text: qsTr("Connection")
font.bold: true
ApiStatusRow {
apiKey: "ccw"
label: qsTr("Network")
valueFormatter: function(value) {
if (value === null || value === undefined)
return qsTr("Not connected")
const address = value.ip ? " • " + value.ip : ""
return (value.ssid || qsTr("Unknown network")) + address
}
}
ApiStatusRow {
apiKey: "wst"
label: qsTr("Status")
valueFormatter: function(value) { return stationStatusName(value) }
ApiStatusRow {
apiKey: "rssi"
label: qsTr("Signal")
valueFormatter: function(value) {
return value === null || value === undefined
? "—"
: signalBars(value) + " " + value + " dBm"
}
}
ApiStatusRow {
apiKey: "ccw"
label: qsTr("Network")
valueFormatter: function(value) {
if (value === null || value === undefined)
return qsTr("Not connected")
const address = value.ip ? " • " + value.ip : ""
return (value.ssid || qsTr("Unknown network")) + address
}
}
ApiStatusRow {
apiKey: "scas"
label: qsTr("Wi-Fi scan")
valueFormatter: function(value) { return scanStatusName(value) }
}
ApiStatusRow {
apiKey: "rssi"
label: qsTr("Signal")
valueFormatter: function(value) {
return value === null || value === undefined
? "—"
: signalBars(value) + " " + value + " dBm"
}
}
ApiStatusRow {
apiKey: "scas"
label: qsTr("Wi-Fi scan")
valueFormatter: function(value) { return scanStatusName(value) }
}
ApiStatusRow {
apiKey: "scaa"
label: qsTr("Last scan")
valueFormatter: function(value) {
return value === null || value === undefined
? qsTr("Never")
: formatDuration(value - rebootTime.value)
}
ApiStatusRow {
apiKey: "scaa"
label: qsTr("Last scan")
valueFormatter: function(value) {
return value === null || value === undefined
? qsTr("Never")
: formatDuration(value - rebootTime.value)
}
}
}
@@ -279,10 +268,9 @@ NavigationPage {
RowLayout {
Layout.fillWidth: true
Text {
SettingsSectionLabel {
Layout.fillWidth: true
text: qsTr("Configured networks")
font.bold: true
}
BusyIndicator {
@@ -313,15 +301,12 @@ NavigationPage {
id: configuredRepeater
model: configuredNetworks()
delegate: WhiteBox {
delegate: SettingsCard {
required property var modelData
Layout.fillWidth: true
implicitHeight: configuredRow.implicitHeight + 20
RowLayout {
id: configuredRow
anchors.fill: parent
anchors.margins: 10
Layout.fillWidth: true
ColumnLayout {
Layout.fillWidth: true
@@ -359,11 +344,8 @@ NavigationPage {
}
}
Text {
Layout.fillWidth: true
Layout.topMargin: 10
SettingsSectionLabel {
text: qsTr("Available networks")
font.bold: true
}
Repeater {
@@ -451,11 +433,8 @@ NavigationPage {
}
}
Text {
Layout.fillWidth: true
Layout.topMargin: 10
SettingsSectionLabel {
text: qsTr("Advanced Wi-Fi settings")
font.bold: true
}
EditValueItem {
@@ -479,77 +458,69 @@ NavigationPage {
text: qsTr("Require protected management frames")
}
WhiteBox {
Layout.fillWidth: true
implicitHeight: wifiDetailsColumn.implicitHeight + 20
SettingsSectionLabel {
visible: wifiAddressing.exists || wifiAccessPoint.exists || wifiConnectionProcess.exists
|| wifiMac.exists || wifiHostname.exists || wifiErrorCount.exists
text: qsTr("Technical details")
}
SettingsCard {
visible: wifiAddressing.exists || wifiAccessPoint.exists || wifiConnectionProcess.exists
|| wifiMac.exists || wifiHostname.exists || wifiErrorCount.exists
ColumnLayout {
id: wifiDetailsColumn
anchors.fill: parent
anchors.margins: 10
Text {
Layout.fillWidth: true
text: qsTr("Technical details")
font.bold: true
ApiStatusRow {
id: wifiAddressing
apiKey: "ccw"
label: qsTr("Addressing")
valueFormatter: function(value) {
if (value === null || value === undefined)
return "—"
const parts = []
if (value.ip) parts.push(qsTr("IP %1").arg(value.ip))
if (value.netmask) parts.push(qsTr("mask %1").arg(value.netmask))
if (value.gw) parts.push(qsTr("gateway %1").arg(value.gw))
return parts.length > 0 ? parts.join(" • ") : "—"
}
}
ApiStatusRow {
id: wifiAddressing
apiKey: "ccw"
label: qsTr("Addressing")
valueFormatter: function(value) {
if (value === null || value === undefined)
return "—"
const parts = []
if (value.ip) parts.push(qsTr("IP %1").arg(value.ip))
if (value.netmask) parts.push(qsTr("mask %1").arg(value.netmask))
if (value.gw) parts.push(qsTr("gateway %1").arg(value.gw))
return parts.length > 0 ? parts.join(" • ") : "—"
}
ApiStatusRow {
id: wifiAccessPoint
apiKey: "ccw"
label: qsTr("Access point")
valueFormatter: function(value) {
if (value === null || value === undefined)
return "—"
const channel = value.channel !== undefined ? qsTr("channel %1").arg(value.channel) : ""
return [value.bssid, channel].filter(Boolean).join(" • ")
}
}
ApiStatusRow {
id: wifiAccessPoint
apiKey: "ccw"
label: qsTr("Access point")
valueFormatter: function(value) {
if (value === null || value === undefined)
return "—"
const channel = value.channel !== undefined ? qsTr("channel %1").arg(value.channel) : ""
return [value.bssid, channel].filter(Boolean).join(" • ")
}
ApiStatusRow {
id: wifiConnectionProcess
apiKey: "wsms"
label: qsTr("Connection process")
valueFormatter: function(value) {
const names = [qsTr("Idle"), qsTr("Scanning"), qsTr("Connecting"), qsTr("Connected")]
return value >= 0 && value < names.length ? names[value] : qsTr("Unknown")
}
}
ApiStatusRow {
id: wifiConnectionProcess
apiKey: "wsms"
label: qsTr("Connection process")
valueFormatter: function(value) {
const names = [qsTr("Idle"), qsTr("Scanning"), qsTr("Connecting"), qsTr("Connected")]
return value >= 0 && value < names.length ? names[value] : qsTr("Unknown")
}
}
ApiStatusRow {
id: wifiMac
apiKey: "macs"
label: qsTr("MAC address")
}
ApiStatusRow {
id: wifiMac
apiKey: "macs"
label: qsTr("MAC address")
}
ApiStatusRow {
id: wifiHostname
apiKey: "hsts"
label: qsTr("Hostname")
}
ApiStatusRow {
id: wifiHostname
apiKey: "hsts"
label: qsTr("Hostname")
}
ApiStatusRow {
id: wifiErrorCount
apiKey: "wsc"
label: qsTr("Connection errors")
}
ApiStatusRow {
id: wifiErrorCount
apiKey: "wsc"
label: qsTr("Connection errors")
}
}
+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 20">
<rect width="30" height="20" rx="1.5" fill="#ffce00"/>
<rect width="30" height="13.333" rx="1.5" fill="#dd0000"/>
<rect width="30" height="6.667" rx="1.5" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 246 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 20">
<rect width="32" height="20" rx="1.5" fill="#fff"/>
<path d="M0 8h32M10 0v20" stroke="#003580" stroke-width="4"/>
</svg>

After

Width:  |  Height:  |  Size: 186 B

+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 40">
<rect width="60" height="40" rx="3" fill="#012169"/>
<path d="M0 0 60 40M60 0 0 40" stroke="#fff" stroke-width="8"/>
<path d="M0 0 60 40M60 0 0 40" stroke="#c8102e" stroke-width="3"/>
<path d="M30 0v40M0 20h60" stroke="#fff" stroke-width="13"/>
<path d="M30 0v40M0 20h60" stroke="#c8102e" stroke-width="7"/>
</svg>

After

Width:  |  Height:  |  Size: 386 B

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 20">
<rect width="32" height="20" rx="1.5" fill="#ba0c2f"/>
<path d="M0 8h32M10 0v20" stroke="#fff" stroke-width="5"/>
<path d="M0 8h32M10 0v20" stroke="#00205b" stroke-width="2.5"/>
</svg>

After

Width:  |  Height:  |  Size: 252 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 20">
<rect width="32" height="20" rx="1.5" fill="#006aa7"/>
<path d="M0 8h32M10 0v20" stroke="#fecc02" stroke-width="4"/>
</svg>

After

Width:  |  Height:  |  Size: 189 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="9" fill="none" stroke="#5f6368" stroke-width="2"/>
<path d="M3 12h18M12 3c3 3 3 15 0 18M12 3c-3 3-3 15 0 18" fill="none" stroke="#5f6368" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 258 B

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 20">
<rect width="30" height="20" rx="1.5" fill="#e30a17"/>
<circle cx="12" cy="10" r="5" fill="#fff"/>
<circle cx="13.5" cy="10" r="4" fill="#e30a17"/>
<path d="m18.6 7.4.7 1.8 2 .1-1.6 1.2.5 1.9-1.6-1.1-1.7 1.1.6-1.9-1.6-1.2 2-.1z" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 319 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><path d="m313-440 224 224-57 56-320-320 320-320 57 56-224 224h487v80H313Z"/></svg>

After

Width:  |  Height:  |  Size: 190 B