Skip to content

Commit 66b10c5

Browse files
committed
Terminal Watchface Improvements and added Weather.
Use @vkareh's new weather forecast app as a template to implement the weather into the terminal watch face. Use InfintimeTheme Colors instead of hardcoded hex values. Reorder code to match the order of the UI. Implement @vkareh's variable battery icon color to the battery percentage text. Added a new IninftimeTheme color, gray, and turn certain values gray when they contain no data, like weather.
1 parent 2135e12 commit 66b10c5

File tree

5 files changed

+151
-61
lines changed

5 files changed

+151
-61
lines changed

src/displayapp/InfiniTimeTheme.h

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Colors {
88
static constexpr lv_color_t green = LV_COLOR_MAKE(0x0, 0xb0, 0x0);
99
static constexpr lv_color_t blue = LV_COLOR_MAKE(0x0, 0x50, 0xff);
1010
static constexpr lv_color_t lightGray = LV_COLOR_MAKE(0xb0, 0xb0, 0xb0);
11+
static constexpr lv_color_t gray = LV_COLOR_MAKE(0x50, 0x50, 0x50);
1112

1213
static constexpr lv_color_t bg = LV_COLOR_MAKE(0x5d, 0x69, 0x7e);
1314
static constexpr lv_color_t bgAlt = LV_COLOR_MAKE(0x38, 0x38, 0x38);

src/displayapp/screens/WatchFaceTerminal.cpp

+110-55
Original file line numberDiff line numberDiff line change
@@ -2,65 +2,84 @@
22
#include "displayapp/screens/WatchFaceTerminal.h"
33
#include "displayapp/screens/BatteryIcon.h"
44
#include "displayapp/screens/NotificationIcon.h"
5-
#include "displayapp/screens/Symbols.h"
65
#include "components/battery/BatteryController.h"
76
#include "components/ble/BleController.h"
87
#include "components/ble/NotificationManager.h"
8+
#include "components/ble/SimpleWeatherService.h"
99
#include "components/heartrate/HeartRateController.h"
1010
#include "components/motion/MotionController.h"
1111
#include "components/settings/Settings.h"
12+
#include "displayapp/screens/WeatherSymbols.h"
13+
#include "displayapp/InfiniTimeTheme.h"
14+
1215

1316
using namespace Pinetime::Applications::Screens;
1417

18+
namespace {
19+
lv_color_t temperatureColor(int16_t temperature) {
20+
if (temperature <= 0) { // freezing
21+
return Colors::blue;
22+
} else if (temperature <= 400) { // ice
23+
return LV_COLOR_CYAN;
24+
} else if (temperature >= 2700) { // hot
25+
return Colors::deepOrange;
26+
}
27+
return Colors::orange; // normal
28+
}
29+
}
30+
1531
WatchFaceTerminal::WatchFaceTerminal(Controllers::DateTime& dateTimeController,
1632
const Controllers::Battery& batteryController,
1733
const Controllers::Ble& bleController,
1834
Controllers::NotificationManager& notificationManager,
1935
Controllers::Settings& settingsController,
2036
Controllers::HeartRateController& heartRateController,
21-
Controllers::MotionController& motionController)
37+
Controllers::MotionController& motionController,
38+
Controllers::SimpleWeatherService& weatherService)
2239
: currentDateTime {{}},
2340
dateTimeController {dateTimeController},
2441
batteryController {batteryController},
2542
bleController {bleController},
2643
notificationManager {notificationManager},
2744
settingsController {settingsController},
2845
heartRateController {heartRateController},
29-
motionController {motionController} {
30-
batteryValue = lv_label_create(lv_scr_act(), nullptr);
31-
lv_label_set_recolor(batteryValue, true);
32-
lv_obj_align(batteryValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -20);
33-
34-
connectState = lv_label_create(lv_scr_act(), nullptr);
35-
lv_label_set_recolor(connectState, true);
36-
lv_obj_align(connectState, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 40);
46+
motionController {motionController},
47+
weatherService {weatherService} {
3748

3849
notificationIcon = lv_label_create(lv_scr_act(), nullptr);
3950
lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_LEFT_MID, 0, -100);
4051

41-
label_date = lv_label_create(lv_scr_act(), nullptr);
42-
lv_label_set_recolor(label_date, true);
43-
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -40);
44-
4552
label_prompt_1 = lv_label_create(lv_scr_act(), nullptr);
4653
lv_obj_align(label_prompt_1, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -80);
4754
lv_label_set_text_static(label_prompt_1, "user@watch:~ $ now");
4855

