From b0a0afdd4b30a663e1651e63be1549f33fdefb7b Mon Sep 17 00:00:00 2001
From: Tim Teichmann <onlineaccounts@mailbox.org>
Date: Sun, 28 Jan 2024 09:46:56 +0100
Subject: [PATCH 1/7] README: Make hex and UTF-8 code consistent for the chosen
 example

---
 src/displayapp/fonts/README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/displayapp/fonts/README.md b/src/displayapp/fonts/README.md
index bcc2d49c1c..20fb4a43e8 100644
--- a/src/displayapp/fonts/README.md
+++ b/src/displayapp/fonts/README.md
@@ -16,7 +16,7 @@
 - Define the new symbols in `src/displayapp/screens/Symbols.h`:
 
 ```
-static constexpr const char* newSymbol = "\xEF\x86\x85";
+static constexpr const char* newSymbol = "\xEF\x99\x81";
 ```
 
 ### the config file format:

From 5d971690cb7fcf51e0215a9eda16136607cf1851 Mon Sep 17 00:00:00 2001
From: Victor Kareh <vkareh@redhat.com>
Date: Mon, 12 Feb 2024 15:59:40 -0500
Subject: [PATCH 2/7] DateTimeController: Make DayOfWeekShortToStringLow static

This allows it to be used outside of the current datetime context and
makes it consistent with the MonthShortToStringLow function.
---
 src/components/datetime/DateTimeController.cpp | 4 ++--
 src/components/datetime/DateTimeController.h   | 2 +-
 src/displayapp/screens/WatchFaceInfineat.cpp   | 3 ++-
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/components/datetime/DateTimeController.cpp b/src/components/datetime/DateTimeController.cpp
index 8d4a834e06..f0ccb5e579 100644
--- a/src/components/datetime/DateTimeController.cpp
+++ b/src/components/datetime/DateTimeController.cpp
@@ -115,8 +115,8 @@ const char* DateTime::MonthShortToStringLow(Months month) {
   return MonthsStringLow[static_cast<uint8_t>(month)];
 }
 
