This repository has been archived on 2026-05-31. You can view files and clone it, but cannot push or open issues or pull requests.

593 lines
22 KiB
QML

import QtQuick 2.5
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import Nymea 1.0
import "../components"
import "../delegates"
Page {
id: root
property DeviceClass deviceClass: device ? device.deviceClass : null
// Optional: If set, it will be reconfigred, otherwise a new one will be created
property Device device: null
signal done();
header: NymeaHeader {
text: root.device ? qsTr("Reconfigure %1").arg(root.device.name) : qsTr("Set up %1").arg(root.deviceClass.displayName)
onBackPressed: {
if (internalPageStack.depth > 1) {
internalPageStack.pop();
} else {
pageStack.pop();
}
}
HeaderButton {
imageSource: "../images/close.svg"
onClicked: root.done();
}
}
QtObject {
id: d
property var vendorId: null
property DeviceDescriptor deviceDescriptor: null
property var discoveryParams: []
property string deviceName: ""
property int pairRequestId: 0
property var pairingTransactionId: null
property int addRequestId: 0
}
Component.onCompleted: {
print("Starting setup wizard")
if (root.deviceClass.createMethods.indexOf("CreateMethodDiscovery") !== -1) {
print("CreateMethodDiscovery")
if (deviceClass["discoveryParamTypes"].count > 0) {
print("Discovery params:", deviceClass.discoveryParamTypes.count)
internalPageStack.push(discoveryParamsPage)
} else {
print("Starting discovery...")
discovery.discoverDevices(deviceClass.id)
internalPageStack.push(discoveryPage, {deviceClass: deviceClass})
}
} else if (root.deviceClass.createMethods.indexOf("CreateMethodUser") !== -1) {
print("CreateMethodUser")
// Setting up a new device
if (!root.device) {
print("New device. Opening params page")
internalPageStack.push(paramsPage)
// Reconfigure
} else if (root.device) {
print("Existing device")
// There are params. Open params page in any case
if (root.deviceClass.paramTypes.count > 0) {
print("Params:", root.deviceClass.paramTypes.count)
internalPageStack.push(paramsPage)
// No params... go straight to reconfigure/repair
} else {
print("no params")
switch (root.deviceClass.setupMethod) {
case 0:
print("reconfiguring...")
// This totally does not make sense... Maybe we should hide the reconfigure button if there are no params?
engine.deviceManager.reconfigureDevice(root.device.id, [])
busyOverlay.shown = true;
break;
case 1:
case 2:
case 3:
case 4:
print("re-pairing", root.device.id)
engine.deviceManager.rePairDevice(root.device.id, []);
break;
default:
console.warn("Unahndled setup method!")
}
}
}
}
}
Connections {
target: engine.deviceManager
onPairDeviceReply: {
busyOverlay.shown = false
if (params["deviceError"] !== "DeviceErrorNoError") {
busyOverlay.shown = false;
internalPageStack.push(resultsPage, {deviceError: params["deviceError"], message: params["displayMessage"]});
return;
}
d.pairingTransactionId = params["pairingTransactionId"];
switch (params["setupMethod"]) {
case "SetupMethodPushButton":
case "SetupMethodDisplayPin":
case "SetupMethodUserAndPassword":
internalPageStack.push(pairingPageComponent, {text: params["displayMessage"], setupMethod: params["setupMethod"]})
break;
case "SetupMethodOAuth":
internalPageStack.push(oAuthPageComponent, {oAuthUrl: params["oAuthUrl"]})
break;
default:
print("Setup method reply not handled:", JSON.stringify(params));
}
}
onConfirmPairingReply: {
busyOverlay.shown = false
internalPageStack.push(resultsPage, {deviceError: params["deviceError"], deviceId: params["deviceId"], message: params["displayMessage"]})
}
onAddDeviceReply: {
print("Device added:", JSON.stringify(params))
busyOverlay.shown = false;
internalPageStack.push(resultsPage, {deviceError: params["deviceError"], deviceId: params["deviceId"], message: params["displayMessage"]})
}
onReconfigureDeviceReply: {
busyOverlay.shown = false;
internalPageStack.push(resultsPage, {deviceError: params["deviceError"], deviceId: params["deviceId"], message: params["displayMessage"]})
}
}
DeviceDiscovery {
id: discovery
engine: _engine
}
StackView {
id: internalPageStack
anchors.fill: parent
}
Component {
id: discoveryParamsPage
Page {
id: discoveryParamsView
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
Flickable {
Layout.fillHeight: true
Layout.fillWidth: true
ColumnLayout {
width: parent.width
Repeater {
id: paramRepeater
model: root.deviceClass ? root.deviceClass["discoveryParamTypes"] : null
Loader {
Layout.fillWidth: true
sourceComponent: searchStringEntryComponent
property var discoveryParams: model
property var value: item ? item.value : null
}
}
Button {
Layout.fillWidth: true
text: "Next"
onClicked: {
var paramTypes = root.deviceClass["discoveryParamTypes"];
d.discoveryParams = [];
for (var i = 0; i < paramTypes.count; i++) {
var param = {};
param["paramTypeId"] = paramTypes.get(i).id;
param["value"] = paramRepeater.itemAt(i).value
d.discoveryParams.push(param);
}
discovery.discoverDevices(root.deviceClass.id, d.discoveryParams)
internalPageStack.push(discoveryPage, {deviceClass: root.deviceClass})
}
}
}
}
Component {
id: searchStringEntryComponent
ColumnLayout {
property alias value: searchTextField.text
Label {
text: discoveryParams.displayName
Layout.fillWidth: true
}
TextField {
id: searchTextField
Layout.fillWidth: true
}
}
}
}
}
}
Component {
id: discoveryPage
Page {
id: discoveryView
property var deviceClass: null
ColumnLayout {
anchors.fill: parent
anchors.bottomMargin: app.margins
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
model: DeviceDiscoveryProxy {
id: discoveryProxy
deviceDiscovery: discovery
showAlreadyAdded: root.device !== null
showNew: root.device === null
filterDeviceId: root.device !== null ? root.device.id : null
}
delegate: NymeaListItemDelegate {
width: parent.width
height: app.delegateHeight
text: model.name
subText: model.description
iconName: app.interfacesToIcon(discoveryView.deviceClass.interfaces)
onClicked: {
d.deviceDescriptor = discoveryProxy.get(index);
d.deviceName = model.name;
internalPageStack.push(paramsPage)
}
}
}
Button {
id: retryButton
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins
text: qsTr("Search again")
onClicked: discovery.discoverDevices(root.deviceClass.id, d.discoveryParams)
visible: !discovery.busy
}
Button {
id: manualAddButton
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.rightMargin: app.margins;
visible: root.deviceClass.createMethods.indexOf("CreateMethodUser") >= 0
text: qsTr("Add thing manually")
onClicked: internalPageStack.push(paramsPage)
}
}
ColumnLayout {
anchors.centerIn: parent
width: parent.width - app.margins * 2
visible: discovery.busy
spacing: app.margins * 2
Label {
text: qsTr("Searching for things...")
Layout.fillWidth: true
font.pixelSize: app.largeFont
horizontalAlignment: Text.AlignHCenter
}
BusyIndicator {
running: visible
anchors.horizontalCenter: parent.horizontalCenter
}
}
ColumnLayout {
anchors.centerIn: parent
width: parent.width - app.margins * 2
visible: !discovery.busy && discoveryProxy.count === 0
spacing: app.margins * 2
Label {
text: qsTr("Too bad...")
font.pixelSize: app.largeFont
Layout.fillWidth: true
}
Label {
text: qsTr("No things of this kind could be found...")
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
Label {
Layout.fillWidth: true
text: qsTr("Make sure your things are set up and connected, try searching again or go back and pick a different kind of thing.")
wrapMode: Text.WordWrap
}
}
}
}
Component {
id: paramsPage
Page {
id: paramsView
Flickable {
anchors.fill: parent
contentHeight: paramsColumn.implicitHeight
ColumnLayout {
id: paramsColumn
width: parent.width
ColumnLayout {
// visible: root.device === null
Label {
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
Layout.topMargin: app.margins
Layout.fillWidth: true
text: qsTr("Name the thing:")
}
TextField {
id: nameTextField
text: d.deviceName ? d.deviceName : root.deviceClass.displayName
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
}
ThinDivider {
visible: paramRepeater.count > 0
}
}
Repeater {
id: paramRepeater
model: engine.jsonRpcClient.ensureServerVersion("1.12") || d.deviceDescriptor == null ? root.deviceClass.paramTypes : null
delegate: ParamDelegate {
// Layout.preferredHeight: 60
Layout.fillWidth: true
paramType: root.deviceClass.paramTypes.get(index)
value: d.deviceDescriptor && d.deviceDescriptor.params.getParam(paramType.id) ?
d.deviceDescriptor.params.getParam(paramType.id).value :
root.deviceClass.paramTypes.get(index).defaultValue
}
}
Button {
Layout.fillWidth: true
Layout.leftMargin: app.margins
Layout.rightMargin: app.margins
text: "OK"
onClicked: {
print("setupMethod", root.deviceClass.setupMethod)
var params = []
for (var i = 0; i < paramRepeater.count; i++) {
var param = {}
param.paramTypeId = paramRepeater.itemAt(i).paramType.id
param.value = paramRepeater.itemAt(i).value
print("adding param", param.paramTypeId, param.value)
params.push(param)
}
switch (root.deviceClass.setupMethod) {
case 0:
if (root.device !== null) {
if (d.deviceDescriptor) {
engine.deviceManager.reconfigureDiscoveredDevice(root.device.id, d.deviceDescriptor.id);
} else {
engine.deviceManager.reconfigureDevice(root.device.id, params);
}
} else {
if (d.deviceDescriptor) {
engine.deviceManager.addDiscoveredDevice(root.deviceClass.id, d.deviceDescriptor.id, nameTextField.text, params);
} else {
engine.deviceManager.addDevice(root.deviceClass.id, nameTextField.text, params);
}
}
break;
case 1:
case 2:
case 3:
case 4:
case 5:
if (root.device) {
if (d.deviceDescriptor) {
engine.deviceManager.pairDiscoveredDevice(root.deviceClass.id, d.deviceDescriptor.id, params, nameTextField.text);
} else {
engine.deviceManager.rePairDevice(root.device.id, params, nameTextField.text);
}
return;
} else {
if (d.deviceDescriptor) {
engine.deviceManager.pairDevice(d.deviceDescriptor.id, params, nameTextField.text);
} else {
engine.deviceManager.pairDevice(root.deviceClass.id, params, nameTextField.text);
}
}
break;
}
busyOverlay.shown = true;
}
}
}
}
}
}
Component {
id: pairingPageComponent
Page {
id: pairingPage
property alias text: textLabel.text
property string setupMethod
ColumnLayout {
anchors.centerIn: parent
width: parent.width - app.margins * 2
spacing: app.margins * 2
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
font.pixelSize: app.largeFont
text: qsTr("Pairing...")
color: app.accentColor
horizontalAlignment: Text.AlignHCenter
}
Label {
id: textLabel
Layout.fillWidth: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
TextField {
id: usernameTextField
Layout.fillWidth: true
visible: pairingPage.setupMethod === "SetupMethodUserAndPassword"
}
PasswordTextField {
id: pinTextField
Layout.fillWidth: true
visible: pairingPage.setupMethod === "SetupMethodDisplayPin" || pairingPage.setupMethod === "SetupMethodUserAndPassword"
signup: false
}
Button {
Layout.fillWidth: true
text: "OK"
onClicked: {
engine.deviceManager.confirmPairing(d.pairingTransactionId, pinTextField.text, usernameTextField.displayText);
busyOverlay.shown = true;
}
}
}
}
}
Component {
id: oAuthPageComponent
Page {
id: oAuthPage
property string oAuthUrl
ColumnLayout {
anchors.centerIn: parent
width: parent.width - app.margins * 2
spacing: app.margins * 2
Label {
Layout.fillWidth: true
text: qsTr("OAuth is not supported on this platform. Please use this app on a different device to set up this thing.")
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
Label {
Layout.fillWidth: true
text: qsTr("In order to use OAuth on this platform, make sure qml-module-qtwebview is installed.")
wrapMode: Text.WordWrap
font.pixelSize: app.smallFont
horizontalAlignment: Text.AlignHCenter
}
}
Item {
id: webViewContainer
anchors.fill: parent
Component.onCompleted: {
// This might fail if qml-module-qtwebview isn't around
Qt.createQmlObject(webViewString, webViewContainer);
}
property string webViewString:
'
import QtQuick 2.8;
import QtWebView 1.1;
WebView {
id: oAuthWebView
anchors.fill: parent
url: oAuthPage.oAuthUrl
onUrlChanged: {
print("OAUTH URL changed", url)
if (url.toString().indexOf("https://127.0.0.1") == 0) {
print("Redirect URL detected!");
engine.deviceManager.confirmPairing(d.pairingTransactionId, url)
}
}
}
'
}
}
}
Component {
id: resultsPage
Page {
id: resultsView
property string deviceId
property string deviceError
property string message
readonly property bool success: deviceError === "DeviceErrorNoError"
readonly property var device: root.device ? root.device : engine.deviceManager.devices.getDevice(deviceId)
ColumnLayout {
width: parent.width - app.margins * 2
anchors.centerIn: parent
spacing: app.margins * 2
Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
text: resultsView.success ? root.device ? qsTr("Thing reconfigured!") : qsTr("Thing added!") : qsTr("Uh oh")
font.pixelSize: app.largeFont
color: app.accentColor
}
Label {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
text: resultsView.success ? qsTr("All done. You can now start using %1.").arg(resultsView.device.name) : qsTr("Something went wrong setting up this thing...");
}
Label {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
text: resultsView.message
}
Button {
Layout.fillWidth: true
text: qsTr("Ok")
onClicked: {
root.done();
}
}
}
}
}
BusyOverlay {
id: busyOverlay
}
}