Improve network settings in general alot
Build Linux and Android / build (push) Successful in 3m10s

This commit is contained in:
feedc0de committed 2026-08-13 15:11:40 +02:00
1 parent 65cae8cc9c
commit 87bdb649f5
12 files changed
+1113 -786

No files matched your search

+2 -1
View File
@@ -58,6 +58,8 @@ qt_add_qml_module(evcharger-app
qml/AnimatedStackLayout.qml
qml/AnimatedStackView.qml
qml/ApiKeyValueItem.qml
qml/ApiStatusRow.qml
qml/ApiEnumEditItem.qml
qml/ApiSettingsPage.qml
qml/AppInstance.qml
qml/AppSettingsPage.qml
@@ -143,7 +145,6 @@ qt_add_qml_module(evcharger-app
qml/WhiteSwipeDelegate.qml
qml/WiFiErrorsPage.qml
qml/WiFiPage.qml
qml/WiFiScanPage.qml
RESOURCES
qml/icons/Charger.svg
qml/icons/ChargerV3.svg
-108
View File
@@ -3087,114 +3087,6 @@
<translation>Pmf erforderlich</translation>
</message>
</context>
<context>
<name>WiFiScanPage</name>
<message>
<location filename="../qml/WiFiScanPage.qml" line="9"/>
<source>Wi-Fi Scan Result</source>
<translation>WLAN Scan Result</translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="31"/>
<source>SSID:</source>
<translation>SSID:</translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="40"/>
<source>Authmode:</source>
<translation>Authentifizierungsart:</translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="48"/>
<source>OPEN</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="49"/>
<source>WEP</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="50"/>
<source>WPA_PSK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="51"/>
<source>WPA2_PSK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="52"/>
<source>WPA_WPA2_PSK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="53"/>
<source>WPA2_ENTERPRISE</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="54"/>
<source>WPA3_PSK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="55"/>
<source>WPA2_WPA3_PSK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="56"/>
<source>WAPI_PSK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="57"/>
<source>OWE</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="58"/>
<source>WPA3_ENT_192</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="59"/>
<source>WPA3_EXT_PSK</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="60"/>
<source>WPA3_EXT_PSK_MIXED_MODE</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="76"/>
<source>Pairwise cipher:</source>
<translation>Paarweise Cipher:</translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="85"/>
<source>Group cipher:</source>
<translation>Gruppen Cipher:</translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="94"/>
<source>RSSI:</source>
<translation>RSSI:</translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="103"/>
<source>Channel:</source>
<translation>Kanal:</translation>
</message>
<message>
<location filename="../qml/WiFiScanPage.qml" line="112"/>
<source>BSSID:</source>
<translation>BSSID:</translation>
</message>
</context>
<context>
<name>main</name>
<message>
+37
View File
@@ -0,0 +1,37 @@
import QtQuick
import QtQuick.Controls
import EVChargerApp
EditValueItem {
id: item
required property var choices
function textForValue(value) {
for (let i = 0; i < choices.length; ++i) {
if (choices[i].value === value)
return choices[i].text
}
return value === null || value === undefined ? qsTr("Automatic") : String(value)
}
valueText: textForValue(value)
editableItem: ComboBox {
id: comboBox
property var value
model: item.choices
textRole: "text"
valueRole: "value"
function selectValue() {
const index = indexOfValue(value)
if (index >= 0)
currentIndex = index
}
Component.onCompleted: selectValue()
onValueChanged: selectValue()
onActivated: value = currentValue
}
}
+47
View File
@@ -0,0 +1,47 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
RowLayout {
id: row
required property string apiKey
required property string label
property var valueFormatter: null
property alias value: apiValue.value
property alias exists: apiValue.exists
visible: apiValue.exists
function defaultValueText(value) {
if (value === null || value === undefined || value === "")
return "—"
if (typeof value === "object")
return JSON.stringify(value)
return String(value)
}
ApiKeyValueHelper {
id: apiValue
deviceConnection: theDeviceConnection
apiKey: row.apiKey
}
Label {
Layout.preferredWidth: 125
Layout.maximumWidth: 125
text: row.label
font.bold: true
wrapMode: Text.Wrap
}
Text {
Layout.fillWidth: true
text: row.valueFormatter === null
? row.defaultValueText(apiValue.value)
: row.valueFormatter(apiValue.value)
wrapMode: Text.Wrap
selectable: true
}
}
+182 -159
View File
@@ -4,214 +4,237 @@ import QtQuick.Layouts
import EVChargerApp
NavigationPage {
id: cellularPage
title: qsTr("Cellular")
function technologyName(value) {
switch (value) {
case 0: return qsTr("No service")
case 1: return qsTr("GSM")
case 2: return qsTr("GPRS")
case 3: return qsTr("EDGE")
case 4: return qsTr("WCDMA")
case 5: return qsTr("HSDPA")
case 6: return qsTr("HSUPA")
case 7: return qsTr("HSPA")
case 8: return qsTr("LTE")
default: return qsTr("Disconnected")
}
}
function signalText(value) {
if (value === null || value === undefined || value.rssi === null || value.rssi === undefined)
return qsTr("Unavailable")
return value.rssi + " dBm" + (value.ber === null || value.ber === undefined
? "" : " • BER " + value.ber)
}
ConfirmingOnOffSwitch {
apiKey: "cen"
text: checked ? qsTr("Cellular on") : qsTr("Cellular off")
dialogTitle: qsTr("Are you sure?")
dialogText: qsTr("Disabling Cellular could make your device unreachable from the cloud!")
dialogText: qsTr("Disabling cellular could make your device unreachable from the cloud!")
}
WhiteBox {
Layout.fillWidth: true
implicitHeight: modemStatus.implicitHeight + 20
ColumnLayout {
id: modemStatus
anchors.fill: parent
anchors.margins: 10
Text {
Layout.fillWidth: true
text: qsTr("Mobile network")
font.bold: true
}
ApiStatusRow {
apiKey: "ccm"
label: qsTr("Connection")
valueFormatter: function(value) { return technologyName(value) }
}
ApiStatusRow {
apiKey: "mon"
label: qsTr("Operator")
valueFormatter: function(value) {
return value || qsTr("No operator")
}
}
ApiStatusRow {
apiKey: "msq"
label: qsTr("Signal")
valueFormatter: function(value) { return signalText(value) }
}
ApiStatusRow {
apiKey: "mia"
label: qsTr("IP address")
valueFormatter: function(value) {
return value && value.ip ? value.ip : qsTr("No address")
}
}
}
}
Text {
Layout.fillWidth: true
text: qsTr("Connection settings")
font.bold: true
}
EditValueItem {
text: qsTr("APN")
apiKey: "moap"
visible: exists
fullWidth: true
editableItem: TextField {
id: textField0
property alias value: textField0.text
id: apnInput
property alias value: apnInput.text
placeholderText: qsTr("Access point name")
}
}
ApiEnumEditItem {
text: qsTr("SIM")
apiKey: "simi"
visible: exists
choices: [
{ value: 0, text: qsTr("Embedded SIM") },
{ value: 1, text: qsTr("SIM card slot") }
]
}
EditValueItem {
text: qsTr("PIN")
text: qsTr("SIM PIN")
apiKey: "mopi"
visible: exists
valueText: value ? qsTr("Configured") : qsTr("Not configured")
editValueTransform: function() { return "" }
fullWidth: true
editableItem: TextField {
id: textField1
property alias value: textField1.text
id: pinInput
property alias value: pinInput.text
echoMode: TextInput.Password
inputMethodHints: Qt.ImhDigitsOnly
placeholderText: qsTr("Enter a new PIN")
}
}
// TODO auth Type
ApiEnumEditItem {
text: qsTr("Preferred network")
apiKey: "csm"
visible: exists
choices: [
{ value: null, text: qsTr("Automatic") },
{ value: 0, text: qsTr("2G only") },
{ value: 1, text: qsTr("4G only") }
]
}
ApiEnumEditItem {
id: authenticationType
text: qsTr("Authentication")
apiKey: "mat"
visible: exists
choices: [
{ value: 0, text: qsTr("None") },
{ value: 1, text: qsTr("PAP") },
{ value: 2, text: qsTr("CHAP") },
{ value: 4, text: qsTr("MSCHAP") },
{ value: 8, text: qsTr("MSCHAP v2") },
{ value: 16, text: qsTr("EAP") }
]
}
EditValueItem {
text: qsTr("Username")
apiKey: "mau"
visible: exists && authenticationType.value !== 0
fullWidth: true
editableItem: TextField {
id: textField2
property alias value: textField2.text
id: usernameInput
property alias value: usernameInput.text
}
}
EditValueItem {
text: qsTr("Password")
apiKey: "mpap"
visible: exists && authenticationType.value !== 0
valueText: value ? qsTr("Configured") : qsTr("Not configured")
editValueTransform: function() { return "" }
fullWidth: true
editableItem: TextField {
id: textField3
property alias value: textField3.text
id: passwordInput
property alias value: passwordInput.text
echoMode: TextInput.Password
placeholderText: qsTr("Enter a new password")
}
}
WhiteItemDelegate {
id: delegate
Layout.fillWidth: true
text: qsTr("Show technical details of LTE modem")
onClicked: {
gridLayout.visible = true
delegate.text = null
delegate.implicitWidth = Qt.binding(() => gridLayout.implicitWidth + gridLayout.anchors.leftMargin + gridLayout.anchors.rightMargin)
delegate.implicitHeight = Qt.binding(() => gridLayout.implicitHeight + gridLayout.anchors.topMargin + gridLayout.anchors.bottomMargin)
EditValueItem {
text: qsTr("Forced operator")
apiKey: "mopn"
visible: exists
valueText: value ? value : qsTr("Automatic")
fullWidth: true
editableItem: TextField {
id: operatorInput
property alias value: operatorInput.text
placeholderText: qsTr("Automatic when empty")
}
}
GridLayout {
id: gridLayout
Button {
id: technicalDetailsButton
Layout.fillWidth: true
checkable: true
text: checked ? qsTr("Hide modem details") : qsTr("Show modem details")
}
columns: 2
WhiteBox {
Layout.fillWidth: true
implicitHeight: modemDetails.implicitHeight + 20
visible: technicalDetailsButton.checked
ColumnLayout {
id: modemDetails
anchors.fill: parent
anchors.leftMargin: 5
anchors.topMargin: 5
anchors.rightMargin: 5
anchors.bottomMargin: 5
visible: false
anchors.margins: 10
ApiKeyValueHelper {
id: misApiKey
deviceConnection: theDeviceConnection
apiKey: "mis"
}
Label {
text: qsTr("Initialization status:")
Text {
Layout.fillWidth: true
text: qsTr("Modem details")
font.bold: true
}
Text {
text: JSON.stringify(misApiKey.value, null, 2)
wrapMode: Text.Wrap
Layout.fillWidth: true
}
ApiStatusRow { apiKey: "mis"; label: qsTr("Initialization") }
ApiStatusRow { apiKey: "mps"; label: qsTr("SIM status") }
ApiStatusRow { apiKey: "imei"; label: qsTr("IMEI") }
ApiStatusRow { apiKey: "imsi"; label: qsTr("IMSI") }
ApiStatusRow { apiKey: "iccid"; label: qsTr("ICCID") }
ApiStatusRow { apiKey: "msor"; label: qsTr("Operator selection") }
ApiStatusRow { apiKey: "mas"; label: qsTr("Startup commands") }
ApiKeyValueHelper {
id: imeiApiKey
deviceConnection: theDeviceConnection
apiKey: "imei"
}
Label {
text: qsTr("IMEI:")
font.bold: true
}
Text {
text: JSON.stringify(imeiApiKey.value, null, 2)
wrapMode: Text.Wrap
Layout.fillWidth: true
}
ApiKeyValueHelper {
id: imsiApiKey
deviceConnection: theDeviceConnection
apiKey: "imsi"
}
Label {
text: qsTr("IMSI:")
font.bold: true
}
Text {
text: JSON.stringify(imsiApiKey.value, null, 2)
wrapMode: Text.Wrap
Layout.fillWidth: true
}
ApiKeyValueHelper {
id: mpsApiKey
deviceConnection: theDeviceConnection
apiKey: "mps"
}
Label {
text: qsTr("Pin status:")
font.bold: true
}
Text {
text: JSON.stringify(mpsApiKey.value, null, 2)
wrapMode: Text.Wrap
Layout.fillWidth: true
}
ApiKeyValueHelper {
id: monApiKey
deviceConnection: theDeviceConnection
apiKey: "mon"
}
Label {
text: qsTr("Operator name:")
font.bold: true
}
Text {
text: JSON.stringify(monApiKey.value, null, 2)
wrapMode: Text.Wrap
Layout.fillWidth: true
}
ApiKeyValueHelper {
id: msqApiKey
deviceConnection: theDeviceConnection
apiKey: "msq"
}
Label {
text: qsTr("Signal quality:")
font.bold: true
}
Text {
text: JSON.stringify(msqApiKey.value, null, 2)
wrapMode: Text.Wrap
Layout.fillWidth: true
}
ApiKeyValueHelper {
id: ccmApiKey
deviceConnection: theDeviceConnection
apiKey: "ccm"
}
Label {
text: qsTr("Current system mode:")
font.bold: true
}
Text {
text: JSON.stringify(ccmApiKey.value, null, 2)
wrapMode: Text.Wrap
Layout.fillWidth: true
}
ApiKeyValueHelper {
id: miaApiKey
deviceConnection: theDeviceConnection
ApiStatusRow {
apiKey: "mia"
}
Label {
text: qsTr("IP Address:")
font.bold: true
}
Text {
text: JSON.stringify(miaApiKey.value, null, 2)
wrapMode: Text.Wrap
Layout.fillWidth: true
label: qsTr("Addressing")
valueFormatter: function(value) {
if (value === null || value === undefined)
return qsTr("Not connected")
const values = []
if (value.ip) values.push(value.ip)
if (value.subnet) values.push(qsTr("subnet %1").arg(value.subnet))
if (value.gw) values.push(qsTr("gateway %1").arg(value.gw))
if (value.ipv6) values.push(value.ipv6)
return values.length > 0 ? values.join(" • ") : qsTr("No address")
}
}
}
}
+14 -10
View File
@@ -9,6 +9,7 @@ NavigationPage {
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Wi-Fi")
description: qsTr("Join and manage wireless networks")
component: Component {
WiFiPage {
}
@@ -16,19 +17,10 @@ NavigationPage {
visible: wifiStaApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Cellular")
component: Component {
CellularPage {
}
}
visible: cellularApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Hotspot")
description: qsTr("Configure the device's Wi-Fi access point")
component: Component {
HotspotPage {
}
@@ -39,6 +31,7 @@ NavigationPage {
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Ethernet")
description: qsTr("Wired network and IP settings")
component: Component {
EthernetPage {
}
@@ -46,6 +39,17 @@ NavigationPage {
visible: ethernetApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Cellular")
description: qsTr("SIM, APN and mobile network settings")
component: Component {
CellularPage {
}
}
visible: cellularApiKeyValueHelper.exists
}
NavigationItem {
iconSource: "material-icons/grid_guides.svg"
title: qsTr("Cloud")
+3 -2
View File
@@ -10,7 +10,9 @@ WhiteItemDelegate {
property string valueText: apiKeyValueHelper.value
property alias editableItem: dialog.contentItem
property alias value: apiKeyValueHelper.value
property alias exists: apiKeyValueHelper.exists
property bool fullWidth: false
property var editValueTransform: null
Layout.fillWidth: true
@@ -52,7 +54,7 @@ WhiteItemDelegate {
}
onClicked: {
editableItem.value = value
editableItem.value = editValueTransform === null ? value : editValueTransform(value)
dialog.open()
}
@@ -72,7 +74,6 @@ WhiteItemDelegate {
}
onAccepted: {
console.log(editableItem.value)
setValueHelper.setValue(editableItem.value)
}
}
+195 -3
View File
@@ -3,15 +3,207 @@ import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
id: ethernetPage
title: qsTr("Ethernet")
function connectionText(value) {
if (value === null || value === undefined)
return qsTr("Not connected")
return value.ip || qsTr("Connected")
}
function linkText(value) {
if (value === null || value === undefined)
return qsTr("No link")
const speed = value.speed ? value.speed + " Mbit/s" : qsTr("Unknown speed")
const duplex = value.dup === true || value.dup === 1 ? qsTr("full duplex")
: value.dup === false || value.dup === 0 ? qsTr("half duplex")
: value.dup === null || value.dup === undefined ? "" : String(value.dup)
return duplex ? speed + " • " + duplex : speed
}
ApiKeyValueHelper {
id: staticIpMode
deviceConnection: theDeviceConnection
apiKey: "euse"
}
ApiKeyValueHelper {
id: staticDnsMode
deviceConnection: theDeviceConnection
apiKey: "eusd"
}
GeneralOnOffSwitch {
apiKey: "ee"
text: checked ? qsTr("Ethernet on") : qsTr("Ethernet off")
}
WhiteBox {
Layout.fillWidth: true
implicitHeight: connectionStatus.implicitHeight + 20
ColumnLayout {
id: connectionStatus
anchors.fill: parent
anchors.margins: 10
Text {
Layout.fillWidth: true
text: qsTr("Connection")
font.bold: true
}
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 : "—"
}
}
}
}
Text {
text: "TODO"
Layout.fillHeight: true
Layout.fillWidth: true
text: qsTr("IPv4 configuration")
font.bold: true
}
GeneralOnOffSwitch {
apiKey: "euse"
text: checked ? qsTr("Static IP address") : qsTr("Automatic IP address (DHCP)")
}
EditValueItem {
text: qsTr("IP address")
apiKey: "esi"
visible: exists && staticIpMode.value === true
fullWidth: true
editableItem: TextField {
id: ipAddressInput
property alias value: ipAddressInput.text
inputMethodHints: Qt.ImhPreferNumbers
placeholderText: "192.168.1.10"
}
}
EditValueItem {
text: qsTr("Subnet mask")
apiKey: "ess"
visible: exists && staticIpMode.value === true
fullWidth: true
editableItem: TextField {
id: subnetInput
property alias value: subnetInput.text
inputMethodHints: Qt.ImhPreferNumbers
placeholderText: "255.255.255.0"
}
}
EditValueItem {
text: qsTr("Gateway")
apiKey: "esg"
visible: exists && staticIpMode.value === true
fullWidth: true
editableItem: TextField {
id: gatewayInput
property alias value: gatewayInput.text
inputMethodHints: Qt.ImhPreferNumbers
placeholderText: "192.168.1.1"
}
}
GeneralOnOffSwitch {
apiKey: "eusd"
text: checked ? qsTr("Static DNS servers") : qsTr("Automatic DNS servers")
}
EditValueItem {
text: qsTr("Primary DNS")
apiKey: "esd0"
visible: exists && staticDnsMode.value === true
fullWidth: true
editableItem: TextField {
id: dns0Input
property alias value: dns0Input.text
inputMethodHints: Qt.ImhPreferNumbers
placeholderText: "1.1.1.1"
}
}
EditValueItem {
text: qsTr("Secondary DNS")
apiKey: "esd1"
visible: exists && staticDnsMode.value === true
fullWidth: true
editableItem: TextField {
id: dns1Input
property alias value: dns1Input.text
inputMethodHints: Qt.ImhPreferNumbers
placeholderText: "8.8.8.8"
}
}
EditValueItem {
text: qsTr("Additional DNS")
apiKey: "esd2"
visible: exists && staticDnsMode.value === true
fullWidth: true
editableItem: TextField {
id: dns2Input
property alias value: dns2Input.text
inputMethodHints: Qt.ImhPreferNumbers
}
}
WhiteBox {
Layout.fillWidth: true
implicitHeight: ethernetDetails.implicitHeight + 20
visible: ethernetHostname.exists || ethernetMac.exists || ethernetInit.exists
ColumnLayout {
id: ethernetDetails
anchors.fill: parent
anchors.margins: 10
Text {
Layout.fillWidth: true
text: qsTr("Interface details")
font.bold: true
}
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")
}
}
}
}
+64 -3
View File
@@ -3,44 +3,105 @@ import QtQuick.Controls
import QtQuick.Layouts
NavigationPage {
id: hotspotPage
title: qsTr("Hotspot")
GeneralOnOffSwitch {
apiKey: "wae"
text: checked ? qsTr("Hotspot on") : qsTr("Hotspot off")
}
Text {
Layout.fillWidth: true
text: qsTr("Configuration")
font.bold: true
}
GeneralOnOffSwitch {
apiKey: "wda"
text: qsTr("Disable AP when online")
text: qsTr("Turn hotspot off when another internet connection is available")
}
EditValueItem {
text: qsTr("SSID")
apiKey: "wan"
visible: exists
fullWidth: true
editableItem: TextField {
id: textField0
property alias value: textField0.text
placeholderText: qsTr("Hotspot name")
}
}
EditValueItem {
text: qsTr("Password")
apiKey: "wak"
visible: exists
valueText: value ? qsTr("Configured") : qsTr("Open network")
editValueTransform: function() { return "" }
fullWidth: true
editableItem: TextField {
id: textField1
property alias value: textField1.text
echoMode: TextInput.Password
placeholderText: qsTr("Enter a new password")
}
}
ApiEnumEditItem {
text: qsTr("Security")
apiKey: "waap"
visible: exists
choices: [
{ value: 0, text: qsTr("Open") },
{ value: 2, text: qsTr("WPA") },
{ value: 3, text: qsTr("WPA2") },
{ value: 4, text: qsTr("WPA/WPA2") },
{ value: 6, text: qsTr("WPA3") },
{ value: 7, text: qsTr("WPA2/WPA3") }
]
}
EditValueItem {
text: qsTr("Channel")
valueText: Qt.locale().toString(value)
apiKey: "wapc"
visible: exists
editableItem: SpinBox {
from: 0
to: 15
from: 1
to: 14
}
}
WhiteBox {
Layout.fillWidth: true
implicitHeight: hotspotStatus.implicitHeight + 20
visible: hotspotHostname.exists || hotspotMac.exists
ColumnLayout {
id: hotspotStatus
anchors.fill: parent
anchors.margins: 10
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")
}
}
}
}
+567 -85
View File
@@ -4,127 +4,428 @@ import QtQuick.Layouts
import EVChargerApp
NavigationPage {
id: wifiPage
property var selectedNetwork: null
property int selectedWifiSlot: -1
title: qsTr("Wi-Fi")
function configuredNetworks() {
const result = []
const wifis = configuredWifis.value
if (wifis === null || wifis === undefined)
return result
for (let i = 0; i < wifis.length; ++i) {
const wifi = wifis[i]
if (wifi !== null && wifi !== undefined && wifi.ssid) {
result.push({
slot: i,
ssid: wifi.ssid,
useStaticIp: wifi.useStaticIp === true,
staticIp: wifi.staticIp
})
}
}
return result
}
function isConfigured(ssid) {
const networks = configuredNetworks()
for (let i = 0; i < networks.length; ++i) {
if (networks[i].ssid === ssid)
return true
}
return false
}
function firstFreeWifiSlot() {
const wifis = configuredWifis.value
if (wifis === null || wifis === undefined)
return -2
for (let i = 0; i < wifis.length; ++i) {
if (wifis[i] === null || wifis[i] === undefined || !wifis[i].ssid)
return i
}
return -1
}
function availableNetworks() {
const result = []
const scan = wifiScanResult.value
if (scan === null || scan === undefined)
return result
// Android presents an SSID once. Keep the strongest BSSID when the
// same network was observed through multiple access points.
for (let i = 0; i < scan.length; ++i) {
const candidate = scan[i]
if (candidate === null || candidate === undefined || !candidate.ssid)
continue
let existing = -1
for (let j = 0; j < result.length; ++j) {
if (result[j].ssid === candidate.ssid) {
existing = j
break
}
}
if (existing < 0)
result.push(candidate)
else if ((candidate.rssi ?? -1000) > (result[existing].rssi ?? -1000))
result[existing] = candidate
}
result.sort((left, right) => (right.rssi ?? -1000) - (left.rssi ?? -1000))
return result
}
function securityName(encryptionType) {
switch (encryptionType) {
case 0: return qsTr("Open")
case 1: return qsTr("WEP")
case 2: return qsTr("WPA")
case 3: return qsTr("WPA2")
case 4: return qsTr("WPA/WPA2")
case 5: return qsTr("WPA2 Enterprise")
case 6: return qsTr("WPA3")
case 7: return qsTr("WPA2/WPA3")
case 8: return qsTr("WAPI")
case 9: return qsTr("OWE")
case 10: return qsTr("WPA3 Enterprise")
case 11: return qsTr("WPA3 External PSK")
case 12: return qsTr("WPA3 External PSK mixed")
default: return qsTr("Unknown security")
}
}
function needsPassword(network) {
return network.encryptionType !== 0 && network.encryptionType !== 9
}
function signalBars(rssi) {
if (rssi >= -55)
return "▂▄▆█"
if (rssi >= -67)
return "▂▄▆"
if (rssi >= -75)
return "▂▄"
return "▂"
}
function stationStatusName(status) {
switch (status) {
case 0: return qsTr("Idle")
case 1: return qsTr("Network not found")
case 2: return qsTr("Scan completed")
case 3: return qsTr("Connected")
case 4: return qsTr("Connection failed")
case 5: return qsTr("Connection lost")
case 6: return qsTr("Disconnected")
case 7: return qsTr("Connecting")
case 8: return qsTr("Disconnecting")
case 9: return qsTr("Wi-Fi unavailable")
case 10: return qsTr("Waiting for IP address")
default: return qsTr("Unknown")
}
}
function scanStatusName(status) {
switch (status) {
case 0: return qsTr("Not scanned yet")
case 1: return qsTr("Scanning")
case 2: return qsTr("Finished")
case 3: return qsTr("Failed")
default: return qsTr("Unknown")
}
}
function selectNetwork(network) {
if (isConfigured(network.ssid))
return
const slot = firstFreeWifiSlot()
if (slot === -2) {
noSlotDialog.text = qsTr("The Wi-Fi configuration has not been received yet.")
noSlotDialog.open()
return
}
if (slot < 0) {
noSlotDialog.text = qsTr("All Wi-Fi configuration slots are occupied. Delete a configured network first.")
noSlotDialog.open()
return
}
selectedNetwork = network
selectedWifiSlot = slot
passwordInput.clear()
showPassword.checked = false
passwordDialog.open()
}
function provisionSelectedNetwork() {
if (selectedNetwork === null || selectedWifiSlot < 0)
return
const value = []
for (let i = 0; i < selectedWifiSlot; ++i)
value.push(null)
value.push({
ssid: selectedNetwork.ssid,
key: needsPassword(selectedNetwork) ? passwordInput.text : ""
})
provisionWifi.setValue(value)
}
ApiKeyValueHelper {
id: configuredWifis
deviceConnection: theDeviceConnection
apiKey: "wifis"
}
ApiKeyValueHelper {
id: wifiScanResult
deviceConnection: theDeviceConnection
apiKey: "scan"
}
SetValueHelper {
id: provisionWifi
deviceConnection: theDeviceConnection
apiKey: "wifis"
}
SetValueHelper {
id: deleteWifi
deviceConnection: theDeviceConnection
apiKey: "delw"
}
SendMessageHelper {
id: triggerScan
deviceConnection: theDeviceConnection
}
ConfirmingOnOffSwitch {
apiKey: "wen"
dialogTitle: qsTr("Are you sure?")
dialogText: qsTr("Disabling Wi-Fi could make your device unreachable from your local homenetwork or the cloud!")
}
GridLayout {
columns: 2
WhiteBox {
Layout.fillWidth: true
implicitHeight: connectionStatusColumn.implicitHeight + 20
Text {
text: qsTr("Status:")
}
ColumnLayout {
id: connectionStatusColumn
anchors.fill: parent
anchors.margins: 10
Text {
Layout.fillWidth: true
ApiKeyValueHelper {
id: staStatus
deviceConnection: theDeviceConnection
apiKey: "wst"
Text {
Layout.fillWidth: true
text: qsTr("Connection")
font.bold: true
}
text: {
switch (staStatus.value)
{
case 0: return "IDLE_STATUS"
case 1: return "NO_SSID_AVAIL"
case 2: return "SCAN_COMPLETED"
case 3: return "CONNECTED"
case 4: return "CONNECT_FAILED"
case 5: return "CONNECTION_LOST"
case 6: return "DISCONNECTED"
case 7: return "CONNECTING"
case 8: return "DISCONNECTING"
case 9: return "NO_SHIELD"
case 10: return "WAITING_FOR_IP"
default: return staStatus.value
ApiStatusRow {
apiKey: "wst"
label: qsTr("Status")
valueFormatter: function(value) { return stationStatusName(value) }
}
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: "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)
}
}
}
}
Text {
text: qsTr("Scan-Status:")
}
RowLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
text: qsTr("Configured networks")
font.bold: true
}
BusyIndicator {
visible: provisionWifi.pending || deleteWifi.pending || triggerScan.pending
}
Button {
text: scanStatus.value === 1 ? qsTr("Scanning…") : qsTr("Scan again")
enabled: !triggerScan.pending && scanStatus.value !== 1
onClicked: triggerScan.sendMessage({type: "wifiScan"})
ApiKeyValueHelper {
id: scanStatus
deviceConnection: theDeviceConnection
apiKey: "scas"
}
text: {
switch (scanStatus.value)
{
case 0: return "None"
case 1: return "Scanning"
case 2: return "Finished"
case 3: return "Failed"
default: return scanStatus.value
}
}
}
Text {
text: qsTr("Scan-Age:")
}
Text {
Layout.fillWidth: true
ApiKeyValueHelper {
id: scanAgeStatus
deviceConnection: theDeviceConnection
apiKey: "scaa"
}
text: scanAgeStatus.value != null ? formatDuration(scanAgeStatus.value - rebootTime.value) : null
}
}
RowLayout {
SendMessageButton {
Text {
Layout.fillWidth: true
text: qsTr("No configured networks")
visible: configuredRepeater.count === 0
color: "gray"
}
Repeater {
id: configuredRepeater
model: configuredNetworks()
delegate: WhiteBox {
required property var modelData
Layout.fillWidth: true
text: qsTr("Trigger scan")
messageType: "wifiScan"
}
implicitHeight: configuredRow.implicitHeight + 20
Button {
ApiKeyValueHelper {
id: wifiScanResult
deviceConnection: theDeviceConnection
apiKey: "scan"
}
RowLayout {
id: configuredRow
anchors.fill: parent
anchors.margins: 10
ApiKeyValueHelper {
id: configuredWifis
deviceConnection: theDeviceConnection
apiKey: "wifis"
}
ColumnLayout {
Layout.fillWidth: true
text: qsTr("(%0) Available networks").arg(wifiScanResult.value == null ? 0 : wifiScanResult.value.length)
onClicked: stackView.push(wiFiScanPageComponent, {
wifiScanResult: wifiScanResult,
configuredWifis: configuredWifis
})
enabled: wifiScanResult.value != null
Text {
Layout.fillWidth: true
text: modelData.ssid
font.bold: true
elide: Text.ElideRight
}
Component {
id: wiFiScanPageComponent
Text {
text: modelData.useStaticIp
? qsTr("Static IP %1 • slot %2").arg(modelData.staticIp).arg(modelData.slot + 1)
: qsTr("Automatic IP • slot %1").arg(modelData.slot + 1)
color: "gray"
}
}
WiFiScanPage {
Button {
text: qsTr("Delete")
enabled: !deleteWifi.pending
onClicked: {
selectedWifiSlot = modelData.slot
deleteDialog.networkName = modelData.ssid
deleteDialog.open()
}
}
}
}
}
Text {
Layout.fillWidth: true
Layout.topMargin: 10
text: qsTr("Available networks")
font.bold: true
}
Repeater {
model: availableNetworks()
delegate: WhiteItemDelegate {
required property var modelData
readonly property bool alreadyConfigured: isConfigured(modelData.ssid)
Layout.fillWidth: true
implicitHeight: availableRow.implicitHeight + 20
enabled: !alreadyConfigured && !provisionWifi.pending
onClicked: selectNetwork(modelData)
contentItem: RowLayout {
id: availableRow
ColumnLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
text: modelData.ssid
font.bold: true
elide: Text.ElideRight
}
Text {
Layout.fillWidth: true
text: alreadyConfigured
? qsTr("Saved • %1").arg(securityName(modelData.encryptionType))
: securityName(modelData.encryptionType)
color: "gray"
elide: Text.ElideRight
}
}
Text {
text: signalBars(modelData.rssi)
font.bold: true
Accessible.name: qsTr("Signal strength %1 dBm").arg(modelData.rssi)
}
Text {
text: alreadyConfigured ? "✓" : ">"
}
}
}
}
Text {
Layout.fillWidth: true
text: qsTr("No networks found. Start another scan.")
visible: availableNetworks().length === 0
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
}
RequestStatusText {
Layout.fillWidth: true
request: provisionWifi
}
RequestStatusText {
Layout.fillWidth: true
request: deleteWifi
}
Button {
ApiKeyValueHelper {
id: wifiErrorLog
@@ -133,7 +434,7 @@ NavigationPage {
}
text: qsTr("(%0) Wi-Fi Errors").arg(wifiErrorLog.value == null ? 0 : wifiErrorLog.value.length)
onClicked: stackView.push(wiFiErrorsPageComponent, {wifiErrorLog} )
onClicked: stackView.push(wiFiErrorsPageComponent, {wifiErrorLog})
enabled: wifiErrorLog.value != null
Component {
@@ -144,10 +445,18 @@ NavigationPage {
}
}
Text {
Layout.fillWidth: true
Layout.topMargin: 10
text: qsTr("Advanced Wi-Fi settings")
font.bold: true
}
EditValueItem {
text: qsTr("Min RSSI")
valueText: Qt.locale().toString(value)
apiKey: "wsmr"
visible: exists
editableItem: SpinBox {
from: -100
to: 0
@@ -156,11 +465,184 @@ NavigationPage {
GeneralOnOffSwitch {
apiKey: "wspc"
text: qsTr("Pmf capable")
text: qsTr("Support protected management frames")
}
GeneralOnOffSwitch {
apiKey: "wspr"
text: qsTr("Pmf required")
text: qsTr("Require protected management frames")
}
WhiteBox {
Layout.fillWidth: true
implicitHeight: wifiDetailsColumn.implicitHeight + 20
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: 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: wifiMac
apiKey: "macs"
label: qsTr("MAC address")
}
ApiStatusRow {
id: wifiHostname
apiKey: "hsts"
label: qsTr("Hostname")
}
ApiStatusRow {
id: wifiErrorCount
apiKey: "wsc"
label: qsTr("Connection errors")
}
}
}
CenteredDialog {
id: passwordDialog
title: selectedNetwork === null ? qsTr("Connect to Wi-Fi") : selectedNetwork.ssid
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
onOpened: {
passwordInput.forceActiveFocus()
standardButton(Dialog.Ok).enabled = !needsPassword(selectedNetwork)
}
onAccepted: provisionSelectedNetwork()
contentItem: ColumnLayout {
Text {
Layout.fillWidth: true
text: selectedNetwork === null ? "" : securityName(selectedNetwork.encryptionType)
color: "gray"
}
TextField {
id: passwordInput
Layout.minimumWidth: 240
Layout.fillWidth: true
visible: selectedNetwork !== null && needsPassword(selectedNetwork)
placeholderText: qsTr("Password")
echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password
maximumLength: 64
inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText
onTextChanged: {
if (passwordDialog.visible)
passwordDialog.standardButton(Dialog.Ok).enabled = text.length >= 8
}
onAccepted: {
if (text.length >= 8)
passwordDialog.accept()
}
}
CheckBox {
id: showPassword
visible: passwordInput.visible
text: qsTr("Show password")
}
Text {
Layout.fillWidth: true
visible: passwordInput.visible && passwordInput.text.length > 0 && passwordInput.text.length < 8
text: qsTr("The password must contain at least 8 characters.")
color: "red"
wrapMode: Text.Wrap
}
Text {
Layout.fillWidth: true
visible: selectedNetwork !== null && !needsPassword(selectedNetwork)
text: qsTr("This network does not require a password.")
wrapMode: Text.Wrap
}
Text {
Layout.fillWidth: true
text: qsTr("The network will be saved in slot %1.").arg(selectedWifiSlot + 1)
wrapMode: Text.Wrap
}
}
}
CenteredDialog {
id: deleteDialog
property string networkName
title: qsTr("Delete Wi-Fi network?")
standardButtons: Dialog.Yes | Dialog.No
modal: true
focus: true
onAccepted: deleteWifi.setValue(selectedWifiSlot)
contentItem: Text {
text: qsTr("Delete %1 and all of its IP and DNS settings?").arg(deleteDialog.networkName)
wrapMode: Text.Wrap
}
}
CenteredDialog {
id: noSlotDialog
property alias text: noSlotText.text
title: qsTr("Cannot add Wi-Fi network")
standardButtons: Dialog.Ok
modal: true
focus: true
contentItem: Text {
id: noSlotText
wrapMode: Text.Wrap
}
}
}
-414
View File
@@ -1,414 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import EVChargerApp
BaseNavigationPage {
property ApiKeyValueHelper wifiScanResult
property ApiKeyValueHelper configuredWifis
property var selectedNetwork: null
property int selectedWifiSlot: -1
title: qsTr("Wi-Fi networks")
function configuredNetworks() {
const result = []
const wifis = configuredWifis === null ? null : configuredWifis.value
if (wifis === null || wifis === undefined)
return result
for (let i = 0; i < wifis.length; ++i) {
const wifi = wifis[i]
if (wifi !== null && wifi !== undefined && wifi.ssid)
result.push({slot: i, ssid: wifi.ssid, hasKey: wifi.key === true})
}
return result
}
function isConfigured(ssid) {
const networks = configuredNetworks()
for (let i = 0; i < networks.length; ++i) {
if (networks[i].ssid === ssid)
return true
}
return false
}
function firstFreeWifiSlot() {
const wifis = configuredWifis === null ? null : configuredWifis.value
if (wifis === null || wifis === undefined)
return -2
for (let i = 0; i < wifis.length; ++i) {
if (wifis[i] === null || wifis[i] === undefined || !wifis[i].ssid)
return i
}
return -1
}
function availableNetworks() {
const result = []
const scan = wifiScanResult === null ? null : wifiScanResult.value
if (scan === null || scan === undefined)
return result
// Android presents an SSID once. Keep the strongest BSSID when the
// same network was observed through multiple access points.
for (let i = 0; i < scan.length; ++i) {
const candidate = scan[i]
if (candidate === null || candidate === undefined || !candidate.ssid)
continue
let existing = -1
for (let j = 0; j < result.length; ++j) {
if (result[j].ssid === candidate.ssid) {
existing = j
break
}
}
if (existing < 0)
result.push(candidate)
else if ((candidate.rssi ?? -1000) > (result[existing].rssi ?? -1000))
result[existing] = candidate
}
result.sort((left, right) => (right.rssi ?? -1000) - (left.rssi ?? -1000))
return result
}
function securityName(encryptionType) {
switch (encryptionType) {
case 0: return qsTr("Open")
case 1: return qsTr("WEP")
case 2: return qsTr("WPA")
case 3: return qsTr("WPA2")
case 4: return qsTr("WPA/WPA2")
case 5: return qsTr("WPA2 Enterprise")
case 6: return qsTr("WPA3")
case 7: return qsTr("WPA2/WPA3")
case 8: return qsTr("WAPI")
case 9: return qsTr("OWE")
case 10: return qsTr("WPA3 Enterprise")
case 11: return qsTr("WPA3 External PSK")
case 12: return qsTr("WPA3 External PSK mixed")
default: return qsTr("Unknown security")
}
}
function needsPassword(network) {
return network.encryptionType !== 0 && network.encryptionType !== 9
}
function signalBars(rssi) {
if (rssi >= -55)
return "▂▄▆█"
if (rssi >= -67)
return "▂▄▆"
if (rssi >= -75)
return "▂▄"
return "▂"
}
function selectNetwork(network) {
if (isConfigured(network.ssid))
return
const slot = firstFreeWifiSlot()
if (slot === -2) {
noSlotDialog.text = qsTr("The Wi-Fi configuration has not been received yet.")
noSlotDialog.open()
return
}
if (slot < 0) {
noSlotDialog.text = qsTr("All Wi-Fi configuration slots are occupied. Delete a configured network first.")
noSlotDialog.open()
return
}
selectedNetwork = network
selectedWifiSlot = slot
passwordInput.clear()
passwordDialog.open()
}
function provisionSelectedNetwork() {
if (selectedNetwork === null || selectedWifiSlot < 0)
return
const value = []
for (let i = 0; i < selectedWifiSlot; ++i)
value.push(null)
value.push({
ssid: selectedNetwork.ssid,
key: needsPassword(selectedNetwork) ? passwordInput.text : ""
})
provisionWifi.setValue(value)
}
headerItems: RowLayout {
BusyIndicator {
visible: provisionWifi.pending || deleteWifi.pending || triggerScan.pending
}
Button {
text: qsTr("Scan again")
enabled: !triggerScan.pending
onClicked: triggerScan.sendMessage({type: "wifiScan"})
}
}
SetValueHelper {
id: provisionWifi
deviceConnection: theDeviceConnection
apiKey: "wifis"
}
SetValueHelper {
id: deleteWifi
deviceConnection: theDeviceConnection
apiKey: "delw"
}
SendMessageHelper {
id: triggerScan
deviceConnection: theDeviceConnection
}
Flickable {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
contentHeight: networksColumn.implicitHeight
ScrollBar.vertical: ScrollBar {}
ColumnLayout {
id: networksColumn
width: parent.width - 30
x: 15
spacing: 5
Text {
Layout.fillWidth: true
text: qsTr("Configured networks")
font.bold: true
visible: configuredRepeater.count > 0
}
Repeater {
id: configuredRepeater
model: configuredNetworks()
delegate: WhiteBox {
required property var modelData
Layout.fillWidth: true
implicitHeight: configuredRow.implicitHeight + 20
RowLayout {
id: configuredRow
anchors.fill: parent
anchors.margins: 10
ColumnLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
text: modelData.ssid
font.bold: true
elide: Text.ElideRight
}
Text {
text: qsTr("Saved in slot %1").arg(modelData.slot + 1)
color: "gray"
}
}
Button {
text: qsTr("Delete")
enabled: !deleteWifi.pending
onClicked: {
selectedWifiSlot = modelData.slot
deleteDialog.networkName = modelData.ssid
deleteDialog.open()
}
}
}
}
}
Text {
Layout.fillWidth: true
Layout.topMargin: configuredRepeater.count > 0 ? 10 : 0
text: qsTr("Available networks")
font.bold: true
}
Repeater {
model: availableNetworks()
delegate: WhiteItemDelegate {
required property var modelData
readonly property bool alreadyConfigured: isConfigured(modelData.ssid)
Layout.fillWidth: true
implicitHeight: availableRow.implicitHeight + 20
enabled: !alreadyConfigured && !provisionWifi.pending
onClicked: selectNetwork(modelData)
contentItem: RowLayout {
id: availableRow
ColumnLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
text: modelData.ssid
font.bold: true
elide: Text.ElideRight
}
Text {
Layout.fillWidth: true
text: alreadyConfigured
? qsTr("Saved • %1").arg(securityName(modelData.encryptionType))
: securityName(modelData.encryptionType)
color: "gray"
elide: Text.ElideRight
}
}
Text {
text: signalBars(modelData.rssi)
font.bold: true
Accessible.name: qsTr("Signal strength %1 dBm").arg(modelData.rssi)
}
Text {
text: alreadyConfigured ? "✓" : ">"
}
}
}
}
Text {
Layout.fillWidth: true
text: qsTr("No networks found. Start another scan.")
visible: availableNetworks().length === 0
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
}
RequestStatusText {
Layout.fillWidth: true
request: provisionWifi
}
RequestStatusText {
Layout.fillWidth: true
request: deleteWifi
}
}
}
CenteredDialog {
id: passwordDialog
title: selectedNetwork === null ? qsTr("Connect to Wi-Fi") : selectedNetwork.ssid
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
focus: true
onOpened: {
passwordInput.forceActiveFocus()
standardButton(Dialog.Ok).enabled = !needsPassword(selectedNetwork)
}
onAccepted: provisionSelectedNetwork()
contentItem: ColumnLayout {
Text {
Layout.fillWidth: true
text: selectedNetwork === null ? "" : securityName(selectedNetwork.encryptionType)
color: "gray"
}
TextField {
id: passwordInput
Layout.minimumWidth: 240
Layout.fillWidth: true
visible: selectedNetwork !== null && needsPassword(selectedNetwork)
placeholderText: qsTr("Password")
echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password
maximumLength: 64
inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText
onTextChanged: {
if (passwordDialog.visible)
passwordDialog.standardButton(Dialog.Ok).enabled = text.length >= 8
}
onAccepted: {
if (text.length >= 8)
passwordDialog.accept()
}
}
CheckBox {
id: showPassword
visible: passwordInput.visible
text: qsTr("Show password")
}
Text {
Layout.fillWidth: true
visible: passwordInput.visible && passwordInput.text.length > 0 && passwordInput.text.length < 8
text: qsTr("The password must contain at least 8 characters.")
color: "red"
wrapMode: Text.Wrap
}
Text {
Layout.fillWidth: true
visible: selectedNetwork !== null && !needsPassword(selectedNetwork)
text: qsTr("This network does not require a password.")
wrapMode: Text.Wrap
}
Text {
Layout.fillWidth: true
text: qsTr("The network will be saved in slot %1.").arg(selectedWifiSlot + 1)
wrapMode: Text.Wrap
}
}
}
CenteredDialog {
id: deleteDialog
property string networkName
title: qsTr("Delete Wi-Fi network?")
standardButtons: Dialog.Yes | Dialog.No
modal: true
focus: true
onAccepted: deleteWifi.setValue(selectedWifiSlot)
contentItem: Text {
text: qsTr("Delete %1 and all of its IP and DNS settings?").arg(deleteDialog.networkName)
wrapMode: Text.Wrap
}
}
CenteredDialog {
id: noSlotDialog
property alias text: noSlotText.text
title: qsTr("Cannot add Wi-Fi network")
standardButtons: Dialog.Ok
modal: true
focus: true
contentItem: Text {
id: noSlotText
wrapMode: Text.Wrap
}
}
}
+2 -1
View File
@@ -6,6 +6,8 @@ EVChargerApp 1.0 AddDeviceScreen.qml
EVChargerApp 1.0 AnimatedStackLayout.qml
EVChargerApp 1.0 AnimatedStackView.qml
EVChargerApp 1.0 ApiKeyValueItem.qml
EVChargerApp 1.0 ApiStatusRow.qml
EVChargerApp 1.0 ApiEnumEditItem.qml
EVChargerApp 1.0 ApiSettingsPage.qml
EVChargerApp 1.0 AppInstance.qml
EVChargerApp 1.0 AppSettingsPage.qml
@@ -91,4 +93,3 @@ EVChargerApp 1.0 WhiteItemDelegate.qml
EVChargerApp 1.0 WhiteSwipeDelegate.qml
EVChargerApp 1.0 WiFiErrorsPage.qml
EVChargerApp 1.0 WiFiPage.qml
EVChargerApp 1.0 WiFiScanPage.qml