1029 lines
39 KiB
C
1029 lines
39 KiB
C
#include "main.h"
|
|
#include "scheduler.h"
|
|
#include "settings.h"
|
|
#include "triangles.h"
|
|
#include <pebble.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
// 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();
|
|
}
|