Merge pull request 'feat(P4): Letzte Bezüge als Tabelle statt Textblock (1.8.2)' (#62) from feat/statistik-tabelle into main
This commit was merged in pull request #62.
This commit is contained in:
@@ -1,3 +1,16 @@
|
|||||||
|
Version 1.8.2:
|
||||||
|
- Die Liste der letzten Bezüge auf der Statistikseite ist jetzt eine Tabelle mit festen
|
||||||
|
Spalten: Zeitpunkt, Dauer, Gewicht und gegebenenfalls die Kennzeichnung „kalt".
|
||||||
|
- Vorher war es ein einziges mehrzeiliges Textfeld, in dem die Werte durch Leerzeichen
|
||||||
|
getrennt aneinandergereiht standen. Weil Zahlen unterschiedlich breit sind, stand nichts
|
||||||
|
untereinander - zwei Bezüge ließen sich nicht vergleichen, ohne jede Zeile einzeln zu
|
||||||
|
lesen. Genau das sah unruhig aus.
|
||||||
|
- Ziffern haben jetzt gleiche Breite, jede zweite Zeile ist leicht abgesetzt, und eine
|
||||||
|
Kopfzeile benennt die Spalten. Wurde ohne Waage bezogen, bleibt die Gewichtsspalte leer,
|
||||||
|
statt eine Null vorzutäuschen.
|
||||||
|
- Die Zeilen werden einmal angelegt und danach nur gefüllt oder ausgeblendet, statt bei
|
||||||
|
jeder Aktualisierung neu zu entstehen.
|
||||||
|
|
||||||
Version 1.8.1:
|
Version 1.8.1:
|
||||||
- Die Bezugsanzeige ist da, und zwar für alle vier Darstellungen gemeinsam: Sobald ein
|
- Die Bezugsanzeige ist da, und zwar für alle vier Darstellungen gemeinsam: Sobald ein
|
||||||
Bezug oder Dampfbezug läuft, schrumpft der Temperaturbereich und die untere Zeile wächst.
|
Bezug oder Dampfbezug läuft, schrumpft der Temperaturbereich und die untere Zeile wächst.
|
||||||
|
|||||||
+114
-23
@@ -359,7 +359,6 @@ static String g_selProfile;
|
|||||||
|
|
||||||
// Statistik (Nutzungsverlauf)
|
// Statistik (Nutzungsverlauf)
|
||||||
static lv_obj_t* g_statSummary = nullptr; // Aggregate (Heute/Woche/Gesamt)
|
static lv_obj_t* g_statSummary = nullptr; // Aggregate (Heute/Woche/Gesamt)
|
||||||
static lv_obj_t* g_statList = nullptr; // Liste der letzten Einzelbezuege
|
|
||||||
|
|
||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
// Hilfen
|
// Hilfen
|
||||||
@@ -578,6 +577,112 @@ static void grid_at(lv_obj_t* c, uint8_t col, uint8_t row, uint8_t col_span = 1)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------------
|
||||||
|
// Liste der letzten Bezuege
|
||||||
|
//
|
||||||
|
// Frueher war das EIN mehrzeiliges Textfeld: Zeitpunkt, Dauer und Gewicht standen in einer
|
||||||
|
// Zeile aneinandergereiht, durch Leerzeichen getrennt. Weil die Zahlen unterschiedlich
|
||||||
|
// breit sind, stand nichts untereinander - man konnte zwei Bezuege nicht vergleichen, ohne
|
||||||
|
// jede Zeile einzeln zu lesen.
|
||||||
|
//
|
||||||
|
// Jetzt eine Tabelle mit festen Spalten und Ziffern gleicher Breite. Die Zeilen werden
|
||||||
|
// einmal angelegt und danach nur noch gefuellt oder ausgeblendet - eine Statistikseite,
|
||||||
|
// die bei jeder Aktualisierung Dutzende Objekte neu anlegt, waere unnoetig teuer.
|
||||||
|
// ------------------------------------------------------------------------------------
|
||||||
|
#define STAT_ROW_COUNT 12
|
||||||
|
|
||||||
|
static lv_obj_t* g_statRow[STAT_ROW_COUNT] = {};
|
||||||
|
static lv_obj_t* g_statTime[STAT_ROW_COUNT] = {};
|
||||||
|
static lv_obj_t* g_statDur[STAT_ROW_COUNT] = {};
|
||||||
|
static lv_obj_t* g_statWgt[STAT_ROW_COUNT] = {};
|
||||||
|
static lv_obj_t* g_statCold[STAT_ROW_COUNT] = {};
|
||||||
|
static lv_obj_t* g_statEmpty = nullptr;
|
||||||
|
|
||||||
|
static lv_obj_t* stat_cell(lv_obj_t* row, int32_t width, bool rechts, lv_color_t col) {
|
||||||
|
lv_obj_t* l = lv_label_create(row);
|
||||||
|
if (width > 0) lv_obj_set_width(l, width);
|
||||||
|
else lv_obj_set_flex_grow(l, 1);
|
||||||
|
lv_obj_set_style_text_color(l, col, 0);
|
||||||
|
lv_obj_set_style_text_align(l, rechts ? LV_TEXT_ALIGN_RIGHT : LV_TEXT_ALIGN_LEFT, 0);
|
||||||
|
lv_label_set_text(l, "");
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stat_list_build(lv_obj_t* parent) {
|
||||||
|
lv_obj_t* card = th_card(parent);
|
||||||
|
lv_obj_set_width(card, LV_PCT(100));
|
||||||
|
lv_obj_set_height(card, LV_SIZE_CONTENT);
|
||||||
|
lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN);
|
||||||
|
lv_obj_set_style_pad_row(card, 0, 0);
|
||||||
|
lv_obj_clear_flag(card, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
|
||||||
|
// Kopfzeile - benennt die Spalten, damit die Zahlen ohne Einheit auskommen
|
||||||
|
lv_obj_t* head = lv_obj_create(card);
|
||||||
|
lv_obj_set_width(head, LV_PCT(100));
|
||||||
|
lv_obj_set_height(head, LV_SIZE_CONTENT);
|
||||||
|
lv_obj_set_style_bg_opa(head, LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_border_width(head, 0, 0);
|
||||||
|
lv_obj_set_style_pad_all(head, 4, 0);
|
||||||
|
lv_obj_set_style_pad_column(head, 10, 0);
|
||||||
|
lv_obj_set_flex_flow(head, LV_FLEX_FLOW_ROW);
|
||||||
|
lv_obj_clear_flag(head, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
stat_cell(head, 0, false, COL_TEXT_MUT);
|
||||||
|
lv_label_set_text(lv_obj_get_child(head, 0), "Zeitpunkt");
|
||||||
|
lv_label_set_text(stat_cell(head, 96, true, COL_TEXT_MUT), "Dauer");
|
||||||
|
lv_label_set_text(stat_cell(head, 96, true, COL_TEXT_MUT), "Gewicht");
|
||||||
|
lv_label_set_text(stat_cell(head, 58, true, COL_TEXT_MUT), "");
|
||||||
|
|
||||||
|
for (int i = 0; i < STAT_ROW_COUNT; i++) {
|
||||||
|
lv_obj_t* r = lv_obj_create(card);
|
||||||
|
lv_obj_set_width(r, LV_PCT(100));
|
||||||
|
lv_obj_set_height(r, LV_SIZE_CONTENT);
|
||||||
|
// Jede zweite Zeile leicht abgesetzt - fuehrt das Auge ueber die Zeile, ohne dass
|
||||||
|
// dafuer Trennlinien noetig waeren.
|
||||||
|
lv_obj_set_style_bg_color(r, lv_color_hex(0x242424), 0);
|
||||||
|
lv_obj_set_style_bg_opa(r, (i % 2) ? LV_OPA_COVER : LV_OPA_TRANSP, 0);
|
||||||
|
lv_obj_set_style_border_width(r, 0, 0);
|
||||||
|
lv_obj_set_style_radius(r, 5, 0);
|
||||||
|
lv_obj_set_style_pad_all(r, 5, 0);
|
||||||
|
lv_obj_set_style_pad_column(r, 10, 0);
|
||||||
|
lv_obj_set_flex_flow(r, LV_FLEX_FLOW_ROW);
|
||||||
|
lv_obj_set_flex_align(r, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
lv_obj_clear_flag(r, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
g_statTime[i] = stat_cell(r, 0, false, COL_TEXT_DIM);
|
||||||
|
g_statDur[i] = stat_cell(r, 96, true, COL_TEXT);
|
||||||
|
g_statWgt[i] = stat_cell(r, 96, true, COL_TEXT);
|
||||||
|
g_statCold[i] = stat_cell(r, 58, true, lv_color_hex(0x80cbc4));
|
||||||
|
g_statRow[i] = r;
|
||||||
|
lv_obj_add_flag(r, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_statEmpty = lv_label_create(card);
|
||||||
|
lv_obj_set_width(g_statEmpty, LV_PCT(100));
|
||||||
|
lv_label_set_text(g_statEmpty, "Noch keine Bezüge aufgezeichnet.");
|
||||||
|
lv_obj_set_style_text_color(g_statEmpty, COL_TEXT_MUT, 0);
|
||||||
|
lv_obj_set_style_pad_all(g_statEmpty, 5, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eine Zeile fuellen. 'w' unter 0 bedeutet: ohne Waage bezogen, dann bleibt die Spalte leer
|
||||||
|
// statt eine Null vorzutaeuschen.
|
||||||
|
static void stat_list_set(int i, const char* dt, float d, float w, bool kalt) {
|
||||||
|
if (i < 0 || i >= STAT_ROW_COUNT || !g_statRow[i]) return;
|
||||||
|
lv_obj_remove_flag(g_statRow[i], LV_OBJ_FLAG_HIDDEN);
|
||||||
|
lv_label_set_text(g_statTime[i], dt);
|
||||||
|
lv_label_set_text_fmt(g_statDur[i], "%.1f s", d);
|
||||||
|
if (w >= 0.0f) lv_label_set_text_fmt(g_statWgt[i], "%.1f g", w);
|
||||||
|
else lv_label_set_text(g_statWgt[i], "–");
|
||||||
|
lv_label_set_text(g_statCold[i], kalt ? "kalt" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stat_list_finish(int used) {
|
||||||
|
for (int i = used; i < STAT_ROW_COUNT; i++)
|
||||||
|
if (g_statRow[i]) lv_obj_add_flag(g_statRow[i], LV_OBJ_FLAG_HIDDEN);
|
||||||
|
if (g_statEmpty) {
|
||||||
|
if (used > 0) lv_obj_add_flag(g_statEmpty, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
else lv_obj_remove_flag(g_statEmpty, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void scroll_ohne_nachlauf(lv_obj_t* o) {
|
static void scroll_ohne_nachlauf(lv_obj_t* o) {
|
||||||
#if JC_PANEL_TYPE == WS_PANEL_7H
|
#if JC_PANEL_TYPE == WS_PANEL_7H
|
||||||
lv_obj_clear_flag(o, LV_OBJ_FLAG_SCROLL_MOMENTUM);
|
lv_obj_clear_flag(o, LV_OBJ_FLAG_SCROLL_MOMENTUM);
|
||||||
@@ -2922,11 +3027,12 @@ static void build_info(lv_obj_t* p) {
|
|||||||
static void stats_request() {
|
static void stats_request() {
|
||||||
if (!g_client) return;
|
if (!g_client) return;
|
||||||
if (g_statSummary) label_set_if_changed(g_statSummary, "Lade Statistik...");
|
if (g_statSummary) label_set_if_changed(g_statSummary, "Lade Statistik...");
|
||||||
if (g_statList) label_set_if_changed(g_statList, "");
|
stat_list_finish(0);
|
||||||
g_client->sendGetUsageStats();
|
g_client->sendGetUsageStats();
|
||||||
}
|
}
|
||||||
static void stats_refresh_cb(lv_event_t*) { stats_request(); }
|
static void stats_refresh_cb(lv_event_t*) { stats_request(); }
|
||||||
|
|
||||||
|
|
||||||
static void build_stats(lv_obj_t* p) {
|
static void build_stats(lv_obj_t* p) {
|
||||||
lv_obj_set_scroll_dir(p, LV_DIR_VER);
|
lv_obj_set_scroll_dir(p, LV_DIR_VER);
|
||||||
lv_obj_set_scrollbar_mode(p, LV_SCROLLBAR_MODE_AUTO);
|
lv_obj_set_scrollbar_mode(p, LV_SCROLLBAR_MODE_AUTO);
|
||||||
@@ -2951,15 +3057,7 @@ static void build_stats(lv_obj_t* p) {
|
|||||||
|
|
||||||
// Letzte Einzelbezuege
|
// Letzte Einzelbezuege
|
||||||
section_title(p, "Letzte Bezüge");
|
section_title(p, "Letzte Bezüge");
|
||||||
lv_obj_t* lc = th_card(p);
|
stat_list_build(p);
|
||||||
lv_obj_set_width(lc, LV_PCT(100));
|
|
||||||
lv_obj_set_height(lc, LV_SIZE_CONTENT);
|
|
||||||
lv_obj_set_flex_flow(lc, LV_FLEX_FLOW_COLUMN);
|
|
||||||
lv_obj_clear_flag(lc, LV_OBJ_FLAG_SCROLLABLE);
|
|
||||||
g_statList = lv_label_create(lc);
|
|
||||||
lv_obj_set_width(g_statList, LV_PCT(100));
|
|
||||||
lv_label_set_text(g_statList, "");
|
|
||||||
lv_obj_set_style_text_color(g_statList, COL_TEXT_DIM, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================================
|
// =====================================================================================
|
||||||
@@ -4424,13 +4522,13 @@ void ui_set_usage_stats(const String& json) {
|
|||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
if (deserializeJson(doc, json)) {
|
if (deserializeJson(doc, json)) {
|
||||||
label_set_if_changed(g_statSummary, "Statistik konnte nicht gelesen werden.");
|
label_set_if_changed(g_statSummary, "Statistik konnte nicht gelesen werden.");
|
||||||
if (g_statList) label_set_if_changed(g_statList, "");
|
stat_list_finish(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bool avail = doc["available"] | false;
|
bool avail = doc["available"] | false;
|
||||||
if (!avail) {
|
if (!avail) {
|
||||||
label_set_if_changed(g_statSummary, "Noch keine Nutzungsdaten vorhanden.");
|
label_set_if_changed(g_statSummary, "Noch keine Nutzungsdaten vorhanden.");
|
||||||
if (g_statList) label_set_if_changed(g_statList, "");
|
stat_list_finish(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4439,8 +4537,7 @@ void ui_set_usage_stats(const String& json) {
|
|||||||
long week = doc["week"] | 0;
|
long week = doc["week"] | 0;
|
||||||
bool partial = doc["partial"] | false;
|
bool partial = doc["partial"] | false;
|
||||||
|
|
||||||
// Liste + Mittelwert der angezeigten Bezuege aufbauen
|
// Mittelwert der angezeigten Bezuege; die Liste fuellt stat_list_set()
|
||||||
String l;
|
|
||||||
int n = 0;
|
int n = 0;
|
||||||
float durSum = 0.0f;
|
float durSum = 0.0f;
|
||||||
int nDur = 0; // Anzahl der Bezuege, die in den Mittelwert eingehen (ohne kalte)
|
int nDur = 0; // Anzahl der Bezuege, die in den Mittelwert eingehen (ohne kalte)
|
||||||
@@ -4449,10 +4546,7 @@ void ui_set_usage_stats(const String& json) {
|
|||||||
float d = v["d"] | 0.0f;
|
float d = v["d"] | 0.0f;
|
||||||
float w = v["w"] | -1.0f;
|
float w = v["w"] | -1.0f;
|
||||||
bool cold = v["cx"] | false; // kalte Extraktion (fehlt bei S3-FW < 5.3.0)
|
bool cold = v["cx"] | false; // kalte Extraktion (fehlt bei S3-FW < 5.3.0)
|
||||||
if (n > 0) l += "\n";
|
stat_list_set(n, dt, d, w, cold);
|
||||||
l += String(dt) + " " + String(d, 1) + " s";
|
|
||||||
if (w >= 0.0f) l += " " + String(w, 1) + " g";
|
|
||||||
if (cold) l += " (kalt)";
|
|
||||||
// Minutenlange kalte Bezuege wuerden den Mittelwert unbrauchbar machen -> raus
|
// Minutenlange kalte Bezuege wuerden den Mittelwert unbrauchbar machen -> raus
|
||||||
// (gleiche Regel wie in der S3-Statistik)
|
// (gleiche Regel wie in der S3-Statistik)
|
||||||
if (!cold) { durSum += d; nDur++; }
|
if (!cold) { durSum += d; nDur++; }
|
||||||
@@ -4470,10 +4564,7 @@ void ui_set_usage_stats(const String& json) {
|
|||||||
}
|
}
|
||||||
label_set_if_changed(g_statSummary, s.c_str());
|
label_set_if_changed(g_statSummary, s.c_str());
|
||||||
|
|
||||||
if (g_statList) {
|
stat_list_finish(n);
|
||||||
if (n == 0) l = "Keine Einzelbezüge vorhanden.";
|
|
||||||
label_set_if_changed(g_statList, l.c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ui_set_wifi_networks(const String& json) {
|
void ui_set_wifi_networks(const String& json) {
|
||||||
|
|||||||
Reference in New Issue
Block a user