From c4fe2538aa55101e2bc1b8e1413ed266fa788683 Mon Sep 17 00:00:00 2001 From: Victor Kareh Date: Wed, 7 Feb 2024 15:12:40 -0500 Subject: [PATCH 1/2] SimpleWeatherService: Add sunrise and sunset data --- doc/SimpleWeatherService.md | 14 ++++--- src/components/ble/SimpleWeatherService.cpp | 37 +++++++++++++++++-- src/components/ble/SimpleWeatherService.h | 12 +++++- src/displayapp/fonts/fonts.json | 2 +- src/displayapp/screens/Symbols.h | 3 ++ src/displayapp/screens/WatchFaceDigital.cpp | 2 +- .../screens/WatchFacePineTimeStyle.cpp | 2 +- src/displayapp/screens/Weather.cpp | 4 +- src/displayapp/screens/WeatherSymbols.cpp | 12 +++++- src/displayapp/screens/WeatherSymbols.h | 2 +- 10 files changed, 73 insertions(+), 17 deletions(-) diff --git a/doc/SimpleWeatherService.md b/doc/SimpleWeatherService.md index e0fffba788..e6b20aa8d0 100644 --- a/doc/SimpleWeatherService.md +++ b/doc/SimpleWeatherService.md @@ -17,23 +17,25 @@ The host uses this characteristic to update the current weather information and This characteristics accepts a byte array with the following 2-Bytes header: - - [0] Message Type : + - [0] Message Type : - `0` : Current weather - `1` : Forecast - - [1] Message Version : Version `0` is currently supported. Other versions might be added in future releases + - [1] Message Version : + - `0` : Currently supported + - `1` : Adds support for sunrise and sunset -### Current Weather +### Current Weather The byte array must contain the following data: - [0] : Message type = `0` - - [1] : Message version = `0` + - [1] : Message version = `1` - [2][3][4][5][6][7][8][9] : Timestamp (64 bits UNIX timestamp, number of seconds elapsed since 1 JAN 1970) in local time (the same timezone as the one used to set the time) - [10, 11] : Current temperature (°C * 100) - [12, 13] : Minimum temperature (°C * 100) - [14, 15] : Maximum temperature (°C * 100) - [16]..[47] : location (string, unused characters should be set to `0`) - - [48] : icon ID + - [48] : icon ID - 0 = Sun, clear sky - 1 = Few clouds - 2 = Clouds @@ -43,6 +45,8 @@ The byte array must contain the following data: - 6 = Thunderstorm - 7 = Snow - 8 = Mist, smog + - [49, 50] : Sunrise (16 bits, number of minutes elapsed since midnight) + - [51, 52] : Sunset (16 bits, number of minutes elapsed since midnight) ### Forecast diff --git a/src/components/ble/SimpleWeatherService.cpp b/src/components/ble/SimpleWeatherService.cpp index 51baf5433f..1f608d503f 100644 --- a/src/components/ble/SimpleWeatherService.cpp +++ b/src/components/ble/SimpleWeatherService.cpp @@ -33,6 +33,10 @@ namespace { (static_cast(data[5]) << 40) + (static_cast(data[6]) << 48) + (static_cast(data[7]) << 56); } + uint16_t ToUInt16(const uint8_t* data) { + return data[0] + (data[1] << 8); + } + int16_t ToInt16(const uint8_t* data) { return data[0] + (data[1] << 8); } @@ -41,12 +45,20 @@ namespace { SimpleWeatherService::Location cityName; std::memcpy(cityName.data(), &dataBuffer[16], 32); cityName[32] = '\0'; + uint16_t sunrise = 0; + uint16_t sunset = 0; + if (dataBuffer[1] > 0) { + sunrise = ToUInt16(&dataBuffer[49]); + sunset = ToUInt16(&dataBuffer[51]); + } return SimpleWeatherService::CurrentWeather(ToUInt64(&dataBuffer[2]), SimpleWeatherService::Temperature(ToInt16(&dataBuffer[10])), SimpleWeatherService::Temperature(ToInt16(&dataBuffer[12])), SimpleWeatherService::Temperature(ToInt16(&dataBuffer[14])), SimpleWeatherService::Icons {dataBuffer[16 + 32]}, - std::move(cityName)); + std::move(cityName), + sunrise, + sunset); } SimpleWeatherService::Forecast CreateForecast(const uint8_t* dataBuffer) { @@ -94,7 +106,7 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) { switch (GetMessageType(dataBuffer)) { case MessageType::CurrentWeather: - if (GetVersion(dataBuffer) == 0) { + if (GetVersion(dataBuffer) <= 1) { currentWeather = CreateCurrentWeather(dataBuffer); NRF_LOG_INFO("Current weather :\n\tTimestamp : %d\n\tTemperature:%d\n\tMin:%d\n\tMax:%d\n\tIcon:%d\n\tLocation:%s", currentWeather->timestamp, @@ -103,6 +115,9 @@ int SimpleWeatherService::OnCommand(struct ble_gatt_access_ctxt* ctxt) { currentWeather->maxTemperature.PreciseCelsius(), currentWeather->iconId, currentWeather->location.data()); + if (GetVersion(dataBuffer) == 1) { + NRF_LOG_INFO("Sunrise: %d\n\tSunset: %d", currentWeather->sunrise, currentWeather->sunset); + } } break; case MessageType::Forecast: @@ -153,10 +168,26 @@ std::optional SimpleWeatherService::GetForecast( return {}; } +bool SimpleWeatherService::IsNight() const { + if (currentWeather && currentWeather->sunrise > 0 && currentWeather->sunset > 0) { + auto currentTime = dateTimeController.CurrentDateTime().time_since_epoch(); + + // Get timestamp for last midnight + auto midnight = std::chrono::floor(currentTime); + + // Calculate minutes since midnight + auto currentMinutes = std::chrono::duration_cast(currentTime - midnight).count(); + + return currentMinutes < currentWeather->sunrise || currentMinutes > currentWeather->sunset; + } + + return false; +} + bool SimpleWeatherService::CurrentWeather::operator==(const SimpleWeatherService::CurrentWeather& other) const { return this->iconId == other.iconId && this->temperature == other.temperature && this->timestamp == other.timestamp && this->maxTemperature == other.maxTemperature && this->minTemperature == other.maxTemperature && - std::strcmp(this->location.data(), other.location.data()) == 0; + std::strcmp(this->location.data(), other.location.data()) == 0 && this->sunrise == other.sunrise && this->sunset == other.sunset; } bool SimpleWeatherService::Forecast::Day::operator==(const SimpleWeatherService::Forecast::Day& other) const { diff --git a/src/components/ble/SimpleWeatherService.h b/src/components/ble/SimpleWeatherService.h index 469b571aea..b83e3ff947 100644 --- a/src/components/ble/SimpleWeatherService.h +++ b/src/components/ble/SimpleWeatherService.h @@ -112,13 +112,17 @@ namespace Pinetime { Temperature minTemperature, Temperature maxTemperature, Icons iconId, - Location&& location) + Location&& location, + uint16_t sunrise, + uint16_t sunset) : timestamp {timestamp}, temperature {temperature}, minTemperature {minTemperature}, maxTemperature {maxTemperature}, iconId {iconId}, - location {std::move(location)} { + location {std::move(location)}, + sunrise {sunrise}, + sunset {sunset} { } uint64_t timestamp; @@ -127,6 +131,8 @@ namespace Pinetime { Temperature maxTemperature; Icons iconId; Location location; + uint16_t sunrise; + uint16_t sunset; bool operator==(const CurrentWeather& other) const; }; @@ -151,6 +157,8 @@ namespace Pinetime { std::optional Current() const; std::optional GetForecast() const; + bool IsNight() const; + private: // 00050000-78fc-48fe-8e23-433b3a1942d0 static constexpr ble_uuid128_t BaseUuid() { diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json index 90be1febe6..3221c2f171 100644 --- a/src/displayapp/fonts/fonts.json +++ b/src/displayapp/fonts/fonts.json @@ -68,7 +68,7 @@ "sources": [ { "file": "FontAwesome5-Solid+Brands+Regular.woff", - "range": "0xf185, 0xf6c4, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e, 0xf73b, 0xf0e7, 0xf2dc" + "range": "0xf185, 0xf186, 0xf6c3, 0xf6c4, 0xf73c, 0xf743, 0xf740, 0xf75f, 0xf0c2, 0xf05e, 0xf73b, 0xf0e7, 0xf2dc" } ], "bpp": 1, diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h index fb93e80e87..058b2d06e9 100644 --- a/src/displayapp/screens/Symbols.h +++ b/src/displayapp/screens/Symbols.h @@ -45,8 +45,11 @@ namespace Pinetime { // fontawesome_weathericons.c // static constexpr const char* sun = "\xEF\x86\x85"; + static constexpr const char* moon = "\xEF\x86\x86"; // 0xf186 static constexpr const char* cloudSun = "\xEF\x9B\x84"; + static constexpr const char* cloudMoon = "\xEF\x9B\x83"; // 0xf6c3 static constexpr const char* cloudSunRain = "\xEF\x9D\x83"; + static constexpr const char* cloudMoonRain = "\xEF\x9C\xBC"; // 0xf73c static constexpr const char* cloudShowersHeavy = "\xEF\x9D\x80"; static constexpr const char* smog = "\xEF\x9D\x9F"; static constexpr const char* cloud = "\xEF\x83\x82"; diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp index 3163c6e750..a037abfe16 100644 --- a/src/displayapp/screens/WatchFaceDigital.cpp +++ b/src/displayapp/screens/WatchFaceDigital.cpp @@ -183,7 +183,7 @@ void WatchFaceDigital::Refresh() { tempUnit = 'F'; } lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); - lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId)); + lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId, weatherService.IsNight())); } else { lv_label_set_text_static(temperature, ""); lv_label_set_text(weatherIcon, ""); diff --git a/src/displayapp/screens/WatchFacePineTimeStyle.cpp b/src/displayapp/screens/WatchFacePineTimeStyle.cpp index ce8f8f7ed9..bfbbe24bf4 100644 --- a/src/displayapp/screens/WatchFacePineTimeStyle.cpp +++ b/src/displayapp/screens/WatchFacePineTimeStyle.cpp @@ -548,7 +548,7 @@ void WatchFacePineTimeStyle::Refresh() { temp = optCurrentWeather->temperature.Fahrenheit(); } lv_label_set_text_fmt(temperature, "%d°", temp); - lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId)); + lv_label_set_text(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId, weatherService.IsNight())); } else { lv_label_set_text(temperature, "--"); lv_label_set_text(weatherIcon, Symbols::ban); diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp index 0e44df0349..de32a1538b 100644 --- a/src/displayapp/screens/Weather.cpp +++ b/src/displayapp/screens/Weather.cpp @@ -118,7 +118,7 @@ void Weather::Refresh() { tempUnit = 'F'; } lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, optCurrentWeather->temperature.Color()); - lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId)); + lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId, weatherService.IsNight())); lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId)); lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); lv_label_set_text_fmt(minTemperature, "%d°", minTemp); @@ -154,7 +154,7 @@ void Weather::Refresh() { } const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast(wday)); lv_table_set_cell_value(forecast, 0, i, dayOfWeek); - lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i]->iconId)); + lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i]->iconId, false)); // Pad cells based on the largest number of digits on each column char maxPadding[3] = " "; char minPadding[3] = " "; diff --git a/src/displayapp/screens/WeatherSymbols.cpp b/src/displayapp/screens/WeatherSymbols.cpp index de66312f90..8f1d49f4e9 100644 --- a/src/displayapp/screens/WeatherSymbols.cpp +++ b/src/displayapp/screens/WeatherSymbols.cpp @@ -1,11 +1,18 @@ #include "displayapp/screens/WeatherSymbols.h" -const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon) { +const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon, + const bool isNight) { switch (icon) { case Pinetime::Controllers::SimpleWeatherService::Icons::Sun: + if (isNight) { + return Symbols::moon; + } return Symbols::sun; break; case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun: + if (isNight) { + return Symbols::cloudMoon; + } return Symbols::cloudSun; break; case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds: @@ -24,6 +31,9 @@ const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime:: return Symbols::cloudShowersHeavy; break; case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain: + if (isNight) { + return Symbols::cloudMoonRain; + } return Symbols::cloudSunRain; break; case Pinetime::Controllers::SimpleWeatherService::Icons::Smog: diff --git a/src/displayapp/screens/WeatherSymbols.h b/src/displayapp/screens/WeatherSymbols.h index f3eeed5581..f362eff972 100644 --- a/src/displayapp/screens/WeatherSymbols.h +++ b/src/displayapp/screens/WeatherSymbols.h @@ -6,7 +6,7 @@ namespace Pinetime { namespace Applications { namespace Screens { namespace Symbols { - const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon); + const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon, const bool isNight); const char* GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon); } } From 0e2aaf3f481aa53f261500b4a4cfbafb536ed87b Mon Sep 17 00:00:00 2001 From: Victor Kareh Date: Wed, 17 Dec 2025 16:43:20 -0500 Subject: [PATCH 2/2] weather: Add location, last updated, and sunrise/sunset display This change splits the weather screen into 3 pages: - Current weather conditions - 5-day forecast - Sunrise/sunset times --- src/displayapp/screens/Weather.cpp | 597 ++++++++++++++++++++++------- src/displayapp/screens/Weather.h | 32 +- 2 files changed, 477 insertions(+), 152 deletions(-) diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp index de32a1538b..82c6457862 100644 --- a/src/displayapp/screens/Weather.cpp +++ b/src/displayapp/screens/Weather.cpp @@ -1,13 +1,17 @@ #include "displayapp/screens/Weather.h" #include +#include #include "components/ble/SimpleWeatherService.h" #include "components/datetime/DateTimeController.h" #include "components/settings/Settings.h" #include "displayapp/DisplayApp.h" #include "displayapp/screens/WeatherSymbols.h" +#include "displayapp/screens/Label.h" +#include "displayapp/widgets/PageIndicator.h" #include "displayapp/InfiniTimeTheme.h" +#include "utility/DirtyValue.h" using namespace Pinetime::Applications::Screens; @@ -22,162 +26,479 @@ namespace { } return LV_TABLE_PART_CELL5; // normal } -} -Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService) - : settingsController {settingsController}, weatherService {weatherService} { - - temperature = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); - lv_obj_set_style_local_text_font(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); - lv_label_set_text(temperature, "---"); - lv_obj_align(temperature, nullptr, LV_ALIGN_CENTER, 0, -30); - lv_obj_set_auto_realign(temperature, true); - - minTemperature = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(minTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg); - lv_label_set_text(minTemperature, ""); - lv_obj_align(minTemperature, temperature, LV_ALIGN_OUT_LEFT_MID, -10, 0); - lv_obj_set_auto_realign(minTemperature, true); - - maxTemperature = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(maxTemperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::bg); - lv_label_set_text(maxTemperature, ""); - lv_obj_align(maxTemperature, temperature, LV_ALIGN_OUT_RIGHT_MID, 10, 0); - lv_obj_set_auto_realign(maxTemperature, true); - - condition = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(condition, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); - lv_label_set_text(condition, ""); - lv_obj_align(condition, temperature, LV_ALIGN_OUT_TOP_MID, 0, -10); - lv_obj_set_auto_realign(condition, true); - - icon = lv_label_create(lv_scr_act(), nullptr); - lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); - lv_obj_set_style_local_text_font(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); - lv_label_set_text(icon, ""); - lv_obj_align(icon, condition, LV_ALIGN_OUT_TOP_MID, 0, 0); - lv_obj_set_auto_realign(icon, true); - - forecast = lv_table_create(lv_scr_act(), nullptr); - lv_table_set_col_cnt(forecast, Controllers::SimpleWeatherService::MaxNbForecastDays); - lv_table_set_row_cnt(forecast, 4); - // LV_TABLE_PART_CELL1: Default table style - lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, LV_COLOR_BLACK); - lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, Colors::lightGray); - // LV_TABLE_PART_CELL2: Condition icon - lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_BLACK); - lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_WHITE); - lv_obj_set_style_local_text_font(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, &fontawesome_weathericons); - // LV_TABLE_PART_CELL3: Freezing - lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, LV_COLOR_BLACK); - lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, Colors::blue); - // LV_TABLE_PART_CELL4: Ice - lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_BLACK); - lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_CYAN); - // LV_TABLE_PART_CELL5: Normal - lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, LV_COLOR_BLACK); - lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, Colors::orange); - // LV_TABLE_PART_CELL6: Hot - lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, LV_COLOR_BLACK); - lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, Colors::deepOrange); - - lv_obj_align(forecast, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0); - - for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) { - lv_table_set_col_width(forecast, i, 48); - lv_table_set_cell_type(forecast, 1, i, LV_TABLE_PART_CELL2); - lv_table_set_cell_align(forecast, 0, i, LV_LABEL_ALIGN_CENTER); - lv_table_set_cell_align(forecast, 1, i, LV_LABEL_ALIGN_CENTER); - lv_table_set_cell_align(forecast, 2, i, LV_LABEL_ALIGN_CENTER); - lv_table_set_cell_align(forecast, 3, i, LV_LABEL_ALIGN_CENTER); + void FormatTime(uint16_t minutesSinceMidnight, char* buf, size_t size, Pinetime::Controllers::Settings::ClockType clockType) { + uint8_t hours = minutesSinceMidnight / 60; + uint8_t mins = minutesSinceMidnight % 60; + if (clockType == Pinetime::Controllers::Settings::ClockType::H12) { + const char* ampm = (hours < 12) ? "AM" : "PM"; + hours = hours % 12; + if (hours == 0) { + hours = 12; + } + snprintf(buf, size, "%d:%02d %s", hours, mins, ampm); + } else { + snprintf(buf, size, "%02d:%02d", hours, mins); + } } - taskRefresh = lv_task_create(RefreshTaskCallback, 1000, LV_TASK_PRIO_MID, this); - Refresh(); + void FormatLastUpdated(uint64_t weatherTs, uint64_t nowTs, char* buf, size_t size) { + if (nowTs < weatherTs) { + snprintf(buf, size, "unknown"); + return; + } + auto deltaSec = nowTs - weatherTs; + auto deltaMin = deltaSec / 60; + auto deltaHrs = deltaMin / 60; + if (deltaMin < 1) { + snprintf(buf, size, "just now"); + } else if (deltaMin < 60) { + snprintf(buf, size, "%d min ago", static_cast(deltaMin)); + } else if (deltaHrs < 24) { + snprintf(buf, size, "%d hr%s ago", static_cast(deltaHrs), deltaHrs > 1 ? "s" : ""); + } else { + snprintf(buf, size, ">24 hrs ago"); + } + } +} + +// Weather main screen: manages 3 pages +Weather::Weather(DisplayApp* app, + Pinetime::Controllers::Settings& settingsController, + Pinetime::Controllers::SimpleWeatherService& weatherService, + Pinetime::Controllers::DateTime& dateTimeController) + : app {app}, + settingsController {settingsController}, + weatherService {weatherService}, + dateTimeController {dateTimeController}, + screens {app, + 0, + {[this]() -> std::unique_ptr { + return CreateScreen1(); + }, + [this]() -> std::unique_ptr { + return CreateScreen2(); + }, + [this]() -> std::unique_ptr { + return CreateScreen3(); + }}, + Screens::ScreenListModes::UpDown} { } Weather::~Weather() { - lv_task_del(taskRefresh); lv_obj_clean(lv_scr_act()); } -void Weather::Refresh() { - currentWeather = weatherService.Current(); - if (currentWeather.IsUpdated()) { - auto optCurrentWeather = currentWeather.Get(); - if (optCurrentWeather) { - int16_t temp = optCurrentWeather->temperature.Celsius(); - int16_t minTemp = optCurrentWeather->minTemperature.Celsius(); - int16_t maxTemp = optCurrentWeather->maxTemperature.Celsius(); - char tempUnit = 'C'; - if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { - temp = optCurrentWeather->temperature.Fahrenheit(); - minTemp = optCurrentWeather->minTemperature.Fahrenheit(); - maxTemp = optCurrentWeather->maxTemperature.Fahrenheit(); - tempUnit = 'F'; - } - lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, optCurrentWeather->temperature.Color()); - lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId, weatherService.IsNight())); - lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId)); - lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); - lv_label_set_text_fmt(minTemperature, "%d°", minTemp); - lv_label_set_text_fmt(maxTemperature, "%d°", maxTemp); - } else { +bool Weather::OnTouchEvent(TouchEvents event) { + return screens.OnTouchEvent(event); +} + +// Page 1: Current Weather with location and last updated +namespace { + class WeatherPage1 : public Screen { + public: + WeatherPage1(Pinetime::Controllers::Settings& settings, + Pinetime::Controllers::SimpleWeatherService& weather, + Pinetime::Controllers::DateTime& dateTime) + : settingsController {settings}, weatherService {weather}, dateTimeController {dateTime}, pageIndicator(0, 3) { + + icon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_font(icon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); lv_label_set_text(icon, ""); + lv_obj_align(icon, nullptr, LV_ALIGN_IN_TOP_MID, 0, 10); + lv_obj_set_auto_realign(icon, true); + + condition = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(condition, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); lv_label_set_text(condition, ""); - lv_label_set_text(temperature, "---"); + lv_obj_align(condition, icon, LV_ALIGN_OUT_BOTTOM_MID, 0, 5); + lv_obj_set_auto_realign(condition, true); + + temperature = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); - lv_label_set_text(minTemperature, ""); - lv_label_set_text(maxTemperature, ""); + lv_obj_set_style_local_text_font(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42); + lv_label_set_text(temperature, "---"); + lv_obj_align(temperature, nullptr, LV_ALIGN_CENTER, 0, -10); + lv_obj_set_auto_realign(temperature, true); + + minMaxTemp = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(minMaxTemp, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + lv_label_set_text(minMaxTemp, ""); + lv_obj_align(minMaxTemp, temperature, LV_ALIGN_OUT_BOTTOM_MID, 0, 5); + lv_obj_set_auto_realign(minMaxTemp, true); + + location = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(location, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_width(location, LV_HOR_RES - 20); + lv_label_set_text(location, ""); + lv_obj_align(location, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -35); + lv_obj_set_auto_realign(location, true); + + lastUpdated = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(lastUpdated, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + lv_label_set_text(lastUpdated, ""); + lv_obj_align(lastUpdated, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, -10); + lv_obj_set_auto_realign(lastUpdated, true); + + pageIndicator.Create(); + + taskRefresh = lv_task_create(RefreshTaskCallback, 1000, LV_TASK_PRIO_MID, this); + Refresh(); } - } - currentForecast = weatherService.GetForecast(); - if (currentForecast.IsUpdated()) { - auto optCurrentForecast = currentForecast.Get(); - if (optCurrentForecast) { - std::tm localTime = *std::localtime(reinterpret_cast(&optCurrentForecast->timestamp)); - - for (int i = 0; i < optCurrentForecast->nbDays; i++) { - int16_t maxTemp = optCurrentForecast->days[i]->maxTemperature.Celsius(); - int16_t minTemp = optCurrentForecast->days[i]->minTemperature.Celsius(); - if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) { - maxTemp = optCurrentForecast->days[i]->maxTemperature.Fahrenheit(); - minTemp = optCurrentForecast->days[i]->minTemperature.Fahrenheit(); - } - lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(optCurrentForecast->days[i]->maxTemperature)); - lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(optCurrentForecast->days[i]->minTemperature)); - uint8_t wday = localTime.tm_wday + i + 1; - if (wday > 7) { - wday -= 7; - } - const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast(wday)); - lv_table_set_cell_value(forecast, 0, i, dayOfWeek); - lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i]->iconId, false)); - // Pad cells based on the largest number of digits on each column - char maxPadding[3] = " "; - char minPadding[3] = " "; - int diff = snprintf(nullptr, 0, "%d", maxTemp) - snprintf(nullptr, 0, "%d", minTemp); - if (diff <= 0) { - maxPadding[-diff] = '\0'; - minPadding[0] = '\0'; + ~WeatherPage1() override { + lv_task_del(taskRefresh); + lv_obj_clean(lv_scr_act()); + } + + void Refresh() override { + currentWeather = weatherService.Current(); + if (currentWeather.IsUpdated()) { + auto optCurrentWeather = currentWeather.Get(); + if (optCurrentWeather) { + int16_t temp = optCurrentWeather->temperature.Celsius(); + int16_t minTemp = optCurrentWeather->minTemperature.Celsius(); + int16_t maxTemp = optCurrentWeather->maxTemperature.Celsius(); + char tempUnit = 'C'; + if (settingsController.GetWeatherFormat() == Pinetime::Controllers::Settings::WeatherFormat::Imperial) { + temp = optCurrentWeather->temperature.Fahrenheit(); + minTemp = optCurrentWeather->minTemperature.Fahrenheit(); + maxTemp = optCurrentWeather->maxTemperature.Fahrenheit(); + tempUnit = 'F'; + } + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, optCurrentWeather->temperature.Color()); + lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId, weatherService.IsNight())); + lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId)); + lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit); + lv_label_set_text_fmt(minMaxTemp, "%d° / %d°", minTemp, maxTemp); + lv_label_set_text(location, optCurrentWeather->location.data()); + + // Show last updated time + uint64_t currentTime = static_cast( + std::chrono::duration_cast(dateTimeController.CurrentDateTime().time_since_epoch()).count()); + char updateBuf[20]; + FormatLastUpdated(optCurrentWeather->timestamp, currentTime, updateBuf, sizeof(updateBuf)); + lv_label_set_text_fmt(lastUpdated, "Updated: %s", updateBuf); } else { - maxPadding[0] = '\0'; - minPadding[diff] = '\0'; + lv_label_set_text(icon, ""); + lv_label_set_text(condition, ""); + lv_label_set_text(temperature, "---"); + lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_label_set_text(minMaxTemp, ""); + lv_label_set_text(location, ""); + lv_label_set_text(lastUpdated, ""); } - lv_table_set_cell_value_fmt(forecast, 2, i, "%s%d", maxPadding, maxTemp); - lv_table_set_cell_value_fmt(forecast, 3, i, "%s%d", minPadding, minTemp); } - } else { - for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) { - lv_table_set_cell_value(forecast, 0, i, ""); - lv_table_set_cell_value(forecast, 1, i, ""); - lv_table_set_cell_value(forecast, 2, i, ""); - lv_table_set_cell_value(forecast, 3, i, ""); - lv_table_set_cell_type(forecast, 2, i, LV_TABLE_PART_CELL1); - lv_table_set_cell_type(forecast, 3, i, LV_TABLE_PART_CELL1); + } + + private: + Pinetime::Controllers::Settings& settingsController; + Pinetime::Controllers::SimpleWeatherService& weatherService; + Pinetime::Controllers::DateTime& dateTimeController; + + Pinetime::Utility::DirtyValue> currentWeather {}; + + lv_obj_t* icon; + lv_obj_t* condition; + lv_obj_t* temperature; + lv_obj_t* minMaxTemp; + lv_obj_t* location; + lv_obj_t* lastUpdated; + lv_task_t* taskRefresh; + + Pinetime::Applications::Widgets::PageIndicator pageIndicator; + }; +} + +std::unique_ptr Weather::CreateScreen1() { + return std::make_unique(settingsController, weatherService, dateTimeController); +} + +// Page 2: 5-Day Forecast +namespace { + class WeatherPage2 : public Screen { + public: + WeatherPage2(Pinetime::Controllers::Settings& settings, Pinetime::Controllers::SimpleWeatherService& weather) + : settingsController {settings}, weatherService {weather}, pageIndicator(1, 3) { + + // Header + lv_obj_t* header = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(header, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + lv_label_set_text_static(header, "Forecast"); + lv_obj_align(header, nullptr, LV_ALIGN_IN_TOP_MID, 0, 0); + + forecast = lv_table_create(lv_scr_act(), nullptr); + lv_table_set_col_cnt(forecast, Pinetime::Controllers::SimpleWeatherService::MaxNbForecastDays); + lv_table_set_row_cnt(forecast, 4); + // LV_TABLE_PART_CELL1: Default table style + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, Colors::lightGray); + // LV_TABLE_PART_CELL2: Condition icon + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_font(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, &fontawesome_weathericons); + // LV_TABLE_PART_CELL3: Freezing + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, Colors::blue); + // LV_TABLE_PART_CELL4: Ice + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, LV_COLOR_CYAN); + // LV_TABLE_PART_CELL5: Normal + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, Colors::orange); + // LV_TABLE_PART_CELL6: Hot + lv_obj_set_style_local_border_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, LV_COLOR_BLACK); + lv_obj_set_style_local_text_color(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, Colors::deepOrange); + + lv_obj_align(forecast, nullptr, LV_ALIGN_IN_LEFT_MID, 0, 10); + + for (int i = 0; i < Pinetime::Controllers::SimpleWeatherService::MaxNbForecastDays; i++) { + lv_table_set_col_width(forecast, i, 48); + lv_table_set_cell_type(forecast, 1, i, LV_TABLE_PART_CELL2); + lv_table_set_cell_align(forecast, 0, i, LV_LABEL_ALIGN_CENTER); + lv_table_set_cell_align(forecast, 1, i, LV_LABEL_ALIGN_CENTER); + lv_table_set_cell_align(forecast, 2, i, LV_LABEL_ALIGN_CENTER); + lv_table_set_cell_align(forecast, 3, i, LV_LABEL_ALIGN_CENTER); } + + pageIndicator.Create(); + + taskRefresh = lv_task_create(RefreshTaskCallback, 1000, LV_TASK_PRIO_MID, this); + Refresh(); } + + ~WeatherPage2() override { + lv_task_del(taskRefresh); + lv_obj_clean(lv_scr_act()); + } + + void Refresh() override { + currentForecast = weatherService.GetForecast(); + if (currentForecast.IsUpdated()) { + auto optCurrentForecast = currentForecast.Get(); + if (optCurrentForecast) { + std::tm localTime = *std::localtime(reinterpret_cast(&optCurrentForecast->timestamp)); + + for (int i = 0; i < optCurrentForecast->nbDays; i++) { + int16_t maxTemp = optCurrentForecast->days[i]->maxTemperature.Celsius(); + int16_t minTemp = optCurrentForecast->days[i]->minTemperature.Celsius(); + if (settingsController.GetWeatherFormat() == Pinetime::Controllers::Settings::WeatherFormat::Imperial) { + maxTemp = optCurrentForecast->days[i]->maxTemperature.Fahrenheit(); + minTemp = optCurrentForecast->days[i]->minTemperature.Fahrenheit(); + } + lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(optCurrentForecast->days[i]->maxTemperature)); + lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(optCurrentForecast->days[i]->minTemperature)); + uint8_t wday = localTime.tm_wday + i + 1; + if (wday > 7) { + wday -= 7; + } + const char* dayOfWeek = + Pinetime::Controllers::DateTime::DayOfWeekShortToStringLow(static_cast(wday)); + lv_table_set_cell_value(forecast, 0, i, dayOfWeek); + lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i]->iconId, false)); + // Pad cells based on the largest number of digits on each column + char maxPadding[3] = " "; + char minPadding[3] = " "; + int diff = snprintf(nullptr, 0, "%d", maxTemp) - snprintf(nullptr, 0, "%d", minTemp); + if (diff <= 0) { + maxPadding[-diff] = '\0'; + minPadding[0] = '\0'; + } else { + maxPadding[0] = '\0'; + minPadding[diff] = '\0'; + } + lv_table_set_cell_value_fmt(forecast, 2, i, "%s%d", maxPadding, maxTemp); + lv_table_set_cell_value_fmt(forecast, 3, i, "%s%d", minPadding, minTemp); + } + } else { + for (int i = 0; i < Pinetime::Controllers::SimpleWeatherService::MaxNbForecastDays; i++) { + lv_table_set_cell_value(forecast, 0, i, ""); + lv_table_set_cell_value(forecast, 1, i, ""); + lv_table_set_cell_value(forecast, 2, i, ""); + lv_table_set_cell_value(forecast, 3, i, ""); + lv_table_set_cell_type(forecast, 2, i, LV_TABLE_PART_CELL1); + lv_table_set_cell_type(forecast, 3, i, LV_TABLE_PART_CELL1); + } + } + } + } + + private: + Pinetime::Controllers::Settings& settingsController; + Pinetime::Controllers::SimpleWeatherService& weatherService; + + Pinetime::Utility::DirtyValue> currentForecast {}; + + lv_obj_t* forecast; + lv_task_t* taskRefresh; + + Pinetime::Applications::Widgets::PageIndicator pageIndicator; + }; +} + +std::unique_ptr Weather::CreateScreen2() { + return std::make_unique(settingsController, weatherService); +} + +// Page 3: Sunrise/Sunset with arc +namespace { + // Animation callback for arc value + void SetArcValue(void* obj, lv_anim_value_t value) { + lv_arc_set_value(static_cast(obj), value); } + + class WeatherPage3 : public Screen { + public: + WeatherPage3(Pinetime::Controllers::Settings& settings, + Pinetime::Controllers::SimpleWeatherService& weather, + Pinetime::Controllers::DateTime& dateTime) + : settingsController {settings}, weatherService {weather}, dateTimeController {dateTime}, pageIndicator(2, 3) { + + // Header + lv_obj_t* header = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(header, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + lv_label_set_text_static(header, "Sun Times"); + lv_obj_align(header, nullptr, LV_ALIGN_IN_TOP_MID, 0, 0); + + // Create the arc showing the sun's position relative to the daylight period + sunArc = lv_arc_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(sunArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, LV_OPA_0); + lv_obj_set_style_local_line_color(sunArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, Colors::bgAlt); + lv_obj_set_style_local_border_width(sunArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, 2); + lv_obj_set_style_local_radius(sunArc, LV_ARC_PART_BG, LV_STATE_DEFAULT, 0); + lv_obj_set_style_local_line_color(sunArc, LV_ARC_PART_INDIC, LV_STATE_DEFAULT, Colors::orange); + lv_arc_set_bg_angles(sunArc, 180, 0); + lv_obj_set_size(sunArc, 180, 180); + lv_obj_align(sunArc, nullptr, LV_ALIGN_CENTER, 0, 10); + + // Initialize animation with ease-in-out path + lv_anim_init(&anim); + lv_anim_set_var(&anim, sunArc); + lv_anim_set_exec_cb(&anim, SetArcValue); + lv_anim_set_time(&anim, 1000); // 1 second animation + static const lv_anim_path_t ease_in_out_path = {.cb = lv_anim_path_ease_in_out}; + lv_anim_set_path(&anim, &ease_in_out_path); + + // Sunrise icon and time widgets + sunriseIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(sunriseIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_font(sunriseIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); + lv_label_set_text(sunriseIcon, ""); + + sunriseTime = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(sunriseTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + lv_label_set_text(sunriseTime, ""); + + // Sunset icon and time widgets + sunsetIcon = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(sunsetIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_text_font(sunsetIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons); + lv_label_set_text(sunsetIcon, ""); + + sunsetTime = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(sunsetTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + lv_label_set_text(sunsetTime, ""); + + // Current time + currentTimeLabel = lv_label_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_text_color(currentTimeLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::lightGray); + lv_obj_set_style_local_text_font(currentTimeLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_bold_20); + lv_label_set_text(currentTimeLabel, "--:--"); + lv_obj_align(currentTimeLabel, nullptr, LV_ALIGN_CENTER, 0, 10); + lv_obj_set_auto_realign(currentTimeLabel, true); + + pageIndicator.Create(); + + taskRefresh = lv_task_create(RefreshTaskCallback, 1000, LV_TASK_PRIO_MID, this); + Refresh(); + } + + ~WeatherPage3() override { + lv_anim_del(sunArc, nullptr); + lv_task_del(taskRefresh); + lv_obj_clean(lv_scr_act()); + } + + void Refresh() override { + auto optWeather = weatherService.Current(); + if (!optWeather || optWeather->sunrise == 0 || optWeather->sunset == 0) { + lv_arc_set_range(sunArc, 0, 1); + lv_arc_set_value(sunArc, 0); + lv_label_set_text(currentTimeLabel, "--:--"); + return; + } + + // Show current time, but also get the hours/minutes to calculate sun position + lv_label_set_text(currentTimeLabel, dateTimeController.FormattedTime().c_str()); + uint16_t currentMinutes = (dateTimeController.Hours() * 60) + dateTimeController.Minutes(); + + uint16_t sunrise = optWeather->sunrise; + uint16_t sunset = optWeather->sunset; + + // Format sunrise/sunset times + char sunriseBuf[12]; + char sunsetBuf[12]; + FormatTime(sunrise, sunriseBuf, sizeof(sunriseBuf), settingsController.GetClockType()); + FormatTime(sunset, sunsetBuf, sizeof(sunsetBuf), settingsController.GetClockType()); + + // Arc always shows today's daylight period (sunrise to sunset) + lv_arc_set_range(sunArc, sunrise, sunset); + + // Calculate target arc value based on current time + int16_t targetValue; + if (currentMinutes < sunrise) { + // Before sunrise: arc at 0% + targetValue = sunrise; + } else if (currentMinutes > sunset) { + // After sunset: arc at 100% + targetValue = sunset; + } else { + // During daylight: arc shows progress + targetValue = currentMinutes; + } + + // Animate arc only on first refresh + if (!arcInitialized) { + arcInitialized = true; + lv_anim_set_values(&anim, sunrise, targetValue); + lv_anim_start(&anim); + } else { + lv_arc_set_value(sunArc, targetValue); + } + + // Sunrise on left + lv_label_set_text(sunriseIcon, Symbols::GetSymbol(Pinetime::Controllers::SimpleWeatherService::Icons::Sun, false)); + lv_obj_align(sunriseIcon, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 30, -70); + lv_label_set_text(sunriseTime, sunriseBuf); + lv_obj_align(sunriseTime, sunriseIcon, LV_ALIGN_OUT_BOTTOM_MID, 0, 5); + + // Sunset on right + lv_label_set_text(sunsetIcon, Symbols::GetSymbol(Pinetime::Controllers::SimpleWeatherService::Icons::Sun, true)); + lv_obj_align(sunsetIcon, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, -30, -70); + lv_label_set_text(sunsetTime, sunsetBuf); + lv_obj_align(sunsetTime, sunsetIcon, LV_ALIGN_OUT_BOTTOM_MID, 0, 5); + } + + private: + Pinetime::Controllers::Settings& settingsController; + Pinetime::Controllers::SimpleWeatherService& weatherService; + Pinetime::Controllers::DateTime& dateTimeController; + + lv_obj_t* sunArc; + lv_obj_t* sunriseIcon; + lv_obj_t* sunriseTime; + lv_obj_t* sunsetIcon; + lv_obj_t* sunsetTime; + lv_obj_t* currentTimeLabel; + lv_task_t* taskRefresh; + lv_anim_t anim; + bool arcInitialized = false; + + Pinetime::Applications::Widgets::PageIndicator pageIndicator; + }; +} + +std::unique_ptr Weather::CreateScreen3() { + return std::make_unique(settingsController, weatherService, dateTimeController); } diff --git a/src/displayapp/screens/Weather.h b/src/displayapp/screens/Weather.h index 03266be105..cf39adede3 100644 --- a/src/displayapp/screens/Weather.h +++ b/src/displayapp/screens/Weather.h @@ -3,11 +3,11 @@ #include #include #include "displayapp/screens/Screen.h" +#include "displayapp/screens/ScreenList.h" #include "components/ble/SimpleWeatherService.h" #include "displayapp/apps/Apps.h" #include "displayapp/Controllers.h" #include "Symbols.h" -#include "utility/DirtyValue.h" namespace Pinetime { @@ -16,30 +16,31 @@ namespace Pinetime { } namespace Applications { + class DisplayApp; + namespace Screens { class Weather : public Screen { public: - Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService); + Weather(DisplayApp* app, + Controllers::Settings& settingsController, + Controllers::SimpleWeatherService& weatherService, + Controllers::DateTime& dateTimeController); ~Weather() override; - void Refresh() override; + bool OnTouchEvent(TouchEvents event) override; private: + DisplayApp* app; Controllers::Settings& settingsController; Controllers::SimpleWeatherService& weatherService; + Controllers::DateTime& dateTimeController; - Utility::DirtyValue> currentWeather {}; - Utility::DirtyValue> currentForecast {}; - - lv_obj_t* icon; - lv_obj_t* condition; - lv_obj_t* temperature; - lv_obj_t* minTemperature; - lv_obj_t* maxTemperature; - lv_obj_t* forecast; + ScreenList<3> screens; - lv_task_t* taskRefresh; + std::unique_ptr CreateScreen1(); + std::unique_ptr CreateScreen2(); + std::unique_ptr CreateScreen3(); }; } @@ -49,7 +50,10 @@ namespace Pinetime { static constexpr const char* icon = Screens::Symbols::cloudSunRain; static Screens::Screen* Create(AppControllers& controllers) { - return new Screens::Weather(controllers.settingsController, *controllers.weatherController); + return new Screens::Weather(controllers.displayApp, + controllers.settingsController, + *controllers.weatherController, + controllers.dateTimeController); }; static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {