diff --git a/README.it.md b/README.it.md index 8b97a91..9ba87a0 100644 --- a/README.it.md +++ b/README.it.md @@ -20,8 +20,9 @@ e ripiega sull'inglese; la si può scegliere anche nelle impostazioni dell'app. - **Home**, una schermata da guardare: pallino colorato con lo stato in una riga ("Tutto regolare", "Sta scaldando l'acqua"), l'ora dell'ultimo aggiornamento, le due letture che contano (casa e acqua calda, ognuna con il suo obiettivo) - affiancate e **toccabili**, e tre azioni rapide: boost, assenza, comandi - impianto. Sta in una schermata, senza scorrere: i comandi hanno pagine loro + affiancate e **toccabili**, e tre azioni rapide — boost, assenza, comandi + impianto — ognuna con la sua icona. Sta in una schermata, senza scorrere: i + comandi hanno pagine loro - **Regolazioni**: obiettivo casa e obiettivo acqua calda, ognuno con il valore che l'impianto sta tenendo adesso, lo slider (la proposta: il valore vero non si muove), una fila di valori rapidi per le dita fredde e il pulsante di conferma, @@ -228,6 +229,7 @@ qml/components/ValueRow.qml titolo e sottotitolo a sinistra, valore grande a d qml/components/ReadingCell.qml lettura toccabile: titolo, valore grande, barra qml/components/NoticeBar.qml messaggi dei comandi, ancorati in fondo qml/components/ActionButton.qml pulsante di comando, con conferma opzionale +qml/icons/ i tre glifi delle azioni rapide (disegnati, non dal tema) tests/core_test.cpp verifiche del nucleo (headless) tests/ui_contract_check.py chiavi dei QML contro quelle del bridge (bridge attivo) tests/qml_check_bindings.py assegnazioni duplicate di proprieta' nei QML diff --git a/README.md b/README.md index e5e8f0f..134261b 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ status, domestic hot water, and the house target temperature per zone. - **Home**, one screen to look at: a coloured dot with a one-line status ("Everything is fine", "Heating the hot water"), the time of the last update, the two readings that matter (house and domestic hot water, each with its - target) side by side and **tappable**, and three quick actions: boost, away - mode and the plant commands. It fits without scrolling — the controls live in - their own pages + target) side by side and **tappable**, and three quick actions — boost, away + mode and the plant commands, each with its own icon. It fits without scrolling + — the controls live in their own pages - **Setpoints** page: house target and hot water target, each with the value the plant is keeping right now, a slider (the proposal, the plant value does not move), a row of quick values for cold fingers and a confirmation button that @@ -243,6 +243,7 @@ qml/components/ValueRow.qml title + subtitle left, big value right qml/components/ReadingCell.qml tappable reading: title, big value, charge bar qml/components/NoticeBar.qml command messages, anchored to the bottom edge qml/components/ActionButton.qml command button, optional confirmation +qml/icons/ the three quick-action glyphs (drawn, not from the theme) tests/core_test.cpp core checks (headless) 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 diff --git a/docs/PIANO.md b/docs/PIANO.md index 6cee800..04143ad 100644 --- a/docs/PIANO.md +++ b/docs/PIANO.md @@ -344,6 +344,14 @@ toccato e la pagina mostra solo quello — l'acqua porta con sé anche il boost, (`tests/qml_focus_check.py`), perché questa parte della UI non è eseguibile senza il kit Silica. -**Stato attuale (0.6.6).** 85/85 verifiche del nucleo, 48/48 del bridge, +**0.6.7 — le icone dei tasti.** I tre tasti delle azioni rapide in Home hanno +un'icona: fiamma per il boost, casa per l'assenza, tre manopole per i comandi. +Non vengono dal tema Sailfish: nell'elenco ufficiale delle icone non c'è nessuna +fiamma e nessuna goccia, e "boost" e "assenza" non hanno un simbolo di sistema. +Sono disegnate con Pillow (`scripts/make_glyphs.py`) in PNG bianche su +trasparente, in `qml/icons/`: il PNG si vede di sicuro dentro il sandbox, l'SVG +dipende dal supporto SVG. + +**Stato attuale (0.6.7).** 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/anteprima/dashboard.png b/docs/anteprima/dashboard.png index 511980c..465b9b4 100644 Binary files a/docs/anteprima/dashboard.png and b/docs/anteprima/dashboard.png differ diff --git a/docs/anteprima/it/dashboard.png b/docs/anteprima/it/dashboard.png index 5e8b7ff..e37a7a1 100644 Binary files a/docs/anteprima/it/dashboard.png and b/docs/anteprima/it/dashboard.png differ diff --git a/docs/icona/glifi-tasti.png b/docs/icona/glifi-tasti.png new file mode 100644 index 0000000..f8831cf Binary files /dev/null and b/docs/icona/glifi-tasti.png differ diff --git a/qml/icons/quick-away.png b/qml/icons/quick-away.png new file mode 100644 index 0000000..0b28594 Binary files /dev/null and b/qml/icons/quick-away.png differ diff --git a/qml/icons/quick-boost.png b/qml/icons/quick-boost.png new file mode 100644 index 0000000..47939aa Binary files /dev/null and b/qml/icons/quick-boost.png differ diff --git a/qml/icons/quick-commands.png b/qml/icons/quick-commands.png new file mode 100644 index 0000000..60dc559 Binary files /dev/null and b/qml/icons/quick-commands.png differ diff --git a/qml/pages/MainPage.qml b/qml/pages/MainPage.qml index 895f1cc..705c098 100644 --- a/qml/pages/MainPage.qml +++ b/qml/pages/MainPage.qml @@ -248,6 +248,7 @@ Page { id: boostBtn width: (parent.width - 2 * parent.spacing) / 3 text: qsTr("Boost") + icon.source: "../icons/quick-boost.png" onClicked: remorse.execute(boostBtn, qsTr("Starting DHW boost"), function() { api.sendCommand("hot_water_boost_on") }) } @@ -257,6 +258,7 @@ Page { 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") + icon.source: "../icons/quick-away.png" onClicked: { if (page.raw("away_mode")) { remorse.execute(assenzaBtn, qsTr("Away mode off"), @@ -271,6 +273,7 @@ Page { Button { width: (parent.width - 2 * parent.spacing) / 3 text: qsTr("Commands") + icon.source: "../icons/quick-commands.png" onClicked: page.openCommands() } } diff --git a/rpm/harbour-aira.spec b/rpm/harbour-aira.spec index cdd4674..08756f7 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.6 +Version: 0.6.7 Release: 1 Group: Qt/Qt License: MIT @@ -37,6 +37,11 @@ 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.7-1 +- Home: i tre tasti delle azioni rapide hanno un'icona (fiamma = boost, casa = + assenza, tre manopole = comandi). Il tema Sailfish non ha icone per questi + concetti (nell'elenco ufficiale non c'e' nessuna fiamma): sono disegnate con + Pillow in scripts/make_glyphs.py, PNG bianche su trasparente in qml/icons/ * Sun Oct 11 2026 Carlo - 0.6.6-1 - Home: toccare "Casa" o "Acqua calda" apre le regolazioni di quell'oggetto e non piu' casa e acqua insieme; l'acqua porta con se' il boost, che e' suo. diff --git a/scripts/make_glyphs.py b/scripts/make_glyphs.py new file mode 100644 index 0000000..13eb83d --- /dev/null +++ b/scripts/make_glyphs.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""Disegna le icone dei tre tasti delle opzioni rapide in Home. + +Stanno in qml/icons/ perche' il pulsante Silica vuole un'immagine, non un disegno +QML. PNG e non SVG di proposito: il PNG si vede di sicuro, l'SVG dipende dal +supporto SVG dentro il sandbox dell'app. + +Bianche su trasparente: sul tema Sailfish i tasti sono scuri e le icone di +sistema sono cosi'. Tre concetti che il tema Sailfish non ha (nell'elenco +ufficiale non c'e' nessuna fiamma, nessuna goccia): boost = fiamma, assenza = +casa (la stessa casa dell'icona dell'app), comandi = tre righe con le manopole. + +Uso: python3 scripts/make_glyphs.py +""" +import math +import pathlib + +from PIL import Image, ImageDraw + +BASE = 688 # si disegna grande e si riduce: bordi puliti a ogni taglia +OUT = 172 # come l'icona piu' grande dell'app, abbondante per il tasto +DEST = pathlib.Path(__file__).resolve().parent.parent / "qml" / "icons" +BIANCO = (255, 255, 255, 255) +VUOTO = (0, 0, 0, 0) + + +def nuova(): + return Image.new("RGBA", (BASE, BASE), VUOTO) + + +def fiamma(d, cx, cy, w, h): + """Boost: fiamma — stretta, punta piegata a sinistra, fondo tondo.""" + r = 0.26 * w + alto = h - 0.55 * r + fondo = cy + h / 2 + destra, sinistra = [], [] + passi = 160 + for i in range(passi + 1): + t = i / passi + mezza = (w / 2) * math.sin(math.pi * (t ** 0.7)) + piega = (w * 0.18) * (t ** 1.7) # la punta pende a sinistra + y = fondo - alto * t + destra.append((cx + mezza - piega, y)) + sinistra.append((cx - mezza - piega, y)) + d.polygon(destra + list(reversed(sinistra)), fill=BIANCO) + # fondo arrotondato: copre la punta stretta in basso + d.ellipse([cx - r, fondo - 0.55 * r, cx + r, fondo + 0.55 * r], fill=BIANCO) + + +def casa(d, cx, cy, w, h): + """Assenza: casa con la porta (senza la porta si legge come una freccia).""" + cima = cy - h / 2 + base = cy + h / 2 + spalle = cima + h * 0.36 # dove il tetto incontra il muro + d.polygon([(cx - w * 0.52, spalle), (cx, cima), (cx + w * 0.52, spalle)], fill=BIANCO) + d.rectangle([cx - w * 0.33, spalle - 1, cx + w * 0.33, base], fill=BIANCO) + # la porta: e' quello che la distingue da una freccia + d.rectangle([cx - w * 0.20 / 2, base - h * 0.30, cx + w * 0.20 / 2, base], fill=VUOTO) + + +def comandi(d, cx, cy, w, h): + """Comandi: tre righe con la manopola in posizioni diverse.""" + manopole = (-0.18, 0.20, -0.06) + for i, posizione in enumerate(manopole): + y = cy - h / 2 + h * (0.16 + 0.34 * i) + spessore = h * 0.115 + d.rounded_rectangle([cx - w / 2, y - spessore / 2, cx + w / 2, y + spessore / 2], + radius=spessore / 2, fill=BIANCO) + kx = cx + w * posizione + r = h * 0.13 + d.ellipse([kx - r * 1.5, y - r * 1.5, kx + r * 1.5, y + r * 1.5], fill=VUOTO) + d.ellipse([kx - r, y - r, kx + r, y + r], fill=BIANCO) + + +def main(): + DEST.mkdir(parents=True, exist_ok=True) + disegni = ( + ("quick-boost.png", fiamma, 0.58), + ("quick-away.png", casa, 0.74), + ("quick-commands.png", comandi, 0.82), + ) + lato = BASE * 0.72 # margine generoso: il tasto ha il suo + for nome, disegno, strettezza in disegni: + img = nuova() + d = ImageDraw.Draw(img) + disegno(d, BASE / 2, BASE / 2, lato * strettezza, lato) + img = img.resize((OUT, OUT), Image.LANCZOS) + img.save(DEST / nome) + print("scritto", (DEST / nome).relative_to(DEST.parent.parent)) + + +if __name__ == "__main__": + main() diff --git a/scripts/render_mockups.py b/scripts/render_mockups.py index 95f2d1f..c003c0d 100644 --- a/scripts/render_mockups.py +++ b/scripts/render_mockups.py @@ -503,12 +503,20 @@ def main_screen() -> Image.Image: # ---- azioni rapide ----------------------------------------------------- # y = section_header(d, y, T("quick_actions")) bw = (W - 2 * M - 2 * gap) // 3 + # icona a sinistra e testo di fianco, come fa il pulsante Silica + glifi = ("quick-boost.png", "quick-away.png", "quick-commands.png") for i, testo in enumerate((T("boost"), T("away_btn"), T("commands"))): x = M + i * (bw + gap) d.rounded_rectangle([x, y, x + bw, y + 74], radius=18, fill=BTN_BG, outline=BTN_EDGE, width=2) + icona = Image.open(OUT.parent.parent / "qml" / "icons" / glifi[i]).convert("RGBA") + lato = 42 + icona = icona.resize((lato, lato), Image.LANCZOS) tw = d.textlength(testo, font=font(24)) - d.text((x + (bw - tw) / 2, y + 23), testo, font=font(24), fill=PRIMARY) + blocco = lato + 12 + tw + x0 = x + (bw - blocco) / 2 + img.paste(icona, (int(x0), int(y + (74 - lato) / 2)), icona) + d.text((x0 + lato + 12, y + 23), testo, font=font(24), fill=PRIMARY) y += 104 # ---- letture di contorno ----------------------------------------------- # diff --git a/src/main.cpp b/src/main.cpp index 4d880f1..eabbbcd 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.6")); + app->setApplicationVersion(QStringLiteral("0.6.7")); 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.6 build" << __DATE__ << __TIME__; + qDebug() << "harbour-aira v0.6.7 build" << __DATE__ << __TIME__; view->rootContext()->setContextProperty(QStringLiteral("appSettings"), &settings); view->rootContext()->setContextProperty(QStringLiteral("api"), &api);