This commit is contained in:
1 parent
65cae8cc9c
commit
87bdb649f5
12 files changed
+1113
-786
No files matched your search
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in new issue
Block a user