diff --git a/src/W800_SDK_v1.00.10/app/gfx/gfx_task.c b/src/W800_SDK_v1.00.10/app/gfx/gfx_task.c index dd90fac..14f4a13 100644 --- a/src/W800_SDK_v1.00.10/app/gfx/gfx_task.c +++ b/src/W800_SDK_v1.00.10/app/gfx/gfx_task.c @@ -13,6 +13,7 @@ #include "find_my_phone_screen.h" #include "music_player_screen.h" #include "settings_screen.h" +#include "notification_screen.h" #include "watch_peripherals.h" #include "watch_settings.h" #include "firmware_version.h" @@ -38,6 +39,7 @@ MenuScreen_t menuScreen; CompassScreen_t compassScreen; FindMyPhoneScreen_t findMyPhoneScreen; MusicPlayerScreen_t musicPlayerScreen; +NotificationScreen_t notificationScreen; SettingsScreen_t settingsScreen; @@ -362,7 +364,7 @@ static uint16_t angle_with_offset(uint16_t angle, uint16_t offset) return (angle + offset) >= 360 ? angle + offset - 360 : angle + offset; } -static void parser_event_cb(const gadget_bridge_event_data_t *gadget_bridge_event_data) +static void parser_event_cb(gadget_bridge_event_data_t *gadget_bridge_event_data) { APP_LOG_INFO("[GB]Event of type : %s\n", gadget_bridge_event_type_2_str(gadget_bridge_event_data->event_type)); @@ -396,6 +398,23 @@ static void parser_event_cb(const gadget_bridge_event_data_t *gadget_bridge_even } music_player_screen_set_music_position(&musicPlayerScreen, gadget_bridge_event_data->music_state.position_in_seconds); break; + case GADGET_BRIDGE_EVENT_TYPE_NOTIFY: + { + // Let's retrieve the time from the watch : + struct tm date_time; + tls_get_rtc(&date_time); + + notification_screen_notify(¬ificationScreen, gadget_bridge_event_data->notification.handle, + mktime(&date_time), + gadget_bridge_event_data->notification.notification_type, + gadget_bridge_event_data->notification.title, + gadget_bridge_event_data->notification.body); + + //As we don't want the title and the body strings to be freed after the callback returns, let's set them to NULL + gadget_bridge_event_data->notification.title = NULL; + gadget_bridge_event_data->notification.body = NULL; + } + break; default: APP_LOG_INFO("Not yet handled\n"); } @@ -481,6 +500,50 @@ static void scan_result_cb(void) tls_mem_free(buffer); } +static void compass_screen_on_state_change_cb(CompassScreenState_e compassScreenState) +{ + switch(compassScreenState) + { + case COMPASS_SCREEN_STATE_OPENED: + watch_peripherals_magnetometer_power_mode_set(QMC5883L_Mode_Control_Continuous); + break; + case COMPASS_SCREEN_STATE_CLOSED: + default: + watch_peripherals_magnetometer_power_mode_set(QMC5883L_Mode_Control_Standby); + break; + } +} + +static void settings_screen_on_state_change_cb(SettingsScreenState_e settingsScreenState, SettingsScreenCategory_e settingsScreenCategory) +{ + switch(settingsScreenState) + { + case SETTINGS_SCREEN_STATE_OPENED: + if(settingsScreenCategory == SETTINGS_SCREEN_CATEGORY_ABOUT) + { + watch_peripherals_magnetometer_power_mode_set(QMC5883L_Mode_Control_Continuous); + } + break; + case SETTINGS_SCREEN_STATE_CLOSED: + if(settingsScreenCategory == SETTINGS_SCREEN_CATEGORY_ABOUT) + { + watch_peripherals_magnetometer_power_mode_set(QMC5883L_Mode_Control_Standby); + } + break; + default: + break; + } +} + +static float bmp_temperature = 0; + +static void compass_screen_azimuth_and_temperature_cb(uint16_t *azimuth, bool *refreshAzimuth, float *temperature, bool *refreshTemperature) +{ + uint16_t sensor_azimuth = watch_peripherals_magnetometer_azimuth_read(refreshAzimuth); + *azimuth = angle_with_offset(sensor_azimuth, 270); + *temperature = bmp_temperature; +} + static void sendFindMyPhoneBLECommandCb(bool findMyPhone) { gadget_bridge_send_find_phone(findMyPhone); @@ -514,7 +577,7 @@ void gfx_task(void *param) /* Let's init all the watch's sensors */ watch_peripherals_pressure_sensor_init(); watch_peripherals_magnetometer_init(); - watch_peripherals_magnetometer_calibration_data_set(-3120, 1050, 1140, 5200, 1200, 3200, 30.0); + watch_peripherals_magnetometer_calibration_data_set(-7200, -2700, -1300, 3300, 1200, 3200, 30.0); watch_peripherals_accelerometer_init(); watch_peripherals_accelerometer_wrist_wakeup_enable(persistency_get_settings()->display.display_wrist_wakeup); watch_peripherals_accelerometer_step_counter_enable(true); @@ -524,7 +587,7 @@ void gfx_task(void *param) APP_LOG_DEBUG("MAX3010X enable temp int %d", MAX3010X_enable_die_temperature_int(true));*/ /* Make the first battery voltage reading here */ - _battery_stats.battery_voltage = watch_peripherals_get_battery_voltage(battery_unit_mv); + _battery_stats.battery_voltage = watch_peripherals_get_battery_voltage(BATTERY_UNIT_MV); _battery_stats.battery_percentage = battery_voltage_to_percentage(_battery_stats.battery_voltage); ble_service_set_battery_value(_battery_stats.battery_percentage); @@ -553,6 +616,8 @@ void gfx_task(void *param) watch_face_init(&watchFace); menu_screen_init(&menuScreen); compass_screen_init(&compassScreen); + compass_screen_register_on_state_change_cb(&compassScreen, &(compass_screen_on_state_change_cb)); + compass_screen_register_azimuth_and_temperature_cb(&compassScreen, &(compass_screen_azimuth_and_temperature_cb)); find_my_phone_screen_init(&findMyPhoneScreen); find_my_phone_screen_register_BLE_command_send_cb(&findMyPhoneScreen, &(sendFindMyPhoneBLECommandCb)); @@ -561,7 +626,10 @@ void gfx_task(void *param) music_player_screen_register_music_playback_control_cb(&musicPlayerScreen, &(sendMusicPlaybackBLECommandCb)); music_player_screen_register_music_player_time_ref_ms_cb(&musicPlayerScreen, &(elapsed_ms)); + notification_screen_init(¬ificationScreen); + settings_screen_init(&settingsScreen); + settings_screen_register_on_state_change_cb(&settingsScreen, &(settings_screen_on_state_change_cb)); settings_screen_register_API_interface(&settingsScreen, &settingsScreenAPIInterface); watch_face_register_date_time_cb(&watchFace, &(date_time_cb)); @@ -584,8 +652,6 @@ void gfx_task(void *param) /* Enable WiFi hotspot scanning for antenna performance test purposes */ //tls_wifi_scan_result_cb_register(&(scan_result_cb)); - - float temperature = 0; float pressure = 0; uint32_t ble_info_update_ms = 0; @@ -593,29 +659,12 @@ void gfx_task(void *param) for(;;) { + /* Actions to perform when the watch is active only */ if(!_is_in_ble_sleep_mode) { lv_timer_handler(); - if(compass_screen_is_in_use(&compassScreen)) - { - bool is_data_available = false; - uint16_t azimuth = watch_peripherals_magnetometer_azimuth_read(&is_data_available); - - if(is_data_available) - { - /* - QMC5883L_MData_t MDataRaw = watch_peripherals_magnetometer_raw_data_read(); - APP_LOG_TRACE("X %d Y %d Z %d", MDataRaw.MFieldX, MDataRaw.MFieldY, MDataRaw.MFieldZ); - */ - - compass_screen_set_azimuth(&compassScreen, angle_with_offset(azimuth, 270)); - } - - compass_screen_set_temperature(&compassScreen, temperature); - } - /* Throttle CPU freq down when inactive to save power or to increase responsiveness */ tls_sys_clk clk; tls_sys_clk_get(&clk); @@ -624,7 +673,7 @@ void gfx_task(void *param) if(clk.cpuclk != 40) { tls_sys_clk_set(CPU_CLK_40M); - APP_LOG_DEBUG("CPU 40Mhz"); + APP_LOG_DEBUG("CPU 40MHz"); } } else @@ -632,7 +681,7 @@ void gfx_task(void *param) if(clk.cpuclk != 160) { tls_sys_clk_set(CPU_CLK_160M); - APP_LOG_DEBUG("CPU 160Mhz"); + APP_LOG_DEBUG("CPU 160MHz"); } } @@ -692,7 +741,6 @@ void gfx_task(void *param) /* On wake up, we force the watch face to sync up with the rtc /!\ RTC update delay WTF ? */ tls_os_time_delay(1); watch_face_force_sync(&watchFace); - watch_peripherals_magnetometer_power_mode_set(QMC5883L_Mode_Control_Continuous); lcd_sleep(&LCDConfig, false); lcd_on(&LCDConfig, false); @@ -707,12 +755,12 @@ void gfx_task(void *param) { main_data_update = elapsed_ms(); - pressure = watch_peripherals_pressure_sensor_get_pressure(&temperature); + pressure = watch_peripherals_pressure_sensor_get_pressure(&bmp_temperature); - _battery_stats.battery_voltage = watch_peripherals_get_battery_voltage(battery_unit_mv); + _battery_stats.battery_voltage = watch_peripherals_get_battery_voltage(BATTERY_UNIT_MV); _battery_stats.battery_percentage = battery_voltage_to_percentage(_battery_stats.battery_voltage); APP_LOG_DEBUG("GFX thread, temp : %0.2f °C, press : %0.2f hPa, battery(%s) : %u mV <-> %u %%", - temperature, + bmp_temperature, pressure/100, battery_controller_status_2_str(watch_peripherals_get_battery_controller_status()), _battery_stats.battery_voltage, @@ -726,7 +774,7 @@ void gfx_task(void *param) { _interrupts_statuses.battery_controller_status = false; //Let's refresh the battery percentage as well: - _battery_stats.battery_voltage = watch_peripherals_get_battery_voltage(battery_unit_mv); + _battery_stats.battery_voltage = watch_peripherals_get_battery_voltage(BATTERY_UNIT_MV); _battery_stats.battery_percentage = battery_voltage_to_percentage(_battery_stats.battery_voltage); ble_service_set_battery_value(_battery_stats.battery_percentage); watch_face_set_battery_indicator(&watchFace, _battery_stats.battery_percentage, watch_peripherals_get_battery_controller_status()); @@ -762,7 +810,6 @@ void gfx_task(void *param) lv_port_tick_start(); watch_face_force_sync(&watchFace); - watch_peripherals_magnetometer_power_mode_set(QMC5883L_Mode_Control_Continuous); lcd_sleep(&LCDConfig, false); lcd_on(&LCDConfig, false); diff --git a/src/W800_SDK_v1.00.10/app/gfx/notification_screen.c b/src/W800_SDK_v1.00.10/app/gfx/notification_screen.c new file mode 100644 index 0000000..c5f2160 --- /dev/null +++ b/src/W800_SDK_v1.00.10/app/gfx/notification_screen.c @@ -0,0 +1,331 @@ +#include "notification_screen.h" +#include +#include +#include "wm_mem.h" + +/* Internal API to manage notification objects */ +bool _notification_list_is_empty(NotificationDataList_t notificationList); +void _notification_list_add_head(NotificationDataList_t *notificationList, NotificationData_t *notification); +NotificationData_t *_notification_list_remove_tail(NotificationDataList_t *notificationList); +uint8_t _notification_list_count(NotificationDataList_t notificationList); + +void _notification_list_debug(NotificationDataList_t notificationList); +/* Internal UI API */ +void _display_message_notification(NotificationScreen_t * const notificationScreen, const NotificationData_t *notification); +void _notification_popup_destroy(NotificationScreen_t * const notificationScreen); +const char *_notification_type_to_char(NotificationType_e notificationType); +const char *_notification_timestamp_to_date(time_t timestamp); + +static void notification_scrolled_event_cb(lv_event_t *e) +{ + NotificationScreen_t *notificationScreen = lv_event_get_user_data(e); + //If we didn't scroll down enough, make the notif pop again + if(lv_obj_get_scroll_y(e->target) >= 83) + { + lv_obj_scroll_to_y(e->target, 166, LV_ANIM_ON); + } + //Else we know that the user wants to close the notification, so we do it with an animation to reach 0 + else if(lv_obj_get_scroll_y(e->target) != 0) + { + lv_obj_scroll_to_y(e->target, 0, LV_ANIM_ON); + } + else //We scrolled enough to tell that we saw the notification and that we want to close it + { + LV_LOG_USER("Notification closed"); + _notification_popup_destroy(notificationScreen); + if(notificationScreen->notificationOnStateChangeCb) notificationScreen->notificationOnStateChangeCb(NOTIFICATION_STATE_CLEARED); + } +} + +void notification_screen_init(NotificationScreen_t * const notificationScreen) +{ + if(!notificationScreen) + { + LV_LOG_ERROR("NULL pointer given !"); + return; + } + + memset(notificationScreen, 0, sizeof(NotificationScreen_t)); + + //Let's initialize the list of free notifications from the pool + for(uint8_t i = 0; i < MAX_NOTIFICATIONS_COUNT; i++) + _notification_list_add_head(¬ificationScreen->freeNotificationList, notificationScreen->notificationPool + i); +} + +void notification_screen_register_on_state_change_cb(NotificationScreen_t * const notificationScreen, NotificationOnStateChangeCb_t notificationOnStateChangeCb) +{ + if(!notificationScreen) + { + LV_LOG_ERROR("NULL pointer given !"); + return; + } + + notificationScreen->notificationOnStateChangeCb = notificationOnStateChangeCb; +} + +void notification_screen_notify(NotificationScreen_t * const notificationScreen, uint32_t handle, time_t dateOfArrival, NotificationType_e notificationType, char * title, char * body) +{ + if(!notificationScreen) + { + LV_LOG_ERROR("NULL pointer given !"); + return; + } + + NotificationData_t *notification = NULL; + + //We try to get a notification from the free list. + if((notification = _notification_list_remove_tail(¬ificationScreen->freeNotificationList)) == NULL) + { + //If that fails, we reuse the oldest notification from the used list. + if((notification = _notification_list_remove_tail(¬ificationScreen->notificationList)) == NULL) + { + //That's bad + LV_LOG_ERROR("Unable to find a notification to reuse !"); + return; + } + + //Don't forget to free the allocated strings if not already done + tls_mem_free(notification->title); + tls_mem_free(notification->body); + } + + notification->handle = handle; + notification->dateOfArrival = dateOfArrival; + notification->type = notificationType; + notification->title = title; + notification->body = body; + notification->read = false; + + _notification_list_add_head(¬ificationScreen->notificationList, notification); + + switch(notificationType) + { + case NOTIFICATION_TYPE_CALL: + break; + default: + _display_message_notification(notificationScreen, notification); + if(notificationScreen->notificationOnStateChangeCb) notificationScreen->notificationOnStateChangeCb(NOTIFICATION_STATE_DISPLAYED); + } +} + +void notification_screen_destroy(NotificationScreen_t * const notificationScreen) +{ + if(!notificationScreen) + { + LV_LOG_ERROR("NULL pointer given !"); + return; + } +} + +bool _notification_list_is_empty(NotificationDataList_t notificationList) +{ + return notificationList == NULL; +} + +void _notification_list_add_head(NotificationDataList_t *notificationList, NotificationData_t *notification) +{ + if(!notificationList || !notification) return; + + if(_notification_list_is_empty(*notificationList)) + *notificationList = notification; + else + { + notification->next = *notificationList; + *notificationList = notification; + } +} + +NotificationData_t *_notification_list_remove_tail(NotificationDataList_t *notificationList) +{ + if(!notificationList) return NULL; + + if(_notification_list_is_empty(*notificationList)) return NULL; + + NotificationData_t *toReturn = NULL; + + //There is only one item in the list + if((*notificationList)->next == NULL) + { + toReturn = *notificationList; + *notificationList = NULL; + return toReturn; + } + + NotificationDataList_t cursor = *notificationList; + while(!_notification_list_is_empty(cursor->next->next)) + { + cursor = cursor->next; + } + + toReturn = cursor->next; + cursor->next = NULL; + return toReturn; +} + +uint8_t _notification_list_count(NotificationDataList_t notificationList) +{ + uint8_t count = 0; + while(!_notification_list_is_empty(notificationList)) + { + notificationList = notificationList->next; + count++; + } + + return count; +} + +void _display_message_notification(NotificationScreen_t * const notificationScreen, const NotificationData_t *notification) +{ + //Create and display a graphical widget containing the notification + lv_obj_t *notification_display = lv_layer_top(); + + if(!notification_display) + { + LV_LOG_ERROR("Could not retrieve sys layer !"); + return; + } + + /*We can have two cases : + 1) No notification is currently being displayed, so we create the needed UI objects + 2) A notification is currently being shown and all UI elements were already created / allocated + */ + + if(lv_obj_get_child_cnt(notification_display) == 0) + { + //Only allow to scroll down and not up (LV_DIR_TOP makes it, is it a bug ?) + lv_obj_set_scroll_dir(notification_display, LV_DIR_TOP); + lv_obj_add_event_cb(notification_display, &(notification_scrolled_event_cb), LV_EVENT_SCROLL_END, notificationScreen); + + lv_obj_t *main_notification = lv_obj_create(notification_display); + lv_obj_set_size(main_notification, 202, 166); + lv_obj_set_style_pad_all(main_notification, 0, LV_PART_MAIN); + lv_obj_set_style_pad_top(main_notification, 10, LV_PART_MAIN); + lv_obj_set_style_border_width(main_notification, 0, LV_PART_MAIN); + lv_obj_set_style_radius(main_notification, 30, LV_PART_MAIN); + lv_obj_align(main_notification, LV_ALIGN_BOTTOM_MID,0,166); + lv_obj_set_style_bg_color(main_notification, lv_color_make(52, 93, 106), LV_PART_MAIN); + //lv_obj_set_style_opa(main_notification, 240, LV_PART_MAIN); //Opacity is too heavy on ram :-( + + //Create the title, type and date labels + if(notificationScreen->type_label) + { + LV_LOG_ERROR("type_label should be NULL here !"); + lv_obj_del(notificationScreen->type_label); + notificationScreen->type_label = NULL; + } + notificationScreen->type_label = lv_label_create(main_notification); + lv_obj_set_style_text_color(notificationScreen->type_label, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_pad_left(notificationScreen->type_label, 10, LV_PART_MAIN); + lv_label_set_long_mode(notificationScreen->type_label, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_style_anim_speed(notificationScreen->type_label, 10, LV_PART_MAIN); + lv_obj_set_width(notificationScreen->type_label, lv_pct(27)); + lv_label_set_text(notificationScreen->type_label, _notification_type_to_char(notification->type)); + + if(notificationScreen->title_label) + { + LV_LOG_ERROR("title_label should be NULL here !"); + lv_obj_del(notificationScreen->title_label); + notificationScreen->title_label = NULL; + } + notificationScreen->title_label = lv_label_create(main_notification); + lv_obj_set_style_text_color(notificationScreen->title_label, lv_color_white(), LV_PART_MAIN); + lv_obj_align(notificationScreen->title_label, LV_ALIGN_TOP_MID, 0, 0); + lv_obj_set_style_anim_speed(notificationScreen->title_label, 10, LV_PART_MAIN); + lv_label_set_long_mode(notificationScreen->title_label, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_width(notificationScreen->title_label, lv_pct(40)); + lv_label_set_text_static(notificationScreen->title_label, notification->title); + + if(notificationScreen->date_label) + { + LV_LOG_ERROR("date_label should be NULL here !"); + lv_obj_del(notificationScreen->date_label); + notificationScreen->date_label = NULL; + } + notificationScreen->date_label = lv_label_create(main_notification); + lv_obj_set_style_pad_right(notificationScreen->date_label, 10, LV_PART_MAIN); + lv_obj_set_style_text_color(notificationScreen->date_label, lv_color_white(), LV_PART_MAIN); + lv_obj_align(notificationScreen->date_label, LV_ALIGN_TOP_RIGHT, 0, 0); + lv_label_set_text(notificationScreen->date_label, _notification_timestamp_to_date(notification->dateOfArrival)); + + //Create the sub-area in the notification + lv_obj_t *sub_area = lv_obj_create(main_notification); + lv_obj_set_style_pad_all(sub_area, 10, LV_PART_MAIN); + lv_obj_set_style_border_width(sub_area, 0, LV_PART_MAIN); + lv_obj_set_style_radius(sub_area, 0, LV_PART_MAIN); + lv_obj_set_width(sub_area, 202); + lv_obj_set_height(sub_area, 136); + lv_obj_align(sub_area, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_set_style_bg_color(sub_area, lv_color_make(22, 52, 62), LV_PART_MAIN); + //Create the main text + if(notificationScreen->body_label) + { + LV_LOG_ERROR("body_label should be NULL here !"); + lv_obj_del(notificationScreen->body_label); + notificationScreen->body_label = NULL; + } + notificationScreen->body_label = lv_label_create(sub_area); + lv_obj_set_width(notificationScreen->body_label, 182); + lv_obj_set_style_text_color(notificationScreen->body_label, lv_color_white(), LV_PART_MAIN); + lv_obj_set_style_pad_all(notificationScreen->body_label, 0, LV_PART_MAIN); + lv_obj_set_style_pad_bottom(notificationScreen->body_label, 40, LV_PART_MAIN); + lv_label_set_long_mode(notificationScreen->body_label, LV_LABEL_LONG_WRAP); + lv_label_set_text_static(notificationScreen->body_label, notification->body); + + + lv_obj_scroll_to_y(notification_display, 166, LV_ANIM_ON); + } + else + { + LV_LOG_USER("A notification is already being displayed, updating it's content !"); + //We just have to update the notification content + lv_label_set_text_static(notificationScreen->type_label, _notification_type_to_char(notification->type)); + lv_label_set_text_static(notificationScreen->title_label, notification->title); + lv_label_set_text_static(notificationScreen->date_label, _notification_timestamp_to_date(notification->dateOfArrival)); + lv_label_set_text_static(notificationScreen->body_label, notification->body); + } +} + +void _notification_popup_destroy(NotificationScreen_t * const notificationScreen) +{ + lv_obj_clean(lv_layer_top()); + notificationScreen->type_label = NULL; + notificationScreen->title_label = NULL; + notificationScreen->date_label = NULL; + notificationScreen->body_label = NULL; +} + +const char *_notification_type_to_char(NotificationType_e notificationType) +{ + switch(notificationType) + { + case NOTIFICATION_TYPE_SMS: + return "Sms"; + case NOTIFICATION_TYPE_EMAIL: + return "Email"; + case NOTIFICATION_TYPE_WHATSAPP: + return "WhatsApp"; + case NOTIFICATION_TYPE_GADGET_BRIDGE: + return "GadgetBridge"; + case NOTIFICATION_TYPE_UNKNOWN: + default: + return "Unknown"; + } +} + +const char *_notification_timestamp_to_date(time_t timestamp) +{ + static char date[9]; //Ex 7:23PM + + struct tm *time = gmtime(×tamp); + sprintf(date, "%s%d:%s%d", time->tm_hour > 10 ? "" : "0", time->tm_hour, time->tm_min > 10 ? "" : "0", time->tm_min); + return date; +} + +void _notification_list_debug(NotificationDataList_t notificationList) +{ + while(!_notification_list_is_empty(notificationList)) + { + LV_LOG_USER("Notification handle :%u", notificationList->handle); + notificationList = notificationList->next; + } +} diff --git a/src/W800_SDK_v1.00.10/app/gfx/notification_screen.h b/src/W800_SDK_v1.00.10/app/gfx/notification_screen.h new file mode 100644 index 0000000..86fe6d0 --- /dev/null +++ b/src/W800_SDK_v1.00.10/app/gfx/notification_screen.h @@ -0,0 +1,71 @@ +#ifndef NOTIFICATION_SCREEN_H +#define NOTIFICATION_SCREEN_H + +#include "lvgl.h" +#include + +/* The maximum number of notifications which will be stored before the oldest one gets discarded +to make room for the new one. +*/ +#define MAX_NOTIFICATIONS_COUNT (10) + +typedef enum NotificationType +{ + NOTIFICATION_TYPE_SMS = 0, + NOTIFICATION_TYPE_EMAIL, + NOTIFICATION_TYPE_WHATSAPP, + NOTIFICATION_TYPE_GADGET_BRIDGE, + NOTIFICATION_TYPE_UNKNOWN, + /* This enum value has no match in GadgetBridge's enum */ + NOTIFICATION_TYPE_CALL, +} NotificationType_e; + +typedef enum NotificationState +{ + NOTIFICATION_STATE_CLEARED = 0, + NOTIFICATION_STATE_DISPLAYED = 1, +} NotificationState_e; + +typedef struct NotificationData +{ + struct NotificationData *next; + uint32_t handle; + time_t dateOfArrival; + char *title; + char *body; + NotificationType_e type; + bool read; // Has the notification been read (aka closed by the user) + +} NotificationData_t, *NotificationDataList_t; + +typedef void (*NotificationOnStateChangeCb_t)(NotificationState_e notificationState); + +typedef struct NotificationScreen +{ + //Can be erased attributes + lv_obj_t *display; + + //Should not be erased attributes + //Notification UI object + lv_obj_t *type_label; + lv_obj_t *title_label; + lv_obj_t *date_label; + lv_obj_t *body_label; + NotificationOnStateChangeCb_t notificationOnStateChangeCb; + //Notification history data structure + NotificationData_t notificationPool[MAX_NOTIFICATIONS_COUNT]; + NotificationDataList_t notificationList; //Actual notifications + NotificationDataList_t freeNotificationList; //Free notification object pool +} NotificationScreen_t; + +void notification_screen_init(NotificationScreen_t * const notificationScreen); + +void notification_screen_register_on_state_change_cb(NotificationScreen_t * const notificationScreen, NotificationOnStateChangeCb_t notificationOnStateChangeCb); + +void notification_screen_notify(NotificationScreen_t * const notificationScreen, uint32_t handle, time_t dateOfArrival, NotificationType_e notificationType, char * title, char * body); + +void notification_screen_create(NotificationScreen_t * const notificationScreen); + +void notification_screen_destroy(NotificationScreen_t * const notificationScreen); + +#endif // NOTIFICATION_SCREEN_H