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) } }