summaryrefslogtreecommitdiff
path: root/src/displayapp/screens
diff options
context:
space:
mode:
authorJF <jf@codingfield.com>2020-11-10 19:32:36 (GMT)
committerJF <jf@codingfield.com>2020-11-10 19:32:36 (GMT)
commit04abc91f157f5925ffa404728291a69893acf8cf (patch)
tree5c887c6d22ba8d901022ebae395a7b6c165d9dc0 /src/displayapp/screens
parent65ecb65b57bd55582c1aa1a5babd4d76df89e621 (diff)
parentf0e1f98823e41bfc2d9743fa8de70c882f26f93b (diff)
Merge branch 'develop' into master
Diffstat (limited to 'src/displayapp/screens')
-rw-r--r--src/displayapp/screens/ApplicationList.cpp82
-rw-r--r--src/displayapp/screens/ApplicationList.h33
-rw-r--r--src/displayapp/screens/BatteryIcon.cpp21
-rw-r--r--src/displayapp/screens/BatteryIcon.h16
-rw-r--r--src/displayapp/screens/BleIcon.cpp8
-rw-r--r--src/displayapp/screens/BleIcon.h12
-rw-r--r--src/displayapp/screens/Brightness.cpp92
-rw-r--r--src/displayapp/screens/Brightness.h33
-rw-r--r--src/displayapp/screens/Clock.cpp244
-rw-r--r--src/displayapp/screens/Clock.h93
-rw-r--r--src/displayapp/screens/DropDownDemo.cpp64
-rw-r--r--src/displayapp/screens/DropDownDemo.h29
-rw-r--r--src/displayapp/screens/FirmwareUpdate.cpp82
-rw-r--r--src/displayapp/screens/FirmwareUpdate.h42
-rw-r--r--src/displayapp/screens/FirmwareValidation.cpp91
-rw-r--r--src/displayapp/screens/FirmwareValidation.h42
-rw-r--r--src/displayapp/screens/Gauge.cpp58
-rw-r--r--src/displayapp/screens/Gauge.h32
-rw-r--r--src/displayapp/screens/InfiniPaint.cpp44
-rw-r--r--src/displayapp/screens/InfiniPaint.h39
-rw-r--r--src/displayapp/screens/Label.cpp15
-rw-r--r--src/displayapp/screens/Label.h23
-rw-r--r--src/displayapp/screens/Meter.cpp47
-rw-r--r--src/displayapp/screens/Meter.h32
-rw-r--r--src/displayapp/screens/Modal.cpp81
-rw-r--r--src/displayapp/screens/Modal.h39
-rw-r--r--src/displayapp/screens/Music.cpp292
-rw-r--r--src/displayapp/screens/Music.h96
-rw-r--r--src/displayapp/screens/NotificationIcon.cpp8
-rw-r--r--src/displayapp/screens/NotificationIcon.h12
-rw-r--r--src/displayapp/screens/Notifications.cpp174
-rw-r--r--src/displayapp/screens/Notifications.h61
-rw-r--r--src/displayapp/screens/Screen.cpp2
-rw-r--r--src/displayapp/screens/Screen.h42
-rw-r--r--src/displayapp/screens/ScreenList.h66
-rw-r--r--src/displayapp/screens/Symbols.h30
-rw-r--r--src/displayapp/screens/SystemInfo.cpp116
-rw-r--r--src/displayapp/screens/SystemInfo.h48
-rw-r--r--src/displayapp/screens/Tab.cpp67
-rw-r--r--src/displayapp/screens/Tab.h23
-rw-r--r--src/displayapp/screens/Tile.cpp62
-rw-r--r--src/displayapp/screens/Tile.h39
42 files changed, 2532 insertions, 0 deletions
diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp
new file mode 100644
index 0000000..7eb9718
--- /dev/null
+++ b/src/displayapp/screens/ApplicationList.cpp
@@ -0,0 +1,82 @@
+#include <libs/lvgl/lvgl.h>
+#include <displayapp/DisplayApp.h>
+#include <functional>
+#include "ApplicationList.h"
+#include "Tile.h"
+#include "Symbols.h"
+
+using namespace Pinetime::Applications::Screens;
+
+ApplicationList::ApplicationList(Pinetime::Applications::DisplayApp *app) :
+ Screen(app),
+ screens{app, {
+ [this]() -> std::unique_ptr<Screen> { return CreateScreen1(); },
+ [this]() -> std::unique_ptr<Screen> { return CreateScreen2(); },
+ //[this]() -> std::unique_ptr<Screen> { return CreateScreen3(); }
+ }
+ } {}
+
+
+ApplicationList::~ApplicationList() {
+ lv_obj_clean(lv_scr_act());
+}
+
+bool ApplicationList::Refresh() {
+ if(running)
+ running = screens.Refresh();
+ return running;
+}
+
+bool ApplicationList::OnButtonPushed() {
+ running = false;
+ app->StartApp(Apps::Clock);
+ return true;
+}
+
+bool ApplicationList::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
+ return screens.OnTouchEvent(event);
+}
+
+std::unique_ptr<Screen> ApplicationList::CreateScreen1() {
+ std::array<Screens::Tile::Applications, 6> applications {
+ {{Symbols::clock, Apps::Clock},
+ {Symbols::music, Apps::Music},
+ {Symbols::sun, Apps::Brightness},
+ {Symbols::list, Apps::SysInfo},
+ {Symbols::check, Apps::FirmwareValidation},
+ {Symbols::none, Apps::None}
+ }
+
+
+ };
+
+ return std::unique_ptr<Screen>(new Screens::Tile(app, applications));
+}
+
+std::unique_ptr<Screen> ApplicationList::CreateScreen2() {
+ std::array<Screens::Tile::Applications, 6> applications {
+ {{Symbols::tachometer, Apps::Gauge},
+ {Symbols::asterisk, Apps::Meter},
+ {Symbols::paintbrush, Apps::Paint},
+ {Symbols::info, Apps::Notifications},
+ {Symbols::none, Apps::None},
+ {Symbols::none, Apps::None}
+ }
+ };
+
+ return std::unique_ptr<Screen>(new Screens::Tile(app, applications));
+}
+
+std::unique_ptr<Screen> ApplicationList::CreateScreen3() {
+ std::array<Screens::Tile::Applications, 6> applications {
+ {{"A", Apps::Meter},
+ {"B", Apps::Gauge},
+ {"C", Apps::Clock},
+ {"D", Apps::Music},
+ {"E", Apps::SysInfo},
+ {"F", Apps::Brightness}
+ }
+ };
+
+ return std::unique_ptr<Screen>(new Screens::Tile(app, applications));
+}
diff --git a/src/displayapp/screens/ApplicationList.h b/src/displayapp/screens/ApplicationList.h
new file mode 100644
index 0000000..9c95acb
--- /dev/null
+++ b/src/displayapp/screens/ApplicationList.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <functional>
+#include <vector>
+
+#include "components/ble/NimbleController.h"
+#include "Screen.h"
+#include "Label.h"
+#include "ScreenList.h"
+#include "Gauge.h"
+#include "Meter.h"
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ class ApplicationList : public Screen {
+ public:
+ explicit ApplicationList(DisplayApp* app);
+ ~ApplicationList() override;
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+ bool OnTouchEvent(TouchEvents event) override;
+ private:
+ bool running = true;
+
+ ScreenList<2> screens;
+ std::unique_ptr<Screen> CreateScreen1();
+ std::unique_ptr<Screen> CreateScreen2();
+ std::unique_ptr<Screen> CreateScreen3();
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/displayapp/screens/BatteryIcon.cpp b/src/displayapp/screens/BatteryIcon.cpp
new file mode 100644
index 0000000..26939d1
--- /dev/null
+++ b/src/displayapp/screens/BatteryIcon.cpp
@@ -0,0 +1,21 @@
+#include "BatteryIcon.h"
+#include "Symbols.h"
+using namespace Pinetime::Applications::Screens;
+
+const char* BatteryIcon::GetBatteryIcon(float batteryPercent) {
+ if(batteryPercent > 90.0f) return Symbols::batteryFull;
+ if(batteryPercent > 75.0f) return Symbols::batteryThreeQuarter;
+ if(batteryPercent > 50.0f) return Symbols::batteryHalf;
+ if(batteryPercent > 25.0f) return Symbols::batteryOneQuarter;
+ return Symbols::batteryEmpty;
+}
+
+const char* BatteryIcon::GetUnknownIcon() {
+ return Symbols::batteryEmpty;
+}
+
+const char *BatteryIcon::GetPlugIcon(bool isCharging) {
+ if(isCharging)
+ return Symbols::plug;
+ else return "";
+}
diff --git a/src/displayapp/screens/BatteryIcon.h b/src/displayapp/screens/BatteryIcon.h
new file mode 100644
index 0000000..58f04a8
--- /dev/null
+++ b/src/displayapp/screens/BatteryIcon.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <libs/lvgl/src/lv_draw/lv_img_decoder.h>
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ class BatteryIcon {
+ public:
+ static const char* GetUnknownIcon();
+ static const char* GetBatteryIcon(float batteryPercent);
+ static const char* GetPlugIcon(bool isCharging);
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/displayapp/screens/BleIcon.cpp b/src/displayapp/screens/BleIcon.cpp
new file mode 100644
index 0000000..1bbbd05
--- /dev/null
+++ b/src/displayapp/screens/BleIcon.cpp
@@ -0,0 +1,8 @@
+#include "BleIcon.h"
+#include "Symbols.h"
+using namespace Pinetime::Applications::Screens;
+
+const char* BleIcon::GetIcon(bool isConnected) {
+ if(isConnected) return Symbols::bluetooth;
+ else return "";
+} \ No newline at end of file
diff --git a/src/displayapp/screens/BleIcon.h b/src/displayapp/screens/BleIcon.h
new file mode 100644
index 0000000..c1398d2
--- /dev/null
+++ b/src/displayapp/screens/BleIcon.h
@@ -0,0 +1,12 @@
+#pragma once
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ class BleIcon {
+ public:
+ static const char* GetIcon(bool isConnected);
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/displayapp/screens/Brightness.cpp b/src/displayapp/screens/Brightness.cpp
new file mode 100644
index 0000000..8ea9a77
--- /dev/null
+++ b/src/displayapp/screens/Brightness.cpp
@@ -0,0 +1,92 @@
+#include <libs/lvgl/lvgl.h>
+#include "Brightness.h"
+
+using namespace Pinetime::Applications::Screens;
+
+void slider_event_cb(lv_obj_t * slider, lv_event_t event) {
+ if(event == LV_EVENT_VALUE_CHANGED) {
+ auto* brightnessSlider = static_cast<Brightness*>(slider->user_data);
+ brightnessSlider->OnValueChanged();
+ }
+}
+
+Brightness::Brightness(Pinetime::Applications::DisplayApp *app, Controllers::BrightnessController& brightness) : Screen(app), brightness{brightness} {
+ slider = lv_slider_create(lv_scr_act(), nullptr);
+ lv_obj_set_user_data(slider, this);
+ lv_obj_set_width(slider, LV_DPI * 2);
+ lv_obj_align(slider, nullptr, LV_ALIGN_CENTER, 0, 0);
+ lv_obj_set_event_cb(slider, slider_event_cb);
+ lv_slider_set_range(slider, 0, 2);
+ lv_slider_set_value(slider, LevelToInt(brightness.Level()), LV_ANIM_OFF);
+
+ slider_label = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(slider_label, LevelToString(brightness.Level()));
+ lv_obj_set_auto_realign(slider_label, true);
+ lv_obj_align(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
+}
+
+Brightness::~Brightness() {
+ lv_obj_clean(lv_scr_act());
+}
+
+bool Brightness::Refresh() {
+ return running;
+}
+
+bool Brightness::OnButtonPushed() {
+ running = false;
+ return true;
+}
+
+const char *Brightness::LevelToString(Pinetime::Controllers::BrightnessController::Levels level) {
+ switch(level) {
+ case Pinetime::Controllers::BrightnessController::Levels::Off: return "Off";
+ case Pinetime::Controllers::BrightnessController::Levels::Low: return "Low";
+ case Pinetime::Controllers::BrightnessController::Levels::Medium: return "Medium";
+ case Pinetime::Controllers::BrightnessController::Levels::High: return "High";
+ default : return "???";
+ }
+}
+
+void Brightness::OnValueChanged() {
+ SetValue(lv_slider_get_value(slider));
+}
+
+void Brightness::SetValue(uint8_t value) {
+ switch(value) {
+ case 0: brightness.Set(Controllers::BrightnessController::Levels::Low); break;
+ case 1: brightness.Set(Controllers::BrightnessController::Levels::Medium); break;
+ case 2: brightness.Set(Controllers::BrightnessController::Levels::High); break;
+ }
+ lv_label_set_text(slider_label, LevelToString(brightness.Level()));
+}
+
+uint8_t Brightness::LevelToInt(Pinetime::Controllers::BrightnessController::Levels level) {
+ switch(level) {
+ case Pinetime::Controllers::BrightnessController::Levels::Off: return 0;
+ case Pinetime::Controllers::BrightnessController::Levels::Low: return 0;
+ case Pinetime::Controllers::BrightnessController::Levels::Medium: return 1;
+ case Pinetime::Controllers::BrightnessController::Levels::High: return 2;
+ default : return 0;
+ }
+}
+
+bool Brightness::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
+ switch(event) {
+ case TouchEvents::SwipeLeft:
+ brightness.Lower();
+ SetValue();
+ return true;
+ case TouchEvents::SwipeRight:
+ brightness.Higher();
+ SetValue();
+ return true;
+ default:
+ return false;
+ }
+}
+
+void Brightness::SetValue() {
+ lv_slider_set_value(slider, LevelToInt(brightness.Level()), LV_ANIM_OFF);
+ lv_label_set_text(slider_label, LevelToString(brightness.Level()));
+}
diff --git a/src/displayapp/screens/Brightness.h b/src/displayapp/screens/Brightness.h
new file mode 100644
index 0000000..7d599ac
--- /dev/null
+++ b/src/displayapp/screens/Brightness.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+#include "components/brightness/BrightnessController.h"
+#include "Screen.h"
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ class Brightness : public Screen {
+ public:
+ Brightness(DisplayApp* app, Controllers::BrightnessController& brightness);
+ ~Brightness() override;
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+ bool OnTouchEvent(TouchEvents event) override;
+
+ void OnValueChanged();
+ private:
+ bool running = true;
+ Controllers::BrightnessController& brightness;
+
+ lv_obj_t * slider_label;
+ lv_obj_t * slider;
+
+ const char* LevelToString(Controllers::BrightnessController::Levels level);
+ uint8_t LevelToInt(Controllers::BrightnessController::Levels level);
+ void SetValue(uint8_t value);
+ void SetValue();
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/displayapp/screens/Clock.cpp b/src/displayapp/screens/Clock.cpp
new file mode 100644
index 0000000..977321c1
--- /dev/null
+++ b/src/displayapp/screens/Clock.cpp
@@ -0,0 +1,244 @@
+#include <cstdio>
+
+#include <libs/date/includes/date/date.h>
+#include "components/datetime/DateTimeController.h"
+#include <libs/lvgl/lvgl.h>
+#include "Clock.h"
+#include "../DisplayApp.h"
+#include "BatteryIcon.h"
+#include "BleIcon.h"
+#include "Symbols.h"
+#include "components/ble/NotificationManager.h"
+#include "NotificationIcon.h"
+
+using namespace Pinetime::Applications::Screens;
+extern lv_font_t jetbrains_mono_extrabold_compressed;
+extern lv_font_t jetbrains_mono_bold_20;
+extern lv_style_t* LabelBigStyle;
+
+static void event_handler(lv_obj_t * obj, lv_event_t event) {
+ Clock* screen = static_cast<Clock *>(obj->user_data);
+ screen->OnObjectEvent(obj, event);
+}
+
+Clock::Clock(DisplayApp* app,
+ Controllers::DateTime& dateTimeController,
+ Controllers::Battery& batteryController,
+ Controllers::Ble& bleController,
+ Controllers::NotificationManager& notificatioManager) : Screen(app), currentDateTime{{}},
+ dateTimeController{dateTimeController}, batteryController{batteryController},
+ bleController{bleController}, notificatioManager{notificatioManager} {
+ displayedChar[0] = 0;
+ displayedChar[1] = 0;
+ displayedChar[2] = 0;
+ displayedChar[3] = 0;
+ displayedChar[4] = 0;
+
+ batteryIcon = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(batteryIcon, Symbols::batteryFull);
+ lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 2);
+
+ batteryPlug = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(batteryPlug, Symbols::plug);
+ lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+
+ bleIcon = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(bleIcon, Symbols::bluetooth);
+ lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+
+ notificationIcon = lv_label_create(lv_scr_act(), NULL);
+ lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
+ lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 10, 0);
+
+ label_date = lv_label_create(lv_scr_act(), nullptr);
+
+ lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 60);
+
+ label_time = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_style(label_time, LV_LABEL_STYLE_MAIN, LabelBigStyle);
+ lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);
+
+ backgroundLabel = lv_label_create(lv_scr_act(), nullptr);
+ backgroundLabel->user_data = this;
+ lv_obj_set_click(backgroundLabel, true);
+ lv_obj_set_event_cb(backgroundLabel, event_handler);
+ lv_label_set_long_mode(backgroundLabel, LV_LABEL_LONG_CROP);
+ lv_obj_set_size(backgroundLabel, 240, 240);
+ lv_obj_set_pos(backgroundLabel, 0, 0);
+ lv_label_set_text(backgroundLabel, "");
+
+
+ heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(heartbeatIcon, Symbols::heartBeat);
+ lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2);
+
+ heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(heartbeatValue, "0");
+ lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
+
+ heartbeatBpm = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(heartbeatBpm, "BPM");
+ lv_obj_align(heartbeatBpm, heartbeatValue, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
+
+ stepValue = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(stepValue, "0");
+ lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2);
+
+ stepIcon = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(stepIcon, Symbols::shoe);
+ lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+}
+
+Clock::~Clock() {
+ lv_obj_clean(lv_scr_act());
+}
+
+bool Clock::Refresh() {
+ batteryPercentRemaining = batteryController.PercentRemaining();
+ if (batteryPercentRemaining.IsUpdated()) {
+ auto batteryPercent = batteryPercentRemaining.Get();
+ lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
+ auto isCharging = batteryController.IsCharging() || batteryController.IsPowerPresent();
+ lv_label_set_text(batteryPlug, BatteryIcon::GetPlugIcon(isCharging));
+ }
+
+ bleState = bleController.IsConnected();
+ if (bleState.IsUpdated()) {
+ if(bleState.Get() == true) {
+ lv_label_set_text(bleIcon, BleIcon::GetIcon(true));
+ } else {
+ lv_label_set_text(bleIcon, BleIcon::GetIcon(false));
+ }
+ }
+ lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 5);
+ lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+ lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+
+ notificationState = notificatioManager.AreNewNotificationsAvailable();
+ if(notificationState.IsUpdated()) {
+ if(notificationState.Get() == true)
+ lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(true));
+ else
+ lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
+ }
+
+ currentDateTime = dateTimeController.CurrentDateTime();
+
+ if(currentDateTime.IsUpdated()) {
+ auto newDateTime = currentDateTime.Get();
+
+ auto dp = date::floor<date::days>(newDateTime);
+ auto time = date::make_time(newDateTime-dp);
+ auto yearMonthDay = date::year_month_day(dp);
+
+ auto year = (int)yearMonthDay.year();
+ auto month = static_cast<Pinetime::Controllers::DateTime::Months>((unsigned)yearMonthDay.month());
+ auto day = (unsigned)yearMonthDay.day();
+ auto dayOfWeek = static_cast<Pinetime::Controllers::DateTime::Days>(date::weekday(yearMonthDay).iso_encoding());
+
+ auto hour = time.hours().count();
+ auto minute = time.minutes().count();
+
+ char minutesChar[3];
+ sprintf(minutesChar, "%02d", static_cast<int>(minute));
+
+ char hoursChar[3];
+ sprintf(hoursChar, "%02d", static_cast<int>(hour));
+
+ char timeStr[6];
+ sprintf(timeStr, "%c%c:%c%c", hoursChar[0],hoursChar[1],minutesChar[0], minutesChar[1]);
+
+ if(hoursChar[0] != displayedChar[0] || hoursChar[1] != displayedChar[1] || minutesChar[0] != displayedChar[2] || minutesChar[1] != displayedChar[3]) {
+ displayedChar[0] = hoursChar[0];
+ displayedChar[1] = hoursChar[1];
+ displayedChar[2] = minutesChar[0];
+ displayedChar[3] = minutesChar[1];
+
+ lv_label_set_text(label_time, timeStr);
+ }
+
+ if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) {
+ char dateStr[22];
+ sprintf(dateStr, "%s %d %s %d", DayOfWeekToString(dayOfWeek), day, MonthToString(month), year);
+ lv_label_set_text(label_date, dateStr);
+
+
+ currentYear = year;
+ currentMonth = month;
+ currentDayOfWeek = dayOfWeek;
+ currentDay = day;
+ }
+ }
+
+ // TODO heartbeat = heartBeatController.GetValue();
+ if(heartbeat.IsUpdated()) {
+ char heartbeatBuffer[4];
+ sprintf(heartbeatBuffer, "%d", heartbeat.Get());
+ lv_label_set_text(heartbeatValue, heartbeatBuffer);
+ lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2);
+ lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
+ lv_obj_align(heartbeatBpm, heartbeatValue, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
+ }
+
+ // TODO stepCount = stepController.GetValue();
+ if(stepCount.IsUpdated()) {
+ char stepBuffer[5];
+ sprintf(stepBuffer, "%lu", stepCount.Get());
+ lv_label_set_text(stepValue, stepBuffer);
+ lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2);
+ lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+ }
+
+ return running;
+}
+
+const char *Clock::MonthToString(Pinetime::Controllers::DateTime::Months month) {
+ return Clock::MonthsString[static_cast<uint8_t>(month)];
+}
+
+const char *Clock::DayOfWeekToString(Pinetime::Controllers::DateTime::Days dayOfWeek) {
+ return Clock::DaysString[static_cast<uint8_t>(dayOfWeek)];
+}
+
+char const *Clock::DaysString[] = {
+ "",
+ "MONDAY",
+ "TUESDAY",
+ "WEDNESDAY",
+ "THURSDAY",
+ "FRIDAY",
+ "SATURDAY",
+ "SUNDAY"
+};
+
+char const *Clock::MonthsString[] = {
+ "",
+ "JAN",
+ "FEB",
+ "MAR",
+ "APR",
+ "MAY",
+ "JUN",
+ "JUL",
+ "AUG",
+ "SEP",
+ "OCT",
+ "NOV",
+ "DEC"
+};
+
+void Clock::OnObjectEvent(lv_obj_t *obj, lv_event_t event) {
+ if(obj == backgroundLabel) {
+ if (event == LV_EVENT_CLICKED) {
+
+ running = false;
+ }
+ }
+}
+
+bool Clock::OnButtonPushed() {
+ running = false;
+ return false;
+}
+
+
diff --git a/src/displayapp/screens/Clock.h b/src/displayapp/screens/Clock.h
new file mode 100644
index 0000000..58149a7
--- /dev/null
+++ b/src/displayapp/screens/Clock.h
@@ -0,0 +1,93 @@
+#pragma once
+
+#include <cstdint>
+#include <chrono>
+
+#include "Screen.h"
+#include <bits/unique_ptr.h>
+#include <libs/lvgl/src/lv_core/lv_style.h>
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+#include "components/ble/NotificationManager.h"
+#include "components/battery/BatteryController.h"
+#include "components/ble/BleController.h"
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+
+ template <class T>
+ class DirtyValue {
+ public:
+ explicit DirtyValue(T v) { value = v; }
+ explicit DirtyValue(T& v) { value = v; }
+ bool IsUpdated() const { return isUpdated; }
+ T& Get() { this->isUpdated = false; return value; }
+
+ DirtyValue& operator=(const T& other) {
+ if (this->value != other) {
+ this->value = other;
+ this->isUpdated = true;
+ }
+ return *this;
+ }
+ private:
+ T value;
+ bool isUpdated = true;
+ };
+ class Clock : public Screen{
+ public:
+ Clock(DisplayApp* app,
+ Controllers::DateTime& dateTimeController,
+ Controllers::Battery& batteryController,
+ Controllers::Ble& bleController,
+ Controllers::NotificationManager& notificatioManager);
+ ~Clock() override;
+
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+
+ void OnObjectEvent(lv_obj_t *pObj, lv_event_t i);
+ private:
+ static const char* MonthToString(Pinetime::Controllers::DateTime::Months month);
+ static const char* DayOfWeekToString(Pinetime::Controllers::DateTime::Days dayOfWeek);
+ static char const *DaysString[];
+ static char const *MonthsString[];
+
+ char displayedChar[5];
+
+ uint16_t currentYear = 1970;
+ Pinetime::Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown;
+ Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown;
+ uint8_t currentDay = 0;
+
+ DirtyValue<float> batteryPercentRemaining {0};
+ DirtyValue<bool> bleState {false};
+ DirtyValue<std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>> currentDateTime;
+ DirtyValue<uint32_t> stepCount {0};
+ DirtyValue<uint8_t> heartbeat {0};
+ DirtyValue<bool> notificationState {false};
+
+ lv_obj_t* label_time;
+ lv_obj_t* label_date;
+ lv_obj_t* backgroundLabel;
+ lv_obj_t* batteryIcon;
+ lv_obj_t* bleIcon;
+ lv_obj_t* batteryPlug;
+ lv_obj_t* heartbeatIcon;
+ lv_obj_t* heartbeatValue;
+ lv_obj_t* heartbeatBpm;
+ lv_obj_t* stepIcon;
+ lv_obj_t* stepValue;
+ lv_obj_t* notificationIcon;
+
+ Controllers::DateTime& dateTimeController;
+ Controllers::Battery& batteryController;
+ Controllers::Ble& bleController;
+ Controllers::NotificationManager& notificatioManager;
+
+ bool running = true;
+
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/DropDownDemo.cpp b/src/displayapp/screens/DropDownDemo.cpp
new file mode 100644
index 0000000..ce3acd5
--- /dev/null
+++ b/src/displayapp/screens/DropDownDemo.cpp
@@ -0,0 +1,64 @@
+#include <libs/lvgl/lvgl.h>
+#include <libraries/log/nrf_log.h>
+#include "DropDownDemo.h"
+#include "../DisplayApp.h"
+
+using namespace Pinetime::Applications::Screens;
+extern lv_font_t jetbrains_mono_extrabold_compressed;
+extern lv_font_t jetbrains_mono_bold_20;
+
+DropDownDemo::DropDownDemo(Pinetime::Applications::DisplayApp *app) : Screen(app) {
+ // Create the dropdown object, with many item, and fix its height
+ ddlist = lv_ddlist_create(lv_scr_act(), nullptr);
+ lv_ddlist_set_options(ddlist, "Apple\n"
+ "Banana\n"
+ "Orange\n"
+ "Melon\n"
+ "Grape\n"
+ "Raspberry\n"
+ "A\n"
+ "B\n"
+ "C\n"
+ "D\n"
+ "E");
+ lv_ddlist_set_fix_width(ddlist, 150);
+ lv_ddlist_set_draw_arrow(ddlist, true);
+ lv_ddlist_set_fix_height(ddlist, 150);
+ lv_obj_align(ddlist, nullptr, LV_ALIGN_IN_TOP_MID, 0, 20);
+}
+
+DropDownDemo::~DropDownDemo() {
+ // Reset the touchmode
+ app->SetTouchMode(DisplayApp::TouchModes::Gestures);
+ lv_obj_clean(lv_scr_act());
+}
+
+bool DropDownDemo::Refresh() {
+ auto* list = static_cast<lv_ddlist_ext_t *>(ddlist->ext_attr);
+
+ // Switch touchmode to Polling if the dropdown is opened. This will allow to scroll inside the
+ // dropdown while it is opened.
+ // Disable the polling mode when the dropdown is closed to be able to handle the gestures.
+ if(list->opened)
+ app->SetTouchMode(DisplayApp::TouchModes::Polling);
+ else
+ app->SetTouchMode(DisplayApp::TouchModes::Gestures);
+ return running;
+}
+
+bool DropDownDemo::OnButtonPushed() {
+ running = false;
+ return true;
+}
+
+bool DropDownDemo::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
+ // If the dropdown is opened, notify Display app that it doesn't need to handle the event
+ // (this will prevent displayApp from going back to the menu or clock scree).
+ auto* list = static_cast<lv_ddlist_ext_t *>(ddlist->ext_attr);
+ if(list->opened) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
diff --git a/src/displayapp/screens/DropDownDemo.h b/src/displayapp/screens/DropDownDemo.h
new file mode 100644
index 0000000..7c75efc
--- /dev/null
+++ b/src/displayapp/screens/DropDownDemo.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <cstdint>
+#include "Screen.h"
+#include <bits/unique_ptr.h>
+#include <libs/lvgl/src/lv_core/lv_style.h>
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+
+ class DropDownDemo : public Screen{
+ public:
+ DropDownDemo(DisplayApp* app);
+ ~DropDownDemo() override;
+
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+ bool OnTouchEvent(TouchEvents event) override;
+
+ private:
+ lv_obj_t * ddlist;
+ bool running = true;
+ bool isDropDownOpened = false;
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/FirmwareUpdate.cpp b/src/displayapp/screens/FirmwareUpdate.cpp
new file mode 100644
index 0000000..778409e
--- /dev/null
+++ b/src/displayapp/screens/FirmwareUpdate.cpp
@@ -0,0 +1,82 @@
+#include <libs/lvgl/lvgl.h>
+#include "FirmwareUpdate.h"
+#include "../DisplayApp.h"
+
+using namespace Pinetime::Applications::Screens;
+extern lv_font_t jetbrains_mono_extrabold_compressed;
+extern lv_font_t jetbrains_mono_bold_20;
+
+
+FirmwareUpdate::FirmwareUpdate(Pinetime::Applications::DisplayApp *app, Pinetime::Controllers::Ble& bleController) :
+ Screen(app), bleController{bleController} {
+
+ titleLabel = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(titleLabel, "Firmware update");
+ lv_obj_set_auto_realign(titleLabel, true);
+ lv_obj_align(titleLabel, nullptr, LV_ALIGN_IN_TOP_MID, 0, 50);
+
+ bar1 = lv_bar_create(lv_scr_act(), nullptr);
+ lv_obj_set_size(bar1, 200, 30);
+ lv_obj_align(bar1, nullptr, LV_ALIGN_CENTER, 0, 0);
+ lv_bar_set_anim_time(bar1, 10);
+ lv_bar_set_range(bar1, 0, 100);
+ lv_bar_set_value(bar1, 0, LV_ANIM_OFF);
+
+ percentLabel = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(percentLabel, "");
+ lv_obj_set_auto_realign(percentLabel, true);
+ lv_obj_align(percentLabel, bar1, LV_ALIGN_OUT_TOP_MID, 0, 60);
+}
+
+FirmwareUpdate::~FirmwareUpdate() {
+ lv_obj_clean(lv_scr_act());
+}
+
+bool FirmwareUpdate::Refresh() {
+ switch(bleController.State()) {
+ default:
+ case Pinetime::Controllers::Ble::FirmwareUpdateStates::Idle:
+ case Pinetime::Controllers::Ble::FirmwareUpdateStates::Running:
+ if(state != States::Running)
+ state = States::Running;
+ return DisplayProgression();
+ case Pinetime::Controllers::Ble::FirmwareUpdateStates::Validated:
+ if(state != States::Validated) {
+ UpdateValidated();
+ state = States::Validated;
+ }
+ return running;
+ case Pinetime::Controllers::Ble::FirmwareUpdateStates::Error:
+ if(state != States::Error) {
+ UpdateError();
+ state = States::Error;
+ }
+ return running;
+ }
+}
+
+bool FirmwareUpdate::DisplayProgression() const {
+ float current = bleController.FirmwareUpdateCurrentBytes() / 1024.0f;
+ float total = bleController.FirmwareUpdateTotalBytes() / 1024.0f;
+ int16_t pc = (current / total) * 100.0f;
+ sprintf(percentStr, "%d %%", pc);
+ lv_label_set_text(percentLabel, percentStr);
+
+ lv_bar_set_value(bar1, pc, LV_ANIM_OFF);
+ return running;
+}
+
+bool FirmwareUpdate::OnButtonPushed() {
+ running = false;
+ return true;
+}
+
+void FirmwareUpdate::UpdateValidated() {
+ lv_label_set_recolor(percentLabel, true);
+ lv_label_set_text(percentLabel, "#00ff00 Image Ok!#");
+}
+
+void FirmwareUpdate::UpdateError() {
+ lv_label_set_recolor(percentLabel, true);
+ lv_label_set_text(percentLabel, "#ff0000 Error!#");
+}
diff --git a/src/displayapp/screens/FirmwareUpdate.h b/src/displayapp/screens/FirmwareUpdate.h
new file mode 100644
index 0000000..893fe68
--- /dev/null
+++ b/src/displayapp/screens/FirmwareUpdate.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <cstdint>
+#include <chrono>
+
+#include "Screen.h"
+#include <bits/unique_ptr.h>
+#include <libs/lvgl/src/lv_core/lv_style.h>
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+#include "components/ble/BleController.h"
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+
+ class FirmwareUpdate : public Screen{
+ public:
+ FirmwareUpdate(DisplayApp* app, Pinetime::Controllers::Ble& bleController);
+ ~FirmwareUpdate() override;
+
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+
+ private:
+ enum class States { Idle, Running, Validated, Error };
+ Pinetime::Controllers::Ble& bleController;
+ lv_obj_t* bar1;
+ lv_obj_t* percentLabel;
+ lv_obj_t* titleLabel;
+ mutable char percentStr[10];
+ bool running = true;
+ States state;
+
+ bool DisplayProgression() const;
+
+ void UpdateValidated();
+
+ void UpdateError();
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/FirmwareValidation.cpp b/src/displayapp/screens/FirmwareValidation.cpp
new file mode 100644
index 0000000..4ac399f
--- /dev/null
+++ b/src/displayapp/screens/FirmwareValidation.cpp
@@ -0,0 +1,91 @@
+#include <libs/lvgl/lvgl.h>
+#include "FirmwareValidation.h"
+#include "../DisplayApp.h"
+#include "../../Version.h"
+#include "components/firmwarevalidator/FirmwareValidator.h"
+
+using namespace Pinetime::Applications::Screens;
+extern lv_font_t jetbrains_mono_extrabold_compressed;
+extern lv_font_t jetbrains_mono_bold_20;
+
+namespace {
+ static void ButtonEventHandler(lv_obj_t * obj, lv_event_t event)
+ {
+ FirmwareValidation* screen = static_cast<FirmwareValidation *>(obj->user_data);
+ screen->OnButtonEvent(obj, event);
+ }
+
+}
+
+FirmwareValidation::FirmwareValidation(Pinetime::Applications::DisplayApp *app,
+ Pinetime::Controllers::FirmwareValidator &validator)
+ : Screen{app}, validator{validator} {
+ labelVersionInfo = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_align(labelVersionInfo, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 0);
+ lv_label_set_text(labelVersionInfo, "Version : ");
+ lv_label_set_align(labelVersionInfo, LV_LABEL_ALIGN_LEFT);
+
+
+ labelVersionValue = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_align(labelVersionValue, labelVersionInfo, LV_ALIGN_OUT_RIGHT_MID, 0, 0);
+ lv_label_set_recolor(labelVersionValue, true);
+ sprintf(version, "%ld.%ld.%ld", Version::Major(), Version::Minor(), Version::Patch());
+ lv_label_set_text(labelVersionValue, version);
+
+ labelIsValidated = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_align(labelIsValidated, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 50);
+ lv_label_set_recolor(labelIsValidated, true);
+ lv_label_set_long_mode(labelIsValidated, LV_LABEL_LONG_BREAK);
+ lv_obj_set_width(labelIsValidated, 240);
+
+ if(validator.IsValidated())
+ lv_label_set_text(labelIsValidated, "You have already\n#00ff00 validated# this firmware#");
+ else {
+ lv_label_set_text(labelIsValidated,
+ "Please #00ff00 Validate# this version or\n#ff0000 Reset# to rollback to the previous version.");
+
+ buttonValidate = lv_btn_create(lv_scr_act(), nullptr);
+ lv_obj_align(buttonValidate, NULL, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
+ buttonValidate->user_data = this;
+ lv_obj_set_event_cb(buttonValidate, ButtonEventHandler);
+
+ labelButtonValidate = lv_label_create(buttonValidate, nullptr);
+ lv_label_set_recolor(labelButtonValidate, true);
+ lv_label_set_text(labelButtonValidate, "#00ff00 Validate#");
+
+ buttonReset = lv_btn_create(lv_scr_act(), nullptr);
+ buttonReset->user_data = this;
+ lv_obj_align(buttonReset, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
+ lv_obj_set_event_cb(buttonReset, ButtonEventHandler);
+
+ labelButtonReset = lv_label_create(buttonReset, nullptr);
+ lv_label_set_recolor(labelButtonReset, true);
+ lv_label_set_text(labelButtonReset, "#ff0000 Reset#");
+ }
+}
+
+
+FirmwareValidation::~FirmwareValidation() {
+ lv_obj_clean(lv_scr_act());
+}
+
+bool FirmwareValidation::Refresh() {
+ return running;
+}
+
+bool FirmwareValidation::OnButtonPushed() {
+ running = false;
+ return true;
+}
+
+void FirmwareValidation::OnButtonEvent(lv_obj_t *object, lv_event_t event) {
+ if(object == buttonValidate && event == LV_EVENT_PRESSED) {
+ validator.Validate();
+ running = false;
+ } else if(object == buttonReset && event == LV_EVENT_PRESSED) {
+ validator.Reset();
+ }
+
+}
+
+
diff --git a/src/displayapp/screens/FirmwareValidation.h b/src/displayapp/screens/FirmwareValidation.h
new file mode 100644
index 0000000..947f557
--- /dev/null
+++ b/src/displayapp/screens/FirmwareValidation.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <cstdint>
+#include "Screen.h"
+#include <bits/unique_ptr.h>
+#include <libs/lvgl/src/lv_core/lv_style.h>
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+
+namespace Pinetime {
+ namespace Controllers {
+ class FirmwareValidator;
+ }
+
+ namespace Applications {
+ namespace Screens {
+
+ class FirmwareValidation : public Screen{
+ public:
+ FirmwareValidation(DisplayApp* app, Pinetime::Controllers::FirmwareValidator& validator);
+ ~FirmwareValidation() override;
+
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+
+ void OnButtonEvent(lv_obj_t *object, lv_event_t event);
+
+ private:
+ Pinetime::Controllers::FirmwareValidator& validator;
+
+ lv_obj_t* labelVersionInfo;
+ lv_obj_t* labelVersionValue;
+ char version[9];
+ lv_obj_t* labelIsValidated;
+ lv_obj_t* buttonValidate;
+ lv_obj_t* labelButtonValidate;
+ lv_obj_t* buttonReset;
+ lv_obj_t* labelButtonReset;
+ bool running = true;
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/Gauge.cpp b/src/displayapp/screens/Gauge.cpp
new file mode 100644
index 0000000..81c283c
--- /dev/null
+++ b/src/displayapp/screens/Gauge.cpp
@@ -0,0 +1,58 @@
+#include <libs/lvgl/lvgl.h>
+#include "Gauge.h"
+#include "../DisplayApp.h"
+
+using namespace Pinetime::Applications::Screens;
+extern lv_font_t jetbrains_mono_extrabold_compressed;
+extern lv_font_t jetbrains_mono_bold_20;
+
+
+Gauge::Gauge(Pinetime::Applications::DisplayApp *app) : Screen(app) {
+ /*Create a style*/
+ lv_style_copy(&style, &lv_style_pretty_color);
+ style.body.main_color = LV_COLOR_CYAN; /*Line color at the beginning*/
+ style.body.grad_color = LV_COLOR_RED; /*Line color at the end*/
+ style.body.padding.left = 10; /*Scale line length*/
+ style.body.padding.inner = 8 ; /*Scale label padding*/
+ style.body.border.color = lv_color_hex3(0x333); /*Needle middle circle color*/
+ style.line.width = 3;
+ style.text.color = LV_COLOR_WHITE;
+ style.line.color = LV_COLOR_RED; /*Line color after the critical value*/
+
+
+ /*Describe the color for the needles*/
+
+ needle_colors[0] = LV_COLOR_ORANGE;
+
+ /*Create a gauge*/
+ gauge1 = lv_gauge_create(lv_scr_act(), nullptr);
+ lv_gauge_set_style(gauge1, LV_GAUGE_STYLE_MAIN, &style);
+ lv_gauge_set_needle_count(gauge1, 1, needle_colors);
+ lv_obj_set_size(gauge1, 180, 180);
+ lv_obj_align(gauge1, nullptr, LV_ALIGN_CENTER, 0, 0);
+ lv_gauge_set_scale(gauge1, 360, 60, 0);
+ lv_gauge_set_range(gauge1, 0, 59);
+
+ /*Set the values*/
+ lv_gauge_set_value(gauge1, 0, value);
+}
+
+Gauge::~Gauge() {
+
+
+ lv_obj_clean(lv_scr_act());
+}
+
+bool Gauge::Refresh() {
+// lv_lmeter_set_value(lmeter, value++); /*Set the current value*/
+// if(value>=60) value = 0;
+
+ lv_gauge_set_value(gauge1, 0, value++);
+ if(value == 59) value = 0;
+ return running;
+}
+
+bool Gauge::OnButtonPushed() {
+ running = false;
+ return true;
+}
diff --git a/src/displayapp/screens/Gauge.h b/src/displayapp/screens/Gauge.h
new file mode 100644
index 0000000..03c06be
--- /dev/null
+++ b/src/displayapp/screens/Gauge.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <cstdint>
+#include "Screen.h"
+#include <bits/unique_ptr.h>
+#include <libs/lvgl/src/lv_core/lv_style.h>
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+
+ class Gauge : public Screen{
+ public:
+ Gauge(DisplayApp* app);
+ ~Gauge() override;
+
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+
+ private:
+ lv_style_t style;
+ lv_color_t needle_colors[3];
+ lv_obj_t * gauge1;
+
+ uint32_t value=30;
+ bool running = true;
+
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/InfiniPaint.cpp b/src/displayapp/screens/InfiniPaint.cpp
new file mode 100644
index 0000000..3ea75e9
--- /dev/null
+++ b/src/displayapp/screens/InfiniPaint.cpp
@@ -0,0 +1,44 @@
+#include <libs/lvgl/lvgl.h>
+#include <libraries/log/nrf_log.h>
+#include "InfiniPaint.h"
+#include "../DisplayApp.h"
+
+using namespace Pinetime::Applications::Screens;
+extern lv_font_t jetbrains_mono_extrabold_compressed;
+extern lv_font_t jetbrains_mono_bold_20;
+
+InfiniPaint::InfiniPaint(Pinetime::Applications::DisplayApp* app, Pinetime::Components::LittleVgl& lvgl) : Screen(app), lvgl{lvgl} {
+ app->SetTouchMode(DisplayApp::TouchModes::Polling);
+ std::fill(b, b + bufferSize, LV_COLOR_WHITE);
+}
+
+InfiniPaint::~InfiniPaint() {
+ // Reset the touchmode
+ app->SetTouchMode(DisplayApp::TouchModes::Gestures);
+ lv_obj_clean(lv_scr_act());
+}
+
+bool InfiniPaint::Refresh() {
+ return running;
+}
+
+bool InfiniPaint::OnButtonPushed() {
+ running = false;
+ return true;
+}
+
+bool InfiniPaint::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
+ return true;
+}
+
+bool InfiniPaint::OnTouchEvent(uint16_t x, uint16_t y) {
+ lv_area_t area;
+ area.x1 = x - (width / 2);
+ area.y1 = y - (height / 2);
+ area.x2 = x + (width / 2) - 1;
+ area.y2 = y + (height / 2) - 1;
+ lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::None);
+ lvgl.FlushDisplay(&area, b);
+ return true;
+}
+
diff --git a/src/displayapp/screens/InfiniPaint.h b/src/displayapp/screens/InfiniPaint.h
new file mode 100644
index 0000000..f29135d
--- /dev/null
+++ b/src/displayapp/screens/InfiniPaint.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <cstdint>
+#include "Screen.h"
+#include <bits/unique_ptr.h>
+#include <libs/lvgl/src/lv_core/lv_style.h>
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+#include <drivers/St7789.h>
+#include "displayapp/LittleVgl.h"
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+
+ class InfiniPaint : public Screen {
+ public:
+ InfiniPaint(DisplayApp* app, Pinetime::Components::LittleVgl& lvgl);
+
+ ~InfiniPaint() override;
+
+ bool Refresh() override;
+
+ bool OnButtonPushed() override;
+
+ bool OnTouchEvent(TouchEvents event) override;
+
+ bool OnTouchEvent(uint16_t x, uint16_t y) override;
+
+ private:
+ Pinetime::Components::LittleVgl& lvgl;
+ static constexpr uint16_t width = 10;
+ static constexpr uint16_t height = 10;
+ static constexpr uint16_t bufferSize = width * height;
+ lv_color_t b[bufferSize];
+ bool running = true;
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/Label.cpp b/src/displayapp/screens/Label.cpp
new file mode 100644
index 0000000..540776c
--- /dev/null
+++ b/src/displayapp/screens/Label.cpp
@@ -0,0 +1,15 @@
+#include <libs/lvgl/lvgl.h>
+#include "Label.h"
+
+using namespace Pinetime::Applications::Screens;
+
+Label::Label(Pinetime::Applications::DisplayApp *app, const char *text) : Screen(app), text{text} {
+ label = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_align(label, LV_LABEL_ALIGN_LEFT);
+ lv_obj_set_size(label, 240, 240);
+ lv_label_set_text(label, text);
+}
+
+Label::~Label() {
+ lv_obj_clean(lv_scr_act());
+}
diff --git a/src/displayapp/screens/Label.h b/src/displayapp/screens/Label.h
new file mode 100644
index 0000000..3e7b379
--- /dev/null
+++ b/src/displayapp/screens/Label.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <vector>
+#include "Screen.h"
+#include <lvgl/lvgl.h>
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+
+ class Label : public Screen {
+ public:
+ Label(DisplayApp* app, const char* text);
+ ~Label() override;
+ bool Refresh() override {return false;}
+
+ private:
+ lv_obj_t * label = nullptr;
+ const char* text = nullptr;
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/displayapp/screens/Meter.cpp b/src/displayapp/screens/Meter.cpp
new file mode 100644
index 0000000..273e111
--- /dev/null
+++ b/src/displayapp/screens/Meter.cpp
@@ -0,0 +1,47 @@
+#include <libs/lvgl/lvgl.h>
+#include "Meter.h"
+#include "../DisplayApp.h"
+
+using namespace Pinetime::Applications::Screens;
+extern lv_font_t jetbrains_mono_extrabold_compressed;
+extern lv_font_t jetbrains_mono_bold_20;
+
+
+Meter::Meter(Pinetime::Applications::DisplayApp *app) : Screen(app) {
+
+ lv_style_copy(&style_lmeter, &lv_style_pretty_color);
+ style_lmeter.line.width = 2;
+ style_lmeter.line.color = LV_COLOR_SILVER;
+ style_lmeter.body.main_color = lv_color_make(255,0,0);
+ style_lmeter.body.grad_color = lv_color_make(160,0,0);
+ style_lmeter.body.padding.left = 16; /*Line length*/
+
+ /*Create a line meter */
+ lmeter = lv_lmeter_create(lv_scr_act(), nullptr);
+ lv_lmeter_set_range(lmeter, 0, 60); /*Set the range*/
+ lv_lmeter_set_value(lmeter, value); /*Set the current value*/
+ lv_lmeter_set_angle_offset(lmeter, 180);
+ lv_lmeter_set_scale(lmeter, 360, 60); /*Set the angle and number of lines*/
+ lv_lmeter_set_style(lmeter, LV_LMETER_STYLE_MAIN, &style_lmeter); /*Apply the new style*/
+ lv_obj_set_size(lmeter, 150, 150);
+ lv_obj_align(lmeter, nullptr, LV_ALIGN_CENTER, 0, 0);
+
+}
+
+Meter::~Meter() {
+
+
+ lv_obj_clean(lv_scr_act());
+}
+
+bool Meter::Refresh() {
+ lv_lmeter_set_value(lmeter, value++); /*Set the current value*/
+ if(value>=60) value = 0;
+
+ return running;
+}
+
+bool Meter::OnButtonPushed() {
+ running = false;
+ return true;
+}
diff --git a/src/displayapp/screens/Meter.h b/src/displayapp/screens/Meter.h
new file mode 100644
index 0000000..ddf8be8
--- /dev/null
+++ b/src/displayapp/screens/Meter.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <cstdint>
+#include <chrono>
+#include "Screen.h"
+#include <bits/unique_ptr.h>
+#include <libs/lvgl/src/lv_core/lv_style.h>
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+
+ class Meter : public Screen{
+ public:
+ Meter(DisplayApp* app);
+ ~Meter() override;
+
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+
+ private:
+ lv_style_t style_lmeter;
+ lv_obj_t * lmeter;
+
+ uint32_t value=0;
+ bool running = true;
+
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/Modal.cpp b/src/displayapp/screens/Modal.cpp
new file mode 100644
index 0000000..29f7bfa
--- /dev/null
+++ b/src/displayapp/screens/Modal.cpp
@@ -0,0 +1,81 @@
+#include <libs/lvgl/lvgl.h>
+#include "Modal.h"
+#include "../DisplayApp.h"
+
+using namespace Pinetime::Applications::Screens;
+extern lv_font_t jetbrains_mono_extrabold_compressed;
+extern lv_font_t jetbrains_mono_bold_20;
+
+Modal::Modal(Pinetime::Applications::DisplayApp *app) : Screen(app) {
+
+
+}
+
+Modal::~Modal() {
+ lv_obj_clean(lv_scr_act());
+}
+
+bool Modal::Refresh() {
+
+ return running;
+}
+
+bool Modal::OnButtonPushed() {
+ running = false;
+ return true;
+}
+
+void Modal::Hide() {
+ /* Delete the parent modal background */
+ lv_obj_del_async(lv_obj_get_parent(mbox));
+ mbox = NULL; /* happens before object is actually deleted! */
+ isVisible = false;
+}
+
+void Modal::mbox_event_cb(lv_obj_t *obj, lv_event_t evt) {
+ auto* m = static_cast<Modal *>(obj->user_data);
+ m->OnEvent(obj, evt);
+}
+
+void Modal::OnEvent(lv_obj_t *event_obj, lv_event_t evt) {
+ if(evt == LV_EVENT_DELETE && event_obj == mbox) {
+ Hide();
+ } else if(evt == LV_EVENT_VALUE_CHANGED) {
+ /* A button was clicked */
+ lv_mbox_start_auto_close(mbox, 0);
+// Hide();
+ }
+}
+
+void Modal::Show(const char* msg) {
+ if(isVisible) return;
+ isVisible = true;
+ lv_style_copy(&modal_style, &lv_style_plain_color);
+ modal_style.body.main_color = modal_style.body.grad_color = LV_COLOR_BLACK;
+ modal_style.body.opa = LV_OPA_50;
+
+ obj = lv_obj_create(lv_scr_act(), nullptr);
+ lv_obj_set_style(obj, &modal_style);
+ lv_obj_set_pos(obj, 0, 0);
+ lv_obj_set_size(obj, LV_HOR_RES, LV_VER_RES);
+ lv_obj_set_opa_scale_enable(obj, true); /* Enable opacity scaling for the animation */
+
+ static const char * btns2[] = {"Ok", ""};
+
+ /* Create the message box as a child of the modal background */
+ mbox = lv_mbox_create(obj, nullptr);
+ lv_mbox_add_btns(mbox, btns2);
+ lv_mbox_set_text(mbox, msg);
+ lv_obj_align(mbox, nullptr, LV_ALIGN_CENTER, 0, 0);
+ lv_obj_set_event_cb(mbox, Modal::mbox_event_cb);
+
+ mbox->user_data = this;
+
+ /* Fade the message box in with an animation */
+ lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_time(&a, 500, 0);
+ lv_anim_set_values(&a, LV_OPA_TRANSP, LV_OPA_COVER);
+ lv_anim_set_exec_cb(&a, obj, (lv_anim_exec_xcb_t)lv_obj_set_opa_scale);
+ lv_anim_create(&a);
+}
diff --git a/src/displayapp/screens/Modal.h b/src/displayapp/screens/Modal.h
new file mode 100644
index 0000000..c616c29
--- /dev/null
+++ b/src/displayapp/screens/Modal.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <cstdint>
+#include <chrono>
+#include "Screen.h"
+#include <bits/unique_ptr.h>
+#include <libs/lvgl/src/lv_core/lv_style.h>
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+
+ class Modal : public Screen{
+ public:
+ Modal(DisplayApp* app);
+ ~Modal() override;
+
+ void Show(const char* msg);
+ void Hide();
+
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+
+ static void mbox_event_cb(lv_obj_t *obj, lv_event_t evt);
+ private:
+ void OnEvent(lv_obj_t *event_obj, lv_event_t evt);
+
+ lv_style_t modal_style;
+ lv_obj_t *obj;
+ lv_obj_t *mbox;
+ lv_obj_t *info;
+ bool running = true;
+ bool isVisible = false;
+
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/Music.cpp b/src/displayapp/screens/Music.cpp
new file mode 100644
index 0000000..225a15a
--- /dev/null
+++ b/src/displayapp/screens/Music.cpp
@@ -0,0 +1,292 @@
+/* Copyright (C) 2020 JF, Adam Pigg, Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#include <libs/lvgl/lvgl.h>
+
+#include "Music.h"
+
+using namespace Pinetime::Applications::Screens;
+
+extern lv_font_t jetbrains_mono_extrabold_compressed;
+extern lv_font_t jetbrains_mono_bold_20;
+
+static void event_handler(lv_obj_t *obj, lv_event_t event) {
+ Music *screen = static_cast<Music *>(obj->user_data);
+ screen->OnObjectEvent(obj, event);
+}
+
+/**
+ * Set the pixel array to display by the image
+ * This just calls lv_img_set_src but adds type safety
+ *
+ * @param img pointer to an image object
+ * @param data the image array
+ */
+inline void lv_img_set_src_arr(lv_obj_t *img, const lv_img_dsc_t *src_img) {
+ lv_img_set_src(img, src_img);
+}
+
+/**
+ * Music control watchapp
+ *
+ * TODO: Investigate Apple Media Service and AVRCPv1.6 support for seamless integration
+ */
+Music::Music(Pinetime::Applications::DisplayApp *app, Pinetime::Controllers::MusicService &music) : Screen(app), musicService(music) {
+ lv_obj_t *label;
+
+ btnVolDown = lv_btn_create(lv_scr_act(), nullptr);
+ btnVolDown->user_data = this;
+ lv_obj_set_event_cb(btnVolDown, event_handler);
+ lv_obj_set_size(btnVolDown, LV_HOR_RES / 3, 80);
+ lv_obj_align(btnVolDown, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
+ label = lv_label_create(btnVolDown, nullptr);
+ lv_label_set_text(label, "V-");
+ lv_obj_set_hidden(btnVolDown, !displayVolumeButtons);
+
+ btnVolUp = lv_btn_create(lv_scr_act(), nullptr);
+ btnVolUp->user_data = this;
+ lv_obj_set_event_cb(btnVolUp, event_handler);
+ lv_obj_set_size(btnVolUp, LV_HOR_RES / 3, 80);
+ lv_obj_align(btnVolUp, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
+ label = lv_label_create(btnVolUp, nullptr);
+ lv_label_set_text(label, "V+");
+ lv_obj_set_hidden(btnVolDown, !displayVolumeButtons);
+
+ btnPrev = lv_btn_create(lv_scr_act(), nullptr);
+ btnPrev->user_data = this;
+ lv_obj_set_event_cb(btnPrev, event_handler);
+ lv_obj_set_size(btnPrev, LV_HOR_RES / 3, 80);
+ lv_obj_align(btnPrev, nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
+ label = lv_label_create(btnPrev, nullptr);
+ lv_label_set_text(label, "<<");
+
+ btnNext = lv_btn_create(lv_scr_act(), nullptr);
+ btnNext->user_data = this;
+ lv_obj_set_event_cb(btnNext, event_handler);
+ lv_obj_set_size(btnNext, LV_HOR_RES / 3, 80);
+ lv_obj_align(btnNext, nullptr, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
+ label = lv_label_create(btnNext, nullptr);
+ lv_label_set_text(label, ">>");
+
+ btnPlayPause = lv_btn_create(lv_scr_act(), nullptr);
+ btnPlayPause->user_data = this;
+ lv_obj_set_event_cb(btnPlayPause, event_handler);
+ lv_obj_set_size(btnPlayPause, LV_HOR_RES / 3, 80);
+ lv_obj_align(btnPlayPause, nullptr, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
+ txtPlayPause = lv_label_create(btnPlayPause, nullptr);
+ lv_label_set_text(txtPlayPause, ">");
+
+ txtTrackDuration = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_long_mode(txtTrackDuration, LV_LABEL_LONG_SROLL);
+ lv_obj_align(txtTrackDuration, nullptr, LV_ALIGN_IN_TOP_LEFT, 12, 20);
+ lv_label_set_text(txtTrackDuration, "--:--/--:--");
+ lv_label_set_align(txtTrackDuration, LV_ALIGN_IN_LEFT_MID);
+ lv_obj_set_width(txtTrackDuration, LV_HOR_RES);
+
+ constexpr uint8_t FONT_HEIGHT = 12;
+ constexpr uint8_t LINE_PAD = 15;
+ constexpr int8_t MIDDLE_OFFSET = -25;
+ txtArtist = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_long_mode(txtArtist, LV_LABEL_LONG_SROLL);
+ lv_obj_align(txtArtist, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 1 * FONT_HEIGHT);
+ lv_label_set_text(txtArtist, "Artist Name");
+ lv_label_set_align(txtArtist, LV_ALIGN_IN_LEFT_MID);
+ lv_obj_set_width(txtArtist, LV_HOR_RES);
+
+ txtTrack = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_long_mode(txtTrack, LV_LABEL_LONG_SROLL);
+ lv_obj_align(txtTrack, nullptr, LV_ALIGN_IN_LEFT_MID, 12, MIDDLE_OFFSET + 2 * FONT_HEIGHT + LINE_PAD);
+ lv_label_set_text(txtTrack, "This is a very long getTrack name");
+ lv_label_set_align(txtTrack, LV_ALIGN_IN_LEFT_MID);
+ lv_obj_set_width(txtTrack, LV_HOR_RES);
+
+ /** Init animation */
+ imgDisc = lv_img_create(lv_scr_act(), nullptr);
+ lv_img_set_src_arr(imgDisc, &disc);
+ lv_obj_align(imgDisc, nullptr, LV_ALIGN_IN_TOP_RIGHT, -15, 15);
+
+ imgDiscAnim = lv_img_create(lv_scr_act(), nullptr);
+ lv_img_set_src_arr(imgDiscAnim, &disc_f_1);
+ lv_obj_align(imgDiscAnim, nullptr, LV_ALIGN_IN_TOP_RIGHT, -15 - 32, 15);
+
+ frameB = false;
+
+ musicService.event(Controllers::MusicService::EVENT_MUSIC_OPEN);
+}
+
+Music::~Music() {
+ lv_obj_clean(lv_scr_act());
+}
+
+bool Music::OnButtonPushed() {
+ running = false;
+ return true;
+}
+
+bool Music::Refresh() {
+ if (artist != musicService.getArtist()) {
+ artist = musicService.getArtist();
+ currentLength = 0;
+ lv_label_set_text(txtArtist, artist.data());
+ }
+
+ if (track != musicService.getTrack()) {
+ track = musicService.getTrack();
+ currentLength = 0;
+ lv_label_set_text(txtTrack, track.data());
+ }
+
+ if (album != musicService.getAlbum()) {
+ album = musicService.getAlbum();
+ currentLength = 0;
+ }
+
+ if (playing != musicService.isPlaying()) {
+ playing = musicService.isPlaying();
+ }
+
+ // Because we increment this ourselves,
+ // we can't compare with the old data directly
+ // have to update it when there's actually new data
+ // just to avoid unnecessary draws that make UI choppy
+ if (lastLength != musicService.getProgress()) {
+ currentLength = musicService.getProgress();
+ lastLength = currentLength;
+ UpdateLength();
+ }
+
+ if (totalLength != musicService.getTrackLength()) {
+ totalLength = musicService.getTrackLength();
+ UpdateLength();
+ }
+
+ if (playing == Pinetime::Controllers::MusicService::MusicStatus::Playing) {
+ lv_label_set_text(txtPlayPause, "||");
+ if (xTaskGetTickCount() - 1024 >= lastIncrement) {
+
+ if (frameB) {
+ lv_img_set_src(imgDiscAnim, &disc_f_1);
+ } else {
+ lv_img_set_src(imgDiscAnim, &disc_f_2);
+ }
+ frameB = !frameB;
+
+ if (currentLength < totalLength) {
+ currentLength += static_cast<int>((static_cast<float>(xTaskGetTickCount() - lastIncrement) / 1024.0f) *
+ musicService.getPlaybackSpeed());
+ } else {
+ // Let's assume the getTrack finished, paused when the timer ends
+ // and there's no new getTrack being sent to us
+ // TODO: ideally this would be configurable
+ playing = false;
+ }
+ lastIncrement = xTaskGetTickCount();
+
+ UpdateLength();
+ }
+ } else {
+ lv_label_set_text(txtPlayPause, ">");
+ }
+
+ return running;
+}
+
+void Music::UpdateLength() {
+ if (totalLength > (99 * 60 * 60)) {
+ lv_label_set_text(txtTrackDuration, "Inf/Inf");
+ } else if (totalLength > (99 * 60)) {
+ char timer[12];
+ sprintf(timer, "%02d:%02d/%02d:%02d",
+ (currentLength / (60 * 60)) % 100,
+ ((currentLength % (60 * 60)) / 60) % 100,
+ (totalLength / (60 * 60)) % 100,
+ ((totalLength % (60 * 60)) / 60) % 100
+ );
+ lv_label_set_text(txtTrackDuration, timer);
+ } else {
+ char timer[12];
+ sprintf(timer, "%02d:%02d/%02d:%02d",
+ (currentLength / 60) % 100,
+ (currentLength % 60) % 100,
+ (totalLength / 60) % 100,
+ (totalLength % 60) % 100
+ );
+ lv_label_set_text(txtTrackDuration, timer);
+ }
+}
+
+void Music::OnObjectEvent(lv_obj_t *obj, lv_event_t event) {
+ if (event == LV_EVENT_CLICKED) {
+ if (obj == btnVolDown) {
+ musicService.event(Controllers::MusicService::EVENT_MUSIC_VOLDOWN);
+ } else if (obj == btnVolUp) {
+ musicService.event(Controllers::MusicService::EVENT_MUSIC_VOLUP);
+ } else if (obj == btnPrev) {
+ musicService.event(Controllers::MusicService::EVENT_MUSIC_PREV);
+ } else if (obj == btnPlayPause) {
+ if (playing == Pinetime::Controllers::MusicService::MusicStatus::Playing) {
+ musicService.event(Controllers::MusicService::EVENT_MUSIC_PAUSE);
+
+ // Let's assume it stops playing instantly
+ playing = Controllers::MusicService::NotPlaying;
+ } else {
+ musicService.event(Controllers::MusicService::EVENT_MUSIC_PLAY);
+
+ // Let's assume it starts playing instantly
+ // TODO: In the future should check for BT connection for better UX
+ playing = Controllers::MusicService::Playing;
+ }
+ } else if (obj == btnNext) {
+ musicService.event(Controllers::MusicService::EVENT_MUSIC_NEXT);
+ }
+ }
+}
+
+
+bool Music::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
+ switch (event) {
+ case TouchEvents::SwipeUp: {
+ displayVolumeButtons = true;
+ lv_obj_set_hidden(btnVolDown, !displayVolumeButtons);
+ lv_obj_set_hidden(btnVolUp, !displayVolumeButtons);
+
+ lv_obj_set_hidden(btnNext, displayVolumeButtons);
+ lv_obj_set_hidden(btnPrev, displayVolumeButtons);
+ return true;
+ }
+ case TouchEvents::SwipeDown: {
+ displayVolumeButtons = false;
+ lv_obj_set_hidden(btnNext, displayVolumeButtons);
+ lv_obj_set_hidden(btnPrev, displayVolumeButtons);
+
+ lv_obj_set_hidden(btnVolDown, !displayVolumeButtons);
+ lv_obj_set_hidden(btnVolUp, !displayVolumeButtons);
+ return true;
+ }
+ case TouchEvents::SwipeLeft: {
+ musicService.event(Controllers::MusicService::EVENT_MUSIC_NEXT);
+ return true;
+ }
+ case TouchEvents::SwipeRight: {
+ musicService.event(Controllers::MusicService::EVENT_MUSIC_PREV);
+ return true;
+ }
+ default: {
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/displayapp/screens/Music.h b/src/displayapp/screens/Music.h
new file mode 100644
index 0000000..81ba793
--- /dev/null
+++ b/src/displayapp/screens/Music.h
@@ -0,0 +1,96 @@
+/* Copyright (C) 2020 JF, Adam Pigg, Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include <cstdint>
+#include <chrono>
+#include <string>
+
+#include "components/gfx/Gfx.h"
+#include "components/battery/BatteryController.h"
+#include "components/ble/BleController.h"
+#include "components/ble/MusicService.h"
+#include "Screen.h"
+#include <bits/unique_ptr.h>
+#include <libs/lvgl/src/lv_core/lv_style.h>
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+#include "../../Version.h"
+#include "displayapp/icons/music/disc.cpp"
+#include "displayapp/icons/music/disc_f_1.cpp"
+#include "displayapp/icons/music/disc_f_2.cpp"
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ class Music : public Screen {
+ public:
+ Music(DisplayApp *app, Pinetime::Controllers::MusicService &music);
+
+ ~Music() override;
+
+ bool Refresh() override;
+
+ bool OnButtonPushed() override;
+
+ void OnObjectEvent(lv_obj_t *obj, lv_event_t event);
+
+ private:
+ bool OnTouchEvent(TouchEvents event);
+
+ void UpdateLength();
+
+ lv_obj_t *btnPrev;
+ lv_obj_t *btnPlayPause;
+ lv_obj_t *btnNext;
+ lv_obj_t *btnVolDown;
+ lv_obj_t *btnVolUp;
+ lv_obj_t *txtArtist;
+ lv_obj_t *txtTrack;
+ lv_obj_t *txtPlayPause;
+
+ lv_obj_t *imgDisc;
+ lv_obj_t *imgDiscAnim;
+ lv_obj_t *txtTrackDuration;
+
+ /** For the spinning disc animation */
+ bool frameB;
+
+ bool displayVolumeButtons = false;
+ Pinetime::Controllers::MusicService &musicService;
+
+ std::string artist;
+ std::string album;
+ std::string track;
+
+ /** Total length in seconds */
+ int totalLength;
+ /** Current length in seconds */
+ int currentLength;
+ /** Last length */
+ int lastLength;
+ /** Last time an animation update or timer was incremented */
+ TickType_t lastIncrement;
+
+ bool playing;
+
+ /** Watchapp */
+ bool running = true;
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/NotificationIcon.cpp b/src/displayapp/screens/NotificationIcon.cpp
new file mode 100644
index 0000000..64898c2
--- /dev/null
+++ b/src/displayapp/screens/NotificationIcon.cpp
@@ -0,0 +1,8 @@
+#include "NotificationIcon.h"
+#include "Symbols.h"
+using namespace Pinetime::Applications::Screens;
+
+const char* NotificationIcon::GetIcon(bool newNotificationAvailable) {
+ if(newNotificationAvailable) return Symbols::info;
+ else return "";
+} \ No newline at end of file
diff --git a/src/displayapp/screens/NotificationIcon.h b/src/displayapp/screens/NotificationIcon.h
new file mode 100644
index 0000000..dc34c3f
--- /dev/null
+++ b/src/displayapp/screens/NotificationIcon.h
@@ -0,0 +1,12 @@
+#pragma once
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ class NotificationIcon {
+ public:
+ static const char* GetIcon(bool newNotificationAvailable);
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/displayapp/screens/Notifications.cpp b/src/displayapp/screens/Notifications.cpp
new file mode 100644
index 0000000..85848b2
--- /dev/null
+++ b/src/displayapp/screens/Notifications.cpp
@@ -0,0 +1,174 @@
+#include <libs/lvgl/lvgl.h>
+#include <displayapp/DisplayApp.h>
+#include <functional>
+#include "Notifications.h"
+
+using namespace Pinetime::Applications::Screens;
+
+Notifications::Notifications(DisplayApp *app, Pinetime::Controllers::NotificationManager &notificationManager, Modes mode) :
+ Screen(app), notificationManager{notificationManager}, mode{mode} {
+ notificationManager.ClearNewNotificationFlag();
+ auto notification = notificationManager.GetLastNotification();
+ if(notification.valid) {
+ currentId = notification.id;
+ currentItem.reset(new NotificationItem("\nNotification", notification.message.data(), notification.index, notificationManager.NbNotifications(), mode));
+ validDisplay = true;
+ } else {
+ currentItem.reset(new NotificationItem("\nNotification", "No notification to display", 0, notificationManager.NbNotifications(), Modes::Preview));
+ }
+
+ if(mode == Modes::Preview) {
+ static lv_style_t style_line;
+ lv_style_copy(&style_line, &lv_style_plain);
+ style_line.line.color = LV_COLOR_WHITE;
+ style_line.line.width = 3;
+ style_line.line.rounded = 0;
+
+
+ timeoutLine = lv_line_create(lv_scr_act(), nullptr);
+ lv_line_set_style(timeoutLine, LV_LINE_STYLE_MAIN, &style_line);
+ lv_line_set_points(timeoutLine, timeoutLinePoints, 2);
+ timeoutTickCountStart = xTaskGetTickCount();
+ timeoutTickCountEnd = timeoutTickCountStart + (5*1024);
+ }
+}
+
+Notifications::~Notifications() {
+ lv_obj_clean(lv_scr_act());
+}
+
+bool Notifications::Refresh() {
+ if (mode == Modes::Preview) {
+ auto tick = xTaskGetTickCount();
+ int32_t pos = 240 - ((tick - timeoutTickCountStart) / ((timeoutTickCountEnd - timeoutTickCountStart) / 240));
+ if (pos < 0)
+ running = false;
+
+ timeoutLinePoints[1].x = pos;
+ lv_line_set_points(timeoutLine, timeoutLinePoints, 2);
+
+ if (!running) {
+ // Start clock app when exiting this one
+ app->StartApp(Apps::Clock);
+ }
+ }
+
+ return running;
+}
+
+bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
+ switch (event) {
+ case Pinetime::Applications::TouchEvents::SwipeUp: {
+ Controllers::NotificationManager::Notification previousNotification;
+ if(validDisplay)
+ previousNotification = notificationManager.GetPrevious(currentId);
+ else
+ previousNotification = notificationManager.GetLastNotification();
+
+ if (!previousNotification.valid) return true;
+
+ validDisplay = true;
+ currentId = previousNotification.id;
+ currentItem.reset(nullptr);
+ app->SetFullRefresh(DisplayApp::FullRefreshDirections::Up);
+ currentItem.reset(new NotificationItem("\nNotification", previousNotification.message.data(), previousNotification.index, notificationManager.NbNotifications(), mode));
+ }
+ return true;
+ case Pinetime::Applications::TouchEvents::SwipeDown: {
+ Controllers::NotificationManager::Notification nextNotification;
+ if(validDisplay)
+ nextNotification = notificationManager.GetNext(currentId);
+ else
+ nextNotification = notificationManager.GetLastNotification();
+
+ if (!nextNotification.valid) return true;
+
+ validDisplay = true;
+ currentId = nextNotification.id;
+ currentItem.reset(nullptr);
+ app->SetFullRefresh(DisplayApp::FullRefreshDirections::Down);
+ currentItem.reset(new NotificationItem("\nNotification", nextNotification.message.data(), nextNotification.index, notificationManager.NbNotifications(), mode));
+ }
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+bool Notifications::OnButtonPushed() {
+ running = false;
+ return true;
+}
+
+
+Notifications::NotificationItem::NotificationItem(const char *title, const char *msg, uint8_t notifNr, uint8_t notifNb, Modes mode)
+ : notifNr{notifNr}, notifNb{notifNb}, mode{mode} {
+ container1 = lv_cont_create(lv_scr_act(), nullptr);
+ static lv_style_t contStyle;
+ lv_style_copy(&contStyle, lv_cont_get_style(container1, LV_CONT_STYLE_MAIN));
+ contStyle.body.padding.inner = 20;
+ lv_cont_set_style(container1, LV_CONT_STYLE_MAIN, &contStyle);
+ lv_obj_set_width(container1, LV_HOR_RES);
+ lv_obj_set_height(container1, LV_VER_RES);
+ lv_obj_set_pos(container1, 0, 0);
+ lv_cont_set_layout(container1, LV_LAYOUT_OFF);
+ lv_cont_set_fit2(container1, LV_FIT_FLOOD, LV_FIT_FLOOD);
+
+ t1 = lv_label_create(container1, nullptr);
+ static lv_style_t titleStyle;
+ static lv_style_t textStyle;
+ static lv_style_t bottomStyle;
+ lv_style_copy(&titleStyle, lv_label_get_style(t1, LV_LABEL_STYLE_MAIN));
+ lv_style_copy(&textStyle, lv_label_get_style(t1, LV_LABEL_STYLE_MAIN));
+ lv_style_copy(&bottomStyle, lv_label_get_style(t1, LV_LABEL_STYLE_MAIN));
+ titleStyle.body.padding.inner = 5;
+ titleStyle.body.grad_color = LV_COLOR_GRAY;
+ titleStyle.body.main_color = LV_COLOR_GRAY;
+ titleStyle.body.radius = 20;
+ textStyle.body.border.part = LV_BORDER_NONE;
+ textStyle.body.padding.inner = 5;
+
+ bottomStyle.body.main_color = LV_COLOR_GREEN;
+ bottomStyle.body.grad_color = LV_COLOR_GREEN;
+ bottomStyle.body.border.part = LV_BORDER_TOP;
+ bottomStyle.body.border.color = LV_COLOR_RED;
+
+ lv_label_set_style(t1, LV_LABEL_STYLE_MAIN, &titleStyle);
+ lv_label_set_long_mode(t1, LV_LABEL_LONG_BREAK);
+ lv_label_set_body_draw(t1, true);
+ lv_obj_set_width(t1, LV_HOR_RES - (titleStyle.body.padding.left + titleStyle.body.padding.right));
+ lv_label_set_text(t1, title);
+ static constexpr int16_t offscreenOffset = -20 ;
+ lv_obj_set_pos(t1, titleStyle.body.padding.left, offscreenOffset);
+
+ auto titleHeight = lv_obj_get_height(t1);
+
+ l1 = lv_label_create(container1, nullptr);
+ lv_label_set_style(l1, LV_LABEL_STYLE_MAIN, &textStyle);
+ lv_obj_set_pos(l1, textStyle.body.padding.left,
+ titleHeight + offscreenOffset + textStyle.body.padding.bottom +
+ textStyle.body.padding.top);
+
+ lv_label_set_long_mode(l1, LV_LABEL_LONG_BREAK);
+ lv_label_set_body_draw(l1, true);
+ lv_obj_set_width(l1, LV_HOR_RES - (textStyle.body.padding.left + textStyle.body.padding.right));
+ lv_label_set_text(l1, msg);
+
+ if(mode == Modes::Normal) {
+ if(notifNr < notifNb) {
+ bottomPlaceholder = lv_label_create(container1, nullptr);
+ lv_label_set_style(bottomPlaceholder, LV_LABEL_STYLE_MAIN, &titleStyle);
+ lv_label_set_long_mode(bottomPlaceholder, LV_LABEL_LONG_BREAK);
+ lv_label_set_body_draw(bottomPlaceholder, true);
+ lv_obj_set_width(bottomPlaceholder, LV_HOR_RES - (titleStyle.body.padding.left + titleStyle.body.padding.right));
+ lv_label_set_text(bottomPlaceholder, " ");
+ lv_obj_set_pos(bottomPlaceholder, titleStyle.body.padding.left, LV_VER_RES - 5);
+ }
+ }
+}
+
+
+Notifications::NotificationItem::~NotificationItem() {
+ lv_obj_clean(lv_scr_act());
+}
diff --git a/src/displayapp/screens/Notifications.h b/src/displayapp/screens/Notifications.h
new file mode 100644
index 0000000..fb4e1ef
--- /dev/null
+++ b/src/displayapp/screens/Notifications.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <functional>
+#include <vector>
+
+#include "Screen.h"
+#include "ScreenList.h"
+
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ class Notifications : public Screen {
+ public:
+ enum class Modes {Normal, Preview};
+ explicit Notifications(DisplayApp* app, Pinetime::Controllers::NotificationManager& notificationManager, Modes mode);
+ ~Notifications() override;
+
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+ bool OnTouchEvent(Pinetime::Applications::TouchEvents event) override;
+
+ private:
+ bool running = true;
+
+ class NotificationItem {
+ public:
+ NotificationItem(const char* title, const char* msg, uint8_t notifNr, uint8_t notifNb, Modes mode);
+ ~NotificationItem();
+ bool Refresh() {return false;}
+
+ private:
+ uint8_t notifNr = 0;
+ uint8_t notifNb = 0;
+ char pageText[4];
+
+ lv_obj_t* container1;
+ lv_obj_t* t1;
+ lv_obj_t* l1;
+ lv_obj_t* bottomPlaceholder;
+ Modes mode;
+ };
+
+ struct NotificationData {
+ const char* title;
+ const char* text;
+ };
+ Pinetime::Controllers::NotificationManager& notificationManager;
+ Modes mode = Modes::Normal;
+ std::unique_ptr<NotificationItem> currentItem;
+ Controllers::NotificationManager::Notification::Id currentId;
+ bool validDisplay = false;
+
+ lv_point_t timeoutLinePoints[2] { {0, 237}, {239, 237} };
+ lv_obj_t* timeoutLine;
+ uint32_t timeoutTickCountStart;
+ uint32_t timeoutTickCountEnd;
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/Screen.cpp b/src/displayapp/screens/Screen.cpp
new file mode 100644
index 0000000..1467df3
--- /dev/null
+++ b/src/displayapp/screens/Screen.cpp
@@ -0,0 +1,2 @@
+#include "Screen.h"
+using namespace Pinetime::Applications::Screens; \ No newline at end of file
diff --git a/src/displayapp/screens/Screen.h b/src/displayapp/screens/Screen.h
new file mode 100644
index 0000000..6b1d0ee
--- /dev/null
+++ b/src/displayapp/screens/Screen.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <cstdint>
+#include "../TouchEvents.h"
+
+namespace Pinetime {
+ namespace Applications {
+ class DisplayApp;
+ namespace Screens {
+ class Screen {
+ public:
+ explicit Screen(DisplayApp* app) : app{app} {}
+ virtual ~Screen() = default;
+
+ /**
+ * Most of the time, apps only react to events (touch events, for example).
+ * In this case you don't need to do anything in this method.
+ *
+ * For example, InfiniPaint does nothing in Refresh().
+ * But, if you want to update your display periodically, draw an animation...
+ * you cannot do it in a touch event handler because these handlers are not
+ * called if the user does not touch the screen.
+ *
+ * That's why Refresh() is there: update the display periodically.
+ *
+ * @return false if the app can be closed, true if it must continue to run
+ **/
+ virtual bool Refresh() = 0;
+
+ /** @return false if the button hasn't been handled by the app, true if it has been handled */
+ virtual bool OnButtonPushed() { return false; }
+
+ /** @return false if the event hasn't been handled by the app, true if it has been handled */
+ virtual bool OnTouchEvent(TouchEvents event) { return false; }
+ virtual bool OnTouchEvent(uint16_t x, uint16_t y) { return false; }
+
+ protected:
+ DisplayApp* app;
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/ScreenList.h b/src/displayapp/screens/ScreenList.h
new file mode 100644
index 0000000..b198634
--- /dev/null
+++ b/src/displayapp/screens/ScreenList.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <vector>
+#include <functional>
+#include "components/ble/NimbleController.h"
+#include "Screen.h"
+#include "Label.h"
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ template <size_t N>
+ class ScreenList : public Screen {
+ public:
+ ScreenList(DisplayApp* app, std::array<std::function<std::unique_ptr<Screen>()>, N>&& screens)
+ : Screen(app), screens{std::move(screens)}, current{this->screens[0]()} {
+
+ }
+
+ ~ScreenList() override {
+
+ }
+
+ bool Refresh() override {
+ running = current->Refresh();
+ return running;
+ }
+
+ bool OnButtonPushed() override {
+ running = false;
+ return true;
+ }
+
+ bool OnTouchEvent(TouchEvents event) override {
+ switch (event) {
+ case TouchEvents::SwipeDown:
+ if (screenIndex > 0) {
+ current.reset(nullptr);
+ app->SetFullRefresh(DisplayApp::FullRefreshDirections::Down);
+ screenIndex--;
+ current = screens[screenIndex]();
+ }
+ return true;
+ case TouchEvents::SwipeUp:
+ if (screenIndex < screens.size() - 1) {
+ current.reset(nullptr);
+ app->SetFullRefresh(DisplayApp::FullRefreshDirections::Up);
+ screenIndex++;
+ current = screens[screenIndex]();
+ }
+ return true;
+ default:
+ return false;
+ }
+ return false;
+ }
+
+ private:
+ bool running = true;
+ uint8_t screenIndex = 0;
+ std::array<std::function<std::unique_ptr<Screen>()>, N> screens;
+ std::unique_ptr<Screen> current;
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/displayapp/screens/Symbols.h b/src/displayapp/screens/Symbols.h
new file mode 100644
index 0000000..aeea324
--- /dev/null
+++ b/src/displayapp/screens/Symbols.h
@@ -0,0 +1,30 @@
+#pragma once
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ namespace Symbols {
+ static constexpr const char* none = "";
+ static constexpr const char* batteryFull = "\xEF\x89\x80";
+ static constexpr const char* batteryEmpty = "\xEF\x89\x84";
+ static constexpr const char* batteryThreeQuarter = "\xEF\x89\x81";
+ static constexpr const char* batteryHalf = "\xEF\x89\x82";
+ static constexpr const char* batteryOneQuarter = "\xEF\x89\x83";
+ static constexpr const char* heartBeat = "\xEF\x88\x9E";
+ static constexpr const char* bluetoothFull = "\xEF\x8A\x93";
+ static constexpr const char* bluetooth = "\xEF\x8A\x94";
+ static constexpr const char* plug = "\xEF\x87\xA6";
+ static constexpr const char* shoe = "\xEF\x95\x8B";
+ static constexpr const char* clock = "\xEF\x80\x97";
+ static constexpr const char* info = "\xEF\x84\xA9";
+ static constexpr const char* list = "\xEF\x80\xBA";
+ static constexpr const char* sun = "\xEF\x86\x85";
+ static constexpr const char* check = "\xEF\x95\xA0";
+ static constexpr const char* music = "\xEF\x80\x81";
+ static constexpr const char* tachometer = "\xEF\x8F\xBD";
+ static constexpr const char* asterisk = "\xEF\x81\xA9";
+ static constexpr const char* paintbrush = "\xEF\x87\xBC";
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/displayapp/screens/SystemInfo.cpp b/src/displayapp/screens/SystemInfo.cpp
new file mode 100644
index 0000000..867fdae
--- /dev/null
+++ b/src/displayapp/screens/SystemInfo.cpp
@@ -0,0 +1,116 @@
+#include <libs/lvgl/lvgl.h>
+#include <displayapp/DisplayApp.h>
+#include <functional>
+#include "SystemInfo.h"
+#include "../../Version.h"
+#include "Tile.h"
+
+using namespace Pinetime::Applications::Screens;
+
+SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp *app,
+ Pinetime::Controllers::DateTime &dateTimeController,
+ Pinetime::Controllers::Battery& batteryController,
+ Pinetime::Controllers::BrightnessController& brightnessController,
+ Pinetime::Controllers::Ble& bleController,
+ Pinetime::Drivers::WatchdogView& watchdog) :
+ Screen(app),
+ dateTimeController{dateTimeController}, batteryController{batteryController},
+ brightnessController{brightnessController}, bleController{bleController}, watchdog{watchdog},
+ screens{app, {
+ [this]() -> std::unique_ptr<Screen> { return CreateScreen1(); },
+ [this]() -> std::unique_ptr<Screen> { return CreateScreen2(); },
+ [this]() -> std::unique_ptr<Screen> { return CreateScreen3(); }
+ }
+ } {}
+
+
+SystemInfo::~SystemInfo() {
+ lv_obj_clean(lv_scr_act());
+}
+
+bool SystemInfo::Refresh() {
+ screens.Refresh();
+ return running;
+}
+
+bool SystemInfo::OnButtonPushed() {
+ running = false;
+ return true;
+}
+
+bool SystemInfo::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
+ return screens.OnTouchEvent(event);
+}
+
+std::unique_ptr<Screen> SystemInfo::CreateScreen1() {
+ auto batteryPercentF = batteryController.PercentRemaining();
+ uint16_t batteryPercent = 0;
+ if(batteryPercentF > 100.0f) batteryPercent = 100;
+ else if(batteryPercentF < 0.0f) batteryPercent = 0;
+
+ uint8_t brightness = 0;
+ switch(brightnessController.Level()) {
+ case Controllers::BrightnessController::Levels::Off: brightness = 0; break;
+ case Controllers::BrightnessController::Levels::Low: brightness = 1; break;
+ case Controllers::BrightnessController::Levels::Medium: brightness = 2; break;
+ case Controllers::BrightnessController::Levels::High: brightness = 3; break;
+ }
+ auto resetReason = [this]() {
+ switch (watchdog.ResetReason()) {
+ case Drivers::Watchdog::ResetReasons::Watchdog: return "wtdg";
+ case Drivers::Watchdog::ResetReasons::HardReset: return "hardr";
+ case Drivers::Watchdog::ResetReasons::NFC: return "nfc";
+ case Drivers::Watchdog::ResetReasons::SoftReset: return "softr";
+ case Drivers::Watchdog::ResetReasons::CpuLockup: return "cpulock";
+ case Drivers::Watchdog::ResetReasons::SystemOff: return "off";
+ case Drivers::Watchdog::ResetReasons::LpComp: return "lpcomp";
+ case Drivers::Watchdog::ResetReasons::DebugInterface: return "dbg";
+ case Drivers::Watchdog::ResetReasons::ResetPin: return "rst";
+ default: return "?";
+ }
+ }();
+
+ // uptime
+ static constexpr uint32_t secondsInADay = 60*60*24;
+ static constexpr uint32_t secondsInAnHour = 60*60;
+ static constexpr uint32_t secondsInAMinute = 60;
+ uint32_t uptimeSeconds = dateTimeController.Uptime().count();
+ uint32_t uptimeDays = (uptimeSeconds / secondsInADay);
+ uptimeSeconds = uptimeSeconds % secondsInADay;
+ uint32_t uptimeHours = uptimeSeconds / secondsInAnHour;
+ uptimeSeconds = uptimeSeconds % secondsInAnHour;
+ uint32_t uptimeMinutes = uptimeSeconds / secondsInAMinute;
+ uptimeSeconds = uptimeSeconds % secondsInAMinute;
+ // TODO handle more than 100 days of uptime
+
+ sprintf(t1, "Pinetime\n"
+ "Version:%ld.%ld.%ld\n"
+ "Build: %s\n"
+ " %s\n"
+ "Date: %02d/%02d/%04d\n"
+ "Time: %02d:%02d:%02d\n"
+ "Uptime: %02lud %02lu:%02lu:%02lu\n"
+ "Battery: %d%%\n"
+ "Backlight: %d/3\n"
+ "Last reset: %s\n",
+ Version::Major(), Version::Minor(), Version::Patch(),
+ __DATE__, __TIME__,
+ dateTimeController.Day(), static_cast<uint8_t>(dateTimeController.Month()), dateTimeController.Year(),
+ dateTimeController.Hours(), dateTimeController.Minutes(), dateTimeController.Seconds(),
+ uptimeDays, uptimeHours, uptimeMinutes, uptimeSeconds,
+ batteryPercent, brightness, resetReason);
+
+ return std::unique_ptr<Screen>(new Screens::Label(app, t1));
+}
+
+std::unique_ptr<Screen> SystemInfo::CreateScreen2() {
+ auto& bleAddr = bleController.Address();
+ sprintf(t2, "BLE MAC: \n %02x:%02x:%02x:%02x:%02x:%02x",
+ bleAddr[5], bleAddr[4], bleAddr[3], bleAddr[2], bleAddr[1], bleAddr[0]);
+ return std::unique_ptr<Screen>(new Screens::Label(app, t2));
+}
+
+std::unique_ptr<Screen> SystemInfo::CreateScreen3() {
+ strncpy(t3, "Hello from\nthe developer!", 27);
+ return std::unique_ptr<Screen>(new Screens::Label(app, t3));
+}
diff --git a/src/displayapp/screens/SystemInfo.h b/src/displayapp/screens/SystemInfo.h
new file mode 100644
index 0000000..987a584
--- /dev/null
+++ b/src/displayapp/screens/SystemInfo.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <functional>
+#include <vector>
+
+#include "components/ble/NimbleController.h"
+#include "Screen.h"
+#include "Label.h"
+#include "ScreenList.h"
+#include "Gauge.h"
+#include "Meter.h"
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ class SystemInfo : public Screen {
+ public:
+ explicit SystemInfo(DisplayApp* app,
+ Pinetime::Controllers::DateTime& dateTimeController,
+ Pinetime::Controllers::Battery& batteryController,
+ Pinetime::Controllers::BrightnessController& brightnessController,
+ Pinetime::Controllers::Ble& bleController,
+ Pinetime::Drivers::WatchdogView& watchdog);
+ ~SystemInfo() override;
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+ bool OnTouchEvent(TouchEvents event) override;
+ private:
+ bool running = true;
+
+ Pinetime::Controllers::DateTime& dateTimeController;
+ Pinetime::Controllers::Battery& batteryController;
+ Pinetime::Controllers::BrightnessController& brightnessController;
+ Pinetime::Controllers::Ble& bleController;
+ Pinetime::Drivers::WatchdogView& watchdog;
+
+ char t1[200];
+ char t2[200];
+ char t3[30];
+
+ ScreenList<3> screens;
+ std::unique_ptr<Screen> CreateScreen1();
+ std::unique_ptr<Screen> CreateScreen2();
+ std::unique_ptr<Screen> CreateScreen3();
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/displayapp/screens/Tab.cpp b/src/displayapp/screens/Tab.cpp
new file mode 100644
index 0000000..44b806c
--- /dev/null
+++ b/src/displayapp/screens/Tab.cpp
@@ -0,0 +1,67 @@
+#include <cstdio>
+#include <libs/date/includes/date/date.h>
+#include "components/datetime/DateTimeController.h"
+#include <Version.h>
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+#include <libs/lvgl/src/lv_font/lv_font.h>
+#include <libs/lvgl/lvgl.h>
+#include <libraries/log/nrf_log.h>
+#include "Tab.h"
+#include "displayapp/DisplayApp.h"
+
+
+using namespace Pinetime::Applications::Screens;
+
+extern lv_font_t jetbrains_mono_bold_20;
+
+//static void event_handler(lv_obj_t * obj, lv_event_t event) {
+// Tile* screen = static_cast<Tile *>(obj->user_data);
+// screen->OnObjectEvent(obj, event);
+//}
+
+Tab::Tab(DisplayApp* app, Pinetime::Components::Gfx &gfx) : Screen(app, gfx) {
+/*Create a Tab view object*/
+ lv_obj_t *tabview;
+ tabview = lv_tabview_create(lv_scr_act(), NULL);
+
+ /*Add 3 tabs (the tabs are page (lv_page) and can be scrolled*/
+ lv_obj_t *tab1 = lv_tabview_add_tab(tabview, "Tab 1");
+ lv_obj_t *tab2 = lv_tabview_add_tab(tabview, "Tab 2");
+ lv_obj_t *tab3 = lv_tabview_add_tab(tabview, "Tab 3");
+
+
+ /*Add content to the tabs*/
+ lv_obj_t * label = lv_label_create(tab1, NULL);
+ lv_label_set_text(label, "This the first tab\n\n"
+ "If the content\n"
+ "of a tab\n"
+ "become too long\n"
+ "the it \n"
+ "automatically\n"
+ "become\n"
+ "scrollable.");
+
+ label = lv_label_create(tab2, NULL);
+ lv_label_set_text(label, "Second tab");
+
+ label = lv_label_create(tab3, NULL);
+ lv_label_set_text(label, "Third tab");
+
+}
+
+Tab::~Tab() {
+ lv_obj_clean(lv_scr_act());
+}
+
+void Tab::Refresh(bool fullRefresh) {
+
+}
+
+void Tab::OnObjectEvent(lv_obj_t *obj, lv_event_t event) {
+ if(event == LV_EVENT_CLICKED) {
+ NRF_LOG_INFO("Clicked");
+ }
+ else if(event == LV_EVENT_VALUE_CHANGED) {
+ NRF_LOG_INFO("Toggled");
+ }
+}
diff --git a/src/displayapp/screens/Tab.h b/src/displayapp/screens/Tab.h
new file mode 100644
index 0000000..e16dbb9
--- /dev/null
+++ b/src/displayapp/screens/Tab.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <cstdint>
+#include "Screen.h"
+#include <bits/unique_ptr.h>
+#include <lvgl/src/lv_core/lv_style.h>
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ class Tab : public Screen {
+ public:
+ explicit Tab(DisplayApp* app, Components::Gfx& gfx);
+ ~Tab() override;
+ void Refresh(bool fullRefresh) override;
+ void OnObjectEvent(lv_obj_t* obj, lv_event_t event);
+
+ private:
+
+ };
+ }
+ }
+}
diff --git a/src/displayapp/screens/Tile.cpp b/src/displayapp/screens/Tile.cpp
new file mode 100644
index 0000000..75fa6ef
--- /dev/null
+++ b/src/displayapp/screens/Tile.cpp
@@ -0,0 +1,62 @@
+#include <libs/lvgl/src/lv_core/lv_obj.h>
+#include <libs/lvgl/src/lv_font/lv_font.h>
+#include <libs/lvgl/lvgl.h>
+
+#include "Tile.h"
+#include "displayapp/DisplayApp.h"
+#include "Symbols.h"
+#include "../../Version.h"
+
+using namespace Pinetime::Applications::Screens;
+
+extern lv_font_t jetbrains_mono_bold_20;
+
+static void event_handler(lv_obj_t * obj, lv_event_t event) {
+ Tile* screen = static_cast<Tile *>(obj->user_data);
+ uint32_t* eventDataPtr = (uint32_t*) lv_event_get_data();
+ uint32_t eventData = *eventDataPtr;
+ screen->OnObjectEvent(obj, event, eventData);
+}
+
+Tile::Tile(DisplayApp* app, std::array<Applications, 6>& applications) : Screen(app) {
+ for(int i = 0, appIndex = 0; i < 8; i++) {
+ if(i == 3) btnm_map1[i] = "\n";
+ else if(i == 7) btnm_map1[i] = "";
+ else {
+ btnm_map1[i] = applications[appIndex].icon;
+ apps[appIndex] = applications[appIndex].application;
+ appIndex++;
+ }
+ }
+ modal.reset(new Modal(app));
+
+ btnm1 = lv_btnm_create(lv_scr_act(), nullptr);
+ lv_btnm_set_map(btnm1, btnm_map1);
+ lv_obj_set_size(btnm1, LV_HOR_RES, LV_VER_RES);
+
+ btnm1->user_data = this;
+ lv_obj_set_event_cb(btnm1, event_handler);
+}
+
+Tile::~Tile() {
+ lv_obj_clean(lv_scr_act());
+}
+
+bool Tile::Refresh() {
+ return running;
+}
+
+void Tile::OnObjectEvent(lv_obj_t *obj, lv_event_t event, uint32_t buttonId) {
+ if(event == LV_EVENT_VALUE_CHANGED) {
+ app->StartApp(apps[buttonId]);
+ running = false;
+ }
+}
+
+bool Tile::OnButtonPushed() {
+ app->StartApp(Apps::Clock);
+ running = false;
+ return true;
+}
+
+
diff --git a/src/displayapp/screens/Tile.h b/src/displayapp/screens/Tile.h
new file mode 100644
index 0000000..cf5fcf1
--- /dev/null
+++ b/src/displayapp/screens/Tile.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <cstdint>
+#include "Screen.h"
+#include <bits/unique_ptr.h>
+#include "Modal.h"
+#include <lvgl/src/lv_core/lv_style.h>
+#include <displayapp/Apps.h>
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ class Tile : public Screen {
+ public:
+ struct Applications {
+ const char* icon;
+ Pinetime::Applications::Apps application;
+ };
+
+ explicit Tile(DisplayApp* app, std::array<Applications, 6>& applications);
+ ~Tile() override;
+
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+
+ void OnObjectEvent(lv_obj_t* obj, lv_event_t event, uint32_t buttonId);
+
+ private:
+ lv_obj_t * btnm1;
+ bool running = true;
+
+ std::unique_ptr<Modal> modal;
+
+ const char* btnm_map1[8];
+ Pinetime::Applications::Apps apps[6];
+ };
+ }
+ }
+}