feat(NetworkDevices): implement NetworkDevicesToConnect component for managing network device connections

This commit is contained in:
uncor3
2026-06-08 21:04:59 +00:00
parent f159295e71
commit 6b4fc34b52
+303
View File
@@ -0,0 +1,303 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Item {
id: root
// device fields expected from NetworkDevice::toVariantMap():
// name, address, port, macAddress (may or may not be present)
ListModel { id: deviceModel }
property string statusText: "Scanning for network devices..."
function _normalizeDevice(mac, dev) {
return {
mac: mac || dev.macAddress || "",
name: dev.name || dev.deviceName || "Unknown device",
address: dev.address || dev.ip || "",
port: dev.port || "",
raw: dev,
// UI state
state: "idle", // idle|connecting|failed|no_pairing|connected|already_exists
stateText: "",
buttonText: "Connect",
buttonEnabled: true
}
}
function _indexByMac(mac) {
for (var i = 0; i < deviceModel.count; i++) {
if (deviceModel.get(i).mac === mac) return i
}
return -1
}
function _setStatusForMac(mac, state) {
var i = _indexByMac(mac)
if (i < 0) return
if (state === "failed") {
deviceModel.setProperty(i, "state", "failed")
deviceModel.setProperty(i, "buttonText", "Failed to connect")
deviceModel.setProperty(i, "buttonEnabled", false)
} else if (state === "no_pairing") {
deviceModel.setProperty(i, "state", "no_pairing")
deviceModel.setProperty(i, "buttonText", "No pairing file")
deviceModel.setProperty(i, "buttonEnabled", false)
} else if (state === "connecting") {
deviceModel.setProperty(i, "state", "connecting")
deviceModel.setProperty(i, "buttonText", "Connecting...")
deviceModel.setProperty(i, "buttonEnabled", false)
} else if (state === "connected") {
deviceModel.setProperty(i, "state", "connected")
deviceModel.setProperty(i, "buttonText", "Connected")
deviceModel.setProperty(i, "buttonEnabled", false)
} else if (state === "already_exists") {
deviceModel.setProperty(i, "state", "already_exists")
deviceModel.setProperty(i, "buttonText", "Already connected")
deviceModel.setProperty(i, "buttonEnabled", false)
} else {
deviceModel.setProperty(i, "state", "idle")
deviceModel.setProperty(i, "buttonText", "Connect")
deviceModel.setProperty(i, "buttonEnabled", true)
}
}
function _updateStatusLabel() {
if (deviceModel.count === 0) statusText = "No network devices found"
else statusText = "Found " + deviceModel.count + " network device(s)"
}
function refreshDevices() {
deviceModel.clear()
// NetworkDeviceProvider.getNetworkDevices(): QMap<QString, QVariant>
var map = NetworkDeviceProvider.getNetworkDevices()
if (!map) {
statusText = "No network devices found"
return
}
var keys = Object.keys(map)
for (var k = 0; k < keys.length; k++) {
var mac = keys[k]
var dev = map[mac]
deviceModel.append(_normalizeDevice(mac, dev))
}
_updateStatusLabel()
}
Component.onCompleted: refreshDevices()
// Live updates from C++ provider
Connections {
target: NetworkDeviceProvider
function onDeviceAdded(device) {
var mac = device.macAddress || device.mac || ""
if (!mac) return
var i = root._indexByMac(mac)
if (i >= 0) return
deviceModel.append(root._normalizeDevice(mac, device))
root._updateStatusLabel()
}
function onDeviceRemoved(macAddress) {
var i = root._indexByMac(macAddress)
if (i >= 0) deviceModel.remove(i, 1)
root._updateStatusLabel()
}
}
// Backend events (core)
Connections {
target: core
function onInit_failed(mac_address) {
root._setStatusForMac(mac_address, "failed")
}
function onNo_pairing_file(mac_address) {
root._setStatusForMac(mac_address, "no_pairing")
}
// FIXME: implement and wire these events on core (parity with QWidget/AppContext):
// - initStarted(mac) -> setStatusForMac(mac, "connecting")
// - deviceAdded(mac) -> setStatusForMac(mac, "connected")
// - deviceAlreadyExistsMAC -> setStatusForMac(mac, "already_exists")
}
// eval interval (every 30 seconds)
Timer {
id: evalTimer
interval: 30000
repeat: true
running: true
onTriggered: {
// FIXME: implement auto-connect eval logic + settings gate (like SettingsManager::autoConnectWirelessDevices()).
// FIXME: implement events on core to reflect initStarted/connected/already exists.
// For now: placeholder to keep behavior parity (timer exists).
// console.log("eval tick: devices=", deviceModel.count)
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
Label {
Layout.fillWidth: true
text: root.statusText
horizontalAlignment: Text.AlignHCenter
font.pointSize: 12
font.weight: Font.Medium
wrapMode: Text.WordWrap
}
Pane {
Layout.fillWidth: true
Layout.fillHeight: true
padding: 10
ColumnLayout {
anchors.fill: parent
spacing: 8
Label {
text: "Network Devices"
font.pointSize: 14
font.weight: Font.Bold
}
ScrollView {
id: deviceScroll
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
Column {
width: deviceScroll.availableWidth
spacing: 8
Repeater {
model: deviceModel
delegate: Rectangle {
width: parent.width
implicitHeight: content.implicitHeight + 24
height: implicitHeight
radius: 8
color: Qt.rgba(1, 1, 1, 0.04)
border.color: Qt.rgba(1, 1, 1, 0.10)
border.width: 1
Timer {
id: resetTimer
repeat: false
onTriggered: root._setStatusForMac(mac, "idle")
}
ColumnLayout {
id: content
anchors.fill: parent
anchors.margins: 12
spacing: 6
Label {
Layout.fillWidth: true
text: name
wrapMode: Text.WordWrap
font.pointSize: 13
font.weight: Font.Medium
}
RowLayout {
Layout.fillWidth: true
spacing: 12
Label {
text: "IP: " + (address || "—")
font.pointSize: 11
opacity: 0.8
}
Label {
text: "Port: " + (port !== "" ? port : "—")
font.pointSize: 11
opacity: 0.8
}
Item { Layout.fillWidth: true }
Button {
text: buttonText
enabled: buttonEnabled
onClicked: {
root._setStatusForMac(mac, "connecting")
resetTimer.stop()
resetTimer.interval = 10000
resetTimer.start()
}
}
Label {
text: "●"
font.pointSize: 14
color: {
switch (state) {
case "failed": return "#d83b01"
case "no_pairing": return "#ffb900"
case "connecting": return "#0078d4"
case "connected": return "#2e7d32"
case "already_exists": return "#6b6b6b"
default: return "#2e7d32"
}
}
}
}
// State-driven auto-reset timings (mirrors QWidget roughly)
Connections {
target: deviceModel
function onDataChanged() {
// ...existing code...
}
}
Component.onCompleted: {
// no-op
}
onStateChanged: {
// ...existing code...
}
// react to model.state changes
Binding { target: resetTimer; property: "running"; value: false }
// Start/reset timer when entering transient states
// (failed: 2s, no_pairing: 5s, connected: 10s, already_exists: 3s)
// NOTE: we use Component.onCompleted + onModelChanged pattern via onButtonTextChanged below.
// onButtonTextChanged: {
// resetTimer.stop()
// if (model.state === "failed") { resetTimer.interval = 2000; resetTimer.start() }
// else if (model.state === "no_pairing") { resetTimer.interval = 5000; resetTimer.start() }
// else if (model.state === "connected") { resetTimer.interval = 10000; resetTimer.start() }
// else if (model.state === "already_exists") { resetTimer.interval = 3000; resetTimer.start() }
// }
}
}
}
Item { width: 1; height: 1 } // spacer
}
}
}
}
}
}