Network/WiFi: improve UI with more immediate feedback on operations.
+ proper deletion of profiles when forgetting a network
This commit is contained in:
parent
5bc8f410e7
commit
fc1ee9fb2f
2 changed files with 210 additions and 23 deletions
|
|
@ -215,11 +215,23 @@ NPanel {
|
|||
Layout.fillWidth: true
|
||||
implicitHeight: netColumn.implicitHeight + (Style.marginM * scaling * 2)
|
||||
radius: Style.radiusM * scaling
|
||||
|
||||
// Add opacity for operations in progress
|
||||
opacity: (NetworkService.disconnectingFrom === modelData.ssid
|
||||
|| NetworkService.forgettingNetwork === modelData.ssid) ? 0.6 : 1.0
|
||||
|
||||
color: modelData.connected ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b,
|
||||
0.05) : Color.mSurface
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
border.color: modelData.connected ? Color.mPrimary : Color.mOutline
|
||||
|
||||
// Smooth opacity animation
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationNormal
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: netColumn
|
||||
width: parent.width - (Style.marginM * scaling * 2)
|
||||
|
|
@ -276,8 +288,9 @@ NPanel {
|
|||
Layout.preferredWidth: Style.marginXXS * scaling
|
||||
}
|
||||
|
||||
// Update the status badges area (around line 237)
|
||||
Rectangle {
|
||||
visible: modelData.connected
|
||||
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
color: Color.mPrimary
|
||||
radius: height * 0.5
|
||||
width: connectedText.implicitWidth + (Style.marginS * scaling * 2)
|
||||
|
|
@ -292,8 +305,42 @@ NPanel {
|
|||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: NetworkService.disconnectingFrom === modelData.ssid
|
||||
color: Color.mError
|
||||
radius: height * 0.5
|
||||
width: disconnectingText.implicitWidth + (Style.marginS * scaling * 2)
|
||||
height: disconnectingText.implicitHeight + (Style.marginXXS * scaling * 2)
|
||||
|
||||
NText {
|
||||
id: disconnectingText
|
||||
anchors.centerIn: parent
|
||||
text: "Disconnecting..."
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
color: Color.mOnPrimary
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: NetworkService.forgettingNetwork === modelData.ssid
|
||||
color: Color.mError
|
||||
radius: height * 0.5
|
||||
width: forgettingText.implicitWidth + (Style.marginS * scaling * 2)
|
||||
height: forgettingText.implicitHeight + (Style.marginXXS * scaling * 2)
|
||||
|
||||
NText {
|
||||
id: forgettingText
|
||||
anchors.centerIn: parent
|
||||
text: "Forgetting..."
|
||||
font.pointSize: Style.fontSizeXXS * scaling
|
||||
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 * scaling)
|
||||
|
|
@ -318,6 +365,8 @@ NPanel {
|
|||
|
||||
NBusyIndicator {
|
||||
visible: NetworkService.connectingTo === modelData.ssid
|
||||
|| NetworkService.disconnectingFrom === modelData.ssid
|
||||
|| NetworkService.forgettingNetwork === modelData.ssid
|
||||
running: visible
|
||||
color: Color.mPrimary
|
||||
size: Style.baseWidgetSize * 0.5 * scaling
|
||||
|
|
@ -326,6 +375,8 @@ NPanel {
|
|||
NIconButton {
|
||||
visible: (modelData.existing || modelData.cached) && !modelData.connected
|
||||
&& NetworkService.connectingTo !== modelData.ssid
|
||||
&& NetworkService.forgettingNetwork !== modelData.ssid
|
||||
&& NetworkService.disconnectingFrom !== modelData.ssid
|
||||
icon: "delete"
|
||||
tooltipText: "Forget network"
|
||||
sizeRatio: 0.7
|
||||
|
|
@ -335,6 +386,8 @@ NPanel {
|
|||
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"
|
||||
|
|
@ -356,7 +409,7 @@ NPanel {
|
|||
}
|
||||
|
||||
NButton {
|
||||
visible: modelData.connected
|
||||
visible: modelData.connected && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
text: "Disconnect"
|
||||
outlined: !hovered
|
||||
fontSize: Style.fontSizeXS * scaling
|
||||
|
|
@ -368,7 +421,8 @@ NPanel {
|
|||
|
||||
// Password input
|
||||
Rectangle {
|
||||
visible: passwordSsid === modelData.ssid
|
||||
visible: passwordSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
&& NetworkService.forgettingNetwork !== modelData.ssid
|
||||
Layout.fillWidth: true
|
||||
height: passwordRow.implicitHeight + Style.marginS * scaling * 2
|
||||
color: Color.mSurfaceVariant
|
||||
|
|
@ -449,7 +503,8 @@ NPanel {
|
|||
|
||||
// Forget network
|
||||
Rectangle {
|
||||
visible: expandedSsid === modelData.ssid
|
||||
visible: expandedSsid === modelData.ssid && NetworkService.disconnectingFrom !== modelData.ssid
|
||||
&& NetworkService.forgettingNetwork !== modelData.ssid
|
||||
Layout.fillWidth: true
|
||||
height: forgetRow.implicitHeight + Style.marginS * 2 * scaling
|
||||
color: Color.mSurfaceVariant
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ Singleton {
|
|||
property string connectingTo: ""
|
||||
property string lastError: ""
|
||||
property bool ethernetConnected: false
|
||||
property string disconnectingFrom: ""
|
||||
property string forgettingNetwork: ""
|
||||
|
||||
// Persistent cache
|
||||
property string cacheFile: Settings.cacheDir + "network.json"
|
||||
|
|
@ -123,11 +125,14 @@ Singleton {
|
|||
}
|
||||
|
||||
function disconnect(ssid) {
|
||||
disconnectingFrom = ssid
|
||||
disconnectProcess.ssid = ssid
|
||||
disconnectProcess.running = true
|
||||
}
|
||||
|
||||
function forget(ssid) {
|
||||
forgettingNetwork = ssid
|
||||
|
||||
// Remove from cache
|
||||
let known = cacheAdapter.knownNetworks
|
||||
delete known[ssid]
|
||||
|
|
@ -144,6 +149,40 @@ Singleton {
|
|||
forgetProcess.running = true
|
||||
}
|
||||
|
||||
// Helper function to immediately update network status
|
||||
function updateNetworkStatus(ssid, connected) {
|
||||
let nets = networks
|
||||
|
||||
// Update all networks connected status
|
||||
for (let key in nets) {
|
||||
if (nets[key].connected && key !== ssid) {
|
||||
nets[key].connected = false
|
||||
}
|
||||
}
|
||||
|
||||
// Update the target network if it exists
|
||||
if (nets[ssid]) {
|
||||
nets[ssid].connected = connected
|
||||
nets[ssid].existing = true
|
||||
nets[ssid].cached = true
|
||||
} else if (connected) {
|
||||
// Create a temporary entry if network doesn't exist yet
|
||||
nets[ssid] = {
|
||||
"ssid": ssid,
|
||||
"security": "--",
|
||||
"signal": 100,
|
||||
"connected"// Default to good signal until real scan
|
||||
: true,
|
||||
"existing": true,
|
||||
"cached": true
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger property change notification
|
||||
networks = ({})
|
||||
networks = nets
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
function signalIcon(signal) {
|
||||
if (signal >= 80)
|
||||
|
|
@ -170,9 +209,9 @@ Singleton {
|
|||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const connected = text.split("\n").some(line => {
|
||||
const parts = line.split(":")
|
||||
return parts[1] === "ethernet" && parts[2] === "connected"
|
||||
})
|
||||
const parts = line.split(":")
|
||||
return parts[1] === "ethernet" && parts[2] === "connected"
|
||||
})
|
||||
if (root.ethernetConnected !== connected) {
|
||||
root.ethernetConnected = connected
|
||||
Logger.log("Network", "Ethernet connected:", root.ethernetConnected)
|
||||
|
|
@ -189,7 +228,7 @@ Singleton {
|
|||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const enabled = text.trim() === "enabled"
|
||||
Logger.log("Network", "Wifi enabled:", enabled)
|
||||
Logger.log("Network", "Wi-Fi enabled:", enabled)
|
||||
if (Settings.data.network.wifiEnabled !== enabled) {
|
||||
Settings.data.network.wifiEnabled = enabled
|
||||
}
|
||||
|
|
@ -229,11 +268,11 @@ Singleton {
|
|||
id: scanProcess
|
||||
running: false
|
||||
command: ["sh", "-c", `
|
||||
# Get existing profiles
|
||||
profiles=$(nmcli -t -f NAME,TYPE connection show | grep ':802-11-wireless' | cut -d: -f1)
|
||||
# Get list of saved connection profiles (just the names)
|
||||
profiles=$(nmcli -t -f NAME connection show | tr '\n' '|')
|
||||
|
||||
# Get WiFi networks
|
||||
nmcli -t -f SSID,SECURITY,SIGNAL,IN-USE device wifi list | while read line; do
|
||||
nmcli -t -f SSID,SECURITY,SIGNAL,IN-USE device wifi list --rescan yes | while read line; do
|
||||
ssid=$(echo "$line" | cut -d: -f1)
|
||||
security=$(echo "$line" | cut -d: -f2)
|
||||
signal=$(echo "$line" | cut -d: -f3)
|
||||
|
|
@ -244,8 +283,10 @@ Singleton {
|
|||
continue
|
||||
fi
|
||||
|
||||
# Check if SSID matches any profile name (simple check)
|
||||
# This covers most cases where profile name equals or contains the SSID
|
||||
existing=false
|
||||
if echo "$profiles" | grep -q "^$ssid$"; then
|
||||
if echo "$profiles" | grep -qF "$ssid|"; then
|
||||
existing=true
|
||||
fi
|
||||
|
||||
|
|
@ -288,12 +329,24 @@ Singleton {
|
|||
}
|
||||
}
|
||||
|
||||
if (JSON.stringify(root.networks) !== JSON.stringify(nets)) {
|
||||
Logger.log("Network", "Discovered", Object.keys(nets).length, "Wi-Fi networks")
|
||||
// For logging purpose only
|
||||
const oldSSIDs = Object.keys(root.networks)
|
||||
const newSSIDs = Object.keys(nets)
|
||||
const newNetworks = newSSIDs.filter(ssid => !oldSSIDs.includes(ssid))
|
||||
const lostNetworks = oldSSIDs.filter(ssid => !newSSIDs.includes(ssid))
|
||||
if (newNetworks.length > 0 || lostNetworks.length > 0) {
|
||||
if (newNetworks.length > 0) {
|
||||
Logger.log("Network", "New Wi-Fi SSID discovered:", newNetworks.join(", "))
|
||||
}
|
||||
if (lostNetworks.length > 0) {
|
||||
Logger.log("Network", "Wi-Fi SSID disappeared:", lostNetworks.join(", "))
|
||||
}
|
||||
Logger.log("Network", "Total Wi-Fi SSIDs:", Object.keys(nets).length)
|
||||
}
|
||||
|
||||
// Assign the results
|
||||
root.networks = nets
|
||||
root.scanning = false
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -343,11 +396,14 @@ Singleton {
|
|||
cacheAdapter.lastConnected = connectProcess.ssid
|
||||
saveCache()
|
||||
|
||||
// Immediately update the UI before scanning
|
||||
root.updateNetworkStatus(connectProcess.ssid, true)
|
||||
|
||||
root.connecting = false
|
||||
root.connectingTo = ""
|
||||
Logger.log("Network", "Connected to " + connectProcess.ssid)
|
||||
Logger.log("Network", `Connected to network: "${connectProcess.ssid}"`)
|
||||
|
||||
// Rescan to update status
|
||||
// Still do a scan to get accurate signal and security info
|
||||
delayedScanTimer.interval = 1000
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
|
|
@ -383,8 +439,27 @@ Singleton {
|
|||
running: false
|
||||
command: ["nmcli", "connection", "down", "id", ssid]
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
Logger.log("Network", `Disconnected from network: "${disconnectProcess.ssid}"`)
|
||||
|
||||
// Immediately update UI on successful disconnect
|
||||
root.updateNetworkStatus(disconnectProcess.ssid, false)
|
||||
root.disconnectingFrom = ""
|
||||
|
||||
// Do a scan to refresh the list
|
||||
delayedScanTimer.interval = 1000
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.disconnectingFrom = ""
|
||||
if (text.trim()) {
|
||||
Logger.warn("Network", "Disconnect error: " + text)
|
||||
}
|
||||
// Still trigger a scan even on error
|
||||
delayedScanTimer.interval = 1000
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
|
|
@ -395,11 +470,68 @@ Singleton {
|
|||
id: forgetProcess
|
||||
property string ssid: ""
|
||||
running: false
|
||||
command: ["nmcli", "connection", "delete", "id", ssid]
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
delayedScanTimer.interval = 1000
|
||||
// Try multiple common profile name patterns
|
||||
command: ["sh", "-c", `
|
||||
ssid="$1"
|
||||
deleted=false
|
||||
|
||||
# Try exact SSID match first
|
||||
if nmcli connection delete id "$ssid" 2>/dev/null; then
|
||||
echo "Deleted profile: $ssid"
|
||||
deleted=true
|
||||
fi
|
||||
|
||||
# Try "Auto <SSID>" pattern
|
||||
if nmcli connection delete id "Auto $ssid" 2>/dev/null; then
|
||||
echo "Deleted profile: Auto $ssid"
|
||||
deleted=true
|
||||
fi
|
||||
|
||||
# Try "<SSID> 1", "<SSID> 2", etc. patterns
|
||||
for i in 1 2 3; do
|
||||
if nmcli connection delete id "$ssid $i" 2>/dev/null; then
|
||||
echo "Deleted profile: $ssid $i"
|
||||
deleted=true
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$deleted" = "false" ]; then
|
||||
echo "No profiles found for SSID: $ssid"
|
||||
fi
|
||||
`, "--", ssid]
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
Logger.log("Network", `Forget network: "${forgetProcess.ssid}"`)
|
||||
Logger.log("Network", text.trim().replace(/[\r\n]/g, " "))
|
||||
|
||||
// Update both cached and existing status immediately
|
||||
let nets = root.networks
|
||||
if (nets[forgetProcess.ssid]) {
|
||||
nets[forgetProcess.ssid].cached = false
|
||||
nets[forgetProcess.ssid].existing = false
|
||||
// Trigger property change
|
||||
root.networks = ({})
|
||||
root.networks = nets
|
||||
}
|
||||
|
||||
root.forgettingNetwork = ""
|
||||
|
||||
// Quick scan to verify the profile is gone
|
||||
delayedScanTimer.interval = 500
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
stderr: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.forgettingNetwork = ""
|
||||
if (text.trim() && !text.includes("No profiles found")) {
|
||||
Logger.warn("Network", "Forget error: " + text)
|
||||
}
|
||||
// Still Trigger a scan even on error
|
||||
delayedScanTimer.interval = 500
|
||||
delayedScanTimer.restart()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue