import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Wayland import qs.Constants import qs.Noctalia import qs.Services import qs.Utils NPanel { id: root property string passwordSsid: "" property string passwordInput: "" property string expandedSsid: "" preferredWidth: 400 preferredHeight: 500 onOpened: NetworkService.scan() panelContent: Rectangle { color: Color.transparent ColumnLayout { anchors.fill: parent anchors.margins: Style.marginL spacing: Style.marginM // Header RowLayout { Layout.fillWidth: true spacing: Style.marginM NIcon { icon: SettingsService.wifiEnabled ? "wifi" : "wifi-off" pointSize: Style.fontSizeXXL color: SettingsService.wifiEnabled ? Color.mPrimary : Color.mOnSurfaceVariant } NText { text: "WiFi" pointSize: Style.fontSizeL font.weight: Style.fontWeightBold color: Color.mOnSurface Layout.fillWidth: true } NToggle { id: wifiSwitch checked: SettingsService.wifiEnabled onToggled: (checked) => { return NetworkService.setWifiEnabled(checked); } baseSize: Style.baseWidgetSize * 0.65 } NIconButton { icon: "refresh" baseSize: Style.baseWidgetSize * 0.8 enabled: SettingsService.wifiEnabled && !NetworkService.scanning onClicked: NetworkService.scan() colorFg: Colors.green colorBg: Color.transparent colorFgHover: Colors.base colorBgHover: Colors.green } NIconButton { icon: "close" baseSize: Style.baseWidgetSize * 0.8 onClicked: root.close() colorFg: Colors.red colorBg: Color.transparent colorFgHover: Colors.base colorBgHover: Colors.red } } NDivider { Layout.fillWidth: true } // Error message Rectangle { visible: NetworkService.lastError.length > 0 Layout.fillWidth: true Layout.preferredHeight: errorRow.implicitHeight + (Style.marginM * 2) color: Qt.rgba(Color.mError.r, Color.mError.g, Color.mError.b, 0.1) radius: Style.radiusS border.width: Math.max(1, Style.borderS) border.color: Color.mError RowLayout { id: errorRow anchors.fill: parent anchors.margins: Style.marginM spacing: Style.marginS NIcon { icon: "warning" pointSize: Style.fontSizeL color: Color.mError } NText { text: NetworkService.lastError color: Color.mError pointSize: Style.fontSizeS wrapMode: Text.Wrap Layout.fillWidth: true } NIconButton { icon: "close" baseSize: Style.baseWidgetSize * 0.6 onClicked: NetworkService.lastError = "" } } } // Main content area Rectangle { Layout.fillWidth: true Layout.fillHeight: true color: Color.transparent // WiFi disabled state ColumnLayout { visible: !SettingsService.wifiEnabled anchors.fill: parent spacing: Style.marginM Item { Layout.fillHeight: true } NIcon { icon: "wifi-off" pointSize: 64 color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: "Wi-Fi Disabled" pointSize: Style.fontSizeL color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: "Please enable Wi-Fi to connect to a network." pointSize: Style.fontSizeS color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } Item { Layout.fillHeight: true } } // Scanning state ColumnLayout { visible: SettingsService.wifiEnabled && NetworkService.scanning && Object.keys(NetworkService.networks).length === 0 anchors.fill: parent spacing: Style.marginL Item { Layout.fillHeight: true } NBusyIndicator { running: true color: Color.mPrimary size: Style.baseWidgetSize Layout.alignment: Qt.AlignHCenter } NText { text: "Searching for networks..." pointSize: Style.fontSizeM color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } Item { Layout.fillHeight: true } } // Networks list container NScrollView { visible: SettingsService.wifiEnabled && (!NetworkService.scanning || Object.keys(NetworkService.networks).length > 0) anchors.fill: parent horizontalPolicy: ScrollBar.AlwaysOff verticalPolicy: ScrollBar.AsNeeded clip: true ColumnLayout { width: parent.width spacing: Style.marginM // Network list Repeater { model: { if (!SettingsService.wifiEnabled) return []; const nets = Object.values(NetworkService.networks); return nets.sort((a, b) => { if (a.connected !== b.connected) return b.connected - a.connected; return b.signal - a.signal; }); } NBox { Layout.fillWidth: true implicitHeight: netColumn.implicitHeight + (Style.marginM * 2) // Add opacity for operations in progress opacity: (NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid) ? 0.6 : 1 ColumnLayout { id: netColumn width: parent.width - (Style.marginM * 2) x: Style.marginM y: Style.marginM spacing: Style.marginS // Main row RowLayout { Layout.fillWidth: true spacing: Style.marginS NIcon { icon: NetworkService.signalIcon(modelData.signal) pointSize: Style.fontSizeXXL color: modelData.connected ? Color.mPrimary : Color.mOnSurface } ColumnLayout { Layout.fillWidth: true spacing: 2 NText { text: modelData.ssid pointSize: Style.fontSizeM font.weight: modelData.connected ? Style.fontWeightBold : Style.fontWeightMedium color: Color.mOnSurface elide: Text.ElideRight Layout.fillWidth: true } RowLayout { spacing: Style.marginXS NText { text: `${modelData.signal}%` pointSize: Style.fontSizeXXS color: Color.mOnSurfaceVariant } NText { text: "•" pointSize: Style.fontSizeXXS color: Color.mOnSurfaceVariant } NText { text: NetworkService.isSecured(modelData.security) ? modelData.security : "Open" pointSize: Style.fontSizeXXS color: Color.mOnSurfaceVariant } Item { Layout.preferredWidth: Style.marginXXS } // Update the status badges area (around line 237) Rectangle { visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid color: Color.mPrimary radius: height * 0.5 width: connectedText.implicitWidth + (Style.marginS * 2) height: connectedText.implicitHeight + (Style.marginXXS * 2) NText { id: connectedText anchors.centerIn: parent text: "Connected" pointSize: Style.fontSizeXXS color: Color.mOnPrimary } } Rectangle { visible: NetworkService.disconnectingFrom === modelData.ssid color: Color.mError radius: height * 0.5 width: disconnectingText.implicitWidth + (Style.marginS * 2) height: disconnectingText.implicitHeight + (Style.marginXXS * 2) NText { id: disconnectingText anchors.centerIn: parent text: "disconnecting" pointSize: Style.fontSizeXXS color: Color.mOnPrimary } } Rectangle { visible: NetworkService.forgettingNetwork === modelData.ssid color: Color.mError radius: height * 0.5 width: forgettingText.implicitWidth + (Style.marginS * 2) height: forgettingText.implicitHeight + (Style.marginXXS * 2) NText { id: forgettingText anchors.centerIn: parent text: "forgetting" pointSize: Style.fontSizeXXS color: Color.mOnPrimary } } Rectangle { visible: modelData.cached && !modelData.connected && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid color: Color.transparent border.color: Color.mOutline border.width: Math.max(1, Style.borderS) radius: height * 0.5 width: savedText.implicitWidth + (Style.marginS * 2) height: savedText.implicitHeight + (Style.marginXXS * 2) NText { id: savedText anchors.centerIn: parent text: "saved" pointSize: Style.fontSizeXXS color: Color.mOnSurfaceVariant } } } } // Action area RowLayout { spacing: Style.marginS NBusyIndicator { visible: NetworkService.connectingTo === modelData.ssid || NetworkService.disconnectingFrom === modelData.ssid || NetworkService.forgettingNetwork === modelData.ssid running: visible color: Color.mPrimary size: Style.baseWidgetSize * 0.5 } NIconButton { visible: (modelData.existing || modelData.cached) && !modelData.connected && NetworkService.connectingTo !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid icon: "trash" baseSize: Style.baseWidgetSize * 0.8 onClicked: expandedSsid = expandedSsid === modelData.ssid ? "" : modelData.ssid } NButton { visible: !modelData.connected && NetworkService.connectingTo !== modelData.ssid && passwordSsid !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid text: { if (modelData.existing || modelData.cached) return "Connect"; if (!NetworkService.isSecured(modelData.security)) return "Connect"; return "Enter Password"; } outlined: !hovered fontSize: Style.fontSizeXS enabled: !NetworkService.connecting onClicked: { if (modelData.existing || modelData.cached || !NetworkService.isSecured(modelData.security)) { NetworkService.connect(modelData.ssid); } else { passwordSsid = modelData.ssid; passwordInput = ""; expandedSsid = ""; } } } NButton { visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid text: "Disconnect" outlined: !hovered fontSize: Style.fontSizeXS backgroundColor: Color.mError onClicked: NetworkService.disconnect(modelData.ssid) } } } // Password input Rectangle { visible: passwordSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid Layout.fillWidth: true height: passwordRow.implicitHeight + Style.marginS * 2 color: Color.mSurfaceVariant border.color: Color.mOutline border.width: Math.max(1, Style.borderS) radius: Style.radiusS RowLayout { id: passwordRow anchors.fill: parent anchors.margins: Style.marginS spacing: Style.marginM Rectangle { Layout.fillWidth: true Layout.fillHeight: true radius: Style.radiusXS color: Color.mSurface border.color: pwdInput.activeFocus ? Color.mSecondary : Color.mOutline border.width: Math.max(1, Style.borderS) TextInput { id: pwdInput anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.margins: Style.marginS text: passwordInput font.family: Fonts.sans font.pointSize: Style.fontSizeS color: Color.mOnSurface echoMode: TextInput.Password selectByMouse: true focus: visible passwordCharacter: "●" onTextChanged: passwordInput = text onVisibleChanged: { if (visible) forceActiveFocus(); } onAccepted: { if (text && !NetworkService.connecting) { NetworkService.connect(passwordSsid, text); passwordSsid = ""; passwordInput = ""; } } NText { visible: parent.text.length === 0 anchors.verticalCenter: parent.verticalCenter text: "Enter Password" color: Color.mOnSurfaceVariant pointSize: Style.fontSizeS } } } NButton { text: "Connect" fontSize: Style.fontSizeXXS enabled: passwordInput.length > 0 && !NetworkService.connecting outlined: true onClicked: { NetworkService.connect(passwordSsid, passwordInput); passwordSsid = ""; passwordInput = ""; } } NIconButton { icon: "close" baseSize: Style.baseWidgetSize * 0.8 onClicked: { passwordSsid = ""; passwordInput = ""; } } } } // Forget network Rectangle { visible: expandedSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid && NetworkService.forgettingNetwork !== modelData.ssid Layout.fillWidth: true height: forgetRow.implicitHeight + Style.marginS * 2 color: Color.mSurfaceVariant radius: Style.radiusS border.width: Math.max(1, Style.borderS) border.color: Color.mOutline RowLayout { id: forgetRow anchors.fill: parent anchors.margins: Style.marginS spacing: Style.marginM RowLayout { NIcon { icon: "trash" pointSize: Style.fontSizeL color: Color.mError } NText { text: "Forget this network?" pointSize: Style.fontSizeS color: Color.mError Layout.fillWidth: true } } NButton { id: forgetButton text: "Forget" fontSize: Style.fontSizeXXS backgroundColor: Color.mError outlined: forgetButton.hovered ? false : true onClicked: { NetworkService.forget(modelData.ssid); expandedSsid = ""; } } NIconButton { icon: "close" baseSize: Style.baseWidgetSize * 0.8 onClicked: expandedSsid = "" } } } } // Smooth opacity animation Behavior on opacity { NumberAnimation { duration: Style.animationNormal } } } } } } // Empty state when no networks ColumnLayout { visible: SettingsService.wifiEnabled && !NetworkService.scanning && Object.keys(NetworkService.networks).length === 0 anchors.fill: parent spacing: Style.marginL Item { Layout.fillHeight: true } NIcon { icon: "search" pointSize: 64 color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NText { text: "No networks found" pointSize: Style.fontSizeL color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter } NButton { text: "Scan Again" icon: "refresh" Layout.alignment: Qt.AlignHCenter onClicked: NetworkService.scan() } Item { Layout.fillHeight: true } } } } } }