0.6.9: azione sulla copertina (P8) — il boost senza aprire l'app

Sulla copertina compare l'icona del boost (CoverAction). Una sola azione: la
documentazione Silica ammette piu' di una azione ma dice che lo stile delle app
di sistema ne mostra una. CoverBackground eredita da Cover, quindi l'elenco
dichiarato li' si attacca; l'icona e' iconSource (non icon.source del Button:
sbagliarlo non da' errore, l'icona semplicemente non compare). La riga in fondo
si ancora a Cover.coverActionArea, cosi' l'icona non la copre.

Nuovo controllo statico tests/cover_action_check.py: elenco figlio diretto del
Cover, una sola azione, icona che esiste, id del comando presente nella tabella
del bridge. Il componente e' nominato nella guida Silica dell'SDK di Carlo.
This commit is contained in:
Carlo
2026-10-11 11:35:49 +02:00
parent 1ac1617c2c
commit 2136122398
11 changed files with 164 additions and 14 deletions
+2
View File
@@ -166,6 +166,7 @@ nessuno. Impiegano millisecondi e dicono il file di cui non sono contenti:
python3 tests/qml_check_bindings.py qml # nessuna proprietà assegnata due volte
python3 tests/qml_focus_check.py # un tocco apre solo quello che ha toccato
python3 tests/label_fit_check.py # le etichette dei tasti ci stanno
python3 tests/cover_action_check.py # l'azione di copertina c'e' ed e' collegata
```
## Stato
@@ -236,6 +237,7 @@ tests/ui_contract_check.py chiavi dei QML contro quelle del bridge (bridge atti
tests/qml_check_bindings.py assegnazioni duplicate di proprieta' nei QML
tests/qml_focus_check.py un tocco apre solo le regolazioni di quell'oggetto
tests/label_fit_check.py le etichette dei tasti ci stanno accanto all'icona
tests/cover_action_check.py azione di copertina: figlia diretta del Cover, icona e comando esistono
bridge/aira_bridge.py servizio HTTP/1.1 + JSON verso il cloud (gRPC)
bridge/setup_creds.py salva le credenziali Aira (file 600)
bridge/aira_probe.py sonda di sola lettura: login + stato impianto
+2
View File
@@ -149,6 +149,7 @@ milliseconds and name the file they are unhappy about:
python3 tests/qml_check_bindings.py qml # no property assigned twice in one object
python3 tests/qml_focus_check.py # a tap opens only the object it touched
python3 tests/label_fit_check.py # quick-action labels fit the button
python3 tests/cover_action_check.py # the cover action is declared and wired
```
## Status
@@ -250,6 +251,7 @@ tests/ui_contract_check.py QML keys vs bridge keys (bridge must be running)
tests/qml_check_bindings.py duplicate property assignments in the QML
tests/qml_focus_check.py a tap opens only that object's settings
tests/label_fit_check.py quick-action labels fit next to the icon (measured on a phone)
tests/cover_action_check.py cover action: direct child of Cover, icon exists, command exists
bridge/ local HTTP bridge -> see bridge/README.md
translations/ harbour-aira-it.ts (English is the source language)
docs/PIANO.md project plan and decisions (Italian)
+16 -1
View File
@@ -367,6 +367,21 @@ una si allunga il controllo lo segnala) e `docs/icona/tasti-alla-misura-vera.png
per l'occhio. Il pulsante Silica non espone il carattere, quindi non si può
rimpicciolire il testo: l'unica leva è l'icona.
**Stato attuale (0.6.8).** 85/85 verifiche del nucleo, 48/48 del bridge,
**0.6.9 — l'azione sulla copertina (P8).** Sulla copertina c'è l'icona del
boost: si avvia il boost dell'acqua calda senza aprire l'app. Una sola azione,
perché la documentazione Silica dice che due si possono avere ma che lo stile
delle app di sistema ne mostra una. Il componente esiste: `CoverActionList` e
`CoverAction` sono nominati nella guida Silica che sta nell'SDK, e
`CoverBackground` eredita da `Cover`, quindi l'elenco dichiarato lì si attacca
(non serve avvolgere la copertina in un `Cover`). L'icona si dichiara con
`iconSource`, non con `icon.source` del pulsante `Button`: sbagliarlo non dà
errore, semplicemente l'icona non compare. La riga in fondo (esterna o «non
aggiornato») si ancora a `Cover.coverActionArea`, così l'icona dell'azione non
la copre; se l'area non c'è la riga resta dov'era. Nuovo controllo statico
`tests/cover_action_check.py`: elenco figlio diretto, una sola azione, icona che
esiste, e l'id del comando confrontato con la tabella del bridge — un id
sbagliato sarebbe un 400 scoperto solo sul telefono.
**Stato attuale (0.6.9).** 85/85 verifiche del nucleo, 48/48 del bridge,
191/191 stringhe tradotte, `qmllint` pulito, contratto UI↔bridge rispettato.
Nessun comando è stato inviato all'impianto: tutto quanto sopra è a sola lettura.
+9 -8
View File
@@ -24,14 +24,14 @@ Le quattro scelte, applicate:
compatta con il pallino colorato.
2. "Altri comandi" è diventata **"Comandi impianto"**.
3. Le azioni rapide sono **pulsanti visibili** in Home.
4. Azioni sulla copertina: **rimandate** a dopo la prova di `CoverAction` sul kit.
4. Azioni sulla copertina: **fatte** in 0.6.9 (il boost). Il componente c'è: la guida Silica dell'SDK nomina `CoverActionList` e `CoverAction`.
Problemi risolti: P1 (Home di consultazione), P2 (feedback ancorato), P3
(interruttori), P4 (un solo percorso per pagina), P5 (un blocco per
grandezza, valori rapidi), P6 (numeri vecchi spenti + ora), P7 ("esterna"),
P9, P10 (diagnostica in Impianto), P11.
Rimandato: P8 (azioni sulla copertina), in attesa della prova sul telefono.
Fatto in 0.6.9: P8 (azione boost sulla copertina), con `tests/cover_action_check.py`.
Verifiche fatte qui: **85 verifiche del nucleo** (erano 79), `qmllint` pulito su
tutti i file QML, 191/191 stringhe tradotte, anteprime rigenerate in inglese e
@@ -242,9 +242,10 @@ Resta com'è nelle letture (casa grande, ACS, esterna), con due migliorie basse:
- Dicitura chiara: "esterna" invece di "est." (P7).
- **Azioni sulla copertina**: Silica ha `CoverAction`/`CoverActionList`
documentati ("un'azione per una copertina"). Una o due icone — boost e
assenza — permettono di agire senza aprire l'app (P8). È rifinitura: da
provare sul kit prima di contarla, come ultimo passo.
documentati ("un'azione per una copertina"). Fatto in 0.6.9 con **una** icona —
il boost — perché la documentazione dice che due si possono avere ma che lo
stile delle app di sistema ne mostra una. Si può aggiungere l'assenza quando
serve. Attenzione: qui l'icona è `iconSource`, non `icon.source` del `Button`.
### 4.6 Feedback e stato non aggiornato (P2, P6)
@@ -336,7 +337,7 @@ davvero: **quanti tocchi per T2 e T3**, prima e dopo.
| Rischio | Come si governa |
|---|---|
| `TextSwitch` documentato ma non istanziabile sul kit 5.1 | Provarlo in una pagina di prova alla prima build; ripiego: si resta sui pulsanti (già funzionanti) |
| `CoverAction` idem | È l'ultimo passo; se non c'è, la copertina resta com'è |
| `CoverAction` | C'è: nominato nella guida Silica dell'SDK. Usato in 0.6.9, con `tests/cover_action_check.py` |
| Le anteprime non rappresentano lo scroll reale | La riga di log con `contentHeight` dà la misura vera |
| Un interruttore che anticipa lo stato (bug classico) | `checked` legato ai dati del bridge, non al tocco (§4.4) |
| Il cambio lingua ricarica la radice (Qt 5.6) | Nessun impatto: già così |
@@ -363,5 +364,5 @@ una build e un controllo tuo sul telefono.
2. **"Altri comandi" → "Comandi impianto"**: rinominare?
3. **Azioni rapide**: pulsanti visibili sulla Home (proposta) o menù dal basso
all'altezza del pollice?
4. **Copertina con le azioni**: la facciamo (dopo aver verificato `CoverAction`
sul kit) o resta di sola lettura?
4. **Copertina con le azioni**: fatto in 0.6.9 — il boost sulla copertina. Se
serve, si aggiunge l'assenza (una sola per lo stile di sistema).
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 25 KiB

+17 -1
View File
@@ -149,7 +149,10 @@ CoverBackground {
Label {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
// Sopra l'area delle azioni: quando l'azione c'e', Silica disegna li'
// l'icona del boost, che non deve coprire questa riga. Se l'area non
// c'e' la riga resta attaccata al fondo, come prima.
anchors.bottom: cover.coverActionArea ? cover.coverActionArea.top : parent.bottom
anchors.bottomMargin: Theme.paddingMedium
// Qui c'e' posto per dire che i numeri non sono freschi.
text: api.stale
@@ -160,4 +163,17 @@ CoverBackground {
truncationMode: TruncationMode.Fade
width: parent.width - 2 * Theme.paddingMedium
}
// ---- azione sulla copertina (P8) --------------------------------------- //
// Agire senza aprire l'app: il boost dell'acqua calda. Una sola, perche' la
// documentazione Silica dice che due azioni si possono avere ma che lo stile
// delle app di sistema ne mostra una. CoverBackground eredita da Cover,
// quindi l'elenco si attacca: e' il posto documentato dove dichiararlo.
// Il componente e' in Sailfish.Silica 1.0 (verificato nella guida dell'SDK).
CoverActionList {
CoverAction {
iconSource: "../icons/quick-boost.png"
onTriggered: api.sendCommand("hot_water_boost_on")
}
}
}
+12 -1
View File
@@ -1,6 +1,6 @@
Name: harbour-aira
Summary: Control your Aira heat pump through a local bridge
Version: 0.6.8
Version: 0.6.9
Release: 1
Group: Qt/Qt
License: MIT
@@ -37,6 +37,17 @@ Address and token of the bridge live in the app settings (AppConfigLocation);
no personal data is stored in the binary.
%changelog
* Sun Oct 11 2026 Carlo <carlo@carlobaratto.it> - 0.6.9-1
- copertina: azione rapida (P8 del piano). Sulla copertina compare l'icona del
boost: si avvia il boost dell'acqua calda senza aprire l'app. Una sola azione,
perche' la documentazione Silica dice che due si possono avere ma che lo stile
delle app di sistema ne mostra una. La riga dei dati non freschi si sposta
sopra l'area delle azioni (Cover.coverActionArea), cosi' l'icona non la copre.
Componente e nomi verificati nella guida dell'SDK (CoverActionList, CoverAction,
iconSource, onTriggered: attenzione, e' iconSource, non icon.source del Button)
- nuovo controllo statico tests/cover_action_check.py: elenco figlio diretto del
Cover, una sola azione, icona che esiste, comando presente nella tabella del
bridge (un id sbagliato sarebbe un 400 scoperto solo sul telefono)
* Sun Oct 11 2026 Carlo <carlo@carlobaratto.it> - 0.6.8-1
- Home: le icone dei tasti rimpicciolite. Il pulsante Silica, se non si dice
altro, disegna l'icona a Theme.iconSizeMedium (64 px): in un tasto largo un
+7 -1
View File
@@ -586,8 +586,14 @@ def cover_screen() -> Image.Image:
d.text((card[0] + colonna + d.textlength(ora, font=font(30, bold=True)), y + 4),
"/" + obiettivo + " °C", font=font(24), fill=SECONDARY)
y += 72
d.text((card[0] + 34, card[3] - 62),
d.text((card[0] + 34, card[3] - 118),
T("cover_outdoor") + " " + dec(18.0) + " °C", font=font(22), fill=SECONDARY)
# l'azione sulla copertina (P8): il boost, nell'area in basso. Dove la
# disegni Silica lo decide lei; qui si mostra che c'e'.
icona = Image.open(OUT.parent.parent / "qml" / "icons" / "quick-boost.png").convert("RGBA")
lato = 64
icona = icona.resize((lato, lato), Image.LANCZOS)
img.paste(icona, (int(card[0] + (card[2] - card[0] - lato) / 2), card[3] - 96), icona)
return img
+2 -2
View File
@@ -21,7 +21,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
app->setOrganizationName(QStringLiteral("harbour"));
app->setApplicationName(QStringLiteral("aira"));
app->setApplicationVersion(QStringLiteral("0.6.8"));
app->setApplicationVersion(QStringLiteral("0.6.9"));
Settings settings;
ApiClient api(&settings);
@@ -36,7 +36,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
// Prima riga del log: dice subito se il device sta girando la build nuova
// o una stale (trappola nota del build engine).
qDebug() << "harbour-aira v0.6.8 build" << __DATE__ << __TIME__;
qDebug() << "harbour-aira v0.6.9 build" << __DATE__ << __TIME__;
view->rootContext()->setContextProperty(QStringLiteral("appSettings"), &settings);
view->rootContext()->setContextProperty(QStringLiteral("api"), &api);
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""Controllo statico dell'azione sulla copertina (P8 del piano).
Verifica le regole che la documentazione Silica pone sulle azioni di copertina,
e che il componente sia usato come va usato:
- il `CoverActionList` sta come figlio diretto della copertina (qui la radice
e' `CoverBackground`, che eredita da `Cover`: e' li' che va dichiarato).
Un elenco annidato dentro un altro elemento non si attaccherebbe;
- al massimo una azione: la documentazione dice che due si possono avere, ma
che lo stile delle app di sistema ne mostra una;
- l'icona si dichiara con `iconSource` (non `icon.source`, che e' del
pulsante Button: sbagliarlo qui non da' errore, l'icona semplicemente non
compare);
- l'icona esiste davvero, risolta rispetto al file QML che definisce la
copertina;
- il comando che l'azione invia esiste nella tabella dei comandi del bridge
(un id sbagliato lo scoprirebbe solo il telefono, con un 400).
Non serve il kit Silica: legge i file. Uscita 0 = tutto a posto.
"""
import pathlib
import re
import sys
RADICE = pathlib.Path(__file__).resolve().parent.parent
COPERTINA = RADICE / "qml" / "cover" / "CoverPage.qml"
BRIDGE = RADICE / "bridge" / "aira_bridge.py"
def main():
if not COPERTINA.exists():
print("ESITO: manca %s" % COPERTINA)
return 1
testo = COPERTINA.read_text(encoding="utf-8")
righe = testo.splitlines()
problemi = []
radice = next((r.strip() for r in righe
if re.match(r"^[A-Z][A-Za-z]*\s*\{", r)), "")
if not re.match(r"CoverBackground|Cover\s*\{", radice):
problemi.append("la radice della copertina e' %r: le azioni vanno dentro "
"un Cover (CoverBackground eredita da Cover)" % radice[:40])
# il CoverActionList deve stare al primo livello dentro la radice
elenchi = [(i, r) for i, r in enumerate(righe) if re.match(r"^\s*CoverActionList\s*\{", r)]
if not elenchi:
print("nessuna azione sulla copertina: niente da controllare (0.6.8 e prima)")
return 0
for i, r in elenchi:
rientro = len(r) - len(r.lstrip())
if rientro != 4:
problemi.append("riga %d: il CoverActionList e' rientrato di %d spazi, "
"deve essere figlio diretto della copertina" % (i + 1, rientro))
azioni = [r for r in righe if re.match(r"^\s*CoverAction\s*\{", r)]
if len(azioni) > 1:
problemi.append("%d azioni: la documentazione Silica ne ammette piu' di una "
"ma lo stile delle app di sistema ne mostra una" % len(azioni))
if re.search(r"^\s*icon\.source\s*:", testo, re.M):
problemi.append("usa `iconSource`, non `icon.source`: `icon.source` e' del "
"pulsante Button e qui non darebbe errore, solo nessuna icona")
for m in re.finditer(r'iconSource\s*:\s*"([^"]+)"', testo):
icona = (COPERTINA.parent / m.group(1)).resolve()
if not icona.exists():
problemi.append("l'icona %s non esiste (risolta da %s)"
% (m.group(1), COPERTINA.name))
else:
print("icona: %s -> %s" % (m.group(1), icona.relative_to(RADICE)))
comandi = re.findall(r'sendCommand\("([^"]+)"', testo)
if not comandi:
problemi.append("l'azione non invia nessun comando: a che serve?")
for c in comandi:
if not BRIDGE.exists():
print("salto il controllo del comando %r: manca %s" % (c, BRIDGE))
continue
ponte = BRIDGE.read_text(encoding="utf-8")
if not re.search(r'"%s"\s*:' % re.escape(c), ponte):
problemi.append("il comando %r non e' nella tabella del bridge: "
"il telefono riceverebbe un 400" % c)
else:
print("comando: %s -> presente nel bridge" % c)
if problemi:
for p in problemi:
print("PROBLEMA: %s" % p)
print("ESITO: %d problemi sull'azione di copertina" % len(problemi))
return 1
print("ESITO: azione di copertina a posto")
return 0
if __name__ == "__main__":
sys.exit(main())