From 96640fba507f8d4d6b16021362b5e19edcef91a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zsolt=20V=C3=A1radi?= Date: Tue, 14 Oct 2014 19:36:16 +0200 Subject: [PATCH 1/5] Minor UX changes Centered timer text. Reverted buttons to the "natural" order. Added teapot icon. --- appinfo.json | 24 ++++++++---- resources/images/tea_timer_icon.png | Bin 0 -> 1040 bytes src/display.c | 3 +- src/timer.c | 4 +- wscript | 57 ++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 resources/images/tea_timer_icon.png create mode 100644 wscript diff --git a/appinfo.json b/appinfo.json index 790831e..52ff466 100644 --- a/appinfo.json +++ b/appinfo.json @@ -1,18 +1,26 @@ { - "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": [ + { + "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/images/tea_timer_icon.png b/resources/images/tea_timer_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b6831568e13da7b09146b24360eb60e5e57d03ed GIT binary patch literal 1040 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*s3?yAI>n{URjKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WMyDrW(e>JaRrLAva+(XvvYBAadUI?^74v_iHVDg|Ns9VC^ZU3LtuD^z>C63 zIzS(C7I;J!Gca%qfiUBxyLEqnf>I@}5hcO-X(i=}MX3w{iJ5sNdVa1U3T4K6rh0}3 z249L60#y}zx;Tbd_@C{K7HUx7VgA_o?VtLzC5#9A`sSx>nRR#3Njsz6J}cSxryBOW zQdsJB=2K_q>GGQ^EH1c~2zcKzxcwt?pYYZM&ENJHWv(^6^R;8zwW~+$O~v#*n$59) wHk?k7Et3?|ab5PuPy85}Sb4q9e0O}rPo&W#< literal 0 HcmV?d00001 diff --git a/src/display.c b/src/display.c index eab2961..66b29d7 100644 --- a/src/display.c +++ b/src/display.c @@ -53,8 +53,9 @@ void initialize_display() { window_stack_push(window, true); window_set_background_color(window, GColorBlack); - timer = text_layer_create(GRect(14, 49, 130, 50)); + timer = text_layer_create(GRect(0, 30, 144, 138)); text_layer_set_text_color(timer, GColorWhite); + text_layer_set_text_alignment(timer, GTextAlignmentCenter); text_layer_set_background_color(timer, GColorClear); text_layer_set_font(timer, fonts_get_system_font(FONT_KEY_BITHAM_42_MEDIUM_NUMBERS)); diff --git a/src/timer.c b/src/timer.c index d85c93c..eede0f2 100644 --- a/src/timer.c +++ b/src/timer.c @@ -38,14 +38,14 @@ void toggle_timer(ClickRecognizerRef recognizer, void *context) { void handle_up(ClickRecognizerRef recognizer, void *context) { if( ! timer_running ) { - subtract_time(BUTTON_CLICK_TIME_SHIFT); + add_time(BUTTON_CLICK_TIME_SHIFT); update_display_with_time(current_time()); } } void handle_down(ClickRecognizerRef recognizer, void *context) { if( ! timer_running ) { - add_time(BUTTON_CLICK_TIME_SHIFT); + subtract_time(BUTTON_CLICK_TIME_SHIFT); update_display_with_time(current_time()); } } 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 []) + From 5b54cd2efcb7ad51cf564766d741f1adce7f4f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zsolt=20V=C3=A1radi?= Date: Tue, 21 Oct 2014 13:48:36 +0200 Subject: [PATCH 2/5] Resources. --- appinfo.json | 20 +++++++++++++++++ resources/images/minus_icon.png | Bin 0 -> 949 bytes resources/images/pause_icon.png | Bin 0 -> 951 bytes resources/images/play_icon.png | Bin 0 -> 975 bytes resources/images/plus_icon.png | Bin 0 -> 962 bytes src/display.c | 37 +++++++++++++++++++++++++++++--- 6 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 resources/images/minus_icon.png create mode 100644 resources/images/pause_icon.png create mode 100644 resources/images/play_icon.png create mode 100644 resources/images/plus_icon.png diff --git a/appinfo.json b/appinfo.json index 52ff466..9a9d7f8 100644 --- a/appinfo.json +++ b/appinfo.json @@ -8,6 +8,26 @@ "projectType": "native", "resources": { "media": [ + { + "file": "images/pause_icon.png", + "name": "PAUSE_ICON", + "type": "png" + }, + { + "file": "images/play_icon.png", + "name": "PLAY_ICON", + "type": "png" + }, + { + "file": "images/minus_icon.png", + "name": "MINUS_ICON", + "type": "png" + }, + { + "file": "images/plus_icon.png", + "name": "PLUS_ICON", + "type": "png" + }, { "file": "images/tea_timer_icon.png", "menuIcon": true, diff --git a/resources/images/minus_icon.png b/resources/images/minus_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8dc1db83eabd98b0b13561699cdb9f977d38c719 GIT binary patch literal 949 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh3?wzC-F*zC7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$jZRL%n;xc;tCZ1|NlRb90j8xFakp$z3fdg(A%5^9+Abs%ySro8IR|$NC65; zmAFQf1m~xflqVLYG6W=M=9TFAxrQi|8S9zq85$UTDOw0rCE@Af7$Om#oZ!NE<*CsD hMl(I51ya)|3Nm;yF>H)_B5D8%RZmwxmvv4FO#sd@GEo2k literal 0 HcmV?d00001 diff --git a/resources/images/pause_icon.png b/resources/images/pause_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..87f5b33c4ae1cfd32314cdb51bdb842af5e2ebf3 GIT binary patch literal 951 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh3?wzC-F*zC7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$jZRL%n;xc;tCZ1|NlRb90j8xFakp$z3fdg(A%5^9+Abs%ySro8IR|$NC65; zmAFQf1m~xflqVLYG6W=M=9TFAxrQi|8S9zq85$UTDOw0p<>}%WA`zaP;KCTOgD1hz jz_Me92Se-W6C4Z<=NRUGU3}p;C|o^V{an^LB{Ts5Qa?3o literal 0 HcmV?d00001 diff --git a/resources/images/play_icon.png b/resources/images/play_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3cc6b2c49847e3fb0fc8170d8419a93a3822a1aa GIT binary patch literal 975 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh3?wzC-F*zC7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$jZRL%n;xc;tCZ1|NlRb90j8xFakp$z3fdg(A%5^9+Abs%ySro8IR|$NC65; zmAFQf1m~xflqVLYG6W=M=9TFAxrQi|8S9zq85$UTDOw0rW#sAN7$OngdeTvhL4m`` zap(V~-7be+^~27Y8+=QRu-fCe=E9kTl#1HUG^Zzopr064ioYybcN literal 0 HcmV?d00001 diff --git a/resources/images/plus_icon.png b/resources/images/plus_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3129d989c8c013ca0c8acd7f61217ae7b94f43d3 GIT binary patch literal 962 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh3?wzC-F*zC7>k44ofy`glX(f`u%tWsIx;Y9 z?C1WI$jZRL%n;xc;tCZ1|NlRb90j8xFakp$z3fdg(A%5^9+Abs%ySro8IR|$NC65; zmAFQf1m~xflqVLYG6W=M=9TFAxrQi|8S9zq85$UTDOw0rrRwS87$Om#oZ!NEV<(S+ vl$YS?)CT69m<>u2yh}YE9%A5{K2eatsfc0W%Xy;ypdj{i^>bP0l+XkKmPI#l literal 0 HcmV?d00001 diff --git a/src/display.c b/src/display.c index 66b29d7..94f93e2 100644 --- a/src/display.c +++ b/src/display.c @@ -10,6 +10,11 @@ bool light_enabled = false; static Window *window = NULL; static TextLayer *timer = NULL; +static ActionBarLayer *action_bar = NULL; + +static GBitmap *bitmap_plus_icon = NULL; +static GBitmap *bitmap_play_icon = NULL; +static GBitmap *bitmap_minus_icon = NULL; void update_display_with_time(int time_left) { if (!timer) { return; } @@ -48,12 +53,25 @@ Window* get_window() { void initialize_display() { Window *old_window = window; TextLayer *old_timer = timer; - + ActionBarLayer *old_action_bar = action_bar; + + if (!bitmap_play_icon) { + bitmap_play_icon = gbitmap_create_with_resource(RESOURCE_ID_PLAY_ICON); + } + + if (!bitmap_plus_icon) { + bitmap_plus_icon = gbitmap_create_with_resource(RESOURCE_ID_PLUS_ICON); + } + + if (!bitmap_minus_icon) { + bitmap_minus_icon = gbitmap_create_with_resource(RESOURCE_ID_MINUS_ICON); + } + window = window_create(); window_stack_push(window, true); window_set_background_color(window, GColorBlack); - timer = text_layer_create(GRect(0, 30, 144, 138)); + timer = text_layer_create(GRect(0, 30, 144 - ACTION_BAR_WIDTH, 138)); text_layer_set_text_color(timer, GColorWhite); text_layer_set_text_alignment(timer, GTextAlignmentCenter); text_layer_set_background_color(timer, GColorClear); @@ -61,14 +79,27 @@ void initialize_display() { layer_add_child(window_get_root_layer(window), text_layer_get_layer(timer)); text_layer_set_text(timer, "Starting..."); - + + action_bar = action_bar_layer_create(); + action_bar_layer_set_background_color(action_bar, GColorClear); + action_bar_layer_set_icon(action_bar, BUTTON_ID_UP, bitmap_plus_icon); + action_bar_layer_set_icon(action_bar, BUTTON_ID_SELECT, bitmap_play_icon); + action_bar_layer_set_icon(action_bar, BUTTON_ID_DOWN, bitmap_minus_icon); + + action_bar_layer_add_to_window(action_bar, window); + update_display_with_time((int) current_time()); if (old_window) { window_destroy(old_window); } if (old_timer) { text_layer_destroy(old_timer); } + if (old_action_bar) { action_bar_layer_destroy(old_action_bar); } } void deinitialize_display() { if (window) { window_destroy(window); window = NULL; } if (timer) { text_layer_destroy(timer); timer = NULL; } + if (action_bar) { action_bar_layer_destroy(action_bar); action_bar = NULL; } + if (bitmap_play_icon) { gbitmap_destroy(bitmap_play_icon); bitmap_play_icon = NULL; } + if (bitmap_plus_icon) { gbitmap_destroy(bitmap_plus_icon); bitmap_plus_icon = NULL; } + if (bitmap_minus_icon) { gbitmap_destroy(bitmap_minus_icon); bitmap_minus_icon = NULL; } } From 26e1fb1fb8cfe800b9219b8fa32a10567fa018a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zsolt=20V=C3=A1radi?= Date: Tue, 21 Oct 2014 14:01:30 +0200 Subject: [PATCH 3/5] Inverted images to fix the action bar. --- resources/images/minus_icon.png | Bin 949 -> 929 bytes resources/images/pause_icon.png | Bin 951 -> 932 bytes resources/images/play_icon.png | Bin 975 -> 957 bytes resources/images/plus_icon.png | Bin 962 -> 944 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/images/minus_icon.png b/resources/images/minus_icon.png index 8dc1db83eabd98b0b13561699cdb9f977d38c719..422e5f30614593687452d5ad919370bb1eb55f80 100644 GIT binary patch delta 100 zcmdnWzL0%_%0@#K=Ey?~419+eMK!z{7#J8NN?apKg7ec#$`gxH8440J^GfvcQcDy} z^bGWjd!9`X0ICu8ba4!k2v1(1n$W_W6Ell}jg5hmjX`wf@|9nJ(hQ!ielF{r5}E+W CSREb! delta 118 zcmZ3;zLkA~3KwH>kh>GZx^prwH!7+#Cmd#A;5*ECJby(B0|SFpiEBhjaDG}zd16s2 zLqK9?UWuNcYluRbv7V`(p@G4dqJ=kh>GZx^prwH!7+#Cmm*B;5*ECJby(B0|SFpiEBhjaDG}zd16s2 zLqK9?UWuNcYluRbv7V`(p@G4dqJu)~8%Kre>j6f={E-i+o@{VAMG e8ZtHwjf@N;x(vn=QNo%)tqh*7elF{r5}E)ch9S5B delta 144 zcmV;B0B`@j2hRtPBnkm@Qb$4nuFf3ku^}e|dcyz!4#NS*Z>VGd000_vMObuGZ)S9N zVRB^vP+@6qbS_RsR3LUUE;TMOFfjCTp#T5?GD$>1R2Wx<$w3wXAP7Q1x&Nh$M8icd yR?ast_GMT)yg{tc%wS}AeTim5)V*gXHwY{}+W?bm3i8JQ0000(VIx2S;u0|Wmw2G#f# SvXgkh>GZx^prwH!7+#=N@KY;5*ECJby(B0|SFpiEBhjaDG}zd16s2 zLqK9?UWuNcYluRbv7V`(p@G4dqJ=gTe~DWM4f-7_h( From 770dfa4da1007e728492173d70470203a67ffa43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zsolt=20V=C3=A1radi?= Date: Tue, 21 Oct 2014 20:20:48 +0200 Subject: [PATCH 4/5] Cosmetic changes. --- src/display.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/display.c b/src/display.c index 94f93e2..b952b37 100644 --- a/src/display.c +++ b/src/display.c @@ -71,7 +71,7 @@ void initialize_display() { window_stack_push(window, true); window_set_background_color(window, GColorBlack); - timer = text_layer_create(GRect(0, 30, 144 - ACTION_BAR_WIDTH, 138)); + timer = text_layer_create(GRect(0, 48, 144 - ACTION_BAR_WIDTH, 138)); text_layer_set_text_color(timer, GColorWhite); text_layer_set_text_alignment(timer, GTextAlignmentCenter); text_layer_set_background_color(timer, GColorClear); From b4a21f827f89a05aaf9b8d918206649f8ee30908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zsolt=20V=C3=A1radi?= Date: Sat, 25 Oct 2014 23:45:31 +0200 Subject: [PATCH 5/5] Refactoring logic and UI, styling - Static display and counting logic moved to TimerDisplay and TimerController classes. - Finished hooking up the action bar. - Added a monospaced Dosis SemiBold font for the timer. --- appinfo.json | 20 ++- resources/fonts/Dosis-Watch.ttf | Bin 0 -> 28924 bytes .../{minus_icon.png => minus_icon_white.png} | Bin 929 -> 934 bytes .../{pause_icon.png => pause_icon_white.png} | Bin 932 -> 936 bytes .../{play_icon.png => play_icon_white.png} | Bin 957 -> 960 bytes .../{plus_icon.png => plus_icon_white.png} | Bin 944 -> 947 bytes src/controller.c | 86 +++++++++ src/controller.h | 9 + src/display.c | 167 +++++++++--------- src/display.h | 23 ++- src/time_handler.c | 34 ---- src/time_handler.h | 17 -- src/timer.c | 72 ++------ src/util.h | 5 + 14 files changed, 224 insertions(+), 209 deletions(-) create mode 100644 resources/fonts/Dosis-Watch.ttf rename resources/images/{minus_icon.png => minus_icon_white.png} (89%) rename resources/images/{pause_icon.png => pause_icon_white.png} (89%) rename resources/images/{play_icon.png => play_icon_white.png} (87%) rename resources/images/{plus_icon.png => plus_icon_white.png} (88%) create mode 100644 src/controller.c create mode 100644 src/controller.h delete mode 100644 src/time_handler.c delete mode 100644 src/time_handler.h create mode 100644 src/util.h diff --git a/appinfo.json b/appinfo.json index 9a9d7f8..139b206 100644 --- a/appinfo.json +++ b/appinfo.json @@ -9,23 +9,29 @@ "resources": { "media": [ { - "file": "images/pause_icon.png", - "name": "PAUSE_ICON", + "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.png", + "file": "images/play_icon_white.png", "name": "PLAY_ICON", "type": "png" }, { - "file": "images/minus_icon.png", - "name": "MINUS_ICON", + "file": "images/pause_icon_white.png", + "name": "PAUSE_ICON", "type": "png" }, { - "file": "images/plus_icon.png", - "name": "PLUS_ICON", + "file": "images/minus_icon_white.png", + "name": "MINUS_ICON", "type": "png" }, { diff --git a/resources/fonts/Dosis-Watch.ttf b/resources/fonts/Dosis-Watch.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e53840062b0fd0b32aa8d7e8989699e66f4cc703 GIT binary patch literal 28924 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 Xe!stS-RCI%&Z*ts5#?LH@rC~jT{qx{ literal 0 HcmV?d00001 diff --git a/resources/images/minus_icon.png b/resources/images/minus_icon_white.png similarity index 89% rename from resources/images/minus_icon.png rename to resources/images/minus_icon_white.png index 422e5f30614593687452d5ad919370bb1eb55f80..9144cad30d8ff1c76aa8b3282e9be88f85a9aa27 100644 GIT binary patch delta 68 zcmZ3;zKnf?2Q$|}1_r)^j5{V~znvV!tgb8J>Eak75uTjj!g%GW(E&y?J);Fu(bI)is1YT^vIs!jl)MCbTf;#LQw~V`JcCV-Q`r SeB~DgAnEak75uTjj!WglGC&AFbvSWt_L+j}i a91ITU80LOmeBn0(5O})!xvXbI)wRVuT^vIs!jly^BqRk@<`_PRiD_tLWDvAs V(Ckup|BnF(JYD@<);T3K0RT?95-bI)m`*GT^vIs!jlyQ4UC*51rBhq1gLQ|?C@X` u(2HR>#mpq4H={XJe+nm)hKx-^BO`-|E`zZ|l&~fP5O})!xvXEak75uTjj!gymRkAakz;OW!`=A4)f lN)o(FJsuun;F>;Bkin^lVd2YpqW%m(;OXk;vd$@?2>?oM8fgFk delta 78 zcmdnYzJYy%2Q$|p1_r)EjG`J|4U>bI)y|_QY@O1TaS?83{1OPI*6t@5X 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 b952b37..9a342ae 100644 --- a/src/display.c +++ b/src/display.c @@ -1,105 +1,102 @@ #include "display.h" -#include "time_handler.h" - -#ifndef TOGGLE -#define TOGGLE(x) x = !x -#endif - -bool flash_background = true; -bool light_enabled = false; - -static Window *window = NULL; -static TextLayer *timer = NULL; -static ActionBarLayer *action_bar = NULL; +#include "util.h" + +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; + +TimerDisplay* timer_display_create() { + TimerDisplay* self = calloc(1, sizeof(TimerDisplay)); + + 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); + + 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; +} -static GBitmap *bitmap_plus_icon = NULL; -static GBitmap *bitmap_play_icon = NULL; -static GBitmap *bitmap_minus_icon = NULL; +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 update_display_with_time(int time_left) { - if (!timer) { return; } - - static char time_text[] = "00:00:00"; +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; - ActionBarLayer *old_action_bar = action_bar; - - if (!bitmap_play_icon) { - bitmap_play_icon = gbitmap_create_with_resource(RESOURCE_ID_PLAY_ICON); - } - - if (!bitmap_plus_icon) { - bitmap_plus_icon = gbitmap_create_with_resource(RESOURCE_ID_PLUS_ICON); - } - - if (!bitmap_minus_icon) { - bitmap_minus_icon = gbitmap_create_with_resource(RESOURCE_ID_MINUS_ICON); - } - - window = window_create(); - window_stack_push(window, true); - window_set_background_color(window, GColorBlack); - - timer = text_layer_create(GRect(0, 48, 144 - ACTION_BAR_WIDTH, 138)); - text_layer_set_text_color(timer, GColorWhite); - text_layer_set_text_alignment(timer, GTextAlignmentCenter); - 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..."); - - action_bar = action_bar_layer_create(); - action_bar_layer_set_background_color(action_bar, GColorClear); - action_bar_layer_set_icon(action_bar, BUTTON_ID_UP, bitmap_plus_icon); - action_bar_layer_set_icon(action_bar, BUTTON_ID_SELECT, bitmap_play_icon); - action_bar_layer_set_icon(action_bar, BUTTON_ID_DOWN, bitmap_minus_icon); - - action_bar_layer_add_to_window(action_bar, window); - - update_display_with_time((int) current_time()); - - if (old_window) { window_destroy(old_window); } - if (old_timer) { text_layer_destroy(old_timer); } - if (old_action_bar) { action_bar_layer_destroy(old_action_bar); } +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; } - if (action_bar) { action_bar_layer_destroy(action_bar); action_bar = NULL; } - if (bitmap_play_icon) { gbitmap_destroy(bitmap_play_icon); bitmap_play_icon = NULL; } - if (bitmap_plus_icon) { gbitmap_destroy(bitmap_plus_icon); bitmap_plus_icon = NULL; } - if (bitmap_minus_icon) { gbitmap_destroy(bitmap_minus_icon); bitmap_minus_icon = 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 eede0f2..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 ) { - add_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 ) { - subtract_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