Files
Trigalarm/src/c/main.c
T
2026-11-05 20:51:09 -05:00

1184 lines
45 KiB
C

#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;
//// 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();
}