-const char* DateTime::DayOfWeekShortToStringLow() const {
-  return DaysStringShortLow[static_cast<uint8_t>(DayOfWeek())];
+const char* DateTime::DayOfWeekShortToStringLow(Days day) {
+  return DaysStringShortLow[static_cast<uint8_t>(day)];
 }
 
 void DateTime::Register(Pinetime::System::SystemTask* systemTask) {
diff --git a/src/components/datetime/DateTimeController.h b/src/components/datetime/DateTimeController.h
index 0bf6ac2a06..f719df7d52 100644
--- a/src/components/datetime/DateTimeController.h
+++ b/src/components/datetime/DateTimeController.h
@@ -122,7 +122,7 @@ namespace Pinetime {
       const char* MonthShortToString() const;
       const char* DayOfWeekShortToString() const;
       static const char* MonthShortToStringLow(Months month);
-      const char* DayOfWeekShortToStringLow() const;
+      static const char* DayOfWeekShortToStringLow(Days day);
 
       std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> CurrentDateTime() const {
         return currentDateTime;
diff --git a/src/displayapp/screens/WatchFaceInfineat.cpp b/src/displayapp/screens/WatchFaceInfineat.cpp
index 3308303dcc..c643f3bd79 100644
--- a/src/displayapp/screens/WatchFaceInfineat.cpp
+++ b/src/displayapp/screens/WatchFaceInfineat.cpp
@@ -426,7 +426,8 @@ void WatchFaceInfineat::Refresh() {
     currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
     if (currentDate.IsUpdated()) {
       uint8_t day = dateTimeController.Day();
-      lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(), day);
+      Controllers::DateTime::Days dayOfWeek = dateTimeController.DayOfWeek();
+      lv_label_set_text_fmt(labelDate, "%s %02d", dateTimeController.DayOfWeekShortToStringLow(dayOfWeek), day);
       lv_obj_realign(labelDate);
     }
   }

From f422929d8cd0877c56e55d285f3ab3cc1223ea96 Mon Sep 17 00:00:00 2001
From: Victor Kareh <vkareh@redhat.com>
Date: Wed, 31 Jan 2024 17:41:39 -0500
Subject: [PATCH 3/7] weather: Add new app with forecast

---
 src/CMakeLists.txt                        |   1 +
 src/displayapp/DisplayApp.cpp             |   1 +
 src/displayapp/apps/Apps.h.in             |   4 +-
 src/displayapp/apps/CMakeLists.txt        |   2 +-
 src/displayapp/fonts/fonts.json           |   4 +-
 src/displayapp/screens/Weather.cpp        | 156 ++++++++++++++++++++++
 src/displayapp/screens/Weather.h          |  56 ++++++++
 src/displayapp/screens/WeatherSymbols.cpp |  25 ++++
 src/displayapp/screens/WeatherSymbols.h   |   1 +
 9 files changed, 245 insertions(+), 5 deletions(-)
 create mode 100644 src/displayapp/screens/Weather.cpp
 create mode 100644 src/displayapp/screens/Weather.h

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 8d0b792cbd..fd8ece62a6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -379,6 +379,7 @@ list(APPEND SOURCE_FILES
         displayapp/screens/Navigation.cpp
         displayapp/screens/Metronome.cpp
         displayapp/screens/Motion.cpp
+        displayapp/screens/Weather.cpp
         displayapp/screens/FirmwareValidation.cpp
         displayapp/screens/ApplicationList.cpp
         displayapp/screens/Notifications.cpp
diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp
index e5329b2d9d..f3f0bd934b 100644
--- a/src/displayapp/DisplayApp.cpp
+++ b/src/displayapp/DisplayApp.cpp
@@ -27,6 +27,7 @@
 #include "displayapp/screens/BatteryInfo.h"
 #include "displayapp/screens/Steps.h"
 #include "displayapp/screens/Dice.h"
+#include "displayapp/screens/Weather.h"
 #include "displayapp/screens/PassKey.h"
 #include "displayapp/screens/Error.h"
 
diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in
index 77d3b366f3..2104a267c0 100644
--- a/src/displayapp/apps/Apps.h.in
+++ b/src/displayapp/apps/Apps.h.in
@@ -28,6 +28,7 @@ namespace Pinetime {
       Motion,
       Steps,
       Dice,
+      Weather,
       PassKey,
       QuickSettings,
       Settings,
@@ -41,8 +42,7 @@ namespace Pinetime {
       SettingChimes,
       SettingShakeThreshold,
       SettingBluetooth,
-      Error,
-      Weather
+      Error
     };
 
     enum class WatchFace : uint8_t {
diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt
index 51c08595d1..d78587609e 100644
--- a/src/displayapp/apps/CMakeLists.txt
+++ b/src/displayapp/apps/CMakeLists.txt
@@ -13,7 +13,7 @@ else ()
     set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Dice")
     set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Metronome")
     set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Navigation")
-    #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather")
+    set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Weather")
     #set(DEFAULT_USER_APP_TYPES "${DEFAULT_USER_APP_TYPES}, Apps::Motion")
     set(USERAPP_TYPES "${DEFAULT_USER_APP_TYPES}" CACHE STRING "List of user apps to build into the firmware")
 endif ()
diff --git a/src/displayapp/fonts/fonts.json b/src/displayapp/fonts/fonts.json
index a3132504bc..41c383c0d4 100644
--- a/src/displayapp/fonts/fonts.json
+++ b/src/displayapp/fonts/fonts.json
@@ -18,7 +18,7 @@
       "sources": [
          {
             "file": "JetBrainsMono-Regular.ttf",
-            "range": "0x25, 0x2b, 0x2d, 0x2e, 0x30-0x3a, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74"
+            "range": "0x25, 0x2b, 0x2d, 0x2e, 0x30-0x3a, 0x43, 0x46, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74, 0xb0"
          }
       ],
       "bpp": 1,
@@ -28,7 +28,7 @@
       "sources": [
          {
             "file": "JetBrainsMono-Light.ttf",
-            "range": "0x25, 0x2D, 0x2F, 0x30-0x3a"
+            "range": "0x25, 0x2D, 0x2F, 0x30-0x3a, 0x43, 0x46, 0xb0"
          }
       ],
       "bpp": 1,
diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp
new file mode 100644
index 0000000000..d5bdf127ab
--- /dev/null
+++ b/src/displayapp/screens/Weather.cpp
@@ -0,0 +1,156 @@
+#include "displayapp/screens/Weather.h"
+#include <lvgl/lvgl.h>
+#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/InfiniTimeTheme.h"
+
+using namespace Pinetime::Applications::Screens;
+
+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_obj_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, 6);
+  // 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_obj_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, 6);
+
+  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_RIGHT);
+    lv_table_set_cell_align(forecast, 1, i, LV_LABEL_ALIGN_RIGHT);
+    lv_table_set_cell_align(forecast, 2, i, LV_LABEL_ALIGN_RIGHT);
+    lv_table_set_cell_align(forecast, 3, i, LV_LABEL_ALIGN_RIGHT);
+  }
+
+  taskRefresh = lv_task_create(RefreshTaskCallback, 1000, LV_TASK_PRIO_MID, this);
+  Refresh();
+}
+
+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;
+      int16_t minTemp = optCurrentWeather->minTemperature;
+      int16_t maxTemp = optCurrentWeather->maxTemperature;
+      lv_color_t color = Colors::orange;
+      if (temp <= 0) { // freezing
+        color = Colors::blue;
+      } else if (temp <= 400) { // ice danger
+        color = LV_COLOR_CYAN;
+      } else if (temp >= 2700) { // hot
+        color = Colors::deepOrange;
+      }
+      char tempUnit = 'C';
+      if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
+        temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
+        minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
+        maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
+        tempUnit = 'F';
+      }
+      temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
+      maxTemp = maxTemp / 100 + (maxTemp % 100 >= 50 ? 1 : 0);
+      minTemp = minTemp / 100 + (minTemp % 100 >= 50 ? 1 : 0);
+      lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId));
+      lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId));
+      lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
+      lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
+      lv_label_set_text_fmt(minTemperature, "%d°", minTemp);
+      lv_label_set_text_fmt(maxTemperature, "%d°", maxTemp);
+    } else {
+      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(minTemperature, "");
+      lv_label_set_text(maxTemperature, "");
+    }
+  }
+
+  currentForecast = weatherService.GetForecast();
+  if (currentForecast.IsUpdated()) {
+    auto optCurrentForecast = currentForecast.Get();
+    if (optCurrentForecast) {
+      std::tm localTime = *std::localtime(reinterpret_cast<const time_t*>(&optCurrentForecast->timestamp));
+
+      for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
+        int16_t maxTemp = optCurrentForecast->days[i].maxTemperature;
+        int16_t minTemp = optCurrentForecast->days[i].minTemperature;
+        if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
+          maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
+          minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
+        }
+        uint8_t wday = localTime.tm_wday + i + 1;
+        if (wday > 7) {
+          wday -= 7;
+        }
+        maxTemp = maxTemp / 100 + (maxTemp % 100 >= 50 ? 1 : 0);
+        minTemp = minTemp / 100 + (minTemp % 100 >= 50 ? 1 : 0);
+        const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(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_fmt(forecast, 2, i, "%d", maxTemp);
+        lv_table_set_cell_value_fmt(forecast, 3, i, "%d", 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, "");
+      }
+    }
+  }
+}
diff --git a/src/displayapp/screens/Weather.h b/src/displayapp/screens/Weather.h
new file mode 100644
index 0000000000..6975311e06
--- /dev/null
+++ b/src/displayapp/screens/Weather.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include <cstdint>
+#include <lvgl/lvgl.h>
+#include "displayapp/screens/Screen.h"
+#include "components/ble/SimpleWeatherService.h"
+#include "displayapp/apps/Apps.h"
+#include "displayapp/Controllers.h"
+#include "Symbols.h"
+#include "utility/DirtyValue.h"
+
+namespace Pinetime {
+
+  namespace Controllers {
+    class Settings;
+  }
+
+  namespace Applications {
+    namespace Screens {
+
+      class Weather : public Screen {
+      public:
+        Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService);
+        ~Weather() override;
+
+        void Refresh() override;
+
+      private:
+        Controllers::Settings& settingsController;
+        Controllers::SimpleWeatherService& weatherService;
+
+        Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
+        Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::Forecast>> 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;
+
+        lv_task_t* taskRefresh;
+      };
+    }
+
+    template <>
+    struct AppTraits<Apps::Weather> {
+      static constexpr Apps app = Apps::Weather;
+      static constexpr const char* icon = Screens::Symbols::cloudSunRain;
+
+      static Screens::Screen* Create(AppControllers& controllers) {
+        return new Screens::Weather(controllers.settingsController, *controllers.weatherController);
+      };
+    };
+  }
+}
diff --git a/src/displayapp/screens/WeatherSymbols.cpp b/src/displayapp/screens/WeatherSymbols.cpp
index a7749541c3..de66312f90 100644
--- a/src/displayapp/screens/WeatherSymbols.cpp
+++ b/src/displayapp/screens/WeatherSymbols.cpp
@@ -34,3 +34,28 @@ const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime::
       break;
   }
 }
