diff --git a/appinfo.json b/appinfo.json index 790831e..139b206 100644 --- a/appinfo.json +++ b/appinfo.json @@ -1,18 +1,52 @@ { - "versionLabel": "1.0", - "uuid": "081b46ac-5530-413c-b82e-a192fb698838", "appKeys": {}, - "longName": "Tea Timer", - "versionCode": 14, "capabilities": [ "" ], + "companyName": "Ouroboros", + "longName": "Tea Timer", + "projectType": "native", + "resources": { + "media": [ + { + "characterRegex": "[0-9:]", + "file": "fonts/Dosis-Watch.ttf", + "name": "FONT_DOSIS_WATCH_46", + "type": "font" + }, + { + "file": "images/plus_icon_white.png", + "name": "PLUS_ICON", + "type": "png" + }, + { + "file": "images/play_icon_white.png", + "name": "PLAY_ICON", + "type": "png" + }, + { + "file": "images/pause_icon_white.png", + "name": "PAUSE_ICON", + "type": "png" + }, + { + "file": "images/minus_icon_white.png", + "name": "MINUS_ICON", + "type": "png" + }, + { + "file": "images/tea_timer_icon.png", + "menuIcon": true, + "name": "TEA_TIMER_ICON", + "type": "png" + } + ] + }, "shortName": "Tea Timer", - "companyName": "", + "uuid": "081b46ac-5530-413c-b82e-a192fb698839", + "versionCode": 14, + "versionLabel": "1.0", "watchapp": { "watchface": false - }, - "resources": { - "media": [] } } diff --git a/resources/fonts/Dosis-Watch.ttf b/resources/fonts/Dosis-Watch.ttf new file mode 100644 index 0000000..e538400 Binary files /dev/null and b/resources/fonts/Dosis-Watch.ttf differ diff --git a/resources/images/minus_icon_white.png b/resources/images/minus_icon_white.png new file mode 100644 index 0000000..9144cad Binary files /dev/null and b/resources/images/minus_icon_white.png differ diff --git a/resources/images/pause_icon_white.png b/resources/images/pause_icon_white.png new file mode 100644 index 0000000..55c63ca Binary files /dev/null and b/resources/images/pause_icon_white.png differ diff --git a/resources/images/play_icon_white.png b/resources/images/play_icon_white.png new file mode 100644 index 0000000..187da66 Binary files /dev/null and b/resources/images/play_icon_white.png differ diff --git a/resources/images/plus_icon_white.png b/resources/images/plus_icon_white.png new file mode 100644 index 0000000..b732c69 Binary files /dev/null and b/resources/images/plus_icon_white.png differ diff --git a/resources/images/tea_timer_icon.png b/resources/images/tea_timer_icon.png new file mode 100644 index 0000000..b683156 Binary files /dev/null and b/resources/images/tea_timer_icon.png differ diff --git a/src/controller.c b/src/controller.c new file mode 100644 index 0000000..d82a81f --- /dev/null +++ b/src/controller.c @@ -0,0 +1,86 @@ +#include +#include "controller.h" +#include "util.h" + +#define BUTTON_CLICK_TIME_SHIFT 30 +#define INITIAL_TIME 3*60 + +typedef struct TimerController { + TimerDisplay *display; + int timer_running; + int time_left; + TickSource tick_source; +} TimerController; + +void click_config_provider(void *context); +void toggle_timer(ClickRecognizerRef recognizer, void *context); +void handle_up(ClickRecognizerRef recognizer, void *context); +void handle_down(ClickRecognizerRef recognizer, void *context); + +TimerController *timer_controller_create(TimerDisplay *display, TickSource source) { + TimerController *self = calloc(1, sizeof(TimerController)); + self->display = display; + self->time_left = INITIAL_TIME; + self->tick_source = source; + timer_display_set_click_config_provider(self->display, click_config_provider, self); + timer_display_update(self->display, self->time_left); + return self; +} + +void timer_controller_destroy(TimerController *controller) { + timer_display_destroy(controller->display); + free(controller); +} + +void timer_controller_tick(TimerController *controller) { + controller->time_left = MAX(0, controller->time_left-1); + + if(controller->time_left == 0) { + timer_display_alert(controller->display); + } + + if(controller->time_left < 5 && controller->time_left > 0) { + timer_display_warning(controller->display); + } + + timer_display_update(controller->display, controller->time_left); +} + +void click_config_provider(void *context) { + window_set_click_context(BUTTON_ID_UP, context); + window_set_click_context(BUTTON_ID_SELECT, context); + window_set_click_context(BUTTON_ID_DOWN, context); + window_single_repeating_click_subscribe(BUTTON_ID_UP, 60, handle_up); + window_single_click_subscribe(BUTTON_ID_SELECT, toggle_timer); + window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 60, handle_down); +} + +void toggle_timer(ClickRecognizerRef recognizer, void *context) { + TimerController *controller = (TimerController *)context; + TOGGLE(controller->timer_running); + timer_display_cancel(controller->display); + timer_display_set_mode(controller->display, controller->timer_running); + controller->tick_source(controller->timer_running); +} + +void handle_up(ClickRecognizerRef recognizer, void *context) { + TimerController *controller = (TimerController *)context; + if(!controller->timer_running) { + controller->time_left = MIN(controller->time_left + + BUTTON_CLICK_TIME_SHIFT + - (controller->time_left % BUTTON_CLICK_TIME_SHIFT), + 99*60); + } + + timer_display_update(controller->display, controller->time_left); +} + +void handle_down(ClickRecognizerRef recognizer, void *context) { + TimerController *controller = (TimerController *)context; + if(!controller->timer_running) { + int mod = controller->time_left % BUTTON_CLICK_TIME_SHIFT; + controller->time_left = MAX(controller->time_left - (mod > 0 ? mod : BUTTON_CLICK_TIME_SHIFT), 0); + } + + timer_display_update(controller->display, controller->time_left); +} \ No newline at end of file diff --git a/src/controller.h b/src/controller.h new file mode 100644 index 0000000..12da775 --- /dev/null +++ b/src/controller.h @@ -0,0 +1,9 @@ +#pragma once + +#include "display.h" +typedef struct TimerController TimerController; +typedef void (*TickSource)(int enabled); + +TimerController *timer_controller_create(TimerDisplay *display, TickSource source); +void timer_controller_destroy(TimerController *controller); +void timer_controller_tick(TimerController *controller); \ No newline at end of file diff --git a/src/display.c b/src/display.c index eab2961..9a342ae 100644 --- a/src/display.c +++ b/src/display.c @@ -1,73 +1,102 @@ #include "display.h" -#include "time_handler.h" +#include "util.h" -#ifndef TOGGLE -#define TOGGLE(x) x = !x -#endif +typedef struct TimerDisplay { + Window *window; + TextLayer *time_text; + ActionBarLayer *action_bar; + InverterLayer *inverter; + GBitmap *bitmap_plus_icon; + GBitmap *bitmap_play_icon; + GBitmap *bitmap_minus_icon; + GBitmap *bitmap_pause_icon; +} TimerDisplay; -bool flash_background = true; -bool light_enabled = false; +TimerDisplay* timer_display_create() { + TimerDisplay* self = calloc(1, sizeof(TimerDisplay)); -static Window *window = NULL; -static TextLayer *timer = NULL; + self->bitmap_plus_icon = gbitmap_create_with_resource(RESOURCE_ID_PLUS_ICON); + self->bitmap_play_icon = gbitmap_create_with_resource(RESOURCE_ID_PLAY_ICON); + self->bitmap_minus_icon = gbitmap_create_with_resource(RESOURCE_ID_MINUS_ICON); + self->bitmap_pause_icon = gbitmap_create_with_resource(RESOURCE_ID_PAUSE_ICON); + + self->window = window_create(); + window_stack_push(self->window, true); + window_set_background_color(self->window, GColorWhite); -void update_display_with_time(int time_left) { - if (!timer) { return; } - - static char time_text[] = "00:00:00"; + self->time_text = text_layer_create(GRect(0, 48, 144 - ACTION_BAR_WIDTH, 138)); + text_layer_set_text_color(self->time_text, GColorBlack); + text_layer_set_text_alignment(self->time_text, GTextAlignmentCenter); + text_layer_set_background_color(self->time_text, GColorClear); + text_layer_set_font(self->time_text, fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_DOSIS_WATCH_46))); + layer_add_child(window_get_root_layer(self->window), text_layer_get_layer(self->time_text)); + + self->action_bar = action_bar_layer_create(); + action_bar_layer_add_to_window(self->action_bar, self->window); + + self->inverter = inverter_layer_create(GRect(0, 0, 144, 168)); + layer_set_hidden(inverter_layer_get_layer(self->inverter), true); + layer_add_child(window_get_root_layer(self->window), inverter_layer_get_layer(self->inverter)); + + timer_display_update(self, 0); + timer_display_set_mode(self, STOPPED); + return self; +} + +void timer_display_destroy(TimerDisplay *display) { + window_destroy(display->window); + text_layer_destroy(display->time_text); + action_bar_layer_destroy(display->action_bar); + inverter_layer_destroy(display->inverter); + gbitmap_destroy(display->bitmap_play_icon); + gbitmap_destroy(display->bitmap_plus_icon); + gbitmap_destroy(display->bitmap_minus_icon); + gbitmap_destroy(display->bitmap_pause_icon); + free(display); +} + +void timer_display_update(TimerDisplay *display, int time_left) { + static char time_text[] = "00:00"; struct tm pebble_time_left = { .tm_sec = time_left%60, .tm_min = time_left/60 }; strftime(time_text, sizeof(time_text), "%M:%S", &pebble_time_left); - - text_layer_set_text(timer, time_text); + text_layer_set_text(display->time_text, time_text); } -void alert() { - vibes_short_pulse(); - TOGGLE(light_enabled); - // XXX Need to figure out how to do this in a way that doesn't cause the - // light to just stay on. - //light_enable(light_enabled); +void timer_display_set_click_config_provider(TimerDisplay *display, ClickConfigProvider provider, void *context) { + action_bar_layer_set_context(display->action_bar, context); + action_bar_layer_set_click_config_provider(display->action_bar, provider); } -void warning() { - int foreground = (flash_background ? GColorBlack : GColorWhite); - int background = (flash_background ? GColorWhite : GColorBlack); - text_layer_set_text_color(timer, foreground); - window_set_background_color(window, background); - TOGGLE(flash_background); +void timer_display_set_mode(TimerDisplay *display, TimerDisplayMode mode) { + switch (mode) { + case STOPPED: + action_bar_layer_set_icon(display->action_bar, BUTTON_ID_UP, display->bitmap_plus_icon); + action_bar_layer_set_icon(display->action_bar, BUTTON_ID_SELECT, display->bitmap_play_icon); + action_bar_layer_set_icon(display->action_bar, BUTTON_ID_DOWN, display->bitmap_minus_icon); + break; + default: + action_bar_layer_set_icon(display->action_bar, BUTTON_ID_UP, NULL); + action_bar_layer_set_icon(display->action_bar, BUTTON_ID_SELECT, display->bitmap_pause_icon); + action_bar_layer_set_icon(display->action_bar, BUTTON_ID_DOWN, NULL); + break; + } } -Window* get_window() { - return window; +void timer_display_alert(TimerDisplay *display) { + layer_set_hidden(inverter_layer_get_layer(display->inverter), true); + vibes_short_pulse(); } -void initialize_display() { - Window *old_window = window; - TextLayer *old_timer = timer; - - window = window_create(); - window_stack_push(window, true); - window_set_background_color(window, GColorBlack); - - timer = text_layer_create(GRect(14, 49, 130, 50)); - text_layer_set_text_color(timer, GColorWhite); - text_layer_set_background_color(timer, GColorClear); - text_layer_set_font(timer, fonts_get_system_font(FONT_KEY_BITHAM_42_MEDIUM_NUMBERS)); - - layer_add_child(window_get_root_layer(window), text_layer_get_layer(timer)); - text_layer_set_text(timer, "Starting..."); - - update_display_with_time((int) current_time()); - - if (old_window) { window_destroy(old_window); } - if (old_timer) { text_layer_destroy(old_timer); } +void timer_display_warning(TimerDisplay *display) { + layer_set_hidden(inverter_layer_get_layer(display->inverter), + !layer_get_hidden(inverter_layer_get_layer(display->inverter))); } -void deinitialize_display() { - if (window) { window_destroy(window); window = NULL; } - if (timer) { text_layer_destroy(timer); timer = NULL; } +void timer_display_cancel(TimerDisplay *display) { + layer_set_hidden(inverter_layer_get_layer(display->inverter), true); + vibes_cancel(); } diff --git a/src/display.h b/src/display.h index 6434cb9..eafad60 100644 --- a/src/display.h +++ b/src/display.h @@ -1,13 +1,18 @@ +#pragma once #include -#ifndef __MY_DISPLAY_H -#define __MY_DISPLAY_H +typedef struct TimerDisplay TimerDisplay; +typedef enum { + STOPPED, RUNNING +} TimerDisplayMode; -void update_display_with_time(int display); -void alert(); -void warning(); -void initialize_display(); -void deinitialize_display(); -Window* get_window(); +TimerDisplay *timer_display_create(); +void timer_display_destroy(TimerDisplay *display); -#endif \ No newline at end of file +void timer_display_set_click_config_provider(TimerDisplay *display, ClickConfigProvider provider, void *context); +void timer_display_set_mode(TimerDisplay *display, TimerDisplayMode mode); + +void timer_display_update(TimerDisplay *display, int value); +void timer_display_alert(TimerDisplay *display); +void timer_display_warning(TimerDisplay *display); +void timer_display_cancel(TimerDisplay *display); \ No newline at end of file diff --git a/src/time_handler.c b/src/time_handler.c deleted file mode 100644 index 0069a65..0000000 --- a/src/time_handler.c +++ /dev/null @@ -1,34 +0,0 @@ -#include "time_handler.h" - -timer_t time_left; - -void initialize_time_handler() { return; /*no-op*/ } -void deinitialize_time_handler() { return; /*no-op*/ } - -void add_time(timer_t amount) { - if(time_left < 99*60) { - time_left += amount - (time_left % amount); - } -} - -void subtract_time(timer_t amount) { - if(time_left > amount) { - time_left -= ( ((time_left%amount)==0) ? amount : (time_left%amount) ); - } else { - time_left = 0; - } -} - -void decrement_time() { - if(time_left != 0) { - time_left--; - } -} - -timer_t current_time() { - return time_left; -} - -void set_time(timer_t t) { - time_left = t; -} diff --git a/src/time_handler.h b/src/time_handler.h deleted file mode 100644 index b971c85..0000000 --- a/src/time_handler.h +++ /dev/null @@ -1,17 +0,0 @@ -// gives me timer_t -#include "sys/types.h" - -#ifndef __MY_TIME_HANDLER_H -#define __MY_TIME_HANDLER_H - -void add_time(timer_t amount); -void subtract_time(timer_t amount); - -timer_t current_time(); -void set_time(timer_t t); -void decrement_time(); - -void initialize_time_handler(); -void deinitialize_time_handler(); - -#endif \ No newline at end of file diff --git a/src/timer.c b/src/timer.c index d85c93c..eb516f0 100644 --- a/src/timer.c +++ b/src/timer.c @@ -1,74 +1,32 @@ #include -#include "time_handler.h" #include "display.h" +#include "controller.h" -#ifndef TOGGLE -#define TOGGLE(x) x = !x -#endif +TimerController *controller; -#define BUTTON_CLICK_TIME_SHIFT 30 -#define INITIAL_TIME 3*60 - -bool timer_running = false; - -void handle_second_tick() { - int time = current_time(); - - decrement_time(); - - if(time == 0) { - alert(); - } - - if( time < 5 && time > 0) { - warning(); - } - - update_display_with_time(time); -} - -void toggle_timer(ClickRecognizerRef recognizer, void *context) { - TOGGLE(timer_running); - if ( timer_running ) { - tick_timer_service_subscribe(SECOND_UNIT, (TickHandler) handle_second_tick); - } else { - tick_timer_service_unsubscribe(); - } -} - -void handle_up(ClickRecognizerRef recognizer, void *context) { - if( ! timer_running ) { - subtract_time(BUTTON_CLICK_TIME_SHIFT); - update_display_with_time(current_time()); +void handle_tick(struct tm *tick_time, TimeUnits units_changed); + +void tick_source_control(int enabled) { + if (enabled) { + tick_timer_service_subscribe(SECOND_UNIT, handle_tick); + } else { + tick_timer_service_unsubscribe(); } } -void handle_down(ClickRecognizerRef recognizer, void *context) { - if( ! timer_running ) { - add_time(BUTTON_CLICK_TIME_SHIFT); - update_display_with_time(current_time()); +void handle_tick(struct tm *tick_time, TimeUnits units_changed) { + if (units_changed & SECOND_UNIT) { + timer_controller_tick(controller); } } -void click_config_provider(void *context) { - window_set_click_context(BUTTON_ID_UP, context); - window_set_click_context(BUTTON_ID_SELECT, context); - window_set_click_context(BUTTON_ID_DOWN, context); - window_single_repeating_click_subscribe(BUTTON_ID_UP, 60, handle_up); - window_single_click_subscribe(BUTTON_ID_SELECT, toggle_timer); - window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 60, handle_down); -} - void handle_init() { - initialize_time_handler(); - set_time(INITIAL_TIME); - initialize_display(); - window_set_click_config_provider(get_window(), click_config_provider); + TimerDisplay *display = timer_display_create(); + controller = timer_controller_create(display, tick_source_control); } void handle_deinit(void) { - deinitialize_time_handler(); - deinitialize_display(); + timer_controller_destroy(controller); } int main(void) { diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..6f87a6f --- /dev/null +++ b/src/util.h @@ -0,0 +1,5 @@ +#pragma once + +#define TOGGLE(x) x = !x +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) \ No newline at end of file diff --git a/wscript b/wscript new file mode 100644 index 0000000..fbc4753 --- /dev/null +++ b/wscript @@ -0,0 +1,57 @@ + +# +# This file is the default set of rules to compile a Pebble project. +# +# Feel free to customize this to your needs. +# + +import os.path +try: + from sh import CommandNotFound, jshint, cat, ErrorReturnCode_2 + hint = jshint +except (ImportError, CommandNotFound): + hint = None + +top = '.' +out = 'build' + +def options(ctx): + ctx.load('pebble_sdk') + +def configure(ctx): + ctx.load('pebble_sdk') + global hint + if hint is not None: + hint = hint.bake(['--config', 'pebble-jshintrc']) + +def build(ctx): + if False and hint is not None: + try: + hint([node.abspath() for node in ctx.path.ant_glob("src/**/*.js")], _tty_out=False) # no tty because there are none in the cloudpebble sandbox. + except ErrorReturnCode_2 as e: + ctx.fatal("\nJavaScript linting failed (you can disable this in Project Settings):\n" + e.stdout) + + # Concatenate all our JS files (but not recursively), and only if any JS exists in the first place. + ctx.path.make_node('src/js/').mkdir() + js_paths = ctx.path.ant_glob(['src/*.js', 'src/**/*.js']) + if js_paths: + ctx(rule='cat ${SRC} > ${TGT}', source=js_paths, target='pebble-js-app.js') + has_js = True + else: + has_js = False + + ctx.load('pebble_sdk') + + ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), + target='pebble-app.elf') + + if os.path.exists('worker_src'): + ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), + target='pebble-worker.elf') + ctx.pbl_bundle(elf='pebble-app.elf', + worker_elf='pebble-worker.elf', + js='pebble-js-app.js' if has_js else []) + else: + ctx.pbl_bundle(elf='pebble-app.elf', + js='pebble-js-app.js' if has_js else []) +