pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import Caelestia.Config import qs.components import qs.components.controls import qs.services import qs.utils import qs.modules.nexus.common PageBase { id: root signal networkSelected(ap: Nmcli.AccessPoint) title: qsTr("Network") ColumnLayout { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top width: root.cappedWidth spacing: Tokens.spacing.extraSmall / 2 Timer { running: root.visible && Nmcli.wifiEnabled repeat: true triggeredOnStart: true interval: GlobalConfig.nexus.networkRescanInterval onTriggered: Nmcli.rescanWifi() } Timer { id: wifiScanDelay interval: 100 onTriggered: Nmcli.rescanWifi() } Connections { function onWifiEnabledChanged(): void { if (Nmcli.wifiEnabled) wifiScanDelay.start(); } target: Nmcli } ToggleRow { Layout.fillWidth: true first: true text: qsTr("Wi-Fi") font: Tokens.font.body.medium horizontalPadding: Tokens.padding.largeIncreased checked: Nmcli.wifiEnabled onToggled: Nmcli.enableWifi(checked) } ItemList { id: networkList showList: Nmcli.wifiEnabled placeholderIcon: Nmcli.wifiEnabled ? "wifi_find" : "signal_wifi_off" placeholderText: Nmcli.wifiEnabled ? qsTr("No networks found") : qsTr("Wi-Fi disabled") extraHeight: Nmcli.scanning ? Tokens.rounding.extraSmall : 0 // Inline so it isn't affected by anim list.anchors.top: scanningIndicator.bottom model: ScriptModel { values: { const connecting = Nmcli.connectingSsid(); // Lower rank sorts higher in the list const rank = n => n.active ? 0 : n.ssid === connecting ? 1 : Nmcli.hasSavedProfile(n.ssid) ? 2 : 3; return [...Nmcli.networks].sort((a, b) => rank(a) - rank(b) || b.strength - a.strength); } } delegate: StateLayer { id: network required property Nmcli.AccessPoint modelData property bool currentSelected property real textOpacity: disabled ? 0.5 : 1 disabled: currentSelected || Nmcli.connectingSsid() === modelData.ssid anchors.left: networkList.list.contentItem.left anchors.right: networkList.list.contentItem.right implicitHeight: networkLayout.implicitHeight + networkLayout.anchors.margins * 2 radius: Tokens.rounding.extraSmall anchors.fill: undefined onClicked: { if (!modelData.active) { NetworkConnection.handleConnect(modelData); currentSelected = true; root.networkSelected(modelData); } } Behavior on textOpacity { Anim { type: Anim.DefaultEffects } } Connections { function onActiveChanged(): void { if (network.modelData.active) network.currentSelected = false; } target: network.modelData } Connections { function onNetworkSelected(ap: Nmcli.AccessPoint): void { if (ap !== network.modelData) network.currentSelected = false; } target: root } RowLayout { id: networkLayout anchors.fill: parent anchors.margins: Tokens.padding.large anchors.leftMargin: Tokens.padding.extraLarge anchors.rightMargin: Tokens.padding.extraLarge spacing: Tokens.spacing.medium MaterialIcon { text: Icons.getNetworkIcon(network.modelData.strength) color: network.modelData.active ? Colours.palette.m3primary : Colours.tPalette.m3onSurfaceVariant font: Tokens.font.icon.medium opacity: network.textOpacity } ColumnLayout { Layout.fillWidth: true spacing: 0 opacity: network.textOpacity StyledText { Layout.fillWidth: true text: network.modelData.ssid font: Tokens.font.body.small elide: Text.ElideRight } StyledText { Layout.fillWidth: true text: qsTr("Security: %1%2").arg(network.modelData.security).arg(Nmcli.hasSavedProfile(network.modelData.ssid) ? qsTr(" • Saved") : "") color: Colours.tPalette.m3outline font: Tokens.font.label.small elide: Text.ElideRight } } AnimLoader { sourceComp: Nmcli.connectingSsid() === network.modelData.ssid ? loadingComp : iconComp Component { id: iconComp MaterialIcon { text: network.modelData.active ? "settings" : "lock" color: network.modelData.active ? Colours.palette.m3primary : Colours.tPalette.m3onSurfaceVariant font: Tokens.font.icon.medium opacity: network.textOpacity } } Component { id: loadingComp LoadingIndicator { implicitSize: Math.round(Tokens.font.icon.medium.pointSize * 1.3) } } } } } StyledProgressBar { id: scanningIndicator anchors.left: parent.left anchors.right: parent.right anchors.margins: 1 implicitHeight: Nmcli.scanning ? Tokens.rounding.extraSmall : 0 indeterminate: true Behavior on implicitHeight { Anim { type: Anim.DefaultEffects } } } } StyledRect { Layout.fillWidth: true bottomLeftRadius: Tokens.rounding.extraLarge bottomRightRadius: Tokens.rounding.extraLarge radius: Tokens.rounding.extraSmall implicitHeight: addNetworkLayout.implicitHeight + addNetworkLayout.anchors.margins * 2 color: Colours.tPalette.m3surfaceContainer StateLayer {} RowLayout { id: addNetworkLayout anchors.fill: parent anchors.margins: Tokens.padding.medium anchors.leftMargin: Tokens.padding.largeIncreased anchors.rightMargin: Tokens.padding.largeIncreased spacing: Tokens.spacing.medium MaterialIcon { text: "add" font: Tokens.font.icon.medium } StyledText { Layout.fillWidth: true text: qsTr("Add network") font: Tokens.font.body.small elide: Text.ElideRight } } } } }