diff --git a/appinfo.json b/appinfo.json index 2705ddc..97645ae 100644 --- a/appinfo.json +++ b/appinfo.json @@ -7,6 +7,7 @@ "KEY_MEDICATIONS": 100, "KEY_MODE": 1, "KEY_SORT": 3, + "KEY_TIMELINE": 8, "KEY_TIMESTAMP": 7, "KEY_VERSION": 4 }, @@ -14,10 +15,17 @@ "configurable" ], "companyName": "cmorison@gmail.com", + "enableMultiJS": false, "longName": "Medication Timer", "projectType": "native", "resources": { "media": [ + { + "file": "images/icons-inv.png", + "name": "IMAGE_ICON_MATRIX_INV", + "targetPlatforms": null, + "type": "png" + }, { "file": "images/menu_icon.png", "menuIcon": true, @@ -31,11 +39,15 @@ } ] }, - "sdkVersion": "2", + "sdkVersion": "3", "shortName": "Meds Timer", + "targetPlatforms": [ + "aplite", + "basalt", + "chalk" + ], "uuid": "95d07e1a-2451-4ffa-aa44-836e523a7648", - "versionCode": 1, - "versionLabel": "1.3", + "versionLabel": "1.6", "watchapp": { "watchface": false } diff --git a/resources/images/icons-inv.png b/resources/images/icons-inv.png new file mode 100644 index 0000000..b454339 Binary files /dev/null and b/resources/images/icons-inv.png differ diff --git a/src/job_adjust.c b/src/job_adjust.c index 808b96a..168bbcf 100644 --- a/src/job_adjust.c +++ b/src/job_adjust.c @@ -10,11 +10,9 @@ static const char* help_fixed[2]={"Timer runs from when drug WAS actually taken" static char num_buffer[NUM_BUFFER_LENGTH]; static Window *s_window; -static GFont s_res_gothic_24_bold; -static GFont s_res_gothic_18; -static GFont s_res_gothic_14; static ActionBarLayer *s_actionbarlayer; static TextLayer *s_textlayer_name; +static TextLayer *s_textlayer_repeat; static TextLayer *s_textlayer_hrs; static TextLayer *s_textlayer_note; static TextLayer *layers[N_LAYERS]; @@ -51,7 +49,7 @@ static void action_bar_select_click_handler() { } else { active_layer++; set_layer_text(); - action_bar_layer_set_icon(s_actionbarlayer, BUTTON_ID_SELECT, bitmap_tick); + action_bar_layer_set_icon(s_actionbarlayer, BUTTON_ID_SELECT, bitmaps[BITMAP_TICK][PBL_IF_SDK_3_ELSE(1,0)]); } } @@ -67,51 +65,63 @@ static void initialise_ui(void) { window_set_fullscreen(s_window, false); #endif - s_res_gothic_24_bold = fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD); - s_res_gothic_18 = fonts_get_system_font(FONT_KEY_GOTHIC_18); - s_res_gothic_14 = fonts_get_system_font(FONT_KEY_GOTHIC_14); - // s_actionbarlayer - s_actionbarlayer = action_bar_layer_create(); - action_bar_layer_add_to_window(s_actionbarlayer, s_window); - action_bar_layer_set_background_color(s_actionbarlayer, GColorBlack); - action_bar_layer_set_icon(s_actionbarlayer, BUTTON_ID_UP, bitmap_add); - action_bar_layer_set_icon(s_actionbarlayer, BUTTON_ID_DOWN, bitmap_minus); - action_bar_layer_set_click_config_provider(s_actionbarlayer, action_bar_click_config_provider); - layer_add_child(window_get_root_layer(s_window), (Layer *)s_actionbarlayer); - // s_textlayer_name s_textlayer_name = text_layer_create(GRect(5, 7, 100, 30)); - text_layer_set_font(s_textlayer_name, s_res_gothic_24_bold); + text_layer_set_font(s_textlayer_name, FONT_GOTHIC_24_BOLD); layer_add_child(window_get_root_layer(s_window), (Layer *)s_textlayer_name); - layers[0] = text_layer_create(GRect(20, 60, 20, 24)); + // s_textlayer_repeat + s_textlayer_repeat = text_layer_create(GRect(5+PBL_IF_ROUND_ELSE(20,0), 36+PBL_IF_ROUND_ELSE(16,0), 100, 30)); + text_layer_set_font(s_textlayer_repeat, FONT_GOTHIC_18); + text_layer_set_text(s_textlayer_repeat, "Repeat Every"); + layer_add_child(window_get_root_layer(s_window), (Layer *)s_textlayer_repeat); + + // hours number + layers[0] = text_layer_create(GRect(20-14+PBL_IF_ROUND_ELSE(20,0), 60+PBL_IF_ROUND_ELSE(16,0), 20, 24)); text_layer_set_text_alignment(layers[0], GTextAlignmentCenter); - text_layer_set_font(layers[0], s_res_gothic_18); + text_layer_set_font(layers[0], FONT_GOTHIC_18); layer_add_child(window_get_root_layer(s_window), (Layer *)layers[0]); // s_textlayer_hrs - s_textlayer_hrs = text_layer_create(GRect(20+20+2, 60, 30, 24)); - text_layer_set_font(s_textlayer_hrs, s_res_gothic_18); + s_textlayer_hrs = text_layer_create(GRect(20+20+2-14+PBL_IF_ROUND_ELSE(20,0), 60+PBL_IF_ROUND_ELSE(16,0), 30, 24)); + text_layer_set_font(s_textlayer_hrs, FONT_GOTHIC_18); text_layer_set_text(s_textlayer_hrs, "hrs"); text_layer_set_text_alignment(s_textlayer_hrs, GTextAlignmentLeft); layer_add_child(window_get_root_layer(s_window), (Layer *)s_textlayer_hrs); // s_textlayer_fixed - layers[1] = text_layer_create(GRect(66, 60, 50, 24)); + layers[1] = text_layer_create(GRect(66-14+PBL_IF_ROUND_ELSE(20,0), 60+PBL_IF_ROUND_ELSE(16,0), 50, 24)); text_layer_set_text_alignment(layers[1], GTextAlignmentCenter); - text_layer_set_font(layers[1], s_res_gothic_18); + text_layer_set_font(layers[1], FONT_GOTHIC_18); layer_add_child(window_get_root_layer(s_window), (Layer *)layers[1]); // s_textlayer_note - s_textlayer_note = text_layer_create(GRect(15, 94, 100, 45)); - text_layer_set_font(s_textlayer_note, s_res_gothic_14); + s_textlayer_note = text_layer_create(GRect(15-2+PBL_IF_ROUND_ELSE(20,0), 94+PBL_IF_ROUND_ELSE(16,0), 100, 45)); + text_layer_set_font(s_textlayer_note, FONT_GOTHIC_14); layer_add_child(window_get_root_layer(s_window), (Layer *)s_textlayer_note); + + // s_actionbarlayer + s_actionbarlayer = action_bar_layer_create(); + action_bar_layer_add_to_window(s_actionbarlayer, s_window); + action_bar_layer_set_background_color(s_actionbarlayer, GColorBlack); + action_bar_layer_set_icon(s_actionbarlayer, BUTTON_ID_UP, bitmaps[BITMAP_ADD][PBL_IF_SDK_3_ELSE(1,0)]); + action_bar_layer_set_icon(s_actionbarlayer, BUTTON_ID_DOWN, bitmaps[BITMAP_MINUS][PBL_IF_SDK_3_ELSE(1,0)]); + action_bar_layer_set_click_config_provider(s_actionbarlayer, action_bar_click_config_provider); + layer_add_child(window_get_root_layer(s_window), (Layer *)s_actionbarlayer); + + #ifdef PBL_ROUND + GRect bounds = layer_get_bounds(window_get_root_layer(s_window)); + layer_set_frame((Layer *)s_textlayer_name, GRect(0,20,bounds.size.w,30)); + text_layer_set_text_alignment(s_textlayer_name, GTextAlignmentCenter); + #endif } static void destroy_ui(void) { window_destroy(s_window); action_bar_layer_destroy(s_actionbarlayer); text_layer_destroy(s_textlayer_name); + text_layer_destroy(s_textlayer_repeat); + text_layer_destroy(s_textlayer_hrs); text_layer_destroy(s_textlayer_note); for (uint8_t l=0; lrow) { case MENU_JOB: menu_cell_draw_job(ctx, cell_layer, job_index); break; - case MENU_RESET: menu_cell_draw_other(ctx, cell_layer, "Reset Timer", NULL, bitmap_reset); break; - case MENU_ADD10: menu_cell_draw_other(ctx, cell_layer, "Add 10 Minutes", NULL, bitmap_add); break; - case MENU_SUB10: menu_cell_draw_other(ctx, cell_layer, "Sub 10 Minutes", NULL, bitmap_minus); break; - case MENU_RENAME: menu_cell_draw_other(ctx, cell_layer, "Rename", NULL, bitmap_edit); break; - case MENU_ADJUST: menu_cell_draw_other(ctx, cell_layer, "Set Repeat", NULL, bitmap_adjust); break; - case MENU_DELETE: menu_cell_draw_other(ctx, cell_layer, "Delete", NULL, bitmap_delete); break; + case MENU_RESET: menu_cell_draw_other(ctx, cell_layer, "Reset Timer", NULL, bitmaps[BITMAP_RESET]); break; + case MENU_ADD10: menu_cell_draw_other(ctx, cell_layer, "Add 10 Minutes", NULL, bitmaps[BITMAP_ADD]); break; + case MENU_SUB10: menu_cell_draw_other(ctx, cell_layer, "Sub 10 Minutes", NULL, bitmaps[BITMAP_MINUS]); break; + case MENU_RENAME: menu_cell_draw_other(ctx, cell_layer, "Rename", NULL, bitmaps[BITMAP_EDIT]); break; + case MENU_ADJUST: menu_cell_draw_other(ctx, cell_layer, "Set Repeat", NULL, bitmaps[BITMAP_ADJUST]); break; + case MENU_DELETE: menu_cell_draw_other(ctx, cell_layer, "Delete", NULL, bitmaps[BITMAP_DELETE]); break; } } diff --git a/src/jobs.c b/src/jobs.c index de66df0..c3ad2e3 100644 --- a/src/jobs.c +++ b/src/jobs.c @@ -1,19 +1,6 @@ #include "main.h" -#define JOB_NAME_LENGTH 24 -typedef struct { - char Name[JOB_NAME_LENGTH]; - time_t Seconds; - uint8_t Repeat_hrs; - bool Fixed; -} Job; - -typedef struct Job_ptr { - Job* Job; - struct Job_ptr* Next_ptr; -} Job_ptr ; - -static Job_ptr* first_job_ptr=NULL; +struct Job jobs[MAX_JOBS]; uint8_t jobs_count=0; // ***************************************************************************************************** @@ -21,89 +8,60 @@ uint8_t jobs_count=0; // ***************************************************************************************************** static void jobs_list_append_job(const char* name, time_t seconds, uint8_t repeat, int fixed) { - Job* new_job = malloc(sizeof(Job)); - Job_ptr* new_job_ptr = malloc(sizeof(Job_ptr)); - - new_job_ptr->Job = new_job; - new_job_ptr->Next_ptr = NULL; - strncpy(new_job->Name, name, JOB_NAME_LENGTH); + if (jobs_count==MAX_JOBS) return; + struct Job* new_job=&jobs[jobs_count++]; + strncpy(new_job->Name, name, JOB_NAME_SIZE)[JOB_NAME_SIZE-1]=0; new_job->Seconds = seconds; new_job->Repeat_hrs = repeat; new_job->Fixed = fixed ? true : false; - - if (first_job_ptr) { - Job_ptr* last_job_ptr = first_job_ptr; - while (last_job_ptr->Next_ptr) last_job_ptr=last_job_ptr->Next_ptr; - last_job_ptr->Next_ptr = new_job_ptr; - } else { - first_job_ptr = new_job_ptr; - } LOG("appended job: %s, seconds=%ld, repeat=%d, fixed=%d", new_job->Name, new_job->Seconds, new_job->Repeat_hrs, new_job->Fixed); - jobs_count++; } void jobs_list_sort(void) { - time_t end_time; - - Job_ptr* job_ptr_before = first_job_ptr; - while (job_ptr_before && job_ptr_before->Next_ptr) { - end_time = END_TIME(job_ptr_before->Job); - Job_ptr* job_ptr_min = job_ptr_before; + for (uint8_t before=0; beforeNext_ptr; - while (job_ptr_loop) { - if (END_TIME(job_ptr_loop->Job) < end_time) { - end_time = END_TIME(job_ptr_loop->Job); - job_ptr_min = job_ptr_loop; + for (uint8_t loop=before+1; loopNext_ptr; } - if (job_ptr_min != job_ptr_before) { + if (job_min!=before) { // swap med - Job* temp_job = job_ptr_before->Job; - job_ptr_before->Job = job_ptr_min->Job; - job_ptr_min->Job = temp_job; + struct Job temp_job = jobs[before]; + jobs[before] = jobs[job_min]; + jobs[job_min] = temp_job; } - - job_ptr_before = job_ptr_before->Next_ptr; } } void jobs_list_save(uint8_t first_key) { - Job_ptr* job_ptr = first_job_ptr; - while (job_ptr) { - persist_write_data(first_key++, job_ptr->Job, sizeof(Job)); - job_ptr=job_ptr->Next_ptr; - } + for (uint8_t a=0; aJob; - snprintf(buffer,JOB_NAME_LENGTH+30,"%s|%ld|%u|%d",job->Name, job->Seconds, job->Repeat_hrs, job->Fixed ? 1:0); + char buffer[JOB_NAME_SIZE+30]; + for (uint8_t a=0; aNext_ptr; } } void jobs_list_read_dict(DictionaryIterator *iter, uint8_t first_key, const uint8_t version) { - if (first_job_ptr!=NULL) return; - + if (jobs_count) return; Tuple *tuple_t; uint8_t fields=4; - char buffer[fields][JOB_NAME_LENGTH]; - + char buffer[fields][JOB_NAME_SIZE]; while ((tuple_t=dict_find(iter, first_key++))) { char *source = tuple_t->value->cstring; for (int c=0; cFixed = false; // this flag was not included before storage version 2 - LOG("loaded job: %s, seconds=%ld, repeat=%d, fixed=%d, version=%d", new_job->Name, new_job->Seconds, new_job->Repeat_hrs, new_job->Fixed, version); - new_job_ptr = malloc(sizeof(Job_ptr)); - new_job_ptr->Job = new_job; - new_job_ptr->Next_ptr = NULL; - if (prev_job_ptr) prev_job_ptr->Next_ptr = new_job_ptr; - prev_job_ptr = new_job_ptr; - if (NULL==first_job_ptr) first_job_ptr = new_job_ptr; - jobs_count++; - first_key++; } LOG("Loaded %d jobs.",jobs_count); } -Job* jobs_list_get_index(uint8_t index) { - if (index>=jobs_count) return NULL; - Job_ptr* job_ptr = first_job_ptr; - while (index--) job_ptr=job_ptr->Next_ptr; - return job_ptr->Job; -} - -void jobs_list_move_to_top(uint8_t index) { +void jobs_list_move_to_top_not_used(uint8_t index) { if (index==0 || index>=jobs_count) return; - Job_ptr* job_ptr = first_job_ptr; - Job_ptr* prev_job_ptr=NULL; - while (index--) { - prev_job_ptr=job_ptr; - job_ptr=job_ptr->Next_ptr; + + struct Job removed_job=jobs[index]; + while (index) { + jobs[index]=jobs[index-1]; + index--; } - // remove job_ptr from list - prev_job_ptr->Next_ptr = job_ptr->Next_ptr; - // Insert before first_job_ptr - job_ptr->Next_ptr = first_job_ptr; - first_job_ptr=job_ptr; + jobs[0]=removed_job; } // ***************************************************************************************************** // PUBLIC FUNCTIONS // ***************************************************************************************************** +static uint8_t jobs_find_job_index_from_name(const char *name) { + // get new index of job after sort + uint8_t index=0; + while (strcmp(jobs[index].Name,name)) index++; + return index; +} + static void callback(const char* result, size_t result_length, void* extra) { // Do something with result int index = (int) extra; if (index==-1) { - jobs_list_append_job(result, time(NULL), 0, 0); + jobs_list_append_job(result, time(NULL), 0, 0); } else { - snprintf(jobs_list_get_index(index)->Name,JOB_NAME_LENGTH, result); + strncpy(jobs[index].Name, result, JOB_NAME_SIZE)[JOB_NAME_SIZE-1]=0; } main_save_data(); main_menu_update(); + if (index==-1) { + job_menu_show(jobs_find_job_index_from_name(result)); + job_adjust_show(); + } } void jobs_add_job() { @@ -185,82 +131,31 @@ void jobs_add_job() { } void jobs_rename_job(uint8_t index) { - tertiary_text_prompt(jobs_get_job_name(index), callback, (void*) (int) index); + tertiary_text_prompt(jobs[index].Name, callback, (void*) (int) index); } void jobs_delete_all_jobs(void) { - Job_ptr* job_ptr = first_job_ptr; - while (first_job_ptr) { - Job_ptr * next_job=first_job_ptr->Next_ptr; - free(first_job_ptr->Job); - free(first_job_ptr); - first_job_ptr=next_job; - } jobs_count=0; } void jobs_delete_job_and_save(uint8_t index) { - if (index>=jobs_count) return; - - Job_ptr* job_ptr = first_job_ptr; - - if (index) { - Job_ptr* prev_job_ptr = NULL; - while (index--) { - prev_job_ptr=job_ptr; - job_ptr=job_ptr->Next_ptr; - } - prev_job_ptr->Next_ptr = job_ptr->Next_ptr; - } else { - first_job_ptr = job_ptr->Next_ptr; + jobs_count--; + while (indexJob); - free(job_ptr); - jobs_count--; main_save_data(); main_menu_update(); } -char* jobs_get_job_name(uint8_t index) { - Job* job=jobs_list_get_index(index); - return (job) ? job->Name : NULL; -} - -uint32_t jobs_get_job_seconds(uint8_t index) { - Job* job=jobs_list_get_index(index); - return (job) ? time(NULL) - job->Seconds : 0; -} - -uint8_t jobs_get_job_repeat(uint8_t index) { - Job* job=jobs_list_get_index(index); - return (job) ? job->Repeat_hrs : 0; -} - -bool jobs_get_job_fixed(uint8_t index) { - Job* job=jobs_list_get_index(index); - return (job) ? job->Fixed : 0; -} -static void jobs_update_job_index(Job* job, uint8_t *index) { - // check if job was moved during sort - if (job==jobs_list_get_index(*index)) return; - // get new index - Job_ptr* job_ptr = first_job_ptr; - *index=0; - while (job_ptr->Job != job) { - (*index)++; - job_ptr=job_ptr->Next_ptr; - } -} - void jobs_set_job_repeat(uint8_t *index, uint8_t repeat, uint8_t fixed) { - Job* job=jobs_list_get_index(*index); - if (job) { - job->Repeat_hrs=repeat; - job->Fixed = fixed ? true : false; - main_save_data(); - jobs_update_job_index(job, index); - } + char name[JOB_NAME_SIZE]; + strncpy(name,jobs[*index].Name,JOB_NAME_SIZE); + jobs[*index].Repeat_hrs=repeat; + jobs[*index].Fixed = fixed ? true : false; + main_save_data(); // this might resort the jobs. + *index=jobs_find_job_index_from_name(name); } #define MAX_CLOCK_LENGTH 24 @@ -268,22 +163,21 @@ char clock_buffer[MAX_CLOCK_LENGTH]; char repeat_buffer[MAX_CLOCK_LENGTH]; char* jobs_get_job_clock_as_text(uint8_t index) { - Job* job=jobs_list_get_index(index); time_t seconds; switch (settings.Mode) { case MODE_COUNT_DOWN: - seconds = job->Seconds + job->Repeat_hrs*3600 - time(NULL); + seconds = END_TIME(jobs[index]) - time(NULL); bool minus = seconds < 0; if (minus) seconds = -seconds; snprintf(clock_buffer,MAX_CLOCK_LENGTH,"%s%ld:%02ld:%02ld",minus?"+":"-",(seconds/3600) /*hours*/,(seconds / 60) % 60 /*mins*/,seconds % 60 /*secs*/); break; case MODE_COUNT_UP: - seconds = time(NULL) - job->Seconds; + seconds = time(NULL) - jobs[index].Seconds; snprintf(clock_buffer,MAX_CLOCK_LENGTH,"%ld:%02ld:%02ld",(seconds/3600) /*hours*/,(seconds / 60) % 60 /*mins*/,seconds % 60 /*secs*/); break; case MODE_NEXT_TIME: - ; time_t next = END_TIME(job); + ; time_t next = END_TIME(jobs[index]); strftime(clock_buffer,MAX_CLOCK_LENGTH,clock_is_24h_style() ? "%H:%M" : "%I:%M %p",localtime(&next)); break; } @@ -291,42 +185,33 @@ char* jobs_get_job_clock_as_text(uint8_t index) { } char* jobs_get_job_repeat_as_text(uint8_t index) { - Job* job=jobs_list_get_index(index); - - snprintf(repeat_buffer,MAX_CLOCK_LENGTH,"Every %d%s hrs", job->Repeat_hrs, job->Fixed ? "" : "+"); + snprintf(repeat_buffer,MAX_CLOCK_LENGTH,"Every %d%s hrs", jobs[index].Repeat_hrs, jobs[index].Fixed ? "" : "+"); return repeat_buffer; } void jobs_reset_and_save(uint8_t *index) { - Job* job=jobs_list_get_index(*index); - if (job->Fixed) { - job->Seconds+=job->Repeat_hrs*3600; + char name[JOB_NAME_SIZE]; + strncpy(name,jobs[*index].Name,JOB_NAME_SIZE); + + if (jobs[*index].Fixed) { + jobs[*index].Seconds+=jobs[*index].Repeat_hrs*3600; } else { - job->Seconds=time(NULL); + jobs[*index].Seconds=time(NULL); } - main_save_data(); - jobs_update_job_index(job, index); + main_save_data(); // this might resort the jobs. + *index=jobs_find_job_index_from_name(name); } void jobs_add_minutes(uint8_t *index, int minutes) { - Job* job=jobs_list_get_index(*index); - int seconds = (int) job->Seconds; - if (seconds + 60*minutes < time(NULL)) { - seconds += 60*minutes; - } else { - seconds = time(NULL); - } - job->Seconds = seconds; + jobs[*index].Seconds += 60*minutes; } time_t jobs_get_next_wakeup_time(void) { - Job_ptr* job_ptr = first_job_ptr; - time_t min_time = (END_TIME(job_ptr->Job) > time(NULL)) ? END_TIME(job_ptr->Job) : 0; - - while (job_ptr) { - if (job_ptr->Job->Repeat_hrs && END_TIME(job_ptr->Job) > time(NULL) && (min_time==0 || END_TIME(job_ptr->Job)Job); - job_ptr = job_ptr->Next_ptr; + time_t min_time = 0; + for (uint8_t a=0; a time(NULL) && (min_time==0 || END_TIME(jobs[a])Job->Repeat_hrs && END_TIME(job_ptr->Job) <= time(NULL)) new_alarm_count++; - job_ptr=job_ptr->Next_ptr; + for (uint8_t a=0; ajobs_alarm_count) vibrate(); jobs_alarm_count=new_alarm_count; diff --git a/src/jobs.h b/src/jobs.h index 946badd..3b31c26 100644 --- a/src/jobs.h +++ b/src/jobs.h @@ -1,5 +1,16 @@ #pragma once +#define MAX_JOBS 20 +#define JOB_NAME_SIZE 24 + +extern uint8_t jobs_count; +struct Job { + char Name[JOB_NAME_SIZE]; + time_t Seconds; + uint8_t Repeat_hrs; + bool Fixed; +}; +extern struct Job jobs[MAX_JOBS]; extern uint8_t jobs_count; void jobs_list_sort(void); @@ -12,11 +23,7 @@ void jobs_delete_all_jobs(void); void jobs_delete_job_and_save(uint8_t index); void jobs_add_job(); void jobs_rename_job(uint8_t index); -uint32_t jobs_get_job_seconds(uint8_t index); -uint8_t jobs_get_job_repeat(uint8_t index); -bool jobs_get_job_fixed(uint8_t index); void jobs_set_job_repeat(uint8_t *index, uint8_t repeat, uint8_t fixed); -char* jobs_get_job_name(uint8_t index); char* jobs_get_job_clock_as_text(uint8_t index); char* jobs_get_job_repeat_as_text(uint8_t index); void jobs_reset_and_save(uint8_t *index); diff --git a/src/main.c b/src/main.c index 389022f..e39a14d 100644 --- a/src/main.c +++ b/src/main.c @@ -5,17 +5,7 @@ extern const PebbleProcessInfo __pbl_app_info; #define APP_VERSION_LENGTH 10 char app_version[APP_VERSION_LENGTH]; -GBitmap *bitmap_matrix; -//GBitmap *bitmap_pause; -GBitmap *bitmap_play; -GBitmap *bitmap_add; -GBitmap *bitmap_settings; -GBitmap *bitmap_delete; -GBitmap *bitmap_edit; -GBitmap *bitmap_adjust; -GBitmap *bitmap_reset; -GBitmap *bitmap_minus; -GBitmap *bitmap_tick; +GBitmap *bitmaps[N_BITMAPS][PBL_IF_SDK_3_ELSE(2,1)]; Settings settings={MODE_COUNT_UP, false /*alarm*/, true /*sort*/}; static bool JS_ready = false; @@ -23,6 +13,10 @@ static bool data_loaded_from_watch = false; static uint32_t data_timestamp = 0; uint8_t stored_version=0; bool export_after_save=false; +#ifdef PBL_SDK_3 + uint8_t timeline_settings=TIMELINE_FLAG_ON | TIMELINE_FLAG_NOTIFICATIONS; + uint8_t quit_after_secs=0; +#endif // ***************************************************************************************************** // MESSAGES @@ -37,6 +31,7 @@ bool export_after_save=false; #define KEY_APP_VERSION 5 #define KEY_EXPORT 6 #define KEY_TIMESTAMP 7 +#define KEY_TIMELINE 8 static void send_settings_to_phone() { if (!JS_ready) return; @@ -47,9 +42,12 @@ static void send_settings_to_phone() { dict_write_cstring(iter, KEY_APP_VERSION, app_version); dummy_int=CURRENT_STORAGE_VERSION; dict_write_int(iter, KEY_VERSION, &dummy_int, sizeof(int), true); dummy_int=data_timestamp; dict_write_int(iter, KEY_TIMESTAMP, &dummy_int, sizeof(int), true); - dummy_int=settings.Mode; dict_write_int(iter, KEY_MODE, &dummy_int, sizeof(int), true); - dummy_int=settings.Alarm; dict_write_int(iter, KEY_ALARM, &dummy_int, sizeof(int), true); - dummy_int=settings.Sort; dict_write_int(iter, KEY_SORT, &dummy_int, sizeof(int), true); + dummy_int=settings.Mode; dict_write_int(iter, KEY_MODE, &dummy_int, sizeof(int), true); + dummy_int=settings.Alarm; dict_write_int(iter, KEY_ALARM, &dummy_int, sizeof(int), true); + dummy_int=settings.Sort; dict_write_int(iter, KEY_SORT, &dummy_int, sizeof(int), true); + #ifdef PBL_SDK_3 + dummy_int=timeline_settings; dict_write_int(iter, KEY_TIMELINE, &dummy_int, sizeof(int), true); + #endif jobs_list_write_dict(iter, KEY_MEDICATIONS); if (export_after_save) { @@ -59,6 +57,8 @@ static void send_settings_to_phone() { } dict_write_end(iter); + + //LOG("ended, dict_size=%d", (int) dict_size(iter)); app_message_outbox_send(); } @@ -66,6 +66,7 @@ static void inbox_received_handler(DictionaryIterator *iter, void *context) { LOG("Inbox received..."); JS_ready = true; Tuple *tuple_t; + bool new_data_from_config_page = dict_find(iter, KEY_CONFIG_DATA); tuple_t= dict_find(iter, KEY_TIMESTAMP); uint32_t inbox_timestamp = tuple_t ? tuple_t->value->int32 : 0; @@ -77,6 +78,9 @@ static void inbox_received_handler(DictionaryIterator *iter, void *context) { tuple_t=dict_find(iter, KEY_MODE); if (tuple_t) settings.Mode = tuple_t->value->int32; tuple_t=dict_find(iter, KEY_ALARM); if (tuple_t) settings.Alarm = tuple_t->value->int8 > 0; // convert int to boolean tuple_t=dict_find(iter, KEY_SORT); if (tuple_t) settings.Sort = tuple_t->value->int8 > 0; // convert int to boolean + #ifdef PBL_SDK_3 + tuple_t=dict_find(iter, KEY_TIMELINE); if (tuple_t) timeline_settings = tuple_t->value->int32; + #endif jobs_delete_all_jobs(); jobs_list_read_dict(iter, KEY_MEDICATIONS, stored_version); @@ -118,6 +122,9 @@ void main_save_data(void) { persist_write_int(STORAGE_KEY_VERSION, CURRENT_STORAGE_VERSION); data_timestamp=time(NULL); persist_write_int(STORAGE_KEY_TIMESTAMP, data_timestamp); + #ifdef PBL_SDK_3 + persist_write_int(STORAGE_KEY_TIMELINE, timeline_settings); + #endif persist_write_data(STORAGE_KEY_SETTINGS, &settings, sizeof(Settings)); if (settings.Sort) jobs_list_sort(); jobs_list_save(STORAGE_KEY_FIRST_MED); @@ -131,6 +138,9 @@ static void main_load_data(void) { if (stored_version) { data_loaded_from_watch = true; if (persist_exists(STORAGE_KEY_TIMESTAMP)) data_timestamp=persist_read_int(STORAGE_KEY_TIMESTAMP); + #ifdef PBL_SDK_3 + timeline_settings = persist_exists(STORAGE_KEY_TIMELINE) ? persist_read_int(STORAGE_KEY_TIMELINE) : (TIMELINE_FLAG_ON | TIMELINE_FLAG_NOTIFICATIONS); + #endif persist_read_data(STORAGE_KEY_SETTINGS, &settings, sizeof(Settings)); jobs_list_load(STORAGE_KEY_FIRST_MED, stored_version); if (stored_version < CURRENT_STORAGE_VERSION) { @@ -150,22 +160,65 @@ void init(void) { snprintf(app_version,APP_VERSION_LENGTH,"%d.%d",__pbl_app_info.process_version.major, __pbl_app_info.process_version.minor); main_load_data(); - bitmap_matrix=gbitmap_create_with_resource(RESOURCE_ID_IMAGE_ICON_MATRIX); - //bitmap_pause=gbitmap_create_as_sub_bitmap(bitmap_matrix, ICON_RECT_PAUSE); - bitmap_play=gbitmap_create_as_sub_bitmap(bitmap_matrix, ICON_RECT_PLAY); - bitmap_add=gbitmap_create_as_sub_bitmap(bitmap_matrix, ICON_RECT_ADD); - bitmap_settings=gbitmap_create_as_sub_bitmap(bitmap_matrix, ICON_RECT_SETTINGS); - bitmap_delete=gbitmap_create_as_sub_bitmap(bitmap_matrix, ICON_RECT_DELETE); - bitmap_edit=gbitmap_create_as_sub_bitmap(bitmap_matrix, ICON_RECT_EDIT); - bitmap_adjust=gbitmap_create_as_sub_bitmap(bitmap_matrix, ICON_RECT_ADJUST); - bitmap_reset=gbitmap_create_as_sub_bitmap(bitmap_matrix, ICON_RECT_RESET); - bitmap_minus=gbitmap_create_as_sub_bitmap(bitmap_matrix, ICON_RECT_MINUS); - bitmap_tick=gbitmap_create_as_sub_bitmap(bitmap_matrix, ICON_RECT_TICK); - main_menu_show(); + + bitmaps[BITMAP_MATRIX][0] =gbitmap_create_with_resource(RESOURCE_ID_IMAGE_ICON_MATRIX); + bitmaps[BITMAP_ADD][0] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][0], ICON_RECT_ADD); + bitmaps[BITMAP_MINUS][0] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][0], ICON_RECT_MINUS); + bitmaps[BITMAP_SETTINGS][0]=gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][0], ICON_RECT_SETTINGS); + bitmaps[BITMAP_DELETE][0] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][0], ICON_RECT_DELETE); + bitmaps[BITMAP_EDIT][0] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][0], ICON_RECT_EDIT); + bitmaps[BITMAP_ADJUST][0] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][0], ICON_RECT_ADJUST); + bitmaps[BITMAP_RESET][0] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][0], ICON_RECT_RESET); + bitmaps[BITMAP_PLAY][0] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][0], ICON_RECT_PLAY); + bitmaps[BITMAP_TICK][0] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][0], ICON_RECT_TICK); + #ifdef PBL_SDK_3 + // SDK 3 doesn't invert highligted menu icon, so need to use pre-inverted image... + bitmaps[BITMAP_MATRIX][1] =gbitmap_create_with_resource(RESOURCE_ID_IMAGE_ICON_MATRIX_INV); + bitmaps[BITMAP_ADD][1] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][1], ICON_RECT_ADD); + bitmaps[BITMAP_MINUS][1] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][1], ICON_RECT_MINUS); + bitmaps[BITMAP_SETTINGS][1]=gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][1], ICON_RECT_SETTINGS); + bitmaps[BITMAP_DELETE][1] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][1], ICON_RECT_DELETE); + bitmaps[BITMAP_EDIT][1] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][1], ICON_RECT_EDIT); + bitmaps[BITMAP_ADJUST][1] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][1], ICON_RECT_ADJUST); + bitmaps[BITMAP_RESET][1] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][1], ICON_RECT_RESET); + bitmaps[BITMAP_PLAY][1] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][1], ICON_RECT_PLAY); + bitmaps[BITMAP_TICK][1] =gbitmap_create_as_sub_bitmap(bitmaps[BITMAP_MATRIX][1], ICON_RECT_TICK); + #endif + if (data_loaded_from_watch && stored_version < CURRENT_STORAGE_VERSION) update_show(stored_version); - + app_message_register_inbox_received(inbox_received_handler); - app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum()); + app_message_open(636, 636); // should be enough for 23 meds: 78bytes+23meds*24bytes = 630 + + #ifdef PBL_SDK_3 + if (launch_reason() == APP_LAUNCH_TIMELINE_ACTION) { + uint8_t reason=launch_get_args(); + LOG("launch code: %d", reason); + if (reason>=10) { + // let list of med names, to find alphabetical order + uint8_t names[MAX_JOBS]; + uint8_t temp; + for (uint8_t a=0; abounds = (new_bounds)) +#define GColorLightGray GColorBlack +#define GColorDarkGray GColorBlack +#endif // PBL_SDK_3 +#define ROUND_MARGIN PBL_IF_ROUND_ELSE(2,0) +#ifndef STATUS_BAR_LAYER_HEIGHT +#define STATUS_BAR_LAYER_HEIGHT 0 +#endif + #define ANIMATED true #define HIDDEN true @@ -31,15 +56,17 @@ #define MENU_HEIGHT_SINGLE 28 #define MENU_HEIGHT_DOUBLE 42 -#define END_TIME(JOB) ((time_t) (JOB)->Seconds + (time_t) (JOB)->Repeat_hrs*3600) +#define END_TIME(JOB) ((time_t) (JOB).Seconds + (time_t) (JOB).Repeat_hrs*3600) #define FONT_GOTHIC_24_BOLD fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD) +#define FONT_GOTHIC_28_BOLD fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD) #define FONT_GOTHIC_18_BOLD fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD) #define FONT_GOTHIC_14_BOLD fonts_get_system_font(FONT_KEY_GOTHIC_14_BOLD) #define FONT_GOTHIC_18 fonts_get_system_font(FONT_KEY_GOTHIC_18) #define FONT_GOTHIC_14 fonts_get_system_font(FONT_KEY_GOTHIC_14) #define FONT_BITHAM_30_BLACK fonts_get_system_font(FONT_KEY_BITHAM_30_BLACK) #define FONT_BITHAM_34_MEDIUM_NUMBERS fonts_get_system_font(FONT_KEY_BITHAM_34_MEDIUM_NUMBERS) +#define FONT_ROBOTO_21_CONDENSED fonts_get_system_font(FONT_KEY_ROBOTO_CONDENSED_21) #define ICON_RECT_PLAY (GRect) { { 0, 0 }, { 16, 16 } } #define ICON_RECT_PAUSE (GRect) { { 16, 0 }, { 16, 16 } } @@ -58,27 +85,33 @@ #define ICON_RECT_MINUS (GRect) { { 16, 48 }, { 16, 16 } } #define ICON_RECT_CLOCK (GRect) { { 32, 16 }, { 16, 16 } } -extern GBitmap *bitmap_matrix; -//extern GBitmap *bitmap_pause; -extern GBitmap *bitmap_play; -extern GBitmap *bitmap_add; -extern GBitmap *bitmap_settings; -extern GBitmap *bitmap_delete; -extern GBitmap *bitmap_edit; -extern GBitmap *bitmap_adjust; -extern GBitmap *bitmap_reset; -extern GBitmap *bitmap_minus; -extern GBitmap *bitmap_tick; +enum { + BITMAP_MATRIX, + BITMAP_ADD, + BITMAP_MINUS, + BITMAP_SETTINGS, + BITMAP_DELETE, + BITMAP_EDIT, + BITMAP_ADJUST, + BITMAP_RESET, + BITMAP_TICK, + BITMAP_PLAY, + N_BITMAPS +}; + +extern GBitmap *bitmaps[N_BITMAPS][PBL_IF_SDK_3_ELSE(2,1)]; // Persistent Storage Keys #define STORAGE_KEY_VERSION 1 #define STORAGE_KEY_SETTINGS 2 #define STORAGE_KEY_TIMESTAMP 3 +#define STORAGE_KEY_TIMELINE 4 #define STORAGE_KEY_FIRST_MED 100 -#define CURRENT_STORAGE_VERSION 3 +#define CURRENT_STORAGE_VERSION 4 //changes in storage version: 2 added bool Fixed to end of every Job struct //changes in storage version: 3 configuration +//changes in storage version: 4 timeline typedef struct { uint8_t Mode; @@ -95,4 +128,12 @@ enum { MODE_NEXT_TIME }; + +#ifdef PBL_SDK_3 + extern uint8_t timeline_settings; + extern uint8_t quit_after_secs; +#define TIMELINE_FLAG_ON 1 +#define TIMELINE_FLAG_NOTIFICATIONS 2 +#endif + void main_save_data(void); \ No newline at end of file diff --git a/src/main_menu.c b/src/main_menu.c index 33752e9..c86b8a2 100644 --- a/src/main_menu.c +++ b/src/main_menu.c @@ -2,28 +2,49 @@ static Window *s_window; static MenuLayer *s_menulayer; +#ifdef PBL_SDK_3 +static StatusBarLayer *s_status_bar; +#endif + +static bool check_phone_message = false; static void initialise_ui(void) { s_window = window_create(); #ifndef PBL_SDK_3 window_set_fullscreen(s_window, false); #endif - GRect bounds = layer_get_bounds(window_get_root_layer(s_window)); + uint8_t margin=(bounds.size.w-144)/2; // s_menulayer - s_menulayer = menu_layer_create(GRect(0, 0, bounds.size.w, bounds.size.h)); + s_menulayer = menu_layer_create(GRect(0, STATUS_BAR_LAYER_HEIGHT, bounds.size.w, bounds.size.h-STATUS_BAR_LAYER_HEIGHT)); menu_layer_set_click_config_onto_window(s_menulayer, s_window); layer_add_child(window_get_root_layer(s_window), (Layer *)s_menulayer); + + #ifdef PBL_SDK_3 + // Set up the status bar last to ensure it is on top of other Layers + s_status_bar = status_bar_layer_create(); + layer_add_child(window_get_root_layer(s_window), status_bar_layer_get_layer(s_status_bar)); + #endif } static void destroy_ui(void) { window_destroy(s_window); menu_layer_destroy(s_menulayer); + + #ifdef PBL_SDK_3 + status_bar_layer_destroy(s_status_bar); + #endif } void handle_ticktimer_tick(struct tm *tick_time, TimeUnits units_changed) { //LOG("tick timer, units changed=%d",(int) units_changed); + #ifdef PBL_SDK_3 + if (quit_after_secs) { + if (--quit_after_secs==1) main_menu_hide(); + return; + } + #endif if (units_changed & SECOND_UNIT) { main_menu_update(); job_menu_update(); @@ -48,8 +69,12 @@ enum { // main menu structure MENU_SETTINGS_MODE=MENU_SECTION_SETTINGS*100, MENU_SETTINGS_ALARM, MENU_SETTINGS_SORT, + #ifdef PBL_SDK_3 + MENU_SETTINGS_TIMELINE, + MENU_SETTINGS_TL_NOTIFICATIONS, + #endif MENU_SETTINGS_CONFIG, - NUM_MENU_ITEMS_SETTINGS=4 + NUM_MENU_ITEMS_SETTINGS=PBL_IF_SDK_3_ELSE(6, 4) }; static uint16_t menu_get_num_sections_callback(MenuLayer *menu_layer, void *data) { @@ -77,7 +102,7 @@ static void menu_draw_header_callback(GContext* ctx, const Layer *cell_layer, ui case MENU_SECTION_JOBS: case MENU_SECTION_OTHER: break; - case MENU_SECTION_SETTINGS: menu_cell_basic_header_draw(ctx, cell_layer, "Options"); break; + case MENU_SECTION_SETTINGS: menu_cell_basic_header_draw(ctx, cell_layer, PBL_IF_ROUND_ELSE(" Options","Options")); break; } } @@ -91,32 +116,44 @@ static int16_t menu_get_cell_height_callback(MenuLayer *menu_layer, MenuIndex *c void menu_cell_draw_job(GContext* ctx, const Layer *cell_layer, const uint8_t index) { GRect bounds = layer_get_frame(cell_layer); - + + #ifndef PBL_SDK_3 graphics_context_set_text_color(ctx, GColorBlack); - graphics_draw_text(ctx, jobs_get_job_name(index), FONT_GOTHIC_24_BOLD, GRect(4, -4, bounds.size.w-8, 4+18), GTextOverflowModeFill, GTextAlignmentLeft, NULL); - graphics_draw_text(ctx, jobs_get_job_clock_as_text(index), FONT_GOTHIC_18, GRect(4, 20, bounds.size.w-8, 14), GTextOverflowModeFill, GTextAlignmentRight, NULL); - graphics_draw_text(ctx, jobs_get_job_repeat_as_text(index), FONT_GOTHIC_14, GRect(4, 20+4, bounds.size.w-8, 14), GTextOverflowModeFill, GTextAlignmentLeft, NULL); + #endif + uint8_t margin=(bounds.size.w-144)/2; + + graphics_draw_text(ctx, jobs[index].Name, FONT_GOTHIC_24_BOLD, GRect(4+margin, -4, bounds.size.w-2*(4+margin), 4+18), GTextOverflowModeFill, GTextAlignmentLeft, NULL); + graphics_draw_text(ctx, jobs_get_job_clock_as_text(index), FONT_GOTHIC_18, GRect(4+margin, 20, bounds.size.w-2*(4+margin), 14), GTextOverflowModeFill, GTextAlignmentRight, NULL); + graphics_draw_text(ctx, jobs_get_job_repeat_as_text(index), FONT_GOTHIC_14, GRect(4+margin, 20+4, bounds.size.w-2*(4+margin), 14), GTextOverflowModeFill, GTextAlignmentLeft, NULL); //graphics_draw_bitmap_in_rect(ctx, timer.Active && timer.Job==index ? bitmap_play : bitmap_pause, GRect(6, (bounds.size.h-16)/2, 16, 16)); } -void menu_cell_draw_other(GContext* ctx, const Layer *cell_layer, const char *title, const char *sub_title, GBitmap * icon) { +void menu_cell_draw_other(GContext* ctx, const Layer *cell_layer, const char *title, const char *sub_title, GBitmap ** icon) { GRect bounds = layer_get_frame(cell_layer); - + + #ifndef PBL_SDK_3 graphics_context_set_text_color(ctx, GColorBlack); - graphics_draw_text(ctx, title, FONT_GOTHIC_24_BOLD, GRect(28, -4, bounds.size.w-28, 4+18), GTextOverflowModeFill, GTextAlignmentLeft, NULL); - if (sub_title) graphics_draw_text(ctx, sub_title, FONT_GOTHIC_18, GRect(28, 20, bounds.size.w-28-4, 14), GTextOverflowModeFill, GTextAlignmentLeft, NULL); + #endif + uint8_t margin=(bounds.size.w-144)/2; + + graphics_draw_text(ctx, title, FONT_GOTHIC_24_BOLD, GRect(28+margin, -4, bounds.size.w-28-2*margin, 4+18), GTextOverflowModeFill, GTextAlignmentLeft, NULL); + if (sub_title) graphics_draw_text(ctx, sub_title, FONT_GOTHIC_18, GRect(28+margin, 20, bounds.size.w-28-margin-4, 14), GTextOverflowModeFill, GTextAlignmentLeft, NULL); - if (icon) graphics_draw_bitmap_in_rect(ctx, icon, GRect(6,(bounds.size.h-16)/2, 16, 16)); + if (icon) graphics_draw_bitmap_in_rect(ctx, icon[PBL_IF_SDK_3_ELSE(menu_cell_layer_is_highlighted(cell_layer), 0)], GRect(6+margin,(bounds.size.h-16)/2, 16, 16)); } static void menu_cell_draw_setting(GContext* ctx, const Layer *cell_layer, const char *title, const char *setting, const char *hint) { GRect bounds = layer_get_frame(cell_layer); - + + #ifndef PBL_SDK_3 graphics_context_set_text_color(ctx, GColorBlack); - graphics_draw_text(ctx, title, FONT_GOTHIC_24_BOLD, GRect(4, -4, bounds.size.w-8, 4+18), GTextOverflowModeFill, GTextAlignmentLeft, NULL); - graphics_draw_text(ctx, setting, FONT_GOTHIC_18_BOLD, GRect(4, 2, bounds.size.w-8, 18), GTextOverflowModeFill, GTextAlignmentRight, NULL); - graphics_draw_text(ctx, hint, FONT_GOTHIC_18, GRect(4, 20, bounds.size.w-8, 14), GTextOverflowModeFill, GTextAlignmentLeft, NULL); + #endif + uint8_t margin=(bounds.size.w-144)/2; + + graphics_draw_text(ctx, title, FONT_GOTHIC_24_BOLD, GRect(4+margin, -4, bounds.size.w-2*(4+margin), 4+18), GTextOverflowModeFill, GTextAlignmentLeft, NULL); + graphics_draw_text(ctx, setting, FONT_GOTHIC_18_BOLD, GRect(4+margin, 2, bounds.size.w-2*(4+margin), 18), GTextOverflowModeFill, GTextAlignmentRight, NULL); + graphics_draw_text(ctx, hint, FONT_GOTHIC_18, GRect(4+margin, 20, bounds.size.w-2*(4+margin), 14), GTextOverflowModeFill, GTextAlignmentLeft, NULL); } static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuIndex *cell_index, void *data) { @@ -128,17 +165,26 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI default: switch (MENU_SECTION_CELL) { - case MENU_OTHER_ADD: menu_cell_draw_other(ctx, cell_layer, "Add Medication", NULL, bitmap_add); break; + case MENU_OTHER_ADD: menu_cell_draw_other(ctx, cell_layer, "Add Medication", NULL, bitmaps[BITMAP_ADD]); break; case MENU_SETTINGS_MODE: menu_cell_draw_setting(ctx, cell_layer, "Mode", mode[settings.Mode],NULL); break; - case MENU_SETTINGS_ALARM: menu_cell_draw_setting(ctx, cell_layer, "Alarm", settings.Alarm ? "YES" : "NO",NULL); break; + case MENU_SETTINGS_ALARM: menu_cell_draw_setting(ctx, cell_layer, "Alarm", settings.Alarm ? "ON" : "OFF",NULL); break; case MENU_SETTINGS_SORT: menu_cell_draw_setting(ctx, cell_layer, "Sort", settings.Sort ? "YES" : "NO",NULL); break; - case MENU_SETTINGS_CONFIG: menu_cell_draw_other(ctx, cell_layer, "Config/Donate", NULL , bitmap_settings); break; + case MENU_SETTINGS_CONFIG: menu_cell_draw_other(ctx, cell_layer, check_phone_message ? "Check phone..." : "Config/Donate", NULL , bitmaps[BITMAP_SETTINGS]); break; + #ifdef PBL_SDK_3 + case MENU_SETTINGS_TIMELINE: menu_cell_draw_setting(ctx, cell_layer, "Timeline", timeline_settings&TIMELINE_FLAG_ON ? "ON" : "OFF",NULL); break; + case MENU_SETTINGS_TL_NOTIFICATIONS: menu_cell_draw_setting(ctx, cell_layer, "..Notifications", timeline_settings&TIMELINE_FLAG_NOTIFICATIONS ? "ON" : "OFF",NULL); break; + #endif } } } +static void timer_callback(void *data) { + check_phone_message=false; + menu_layer_reload_data(s_menulayer); +} + static void menu_select_callback(MenuLayer *menu_layer, MenuIndex *cell_index, void *data) { switch (cell_index->section) { case MENU_SECTION_JOBS: @@ -165,7 +211,22 @@ static void menu_select_callback(MenuLayer *menu_layer, MenuIndex *cell_index, v case MENU_SETTINGS_CONFIG: export_after_save=true; main_save_data(); + check_phone_message=true; + menu_layer_reload_data(s_menulayer); + app_timer_register(2000 /* milliseconds */, timer_callback, NULL); + break; + #ifdef PBL_SDK_3 + case MENU_SETTINGS_TIMELINE: + timeline_settings^=TIMELINE_FLAG_ON ; // ^ = XOR + main_save_data(); + menu_layer_reload_data(s_menulayer); + break; + case MENU_SETTINGS_TL_NOTIFICATIONS: + timeline_settings^=TIMELINE_FLAG_NOTIFICATIONS ; // ^ = XOR + main_save_data(); + menu_layer_reload_data(s_menulayer); break; + #endif } } } diff --git a/src/main_menu.h b/src/main_menu.h index 5a3474e..c13c437 100644 --- a/src/main_menu.h +++ b/src/main_menu.h @@ -1,7 +1,7 @@ #pragma once void menu_cell_draw_job(GContext* ctx, const Layer *cell_layer, const uint8_t index); -void menu_cell_draw_other(GContext* ctx, const Layer *cell_layer, const char *title, const char *sub_title, GBitmap * icon); +void menu_cell_draw_other(GContext* ctx, const Layer *cell_layer, const char *title, const char *sub_title, GBitmap ** icon); void handle_ticktimer_tick(struct tm *tick_time, TimeUnits units_changed); void main_menu_show(void); void main_menu_hide(void); diff --git a/src/pebble-js-app.js b/src/pebble-js-app.js index 53ca245..0977d61 100644 --- a/src/pebble-js-app.js +++ b/src/pebble-js-app.js @@ -1,23 +1,24 @@ -// https://github.com/pebble-examples/slate-config-example/blob/master/src/js/pebble-js-app.js +var TIMELINE_FLAG_ON = 1; +var TIMELINE_FLAG_NOTIFICATIONS = 2; Pebble.addEventListener('ready', function() { console.log('PebbleKit JS ready!'); var settings=localStorage.getItem("settings"); - //settings='{"1":2,"2":1,"3":1,"4":3,"5":"1.2","100":"Ibuprofin|1453318208|12|1","101":"Omeprazole|1453275012|24|1","102":"Paracetamol|1453347465|6|0","103":"Tremadol|1453347472|8|0","104":"Zopiclone|1453330855|24|1","KEY_MODE":2,"KEY_ALARM":1,"KEY_SORT":1,"KEY_VERSION":3,"KEY_APP_VERSION":"1.2","KEY_MEDICATIONS":"Ibuprofin|1453318208|12|1"}'; - var dict=settings ? JSON.parse(settings) : {}; - if (!dict.KEY_TIMESTAMP) { + //settings='{"101":"Ibuprofen|1454731249|6|1","102":"Tremadol|1454738400|8|1","103":"Omeprazole|1454697012|24|1","KEY_TIMELINE":0,"KEY_APP_VERSION":"1.5","KEY_SORT":1,"KEY_MEDICATIONS":"Paracetamol|1454720453|6|1","KEY_VERSION":4,"KEY_MODE":2,"KEY_TIMESTAMP":1454739389,"KEY_ALARM":0}'; + settings=settings ? JSON.parse(settings) : {}; + if (!settings.KEY_TIMESTAMP) { var d=new Date(); - dict.KEY_TIMESTAMP = Math.floor(d.getTime()/1000 - d.getTimezoneOffset()*60); + settings.KEY_TIMESTAMP = Math.floor(d.getTime()/1000 - d.getTimezoneOffset()*60); + } + sendDict(settings); + if (localStorage.getItem("old_pins")) { + deleteAllPins(); + if (settings.KEY_TIMELINE & TIMELINE_FLAG_ON) createAllPins(settings); } - Pebble.sendAppMessage(dict, function() { - console.log('Send successful: ' + JSON.stringify(dict)); - }, function() { - console.log('Send failed!'); - }); }); -Pebble.addEventListener("appmessage", function(e) { - console.log("Received message: " + JSON.stringify(e.payload)); +Pebble.addEventListener("appmessage", function(e) { + console.log("Received message: " + JSON.stringify(e.payload)); if (e.payload.KEY_EXPORT) { delete e.payload.KEY_EXPORT; localStorage.setItem("settings",JSON.stringify(e.payload)); @@ -25,8 +26,20 @@ Pebble.addEventListener("appmessage", function(e) { } else { localStorage.setItem("settings",JSON.stringify(e.payload)); } + if (typeof(e.payload.KEY_TIMELINE)!="undefined") { + deleteAllPins(); + if (e.payload.KEY_TIMELINE & TIMELINE_FLAG_ON) createAllPins(e.payload); + } }); +function sendDict(dict) { + Pebble.sendAppMessage(dict, function() { + console.log('Send successful: ' + JSON.stringify(dict)); + }, function() { + console.log('Send failed!'); + }); +} + function padZero(i) { return (i<10 ? "0":"")+i; } @@ -44,15 +57,18 @@ function showConfiguration() { config+="&mode="+settings.KEY_MODE; config+="&alarm="+settings.KEY_ALARM; config+="&sort="+settings.KEY_SORT; + if (settings.KEY_TIMELINE) config+="&timeline="+settings.KEY_TIMELINE; var med=0; var setting; + var d; + var secs; while (setting=(med===0) ? settings.KEY_MEDICATIONS : settings[100+med]) { setting=setting.split("|"); - var d = new Date(0); // The 0 there is the key, which sets the date to the epoch - d.setUTCSeconds(setting[1]*1.0 +setting[2]*3600); - var secs=d.getSeconds(); - d.setUTCSeconds(d.getTimezoneOffset()*60); // this sets seconds to zero + d = new Date(0); // The 0 there is the key, which sets the date to the epoch + d.setUTCSeconds(setting[1]*1.0 +setting[2]*3600); // setting[1] = last time, setting[2] = repeat_hrs + secs=d.getSeconds(); + /*d.setUTCSeconds(d.getTimezoneOffset()*60); // this sets seconds to zero */ setting[1]=padZero(d.getHours())+":"+padZero(d.getMinutes()); setting.push(secs); config+="&med_"+med+"="+encodeURIComponent(setting.join("|")); @@ -70,33 +86,243 @@ Pebble.addEventListener('webviewclosed', function(e) { var dict = {}; dict.KEY_CONFIG_DATA = 1; - dict.KEY_MODE = configData.mode; + dict.KEY_MODE = configData.mode*1.0; // convert to integer dict.KEY_ALARM = configData.alarm ? 1 : 0; // Send a boolean as an integer dict.KEY_SORT = configData.sort ? 1 : 0; // Send a boolean as an integer dict.KEY_VERSION = configData.data_version; var d=new Date(); - dict.KEY_TIMESTAMP = Math.floor(d.getTime()/1000 - d.getTimezoneOffset()*60); + dict.KEY_TIMESTAMP = Math.floor(d.getTime()/1000 /*- d.getTimezoneOffset()*60 */); + if (typeof(configData.timeline)!="undefined") dict.KEY_TIMELINE=configData.timeline*1.0; var med=0; + var data; + var hhmm; + var secs; while (configData["med_"+med]) { - var data=decodeURIComponent(configData["med_"+med]).split("|"); - var hhmm=data[1].split(":"); + data=decodeURIComponent(configData["med_"+med]).split("|"); + hhmm=data[1].split(":"); d = new Date(); // The 0 there is the key, which sets the date to the epoch d.setHours(hhmm[0]); d.setMinutes(hhmm[1]); d.setSeconds(data[4]); d.setMilliseconds(0); if (d < new Date()) d.setHours(hhmm[0]*1.0 + 24); - var secs=d.getTime()/1000; - data[1]=secs - d.getTimezoneOffset()*60 - data[2]*3600; + secs=d.getTime()/1000; + data[1]=secs /*- d.getTimezoneOffset()*60 */- data[2]*3600; dict[100+med]=data.join("|"); med++; } // Send to watchapp - Pebble.sendAppMessage(dict, function() { - console.log('Send successful: ' + JSON.stringify(dict)); - }, function() { - console.log('Send failed!'); - }); -}); \ No newline at end of file + sendDict(dict); +}); + +/****************************** Custom Timeline Stuff ************************/ + +var pin_prefix="MedTimer-pin-"; + +function deleteAllPins() { + // delete any old pins + var pin; + + var pins=JSON.parse(localStorage.getItem("pins")) || []; + var old_pins=JSON.parse(localStorage.getItem("old_pins")) || []; + + old_pins=old_pins.concat(pins); + localStorage.removeItem("pins"); + localStorage.setItem("old_pins",JSON.stringify(old_pins)); + + console.log("old pins: "+JSON.stringify(old_pins)); + + for (var med=0; med -1) old_pins.splice(index, 1); + if (old_pins.length) { + localStorage.setItem("old_pins",JSON.stringify(old_pins)); + } else { + localStorage.removeItem("old_pins"); + } + console.log("remaining old pins: "+JSON.stringify(old_pins)); + }); + } +} + +function createAllPins(dict) { + if (!dict) return; + var pin; + var med; + var setting; + var pin_now=(new Date()).valueOf()+"-"; + var pins=JSON.parse(localStorage.getItem("pins")) || []; + + // get list of med names and sort them alphabetically. + var new_pins=[]; + med=0; + while (setting=(med===0) ? dict.KEY_MEDICATIONS : dict[100+med]) { + setting=setting.split("|"); + new_pins.push(setting[0]); // med name + med++; + } + new_pins.sort(); + + // create and send pins + med=0; + while (setting=(med===0) ? dict.KEY_MEDICATIONS : dict[100+med]) { + // get the time + setting=setting.split("|"); + var d = new Date(0); // The 0 there is the key, which sets the date to the epoch + d.setUTCSeconds(setting[1]*1.0 +setting[2]*3600); // setting[1] = last time, setting[2] = repeat_hrs + d.setSeconds(0); + d.setMilliseconds(0); + + // configure the pin + pin = { + "id": pin_prefix+pin_now+med,//pin_prefix+med, + "time": d.toISOString(), + "layout": { + "type": "genericPin", + "title": "Take "+setting[0], + "tinyIcon": "system://images/NOTIFICATION_REMINDER" + }, + "actions": [ + { + "title": "Med Taken", + "type": "openWatchApp", + "launchCode": 10+new_pins.indexOf(setting[0]) + }, + { + "title": "Open Meds Timer", + "type": "openWatchApp", + "launchCode": 0 + } + ], + }; + if (dict.KEY_TIMELINE & TIMELINE_FLAG_NOTIFICATIONS) { + pin.reminders=[ + { + "time": d.toISOString(), + "layout": { + "type": "genericReminder", + "tinyIcon": "system://images/NOTIFICATION_REMINDER", + "title": "Take "+setting[0] + } + } + ]; + } + // remember pin, to be deleted later + pins.push(pin_now+med); + // Push the pin + console.log('Inserting pin in the future: ' + JSON.stringify(pin)); + insertUserPin(pin, function(responseText, id) { + //console.log('insertUserPin Result: ' + responseText+" "+id); + }); + med++; + } + console.log("remembering pins: "+JSON.stringify(pins)); + localStorage.setItem("pins",JSON.stringify(pins)); +} + + +// https://gist.github.com/pebble-gists/6a4082ef12e625d23455 +/******************************* timeline lib *********************************/ + +// The timeline public URL root +var API_URL_ROOT = 'https://timeline-api.getpebble.com/'; +var usertoken=null; + +/** + * Send a request to the Pebble public web timeline API. + * @param pin The JSON pin to insert. Must contain 'id' field. + * @param type The type of request, either PUT or DELETE. + * @param topics Array of topics if a shared pin, 'null' otherwise. + * @param apiKey Timeline API key for this app, available from dev-portal.getpebble.com + * @param callback The callback to receive the responseText after the request has completed. + */ +function timelineRequest(pin, type, topics, apiKey, callback) { + // User or shared? + var url = API_URL_ROOT + 'v1/' + ((topics !== null) ? 'shared/' : 'user/') + 'pins/' + pin.id; + + // Create XHR + var xhr = new XMLHttpRequest(); + xhr.onload = function () { + //console.log('timeline: response received: ' + this.responseText); + callback(this.responseText, pin.id); + }; + xhr.onerror=function(error) { + console.log('timeline: error: ' +JSON.stringify(error)); + callback("error"); + }; + xhr.open(type, url); + + // Set headers + xhr.setRequestHeader('Content-Type', 'application/json'); + if(topics !== null) { + xhr.setRequestHeader('X-Pin-Topics', '' + topics.join(',')); + xhr.setRequestHeader('X-API-Key', '' + apiKey); + } + + // Get token + if (usertoken) { + send(xhr,pin); + } else { + Pebble.getTimelineToken(function(token) { + usertoken=token; + send(xhr,pin); + }, function(error) { console.log('timeline: error getting timeline token: ' + error); }); + } +} + +function send(xhr,pin) { + // Add headers + xhr.setRequestHeader('X-User-Token', '' + usertoken); + // Send + xhr.send(JSON.stringify(pin)); + //console.log('timeline: request sent. '); +} + +/** + * Insert a pin into the timeline for this user. + * @param pin The JSON pin to insert. + * @param callback The callback to receive the responseText after the request has completed. + */ +function insertUserPin(pin, callback) { + timelineRequest(pin, 'PUT', null, null, callback); +} + +/** + * Delete a pin from the timeline for this user. + * @param pin The JSON pin to delete. + * @param callback The callback to receive the responseText after the request has completed. + */ +function deleteUserPin(pin, callback) { + timelineRequest(pin, 'DELETE', null, null, callback); +} + +/** + * Insert a pin into the timeline for these topics. + * @param pin The JSON pin to insert. + * @param topics Array of topics to insert pin to. + * @param apiKey Timeline API key for this app, available from dev-portal.getpebble.com + * @param callback The callback to receive the responseText after the request has completed. + */ +function insertSharedPin(pin, topics, apiKey, callback) { + timelineRequest(pin, 'PUT', topics, apiKey, callback); +} + +/** + * Delete a pin from the timeline for these topics. + * @param pin The JSON pin to delete. + * @param topics Array of topics to delete pin from. + * @param apiKey Timeline API key for this app, available from dev-portal.getpebble.com + * @param callback The callback to receive the responseText after the request has completed. + */ +function deleteSharedPin(pin, topics, apiKey, callback) { + timelineRequest(pin, 'DELETE', topics, apiKey, callback); +} + +/****************************** end timeline lib ******************************/ \ No newline at end of file diff --git a/src/tertiary_text.c b/src/tertiary_text.c index ca3a409..8e2895f 100644 --- a/src/tertiary_text.c +++ b/src/tertiary_text.c @@ -11,7 +11,7 @@ static Window* window; static TextLayer* text_title; -static TextLayer* text_layer; +//static TextLayer* text_layer; static TextLayer* text_input; static TextLayer* text_help; @@ -280,18 +280,19 @@ static void initSidesAndText() // Retrieve the window layer and its bounds Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); - + /* // Create a text layer for the text that is typed text_layer = text_layer_create((GRect) { .origin = { 0, 72 }, .size = { bounds.size.w, 20 } }); text_layer_set_font(text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD)); layer_add_child(window_layer, text_layer_get_layer(text_layer)); - + */ // Create a text layer for the title text_title = text_layer_create( GRect( 5, 5, 100, 30 ) ); text_layer_set_font( text_title, fonts_get_system_font( FONT_KEY_GOTHIC_14_BOLD ) ); text_layer_set_text( text_title, title ); layer_add_child( window_layer, text_layer_get_layer( text_title ) ); + // Create a text layer for the text that is typed text_input = text_layer_create((GRect) { .origin = { 10, 40 }, .size = { 100, 150 } }); text_layer_set_background_color(text_input, GColorClear); text_layer_set_font(text_input, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD)); @@ -310,13 +311,19 @@ static void initSidesAndText() for (int i = 0; i<3; i++) { for( int j=0; j<3; j++ ) { - bbuttons[j][i] = text_layer_create(GRect(115, 12*i+50*j, 100, 100)); + bbuttons[j][i] = text_layer_create(GRect(115+PBL_IF_ROUND_ELSE(14,0), 12*i+PBL_IF_ROUND_ELSE(45,50)*j+PBL_IF_ROUND_ELSE(20,0), 100, 100)); text_layer_set_background_color( bbuttons[j][i], GColorBlack); text_layer_set_text_color(bbuttons[j][i], GColorWhite); layer_add_child( window_layer, text_layer_get_layer( bbuttons[j][i] ) ); } } - + + #ifdef PBL_ROUND + layer_set_frame(text_layer_get_layer(text_title),GRect(31+10,28-14,125-31-10,60)); + layer_set_frame(text_layer_get_layer(text_input),GRect(6,70-14,125-6,60)); + layer_set_frame(text_layer_get_layer(text_help),GRect(42,121,100,50)); + layer_set_frame(text_layer_get_layer(inverter_side),GRect(125, 0, bounds.size.w-125, bounds.size.h)); + #endif } static void drawNotepadText() @@ -326,7 +333,7 @@ static void drawNotepadText() static void window_unload(Window *window) { - text_layer_destroy(text_layer); + //text_layer_destroy(text_layer); text_layer_destroy(text_input); text_layer_destroy(text_title); text_layer_destroy(text_help); diff --git a/src/tertiary_text.h b/src/tertiary_text.h index 086a5e9..4581941 100644 --- a/src/tertiary_text.h +++ b/src/tertiary_text.h @@ -1,6 +1,10 @@ #pragma once #include +#ifndef PBL_SDK_3 +#define PBL_IF_ROUND_ELSE(X, Y) (Y) +#endif + // A callback which is called when a user submits data // // Params: diff --git a/src/update.c b/src/update.c index f8422b0..2d48d8b 100644 --- a/src/update.c +++ b/src/update.c @@ -3,30 +3,46 @@ // BEGIN AUTO-GENERATED UI CODE; DO NOT MODIFY static Window *s_window; -static GFont s_res_gothic_28_bold; -static GFont s_res_gothic_14; static TextLayer *s_textlayer_heading; static TextLayer *s_textlayer_features; +#define MARGIN PBL_IF_ROUND_ELSE(12,0) +#define HEIGHT PBL_IF_ROUND_ELSE(56,32) + +void select_button_handler(ClickRecognizerRef recognizer, void *context) { + update_hide(); +} + +void click_config_provider(void *context) { + window_single_click_subscribe(BUTTON_ID_SELECT, select_button_handler); +} + static void initialise_ui(void) { s_window = window_create(); #ifndef PBL_SDK_3 window_set_fullscreen(s_window, false); #endif - s_res_gothic_28_bold = fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD); - s_res_gothic_14 = fonts_get_system_font(FONT_KEY_GOTHIC_18); + GRect bounds = layer_get_bounds(window_get_root_layer(s_window)); + // s_textlayer_heading - s_textlayer_heading = text_layer_create(GRect(2, -1, 140, 28)); - text_layer_set_text(s_textlayer_heading, "New Feature:"); - text_layer_set_font(s_textlayer_heading, s_res_gothic_28_bold); + s_textlayer_heading = text_layer_create(GRect(0, -1, bounds.size.w, HEIGHT+MARGIN)); + text_layer_set_text(s_textlayer_heading, PBL_IF_ROUND_ELSE("New\nFeatures:","New Features:")); + text_layer_set_background_color(s_textlayer_heading,GColorBlack); + text_layer_set_text_alignment(s_textlayer_heading, GTextAlignmentCenter); + text_layer_set_text_color(s_textlayer_heading,GColorWhite); + text_layer_set_font(s_textlayer_heading, FONT_GOTHIC_28_BOLD); layer_add_child(window_get_root_layer(s_window), (Layer *)s_textlayer_heading); // s_textlayer_features - s_textlayer_features = text_layer_create(GRect(2, 27, 140, 125)); - text_layer_set_text(s_textlayer_features, "* Configuration page for easier medication entry.\n\n * DONATIONS can be accepted from the config page!"); - text_layer_set_font(s_textlayer_features, s_res_gothic_14); + s_textlayer_features = text_layer_create(GRect(2+MARGIN, HEIGHT+MARGIN, bounds.size.w-2*(2+MARGIN), bounds.size.h-HEIGHT-MARGIN)); + text_layer_set_text(s_textlayer_features, "* Medications now appear on the timeline\n\n* Improved for Pebble Time Round"); + text_layer_set_font(s_textlayer_features, FONT_GOTHIC_18); + #ifdef PBL_ROUND + text_layer_set_text_alignment(s_textlayer_features, GTextAlignmentCenter); + #endif layer_add_child(window_get_root_layer(s_window), (Layer *)s_textlayer_features); + window_set_click_config_provider(s_window,(void*)click_config_provider); } static void destroy_ui(void) {