+
+const char* Pinetime::Applications::Screens::Symbols::GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
+  switch (icon) {
+    case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
+      return "Clear sky";
+    case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
+      return "Few clouds";
+    case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
+      return "Scattered clouds";
+    case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds:
+      return "Broken clouds";
+    case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy:
+      return "Shower rain";
+    case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
+      return "Rain";
+    case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm:
+      return "Thunderstorm";
+    case Pinetime::Controllers::SimpleWeatherService::Icons::Snow:
+      return "Snow";
+    case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
+      return "Mist";
+    default:
+      return "";
+  }
+}
diff --git a/src/displayapp/screens/WeatherSymbols.h b/src/displayapp/screens/WeatherSymbols.h
index 93453b4e9a..f3eeed5581 100644
--- a/src/displayapp/screens/WeatherSymbols.h
+++ b/src/displayapp/screens/WeatherSymbols.h
@@ -7,6 +7,7 @@ namespace Pinetime {
     namespace Screens {
       namespace Symbols {
         const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
+        const char* GetCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
       }
     }
   }

From 1857b02efa6607694849036eeba6f6c995f1b447 Mon Sep 17 00:00:00 2001
From: Victor Kareh <vkareh@redhat.com>
Date: Fri, 2 Feb 2024 16:54:40 -0500
Subject: [PATCH 4/7] weather: Colorize forecast temperatures

