620 lines
21 KiB
QML
620 lines
21 KiB
QML
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* Copyright 2013 - 2020, nymea GmbH
|
|
* Contact: contact@nymea.io
|
|
*
|
|
* This file is part of nymea.
|
|
* This project including source code and documentation is protected by
|
|
* copyright law, and remains the property of nymea GmbH. All rights, including
|
|
* reproduction, publication, editing and translation, are reserved. The use of
|
|
* this project is subject to the terms of a license agreement to be concluded
|
|
* with nymea GmbH in accordance with the terms of use of nymea GmbH, available
|
|
* under https://nymea.io/license
|
|
*
|
|
* GNU General Public License Usage
|
|
* Alternatively, this project may be redistributed and/or modified under the
|
|
* terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, GNU version 3. This project is distributed in the hope that it
|
|
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
* Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this project. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* For any further details and any questions please contact us under
|
|
* contact@nymea.io or see our FAQ/Licensing Information on
|
|
* https://nymea.io/license/faq
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
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 Qt.labs.folderlistmodel 2.2
|
|
import QtQuick.Window 2.3
|
|
import Nymea 1.0
|
|
|
|
ApplicationWindow {
|
|
id: app
|
|
visible: true
|
|
width: 360
|
|
height: 580
|
|
minimumWidth: 350
|
|
minimumHeight: 480
|
|
visibility: kioskMode ? ApplicationWindow.FullScreen : settings.viewMode
|
|
color: Material.background
|
|
title: Configuration.appName
|
|
|
|
Material.theme: NymeaUtils.isDark(Style.backgroundColor) ? Material.Dark : Material.Light
|
|
Material.background: Style.backgroundColor
|
|
Material.accent: Style.accentColor
|
|
Material.foreground: Style.foregroundColor
|
|
|
|
font.pixelSize: mediumFont
|
|
font.weight: Font.Normal
|
|
font.capitalization: Font.MixedCase
|
|
font.family: Style.fontFamily
|
|
|
|
property int margins: 16
|
|
property int bigMargins: 20
|
|
|
|
property int extraSmallFont: 10
|
|
property int smallFont: 13
|
|
property int mediumFont: 16
|
|
property int largeFont: 20
|
|
property int hugeFont: 40
|
|
|
|
property int delegateHeight: 60
|
|
|
|
readonly property bool landscape: app.width > app.height
|
|
|
|
readonly property var settings: Settings {
|
|
property int viewMode: ApplicationWindow.AutomaticVisibility
|
|
property alias windowWidth: app.width
|
|
property alias windowHeight: app.height
|
|
property bool returnToHome: false
|
|
property string graphStyle: "bars"
|
|
property bool showHiddenOptions: false
|
|
property string cloudEnvironment: "Community"
|
|
// FIXME: This shouldn't be needed... we should probably only use the system locale and not even provide a setting
|
|
// However, the topic is more complex, and in the long run we'd probably want to allow the user selecting the
|
|
// desired unit for particular interfaces/things/views. See https://github.com/nymea/nymea/issues/386
|
|
property string units: Qt.locale().measurementSystem === Locale.MetricSystem ? "metric" : "imperial"
|
|
}
|
|
|
|
property string privacyPolicyUrl: "https://nymea.io/privacy-statement/en/nymea_privacy.html"
|
|
|
|
Component.onCompleted: {
|
|
styleController.setSystemFont(app.font)
|
|
PlatformHelper.topPanelColor = Style.backgroundColor
|
|
PlatformHelper.bottomPanelColor = Style.backgroundColor
|
|
}
|
|
|
|
Binding {
|
|
target: Types
|
|
property: "unitSystem"
|
|
value: settings.units === "metric" ? Types.UnitSystemMetric : Types.UnitSystemImperial
|
|
}
|
|
|
|
Binding {
|
|
target: PushNotifications
|
|
property: "enabled"
|
|
value: PlatformPermissions.notificationsPermission === PlatformPermissions.PermissionStatusGranted
|
|
}
|
|
|
|
ConfiguredHostsModel {
|
|
id: configuredHostsModel
|
|
}
|
|
|
|
property alias mainMenu: m
|
|
MainMenu {
|
|
id: m
|
|
height: app.height
|
|
width: Math.min(300, app.width)
|
|
// z: 1000
|
|
configuredHosts: configuredHostsModel
|
|
onOpenThingSettings: rootItem.openThingSettings();
|
|
onOpenMagicSettings: rootItem.openMagicSettings();
|
|
onOpenAppSettings: rootItem.openAppSettings();
|
|
onOpenSystemSettings: rootItem.openSystemSettings();
|
|
onOpenCustomPage: rootItem.openCustomPage(page);
|
|
onConfigureMainView: rootItem.configureMainView();
|
|
onStartManualConnection: rootItem.startManualConnection();
|
|
onStartWirelessSetup: rootItem.startWirelessSetup();
|
|
}
|
|
|
|
RootItem {
|
|
id: rootItem
|
|
anchors.fill: parent
|
|
anchors.bottomMargin: keyboardRect.height
|
|
}
|
|
|
|
property NymeaDiscovery nymeaDiscovery: NymeaDiscovery {
|
|
objectName: "discovery"
|
|
bluetoothDiscoveryEnabled: false// PlatformPermissions.bluetoothPermission === PlatformPermissions.PermissionStatusGranted
|
|
}
|
|
|
|
property var supportedInterfaces: [
|
|
"light",
|
|
"media",
|
|
"awning",
|
|
"shutter",
|
|
"blind",
|
|
"cleaningrobot",
|
|
"garagedoor",
|
|
"powersocket",
|
|
"thermostat",
|
|
"heating",
|
|
"cooling",
|
|
"smartlock",
|
|
"doorbell",
|
|
"irrigation",
|
|
"ventilation",
|
|
"sensor",
|
|
"weather",
|
|
"evcharger",
|
|
"smartmeter",
|
|
"fingerprintreader",
|
|
"notifications",
|
|
"barcodescanner",
|
|
"button",
|
|
"inputtrigger",
|
|
"outputtrigger",
|
|
"gateway",
|
|
"account"
|
|
]
|
|
|
|
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":
|
|
case "powerswitch":
|
|
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 "noisesensor":
|
|
return qsTr("Noise level");
|
|
case "cosensor":
|
|
return qsTr("CO level")
|
|
case "co2sensor":
|
|
return qsTr("CO2 level")
|
|
case "no2sensor":
|
|
return qsTr("Nitrogen dioxide level")
|
|
case "gassensor":
|
|
return qsTr("Flammable gas level")
|
|
case "vocsensor":
|
|
return qsTr("VOC level")
|
|
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 "garagedoor":
|
|
return qsTr("Garage doors");
|
|
case "accesscontrol":
|
|
return qsTr("Access control");
|
|
case "fingerprintreader":
|
|
return qsTr("Fingerprint reader");
|
|
case "smartmeter":
|
|
case "smartmeterproducer":
|
|
case "smartmeterconsumer":
|
|
case "extendedsmartmeterproducer":
|
|
case "extendedsmartmeterconsumer":
|
|
return qsTr("Smart meters");
|
|
case "heating":
|
|
return qsTr("Heating");
|
|
case "cooling":
|
|
return qsTr("Cooling");
|
|
case "thermostat":
|
|
return qsTr("Thermostats");
|
|
case "evcharger":
|
|
return qsTr("EV-chargers");
|
|
case "powersocket":
|
|
return qsTr("Power sockets")
|
|
case "doorbell":
|
|
return qsTr("Doorbells");
|
|
case "account":
|
|
return qsTr("Accounts");
|
|
case "smartlock":
|
|
return qsTr("Smartlocks")
|
|
case "irrigation":
|
|
return qsTr("Irrigation");
|
|
case "ventilation":
|
|
return qsTr("Ventilation")
|
|
case "barcodescanner":
|
|
return qsTr("Barcode scanners");
|
|
case "cleaningrobot":
|
|
return qsTr("Cleaning robots")
|
|
case "electricvehicle":
|
|
return qsTr("Electric cars");
|
|
case "uncategorized":
|
|
return qsTr("Uncategorized")
|
|
default:
|
|
console.warn("interfaceToString unhandled interface:", name)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
function interfacesToIcon(interfaces) {
|
|
// print("finding icon for interfaces:", 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) {
|
|
// print("finding icon for interface:", name)
|
|
switch (name) {
|
|
case "light":
|
|
case "colorlight":
|
|
case "dimmablelight":
|
|
case "colortemperaturelight":
|
|
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 "noisesensor":
|
|
return Qt.resolvedUrl("images/sensors/noise.svg");
|
|
case "cosensor":
|
|
return Qt.resolvedUrl("images/sensors/co.svg")
|
|
case "co2sensor":
|
|
return Qt.resolvedUrl("images/sensors/co2.svg")
|
|
case "no2sensor":
|
|
return Qt.resolvedUrl("images/sensors/no2.svg")
|
|
case "o3sensor":
|
|
return Qt.resolvedUrl("images/sensors/o3.svg")
|
|
case "vocsensor":
|
|
return Qt.resolvedUrl("images/sensors/voc.svg")
|
|
case "pm10sensor":
|
|
return Qt.resolvedUrl("images/sensors/pm10.svg")
|
|
case "pm25sensor":
|
|
return Qt.resolvedUrl("images/sensors/pm25.svg")
|
|
case "gassensor":
|
|
return Qt.resolvedUrl("images/sensors/gas.svg")
|
|
case "daylightsensor":
|
|
return Qt.resolvedUrl("images/sensors/light.svg")
|
|
case "presencesensor":
|
|
return Qt.resolvedUrl("images/sensors/presence.svg")
|
|
case "closablesensor":
|
|
return Qt.resolvedUrl("images/sensors/closable.svg")
|
|
case "windspeedsensor":
|
|
return Qt.resolvedUrl("images/sensors/windspeed.svg")
|
|
case "watersensor":
|
|
return Qt.resolvedUrl("images/sensors/water.svg")
|
|
case "vibrationsensor":
|
|
return Qt.resolvedUrl("images/sensors/vibration.svg")
|
|
case "waterlevelsensor":
|
|
return Qt.resolvedUrl("images/sensors/water.svg")
|
|
case "firesensor":
|
|
return Qt.resolvedUrl("images/sensors/fire.svg")
|
|
case "o2sensor":
|
|
return Qt.resolvedUrl("images/sensors/o2.svg")
|
|
case "phsensor":
|
|
return Qt.resolvedUrl("images/sensors/ph.svg")
|
|
case "orpsensor":
|
|
return Qt.resolvedUrl("images/sensors/orp.svg")
|
|
case "media":
|
|
case "mediacontroller":
|
|
case "mediaplayer":
|
|
return Qt.resolvedUrl("images/media.svg")
|
|
case "powersocket":
|
|
return Qt.resolvedUrl("images/powersocket.svg")
|
|
case "button":
|
|
case "longpressbutton":
|
|
case "simplemultibutton":
|
|
case "longpressmultibutton":
|
|
case "powerswitch":
|
|
return Qt.resolvedUrl("images/system-shutdown.svg")
|
|
case "weather":
|
|
return Qt.resolvedUrl("images/weather-app-symbolic.svg")
|
|
case "gateway":
|
|
return Qt.resolvedUrl("images/connections/network-wired.svg")
|
|
case "notifications":
|
|
return Qt.resolvedUrl("images/messaging-app-symbolic.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/shutter/shutter-040.svg")
|
|
case "blind":
|
|
case "extendedblind":
|
|
return Qt.resolvedUrl("images/shutter/shutter-060.svg")
|
|
case "garagedoor":
|
|
case "impulsegaragedoor":
|
|
case "statefulgaragedoor":
|
|
case "extendedstatefulgaragedoor":
|
|
case "garagegate":
|
|
return Qt.resolvedUrl("images/garage/garage-100.svg")
|
|
case "awning":
|
|
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/closable-move.svg")
|
|
case "fingerprintreader":
|
|
return Qt.resolvedUrl("images/fingerprint.svg")
|
|
case "accesscontrol":
|
|
return Qt.resolvedUrl("images/lock-closed.svg");
|
|
case "solarinverter":
|
|
return Qt.resolvedUrl("images/weathericons/weather-clear-day.svg")
|
|
case "smartmeter":
|
|
case "smartmeterconsumer":
|
|
case "smartmeterproducer":
|
|
case "energymeter":
|
|
return Qt.resolvedUrl("images/smartmeter.svg")
|
|
// return Qt.resolvedUrl("images/energy.svg")
|
|
case "heating":
|
|
return Qt.resolvedUrl("images/thermostat/heating.svg")
|
|
case "cooling":
|
|
return Qt.resolvedUrl("images/thermostat/cooling.svg")
|
|
case "thermostat":
|
|
return Qt.resolvedUrl("images/dial.svg")
|
|
case "evcharger":
|
|
return Qt.resolvedUrl("images/ev-charger.svg")
|
|
case "doorbell":
|
|
return Qt.resolvedUrl("images/notification.svg")
|
|
case "irrigation":
|
|
return Qt.resolvedUrl("images/irrigation.svg")
|
|
case "ventilation":
|
|
return Qt.resolvedUrl("images/ventilation.svg")
|
|
case "power":
|
|
return Qt.resolvedUrl("images/system-shutdown.svg")
|
|
case "smartlock":
|
|
return Qt.resolvedUrl("images/smartlock.svg")
|
|
case "navigationpad":
|
|
case "extendednavigationpad":
|
|
return Qt.resolvedUrl("images/navigationpad.svg")
|
|
case "volumecontroller":
|
|
return Qt.resolvedUrl("images/audio-speakers-symbolic.svg")
|
|
case "shufflerepeat":
|
|
return Qt.resolvedUrl("images/media-playlist-shuffle.svg")
|
|
case "alert":
|
|
return Qt.resolvedUrl("images/notification.svg")
|
|
case "barcodescanner":
|
|
return Qt.resolvedUrl("images/qrcode.svg")
|
|
case "cleaningrobot":
|
|
return Qt.resolvedUrl("images/cleaning-robot.svg")
|
|
case "account":
|
|
return Qt.resolvedUrl("images/account.svg")
|
|
case "wirelessconnectable":
|
|
return Qt.resolvedUrl("images/connections/network-wifi.svg")
|
|
case "connectable":
|
|
return Qt.resolvedUrl("images/stock_link.svg")
|
|
case "electricvehicle":
|
|
return Qt.resolvedUrl("images/car.svg")
|
|
default:
|
|
console.warn("InterfaceToIcon: Unhandled interface", name)
|
|
}
|
|
return "";
|
|
}
|
|
|
|
|
|
|
|
StyleBase {
|
|
id: styleBase
|
|
}
|
|
|
|
function stateColor(stateName) {
|
|
// Try to load color map from style
|
|
if (Style.stateColors[stateName]) {
|
|
return Style.stateColors[stateName];
|
|
}
|
|
|
|
if (styleBase.stateColors[stateName]) {
|
|
return styleBase.stateColors[stateName];
|
|
}
|
|
console.warn("stateColor(): Color not set for state", stateName)
|
|
return "grey";
|
|
}
|
|
|
|
function stateIcon(stateName) {
|
|
var iconMap = {
|
|
"currentPower": "energy.svg",
|
|
"totalEnergyConsumed": "smartmeter.svg",
|
|
"totalEnergyProduced": "smartmeter.svg",
|
|
}
|
|
if (!iconMap[stateName]) {
|
|
console.warn("stateIcon(): Icon not set for state", stateName)
|
|
}
|
|
return Qt.resolvedUrl("images/" + iconMap[stateName]);
|
|
}
|
|
|
|
function interfaceToColor(name) {
|
|
// Try to load color map from style
|
|
if (Style.interfaceColors[name]) {
|
|
return Style.interfaceColors[name];
|
|
}
|
|
|
|
if (styleBase.interfaceColors[name]) {
|
|
return styleBase.interfaceColors[name];
|
|
}
|
|
|
|
return "grey";
|
|
}
|
|
|
|
function interfaceToDisplayName(name) {
|
|
switch (name) {
|
|
case "light":
|
|
case "dimmablelight":
|
|
case "colorlight":
|
|
case "colortemperaturelight":
|
|
//: Select ...
|
|
return qsTr("light")
|
|
case "sensor":
|
|
//: Select ...
|
|
return qsTr("sensor")
|
|
case "battery":
|
|
//: Select ...
|
|
return qsTr("battery powered thing")
|
|
case "connectable":
|
|
//: Select ...
|
|
return qsTr("connectable thing")
|
|
case "irrigation":
|
|
//: Select ...
|
|
return qsTr("irrigation");
|
|
case "ventilation":
|
|
//: Select ...
|
|
return qsTr("ventilation");
|
|
case "power":
|
|
//: Select ...
|
|
return qsTr("switchable thing")
|
|
case "daylightsensor":
|
|
//: Select ...
|
|
return qsTr("daylight sensor")
|
|
case "presencesensor":
|
|
//: Select ...
|
|
return qsTr("presence sensor")
|
|
case "vibrationsensor":
|
|
//: Select ...
|
|
return qsTr("vibration sensor");
|
|
case "doorbell":
|
|
//: Select ...
|
|
return qsTr("doorbell")
|
|
case "alert":
|
|
//: Select ...
|
|
return qsTr("alert")
|
|
case "simplemultibutton":
|
|
case "simplebutton":
|
|
case "button":
|
|
//: Select ...
|
|
return qsTr("button")
|
|
case "accesscotrol":
|
|
//: Select ...
|
|
return qsTr("access control")
|
|
case "smartmeter":
|
|
case "smartmeterproducer":
|
|
case "smartmeterconsumer":
|
|
case "extendedsmartmeterproducer":
|
|
case "extendedsmartmeterconsumer":
|
|
//: Select ...
|
|
return qsTr("smart meter");
|
|
case "media":
|
|
case "mediaplayer":
|
|
case "mediacontroller":
|
|
//: Select ...
|
|
return qsTr("media player");
|
|
case "moisturesensor":
|
|
//: Select ...
|
|
return qsTr("moisture sensor");
|
|
case "notifications":
|
|
//: Select ...
|
|
return qsTr("thing to notify")
|
|
case "smartlock":
|
|
//: Select ...
|
|
return qsTr("smartlock");
|
|
default:
|
|
console.warn("Unhandled interfaceToDisplayName:", name)
|
|
}
|
|
}
|
|
|
|
|
|
function pad(num, size) {
|
|
var s = "000000000" + num;
|
|
return s.substr(s.length-size);
|
|
}
|
|
|
|
// Handle the Android close event that happens when the back button is pressed
|
|
// It's hard to handle the key press, because we might not have focus all the time
|
|
// So let's handle the window's onClosing signal instad.
|
|
// The problem is, we cannot distinguish between the back button being pressed
|
|
// or the bottom swipe gesture is being used to switch apps. Let's try to figure that out
|
|
// by checking if the app becomes inactive right after the event. If not, it's probably a back
|
|
// button press and we close ourselves.
|
|
onClosing: {
|
|
if (Qt.platform.os == "android") {
|
|
var handled = rootItem.handleAndroidBackButton();
|
|
if (!handled) {
|
|
closeTimer.start()
|
|
}
|
|
close.accepted = false;
|
|
}
|
|
}
|
|
Timer {
|
|
id: closeTimer
|
|
interval: 300
|
|
onTriggered: Qt.quit();
|
|
}
|
|
Connections {
|
|
target: Qt.application
|
|
onStateChanged: closeTimer.stop()
|
|
}
|
|
|
|
FolderListModel {
|
|
id: availableMainViews
|
|
folder: "mainviews"
|
|
showFiles: false
|
|
}
|
|
|
|
// NOTE: If using a Dialog, make sure closePolicy does not contain Dialog.CloseOnPressOutside
|
|
// or the virtual keyboard will close when pressing it...
|
|
|
|
// https://bugreports.qt.io/browse/QTBUG-56918
|
|
KeyboardLoader {
|
|
id: keyboardRect
|
|
parent: app.overlay
|
|
z: 1
|
|
anchors { left: parent.left; bottom: parent.bottom; right: parent.right }
|
|
}
|
|
|
|
Image {
|
|
id: splashScreen
|
|
parent: overlay
|
|
source: "/ui/images/nymea-splash.svg"
|
|
anchors.fill: parent
|
|
fillMode: Image.PreserveAspectCrop
|
|
opacity: PlatformHelper.splashVisible ? 1 : 0
|
|
Behavior on opacity { NumberAnimation {duration: 300 }}
|
|
visible: showSplash && opacity > 0
|
|
antialiasing: true
|
|
smooth: true
|
|
sourceSize.width: Math.max(width, height)
|
|
sourceSize.height: Math.max(width, height)
|
|
}
|
|
}
|