diff --git a/P4_Display_Firmware/Changelog.txt b/P4_Display_Firmware/Changelog.txt index 31459b4..b5d382a 100644 --- a/P4_Display_Firmware/Changelog.txt +++ b/P4_Display_Firmware/Changelog.txt @@ -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: - 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. diff --git a/P4_Display_Firmware/ui.cpp b/P4_Display_Firmware/ui.cpp index 553c88c..0345690 100644 --- a/P4_Display_Firmware/ui.cpp +++ b/P4_Display_Firmware/ui.cpp @@ -359,7 +359,6 @@ static String g_selProfile; // Statistik (Nutzungsverlauf) static lv_obj_t* g_statSummary = nullptr; // Aggregate (Heute/Woche/Gesamt) -static lv_obj_t* g_statList = nullptr; // Liste der letzten Einzelbezuege // ===================================================================================== // 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 +// ------------------------------------------------------------------------------------ +// 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) { #if JC_PANEL_TYPE == WS_PANEL_7H 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() { if (!g_client) return; 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(); } static void stats_refresh_cb(lv_event_t*) { stats_request(); } + static void build_stats(lv_obj_t* p) { lv_obj_set_scroll_dir(p, LV_DIR_VER); lv_obj_set_scrollbar_mode(p, LV_SCROLLBAR_MODE_AUTO); @@ -2951,15 +3057,7 @@ static void build_stats(lv_obj_t* p) { // Letzte Einzelbezuege section_title(p, "Letzte Bezüge"); - lv_obj_t* lc = th_card(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); + stat_list_build(p); } // ===================================================================================== @@ -4424,13 +4522,13 @@ void ui_set_usage_stats(const String& json) { JsonDocument doc; if (deserializeJson(doc, json)) { 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; } bool avail = doc["available"] | false; if (!avail) { label_set_if_changed(g_statSummary, "Noch keine Nutzungsdaten vorhanden."); - if (g_statList) label_set_if_changed(g_statList, ""); + stat_list_finish(0); return; } @@ -4439,8 +4537,7 @@ void ui_set_usage_stats(const String& json) { long week = doc["week"] | 0; bool partial = doc["partial"] | false; - // Liste + Mittelwert der angezeigten Bezuege aufbauen - String l; + // Mittelwert der angezeigten Bezuege; die Liste fuellt stat_list_set() int n = 0; float durSum = 0.0f; 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 w = v["w"] | -1.0f; bool cold = v["cx"] | false; // kalte Extraktion (fehlt bei S3-FW < 5.3.0) - if (n > 0) l += "\n"; - l += String(dt) + " " + String(d, 1) + " s"; - if (w >= 0.0f) l += " " + String(w, 1) + " g"; - if (cold) l += " (kalt)"; + stat_list_set(n, dt, d, w, cold); // Minutenlange kalte Bezuege wuerden den Mittelwert unbrauchbar machen -> raus // (gleiche Regel wie in der S3-Statistik) 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()); - if (g_statList) { - if (n == 0) l = "Keine Einzelbezüge vorhanden."; - label_set_if_changed(g_statList, l.c_str()); - } + stat_list_finish(n); } void ui_set_wifi_networks(const String& json) {