---
 src/displayapp/screens/Weather.cpp | 54 +++++++++++++++++++++++++-----
 src/libs/lv_conf.h                 |  4 ++-
 2 files changed, 48 insertions(+), 10 deletions(-)

diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp
index d5bdf127ab..b4e9ab9fa6 100644
--- a/src/displayapp/screens/Weather.cpp
+++ b/src/displayapp/screens/Weather.cpp
@@ -9,6 +9,30 @@
 
 using namespace Pinetime::Applications::Screens;
 
+namespace {
+  lv_color_t TemperatureColor(int16_t temperature) {
+    if (temperature <= 0) { // freezing
+      return Colors::blue;
+    } else if (temperature <= 400) { // ice
+      return LV_COLOR_CYAN;
+    } else if (temperature >= 2700) { // hot
+      return Colors::deepOrange;
+    }
+    return Colors::orange; // normal
+  }
+
+  uint8_t TemperatureStyle(int16_t temperature) {
+    if (temperature <= 0) { // freezing
+      return LV_TABLE_PART_CELL3;
+    } else if (temperature <= 400) { // ice
+      return LV_TABLE_PART_CELL4;
+    } else if (temperature >= 2700) { // hot
+      return LV_TABLE_PART_CELL6;
+    }
+    return LV_TABLE_PART_CELL5; // normal
+  }
+}
+
 Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService)
   : settingsController {settingsController}, weatherService {weatherService} {
 
@@ -56,6 +80,22 @@ Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleW
   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_obj_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, 6);
