diff --git a/modules/nexus/pages/NetworkPage.qml b/modules/nexus/pages/NetworkPage.qml index 9b74ffbc..38be289d 100644 --- a/modules/nexus/pages/NetworkPage.qml +++ b/modules/nexus/pages/NetworkPage.qml @@ -13,6 +13,8 @@ import qs.modules.nexus.common PageBase { id: root + signal networkSelected(ap: Nmcli.AccessPoint) + title: qsTr("Network") ColumnLayout { @@ -106,6 +108,9 @@ PageBase { id: network required property Nmcli.AccessPoint modelData + readonly property Component rightComp: Nmcli.connectingSsid() === modelData.ssid ? loadingComp : iconComp + property bool isComplete + property real textOpacity: disabled ? 0.5 : 1 anchors.left: networkList.contentItem.left anchors.right: networkList.contentItem.right @@ -113,6 +118,66 @@ PageBase { radius: Tokens.rounding.extraSmall anchors.fill: undefined + onClicked: { + if (!modelData.active) { + NetworkConnection.handleConnect(modelData); + disabled = true; + root.networkSelected(modelData); + } + } + + Component.onCompleted: isComplete = true + onRightCompChanged: { + if (isComplete) + rightCompAnim.restart(); + } + + Behavior on textOpacity { + Anim { + type: Anim.DefaultEffects + } + } + + Connections { + function onActiveChanged(): void { + if (network.modelData.active) + network.disabled = false; + } + + target: network.modelData + } + + Connections { + function onNetworkSelected(ap: Nmcli.AccessPoint): void { + if (ap !== network.modelData) + network.disabled = false; + } + + target: root + } + + SequentialAnimation { + id: rightCompAnim + + running: false + + Anim { + target: rightLoader + property: "opacity" + to: 0 + type: Anim.FastEffects + } + ScriptAction { + script: rightLoader.sourceComponent = network.rightComp + } + Anim { + target: rightLoader + property: "opacity" + to: 1 + type: Anim.DefaultEffects + } + } + RowLayout { id: networkLayout @@ -126,11 +191,13 @@ PageBase { 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 @@ -148,11 +215,30 @@ PageBase { } } - MaterialIcon { - visible: network.modelData.isSecure - text: network.modelData.active ? "settings" : "lock" - color: network.modelData.active ? Colours.palette.m3primary : Colours.tPalette.m3onSurfaceVariant - font: Tokens.font.icon.medium + Loader { + id: rightLoader + + asynchronous: true + sourceComponent: 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) + } + } } } } diff --git a/services/Nmcli.qml b/services/Nmcli.qml index ea5d7eb3..e9827149 100644 --- a/services/Nmcli.qml +++ b/services/Nmcli.qml @@ -12,6 +12,7 @@ Singleton { property var wirelessInterfaces: [] property var ethernetInterfaces: [] property bool isConnected: false + readonly property bool connecting: wirelessInterfaces.some(i => isConnectingState(i.state)) property string activeInterface: "" property string activeConnection: "" property bool wifiEnabled: true @@ -166,6 +167,15 @@ Singleton { return state === "100 (connected)" || state === "connected" || state.startsWith("connected"); } + function isConnectingState(state: string): bool { + return !!state && state.startsWith("connecting"); + } + + function connectingSsid(): string { + const iface = root.wirelessInterfaces.find(i => isConnectingState(i.state)); + return iface ? iface.connection : ""; + } + function executeCommand(args: list, callback: var): void { const proc = commandProc.createObject(root); proc.cmdArgs = ["nmcli", ...args];