313 lines
11 KiB
QML
313 lines
11 KiB
QML
import QtQuick 2.2
|
|
import Sailfish.Silica 1.0
|
|
import "../components"
|
|
|
|
// Home: una schermata da GUARDARE, non un modulo da compilare.
|
|
//
|
|
// Stato in una riga, le due letture che contano (casa e acqua calda), tre
|
|
// azioni rapide. Le regolazioni stanno in SetpointsPage, gli altri comandi in
|
|
// CommandsPage, i numeri in DataPage, i dettagli in PlantPage: qui solo quello
|
|
// che si guarda molte volte al giorno, senza scorrere (docs/UX-STUDIO.md).
|
|
Page {
|
|
id: page
|
|
|
|
allowedOrientations: Orientation.All
|
|
|
|
function raw(key) {
|
|
return api.summary[key]
|
|
}
|
|
|
|
// I numeri si formattano nel C++ (ApiClient::formatValue): cosi' il
|
|
// separatore decimale segue la lingua dell'app, non il toFixed di JS.
|
|
function fmt(value) { return api.formatValue(value) }
|
|
|
|
function count(key) {
|
|
var v = page.raw(key)
|
|
if (v === undefined || v === null)
|
|
return "—"
|
|
return String(Math.round(Number(v)))
|
|
}
|
|
|
|
// Colore del pallino di stato. Il livello (0 regolare, 1 modo attivo,
|
|
// 2 errore) lo calcola il C++; qui si sceglie il colore, e l'ambra quando
|
|
// i numeri sono vecchi: un dato vecchio non e' un dato buono.
|
|
function statusColor() {
|
|
if (!api.hasData || api.stale)
|
|
return "orange"
|
|
var livello = api.statusLevel()
|
|
if (livello === 2)
|
|
return Theme.errorColor
|
|
if (livello === 1)
|
|
return Theme.highlightColor
|
|
return "#4caf50"
|
|
}
|
|
|
|
// Quanto e' carica l'acqua calda verso l'obiettivo (da 10 °C in su).
|
|
function waterProgress() {
|
|
var ora = Number(page.raw("dhw_current"))
|
|
var obiettivo = Number(page.raw("dhw_target"))
|
|
if (!(obiettivo > 10) || isNaN(ora))
|
|
return 0
|
|
return (ora - 10) / (obiettivo - 10)
|
|
}
|
|
|
|
// Riga sotto il titolo: dice anche quando i numeri sono vecchi.
|
|
function updateText() {
|
|
if (!api.hasData)
|
|
return qsTr("No data yet")
|
|
if (api.stale)
|
|
return qsTr("Not updated · last %1").arg(api.lastUpdate)
|
|
if (api.lastUpdate.length === 0)
|
|
return ""
|
|
return qsTr("Updated at %1").arg(api.lastUpdate)
|
|
}
|
|
|
|
function openSettings() {
|
|
pageStack.push(Qt.resolvedUrl("SettingsPage.qml"))
|
|
}
|
|
|
|
function openCommands() {
|
|
pageStack.push(Qt.resolvedUrl("CommandsPage.qml"))
|
|
}
|
|
|
|
function openPlant() {
|
|
pageStack.push(Qt.resolvedUrl("PlantPage.qml"))
|
|
}
|
|
|
|
function openData() {
|
|
pageStack.push(Qt.resolvedUrl("DataPage.qml"))
|
|
}
|
|
|
|
// UNA sola assegnazione: in Qt 5.6 assegnare Component.onCompleted due volte
|
|
// e' un errore di compilazione del file, e la pagina non si crea piu'
|
|
// ("Type MainPage unavailable" e app senza Home).
|
|
Component.onCompleted: {
|
|
if (api.configured)
|
|
api.refresh()
|
|
misura.start()
|
|
}
|
|
|
|
// Fuori dal pulsante: dentro il pulsante i figli finiscono nel contentItem.
|
|
RemorseItem { id: remorse }
|
|
|
|
SilicaFlickable {
|
|
id: flick
|
|
anchors.fill: parent
|
|
contentHeight: col.height + Theme.paddingLarge
|
|
|
|
// I menu pulley stanno DENTRO la vista: come figli diretti della Page
|
|
// fanno deref su null (TypeError + crash).
|
|
PullDownMenu {
|
|
MenuItem {
|
|
text: qsTr("Refresh")
|
|
onClicked: api.refresh()
|
|
}
|
|
MenuItem {
|
|
text: qsTr("Data")
|
|
onClicked: page.openData()
|
|
}
|
|
MenuItem {
|
|
text: qsTr("Plant")
|
|
onClicked: page.openPlant()
|
|
}
|
|
MenuItem {
|
|
text: qsTr("Settings")
|
|
onClicked: page.openSettings()
|
|
}
|
|
}
|
|
|
|
Column {
|
|
id: col
|
|
width: parent.width
|
|
spacing: Theme.paddingSmall
|
|
|
|
PageHeader {
|
|
title: "Aira"
|
|
description: page.updateText()
|
|
}
|
|
|
|
// ---- bridge non configurato ------------------------------------ //
|
|
Label {
|
|
visible: !api.configured
|
|
x: Theme.horizontalPageMargin
|
|
width: parent.width - 2 * Theme.horizontalPageMargin
|
|
text: qsTr("Bridge not configured. Open the settings and enter the server address and the token.")
|
|
color: Theme.secondaryColor
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
wrapMode: Text.Wrap
|
|
}
|
|
|
|
Button {
|
|
visible: !api.configured
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: qsTr("Settings")
|
|
onClicked: page.openSettings()
|
|
}
|
|
|
|
// ---- stato in una riga ----------------------------------------- //
|
|
Item {
|
|
visible: api.configured
|
|
x: Theme.horizontalPageMargin
|
|
width: parent.width - 2 * Theme.horizontalPageMargin
|
|
height: Math.max(stato.height, pallino.height, attesa.height)
|
|
|
|
Rectangle {
|
|
id: pallino
|
|
width: Theme.fontSizeSmall * 0.6
|
|
height: width
|
|
radius: width / 2
|
|
color: page.statusColor()
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
Label {
|
|
id: stato
|
|
anchors {
|
|
left: pallino.right
|
|
leftMargin: Theme.paddingMedium
|
|
right: attesa.left
|
|
rightMargin: Theme.paddingMedium
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
text: api.stateShort()
|
|
color: Theme.primaryColor
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
BusyIndicator {
|
|
id: attesa
|
|
anchors {
|
|
right: parent.right
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
size: BusyIndicatorSize.Small
|
|
running: api.busy
|
|
visible: api.busy
|
|
}
|
|
}
|
|
|
|
// ---- le due letture che contano -------------------------------- //
|
|
Row {
|
|
id: letture
|
|
visible: api.configured
|
|
x: Theme.horizontalPageMargin
|
|
width: parent.width - 2 * Theme.horizontalPageMargin
|
|
spacing: Theme.paddingSmall
|
|
|
|
ReadingCell {
|
|
width: (letture.width - letture.spacing) / 2
|
|
title: qsTr("House")
|
|
value: page.fmt(page.raw("zone1_temperature")) + " °C"
|
|
subtitle: qsTr("target %1 °C").arg(page.fmt(page.raw("zone1_setpoint")))
|
|
valueColor: api.stale ? Theme.secondaryColor : Theme.primaryColor
|
|
onTapped: pageStack.push(Qt.resolvedUrl("SetpointsPage.qml"))
|
|
}
|
|
|
|
ReadingCell {
|
|
width: (letture.width - letture.spacing) / 2
|
|
title: qsTr("Hot water")
|
|
value: page.fmt(page.raw("dhw_current")) + " °C"
|
|
subtitle: qsTr("target %1 °C").arg(page.fmt(page.raw("dhw_target")))
|
|
progress: page.waterProgress()
|
|
showBar: true
|
|
valueColor: api.stale ? Theme.secondaryColor : Theme.primaryColor
|
|
onTapped: pageStack.push(Qt.resolvedUrl("SetpointsPage.qml"))
|
|
}
|
|
}
|
|
|
|
Label {
|
|
visible: api.configured
|
|
x: Theme.horizontalPageMargin
|
|
width: parent.width - 2 * Theme.horizontalPageMargin
|
|
text: qsTr("Tap a value to adjust it")
|
|
color: Theme.secondaryColor
|
|
font.pixelSize: Theme.fontSizeExtraSmall
|
|
}
|
|
|
|
// ---- azioni rapide --------------------------------------------- //
|
|
SectionHeader {
|
|
visible: api.configured
|
|
text: qsTr("Quick actions")
|
|
}
|
|
|
|
Row {
|
|
visible: api.configured
|
|
x: Theme.horizontalPageMargin
|
|
width: parent.width - 2 * Theme.horizontalPageMargin
|
|
spacing: Theme.paddingSmall
|
|
|
|
Button {
|
|
id: boostBtn
|
|
width: (parent.width - 2 * parent.spacing) / 3
|
|
text: qsTr("Boost")
|
|
onClicked: remorse.execute(boostBtn, qsTr("Starting DHW boost"),
|
|
function() { api.sendCommand("hot_water_boost_on") })
|
|
}
|
|
|
|
Button {
|
|
id: assenzaBtn
|
|
width: (parent.width - 2 * parent.spacing) / 3
|
|
// Il pulsante dice cosa succede premendolo, non lo stato.
|
|
text: page.raw("away_mode") ? qsTr("Back home") : qsTr("Away mode")
|
|
onClicked: {
|
|
if (page.raw("away_mode")) {
|
|
remorse.execute(assenzaBtn, qsTr("Away mode off"),
|
|
function() { api.sendCommand("away_mode_off") })
|
|
} else {
|
|
remorse.execute(assenzaBtn, qsTr("Away mode on"),
|
|
function() { api.sendCommand("away_mode_on") })
|
|
}
|
|
}
|
|
}
|
|
|
|
Button {
|
|
width: (parent.width - 2 * parent.spacing) / 3
|
|
text: qsTr("Commands")
|
|
onClicked: page.openCommands()
|
|
}
|
|
}
|
|
|
|
// ---- letture di contorno (si toccano: portano a Impianto) ------ //
|
|
BackgroundItem {
|
|
visible: api.configured
|
|
x: Theme.horizontalPageMargin
|
|
width: parent.width - 2 * Theme.horizontalPageMargin
|
|
height: contornoTxt.height + Theme.paddingMedium
|
|
onClicked: page.openPlant()
|
|
|
|
Label {
|
|
id: contornoTxt
|
|
anchors {
|
|
left: parent.left
|
|
right: parent.right
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
text: qsTr("Outside %1 °C · Thermostat %2 °C · Zones %3")
|
|
.arg(page.fmt(page.raw("outdoor_temperature")))
|
|
.arg(page.fmt(page.raw("thermostat_temperature")))
|
|
.arg(page.count("zones"))
|
|
color: Theme.secondaryColor
|
|
font.pixelSize: Theme.fontSizeExtraSmall
|
|
wrapMode: Text.Wrap
|
|
}
|
|
}
|
|
|
|
Item { width: 1; height: Theme.paddingMedium }
|
|
}
|
|
}
|
|
|
|
NoticeBar { }
|
|
|
|
// La disposizione non e' definitiva subito: si misura un secondo dopo.
|
|
// Questa e' la riga da leggere alla prima build sul telefono: la Home deve
|
|
// stare in una schermata sola, se "contenuto" supera "schermo" c'e' ancora
|
|
// qualcosa da togliere (o da accettare, ma sapendolo).
|
|
Timer {
|
|
id: misura
|
|
interval: 1000
|
|
onTriggered: console.log("harbour-aira home: contenuto", flick.contentHeight,
|
|
"schermo", height)
|
|
}
|
|
}
|