49-
label_prompt_2 = lv_label_create(lv_scr_act(), nullptr);
50-
lv_obj_align(label_prompt_2, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 60);
51-
lv_label_set_text_static(label_prompt_2, "user@watch:~ $");
52-
5356
label_time = lv_label_create(lv_scr_act(), nullptr);
54-
lv_label_set_recolor(label_time, true);
57+
lv_obj_set_style_local_text_color(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::blue);
5558
lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -60);
5659

57-
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
58-
lv_label_set_recolor(heartbeatValue, true);
59-
lv_obj_align(heartbeatValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 20);
60+
label_date = lv_label_create(lv_scr_act(), nullptr);
61+
lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::blue);
62+
lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -40);
63+
64+
weather = lv_label_create(lv_scr_act(), nullptr);
65+
lv_obj_align(weather, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, -20);
66+
67+
batteryValue = lv_label_create(lv_scr_act(), nullptr);
68+
lv_obj_align(batteryValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);
6069

6170
stepValue = lv_label_create(lv_scr_act(), nullptr);
62-
lv_label_set_recolor(stepValue, true);
63-
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);
71+
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::orange);
72+
lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 20);
73+
74+
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
75+
lv_obj_align(heartbeatValue, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 40);
76+
77+
connectState = lv_label_create(lv_scr_act(), nullptr);
78+
lv_obj_align(connectState, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 60);
79+
80+
label_prompt_2 = lv_label_create(lv_scr_act(), nullptr);
81+
lv_obj_align(label_prompt_2, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 80);
82+
lv_label_set_text_static(label_prompt_2, "user@watch:~ $");
6483

6584
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
6685
Refresh();
@@ -72,29 +91,6 @@ WatchFaceTerminal::~WatchFaceTerminal() {
7291
}
7392

7493
void WatchFaceTerminal::Refresh() {
75-
powerPresent = batteryController.IsPowerPresent();
76-
batteryPercentRemaining = batteryController.PercentRemaining();
77-
if (batteryPercentRemaining.IsUpdated() || powerPresent.IsUpdated()) {
78-
lv_label_set_text_fmt(batteryValue, "[BATT]#387b54 %d%%", batteryPercentRemaining.Get());
79-
if (batteryController.IsPowerPresent()) {
80-
lv_label_ins_text(batteryValue, LV_LABEL_POS_LAST, " Charging");
81-
}
82-
}
83-
84-
bleState = bleController.IsConnected();
85-
bleRadioEnabled = bleController.IsRadioEnabled();
86-
if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) {
87-
if (!bleRadioEnabled.Get()) {
88-
lv_label_set_text_static(connectState, "[STAT]#0082fc Disabled#");
89-
} else {
90-
if (bleState.Get()) {
91-
lv_label_set_text_static(connectState, "[STAT]#0082fc Connected#");
92-
} else {
93-
lv_label_set_text_static(connectState, "[STAT]#0082fc Disconnected#");
94-
}
95-
}
96-
}
97-
9894
notificationState = notificationManager.AreNewNotificationsAvailable();
9995
if (notificationState.IsUpdated()) {
10096
if (notificationState.Get()) {
@@ -104,6 +100,7 @@ void WatchFaceTerminal::Refresh() {
104100
}
105101
}
106102

103+
107104
currentDateTime = std::chrono::time_point_cast<std::chrono::seconds>(dateTimeController.CurrentDateTime());
108105
if (currentDateTime.IsUpdated()) {
109106
uint8_t hour = dateTimeController.Hours();
@@ -120,32 +117,90 @@ void WatchFaceTerminal::Refresh() {
120117
hour = hour - 12;
121118
ampmChar[0] = 'P';
122119
}
123-
lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d %s#", hour, minute, second, ampmChar);
120+
lv_label_set_text_fmt(label_time, "[TIME] %02d:%02d:%02d %s#", hour, minute, second, ampmChar);
124121
} else {
125-
lv_label_set_text_fmt(label_time, "[TIME]#11cc55 %02d:%02d:%02d", hour, minute, second);
122+
lv_label_set_text_fmt(label_time, "[TIME] %02d:%02d:%02d", hour, minute, second);
126123
}
127124

128125
currentDate = std::chrono::time_point_cast<days>(currentDateTime.Get());
129126
if (currentDate.IsUpdated()) {
130127
uint16_t year = dateTimeController.Year();
131128
Controllers::DateTime::Months month = dateTimeController.Month();
132129
uint8_t day = dateTimeController.Day();
133-
lv_label_set_text_fmt(label_date, "[DATE]#007fff %04d-%02d-%02d#", short(year), char(month), char(day));
130+
lv_label_set_text_fmt(label_date, "[DATE] %04d-%02d-%02d#", short(year), char(month), char(day));
131+
}
132+
}
133+
134+
135+
currentWeather = weatherService.Current();
136+
if (currentWeather.IsUpdated()) {
137+
auto optCurrentWeather = currentWeather.Get();
138+
if (optCurrentWeather) {
139+
int16_t temp = optCurrentWeather->temperature;
140+
char tempUnit = 'C';
141+
lv_obj_set_style_local_text_color(weather, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, temperatureColor(temp));
142+
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
143+
condition = Symbols::GetSimpleCondition(optCurrentWeather->iconId);
144+
temp = Controllers::SimpleWeatherService::CelsiusToFahrenheit(temp);
145+
tempUnit = 'F';
146+
}
147+
lv_label_set_text_fmt(weather, "[WTHR] %i°%c %s ", temp/100, tempUnit, condition);
148+
} else {
149+
lv_label_set_text(weather, "[WTHR] ---°");
150+
lv_obj_set_style_local_text_color(weather, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::gray);
134151
}
135152
}
136153

