Files
harbour-aira/qml/pages/MainPage.qml
T
kaneda d5b63b57df 0.6.0 blocco 1: Home di consultazione e pagina Regolazioni
- Home: stato in una riga col pallino, due letture grandi toccabili, tre
  azioni rapide; via i due slider, i sette pulsanti e la scheda separata.
- Nuova pagina Regolazioni: un blocco per grandezza, valore attuale sempre
  visibile, preimpostazioni per le dita fredde, conferma esplicita; il
  pulsante e' attivo solo se c'e' davvero qualcosa da cambiare.
- Messaggi dei comandi spostati in ApiClient (api.notice) con NoticeBar
  ancorata in fondo: prima nascevano fuori dallo schermo.
- Stato breve/esteso/livello in C++ (testabile senza SDK), 18 verifiche
  nuove: 79/79.
- stale + lastUpdate: la Home dice 'aggiornato alle HH:MM' e spegne i
  numeri vecchi invece di farli passare per freschi.
- 192/192 stringhe tradotte (54 nuove).
2026-10-10 09:47:16 +02:00

298 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"))
}
Component.onCompleted: {
if (api.configured)
api.refresh()
}
// 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 { }
}