diff --git a/README.it.md b/README.it.md index a091524..9ea1be3 100644 --- a/README.it.md +++ b/README.it.md @@ -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 diff --git a/README.md b/README.md index 4fce3e7..67d0858 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/docs/PIANO.md b/docs/PIANO.md index 48543cd..9fbef6f 100644 --- a/docs/PIANO.md +++ b/docs/PIANO.md @@ -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. diff --git a/docs/UX-STUDIO.md b/docs/UX-STUDIO.md index 6917f61..6a0da83 100644 --- a/docs/UX-STUDIO.md +++ b/docs/UX-STUDIO.md @@ -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). diff --git a/docs/anteprima/copertina.png b/docs/anteprima/copertina.png index 28cbfb2..96548cd 100644 Binary files a/docs/anteprima/copertina.png and b/docs/anteprima/copertina.png differ diff --git a/docs/anteprima/it/copertina.png b/docs/anteprima/it/copertina.png index ffed118..ea30022 100644 Binary files a/docs/anteprima/it/copertina.png and b/docs/anteprima/it/copertina.png differ diff --git a/qml/cover/CoverPage.qml b/qml/cover/CoverPage.qml index 0bfb7e6..550b7f6 100644 --- a/qml/cover/CoverPage.qml +++ b/qml/cover/CoverPage.qml @@ -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") + } + } } diff --git a/rpm/harbour-aira.spec b/rpm/harbour-aira.spec index f7303a3..e20f136 100644 --- a/rpm/harbour-aira.spec +++ b/rpm/harbour-aira.spec @@ -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 - 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 - 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 diff --git a/scripts/render_mockups.py b/scripts/render_mockups.py index 39d4db3..d0cb086 100644 --- a/scripts/render_mockups.py +++ b/scripts/render_mockups.py @@ -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 diff --git a/src/main.cpp b/src/main.cpp index c576d1f..1a03981 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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); diff --git a/tests/cover_action_check.py b/tests/cover_action_check.py new file mode 100644 index 0000000..fb69e96 --- /dev/null +++ b/tests/cover_action_check.py @@ -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())