#include "main.h" #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; static TextLayer *s_countdown_txt_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 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_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; // global vars static GColor s_setter_highlight_color; static uint8_t s_highlight_pos = 0; //// alarm static WakeupReason s_alarm_reason; static uint8_t s_solution_indices[3] = {8, 8, 8}; static uint8_t s_selected_indices[3] = {9, 9, 9}; static uint8_t s_awareness_countdown_int = 0; static char s_awareness_countdown_buf[4]; static uint32_t s_vibe_segments[5]; static VibePattern s_vibe_pattern; // constants 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; //// alarm 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; // alarm drop menu options static const char *s_options_function[3]; static char *s_options_over[2]; static char *s_options_under[2]; ////////////////////// STATUS ////////////////////// static void click_handler_disable_tutorial(void *recognizer, void *ctx) { settings.TutorialEnabled = false; persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings)); window_stack_pop_all(true); } static void click_handler_push_setter(void *recognizer, void *ctx) { s_highlight_pos = 0; s_alarm_reason = WAKE_MAIN_ALARM; // going back to setter means alarm was disarmed or failed to set; do not punish user window_stack_pop_all(true); push_setter_window(); } static void click_handler_exit(void *recognizer, void *ctx) { window_stack_pop_all(true); } static void click_handler_retry(void *recognizer, void *ctx) { window_stack_pop(true); } static void status_main_tutorial_click_config_provider(void *ctx) { window_single_click_subscribe(BUTTON_ID_SELECT, click_handler_exit); window_single_click_subscribe(BUTTON_ID_BACK, click_handler_disable_tutorial); } static void status_confirmation_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_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_math_failure_click_config_provider(void *ctx) { window_single_click_subscribe(BUTTON_ID_SELECT, click_handler_retry); } static void status_window_load(StatusWindowPurpose purpose) { Layer *window_layer = window_get_root_layer(s_status_window); s_status_txt_layer = text_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT)); text_layer_set_font(s_status_txt_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24)); switch (purpose) { case STATUS_MAIN_TUTORIAL: // don't disable ticks to ensure vibrations remain till the tutorial is closed text_layer_set_text(s_status_txt_layer, "Correct! An awareness alarm is now being set. Solve it within the time limit to disarm Trigalarm.\n\nPress SELECT to exit or BACK to disable this tutorial in the future (and exit)."); window_set_click_config_provider(s_status_window, status_main_tutorial_click_config_provider); break; case STATUS_CONFIRMATION: { static char s_status_confirmed_buffer[106]; snprintf(s_status_confirmed_buffer, sizeof(s_status_confirmed_buffer), "Alarm disarmed and re-set (%02u:%02u) successfully.\n\nPress SELECT to exit or BACK to change the 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); break; } case STATUS_SET_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); break; case STATUS_MATH_FAILURE: text_layer_set_text(s_status_txt_layer, "Incorrect equation.\nTry again!"); window_set_click_config_provider(s_status_window, status_math_failure_click_config_provider); } text_layer_set_background_color(s_status_txt_layer, GColorClear); 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_set_text_alignment(s_status_txt_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(s_status_txt_layer)); } static void status_window_unload() { text_layer_destroy(s_status_txt_layer); } 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); } ////////////////////// 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; // TODO use ready signal better } apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_PAVLOK_ENABLE, &settings.PavlokEnabled); apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_AWARENESS_TIME_GAP, &settings.AwarenessTimeGapMinutes); apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_AWARENESS_TIME_LIMIT, &settings.AwarenessTimeLimitSeconds); apply_uint8_from_tuple(iter, MESSAGE_KEY_PKJS_AWARENESS_NUDGE_ENABLE, &settings.AwarenessNudgeEnabled); persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(settings)); } static void send_zap() { if (!settings.PavlokEnabled) { 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, 1); result = app_message_outbox_send(); if (result != APP_MSG_OK) { return; } } ////////////////////// MISC ////////////////////// static bool schedule_alarm(WakeupReason schedule_reason) { time_t target; WakeupId status; switch (schedule_reason) { case WAKE_RECOVERY_ALARM: target = time(NULL) + 15; // no need to cancel alarms, as RECOVERY_ALARM is short-lived and is expired by the time another is being scheduled status = wakeup_schedule_simple_robust(target, 15, schedule_reason); break; case WAKE_AWARENESS_ALARM: target = time(NULL) + settings.AwarenessTimeGapMinutes * SECONDS_PER_MINUTE; wakeup_cancel_all(); status = wakeup_schedule_simple_robust(target, SECONDS_PER_MINUTE, schedule_reason); break; case WAKE_MAIN_ALARM: { time_t now = time(NULL); struct tm target_tm = *localtime(&now); target_tm.tm_hour = the_time.HourWIP; target_tm.tm_min = the_time.MinuteWIP; target_tm.tm_sec = 0; target = mktime(&target_tm); if (target <= now) { target += SECONDS_PER_DAY; } wakeup_cancel_all(); status = wakeup_schedule_simple_robust(target, SECONDS_PER_MINUTE, schedule_reason); } } if (status < 0) { push_status_window(STATUS_SET_FAILURE); persist_write_int(STORAGE_KEY_ALARM_REASON, WAKE_MAIN_ALARM); return false; } if (persist_read_int(STORAGE_KEY_ALARM_REASON) != schedule_reason) { persist_write_int(STORAGE_KEY_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); } ////////////////////// ALARM ////////////////////// static void main_alarm_seconds_handler(struct tm *tick_time, TimeUnits units_changed) { vibes_enqueue_custom_pattern(s_vibe_pattern); } static void awareness_alarm_seconds_handler(struct tm *tick_time, TimeUnits units_changed) { if (s_awareness_countdown_int > 0) { s_awareness_countdown_int--; snprintf(s_awareness_countdown_buf, sizeof(s_awareness_countdown_buf), "%03u", s_awareness_countdown_int); text_layer_set_text(s_countdown_txt_layer, s_awareness_countdown_buf); if (s_awareness_countdown_int == 0) { send_zap(); // TODO implement repeat interval } } else { // TODO flash countdown txt layer every second vibes_enqueue_custom_pattern(s_vibe_pattern); } } 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 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(WAKE_RECOVERY_ALARM); // output is not captured, as it is not actionable } } 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)); //// 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: 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: 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: 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); // drop menus //// determine trigonometric function options s_sin = s_sin_arr[sin_index]; s_cos = s_cos_arr[cos_index]; s_tan = s_tan_arr[tan_index]; //// set up menu options s_options_function[0] = s_sin; s_options_function[1] = s_cos; s_options_function[2] = s_tan; //// create 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}); // countdown if (s_alarm_reason == WAKE_AWARENESS_ALARM) { 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); s_awareness_countdown_int = settings.AwarenessTimeLimitSeconds; snprintf(s_awareness_countdown_buf, sizeof(s_awareness_countdown_buf), "%03u", s_awareness_countdown_int); text_layer_set_text(s_countdown_txt_layer, s_awareness_countdown_buf); layer_add_child(window_layer, text_layer_get_layer(s_countdown_txt_layer)); } // setup vibes s_vibe_segments[0] = (uint32_t)(s_triangle_layer->angle_A * 2); s_vibe_segments[1] = 200; s_vibe_segments[2] = (uint32_t)(s_triangle_layer->angle_B * 2); s_vibe_segments[3] = 200; s_vibe_segments[4] = (uint32_t)(s_triangle_layer->angle_C * 2); s_vibe_pattern = (VibePattern){ .durations = s_vibe_segments, .num_segments = ARRAY_LENGTH(s_vibe_segments), }; schedule_alarm(WAKE_RECOVERY_ALARM); // output is not captured, as it is not actionable if (s_alarm_reason == WAKE_AWARENESS_ALARM) { tick_timer_service_subscribe(SECOND_UNIT, awareness_alarm_seconds_handler); } else { tick_timer_service_subscribe(SECOND_UNIT, main_alarm_seconds_handler); } } static void alarm_window_unload() { tick_timer_service_unsubscribe(); if (s_countdown_txt_layer != NULL) { 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 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 alarm_up_click_handler(void *recognizer, void *ctx) { 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) { 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) { window_stack_push(s_drop_window, true); } static void alarm_select_hold_handler(void *recognizer, void *ctx) { // check solution for (int i = 0; i < 3; ++i) { if (s_selected_indices[i] != s_solution_indices[i]) { push_status_window(STATUS_MATH_FAILURE); return; } } // solution is correct, meaning a non-recovery alarm needs scheduled; // set dummy handler to avoid overwriting an important alarm with a // no-longer-important recovery alarm wakeup_service_subscribe(NULL); if (s_awareness_countdown_int > 0) { // set main alarm if the awareness alarm was solved within time limit tick_timer_service_unsubscribe(); // alarm is fully disarmed - prevent stimulus in confirmation window if (schedule_alarm(WAKE_MAIN_ALARM)) { push_status_window(STATUS_CONFIRMATION); } } else { // otherwise, set awareness alarm and exit if (schedule_alarm(WAKE_AWARENESS_ALARM)) { if (settings.TutorialEnabled && s_alarm_reason == WAKE_MAIN_ALARM) { push_status_window(STATUS_MAIN_TUTORIAL); } else { window_stack_pop_all(true); } } } } 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); window_long_click_subscribe(BUTTON_ID_SELECT, 2500, alarm_select_hold_handler, NULL); } static void push_alarm_window() { 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; // build the target time today struct tm target_tm = now_tm; target_tm.tm_hour = the_time.HourSet; target_tm.tm_min = the_time.MinuteSet; target_tm.tm_sec = 0; time_t now = mktime(&now_tm); time_t target = mktime(&target_tm); if (target <= now) { target += SECONDS_PER_DAY; } 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) { if (s_alarm_reason != WAKE_MAIN_ALARM) { send_zap(); vibes_double_pulse(); return; // early return to avoid overwriting recovery/awareness alarm } bool success = schedule_alarm(WAKE_MAIN_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) { s_alarm_reason = persist_read_int(STORAGE_KEY_ALARM_REASON); s_highlight_pos = 0; 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(); // 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(64, 64); // TODO tighten sizes // 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.AwarenessTimeGapMinutes = 9; settings.AwarenessTimeLimitSeconds = 30; settings.AwarenessNudgeEnabled = true; } s_alarm_reason = persist_read_int(STORAGE_KEY_ALARM_REASON); switch (launch_reason()) { case APP_LAUNCH_WAKEUP: switch (s_alarm_reason) { case WAKE_RECOVERY_ALARM: send_zap(); break; case WAKE_AWARENESS_ALARM: if (settings.AwarenessNudgeEnabled) { vibes_short_pulse(); } break; default: break; } push_alarm_window(); break; default: 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(); }