154+
155+
powerPresent = batteryController.IsPowerPresent();
156+
batteryPercentRemaining = batteryController.PercentRemaining();
157+
if (batteryPercentRemaining.IsUpdated() || powerPresent.IsUpdated()) {
158+
// HSV color model has red at 0° and green at 120°.
159+
// We lock satuation and brightness at 100% and traverse the cilinder
160+
// between red and green, thus avoiding the darker RGB on medium battery
161+
// charges and giving us a much nicer color range.
162+
uint8_t hue = batteryPercentRemaining.Get() * 120 / 100;
163+
lv_obj_set_style_local_text_color(batteryValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hsv_to_rgb(hue, 100, 100));
164+
lv_label_set_text_fmt(batteryValue, "[BATT] %d%%", batteryPercentRemaining.Get());
165+
if (batteryController.IsPowerPresent()) {
166+
lv_label_ins_text(batteryValue, LV_LABEL_POS_LAST, " Charging");
167+
}
168+
}
169+
170+
171+
stepCount = motionController.NbSteps();
172+
if (stepCount.IsUpdated()) {
173+
lv_label_set_text_fmt(stepValue, "[STEP] %lu steps#", stepCount.Get());
174+
}
175+
176+
137177
heartbeat = heartRateController.HeartRate();
138178
heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
139179
if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
140180
if (heartbeatRunning.Get()) {
141-
lv_label_set_text_fmt(heartbeatValue, "[L_HR]#ee3311 %d bpm#", heartbeat.Get());
181+
182+
lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::deepOrange);
183+
lv_label_set_text_fmt(heartbeatValue, "[L_HR] %d bpm#", heartbeat.Get());
142184
} else {
143-
lv_label_set_text_static(heartbeatValue, "[L_HR]#ee3311 ---#");
185+
lv_label_set_text_static(heartbeatValue, "[L_HR] ---#");
186+
lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::gray);
144187
}
145188
}
146189

147-
stepCount = motionController.NbSteps();
148-
if (stepCount.IsUpdated()) {
149-
lv_label_set_text_fmt(stepValue, "[STEP]#ee3377 %lu steps#", stepCount.Get());
190+
bleState = bleController.IsConnected();
191+
bleRadioEnabled = bleController.IsRadioEnabled();
192+
if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) {
193+
if (!bleRadioEnabled.Get()) {
194+
lv_label_set_text_static(connectState, "[STAT] Disabled#");
195+
lv_obj_set_style_local_text_color(connectState, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::gray);
196+
} else {
197+
if (bleState.Get()) {
198+
lv_label_set_text_static(connectState, "[STAT] Connected#");
199+
lv_obj_set_style_local_text_color(connectState, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::blue);
200+
} else {
201+
lv_label_set_text_static(connectState, "[STAT] Disconnected#");
202+
lv_obj_set_style_local_text_color(connectState, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, Colors::gray);
203+
}
204+
}
150205
}
151206
}

src/displayapp/screens/WatchFaceTerminal.h

+14-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <displayapp/Controllers.h>
88
#include "displayapp/screens/Screen.h"
99
#include "components/datetime/DateTimeController.h"
10+
#include "components/ble/SimpleWeatherService.h"
1011
#include "utility/DirtyValue.h"
1112

