#include "scheduler.h" #include "settings.h" #include "triangles.h" #include #include #include // settings static AppSettings settings; // layer-age static Window *s_window; static Window *s_status_window; static Window *s_drop_window; static Layer *s_top_bar_layer; static Layer *s_bottom_bar_layer; //// status static ScrollLayer *s_status_scroll_layer; static TextLayer *s_status_txt_layer; //// setter static BitmapLayer *s_clock_bmp_layer; static BitmapLayer *s_check_bmp_layer; static TextLayer *s_setter_txt_layer; static Layer *s_setter_highlight_layer; //// alarm static BitmapLayer *s_x_bmp_layer; static Animation *s_x_anim_master; static Layer *s_graph_layer; static TextLayer *s_theta_txt_layer; static TextLayer *s_parenthesis_txt_layer; static Layer *s_drop_bg_function_layer; static Layer *s_drop_bg_over_layer; static Layer *s_drop_bg_under_layer; static TextLayer *s_drop_txt_function_layer; static TextLayer *s_drop_txt_over_layer; static TextLayer *s_drop_txt_under_layer; static Layer *s_division_layer; static Layer *s_alarm_highlight_layer; static MenuLayer *s_drop_menu_layer; static RightTriangleLayer *s_triangle_layer; static TextLayer *s_clock_txt_layer; static TextLayer *s_countdown_txt_layer; static TextLayer *s_progress_txt_layer; static TextLayer *s_side_a_txt_layer; static TextLayer *s_side_b_txt_layer; static TextLayer *s_side_c_txt_layer; // fonts GFont s_font_lecoish_small; //// setter GFont s_font_lecoish_big; //// alarm GFont s_font_mansalva_big; GFont s_font_mansalva_small; // bitmaps //// setter static GBitmap *s_clock_bmp; static GBitmap *s_check_bmp; //// alarm static GBitmap *s_x_bmp; // global vars static WakeupReason s_alarm_reason; static GColor s_setter_highlight_color; static uint8_t s_highlight_pos = 0; static bool s_skip_current_day = false; //// alarm static uint8_t s_current_triangle_num; static uint8_t s_solution_indices[3]; static uint8_t s_selected_indices[3] = {9, 9, 9}; static uint8_t s_secs_since_last_input; static uint8_t s_shake_cooldown; static uint8_t s_zap_cooldown; static uint8_t s_countdown_int = 0; static bool s_continuous_zap_on_first_triangle; static char s_countdown_buf[4]; static char s_progress_buf[8]; // constants //// shared (setter+alarm) static const uint8_t s_edge_bar_height = ((PBL_DISPLAY_HEIGHT / 7) * 2); static const GRect s_top_bar_grect = GRect(0, 0, PBL_DISPLAY_WIDTH, s_edge_bar_height); static const GRect s_bottom_bar_grect = GRect(0, PBL_DISPLAY_HEIGHT - s_edge_bar_height, PBL_DISPLAY_WIDTH, s_edge_bar_height); static const GColor s_primary_color = GColorJaegerGreen; //// status #define STATUS_TEXT_INSET 4 //// alarm #define MAX_SECS_WITHOUT_INPUT 10 #define SHAKE_COOLDOWN_SECS 2 #define ZAP_COOLDOWN_SECS 4 static const uint8_t s_drop_height = 22; static const GRect s_drop_function_grect = GRect(PBL_IF_ROUND_ELSE(55, 45), s_bottom_bar_grect.origin.y + ((s_bottom_bar_grect.size.h / 2) - PBL_IF_ROUND_ELSE(s_drop_height - 1, s_drop_height / 2)), 50, s_drop_height); static const GRect s_drop_over_grect = GRect(s_drop_function_grect.origin.x + 78, s_drop_function_grect.origin.y - 13, s_drop_function_grect.size.w + 1, s_drop_function_grect.size.h); static const GRect s_drop_under_grect = GRect(s_drop_over_grect.origin.x, s_drop_over_grect.origin.y + s_drop_over_grect.size.h + 5, s_drop_function_grect.size.w, s_drop_over_grect.size.h); static const char *s_sin_arr[2] = {"sin¯¹", "csc¯¹"}; static const char *s_cos_arr[2] = {"cos¯¹", "sec¯¹"}; static const char *s_tan_arr[2] = {"tan¯¹", "cot¯¹"}; static const char *s_sin; static const char *s_cos; static const char *s_tan; static const uint32_t s_first_triangle_vibe_segments[] = {200, 150, 200, 150, 200}; static const VibePattern s_first_triangle_vibe_pattern = { .durations = s_first_triangle_vibe_segments, .num_segments = ARRAY_LENGTH(s_first_triangle_vibe_segments), }; // alarm drop menu options static const char *s_options_function[3]; static char *s_options_over[2]; static char *s_options_under[2]; ////////////////////// STATUS ////////////////////// typedef enum { STATUS_TUTORIAL = 1, STATUS_CONFIRMATION = 2, STATUS_FAILURE = 3 } StatusWindowPurpose; static void push_setter_window(); static void click_handler_push_setter(void *recognizer, void *ctx) { s_highlight_pos = 0; window_stack_pop_all(true); push_setter_window(); } static void click_handler_disable_tutorial_and_push_setter(void *recognizer, void *ctx) { settings.TutorialEnabled = false; persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings)); click_handler_push_setter(recognizer, ctx); } static void click_handler_push_setter_skip_current_day(void *recognizer, void *ctx) { s_skip_current_day = true; // setting the next alarm should assume the user meant tomorrow click_handler_push_setter(recognizer, ctx); } static void click_handler_exit(void *recognizer, void *ctx) { window_stack_pop_all(true); } static void status_tutorial_click_config_provider(void *ctx) { window_single_click_subscribe(BUTTON_ID_SELECT, click_handler_push_setter); window_single_click_subscribe(BUTTON_ID_BACK, click_handler_disable_tutorial_and_push_setter); } static void status_confirmation_click_config_provider(void *ctx) { window_single_click_subscribe(BUTTON_ID_BACK, click_handler_push_setter_skip_current_day); window_single_click_subscribe(BUTTON_ID_SELECT, click_handler_exit); } static void status_set_failure_click_config_provider(void *ctx) { window_single_click_subscribe(BUTTON_ID_BACK, click_handler_push_setter); window_single_click_subscribe(BUTTON_ID_SELECT, click_handler_push_setter); window_single_click_subscribe(BUTTON_ID_UP, click_handler_push_setter); window_single_click_subscribe(BUTTON_ID_DOWN, click_handler_push_setter); } static void status_window_load(StatusWindowPurpose purpose) { Layer *window_layer = window_get_root_layer(s_status_window); GRect bounds = layer_get_bounds(window_layer); s_status_txt_layer = text_layer_create(GRect(0, 0, bounds.size.w, 750)); text_layer_set_font(s_status_txt_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24)); text_layer_set_background_color(s_status_txt_layer, GColorClear); text_layer_set_text_alignment(s_status_txt_layer, GTextAlignmentCenter); switch (purpose) { case STATUS_TUTORIAL: text_layer_set_text(s_status_txt_layer, "\nWelcome to Trigalarm!\n\nIn Trigalarm, you set one alarm that reoccurs every day and cannot be disabled. You can, however, change the time whenever you please.\n\nWhen your alarm goes off, you are presented with a (configurable) set of triangles. Each triangle has its own time limit it must be solved within in order to progress. Shake the watch to submit each answer.\n\nIf a triangle's time limit expires, your progress will be set back by one. Solve every triangle to disarm the alarm!\n\nSELECT: \"Okay!\"\nBACK: \"Shut up!\"\n\n\n"); s_status_scroll_layer = scroll_layer_create(layer_get_frame(window_layer)); #ifdef PBL_ROUND scroll_layer_set_paging(s_status_scroll_layer, true); #endif scroll_layer_set_callbacks(s_status_scroll_layer, (ScrollLayerCallbacks){.click_config_provider = status_tutorial_click_config_provider}); scroll_layer_set_click_config_onto_window(s_status_scroll_layer, s_status_window); scroll_layer_add_child(s_status_scroll_layer, text_layer_get_layer(s_status_txt_layer)); layer_add_child(window_layer, scroll_layer_get_layer(s_status_scroll_layer)); text_layer_enable_screen_text_flow_and_paging(s_status_txt_layer, STATUS_TEXT_INSET); GSize max_size = text_layer_get_content_size(s_status_txt_layer); text_layer_set_size(s_status_txt_layer, max_size); scroll_layer_set_content_size(s_status_scroll_layer, GSize(bounds.size.w, max_size.h)); return; case STATUS_CONFIRMATION: { static char s_status_confirmed_buffer[88]; snprintf(s_status_confirmed_buffer, sizeof(s_status_confirmed_buffer), "Alarm disarmed and re-set (%02u:%02u) successfully.\n\nSELECT: Exit\nBACK: Change alarm time", the_time.HourSet, the_time.MinuteSet); text_layer_set_text(s_status_txt_layer, s_status_confirmed_buffer); window_set_click_config_provider(s_status_window, status_confirmation_click_config_provider); layer_add_child(window_layer, text_layer_get_layer(s_status_txt_layer)); break; } case STATUS_FAILURE: text_layer_set_text(s_status_txt_layer, "Failed to set an alarm.\nPress any button to return to alarm setter."); window_set_click_config_provider(s_status_window, status_set_failure_click_config_provider); } layer_add_child(window_layer, text_layer_get_layer(s_status_txt_layer)); layer_set_frame(text_layer_get_layer(s_status_txt_layer), GRect(0, (PBL_DISPLAY_HEIGHT / 2) - (text_layer_get_content_size(s_status_txt_layer).h / 2), PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT)); text_layer_enable_screen_text_flow_and_paging(s_status_txt_layer, STATUS_TEXT_INSET); } static void status_window_unload() { text_layer_destroy(s_status_txt_layer); if (s_status_scroll_layer != NULL) { scroll_layer_destroy(s_status_scroll_layer); s_status_scroll_layer = NULL; } window_destroy(s_status_window); } static void push_status_window(StatusWindowPurpose purpose) { s_status_window = window_create(); window_set_window_handlers( s_status_window, (WindowHandlers){.unload = status_window_unload}); status_window_load(purpose); window_stack_push(s_status_window, true); } ////////////////////// MISC ////////////////////// static time_t compute_alarm_time(uint8_t hour, uint8_t minute) { time_t now = time(NULL); struct tm target_tm = *localtime(&now); bool already_passed_today = (hour < target_tm.tm_hour) || (hour == target_tm.tm_hour && minute <= target_tm.tm_min); target_tm.tm_hour = hour; target_tm.tm_min = minute; target_tm.tm_sec = 0; if (s_skip_current_day || already_passed_today) { target_tm.tm_mday += 1; } return mktime(&target_tm); } static bool schedule_alarm(WakeupReason schedule_reason) { time_t target; WakeupId status; switch (schedule_reason) { case WAKE_ALARM: { target = compute_alarm_time(the_time.HourWIP, the_time.MinuteWIP); wakeup_cancel_all(); status = wakeup_schedule_simple_robust(target, schedule_reason); break; } case WAKE_RECOVERY_ALARM: target = time(NULL) + 15; // no need to cancel alarms, as recovery alarms are short-lived and are expired by the time another is being scheduled status = wakeup_schedule_simple_robust(target, schedule_reason); } if (status < 0) { push_status_window(STATUS_FAILURE); persist_write_int(STORAGE_KEY_ALARM_REASON, WAKE_ALARM); s_alarm_reason = WAKE_ALARM; return false; } if (persist_read_int(STORAGE_KEY_ALARM_REASON) != schedule_reason) { persist_write_int(STORAGE_KEY_ALARM_REASON, schedule_reason); s_alarm_reason = schedule_reason; } return true; } static void top_bar_layer_update_proc(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, s_primary_color); graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone); } static void bottom_bar_layer_update_proc(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, (s_highlight_pos == 2 ? s_setter_highlight_color : s_primary_color)); graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone); } static void add_bar_layers_to_window(Layer *window_layer, bool small_top) { if (small_top) { s_top_bar_layer = layer_create(GRect(s_top_bar_grect.origin.x, s_top_bar_grect.origin.y, s_top_bar_grect.size.w, s_top_bar_grect.size.h / 2)); } else { s_top_bar_layer = layer_create(s_top_bar_grect); } layer_set_update_proc(s_top_bar_layer, top_bar_layer_update_proc); layer_add_child(window_layer, s_top_bar_layer); s_bottom_bar_layer = layer_create(s_bottom_bar_grect); layer_set_update_proc(s_bottom_bar_layer, bottom_bar_layer_update_proc); layer_add_child(window_layer, s_bottom_bar_layer); } ////////////////////// PKJS ////////////////////// static void apply_uint8_from_tuple(DictionaryIterator *iter, uint32_t key, void *dest) { Tuple *t = dict_find(iter, key); if (!t) return; *(uint8_t *)dest = t->value->uint8; } static void inbox_received_handler(DictionaryIterator *iter, void *context) { Tuple *ready_tuple = dict_find(iter, MESSAGE_KEY_PKJS_READY); if (ready_tuple) { return; } // handle alarm reset requests if (dict_find(iter, MESSAGE_KEY_PKJS_ALARM_RESET)) { wakeup_service_subscribe(NULL); the_time.HourWIP = 0; the_time.MinuteWIP = 0; s_alarm_reason = WAKE_ALARM; if (!schedule_alarm(WAKE_ALARM)) { return; // leave the failure status window visible } persist_write_int(STORAGE_KEY_ALARM_HOUR, the_time.HourSet); persist_write_int(STORAGE_KEY_ALARM_MINUTE, the_time.MinuteSet); window_stack_pop_all(true); return; } apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ENABLE, &settings.PavlokEnabled); apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ZAP_INTENSITY, &settings.PavlokIntensity); apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_COUNT, &settings.TriangleCount); apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_SECS_MIN, &settings.TriangleSecsMin); apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_TRIANGLE_SECS_STEP, &settings.TriangleSecsStep); persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings)); } static void send_zap() { if (!settings.PavlokEnabled || s_zap_cooldown != 0) { return; } DictionaryIterator *out; AppMessageResult result = app_message_outbox_begin(&out); if (result != APP_MSG_OK) { return; } dict_write_uint8(out, MESSAGE_KEY_PKJS_PAVLOK_ZAP, settings.PavlokIntensity); result = app_message_outbox_send(); if (result != APP_MSG_OK) { return; } s_zap_cooldown = ZAP_COOLDOWN_SECS; } ////////////////////// ALARM ////////////////////// static void set_alarm_highlighter_frame() { switch (s_highlight_pos) { case 0: layer_set_frame(s_alarm_highlight_layer, s_drop_function_grect); break; case 1: layer_set_frame(s_alarm_highlight_layer, s_drop_over_grect); break; case 2: layer_set_frame(s_alarm_highlight_layer, s_drop_under_grect); } } static void clear_selections() { for (int i = 0; i < 3; ++i) { s_selected_indices[i] = 9; } s_highlight_pos = 0; set_alarm_highlighter_frame(); text_layer_set_text(s_drop_txt_function_layer, ""); text_layer_set_text(s_drop_txt_over_layer, ""); text_layer_set_text(s_drop_txt_under_layer, ""); } static void update_for_new_triangle() { clear_selections(); // pick & display 2 side lengths to the user static char side_a_buffer[8], side_b_buffer[8], side_c_buffer[8]; uint8_t ignored_side = rand() % 3; uint8_t sin_index = rand() % 2; uint8_t cos_index = rand() % 2; uint8_t tan_index = rand() % 2; switch (ignored_side) { case 0: side_a_buffer[0] = '\0'; snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b); snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c); s_options_over[0] = side_b_buffer; s_options_over[1] = side_c_buffer; s_options_under[0] = side_b_buffer; s_options_under[1] = side_c_buffer; // store solution s_solution_indices[0] = 2; // tan/cot if (s_triangle_layer->theta_angle_is_left) { if (tan_index == 0) { // tan s_solution_indices[1] = 0; // b (opposite) s_solution_indices[2] = 1; // c (adjacent) } else { // cot s_solution_indices[1] = 1; // c (adjacent) s_solution_indices[2] = 0; // b (opposite) } } else { if (tan_index == 0) { // tan s_solution_indices[1] = 1; // c (opposite) s_solution_indices[2] = 0; // b (adjacent) } else { // cot s_solution_indices[1] = 0; // b (adjacent) s_solution_indices[2] = 1; // c (opposite) } } break; case 1: side_b_buffer[0] = '\0'; snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a); snprintf(side_c_buffer, sizeof(side_c_buffer), "%u", s_triangle_layer->side_c); s_options_over[0] = side_a_buffer; s_options_over[1] = side_c_buffer; s_options_under[0] = side_a_buffer; s_options_under[1] = side_c_buffer; // store solution if (s_triangle_layer->theta_angle_is_left) { s_solution_indices[0] = 1; // cos/sec if (cos_index == 0) { // cos s_solution_indices[1] = 1; // c (adjacent) s_solution_indices[2] = 0; // a (hypotenuse) } else { // sec s_solution_indices[1] = 0; // a (hypotenuse) s_solution_indices[2] = 1; // c (adjacent) } } else { s_solution_indices[0] = 0; // sin/csc if (sin_index == 0) { // sin s_solution_indices[1] = 1; // c (opposite) s_solution_indices[2] = 0; // a (hypotenuse) } else { // csc s_solution_indices[1] = 0; // a (hypotenuse) s_solution_indices[2] = 1; // c (opposite) } } break; case 2: side_c_buffer[0] = '\0'; snprintf(side_a_buffer, sizeof(side_a_buffer), "%u", s_triangle_layer->side_a); snprintf(side_b_buffer, sizeof(side_b_buffer), "%u", s_triangle_layer->side_b); s_options_over[0] = side_a_buffer; s_options_over[1] = side_b_buffer; s_options_under[0] = side_a_buffer; s_options_under[1] = side_b_buffer; // store solution if (s_triangle_layer->theta_angle_is_left) { s_solution_indices[0] = 0; // sin/csc if (sin_index == 0) { // sin s_solution_indices[1] = 1; // b (opposite) s_solution_indices[2] = 0; // a (hypotenuse) } else { // csc s_solution_indices[1] = 0; // a (hypotenuse) s_solution_indices[2] = 1; // b (opposite) } } else { s_solution_indices[0] = 1; if (cos_index == 0) { // cos s_solution_indices[1] = 1; // b (adjacent) s_solution_indices[2] = 0; // a (hypotenuse) } else { // sec s_solution_indices[1] = 0; // a (hypotenuse) s_solution_indices[2] = 1; // b (adjacent) } } } text_layer_set_text(s_side_a_txt_layer, side_a_buffer); text_layer_set_text(s_side_b_txt_layer, side_b_buffer); text_layer_set_text(s_side_c_txt_layer, side_c_buffer); // set trigonometric function options s_sin = s_sin_arr[sin_index]; s_cos = s_cos_arr[cos_index]; s_tan = s_tan_arr[tan_index]; s_options_function[0] = s_sin; s_options_function[1] = s_cos; s_options_function[2] = s_tan; // setup countdown s_countdown_int = settings.TriangleSecsMin + ((settings.TriangleCount - s_current_triangle_num) * settings.TriangleSecsStep); snprintf(s_countdown_buf, sizeof(s_countdown_buf), "%03u", s_countdown_int); text_layer_set_text(s_countdown_txt_layer, s_countdown_buf); // set progress counter snprintf(s_progress_buf, sizeof(s_progress_buf), "%d/%d", s_current_triangle_num, settings.TriangleCount); text_layer_set_text(s_progress_txt_layer, s_progress_buf); } static void increment_secs_since_last_input() { if (s_secs_since_last_input < MAX_SECS_WITHOUT_INPUT) { s_secs_since_last_input++; } } static void decrement_cooldown(uint8_t *cooldown) { if (*cooldown != 0) { (*cooldown)--; } } static void alarm_tick_handler(struct tm *tick_time, TimeUnits units_changed) { // seconds s_countdown_int--; decrement_cooldown(&s_shake_cooldown); if (s_current_triangle_num == 1) { decrement_cooldown(&s_zap_cooldown); increment_secs_since_last_input(); if (s_secs_since_last_input >= MAX_SECS_WITHOUT_INPUT) { // pause stimulus during user input; this fixes shake detection and saves battery vibes_enqueue_custom_pattern(s_first_triangle_vibe_pattern); if (s_continuous_zap_on_first_triangle) { send_zap(); } } } snprintf(s_countdown_buf, sizeof(s_countdown_buf), "%03u", s_countdown_int); text_layer_set_text(s_countdown_txt_layer, s_countdown_buf); if (s_countdown_int == 0) { s_continuous_zap_on_first_triangle = true; // begin zapping once the first countdown expires send_zap(); if (s_current_triangle_num > 1) { s_current_triangle_num--; } s_secs_since_last_input = 10; right_triangle_layer_regen(s_triangle_layer); update_for_new_triangle(); } // minutes if (units_changed == 0 || (units_changed & MINUTE_UNIT)) { static char s_clock_buffer[8]; strftime(s_clock_buffer, sizeof(s_clock_buffer), clock_is_24h_style() ? "%H:%M" : "%I:%M", tick_time); text_layer_set_text(s_clock_txt_layer, s_clock_buffer); } } static void graph_layer_update_proc(Layer *layer, GContext *ctx) { GRect bounds = layer_get_bounds(layer); static const uint8_t s_pixel_gap = PBL_DISPLAY_WIDTH / 20; graphics_context_set_stroke_color(ctx, GColorElectricBlue); for (int i = s_pixel_gap; i < PBL_DISPLAY_WIDTH; i += s_pixel_gap) { graphics_draw_line(ctx, GPoint(0, i), GPoint(bounds.size.w, i)); // horizontal line graphics_draw_line(ctx, GPoint(i, 0), GPoint(i, bounds.size.h)); // vertical line } } static void drop_layer_update_proc(Layer *layer, GContext *ctx) { graphics_context_set_fill_color(ctx, GColorWhite); graphics_fill_rect(ctx, layer_get_bounds(layer), 5, GCornersAll); } static void division_layer_update_proc(Layer *layer, GContext *ctx) { GRect bounds = layer_get_bounds(layer); graphics_context_set_stroke_color(ctx, GColorWhite); graphics_draw_line(ctx, GPoint(bounds.origin.x, bounds.origin.y), GPoint(bounds.origin.x + bounds.size.w, bounds.origin.y)); } static void alarm_highlight_layer_update_proc(Layer *layer, GContext *ctx) { graphics_context_set_stroke_color(ctx, GColorRed); graphics_context_set_stroke_width(ctx, 3); graphics_draw_rect(ctx, layer_get_bounds(layer)); } static void alarm_up_click_handler(void *recognizer, void *ctx) { s_secs_since_last_input = 0; if (s_highlight_pos == 0) { s_highlight_pos = 2; } else { s_highlight_pos--; } set_alarm_highlighter_frame(); } static void alarm_down_click_handler(void *recognizer, void *ctx) { s_secs_since_last_input = 0; if (s_highlight_pos == 2) { s_highlight_pos = 0; } else { s_highlight_pos++; } set_alarm_highlighter_frame(); } static void alarm_select_click_handler(void *recognizer, void *ctx) { s_secs_since_last_input = 0; window_stack_push(s_drop_window, true); } static void accel_tap_handler(AccelAxisType axis, int32_t direction) { if (s_shake_cooldown != 0) { return; } // check solution for (int i = 0; i < 3; ++i) { if (s_selected_indices[i] != s_solution_indices[i]) { animation_schedule(animation_clone(s_x_anim_master)); clear_selections(); return; } } if (s_countdown_int > 0) { if (s_current_triangle_num == settings.TriangleCount) { tick_timer_service_unsubscribe(); // alarm is fully disarmed - prevent stimulus in confirmation window wakeup_service_subscribe(NULL); // ignore wakeup events to avoid overwriting new alarm if (schedule_alarm(WAKE_ALARM)) { push_status_window(STATUS_CONFIRMATION); } } else { s_current_triangle_num++; right_triangle_layer_regen(s_triangle_layer); update_for_new_triangle(); } } s_shake_cooldown = SHAKE_COOLDOWN_SECS; } static void alarm_click_config_provider(void *ctx) { window_single_click_subscribe(BUTTON_ID_BACK, NULL); window_single_click_subscribe(BUTTON_ID_UP, alarm_up_click_handler); window_single_click_subscribe(BUTTON_ID_DOWN, alarm_down_click_handler); window_single_click_subscribe(BUTTON_ID_SELECT, alarm_select_click_handler); } static uint16_t menu_get_num_rows(MenuLayer *menu_layer, uint16_t section_index, void *context) { if (s_highlight_pos == 0) { return 3; } else { return 2; } } static void menu_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { switch (s_highlight_pos) { case 0: menu_cell_basic_draw(ctx, cell_layer, s_options_function[cell_index->row], NULL, NULL); break; case 1: menu_cell_basic_draw(ctx, cell_layer, s_options_over[cell_index->row], NULL, NULL); break; case 2: menu_cell_basic_draw(ctx, cell_layer, s_options_under[cell_index->row], NULL, NULL); } } static void menu_select(MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { switch (s_highlight_pos) { case 0: text_layer_set_text(s_drop_txt_function_layer, s_options_function[cell_index->row]); break; case 1: text_layer_set_text(s_drop_txt_over_layer, s_options_over[cell_index->row]); break; case 2: text_layer_set_text(s_drop_txt_under_layer, s_options_under[cell_index->row]); } s_selected_indices[s_highlight_pos] = cell_index->row; window_stack_remove(s_drop_window, true); } // when the user presses select, call this to load a menu; // this function will determine the type of menu to load by checking the highlight position; // once the user presses select in this menu, the result is written to a text layer and the menu is exited static void drop_menu_window_load(Window *window) { Layer *window_layer = window_get_root_layer(window); s_drop_menu_layer = menu_layer_create(layer_get_bounds(window_layer)); menu_layer_set_callbacks(s_drop_menu_layer, NULL, (MenuLayerCallbacks){ .get_num_rows = menu_get_num_rows, .draw_row = menu_draw_row, .select_click = menu_select, }); menu_layer_set_click_config_onto_window(s_drop_menu_layer, window); layer_add_child(window_layer, menu_layer_get_layer(s_drop_menu_layer)); } static void drop_menu_window_unload(Window *window) { menu_layer_destroy(s_drop_menu_layer); s_drop_menu_layer = NULL; } static void live_wakeup_handler_for_alarm(WakeupId id, int32_t wakeup_reason) { if (wakeup_reason == WAKE_RECOVERY_ALARM) { // if recovery alarm, re-schedule it schedule_alarm(wakeup_reason); } } static void anim_handler_flash_x_start(Animation *animation, void *context) { layer_set_hidden(bitmap_layer_get_layer(s_x_bmp_layer), false); } static void anim_handler_flash_x_end(Animation *animation, bool finished, void *context) { layer_set_hidden(bitmap_layer_get_layer(s_x_bmp_layer), true); animation_destroy(animation); } static void anim_update_flash_x(Animation *animation, const AnimationProgress progress) {} // dummy static const AnimationImplementation s_x_anim_impl = {.update = anim_update_flash_x}; static void alarm_window_load() { wakeup_service_subscribe(live_wakeup_handler_for_alarm); Layer *window_layer = window_get_root_layer(s_window); // change highlight color to avoid changing color of bottom bar s_setter_highlight_color = s_primary_color; // graph paper bg s_graph_layer = layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT)); layer_set_update_proc(s_graph_layer, graph_layer_update_proc); layer_add_child(window_layer, s_graph_layer); // top&bottom bars add_bar_layers_to_window(window_layer, true); // theta text s_font_mansalva_small = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_MANSALVA_REGULAR_22)); s_theta_txt_layer = text_layer_create(GRect(PBL_IF_ROUND_ELSE(0, 8), s_bottom_bar_grect.origin.y + PBL_IF_ROUND_ELSE(50, 16), PBL_DISPLAY_WIDTH, 26)); text_layer_set_text_alignment(s_theta_txt_layer, PBL_IF_ROUND_ELSE(GTextAlignmentCenter, GTextAlignmentLeft)); text_layer_set_background_color(s_theta_txt_layer, GColorClear); text_layer_set_text_color(s_theta_txt_layer, GColorWhite); text_layer_set_font(s_theta_txt_layer, s_font_mansalva_small); text_layer_set_text(s_theta_txt_layer, PBL_IF_ROUND_ELSE("= θ", "θ =")); layer_add_child(window_layer, text_layer_get_layer(s_theta_txt_layer)); // drop backgrounds //// function s_drop_bg_function_layer = layer_create(s_drop_function_grect); layer_set_update_proc(s_drop_bg_function_layer, drop_layer_update_proc); layer_add_child(window_layer, s_drop_bg_function_layer); //// over s_drop_bg_over_layer = layer_create(s_drop_over_grect); layer_set_update_proc(s_drop_bg_over_layer, drop_layer_update_proc); layer_add_child(window_layer, s_drop_bg_over_layer); //// under s_drop_bg_under_layer = layer_create(s_drop_under_grect); layer_set_update_proc(s_drop_bg_under_layer, drop_layer_update_proc); layer_add_child(window_layer, s_drop_bg_under_layer); // drop text (blank by default) //// function s_drop_txt_function_layer = text_layer_create(GRect(s_drop_function_grect.origin.x, s_drop_function_grect.origin.y - 3, s_drop_function_grect.size.w, s_drop_function_grect.size.h)); text_layer_set_background_color(s_drop_txt_function_layer, GColorClear); text_layer_set_font(s_drop_txt_function_layer, s_font_mansalva_small); text_layer_set_text_alignment(s_drop_txt_function_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(s_drop_txt_function_layer)); //// over s_drop_txt_over_layer = text_layer_create(GRect(s_drop_over_grect.origin.x, s_drop_over_grect.origin.y - 3, s_drop_over_grect.size.w, s_drop_over_grect.size.h)); text_layer_set_background_color(s_drop_txt_over_layer, GColorClear); text_layer_set_font(s_drop_txt_over_layer, s_font_mansalva_small); text_layer_set_text_alignment(s_drop_txt_over_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(s_drop_txt_over_layer)); //// under s_drop_txt_under_layer = text_layer_create(GRect(s_drop_under_grect.origin.x, s_drop_under_grect.origin.y - 3, s_drop_under_grect.size.w, s_drop_under_grect.size.h)); text_layer_set_background_color(s_drop_txt_under_layer, GColorClear); text_layer_set_font(s_drop_txt_under_layer, s_font_mansalva_small); text_layer_set_text_alignment(s_drop_txt_under_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(s_drop_txt_under_layer)); // parenthesis text s_font_mansalva_big = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_MANSALVA_REGULAR_50)); s_parenthesis_txt_layer = text_layer_create(GRect(s_drop_function_grect.origin.x + s_drop_function_grect.size.w + 2, s_bottom_bar_grect.origin.y - PBL_IF_ROUND_ELSE(12, 5), 200, 60)); text_layer_set_background_color(s_parenthesis_txt_layer, GColorClear); text_layer_set_text_color(s_parenthesis_txt_layer, GColorWhite); text_layer_set_font(s_parenthesis_txt_layer, s_font_mansalva_big); text_layer_set_text(s_parenthesis_txt_layer, "( )"); layer_add_child(window_layer, text_layer_get_layer(s_parenthesis_txt_layer)); // division layer s_division_layer = layer_create(GRect(s_drop_function_grect.origin.x + s_drop_function_grect.size.w + 25, s_drop_function_grect.origin.y + s_drop_function_grect.size.h / 2, 57, 1)); layer_set_update_proc(s_division_layer, division_layer_update_proc); layer_add_child(window_layer, s_division_layer); // highlighter s_alarm_highlight_layer = layer_create(s_drop_function_grect); layer_set_update_proc(s_alarm_highlight_layer, alarm_highlight_layer_update_proc); layer_add_child(window_layer, s_alarm_highlight_layer); // triangle static const uint8_t s_small_top_bar_height = s_top_bar_grect.size.h / 2; s_triangle_layer = right_triangle_layer_create(GRect(0, s_small_top_bar_height, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT - s_small_top_bar_height - s_bottom_bar_grect.size.h), GColorBlack); layer_add_child(window_layer, s_triangle_layer->layer); // triangle measurements text layers uint16_t bc_y = (PBL_DISPLAY_HEIGHT / 2) - s_small_top_bar_height; //// side_a s_side_a_txt_layer = text_layer_create(GRect(0, s_bottom_bar_grect.origin.y - 26, PBL_DISPLAY_WIDTH, 26)); text_layer_set_background_color(s_side_a_txt_layer, GColorClear); text_layer_set_font(s_side_a_txt_layer, s_font_mansalva_small); text_layer_set_text_alignment(s_side_a_txt_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(s_side_a_txt_layer)); //// side_b s_side_b_txt_layer = text_layer_create(GRect(-4, bc_y, PBL_DISPLAY_WIDTH, 26)); text_layer_set_background_color(s_side_b_txt_layer, GColorClear); text_layer_set_font(s_side_b_txt_layer, s_font_mansalva_small); text_layer_set_text_alignment(s_side_b_txt_layer, GTextAlignmentRight); layer_add_child(window_layer, text_layer_get_layer(s_side_b_txt_layer)); //// side_c s_side_c_txt_layer = text_layer_create(GRect(4, bc_y, PBL_DISPLAY_WIDTH, 26)); text_layer_set_background_color(s_side_c_txt_layer, GColorClear); text_layer_set_font(s_side_c_txt_layer, s_font_mansalva_small); layer_add_child(window_layer, text_layer_get_layer(s_side_c_txt_layer)); // drop window s_drop_window = window_create(); window_set_window_handlers( s_drop_window, (WindowHandlers){.load = drop_menu_window_load, .unload = drop_menu_window_unload}); // clock static const uint16_t s_top_bar_edge_margin = (PBL_DISPLAY_WIDTH - 44) / 2; // 44 is the width of the countdown ((3*16)-4) s_clock_txt_layer = text_layer_create(GRect(0, 4, s_top_bar_edge_margin, s_small_top_bar_height)); text_layer_set_background_color(s_clock_txt_layer, GColorClear); text_layer_set_text_color(s_clock_txt_layer, GColorWhite); text_layer_set_text_alignment(s_clock_txt_layer, GTextAlignmentCenter); text_layer_set_font(s_clock_txt_layer, fonts_get_system_font(FONT_KEY_LECO_20_BOLD_NUMBERS)); layer_add_child(window_layer, text_layer_get_layer(s_clock_txt_layer)); // countdown s_countdown_txt_layer = text_layer_create(GRect(0, (s_small_top_bar_height / 2) - PBL_IF_ROUND_ELSE(6, 10), PBL_DISPLAY_WIDTH, s_small_top_bar_height)); text_layer_set_background_color(s_countdown_txt_layer, GColorClear); text_layer_set_text_color(s_countdown_txt_layer, GColorWhite); text_layer_set_text_alignment(s_countdown_txt_layer, GTextAlignmentCenter); text_layer_set_font(s_countdown_txt_layer, s_font_lecoish_small); layer_add_child(window_layer, text_layer_get_layer(s_countdown_txt_layer)); // progress s_progress_txt_layer = text_layer_create(GRect(PBL_DISPLAY_WIDTH - s_top_bar_edge_margin, 4, s_top_bar_edge_margin, s_small_top_bar_height)); text_layer_set_background_color(s_progress_txt_layer, GColorClear); text_layer_set_text_color(s_progress_txt_layer, GColorWhite); text_layer_set_text_alignment(s_progress_txt_layer, GTextAlignmentCenter); text_layer_set_font(s_progress_txt_layer, fonts_get_system_font(FONT_KEY_LECO_20_BOLD_NUMBERS)); layer_add_child(window_layer, text_layer_get_layer(s_progress_txt_layer)); // x (incorrect equation icon) s_x_bmp = gbitmap_create_with_resource(RESOURCE_ID_X); s_x_bmp_layer = bitmap_layer_create(GRect(20, 24, 160, 179)); bitmap_layer_set_bitmap(s_x_bmp_layer, s_x_bmp); bitmap_layer_set_compositing_mode(s_x_bmp_layer, GCompOpSet); layer_set_hidden(bitmap_layer_get_layer(s_x_bmp_layer), true); // hide until needed layer_add_child(window_layer, bitmap_layer_get_layer(s_x_bmp_layer)); // x flash animation init s_x_anim_master = animation_create(); animation_set_implementation(s_x_anim_master, &s_x_anim_impl); animation_set_duration(s_x_anim_master, 1500); animation_set_handlers(s_x_anim_master, (AnimationHandlers){.started = anim_handler_flash_x_start, .stopped = anim_handler_flash_x_end}, NULL); // configure UI around current triangle in s_triangle_layer s_current_triangle_num = 1; s_secs_since_last_input = MAX_SECS_WITHOUT_INPUT; s_continuous_zap_on_first_triangle = false; s_zap_cooldown = 0; update_for_new_triangle(); // schedule recovery alarm and begin countdown :) schedule_alarm(WAKE_RECOVERY_ALARM); accel_tap_service_subscribe(accel_tap_handler); tick_timer_service_subscribe(SECOND_UNIT | MINUTE_UNIT, alarm_tick_handler); } static void alarm_window_unload() { tick_timer_service_unsubscribe(); accel_tap_service_unsubscribe(); animation_destroy(s_x_anim_master); gbitmap_destroy(s_x_bmp); bitmap_layer_destroy(s_x_bmp_layer); text_layer_destroy(s_progress_txt_layer); text_layer_destroy(s_countdown_txt_layer); window_destroy(s_drop_window); text_layer_destroy(s_side_a_txt_layer); text_layer_destroy(s_side_b_txt_layer); text_layer_destroy(s_side_c_txt_layer); right_triangle_layer_destroy(s_triangle_layer); layer_destroy(s_alarm_highlight_layer); layer_destroy(s_division_layer); text_layer_destroy(s_parenthesis_txt_layer); text_layer_destroy(s_drop_txt_under_layer); text_layer_destroy(s_drop_txt_over_layer); text_layer_destroy(s_drop_txt_function_layer); layer_destroy(s_drop_bg_under_layer); layer_destroy(s_drop_bg_over_layer); layer_destroy(s_drop_bg_function_layer); text_layer_destroy(s_theta_txt_layer); layer_destroy(s_bottom_bar_layer); layer_destroy(s_top_bar_layer); layer_destroy(s_graph_layer); fonts_unload_custom_font(s_font_mansalva_big); fonts_unload_custom_font(s_font_mansalva_small); } static void push_alarm_window() { if (s_alarm_reason == WAKE_RECOVERY_ALARM) { send_zap(); } window_set_window_handlers( s_window, (WindowHandlers){.load = alarm_window_load, .unload = alarm_window_unload}); window_set_click_config_provider(s_window, alarm_click_config_provider); window_stack_push(s_window, false); } ////////////////////// SETTER ////////////////////// static void setter_seconds_handler(struct tm *tick_time, TimeUnits units_changed) { struct tm now_tm = *tick_time; time_t now = mktime(&now_tm); time_t target = compute_alarm_time(the_time.HourSet, the_time.MinuteSet); int32_t diff = (int32_t)(target - now); uint8_t hours = diff / 3600; uint8_t minutes = (diff % 3600) / 60; uint8_t seconds = diff % 60; static char s_countdown_buffer[12]; snprintf(s_countdown_buffer, sizeof(s_countdown_buffer), "%02u:%02u:%02u", hours, minutes, seconds); text_layer_set_text(s_countdown_txt_layer, s_countdown_buffer); } static void update_countdown_display() { time_t now = time(NULL); setter_seconds_handler(localtime(&now), SECOND_UNIT); } static void update_setter_display() { static char s_setter_buffer[PBL_IF_ROUND_ELSE(10, 14)]; snprintf(s_setter_buffer, sizeof(s_setter_buffer), PBL_IF_ROUND_ELSE("%02u : %02u", "%02u : %02u"), the_time.HourWIP, the_time.MinuteWIP); text_layer_set_text(s_setter_txt_layer, s_setter_buffer); } static void setter_highlight_layer_update_proc(Layer *layer, GContext *ctx) { GRect fill_area = layer_get_bounds(layer); fill_area.size.w = PBL_DISPLAY_WIDTH / 2; switch (s_highlight_pos) { case 2: return; // do not draw highlight case 1: // draw highlight on minutes site fill_area.origin.x = fill_area.size.w; break; } graphics_context_set_fill_color(ctx, s_setter_highlight_color); graphics_fill_rect(ctx, fill_area, 0, GCornerNone); } static void setter_up_click_handler(void *recognizer, void *ctx) { switch (s_highlight_pos) { case 0: if (the_time.HourWIP == 23) { the_time.HourWIP = 0; } else { the_time.HourWIP++; } break; case 1: if (the_time.MinuteWIP == 59) { the_time.MinuteWIP = 0; } else { the_time.MinuteWIP++; } break; default: return; } update_setter_display(); } static void setter_down_click_handler(void *recognizer, void *ctx) { switch (s_highlight_pos) { case 0: if (the_time.HourWIP == 0) { the_time.HourWIP = 23; } else { the_time.HourWIP--; } break; case 1: if (the_time.MinuteWIP == 0) { the_time.MinuteWIP = 59; } else { the_time.MinuteWIP--; } break; default: return; } update_setter_display(); } static void setter_select_click_handler(void *recognizer, void *ctx) { if (s_highlight_pos == 2) { bool success = schedule_alarm(WAKE_ALARM); if (!success) { return; // early return to avoid indicating alarm was set when it was not } // persist persist_write_int(STORAGE_KEY_ALARM_HOUR, the_time.HourSet); persist_write_int(STORAGE_KEY_ALARM_MINUTE, the_time.MinuteSet); // update countdown immediately update_countdown_display(); // move selection back to start s_highlight_pos = 0; } else { s_highlight_pos++; } layer_mark_dirty(s_setter_highlight_layer); layer_mark_dirty(s_bottom_bar_layer); } static void setter_back_click_handler(void *recognizer, void *ctx) { if (s_highlight_pos == 0) { window_stack_pop_all(true); } else { s_highlight_pos--; } layer_mark_dirty(s_setter_highlight_layer); layer_mark_dirty(s_bottom_bar_layer); } static void setter_click_config_provider(void *ctx) { window_single_repeating_click_subscribe(BUTTON_ID_UP, 60, setter_up_click_handler); window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 60, setter_down_click_handler); window_single_click_subscribe(BUTTON_ID_SELECT, setter_select_click_handler); window_single_click_subscribe(BUTTON_ID_BACK, setter_back_click_handler); } static void live_wakeup_handler_for_setter(WakeupId id, int32_t reason) { // reset values set in setter s_highlight_pos = 0; // reset values potentially set by prior alarm s_skip_current_day = false; s_selected_indices[0] = 9; s_selected_indices[1] = 9; s_selected_indices[2] = 9; window_stack_pop_all(false); push_alarm_window(); } static void setter_window_load() { wakeup_service_subscribe(live_wakeup_handler_for_setter); s_setter_highlight_color = GColorMediumAquamarine; Layer *window_layer = window_get_root_layer(s_window); // top&bottom bars add_bar_layers_to_window(window_layer, false); // clock icon s_clock_bmp = gbitmap_create_with_resource(RESOURCE_ID_CLOCK35); s_clock_bmp_layer = bitmap_layer_create(GRect(PBL_IF_ROUND_ELSE(0, 15), PBL_IF_ROUND_ELSE(6, 0), s_top_bar_grect.size.w, s_top_bar_grect.size.h)); bitmap_layer_set_bitmap(s_clock_bmp_layer, s_clock_bmp); bitmap_layer_set_alignment(s_clock_bmp_layer, PBL_IF_ROUND_ELSE(GAlignTop, GAlignLeft)); bitmap_layer_set_compositing_mode(s_clock_bmp_layer, GCompOpSet); layer_add_child(window_layer, bitmap_layer_get_layer(s_clock_bmp_layer)); // check icon s_check_bmp = gbitmap_create_with_resource(RESOURCE_ID_CHECK); s_check_bmp_layer = bitmap_layer_create(s_bottom_bar_grect); bitmap_layer_set_bitmap(s_check_bmp_layer, s_check_bmp); bitmap_layer_set_compositing_mode(s_check_bmp_layer, GCompOpSet); layer_add_child(window_layer, bitmap_layer_get_layer(s_check_bmp_layer)); // countdown s_countdown_txt_layer = text_layer_create(GRect(PBL_IF_ROUND_ELSE(0, 43), (s_top_bar_grect.size.h / 2) + PBL_IF_ROUND_ELSE(10, -10), PBL_IF_ROUND_ELSE(s_top_bar_grect.size.w, s_top_bar_grect.size.w - 39), 32)); text_layer_set_text_alignment(s_countdown_txt_layer, GTextAlignmentCenter); text_layer_set_text_color(s_countdown_txt_layer, GColorWhite); text_layer_set_background_color(s_countdown_txt_layer, GColorClear); text_layer_set_font(s_countdown_txt_layer, s_font_lecoish_small); layer_add_child(window_layer, text_layer_get_layer(s_countdown_txt_layer)); update_countdown_display(); // progress // highlighter s_setter_highlight_layer = layer_create(GRect(0, s_top_bar_grect.size.h, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT - (2 * s_top_bar_grect.size.h))); layer_set_update_proc(s_setter_highlight_layer, setter_highlight_layer_update_proc); layer_add_child(window_layer, s_setter_highlight_layer); // setter s_font_lecoish_big = fonts_load_custom_font(resource_get_handle(PBL_IF_ROUND_ELSE(RESOURCE_ID_FONT_LECOISH_MONO_BOLD_60, RESOURCE_ID_FONT_LECOISH_MONO_BOLD_40))); s_setter_txt_layer = text_layer_create(GRect(0, (PBL_DISPLAY_HEIGHT / 2) - PBL_IF_ROUND_ELSE(30, 20), PBL_DISPLAY_WIDTH, PBL_IF_ROUND_ELSE(60, 40))); text_layer_set_text_alignment(s_setter_txt_layer, GTextAlignmentCenter); text_layer_set_background_color(s_setter_txt_layer, GColorClear); text_layer_set_font(s_setter_txt_layer, s_font_lecoish_big); layer_add_child(window_layer, text_layer_get_layer(s_setter_txt_layer)); update_setter_display(); tick_timer_service_subscribe(SECOND_UNIT, setter_seconds_handler); } static void setter_window_unload() { tick_timer_service_unsubscribe(); text_layer_destroy(s_setter_txt_layer); layer_destroy(s_setter_highlight_layer); text_layer_destroy(s_countdown_txt_layer); bitmap_layer_destroy(s_check_bmp_layer); bitmap_layer_destroy(s_clock_bmp_layer); gbitmap_destroy(s_check_bmp); gbitmap_destroy(s_clock_bmp); layer_destroy(s_bottom_bar_layer); layer_destroy(s_top_bar_layer); fonts_unload_custom_font(s_font_lecoish_big); } static void push_setter_window() { window_set_window_handlers( s_window, (WindowHandlers){.load = setter_window_load, .unload = setter_window_unload}); window_set_click_config_provider(s_window, setter_click_config_provider); window_stack_push(s_window, false); } static void init() { s_window = window_create(); s_font_lecoish_small = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_LECOISH_MONO_BOLD_20)); the_time.HourWIP = persist_read_int(STORAGE_KEY_ALARM_HOUR); the_time.MinuteWIP = persist_read_int(STORAGE_KEY_ALARM_MINUTE); the_time.HourSet = the_time.HourWIP; the_time.MinuteSet = the_time.MinuteWIP; // initialize pkjs app_message_register_inbox_received(inbox_received_handler); app_message_open(128, 64); // load settings from persist (or set defaults if not present) if (persist_exists(STORAGE_KEY_SETTINGS)) { persist_read_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings)); } else { settings.TutorialEnabled = true; settings.PavlokEnabled = false; settings.PavlokIntensity = 50; settings.TriangleCount = 6; settings.TriangleSecsMin = 18; settings.TriangleSecsStep = 2; } // read even when opening setter to ensure a recovery alarm is not able to be overwritten s_alarm_reason = persist_read_int(STORAGE_KEY_ALARM_REASON); switch (launch_reason()) { case APP_LAUNCH_WAKEUP: push_alarm_window(); break; default: if (s_alarm_reason == WAKE_RECOVERY_ALARM) { push_alarm_window(); } else { if (settings.TutorialEnabled) { push_status_window(STATUS_TUTORIAL); } else { push_setter_window(); } } }; } static void deinit() { fonts_unload_custom_font(s_font_lecoish_small); window_destroy(s_window); } int main(void) { init(); app_event_loop(); deinit(); }