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.

435 lines
14 KiB
QML

import QtQuick 2.8
import QtQuick.Controls 2.2
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.2
import Qt.labs.settings 1.0
import QtQuick.Window 2.3
import Nymea 1.0
ApplicationWindow {
id: app
visible: true
width: 360
height: 580
visibility: ApplicationWindow.AutomaticVisibility
font: Qt.application.font
// Those variables must be present in the Style
title: appName
Material.primary: primaryColor
Material.accent: accentColor
Material.foreground: foregroundColor
property int margins: 14
property int bigMargins: 20
property int extraSmallFont: 10
property int smallFont: 13
property int mediumFont: 16
property int largeFont: 20
property int iconSize: 30
property int delegateHeight: 60
readonly property bool landscape: app.width > app.height
readonly property var settings: Settings {
property string lastConnectedHost: ""
property alias viewMode: app.visibility
property bool returnToHome: false
property bool darkTheme: false
property string graphStyle: "bars"
property string style: "light"
property int currentMainViewIndex: 0
property bool showHiddenOptions: false
property int cloudEnvironment: 0
}
Binding {
target: Engine.awsClient
property: "config"
value: settings.cloudEnvironment
}
Component.onCompleted: {
pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml"))
setupPushNotifications();
}
Connections {
target: PlatformHelper
onHasPermissionsChanged: {
setupPushNotifications(false)
}
}
Connections {
target: PushNotifications
onTokenChanged: {
setupPushNotifications();
}
}
Connections {
target: Engine.awsClient
onIsLoggedInChanged: {
setupPushNotifications()
}
}
Connections {
target: Engine.jsonRpcClient
onConnectedChanged: {
print("json client connected changed", Engine.jsonRpcClient.connected)
if (Engine.jsonRpcClient.connected) {
settings.lastConnectedHost = Engine.connection.url
}
init();
}
onAuthenticationRequiredChanged: {
print("auth required changed")
init();
}
onInitialSetupRequiredChanged: {
print("setup required changed")
init();
}
onInvalidProtocolVersion: {
var popup = invalidVersionComponent.createObject(app.contentItem);
popup.actualVersion = actualVersion;
popup.minimumVersion = minimumVersion
popup.open()
settings.lastConnectedHost = ""
}
}
function init() {
print("calling init. Auth required:", Engine.jsonRpcClient.authenticationRequired, "initial setup required:", Engine.jsonRpcClient.initialSetupRequired, "jsonrpc connected:", Engine.jsonRpcClient.connected)
pageStack.clear()
if (!Engine.connection.connected) {
pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml"))
return;
}
if (Engine.jsonRpcClient.authenticationRequired || Engine.jsonRpcClient.initialSetupRequired) {
if (Engine.jsonRpcClient.pushButtonAuthAvailable) {
print("opening push button auth")
var page = pageStack.push(Qt.resolvedUrl("PushButtonAuthPage.qml"))
page.backPressed.connect(function() {
settings.lastConnectedHost = "";
Engine.connection.disconnect();
init();
})
} else {
var page = pageStack.push(Qt.resolvedUrl("LoginPage.qml"));
page.backPressed.connect(function() {
settings.lastConnectedHost = "";
Engine.connection.disconnect()
init();
})
}
} else if (Engine.jsonRpcClient.connected) {
pageStack.push(Qt.resolvedUrl("MainPage.qml"))
} else {
pageStack.push(Qt.resolvedUrl("connection/ConnectPage.qml"))
}
}
function setupPushNotifications(askForPermissions) {
if (askForPermissions === undefined) {
askForPermissions = true;
}
if (!Engine.awsClient.isLoggedIn) {
print("AWS not logged in. Cannot register for push");
return;
}
if (PushNotifications.token.length === 0) {
print("Don't have a token yet. Cannot register for push");
return;
}
if (!PlatformHelper.hasPermissions) {
if (askForPermissions) {
PlatformHelper.requestPermissions();
}
} else {
Engine.awsClient.registerPushNotificationEndpoint(PushNotifications.token, PlatformHelper.deviceManufacturer + " " + PlatformHelper.deviceModel, PlatformHelper.deviceSerial + "+io.guh.nymeaapp");
}
}
// Workaround flickering on pageStack animations when the white background shines through
Rectangle {
anchors.fill: parent
color: Material.background
}
StackView {
id: pageStack
objectName: "pageStack"
anchors.fill: parent
initialItem: Page {}
// onDepthChanged: {
// print("stackview depth changed", pageStack.depth)
// }
}
onClosing: {
if (Qt.platform.os == "android") {
// If we're connected, allow going back up to MainPage
if ((Engine.jsonRpcClient.connected && pageStack.depth > 1)
// if we're not connected, only allow using the back button in wizards
|| (!Engine.jsonRpcClient.connected && pageStack.depth > 3)) {
close.accepted = false;
pageStack.pop();
}
}
}
Connections {
target: Qt.application
enabled: Engine.jsonRpcClient.connected && settings.returnToHome
onStateChanged: {
print("App active state changed:", state)
if (state !== Qt.ApplicationActive) {
init();
}
}
}
property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "extendedawning", "extendedshutter", "extendedblind", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
function interfaceToString(name) {
switch(name) {
case "light":
return qsTr("Lighting")
case "weather":
return qsTr("Weather")
case "sensor":
return qsTr("Sensors")
case "media":
return qsTr("Media")
case "button":
return qsTr("Switches")
case "gateway":
return qsTr("Gateways")
case "notifications":
return qsTr("Notifications")
case "temperaturesensor":
return qsTr("Temperature");
case "humiditysensor":
return qsTr("Humidity");
case "pressuresensor":
return qsTr("Pressure");
case "inputtrigger":
return qsTr("Incoming Events");
case "outputtrigger":
return qsTr("Events");
case "shutter":
case "extendedshutter":
return qsTr("Shutters");
case "blind":
case "extendedblind":
return qsTr("Blinds");
case "awning":
case "extendedawning":
return qsTr("Awnings");
case "garagegate":
return qsTr("Garage gates");
case "uncategorized":
return qsTr("Uncategorized")
}
}
function interfacesToIcon(interfaces) {
for (var i = 0; i < interfaces.length; i++) {
var icon = interfaceToIcon(interfaces[i]);
if (icon !== "") {
return icon;
}
}
return Qt.resolvedUrl("images/select-none.svg")
}
function interfaceToIcon(name) {
switch (name) {
case "light":
case "colorlight":
case "dimmablelight":
return Qt.resolvedUrl("images/light-on.svg")
case "sensor":
return Qt.resolvedUrl("images/sensors.svg")
case "temperaturesensor":
return Qt.resolvedUrl("images/sensors/temperature.svg")
case "humiditysensor":
return Qt.resolvedUrl("images/sensors/humidity.svg")
case "moisturesensor":
return Qt.resolvedUrl("images/sensors/moisture.svg")
case "lightsensor":
return Qt.resolvedUrl("images/sensors/light.svg")
case "conductivitysensor":
return Qt.resolvedUrl("images/sensors/conductivity.svg")
case "pressuresensor":
return Qt.resolvedUrl("images/sensors/pressure.svg")
case "media":
case "mediacontroller":
return Qt.resolvedUrl("images/mediaplayer-app-symbolic.svg")
case "button":
case "longpressbutton":
case "simplemultibutton":
case "longpressmultibutton":
return Qt.resolvedUrl("images/system-shutdown.svg")
case "weather":
return Qt.resolvedUrl("images/weather-app-symbolic.svg")
case "temperaturesensor":
return Qt.resolvedUrl("images/sensors/temperature.svg")
case "humiditysensor":
return Qt.resolvedUrl("images/sensors/humidity.svg")
case "gateway":
return Qt.resolvedUrl("images/network-wired-symbolic.svg")
case "notifications":
return Qt.resolvedUrl("images/notification.svg")
case "connectable":
return Qt.resolvedUrl("images/stock_link.svg")
case "inputtrigger":
return Qt.resolvedUrl("images/attention.svg")
case "outputtrigger":
return Qt.resolvedUrl("images/send.svg")
case "shutter":
case "extendedshutter":
return Qt.resolvedUrl("images/DeviceIconRollerShutter.svg")
case "blind":
case "extendedblind":
return Qt.resolvedUrl("images/DeviceIconBlind.svg")
case "garagegate":
return Qt.resolvedUrl("images/shutter/shutter-100.svg")
case "extendedawning":
return Qt.resolvedUrl("images/awning/awning-100.svg")
case "battery":
return Qt.resolvedUrl("images/battery/battery-050.svg")
case "uncategorized":
return Qt.resolvedUrl("images/select-none.svg")
case "simpleclosable":
return Qt.resolvedUrl("images/sort-listitem.svg")
default:
console.warn("InterfaceToIcon: Unhandled interface", name)
}
return "";
}
property var fallbackColors: {
"temperaturesensor": "red",
"humiditysensor": "deepskyblue",
"moisturesensor":"blue",
"lightsensor": "orange",
"conductivitysensor": "green",
"pressuresensor": "grey"
}
function interfaceToColor(name) {
// Try to load color map from style
if (interfaceColors[name]) {
return interfaceColors[name];
}
if (fallbackColors[name]) {
return fallbackColors[name];
}
return "grey";
}
function interfaceToDisplayName(name) {
switch (name) {
case "light":
return qsTr("light")
case "button":
return "button";
case "sensor":
return qsTr("sensor")
case "battery":
return qsTr("battery powered thing")
case "connectable":
return qsTr("connectable thing")
default:
console.warn("Unhandled interfaceToDisplayName:", name)
}
}
function interfaceListToDevicePage(interfaceList) {
var page;
if (interfaceList.indexOf("media") >= 0) {
page = "MediaDevicePage.qml";
} else if (interfaceList.indexOf("button") >= 0) {
page = "ButtonDevicePage.qml";
} else if (interfaceList.indexOf("weather") >= 0) {
page = "WeatherDevicePage.qml";
} else if (interfaceList.indexOf("sensor") >= 0) {
page = "SensorDevicePage.qml";
} else if (interfaceList.indexOf("inputtrigger") >= 0) {
page = "InputTriggerDevicePage.qml";
} else if (interfaceList.indexOf("garagegate") >= 0 ) {
page = "GarageGateDevicePage.qml";
} else if (interfaceList.indexOf("light") >= 0) {
page = "ColorLightDevicePage.qml";
} else if (interfaceList.indexOf("extendedshutter") >= 0 ) {
page = "ShutterDevicePage.qml";
} else if (interfaceList.indexOf("extendedawning") >= 0) {
page = "AwningDevicePage.qml";
} else if (interfaceList.indexOf("notifications") >= 0) {
page = "NotificationsDevicePage.qml";
} else {
page = "GenericDevicePage.qml";
}
print("Selecting page", page, "for interface list:", interfaceList)
return page;
}
function pad(num, size) {
var s = "000000000" + num;
return s.substr(s.length-size);
}
Component {
id: invalidVersionComponent
Popup {
id: popup
property string actualVersion: "0.0"
property string minimumVersion: "1.0"
width: app.width * .8
height: col.childrenRect.height + app.margins * 2
x: (app.width - width) / 2
y: (app.height - height) / 2
visible: false
ColumnLayout {
id: col
anchors { left: parent.left; right: parent.right }
spacing: app.margins
Label {
text: qsTr("Connection error")
Layout.fillWidth: true
font.pixelSize: app.largeFont
}
Label {
text: qsTr("Sorry, the version of the %1 box you are trying to connect to is too old. This app requires at least version %2 but the %1 box only supports %3").arg(app.systemName).arg(popup.minimumVersion).arg(popup.actualVersion)
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
Button {
Layout.fillWidth: true
text: qsTr("OK")
onClicked: {
Engine.connection.disconnect();
popup.close()
}
}
}
}
}
KeyboardLoader {
id: keyboardRect
anchors { left: parent.left; bottom: parent.bottom; right: parent.right }
}
}