mirror of
https://github.com/skoelle/wt32sc01-dashboard.git
synced 2026-09-17 17:30:25 +00:00
feat: add data manager system for background weather/calendar fetching
This commit is contained in:
@@ -0,0 +1,93 @@
|
|||||||
|
#include "data_manager.h"
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr unsigned long REFRESH_INTERVAL_MS = 10UL * 60UL * 1000UL;
|
||||||
|
constexpr uint32_t TASK_STACK_WORDS = 16384; // 64 KB — JSON parsing needs headroom
|
||||||
|
|
||||||
|
// --- Shared state between background task and main loop ---
|
||||||
|
// The task is the only writer of s_weather/s_calendar; the main loop is the
|
||||||
|
// only reader. Access to the data structs is serialized via s_mux (a
|
||||||
|
// portMUX_TYPE critical section which, on ESP32, suspends task switches on
|
||||||
|
// the current core). Simple flag/counter reads (bool/uint32_t) are atomic
|
||||||
|
// on 32-bit Xtensa and only need `volatile`.
|
||||||
|
static WeatherData s_weather;
|
||||||
|
static CalendarData s_calendar;
|
||||||
|
static volatile bool s_weatherOk = false;
|
||||||
|
static volatile bool s_calendarOk = false;
|
||||||
|
static volatile bool s_loading = false;
|
||||||
|
static volatile uint32_t s_dataVersion = 0;
|
||||||
|
static volatile unsigned long s_lastFetchMs = 0;
|
||||||
|
static volatile bool s_refreshRequested = false;
|
||||||
|
static portMUX_TYPE s_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
|
||||||
|
static void dataFetchTask(void *param) {
|
||||||
|
(void)param;
|
||||||
|
while (true) {
|
||||||
|
bool needRefresh = s_refreshRequested ||
|
||||||
|
s_lastFetchMs == 0 ||
|
||||||
|
(millis() - s_lastFetchMs) >= REFRESH_INTERVAL_MS;
|
||||||
|
|
||||||
|
if (needRefresh) {
|
||||||
|
s_refreshRequested = false;
|
||||||
|
s_loading = true;
|
||||||
|
|
||||||
|
WeatherData w = fetchWeather();
|
||||||
|
CalendarData c = fetchCalendar();
|
||||||
|
|
||||||
|
portENTER_CRITICAL(&s_mux);
|
||||||
|
if (w.valid) {
|
||||||
|
s_weather = std::move(w);
|
||||||
|
s_weatherOk = true;
|
||||||
|
} else {
|
||||||
|
s_weatherOk = false;
|
||||||
|
}
|
||||||
|
if (c.valid) {
|
||||||
|
s_calendar = std::move(c);
|
||||||
|
s_calendarOk = true;
|
||||||
|
} else {
|
||||||
|
s_calendarOk = false;
|
||||||
|
}
|
||||||
|
s_dataVersion++;
|
||||||
|
s_lastFetchMs = millis();
|
||||||
|
s_loading = false;
|
||||||
|
portEXIT_CRITICAL(&s_mux);
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void dataManager_begin() {
|
||||||
|
s_lastFetchMs = 0; // ensure first iteration fetches immediately
|
||||||
|
xTaskCreatePinnedToCore(dataFetchTask, "dataFetch", TASK_STACK_WORDS,
|
||||||
|
nullptr, 1, nullptr, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dataManager_tick() {}
|
||||||
|
|
||||||
|
void dataManager_triggerRefresh() {
|
||||||
|
s_refreshRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dataManager_getWeather(WeatherData &out) {
|
||||||
|
portENTER_CRITICAL(&s_mux);
|
||||||
|
out = s_weather;
|
||||||
|
portEXIT_CRITICAL(&s_mux);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dataManager_getCalendar(CalendarData &out) {
|
||||||
|
portENTER_CRITICAL(&s_mux);
|
||||||
|
out = s_calendar;
|
||||||
|
portEXIT_CRITICAL(&s_mux);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dataManager_isWeatherOk() { return s_weatherOk; }
|
||||||
|
bool dataManager_isCalendarOk() { return s_calendarOk; }
|
||||||
|
bool dataManager_isLoading() { return s_loading; }
|
||||||
|
uint32_t dataManager_dataVersion() { return s_dataVersion; }
|
||||||
|
unsigned long dataManager_lastFetchMs() { return s_lastFetchMs; }
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "api/weather_api.h"
|
||||||
|
#include "api/calendar_api.h"
|
||||||
|
|
||||||
|
// Centralized async data manager for weather + calendar.
|
||||||
|
//
|
||||||
|
// A background FreeRTOS task fetches weather and calendar data every 10
|
||||||
|
// minutes (or on manual trigger). Results are cached in memory and exposed
|
||||||
|
// to the UI screens via thread-safe getters.
|
||||||
|
//
|
||||||
|
// Screens poll dataManager_dataVersion() in their tick_fn to detect changes
|
||||||
|
// and re-render from cache. Navigation between screens never blocks on a
|
||||||
|
// fetch — views always render instantly from whatever is cached.
|
||||||
|
|
||||||
|
// Create the background task and trigger the initial fetch. Call once in setup().
|
||||||
|
void dataManager_begin();
|
||||||
|
|
||||||
|
// No-op for now (background task runs independently). Reserved for future use.
|
||||||
|
void dataManager_tick();
|
||||||
|
|
||||||
|
// Request a manual refresh (non-blocking). The background task picks this up
|
||||||
|
// within ~1 second.
|
||||||
|
void dataManager_triggerRefresh();
|
||||||
|
|
||||||
|
// Thread-safe copy of the cached weather/calendar data into `out`.
|
||||||
|
void dataManager_getWeather(WeatherData &out);
|
||||||
|
void dataManager_getCalendar(CalendarData &out);
|
||||||
|
|
||||||
|
// Status flags (safe to read from the main loop without a mutex).
|
||||||
|
bool dataManager_isWeatherOk();
|
||||||
|
bool dataManager_isCalendarOk();
|
||||||
|
bool dataManager_isLoading();
|
||||||
|
uint32_t dataManager_dataVersion();
|
||||||
|
unsigned long dataManager_lastFetchMs();
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <secrets.h>
|
#include <secrets.h>
|
||||||
#include <theme.h>
|
#include <theme.h>
|
||||||
#include "display/display_setup.h"
|
#include "display/display_setup.h"
|
||||||
|
#include "data/data_manager.h"
|
||||||
#include "ui/screen_base.h"
|
#include "ui/screen_base.h"
|
||||||
#include "ui/home_screen.h"
|
#include "ui/home_screen.h"
|
||||||
#include "ui/weather_detail_screen.h"
|
#include "ui/weather_detail_screen.h"
|
||||||
@@ -53,6 +54,9 @@ void setup() {
|
|||||||
display_init();
|
display_init();
|
||||||
connectWifi();
|
connectWifi();
|
||||||
|
|
||||||
|
// Start background data fetcher (weather + calendar every 10 min).
|
||||||
|
dataManager_begin();
|
||||||
|
|
||||||
// Build all four screens and wire navigation callbacks.
|
// Build all four screens and wire navigation callbacks.
|
||||||
screens[(int)ScreenId::HOME] = homeScreen_make();
|
screens[(int)ScreenId::HOME] = homeScreen_make();
|
||||||
screens[(int)ScreenId::WEATHER_DETAIL] = weatherDetailScreen_make();
|
screens[(int)ScreenId::WEATHER_DETAIL] = weatherDetailScreen_make();
|
||||||
@@ -73,6 +77,7 @@ void loop() {
|
|||||||
if (now - lastTickMs >= 5) {
|
if (now - lastTickMs >= 5) {
|
||||||
lastTickMs = now;
|
lastTickMs = now;
|
||||||
display_loop();
|
display_loop();
|
||||||
|
dataManager_tick();
|
||||||
screens[(int)current].tick();
|
screens[(int)current].tick();
|
||||||
|
|
||||||
// Inactivity: return to home after 5 minutes without input.
|
// Inactivity: return to home after 5 minutes without input.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "calendar_detail_screen.h"
|
#include "calendar_detail_screen.h"
|
||||||
#include "ui/widgets/back_button.h"
|
#include "ui/widgets/back_button.h"
|
||||||
#include "api/calendar_api.h"
|
#include "data/data_manager.h"
|
||||||
#include <theme.h>
|
#include <theme.h>
|
||||||
#include <text_utils.h>
|
#include <text_utils.h>
|
||||||
#include <date_utils.h>
|
#include <date_utils.h>
|
||||||
@@ -11,24 +11,28 @@ namespace {
|
|||||||
ScreenId (*g_navigate)(ScreenId) = nullptr;
|
ScreenId (*g_navigate)(ScreenId) = nullptr;
|
||||||
void on_back(lv_event_t *) { if (g_navigate) g_navigate(ScreenId::HOME); }
|
void on_back(lv_event_t *) { if (g_navigate) g_navigate(ScreenId::HOME); }
|
||||||
|
|
||||||
CalendarData lastCalendar;
|
|
||||||
bool ok = false;
|
|
||||||
lv_obj_t *list = nullptr;
|
lv_obj_t *list = nullptr;
|
||||||
|
|
||||||
void buildList() {
|
void buildList() {
|
||||||
if (!list) return;
|
if (!list) return;
|
||||||
lv_obj_clean(list);
|
lv_obj_clean(list);
|
||||||
|
|
||||||
|
CalendarData c;
|
||||||
|
dataManager_getCalendar(c);
|
||||||
|
bool ok = dataManager_isCalendarOk();
|
||||||
|
bool loading = dataManager_isLoading();
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
lv_obj_t *lbl = lv_label_create(list);
|
lv_obj_t *lbl = lv_label_create(list);
|
||||||
lv_label_set_text(lbl, LV_SYMBOL_WARNING " Keine Verbindung");
|
lv_label_set_text(lbl, loading ? LV_SYMBOL_REFRESH " Lade..."
|
||||||
lv_obj_set_style_text_color(lbl, Theme::accentError(), 0);
|
: LV_SYMBOL_WARNING " Keine Verbindung");
|
||||||
|
lv_obj_set_style_text_color(lbl, loading ? Theme::textDim() : Theme::accentError(), 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Columns (content width = 280 - 2*6 pad = 268), side by side like a table:
|
// Columns (content width = 280 - 2*6 pad = 268), side by side like a table:
|
||||||
// date 72 px left (dim grey) | time 56 px centered (grey) | summary (white, wraps)
|
// date 72 px left (dim grey) | time 56 px centered (grey) | summary (white, wraps)
|
||||||
for (const auto &ev : lastCalendar.events) {
|
for (const auto &ev : c.events) {
|
||||||
lv_obj_t *row = lv_obj_create(list);
|
lv_obj_t *row = lv_obj_create(list);
|
||||||
lv_obj_set_size(row, 280, LV_SIZE_CONTENT);
|
lv_obj_set_size(row, 280, LV_SIZE_CONTENT);
|
||||||
lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, 0);
|
lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, 0);
|
||||||
@@ -78,13 +82,17 @@ void calendarDetailScreen_create(Screen &s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void calendarDetailScreen_refresh(Screen &) {
|
void calendarDetailScreen_refresh(Screen &) {
|
||||||
CalendarData c = fetchCalendar();
|
|
||||||
ok = c.valid;
|
|
||||||
if (c.valid) lastCalendar = c;
|
|
||||||
buildList();
|
buildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void calendarDetailScreen_tick(Screen &) {}
|
void calendarDetailScreen_tick(Screen &) {
|
||||||
|
static uint32_t lastVersion = 0;
|
||||||
|
uint32_t v = dataManager_dataVersion();
|
||||||
|
if (v != lastVersion) {
|
||||||
|
lastVersion = v;
|
||||||
|
buildList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Screen calendarDetailScreen_make() {
|
Screen calendarDetailScreen_make() {
|
||||||
Screen s;
|
Screen s;
|
||||||
|
|||||||
+45
-49
@@ -1,8 +1,7 @@
|
|||||||
#include "home_screen.h"
|
#include "home_screen.h"
|
||||||
#include "ui/widgets/tile_button.h"
|
#include "ui/widgets/tile_button.h"
|
||||||
#include "icons/icons.h"
|
#include "icons/icons.h"
|
||||||
#include "api/weather_api.h"
|
#include "data/data_manager.h"
|
||||||
#include "api/calendar_api.h"
|
|
||||||
#include <theme.h>
|
#include <theme.h>
|
||||||
#include <text_utils.h>
|
#include <text_utils.h>
|
||||||
#include <date_utils.h>
|
#include <date_utils.h>
|
||||||
@@ -13,21 +12,9 @@ namespace {
|
|||||||
// --- navigation callback target (set by main.cpp) ---
|
// --- navigation callback target (set by main.cpp) ---
|
||||||
ScreenId (*g_navigate)(ScreenId) = nullptr;
|
ScreenId (*g_navigate)(ScreenId) = nullptr;
|
||||||
|
|
||||||
// Forward declarations (definitions below).
|
|
||||||
void updateWidgets();
|
|
||||||
void doFetch();
|
|
||||||
|
|
||||||
void on_calendar_tile(lv_event_t *) { if (g_navigate) g_navigate(ScreenId::CALENDAR_DETAIL); }
|
void on_calendar_tile(lv_event_t *) { if (g_navigate) g_navigate(ScreenId::CALENDAR_DETAIL); }
|
||||||
void on_mvg_tile(lv_event_t *) { if (g_navigate) g_navigate(ScreenId::MVG); }
|
void on_mvg_tile(lv_event_t *) { if (g_navigate) g_navigate(ScreenId::MVG); }
|
||||||
|
|
||||||
// --- cached data + refresh timer (ported from old home_screen.cpp) ---
|
|
||||||
WeatherData lastWeather;
|
|
||||||
CalendarData lastCalendar;
|
|
||||||
bool weatherOk = false;
|
|
||||||
bool calendarOk = false;
|
|
||||||
unsigned long lastFetchMs = 0;
|
|
||||||
const unsigned long REFRESH_INTERVAL_MS = 10UL * 60UL * 1000UL;
|
|
||||||
|
|
||||||
// --- tile handles (value labels, for updating without rebuild) ---
|
// --- tile handles (value labels, for updating without rebuild) ---
|
||||||
TileButton weatherTile{};
|
TileButton weatherTile{};
|
||||||
TileButton calendarTile{};
|
TileButton calendarTile{};
|
||||||
@@ -43,45 +30,28 @@ void rebuildWeatherIcon(const String &symbol, const String &description) {
|
|||||||
lv_obj_align(weatherTile.icon, LV_ALIGN_CENTER, 0, -14);
|
lv_obj_align(weatherTile.icon, LV_ALIGN_CENTER, 0, -14);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateRainWarning() {
|
void updateRainWarning(const WeatherData &w, bool ok) {
|
||||||
if (rainWarningIcon) {
|
if (rainWarningIcon) {
|
||||||
lv_obj_del(rainWarningIcon);
|
lv_obj_del(rainWarningIcon);
|
||||||
rainWarningIcon = nullptr;
|
rainWarningIcon = nullptr;
|
||||||
}
|
}
|
||||||
if (weatherOk && willRainSoon(lastWeather, 8)) {
|
if (ok && willRainSoon(w, 8)) {
|
||||||
rainWarningIcon = Icons::createRainWarning(weatherTile.btn, 28);
|
rainWarningIcon = Icons::createRainWarning(weatherTile.btn, 28);
|
||||||
lv_obj_align(rainWarningIcon, LV_ALIGN_TOP_RIGHT, -4, 4);
|
lv_obj_align(rainWarningIcon, LV_ALIGN_TOP_RIGHT, -4, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void doFetch() {
|
|
||||||
WeatherData w = fetchWeather();
|
|
||||||
weatherOk = w.valid;
|
|
||||||
if (w.valid) lastWeather = w;
|
|
||||||
CalendarData c = fetchCalendar();
|
|
||||||
calendarOk = c.valid;
|
|
||||||
if (c.valid) lastCalendar = c;
|
|
||||||
lastFetchMs = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manual retry: tapping the weather tile when it is in error state triggers
|
// Manual retry: tapping the weather tile when it is in error state triggers
|
||||||
// a refresh instead of navigating away. Acts as the SPEC.md "retry per tap".
|
// a background refresh instead of navigating away.
|
||||||
void on_weather_tile(lv_event_t *e) {
|
void on_weather_tile(lv_event_t *) {
|
||||||
if (!weatherOk) {
|
if (!dataManager_isWeatherOk()) {
|
||||||
doFetch();
|
dataManager_triggerRefresh();
|
||||||
updateWidgets();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (g_navigate) g_navigate(ScreenId::WEATHER_DETAIL);
|
if (g_navigate) g_navigate(ScreenId::WEATHER_DETAIL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- formatting helpers (ported from old home_screen.cpp rendering) ---
|
// --- formatting helpers ---
|
||||||
|
|
||||||
String weatherIconGlyph(const String &symbol) {
|
|
||||||
// Kept for fallback; real icons are drawn as canvas via rebuildWeatherIcon.
|
|
||||||
(void)symbol;
|
|
||||||
return LV_SYMBOL_IMAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
String formatWeatherValue(const WeatherData &w) {
|
String formatWeatherValue(const WeatherData &w) {
|
||||||
if (!w.valid) return "Wetter n.a.";
|
if (!w.valid) return "Wetter n.a.";
|
||||||
@@ -96,9 +66,9 @@ String formatCalendarPreview(const CalendarData &c) {
|
|||||||
int shown = 0;
|
int shown = 0;
|
||||||
for (const auto &ev : c.events) {
|
for (const auto &ev : c.events) {
|
||||||
if (shown >= 2) break;
|
if (shown >= 2) break;
|
||||||
if (shown > 0) s += "\n\n";
|
if (shown > 0) s += "\n";
|
||||||
s += DateUtils::formatShortDE(ev.startAt, ev.allDay);
|
s += sanitizeGermanText(ev.summary.substring(0, 22));
|
||||||
s += "\n" + sanitizeGermanText(ev.summary.substring(0, 22));
|
s += "\n" + DateUtils::formatShortDE(ev.startAt, ev.allDay);
|
||||||
++shown;
|
++shown;
|
||||||
}
|
}
|
||||||
if (s.length() == 0) s = "Keine Termine";
|
if (s.length() == 0) s = "Keine Termine";
|
||||||
@@ -107,12 +77,38 @@ String formatCalendarPreview(const CalendarData &c) {
|
|||||||
|
|
||||||
void updateWidgets() {
|
void updateWidgets() {
|
||||||
if (!widgetsBuilt) return;
|
if (!widgetsBuilt) return;
|
||||||
rebuildWeatherIcon(lastWeather.current.symbol, lastWeather.current.description);
|
|
||||||
updateRainWarning();
|
WeatherData w;
|
||||||
tile_button_set_value(weatherTile, formatWeatherValue(lastWeather).c_str());
|
CalendarData c;
|
||||||
|
dataManager_getWeather(w);
|
||||||
|
dataManager_getCalendar(c);
|
||||||
|
bool weatherOk = dataManager_isWeatherOk();
|
||||||
|
bool calendarOk = dataManager_isCalendarOk();
|
||||||
|
bool loading = dataManager_isLoading();
|
||||||
|
|
||||||
|
// Weather tile
|
||||||
|
if (weatherOk) {
|
||||||
|
rebuildWeatherIcon(w.current.symbol, w.current.description);
|
||||||
|
updateRainWarning(w, true);
|
||||||
|
tile_button_set_value(weatherTile, formatWeatherValue(w).c_str());
|
||||||
|
} else if (loading) {
|
||||||
|
tile_button_set_value(weatherTile, "Lade...");
|
||||||
|
} else {
|
||||||
|
tile_button_set_value(weatherTile, "Wetter n.a.");
|
||||||
|
}
|
||||||
lv_obj_align(weatherTile.value, LV_ALIGN_CENTER, 0, 50);
|
lv_obj_align(weatherTile.value, LV_ALIGN_CENTER, 0, 50);
|
||||||
tile_button_set_value(calendarTile, formatCalendarPreview(lastCalendar).c_str());
|
|
||||||
|
// Calendar tile
|
||||||
|
if (calendarOk) {
|
||||||
|
tile_button_set_value(calendarTile, formatCalendarPreview(c).c_str());
|
||||||
|
} else if (loading) {
|
||||||
|
tile_button_set_value(calendarTile, "Lade...");
|
||||||
|
} else {
|
||||||
|
tile_button_set_value(calendarTile, "Kalender n.a.");
|
||||||
|
}
|
||||||
tile_button_set_icon(calendarTile, LV_SYMBOL_FILE);
|
tile_button_set_icon(calendarTile, LV_SYMBOL_FILE);
|
||||||
|
|
||||||
|
// MVG tile
|
||||||
tile_button_set_value(mvgTile, "Abfahrten");
|
tile_button_set_value(mvgTile, "Abfahrten");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,13 +144,14 @@ void homeScreen_create(Screen &s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void homeScreen_refresh(Screen &) {
|
void homeScreen_refresh(Screen &) {
|
||||||
doFetch();
|
|
||||||
updateWidgets();
|
updateWidgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
void homeScreen_tick(Screen &) {
|
void homeScreen_tick(Screen &) {
|
||||||
if (lastFetchMs == 0 || millis() - lastFetchMs >= REFRESH_INTERVAL_MS) {
|
static uint32_t lastVersion = 0;
|
||||||
doFetch();
|
uint32_t v = dataManager_dataVersion();
|
||||||
|
if (v != lastVersion) {
|
||||||
|
lastVersion = v;
|
||||||
updateWidgets();
|
updateWidgets();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,4 +163,3 @@ Screen homeScreen_make() {
|
|||||||
s.tick_fn = homeScreen_tick;
|
s.tick_fn = homeScreen_tick;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "weather_detail_screen.h"
|
#include "weather_detail_screen.h"
|
||||||
#include "ui/widgets/back_button.h"
|
#include "ui/widgets/back_button.h"
|
||||||
#include "api/weather_api.h"
|
#include "data/data_manager.h"
|
||||||
#include "icons/icons.h"
|
#include "icons/icons.h"
|
||||||
#include <theme.h>
|
#include <theme.h>
|
||||||
#include <text_utils.h>
|
#include <text_utils.h>
|
||||||
@@ -11,18 +11,22 @@ namespace {
|
|||||||
ScreenId (*g_navigate)(ScreenId) = nullptr;
|
ScreenId (*g_navigate)(ScreenId) = nullptr;
|
||||||
void on_back(lv_event_t *) { if (g_navigate) g_navigate(ScreenId::HOME); }
|
void on_back(lv_event_t *) { if (g_navigate) g_navigate(ScreenId::HOME); }
|
||||||
|
|
||||||
WeatherData lastWeather;
|
|
||||||
bool ok = false;
|
|
||||||
lv_obj_t *list = nullptr;
|
lv_obj_t *list = nullptr;
|
||||||
|
|
||||||
void buildList() {
|
void buildList() {
|
||||||
if (!list) return;
|
if (!list) return;
|
||||||
lv_obj_clean(list);
|
lv_obj_clean(list);
|
||||||
|
|
||||||
|
WeatherData w;
|
||||||
|
dataManager_getWeather(w);
|
||||||
|
bool ok = dataManager_isWeatherOk();
|
||||||
|
bool loading = dataManager_isLoading();
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
lv_obj_t *lbl = lv_label_create(list);
|
lv_obj_t *lbl = lv_label_create(list);
|
||||||
lv_label_set_text(lbl, LV_SYMBOL_WARNING " Keine Verbindung");
|
lv_label_set_text(lbl, loading ? LV_SYMBOL_REFRESH " Lade..."
|
||||||
lv_obj_set_style_text_color(lbl, Theme::accentError(), 0);
|
: LV_SYMBOL_WARNING " Keine Verbindung");
|
||||||
|
lv_obj_set_style_text_color(lbl, loading ? Theme::textDim() : Theme::accentError(), 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +43,7 @@ void buildList() {
|
|||||||
lv_obj_set_style_line_color(header_line, Theme::textDim(), 0);
|
lv_obj_set_style_line_color(header_line, Theme::textDim(), 0);
|
||||||
lv_obj_set_style_line_width(header_line, 1, 0);
|
lv_obj_set_style_line_width(header_line, 1, 0);
|
||||||
|
|
||||||
for (const auto &fe : lastWeather.forecast) {
|
for (const auto &fe : w.forecast) {
|
||||||
lv_obj_t *row = lv_obj_create(list);
|
lv_obj_t *row = lv_obj_create(list);
|
||||||
lv_obj_set_size(row, 280, 30);
|
lv_obj_set_size(row, 280, 30);
|
||||||
lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, 0);
|
lv_obj_set_style_bg_opa(row, LV_OPA_TRANSP, 0);
|
||||||
@@ -105,13 +109,17 @@ void weatherDetailScreen_create(Screen &s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void weatherDetailScreen_refresh(Screen &) {
|
void weatherDetailScreen_refresh(Screen &) {
|
||||||
WeatherData w = fetchWeather();
|
|
||||||
ok = w.valid;
|
|
||||||
if (w.valid) lastWeather = w;
|
|
||||||
buildList();
|
buildList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void weatherDetailScreen_tick(Screen &) {}
|
void weatherDetailScreen_tick(Screen &) {
|
||||||
|
static uint32_t lastVersion = 0;
|
||||||
|
uint32_t v = dataManager_dataVersion();
|
||||||
|
if (v != lastVersion) {
|
||||||
|
lastVersion = v;
|
||||||
|
buildList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Screen weatherDetailScreen_make() {
|
Screen weatherDetailScreen_make() {
|
||||||
Screen s;
|
Screen s;
|
||||||
|
|||||||
Reference in New Issue
Block a user