C++ / fmt (pull_request) Successful in 5s
C++ / build (pull_request) Successful in 2m10s
JS/TS / fmt (pull_request) Successful in 16s
JS/TS / lint (pull_request) Successful in 17s
Python / static (pull_request) Successful in 44s
C++ / clang-tidy (pull_request) Successful in 3m37s
Rust / build (pull_request) Successful in 1m3s
Rust / fmt (pull_request) Failing after 15m10s
Python / verify (pull_request) Failing after 16m16s
Rust / clippy (pull_request) Successful in 45s
257 lines
6.1 KiB
QML
257 lines
6.1 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
import Quickshell
|
|
import ZShell.Config
|
|
import ZShell.Internal
|
|
import ZShell.Blobs
|
|
import ZShell.Services
|
|
import ZShell.Components
|
|
import qs.Components
|
|
import qs.Modules.Settings.Common
|
|
|
|
PageBase {
|
|
id: root
|
|
|
|
property bool connecting: false
|
|
property bool failed: false
|
|
|
|
// Security model: index 0 = none, 1 = WPA/WPA2/WPA3 personal.
|
|
readonly property bool secured: securitySelect.active !== noneItem
|
|
property bool success: false
|
|
|
|
function submit(): void {
|
|
const ssid = ssidField.text.trim();
|
|
if (ssid.length === 0) {
|
|
ssidField.isError = true;
|
|
ssidField.forceActiveFocus();
|
|
return;
|
|
}
|
|
if (root.secured && passwordField.text.length < 8) {
|
|
passwordField.isError = true;
|
|
passwordField.forceActiveFocus();
|
|
return;
|
|
}
|
|
|
|
root.failed = false;
|
|
root.connecting = true;
|
|
|
|
Nmcli.addHiddenNetwork(ssid, root.secured ? passwordField.text : "", root.secured ? "wpa" : "none", hiddenToggle.checked, result => {
|
|
root.connecting = false;
|
|
if (result && result.success) {
|
|
root.success = true;
|
|
root.nState.closeSubPage();
|
|
} else {
|
|
root.failed = true;
|
|
if (root.secured)
|
|
passwordField.isError = true;
|
|
// Clean up the half-created profile so a retry starts fresh.
|
|
Nmcli.forgetNetwork(ssid);
|
|
}
|
|
});
|
|
}
|
|
|
|
isSubPage: true
|
|
title: Tr.tr("Add network")
|
|
|
|
ColumnLayout {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
spacing: Tokens.spacing.large
|
|
width: root.cappedWidth
|
|
|
|
Connections {
|
|
function onSubPageClosed(): void {
|
|
if (root.success)
|
|
return;
|
|
|
|
const ssid = ssidField.text.trim();
|
|
if (ssid)
|
|
Nmcli.forgetNetwork(ssid);
|
|
}
|
|
|
|
target: root.nState
|
|
}
|
|
|
|
CustomText {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Tokens.padding.extraSmall
|
|
color: Colours.palette.m3onSurfaceVariant
|
|
font: Tokens.font.body.small
|
|
text: Tr.tr("Enter the details below to manually connect to a network.")
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
CustomTextField {
|
|
id: ssidField
|
|
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: Tokens.spacing.extraSmall
|
|
errorText: Tr.tr("Network name is required")
|
|
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText
|
|
leadingIcon: "wifi"
|
|
// TRANSLATORS: SSID is a protocol term, leave it untranslated
|
|
placeholderText: Tr.tr("Network name (SSID)")
|
|
// TRANSLATORS: sample network name, translate the e.g. but the name is a placeholder
|
|
supportingText: Tr.tr("e.g. MyHiddenNetwork")
|
|
|
|
onAccepted: root.secured ? passwordField.forceActiveFocus() : root.submit()
|
|
}
|
|
|
|
ToggleRow {
|
|
id: hiddenToggle
|
|
|
|
checked: true
|
|
first: true
|
|
subtext: Tr.tr("Actively probe for a network that doesn't broadcast its name")
|
|
text: Tr.tr("Hidden network")
|
|
}
|
|
|
|
SelectRow {
|
|
id: securitySelect
|
|
|
|
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
|
|
fallbackIcon: "lock"
|
|
// TRANSLATORS: WPA/WPA2/WPA3 are protocol names, leave them untranslated
|
|
fallbackText: Tr.tr("WPA/WPA2/WPA3 Personal")
|
|
// label: Tr.tr("Security")
|
|
last: !root.secured
|
|
|
|
Behavior on bottomLeftRadius {
|
|
Anim {
|
|
type: Anim.DefaultEffects
|
|
}
|
|
}
|
|
Behavior on bottomRightRadius {
|
|
Anim {
|
|
type: Anim.DefaultEffects
|
|
}
|
|
}
|
|
menuItems: [
|
|
MenuItem {
|
|
icon: "lock"
|
|
// TRANSLATORS: WPA/WPA2/WPA3 are protocol names, leave them untranslated
|
|
text: Tr.tr("WPA/WPA2/WPA3 Personal")
|
|
},
|
|
MenuItem {
|
|
id: noneItem
|
|
|
|
icon: "lock_open"
|
|
text: Tr.trCtx("None (open)", "wifi security type")
|
|
}
|
|
]
|
|
}
|
|
|
|
Item {
|
|
Layout.bottomMargin: root.secured ? 0 : -parent.spacing
|
|
Layout.fillWidth: true
|
|
implicitHeight: root.secured ? passwordField.implicitHeight : 0
|
|
opacity: root.secured ? 1 : 0
|
|
|
|
Behavior on Layout.bottomMargin {
|
|
Anim {
|
|
type: Anim.DefaultEffects
|
|
}
|
|
}
|
|
Behavior on implicitHeight {
|
|
Anim {
|
|
type: Anim.DefaultEffects
|
|
}
|
|
}
|
|
Behavior on opacity {
|
|
Anim {
|
|
type: Anim.DefaultEffects
|
|
}
|
|
}
|
|
|
|
CustomTextField {
|
|
id: passwordField
|
|
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
echoMode: TextInput.Password
|
|
enabled: root.secured
|
|
errorText: root.failed ? Tr.tr("Connection failed — check the password") : Tr.tr("Password must be at least 8 characters")
|
|
leadingIcon: "key"
|
|
placeholderText: Tr.tr("Password")
|
|
// TRANSLATORS: WPA is a protocol name, leave it untranslated
|
|
supportingText: Tr.tr("WPA passwords are at least 8 characters")
|
|
|
|
onAccepted: root.submit()
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.alignment: Qt.AlignRight
|
|
Layout.topMargin: Tokens.spacing.extraSmall - parent.spacing
|
|
spacing: Tokens.spacing.small
|
|
|
|
TextButton {
|
|
Layout.fillHeight: true
|
|
horizontalPadding: Tokens.padding.extraLarge
|
|
isRound: true
|
|
text: Tr.trCtx("Cancel", "button")
|
|
type: TextButton.Tonal
|
|
|
|
onClicked: root.nState.closeSubPage()
|
|
}
|
|
|
|
// Connect button — swaps to a loading spinner while connecting,
|
|
// matching the Wi-Fi list connect animation.
|
|
ButtonBase {
|
|
id: connectBtn
|
|
|
|
implicitHeight: connectMetrics.height + Tokens.padding.medium * 2
|
|
implicitWidth: connectMetrics.width + Tokens.padding.extraLarge * 2
|
|
// inactiveColour: Colours.palette.m3primary
|
|
// inactiveOnColour: Colours.palette.m3onPrimary
|
|
isRound: true
|
|
shapeMorph: true
|
|
|
|
// stateLayer.disabled: root.connecting || ssidField.text.trim().length === 0
|
|
|
|
onClicked: {
|
|
if (!root.connecting && ssidField.text.trim().length > 0)
|
|
root.submit();
|
|
}
|
|
|
|
TextMetrics {
|
|
id: connectMetrics
|
|
|
|
font: connectBtn.font
|
|
text: Tr.tr("Connect")
|
|
}
|
|
|
|
Anim {
|
|
id: connectContent
|
|
|
|
// anchors.centerIn: parent
|
|
// inAnimType: Anim.SlowEffects
|
|
// outAnimType: Anim.SlowEffects
|
|
// sourceComp: root.connecting ? connectLoadingComp : connectTextComp
|
|
}
|
|
|
|
Component {
|
|
id: connectLoadingComp
|
|
|
|
LoadingIndicator {
|
|
color: connectBtn.onColour
|
|
implicitSize: Math.round(Tokens.font.body.medium.pointSize * 1.4)
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: connectTextComp
|
|
|
|
CustomText {
|
|
color: connectBtn.onColour
|
|
font: connectBtn.font
|
|
text: connectMetrics.text
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|