+  // 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_obj_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, 6);
+  // 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_obj_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, 6);
+  // 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_obj_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, 6);
+  // 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_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, 6);
 
   lv_obj_align(forecast, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
 
@@ -85,14 +125,7 @@ void Weather::Refresh() {
       int16_t temp = optCurrentWeather->temperature;
       int16_t minTemp = optCurrentWeather->minTemperature;
       int16_t maxTemp = optCurrentWeather->maxTemperature;
-      lv_color_t color = Colors::orange;
-      if (temp <= 0) { // freezing
-        color = Colors::blue;
-      } else if (temp <= 400) { // ice danger
-        color = LV_COLOR_CYAN;
-      } else if (temp >= 2700) { // hot
-        color = Colors::deepOrange;
-      }
+      lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, TemperatureColor(temp));
       char tempUnit = 'C';
       if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
         temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
@@ -106,7 +139,6 @@ void Weather::Refresh() {
       lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId));
       lv_label_set_text(condition, Symbols::GetCondition(optCurrentWeather->iconId));
       lv_label_set_text_fmt(temperature, "%d°%c", temp, tempUnit);
-      lv_obj_set_style_local_text_color(temperature, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
       lv_label_set_text_fmt(minTemperature, "%d°", minTemp);
       lv_label_set_text_fmt(maxTemperature, "%d°", maxTemp);
     } else {
@@ -128,6 +160,8 @@ void Weather::Refresh() {
       for (int i = 0; i < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {
         int16_t maxTemp = optCurrentForecast->days[i].maxTemperature;
         int16_t minTemp = optCurrentForecast->days[i].minTemperature;
+        lv_table_set_cell_type(forecast, 2, i, TemperatureStyle(maxTemp));
+        lv_table_set_cell_type(forecast, 3, i, TemperatureStyle(minTemp));
         if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
           maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
           minTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(minTemp);
@@ -150,6 +184,8 @@ void Weather::Refresh() {
         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);
       }
     }
   }
diff --git a/src/libs/lv_conf.h b/src/libs/lv_conf.h
index e96778ecf8..c23647f2c0 100644
--- a/src/libs/lv_conf.h
+++ b/src/libs/lv_conf.h
@@ -729,7 +729,9 @@ typedef void* lv_obj_user_data_t;
 #define LV_USE_TABLE    1
 #if LV_USE_TABLE
 #define LV_TABLE_COL_MAX    12
-#define LV_TABLE_CELL_STYLE_CNT 5
+#define LV_TABLE_CELL_STYLE_CNT 6
+#define LV_TABLE_PART_CELL5 5
+#define LV_TABLE_PART_CELL6 6
 #endif
 
 

From 68ae335a97230fb1f00bf508648dc784cd4e0ba2 Mon Sep 17 00:00:00 2001
From: Victor Kareh <vkareh@redhat.com>
Date: Sat, 10 Feb 2024 13:26:47 -0500
Subject: [PATCH 5/7] weather: Pad forecast temperatures

This ensures temperatures are correctly aligned with one another
---
 src/displayapp/screens/Weather.cpp | 29 +++++++++++++++++------------
 1 file changed, 17 insertions(+), 12 deletions(-)

diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp
index b4e9ab9fa6..5d5ab6d419 100644
--- a/src/displayapp/screens/Weather.cpp
+++ b/src/displayapp/screens/Weather.cpp
@@ -74,38 +74,32 @@ Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleW
   // 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_obj_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL1, LV_STATE_DEFAULT, 6);
   // 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_obj_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL2, LV_STATE_DEFAULT, 6);
   // 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_obj_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL3, LV_STATE_DEFAULT, 6);
   // 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_obj_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL4, LV_STATE_DEFAULT, 6);
   // 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_obj_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL5, LV_STATE_DEFAULT, 6);
   // 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_set_style_local_pad_right(forecast, LV_TABLE_PART_CELL6, LV_STATE_DEFAULT, 6);
 
   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_RIGHT);
-    lv_table_set_cell_align(forecast, 1, i, LV_LABEL_ALIGN_RIGHT);
-    lv_table_set_cell_align(forecast, 2, i, LV_LABEL_ALIGN_RIGHT);
-    lv_table_set_cell_align(forecast, 3, i, LV_LABEL_ALIGN_RIGHT);
+    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);
   }
 
   taskRefresh = lv_task_create(RefreshTaskCallback, 1000, LV_TASK_PRIO_MID, this);
