0.7.0: assenza con date, conto alla rovescia del boost, catalogo rifatto
- AwayDialog.qml: popup con inizio e fine (Dialog, ValueButton, DatePickerDialog: componenti e proprieta' verificati nella guida Silica dell'SDK). Il tasto Assenza apre il popup invece di mandare il comando; - conto alla rovescia 30:00 -> 00:00 in Home e in copertina, con i secondi dell'impianto (AiraJson::countdown + timer che si riallinea a ogni lettura); - commandPayload accetta campi extra (start/end); - catalogo: lupdate leggeva solo i C++ e marcava 151 voci QML come obsolete. Ora src e qml espliciti: 197 stringhe, 0 non tradotte; - versione 0.7.0 (spec, main.cpp), PROTOCOL, README/PIANO, post per il forum.
This commit is contained in:
+17
-1
@@ -175,15 +175,31 @@ QString monthLabel(const QString &yearMonth)
|
||||
return QStringLiteral("%1 %2").arg(QLocale().monthName(mese, QLocale::LongFormat)).arg(anno);
|
||||
}
|
||||
|
||||
QByteArray commandPayload(const QString &id, const QVariant &value)
|
||||
QByteArray commandPayload(const QString &id, const QVariant &value, const QVariantMap &extra)
|
||||
{
|
||||
QJsonObject o;
|
||||
o.insert(QStringLiteral("id"), id);
|
||||
if (value.isValid() && !value.isNull())
|
||||
o.insert(QStringLiteral("value"), QJsonValue::fromVariant(value));
|
||||
// Campi in piu' (per esempio start ed end dell'assenza): li passa al bridge
|
||||
// cosi' come sono, saltando quelli vuoti.
|
||||
for (QVariantMap::const_iterator it = extra.constBegin(); it != extra.constEnd(); ++it) {
|
||||
const QVariant v = it.value();
|
||||
if (v.isValid() && !v.isNull() && !v.toString().isEmpty())
|
||||
o.insert(it.key(), QJsonValue::fromVariant(v));
|
||||
}
|
||||
return QJsonDocument(o).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
QString countdown(int seconds)
|
||||
{
|
||||
if (seconds < 0)
|
||||
seconds = 0;
|
||||
return QStringLiteral("%1:%2")
|
||||
.arg(seconds / 60, 2, 10, QLatin1Char('0'))
|
||||
.arg(seconds % 60, 2, 10, QLatin1Char('0'));
|
||||
}
|
||||
|
||||
QString errorMessage(const QByteArray &body, int httpStatus)
|
||||
{
|
||||
QJsonParseError err;
|
||||
|
||||
+8
-2
@@ -59,8 +59,14 @@ QString formatMoney(const QVariant &value, const QString ¤cyCode);
|
||||
QString monthLabel(const QString &yearMonth);
|
||||
|
||||
// Corpo JSON per POST /command. value viene omesso se nullo (i comandi
|
||||
// semplici non hanno parametri).
|
||||
QByteArray commandPayload(const QString &id, const QVariant &value);
|
||||
// semplici non hanno parametri). `extra` aggiunge campi in piu' (per esempio
|
||||
// start/end dell'assenza): passano al bridge saltando i vuoti.
|
||||
QByteArray commandPayload(const QString &id, const QVariant &value,
|
||||
const QVariantMap &extra = QVariantMap());
|
||||
|
||||
// Conto alla rovescia leggibile da secondi: 1800 -> "30:00", 65 -> "01:05",
|
||||
// 0 -> "00:00". Oltre l'ora i minuti continuano (240 min -> "240:00").
|
||||
QString countdown(int seconds);
|
||||
|
||||
// Messaggio d'errore leggibile da una risposta del bridge
|
||||
// ({"ok":false,"error":"..."}), con fallback sullo stato HTTP.
|
||||
|
||||
+51
-2
@@ -53,6 +53,14 @@ ApiClient::ApiClient(Settings *settings, QObject *parent)
|
||||
m_refreshTimer->start();
|
||||
});
|
||||
|
||||
// Un secondo alla volta mentre il boost e' in corso: i secondi veri li da'
|
||||
// il bridge a ogni rilettura (ogni 30 s), qui si scorre in mezzo.
|
||||
m_boostActive = false;
|
||||
m_boostRemaining = 0;
|
||||
m_boostTimer = new QTimer(this);
|
||||
m_boostTimer->setInterval(1000);
|
||||
connect(m_boostTimer, &QTimer::timeout, this, &ApiClient::tickBoost);
|
||||
|
||||
if (m_settings) {
|
||||
// La UI deve riflettere subito il passaggio da "non configurato" a
|
||||
// "configurato" quando si salvano indirizzo o token.
|
||||
@@ -186,6 +194,7 @@ void ApiClient::refresh()
|
||||
return;
|
||||
}
|
||||
m_summary = summary;
|
||||
setBoostFromSummary(summary);
|
||||
if (m_stale) {
|
||||
m_stale = false;
|
||||
emit staleChanged();
|
||||
@@ -295,14 +304,14 @@ void ApiClient::refreshStats()
|
||||
});
|
||||
}
|
||||
|
||||
void ApiClient::sendCommand(const QString &id, const QVariant &value)
|
||||
void ApiClient::sendCommand(const QString &id, const QVariant &value, const QVariantMap &extra)
|
||||
{
|
||||
if (!configured()) {
|
||||
emit commandFinished(false, tr("Set the bridge address and token"));
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray payload = AiraJson::commandPayload(id, value);
|
||||
const QByteArray payload = AiraJson::commandPayload(id, value, extra);
|
||||
qDebug() << "[api] comando:" << id; // mai i valori: qui potrebbe passare altro
|
||||
|
||||
QNetworkReply *reply = send("POST", QStringLiteral("/command"), payload, 30);
|
||||
@@ -332,3 +341,43 @@ void ApiClient::sendCommand(const QString &id, const QVariant &value)
|
||||
refresh(); // i valori letti cambiano: rileggo
|
||||
});
|
||||
}
|
||||
|
||||
QString ApiClient::boostCountdown() const
|
||||
{
|
||||
return AiraJson::countdown(m_boostRemaining);
|
||||
}
|
||||
|
||||
// I secondi veri arrivano dal riassunto: il bridge li ricava dalla FINE del
|
||||
// boost letta dall'impianto. Il timer li fa solo scorrere fra una lettura e
|
||||
// l'altra, cosi' il conto non riparte da capo quando si riapre la app (e non
|
||||
// sbaglia se il boost e' stato avviato da un'altra parte).
|
||||
void ApiClient::setBoostFromSummary(const QVariantMap &summary)
|
||||
{
|
||||
const int restanti = summary.value(QStringLiteral("boost_remaining_seconds")).toInt();
|
||||
const bool attivo = restanti > 0;
|
||||
|
||||
if (!attivo && !m_boostActive && m_boostRemaining == 0) {
|
||||
m_boostTimer->stop(); // niente da mostrare: non disturbo
|
||||
return;
|
||||
}
|
||||
|
||||
m_boostRemaining = restanti;
|
||||
m_boostActive = attivo;
|
||||
if (attivo)
|
||||
m_boostTimer->start();
|
||||
else
|
||||
m_boostTimer->stop();
|
||||
emit boostChanged();
|
||||
}
|
||||
|
||||
void ApiClient::tickBoost()
|
||||
{
|
||||
if (m_boostRemaining > 0)
|
||||
--m_boostRemaining;
|
||||
if (m_boostRemaining <= 0) {
|
||||
m_boostRemaining = 0;
|
||||
m_boostActive = false;
|
||||
m_boostTimer->stop(); // arrivato a 00:00
|
||||
}
|
||||
emit boostChanged();
|
||||
}
|
||||
|
||||
+19
-1
@@ -35,6 +35,13 @@ class ApiClient : public QObject
|
||||
// messaggio e' lo stesso ovunque si stia.
|
||||
Q_PROPERTY(QString notice READ notice NOTIFY noticeChanged)
|
||||
Q_PROPERTY(bool noticeIsError READ noticeIsError NOTIFY noticeChanged)
|
||||
// Conto alla rovescia del boost ACS (richiesta dell'utente: da 30:00 a
|
||||
// 00:00). I secondi arrivano dal bridge, che li calcola dalla FINE del
|
||||
// boost letta dall'impianto: cosi' il numero non dipende dal momento in cui
|
||||
// l'app ha visto partire il comando. Qui scorrono di secondo in secondo.
|
||||
Q_PROPERTY(bool boostActive READ boostActive NOTIFY boostChanged)
|
||||
Q_PROPERTY(int boostRemaining READ boostRemaining NOTIFY boostChanged)
|
||||
Q_PROPERTY(QString boostCountdown READ boostCountdown NOTIFY boostChanged)
|
||||
|
||||
public:
|
||||
explicit ApiClient(Settings *settings, QObject *parent = 0);
|
||||
@@ -48,6 +55,9 @@ public:
|
||||
bool hasData() const { return !m_summary.isEmpty(); }
|
||||
QString notice() const { return m_notice; }
|
||||
bool noticeIsError() const { return m_noticeIsError; }
|
||||
bool boostActive() const { return m_boostActive; }
|
||||
int boostRemaining() const { return m_boostRemaining; }
|
||||
QString boostCountdown() const;
|
||||
|
||||
// Stato leggibile in una riga (stateShort) e per esteso (stateLong), piu'
|
||||
// il livello da cui la UI prende il colore. Stanno in C++ perche' sono
|
||||
@@ -66,7 +76,9 @@ public:
|
||||
Q_INVOKABLE void testConnection();
|
||||
|
||||
// Invia un comando: sendCommand("set_hot_water_target", 55).
|
||||
Q_INVOKABLE void sendCommand(const QString &id, const QVariant &value = QVariant());
|
||||
// `extra` aggiunge campi al corpo JSON (start/end dell'assenza).
|
||||
Q_INVOKABLE void sendCommand(const QString &id, const QVariant &value = QVariant(),
|
||||
const QVariantMap &extra = QVariantMap());
|
||||
|
||||
// Formattazione per la UI: unica fonte di verita' in C++ (testata in
|
||||
// tests/core_test.cpp), niente duplicazione di logica nel QML.
|
||||
@@ -82,6 +94,7 @@ signals:
|
||||
void staleChanged();
|
||||
void lastUpdateChanged();
|
||||
void noticeChanged();
|
||||
void boostChanged();
|
||||
void summaryChanged(const QVariantMap &summary);
|
||||
void statsChanged(const QVariantMap &stats);
|
||||
void statsRefreshed(bool ok, const QString &message);
|
||||
@@ -96,6 +109,8 @@ private:
|
||||
void setBusy(bool value);
|
||||
// Mostra un messaggio in fondo alla pagina per qualche secondo.
|
||||
void setNotice(const QString &message, bool isError);
|
||||
void setBoostFromSummary(const QVariantMap &summary);
|
||||
void tickBoost();
|
||||
|
||||
Settings *m_settings;
|
||||
QNetworkAccessManager m_nam;
|
||||
@@ -108,6 +123,9 @@ private:
|
||||
QTimer *m_refreshTimer;
|
||||
QVariantMap m_summary;
|
||||
QVariantMap m_stats;
|
||||
bool m_boostActive;
|
||||
int m_boostRemaining;
|
||||
QTimer *m_boostTimer;
|
||||
};
|
||||
|
||||
#endif // APICLIENT_H
|
||||
|
||||
+2
-2
@@ -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.9"));
|
||||
app->setApplicationVersion(QStringLiteral("0.7.0"));
|
||||
|
||||
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.9 build" << __DATE__ << __TIME__;
|
||||
qDebug() << "harbour-aira v0.7.0 build" << __DATE__ << __TIME__;
|
||||
|
||||
view->rootContext()->setContextProperty(QStringLiteral("appSettings"), &settings);
|
||||
view->rootContext()->setContextProperty(QStringLiteral("api"), &api);
|
||||
|
||||
Reference in New Issue
Block a user