1213
namespace Pinetime {
@@ -30,13 +31,15 @@ namespace Pinetime {
3031
Controllers::NotificationManager& notificationManager,
3132
Controllers::Settings& settingsController,
3233
Controllers::HeartRateController& heartRateController,
33-
Controllers::MotionController& motionController);
34+
Controllers::MotionController& motionController,
35+
Controllers::SimpleWeatherService& weatherService);
3436
~WatchFaceTerminal() override;
3537

3638
void Refresh() override;
3739

3840
private:
3941
Utility::DirtyValue<int> batteryPercentRemaining {};
42+
Utility::DirtyValue<uint8_t> hue {};
4043
Utility::DirtyValue<bool> powerPresent {};
4144
Utility::DirtyValue<bool> bleState {};
4245
Utility::DirtyValue<bool> bleRadioEnabled {};
@@ -47,16 +50,19 @@ namespace Pinetime {
4750
Utility::DirtyValue<bool> notificationState {};
4851
using days = std::chrono::duration<int32_t, std::ratio<86400>>; // TODO: days is standard in c++20
4952
Utility::DirtyValue<std::chrono::time_point<std::chrono::system_clock, days>> currentDate;
53+
Utility::DirtyValue<std::optional<Controllers::SimpleWeatherService::CurrentWeather>> currentWeather {};
54+
Utility::DirtyValue<const char*> condition {};
5055

56+
lv_obj_t* notificationIcon;
57+
lv_obj_t* label_prompt_1;
5158
lv_obj_t* label_time;
5259
lv_obj_t* label_date;
53-
lv_obj_t* label_prompt_1;
54-
lv_obj_t* label_prompt_2;
60+
lv_obj_t* weather;
5561
lv_obj_t* batteryValue;
56-
lv_obj_t* heartbeatValue;
5762
lv_obj_t* stepValue;
58-
lv_obj_t* notificationIcon;
63+
lv_obj_t* heartbeatValue;
5964
lv_obj_t* connectState;
65+
lv_obj_t* label_prompt_2;
6066

6167
Controllers::DateTime& dateTimeController;
6268
const Controllers::Battery& batteryController;
@@ -65,6 +71,7 @@ namespace Pinetime {
6571
Controllers::Settings& settingsController;
6672
Controllers::HeartRateController& heartRateController;
6773
Controllers::MotionController& motionController;
74+
Controllers::SimpleWeatherService& weatherService;
6875

6976
lv_task_t* taskRefresh;
7077
};
@@ -82,7 +89,8 @@ namespace Pinetime {
8289
controllers.notificationManager,
8390
controllers.settingsController,
8491
controllers.heartRateController,
85-
controllers.motionController);
92+
controllers.motionController,
93+
*controllers.weatherController);
8694
};
8795

8896
static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) {

src/displayapp/screens/WeatherSymbols.cpp

+25
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,28 @@ const char* Pinetime::Applications::Screens::Symbols::GetSymbol(const Pinetime::
3434
break;
3535
}
3636
}
37+
38+
const char* Pinetime::Applications::Screens::Symbols::GetSimpleCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon) {
39+
switch (icon) {
40+
case Pinetime::Controllers::SimpleWeatherService::Icons::Sun:
41+
return "Clear";
42+
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudsSun:
43+
return "Cloudy";
44+
case Pinetime::Controllers::SimpleWeatherService::Icons::Clouds:
45+
return "Cloudy";
46+
case Pinetime::Controllers::SimpleWeatherService::Icons::BrokenClouds:
47+
return "Cloudy";
48+
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudShowerHeavy:
49+
return "Rainy";
50+
case Pinetime::Controllers::SimpleWeatherService::Icons::CloudSunRain:
51+
return "Rainy";
52+
case Pinetime::Controllers::SimpleWeatherService::Icons::Thunderstorm:
53+
return "Stormy";
54+
case Pinetime::Controllers::SimpleWeatherService::Icons::Snow:
55+
return "Snowy";
56+
case Pinetime::Controllers::SimpleWeatherService::Icons::Smog:
57+
return "Misty";
58+
default:
59+
return "";
60+
}
61+
}

src/displayapp/screens/WeatherSymbols.h

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace Pinetime {
77
namespace Screens {
88
namespace Symbols {
99
const char* GetSymbol(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
10+
const char* GetSimpleCondition(const Pinetime::Controllers::SimpleWeatherService::Icons icon);
1011
}
1112
}
1213
}

0 commit comments

Comments
 (0)