@@ -175,8 +169,19 @@ void Weather::Refresh() {
         const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(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_fmt(forecast, 2, i, "%d", maxTemp);
-        lv_table_set_cell_value_fmt(forecast, 3, i, "%d", minTemp);
+        // 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 < Controllers::SimpleWeatherService::MaxNbForecastDays; i++) {

From 6ab512a6b6f83255f5798d0ccf574ee7ca1f287d Mon Sep 17 00:00:00 2001
From: Victor Kareh <vkareh@redhat.com>
Date: Mon, 12 Feb 2024 16:32:52 -0500
Subject: [PATCH 6/7] weather: Define function to round and render temperature

---
 src/displayapp/screens/Weather.cpp | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/src/displayapp/screens/Weather.cpp b/src/displayapp/screens/Weather.cpp
index 5d5ab6d419..5321b7cc29 100644
--- a/src/displayapp/screens/Weather.cpp
+++ b/src/displayapp/screens/Weather.cpp
@@ -31,6 +31,10 @@ namespace {
     }
     return LV_TABLE_PART_CELL5; // normal
   }
+
+  int16_t RoundTemperature(int16_t temp) {
+    return temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
+  }
 }
 
 Weather::Weather(Controllers::Settings& settingsController, Controllers::SimpleWeatherService& weatherService)
@@ -127,14 +131,11 @@ void Weather::Refresh() {
         maxTemp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(maxTemp);
         tempUnit = 'F';
       }
-      temp = temp / 100 + (temp % 100 >= 50 ? 1 : 0);
-      maxTemp = maxTemp / 100 + (maxTemp % 100 >= 50 ? 1 : 0);
-      minTemp = minTemp / 100 + (minTemp % 100 >= 50 ? 1 : 0);
       lv_label_set_text(icon, Symbols::GetSymbol(optCurrentWeather->iconId));
       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);
+      lv_label_set_text_fmt(temperature, "%d°%c", RoundTemperature(temp), tempUnit);
+      lv_label_set_text_fmt(minTemperature, "%d°", RoundTemperature(minTemp));
+      lv_label_set_text_fmt(maxTemperature, "%d°", RoundTemperature(maxTemp));
     } else {
       lv_label_set_text(icon, "");
       lv_label_set_text(condition, "");
@@ -164,8 +165,8 @@ void Weather::Refresh() {
         if (wday > 7) {
           wday -= 7;
         }
-        maxTemp = maxTemp / 100 + (maxTemp % 100 >= 50 ? 1 : 0);
-        minTemp = minTemp / 100 + (minTemp % 100 >= 50 ? 1 : 0);
+        maxTemp = RoundTemperature(maxTemp);
+        minTemp = RoundTemperature(minTemp);
         const char* dayOfWeek = Controllers::DateTime::DayOfWeekShortToStringLow(static_cast<Controllers::DateTime::Days>(wday));
         lv_table_set_cell_value(forecast, 0, i, dayOfWeek);
         lv_table_set_cell_value(forecast, 1, i, Symbols::GetSymbol(optCurrentForecast->days[i].iconId));

From 3d093f31094f6a6506fe80a0b34e9f6443ebaf9c Mon Sep 17 00:00:00 2001
From: Victor Kareh <vkareh@redhat.com>
Date: Thu, 18 Jan 2024 16:08:49 -0500
Subject: [PATCH 7/7] DisplayApp: Go to clock on sleep if no app loaded

When turning off the screen, if there is no actual app loaded (i.e. we
are still in the Launcher, Notifications, QuickSettings, or Settings
screens) we should just reload the Clock app directly.
---
 src/displayapp/DisplayApp.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp
index f3f0bd934b..0c51b202fe 100644
--- a/src/displayapp/DisplayApp.cpp
+++ b/src/displayapp/DisplayApp.cpp
@@ -246,6 +246,11 @@ void DisplayApp::Refresh() {
           vTaskDelay(100);
         }
         lcd.Sleep();
+        // Since the active screen is not really an app, go back to Clock.
+        if (currentApp == Apps::Launcher || currentApp == Apps::Notifications || currentApp == Apps::QuickSettings ||
+            currentApp == Apps::Settings) {
+          LoadScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None);
+        }
         PushMessageToSystemTask(Pinetime::System::Messages::OnDisplayTaskSleeping);
         state = States::Idle;
         break;