diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ff0c9b0cf8..86f854139a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -435,6 +435,7 @@ list(APPEND SOURCE_FILES displayapp/screens/Error.cpp displayapp/screens/Alarm.cpp displayapp/screens/Styles.cpp + displayapp/screens/Timeline.cpp displayapp/Colors.cpp ## Settings @@ -495,6 +496,8 @@ list(APPEND SOURCE_FILES components/ble/ServiceDiscovery.cpp components/ble/HeartRateService.cpp components/ble/MotionService.cpp + components/ble/CalendarService.cpp + components/calendar/CalendarManager.cpp components/firmwarevalidator/FirmwareValidator.cpp components/motor/MotorController.cpp components/settings/Settings.cpp @@ -568,6 +571,8 @@ list(APPEND RECOVERY_SOURCE_FILES components/ble/NavigationService.cpp components/ble/HeartRateService.cpp components/ble/MotionService.cpp + components/ble/CalendarService.cpp + components/calendar/CalendarManager.cpp components/firmwarevalidator/FirmwareValidator.cpp components/settings/Settings.cpp components/timer/TimerController.cpp @@ -647,6 +652,7 @@ set(INCLUDE_FILES displayapp/screens/Motion.h displayapp/screens/Timer.h displayapp/screens/Alarm.h + displayapp/screens/Timeline.h displayapp/Colors.h drivers/St7789.h drivers/SpiNorFlash.h @@ -683,6 +689,8 @@ set(INCLUDE_FILES components/ble/HeartRateService.h components/ble/MotionService.h components/ble/weather/WeatherService.h + components/ble/CalendarService.h + components/calendar/CalendarManager.h components/settings/Settings.h components/timer/TimerController.h components/alarm/AlarmController.h diff --git a/src/components/ble/CalendarService.cpp b/src/components/ble/CalendarService.cpp new file mode 100644 index 0000000000..59985d3d4a --- /dev/null +++ b/src/components/ble/CalendarService.cpp @@ -0,0 +1,80 @@ +#include +#include "CalendarService.h" + +using namespace Pinetime::Controllers; + +int CalCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) { + auto pCalendarEventService = static_cast(arg); + return pCalendarEventService->OnCommand(conn_handle, attr_handle, ctxt); +} + +uint32_t bytesToInt(const char* bytes) { + return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; +} + +CalendarService::CalendarService(Pinetime::System::SystemTask& system, CalendarManager& calendarManager) + : m_system(system), m_calendarManager(calendarManager) { + calUuid.value[14] = calId[0]; + calUuid.value[15] = calId[1]; + + calAddEventUuid.value[12] = calAddEventCharId[0]; + calAddEventUuid.value[13] = calAddEventCharId[1]; + calAddEventUuid.value[14] = calId[0]; + calAddEventUuid.value[15] = calId[1]; + + calDeleteEventUuid.value[12] = calDeleteEventCharId[0]; + calDeleteEventUuid.value[13] = calDeleteEventCharId[1]; + calDeleteEventUuid.value[14] = calId[0]; + calDeleteEventUuid.value[15] = calId[1]; + + characteristicDefinition[0] = { + .uuid = (ble_uuid_t*) (&calAddEventUuid), + .access_cb = CalCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE, + }; + characteristicDefinition[1] = { + .uuid = (ble_uuid_t*) (&calDeleteEventUuid), + .access_cb = CalCallback, + .arg = this, + .flags = BLE_GATT_CHR_F_WRITE, + }; + characteristicDefinition[2] = {0}; + + serviceDefinition[0] = { + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = (ble_uuid_t*) &calUuid, + .characteristics = characteristicDefinition, + }; + serviceDefinition[1] = {0}; +} +void CalendarService::Init() { + int res = 0; + res = ble_gatts_count_cfg(serviceDefinition); + ASSERT(res == 0); + + res = ble_gatts_add_svcs(serviceDefinition); + ASSERT(res == 0); +} +int CalendarService::OnCommand(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) { + if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { + size_t notifSize = OS_MBUF_PKTLEN(ctxt->om); + uint8_t data[notifSize + 1]; + data[notifSize] = '\0'; + os_mbuf_copydata(ctxt->om, 0, notifSize, data); + char* s = (char*) &data[0]; + NRF_LOG_INFO("DATA : %s", s); + if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t*) &calAddEventUuid) == 0) { + CalendarManager::CalendarEvent event { + .id = s[0], + .title = &s[9], + .timestamp = bytesToInt(&s[1]), + .duration = bytesToInt(&s[5]), + }; + m_calendarManager.addEvent(event); + } else if (ble_uuid_cmp(ctxt->chr->uuid, (ble_uuid_t*) &calDeleteEventUuid) == 0) { + m_calendarManager.deleteEvent(s[0]); + } + } + return 0; +} diff --git a/src/components/ble/CalendarService.h b/src/components/ble/CalendarService.h new file mode 100644 index 0000000000..8c9bcf03e8 --- /dev/null +++ b/src/components/ble/CalendarService.h @@ -0,0 +1,55 @@ +#pragma once + +// 00000000-78fc-48fe-8e23-433b3a1942d0 +#include +#include +#include +#include "components/calendar/CalendarManager.h" + +#define CALENDAR_SERVICE_UUID_BASE \ + { 0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, 0x00, 0x00, 0x04, 0x00 } + +namespace Pinetime { + namespace System { + class SystemTask; + } + namespace Controllers { + + class CalendarService { + public: + explicit CalendarService(Pinetime::System::SystemTask& system, CalendarManager& calendarManager); + + void Init(); + + int OnCommand(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt); + + void event(char event); + + static const char EVENT_CALENDAR_ADD = 0x01; + static const char EVENT_CALENDAR_DELETE = 0x02; + + private: + static constexpr uint8_t calId[2] = {0x04, 0x00}; + static constexpr uint8_t calAddEventCharId[2] = {0x01, 0x00}; + static constexpr uint8_t calDeleteEventCharId[2] = {0x02, 0x00}; + ble_uuid128_t calUuid { + .u = {.type = BLE_UUID_TYPE_128}, + .value = CALENDAR_SERVICE_UUID_BASE, + }; + ble_uuid128_t calAddEventUuid { + .u = {.type = BLE_UUID_TYPE_128}, + .value = CALENDAR_SERVICE_UUID_BASE, + }; + ble_uuid128_t calDeleteEventUuid { + .u = {.type = BLE_UUID_TYPE_128}, + .value = CALENDAR_SERVICE_UUID_BASE, + }; + + struct ble_gatt_chr_def characteristicDefinition[3]; + struct ble_gatt_svc_def serviceDefinition[2]; + + Pinetime::System::SystemTask& m_system; + CalendarManager& m_calendarManager; + }; + } +}; diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp index 0be7c0f7fb..288f7941aa 100644 --- a/src/components/ble/NimbleController.cpp +++ b/src/components/ble/NimbleController.cpp @@ -16,6 +16,7 @@ #undef max #undef min #include "components/ble/BleController.h" +#include "components/calendar/CalendarManager.h" #include "components/ble/NotificationManager.h" #include "components/datetime/DateTimeController.h" #include "components/fs/FS.h" @@ -25,6 +26,7 @@ using namespace Pinetime::Controllers; NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask, Ble& bleController, + CalendarManager& calendarManager, DateTime& dateTimeController, NotificationManager& notificationManager, Battery& batteryController, @@ -43,6 +45,7 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask, currentTimeClient {dateTimeController}, anService {systemTask, notificationManager}, alertNotificationClient {systemTask, notificationManager}, + calendarService{systemTask, calendarManager}, currentTimeService {dateTimeController}, musicService {systemTask}, weatherService {systemTask, dateTimeController}, @@ -88,6 +91,7 @@ void NimbleController::Init() { ble_svc_gatt_init(); deviceInformationService.Init(); + calendarService.Init(); currentTimeClient.Init(); currentTimeService.Init(); musicService.Init(); diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h index ad1942121a..ff3a7b8c4f 100644 --- a/src/components/ble/NimbleController.h +++ b/src/components/ble/NimbleController.h @@ -10,6 +10,7 @@ #include "components/ble/AlertNotificationClient.h" #include "components/ble/AlertNotificationService.h" #include "components/ble/BatteryInformationService.h" +#include "components/ble/CalendarService.h" #include "components/ble/CurrentTimeClient.h" #include "components/ble/CurrentTimeService.h" #include "components/ble/DeviceInformationService.h" @@ -43,6 +44,7 @@ namespace Pinetime { public: NimbleController(Pinetime::System::SystemTask& systemTask, Ble& bleController, + CalendarManager& calendarManager, DateTime& dateTimeController, NotificationManager& notificationManager, Battery& batteryController, @@ -95,6 +97,7 @@ namespace Pinetime { CurrentTimeClient currentTimeClient; AlertNotificationService anService; AlertNotificationClient alertNotificationClient; + CalendarService calendarService; CurrentTimeService currentTimeService; MusicService musicService; WeatherService weatherService; diff --git a/src/components/calendar/CalendarManager.cpp b/src/components/calendar/CalendarManager.cpp new file mode 100644 index 0000000000..cc7c3747c0 --- /dev/null +++ b/src/components/calendar/CalendarManager.cpp @@ -0,0 +1,63 @@ +#include "CalendarManager.h" +#include + +using namespace Pinetime::Controllers; + +/** + * Determines if the event `event1` should be placed before event `event2` + * @param event1 The event to be inserted + * @param event2 The comparison event + * @return + */ +bool CalendarManager::isBefore(CalendarEvent& event1, CalendarEvent& event2) { + return event1.timestamp < event2.timestamp; +} + + +/** + * Inserts a new event if enough space + * @param event The event to insert + * @return true if the operation succeeded (enough space), false otherwise + */ +bool CalendarManager::addEvent(CalendarEvent& event) { + auto it = calendarEvents.begin(); + + while (it != calendarEvents.end() && isBefore(*it, event)) { + ++it; + } + calendarEvents.insert(it, event); + return true; +} + +/** + * Removes the event corresponding to the given id if found + * @param id The id of the event to remove + * @return true if the event is found, false otherwise + */ +bool CalendarManager::deleteEvent(CalendarManager::CalendarEvent::Id id) { + auto matchId = [id](const CalendarEvent& event) { return event.id == id; }; + auto it = std::find_if(calendarEvents.begin(), calendarEvents.end(), matchId); + + if (it != calendarEvents.end()) { + calendarEvents.erase(it); + return true; + } + + return false; +} + +CalendarManager::CalendarEventIterator CalendarManager::begin() { + return calendarEvents.begin(); +} + +CalendarManager::CalendarEventIterator CalendarManager::end() { + return calendarEvents.end(); +} + +bool CalendarManager::empty() const { + return calendarEvents.empty(); +} + +unsigned int CalendarManager::getCount() const { + return calendarEvents.size(); +} diff --git a/src/components/calendar/CalendarManager.h b/src/components/calendar/CalendarManager.h new file mode 100644 index 0000000000..f66cf7f9f7 --- /dev/null +++ b/src/components/calendar/CalendarManager.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Pinetime { + namespace Controllers { + class CalendarManager { + public: + struct CalendarEvent { + using Id = uint8_t; + Id id; + std::string title; + uint32_t timestamp; + uint32_t duration; + }; + using CalendarEventIterator = std::list::iterator; + + private: + static bool isBefore(CalendarEvent& event1, CalendarEvent& event2); + + std::list calendarEvents; + + public: + bool addEvent(CalendarEvent& event); + bool deleteEvent(CalendarEvent::Id id); + CalendarEventIterator begin(); + CalendarEventIterator end(); + bool empty() const; + unsigned int getCount() const; + }; + } +} \ No newline at end of file diff --git a/src/displayapp/Apps.h b/src/displayapp/Apps.h index dc9e62536b..aaf292239e 100644 --- a/src/displayapp/Apps.h +++ b/src/displayapp/Apps.h @@ -25,6 +25,7 @@ namespace Pinetime { Metronome, Motion, Steps, + Timeline, Weather, PassKey, QuickSettings, diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index fdc6376c5d..0166d43f6a 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -25,6 +25,7 @@ #include "displayapp/screens/Notifications.h" #include "displayapp/screens/SystemInfo.h" #include "displayapp/screens/Tile.h" +#include "displayapp/screens/Timeline.h" #include "displayapp/screens/Twos.h" #include "displayapp/screens/FlashLight.h" #include "displayapp/screens/BatteryInfo.h" @@ -89,6 +90,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Drivers::Cst816S& touchPanel, Controllers::Battery& batteryController, Controllers::Ble& bleController, + Controllers::CalendarManager& calendarManager, Controllers::DateTime& dateTimeController, Drivers::WatchdogView& watchdog, Pinetime::Controllers::NotificationManager& notificationManager, @@ -105,6 +107,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, touchPanel {touchPanel}, batteryController {batteryController}, bleController {bleController}, + calendarManager {calendarManager}, dateTimeController {dateTimeController}, watchdog {watchdog}, notificationManager {notificationManager}, @@ -482,6 +485,10 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction) case Apps::Steps: currentScreen = std::make_unique(this, motionController, settingsController); break; + + case Apps::Timeline: + currentScreen = std::make_unique(this, dateTimeController, calendarManager, lvgl); + break; } currentApp = app; } diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index 1eaefaa106..242b4f734b 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -8,6 +8,7 @@ #include "displayapp/Apps.h" #include "displayapp/LittleVgl.h" #include "displayapp/TouchEvents.h" +#include "components/calendar/CalendarManager.h" #include "components/brightness/BrightnessController.h" #include "components/motor/MotorController.h" #include "components/firmwarevalidator/FirmwareValidator.h" @@ -52,6 +53,7 @@ namespace Pinetime { Drivers::Cst816S&, Controllers::Battery& batteryController, Controllers::Ble& bleController, + Controllers::CalendarManager& calendarManager, Controllers::DateTime& dateTimeController, Drivers::WatchdogView& watchdog, Pinetime::Controllers::NotificationManager& notificationManager, @@ -78,6 +80,7 @@ namespace Pinetime { Pinetime::Drivers::Cst816S& touchPanel; Pinetime::Controllers::Battery& batteryController; Pinetime::Controllers::Ble& bleController; + Pinetime::Controllers::CalendarManager& calendarManager; Pinetime::Controllers::DateTime& dateTimeController; Pinetime::Drivers::WatchdogView& watchdog; Pinetime::System::SystemTask* systemTask = nullptr; diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp index 9d6eb22f68..acd5fc343c 100644 --- a/src/displayapp/DisplayAppRecovery.cpp +++ b/src/displayapp/DisplayAppRecovery.cpp @@ -14,6 +14,7 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, Drivers::Cst816S& touchPanel, Controllers::Battery& batteryController, Controllers::Ble& bleController, + Controllers::CalendarManager& calendarManager, Controllers::DateTime& dateTimeController, Drivers::WatchdogView& watchdog, Pinetime::Controllers::NotificationManager& notificationManager, diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h index 61f1c9bf5c..c8defd9e65 100644 --- a/src/displayapp/DisplayAppRecovery.h +++ b/src/displayapp/DisplayAppRecovery.h @@ -26,6 +26,7 @@ namespace Pinetime { class Settings; class Battery; class Ble; + class CalendarManager; class DateTime; class NotificationManager; class HeartRateController; @@ -49,6 +50,7 @@ namespace Pinetime { Drivers::Cst816S&, Controllers::Battery& batteryController, Controllers::Ble& bleController, + Controllers::CalendarManager& calendarManager, Controllers::DateTime& dateTimeController, Drivers::WatchdogView& watchdog, Pinetime::Controllers::NotificationManager& notificationManager, diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp index 29c8affbc6..80358c5bd5 100644 --- a/src/displayapp/screens/ApplicationList.cpp +++ b/src/displayapp/screens/ApplicationList.cpp @@ -25,7 +25,9 @@ ApplicationList::ApplicationList(Pinetime::Applications::DisplayApp* app, [this]() -> std::unique_ptr { return CreateScreen2(); }, - //[this]() -> std::unique_ptr { return CreateScreen3(); } + [this]() -> std::unique_ptr { + return CreateScreen3(); + } }, Screens::ScreenListModes::UpDown} { } @@ -64,16 +66,16 @@ std::unique_ptr ApplicationList::CreateScreen2() { return std::make_unique(1, 2, app, settingsController, batteryController, dateTimeController, applications); } -/*std::unique_ptr ApplicationList::CreateScreen3() { +std::unique_ptr ApplicationList::CreateScreen3() { std::array applications { - {{"A", Apps::Meter}, + {{"A", Apps::Timeline}, {"B", Apps::Navigation}, {"C", Apps::Clock}, {"D", Apps::Music}, {"E", Apps::SysInfo}, - {"F", Apps::Brightness} + {"F", Apps::Weather} } }; return std::make_unique(2, 3, app, settingsController, batteryController, dateTimeController, applications); -}*/ +} diff --git a/src/displayapp/screens/ApplicationList.h b/src/displayapp/screens/ApplicationList.h index f430a89ef1..27ebbac94d 100644 --- a/src/displayapp/screens/ApplicationList.h +++ b/src/displayapp/screens/ApplicationList.h @@ -25,10 +25,10 @@ namespace Pinetime { Pinetime::Controllers::Battery& batteryController; Controllers::DateTime& dateTimeController; - ScreenList<2> screens; + ScreenList<3> screens; std::unique_ptr CreateScreen1(); std::unique_ptr CreateScreen2(); - // std::unique_ptr CreateScreen3(); + std::unique_ptr CreateScreen3(); }; } } diff --git a/src/displayapp/screens/Timeline.cpp b/src/displayapp/screens/Timeline.cpp new file mode 100644 index 0000000000..582da8836b --- /dev/null +++ b/src/displayapp/screens/Timeline.cpp @@ -0,0 +1,229 @@ +#include "Timeline.h" + +#include +#include + +using namespace Pinetime::Applications::Screens; + +static constexpr char days[] = "M\0T\0W\0T\0F\0S\0S\0M\0T\0W\0T\0F\0S"; +static constexpr char hours_texts[] = " 4\0" " 8\0" "12\0" "16\0" "20"; + +void formatDateTime(char* buffer, time_t timestamp) { + auto *time = localtime(×tamp); + auto year = 1900 + time->tm_year; + auto month = 1 + time->tm_mon; + sprintf(buffer, "%d-%d-%d %d:%d", year, month, time->tm_mday, time->tm_hour, time->tm_min); +} + +Timeline::Timeline(DisplayApp* app, + Controllers::DateTime& dateTimeController, + Controllers::CalendarManager& calendarManager, + Components::LittleVgl& lvgl) + : Screen(app), dateTimeController(dateTimeController), calendarManager(calendarManager), lvgl{lvgl} { + title_label = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text(title_label, "TIMELINE"); + lv_obj_set_auto_realign(title_label, true); + lv_obj_align(title_label, nullptr, LV_ALIGN_IN_TOP_MID, 0, 0); + + time_label = lv_label_create(lv_scr_act(), nullptr); + lv_label_set_text_static(time_label, "Swipe to use!\n\nUp/Down: prev/next\nLeft: timetable\nRight: timeline"); + lv_obj_set_auto_realign(time_label, true); + lv_obj_align(time_label, nullptr, LV_ALIGN_CENTER, 0, 0); + + days_container = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(days_container, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_size(days_container, TIMETABLE_WIDTH, lv_obj_get_height(title_label)); + lv_obj_align(days_container, nullptr, LV_ALIGN_IN_TOP_MID, 0, 0); + + timetable_draw_area = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_size(timetable_draw_area, TIMETABLE_WIDTH + 1, TIMETABLE_HEIGHT + 1); + lv_obj_set_style_local_radius(timetable_draw_area, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 0); + lv_obj_set_style_local_bg_opa(timetable_draw_area, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_style_local_bg_color(timetable_draw_area, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_TRANSP); + lv_obj_set_style_local_border_color(timetable_draw_area, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); + lv_obj_set_style_local_border_width(timetable_draw_area, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, 1); + lv_obj_move_background(timetable_draw_area); + lv_obj_align(timetable_draw_area, days_container, LV_ALIGN_OUT_BOTTOM_MID, 0, 0); + + for (int i=0; i < 7; i++) { + days_labels[i] = lv_label_create(days_container, nullptr); + lv_label_set_text_static(days_labels[i], &days[i*2]); + lv_obj_set_width(days_labels[i], TIMESLOT_PX_WIDTH); + lv_obj_align_mid_x(days_labels[i], nullptr, LV_ALIGN_IN_BOTTOM_LEFT, INTER_DAY_SPACE + TIMESLOT_PX_WIDTH/2 + DAY_PX_OFFSET*i); + lv_obj_align_y(days_labels[i], nullptr, LV_ALIGN_IN_BOTTOM_LEFT, 0); + } + + hours_container = lv_obj_create(lv_scr_act(), nullptr); + lv_obj_set_style_local_bg_opa(hours_container, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP); + lv_obj_set_size(hours_container, 25, TIMETABLE_HEIGHT); + lv_obj_align(hours_container, timetable_draw_area, LV_ALIGN_OUT_LEFT_MID, 0, 0); + + static constexpr lv_coord_t HOURS_LABELS_SPACING = TIMETABLE_HEIGHT / 6; + for (int i=0; i < 5; i++) { + hours_labels[i] = lv_label_create(hours_container, nullptr); + lv_label_set_text_static(hours_labels[i], &hours_texts[i*3]); + lv_obj_align_mid_x(hours_labels[i], nullptr, LV_ALIGN_IN_TOP_MID, 0); + lv_obj_align_mid_y(hours_labels[i], nullptr, LV_ALIGN_IN_TOP_MID, HOURS_LABELS_SPACING*(i+1)); + } + + lv_obj_set_hidden(timetable_draw_area, true); + lv_obj_set_hidden(days_container, true); + lv_obj_set_hidden(hours_container, true); + + currentEvent = calendarManager.begin(); +} + +Timeline::~Timeline() { + lv_obj_clean(lv_scr_act()); +} + +bool Timeline::OnTouchEvent(TouchEvents event) { + switch (event) { + case TouchEvents::SwipeUp: + currentEvent++; + if (currentEvent == calendarManager.end()) { + currentEvent--; + } + + displayCurrentEvent(); + break; + + case TouchEvents::SwipeDown: + if (currentEvent != calendarManager.begin()) { + currentEvent--; + } + + displayCurrentEvent(); + break; + + case TouchEvents::SwipeLeft: + displayWeek(); + break; + + case TouchEvents::SwipeRight: + displayCurrentEvent(); + break; + + default: + return false; + } + return true; +} + +bool Timeline::OnTouchEvent(uint16_t x, uint16_t y) { + return Screen::OnTouchEvent(x, y); +} + +void Timeline::set_timeslot_area(lv_area_t* timeslot_area, uint8_t day_index, uint16_t timeslot_index) { + const lv_coord_t TIMETABLE_X0 = lv_obj_get_x(timetable_draw_area) + INTER_DAY_SPACE; + const lv_coord_t TIMETABLE_Y0 = lv_obj_get_y(timetable_draw_area) + INTER_DAY_SPACE; + + const lv_coord_t x_offset = TIMETABLE_X0 + DAY_PX_OFFSET * day_index; + const lv_coord_t y_offset = TIMETABLE_Y0 + timeslot_index * TIMESLOT_PX_HEIGHT; + + timeslot_area->x1 = x_offset; + timeslot_area->y1 = y_offset; + timeslot_area->x2 = x_offset + TIMESLOT_PX_WIDTH - 1; + timeslot_area->y2 = y_offset + TIMESLOT_PX_HEIGHT - 1; +} + +void Timeline::draw_event(uint8_t day_column, uint16_t event_start, uint16_t duration) { + uint16_t event_end = event_start + duration; + + // Handle day overflow recursively + if (event_end > MINUTES_PER_DAY) { + draw_event(day_column+1, 0, event_end - MINUTES_PER_DAY); + event_end = MINUTES_PER_DAY; + } + + // If the event starts before or finishes after the middle of a timeslot, shrink/expand to next timeslot + const lv_coord_t FIRST_TIMESLOT_UNDERFLOW = (event_start % MINUTES_PER_TIMESLOT > MINUTES_PER_TIMESLOT/2) ? 1 : 0; + const lv_coord_t LAST_TIMESLOT_OVERFLOW = (event_end % MINUTES_PER_TIMESLOT < MINUTES_PER_TIMESLOT/2) ? 0 : 1; + + // Compute rounded timeslot event_start and end + const lv_coord_t FIRST_TIMESLOT_START = event_start / MINUTES_PER_TIMESLOT + FIRST_TIMESLOT_UNDERFLOW; + const lv_coord_t LAST_TIMESLOT_END = event_end / MINUTES_PER_TIMESLOT + LAST_TIMESLOT_OVERFLOW; + + lv_area_t timeslot_area; + for(lv_coord_t timeslot=FIRST_TIMESLOT_START; timeslot < LAST_TIMESLOT_END; timeslot++) { + set_timeslot_area(×lot_area, day_column, timeslot); + lvgl.SetFullRefresh(Components::LittleVgl::FullRefreshDirections::None); + lvgl.FlushDisplay(×lot_area, timeslotBuffer); + } +} + +void Timeline::displayWeek() { + lv_obj_set_hidden(time_label, true); + lv_obj_set_hidden(title_label, true); + + lv_obj_set_hidden(timetable_draw_area, false); + lv_obj_set_hidden(days_container, false); + lv_obj_set_hidden(hours_container, false); + + auto currentDateTime = dateTimeController.CurrentDateTime(); + auto dp = date::floor(currentDateTime); + auto time = date::make_time(currentDateTime-dp); + auto yearMonthDay = date::year_month_day(dp); + today_index = date::weekday(yearMonthDay).iso_encoding() - 1; + + + // Wait to allow lvgl to first draw the UI + lv_refr_now(nullptr); + uint32_t t = lv_tick_get(); + while(lv_tick_elaps(t) < 100); + + std::fill(timeslotBuffer, ×lotBuffer[TIMESLOT_BUFFER_SIZE], LV_COLOR_RED); + + for (int di=0; di<7; di++) { + draw_event(di, 60 * 3, 10*di); + draw_event(di, 60 * 8 + 45 + 60*(di%2), 90); + draw_event(di, 60 * 11 + 15 + 60*(di%2), 90); + draw_event(di, 60 * 14 + 45 + 60*(di%2), 90); + draw_event(di, 60 * 16 + 15 + 60*(di%2), 90); + draw_event(di, 60 * 19 + 45 + 60*(di%2), 90); + } + + lv_refr_now(nullptr); + uint32_t t2 = lv_tick_get(); + while(lv_tick_elaps(t2) < 100); + std::fill(timeslotBuffer, ×lotBuffer[TIMESLOT_BUFFER_SIZE], LV_COLOR_BLUE); + + for(auto & it : calendarManager) { + time_t timestamp = it.timestamp; + tm *it_time = localtime(×tamp); + uint8_t day_index = it_time->tm_wday-1; + if (day_index < today_index) { + day_index += 7; + } + + uint8_t column_index = day_index - today_index; + if (column_index == 0 && timestamp > currentDateTime.time_since_epoch().count()) { + continue; // Do not show events matching the current day of the week but occurring next week + } + + draw_event(column_index, it_time->tm_hour*60 + it_time->tm_min, it.duration/60); + } +} + +void Timeline::displayCurrentEvent() { + lv_obj_set_hidden(timetable_draw_area, true); + lv_obj_set_hidden(days_container, true); + lv_obj_set_hidden(hours_container, true); + + lv_obj_set_hidden(title_label, false); + lv_obj_set_hidden(time_label, false); + + if (currentEvent == calendarManager.end()) { + lv_label_set_text_static(title_label, "TIMELINE"); + lv_label_set_text(time_label, "No event"); + } else { + auto event = *currentEvent; + lv_label_set_text_fmt(title_label, "%s", event.title.c_str()); + + char begin[16]; + formatDateTime(&begin[0], event.timestamp); + char end[16]; + formatDateTime(&end[0], event.timestamp + event.duration); + lv_label_set_text_fmt(time_label, "%s\n->\n%s", &begin[0], &end[0]); + } +} diff --git a/src/displayapp/screens/Timeline.h b/src/displayapp/screens/Timeline.h new file mode 100644 index 0000000000..ec1d18a641 --- /dev/null +++ b/src/displayapp/screens/Timeline.h @@ -0,0 +1,59 @@ +#pragma once + +#include "components/calendar/CalendarManager.h" +#include "displayapp/LittleVgl.h" +#include + +#include "Clock.h" +#include "Screen.h" + +// Timetable hours(y-axis) formatting +static constexpr uint16_t MINUTES_PER_DAY = 24 * 60; +static constexpr uint16_t MINUTES_PER_TIMESLOT = 20; +static constexpr uint16_t TIMESLOT_PX_HEIGHT = 3; +static constexpr lv_coord_t TIMETABLE_HEIGHT = MINUTES_PER_DAY / MINUTES_PER_TIMESLOT * TIMESLOT_PX_HEIGHT; + +// Timetable days(x-axis) formatting +static constexpr lv_coord_t TIMESLOT_PX_WIDTH = 16; +static constexpr lv_coord_t INTER_DAY_SPACE = 8; +static constexpr lv_coord_t DAY_PX_OFFSET = INTER_DAY_SPACE + TIMESLOT_PX_WIDTH; +static constexpr lv_coord_t TIMETABLE_WIDTH = 7 * DAY_PX_OFFSET + INTER_DAY_SPACE; + +namespace Pinetime { + namespace Applications { + namespace Screens { + class Timeline : public Screen { + public: + explicit Timeline(DisplayApp* app, Controllers::DateTime& dateTimeController, Controllers::CalendarManager& calendarManager, Components::LittleVgl& lvgl); + ~Timeline() override; + bool OnTouchEvent(TouchEvents event) override; + bool OnTouchEvent(uint16_t x, uint16_t y) override; + + private: + void displayCurrentEvent(); + void set_timeslot_area(lv_area_t* timeslot_area, uint8_t day_index, uint16_t timeslot_index); + void draw_event(uint8_t day_column, uint16_t event_start, uint16_t duration); + void displayWeek(); + + Controllers::DateTime& dateTimeController; + Controllers::CalendarManager& calendarManager; + Controllers::CalendarManager::CalendarEventIterator currentEvent; + + uint8_t today_index = 0; + + lv_obj_t * title_label; + lv_obj_t * time_label; + + lv_obj_t * days_container; + lv_obj_t* hours_labels[5]; // 4 8 12 16 20 + lv_obj_t * hours_container; + lv_obj_t* days_labels[7]; + + Pinetime::Components::LittleVgl& lvgl; + static constexpr uint16_t TIMESLOT_BUFFER_SIZE = TIMESLOT_PX_WIDTH * TIMESLOT_PX_HEIGHT; + lv_color_t timeslotBuffer[TIMESLOT_BUFFER_SIZE]; + lv_obj_t * timetable_draw_area; + }; + } + } +} diff --git a/src/main.cpp b/src/main.cpp index fa492d08e0..0152317063 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -116,12 +116,14 @@ Pinetime::Controllers::AlarmController alarmController {dateTimeController}; Pinetime::Controllers::TouchHandler touchHandler(touchPanel, lvgl); Pinetime::Controllers::ButtonHandler buttonHandler; Pinetime::Controllers::BrightnessController brightnessController {}; +Pinetime::Controllers::CalendarManager calendarManager {}; Pinetime::Applications::DisplayApp displayApp(lcd, lvgl, touchPanel, batteryController, bleController, + calendarManager, dateTimeController, watchdogView, notificationManager, @@ -142,6 +144,7 @@ Pinetime::System::SystemTask systemTask(spi, lvgl, batteryController, bleController, + calendarManager, dateTimeController, timerController, alarmController, diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 1e45fac194..143a1df706 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -54,6 +54,7 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, Components::LittleVgl& lvgl, Controllers::Battery& batteryController, Controllers::Ble& bleController, + Controllers::CalendarManager& calendarManager, Controllers::DateTime& dateTimeController, Controllers::TimerController& timerController, Controllers::AlarmController& alarmController, @@ -78,6 +79,7 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, lvgl {lvgl}, batteryController {batteryController}, bleController {bleController}, + calendarManager {calendarManager}, dateTimeController {dateTimeController}, timerController {timerController}, alarmController {alarmController}, @@ -96,6 +98,7 @@ SystemTask::SystemTask(Drivers::SpiMaster& spi, buttonHandler {buttonHandler}, nimbleController(*this, bleController, + calendarManager, dateTimeController, notificationManager, batteryController, diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h index c5b0379240..968c5edec1 100644 --- a/src/systemtask/SystemTask.h +++ b/src/systemtask/SystemTask.h @@ -61,6 +61,7 @@ namespace Pinetime { Components::LittleVgl& lvgl, Controllers::Battery& batteryController, Controllers::Ble& bleController, + Controllers::CalendarManager& calendarManager, Controllers::DateTime& dateTimeController, Controllers::TimerController& timerController, Controllers::AlarmController& alarmController, @@ -106,6 +107,7 @@ namespace Pinetime { Pinetime::Controllers::Battery& batteryController; Pinetime::Controllers::Ble& bleController; + Pinetime::Controllers::CalendarManager& calendarManager; Pinetime::Controllers::DateTime& dateTimeController; Pinetime::Controllers::TimerController& timerController; Pinetime::Controllers::AlarmController& alarmController;