├── .gitignore
├── README.md
├── demo_animation.gif
├── package.json
├── resources
└── images
│ ├── add_black.png
│ ├── add_white.png
│ ├── deleted_sequence.pdc
│ ├── menu_icon.png
│ ├── tick_black.png
│ ├── tick_white.png
│ └── warning.png
├── src
├── checklist.c
├── checklist.h
├── js
│ └── config.js
├── main.c
├── messaging.c
├── messaging.h
├── util.c
├── util.h
└── windows
│ ├── checklist_window.c
│ ├── checklist_window.h
│ ├── dialog_message_window.c
│ └── dialog_message_window.h
└── wscript
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode
2 |
3 | # Ignore build generated files
4 | build
5 |
6 | # Ignore waf lock file
7 | .lock-waf*
8 |
9 | # Apple nonsense
10 | .DS_Store
11 | .AppleDouble
12 | .LSOverride
13 |
14 | # Thumbnails
15 | ._*
16 |
17 | # Files that might appear in the root of a volume
18 | .DocumentRevisions-V100
19 | .fseventsd
20 | .Spotlight-V100
21 | .TemporaryItems
22 | .Trashes
23 | .VolumeIcon.icns
24 |
25 | # Directories potentially created on remote AFP share
26 | .AppleDB
27 | .AppleDesktop
28 | Network Trash Folder
29 | Temporary Items
30 | .apdisk
31 |
32 | node_modules/
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PebbleChecklist
2 |
3 |
4 |
5 | A checklist—on your watch!
6 |
7 | Thanks to the Pebble’s microphone and voice input, it’s now possible to keep track of anything on your wrist! Designed to stay out of your way, use Checklist to keep track of anything you need without needing to hold your phone.
8 |
9 | Grocery shopping will never be the same.
10 |
11 | ## Want to try it?
12 | Grab it from the Rebble/Pebble app store:
13 | https://apps.rebble.io/en_US/application/5620e876768e7ada4e00007a?section=watchapps&dev_settings=true
14 |
--------------------------------------------------------------------------------
/demo_animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/demo_animation.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.8.0",
3 | "author": "Freakified",
4 | "private": true,
5 | "dependencies": {},
6 | "keywords": [
7 | "pebble-app"
8 | ],
9 | "pebble": {
10 | "sdkVersion": "3",
11 | "resources": {
12 | "media": [
13 | {
14 | "menuIcon": true,
15 | "type": "png",
16 | "name": "IMAGE_MENU_ICON",
17 | "file": "images/menu_icon.png"
18 | },
19 | {
20 | "type": "bitmap",
21 | "name": "TICK_BLACK",
22 | "file": "images/tick_black.png"
23 | },
24 | {
25 | "type": "bitmap",
26 | "name": "TICK_WHITE",
27 | "file": "images/tick_white.png"
28 | },
29 | {
30 | "type": "bitmap",
31 | "name": "ADD_BLACK",
32 | "file": "images/add_black.png"
33 | },
34 | {
35 | "type": "raw",
36 | "name": "DELETED_SEQUENCE",
37 | "file": "images/deleted_sequence.pdc"
38 | },
39 | {
40 | "type": "bitmap",
41 | "name": "ADD_WHITE",
42 | "file": "images/add_white.png"
43 | },
44 | {
45 | "type": "bitmap",
46 | "name": "WARNING",
47 | "file": "images/warning.png"
48 | }
49 | ]
50 | },
51 | "projectType": "native",
52 | "uuid": "b938082c-c230-4b8f-847d-15b27b1f907e",
53 | "messageKeys": {
54 | "KEY_ITEMS_TO_ADD": 0
55 | },
56 | "enableMultiJS": false,
57 | "displayName": "Checklist",
58 | "watchapp": {
59 | "onlyShownOnCommunication": false,
60 | "hiddenApp": false,
61 | "watchface": false
62 | },
63 | "targetPlatforms": [
64 | "basalt",
65 | "chalk",
66 | "diorite",
67 | "emery"
68 | ],
69 | "capabilities": [
70 | "configurable"
71 | ]
72 | },
73 | "name": "Checklist"
74 | }
--------------------------------------------------------------------------------
/resources/images/add_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/add_black.png
--------------------------------------------------------------------------------
/resources/images/add_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/add_white.png
--------------------------------------------------------------------------------
/resources/images/deleted_sequence.pdc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/deleted_sequence.pdc
--------------------------------------------------------------------------------
/resources/images/menu_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/menu_icon.png
--------------------------------------------------------------------------------
/resources/images/tick_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/tick_black.png
--------------------------------------------------------------------------------
/resources/images/tick_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/tick_white.png
--------------------------------------------------------------------------------
/resources/images/warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/freakified/PebbleChecklist/637c7cae0c7261908bc611126776df9bf988ba08/resources/images/warning.png
--------------------------------------------------------------------------------
/src/checklist.c:
--------------------------------------------------------------------------------
1 | #include "checklist.h"
2 | #include "util.h"
3 |
4 | // constants
5 | #define CURRENT_CHECKLIST_DATA_VERSION 2
6 |
7 | // persistent storage keys
8 | #define PERSIST_KEY_CHECKLIST_DATA_VERSION 50
9 | #define PERSIST_KEY_CHECKLIST_LENGTH 100
10 | #define PERSIST_KEY_CHECKLIST_NUM_CHECKED 101
11 | #define PERSIST_KEY_CHECKLIST_BLOCK_FIRST 300
12 |
13 | static ChecklistItem s_checklist_items[MAX_CHECKLIST_ITEMS];
14 |
15 | static int s_checklist_length;
16 | static int s_checklist_num_checked;
17 |
18 | // storage parameters
19 | static int s_items_per_block;
20 | static int s_block_size;
21 |
22 | // "Private" functions
23 | void read_data_from_storage();
24 | void save_data_to_storage();
25 | void add_item(char *name);
26 |
27 | void checklist_init() {
28 | // determine storage params
29 | s_items_per_block = PERSIST_DATA_MAX_LENGTH / sizeof(ChecklistItem);
30 | s_block_size = sizeof(ChecklistItem) * s_items_per_block;
31 |
32 | read_data_from_storage();
33 | }
34 |
35 | void checklist_deinit() {
36 | save_data_to_storage();
37 | }
38 |
39 | // legacy support: remove in future version
40 | // the checklist will occupy storage keys from 200 to 200 + MAX_CHECKLIST_ITEMS
41 | #define PERSIST_KEY_CHECKLIST_ITEM_FIRST 200
42 |
43 | void migrate_legacy_data() {
44 | // load legacy checklist information from storage
45 | s_checklist_length = persist_read_int(PERSIST_KEY_CHECKLIST_LENGTH);
46 | s_checklist_num_checked = 0;
47 |
48 | // load the legacy checklist data from storage
49 | for(int i = 0; i < MAX_CHECKLIST_ITEMS; i++) {
50 | persist_read_data(PERSIST_KEY_CHECKLIST_ITEM_FIRST + i, &s_checklist_items[i], sizeof(ChecklistItem));
51 |
52 | if(s_checklist_items[i].is_checked) {
53 | s_checklist_num_checked++;
54 | }
55 | }
56 |
57 | // now write the data in the new format
58 | save_data_to_storage();
59 |
60 | // delete the old data
61 | for(int i = 0; i < MAX_CHECKLIST_ITEMS; i++) {
62 | persist_delete(PERSIST_KEY_CHECKLIST_ITEM_FIRST + i);
63 | }
64 | }
65 |
66 | void read_data_from_storage() {
67 | // check if migration is necessary
68 | int saved_version = persist_read_int(PERSIST_KEY_CHECKLIST_DATA_VERSION);
69 |
70 | if(saved_version < CURRENT_CHECKLIST_DATA_VERSION) {
71 | migrate_legacy_data();
72 | }
73 |
74 | // load checklist information from storage
75 | s_checklist_length = persist_read_int(PERSIST_KEY_CHECKLIST_LENGTH);
76 | s_checklist_num_checked = persist_read_int(PERSIST_KEY_CHECKLIST_NUM_CHECKED);
77 |
78 | // load the checklist by the block
79 | int num_blocks_required = s_checklist_length / s_items_per_block + 1;
80 |
81 | for(int block = 0; block < num_blocks_required; block++) {
82 | persist_read_data(PERSIST_KEY_CHECKLIST_BLOCK_FIRST + block,
83 | &s_checklist_items[block * s_items_per_block],
84 | s_block_size);
85 | }
86 | }
87 |
88 | void save_data_to_storage() {
89 | // save version info
90 | persist_write_int(PERSIST_KEY_CHECKLIST_DATA_VERSION, CURRENT_CHECKLIST_DATA_VERSION);
91 |
92 | // save checklist information
93 | persist_write_int(PERSIST_KEY_CHECKLIST_LENGTH, s_checklist_length);
94 | persist_write_int(PERSIST_KEY_CHECKLIST_NUM_CHECKED , s_checklist_num_checked);
95 |
96 | // save the rest of the checklist
97 | // calculate how many persist blocks we'll need
98 | int num_blocks_required = s_checklist_length / s_items_per_block + 1;
99 |
100 | for(int block = 0; block < num_blocks_required; block++) {
101 | persist_write_data(PERSIST_KEY_CHECKLIST_BLOCK_FIRST + block,
102 | &s_checklist_items[block * s_items_per_block],
103 | s_block_size);
104 | }
105 | }
106 |
107 | int checklist_get_num_items() {
108 | return s_checklist_length;
109 | }
110 |
111 | int checklist_get_num_items_checked() {
112 | return s_checklist_num_checked;
113 | }
114 |
115 | void checklist_add_items(char *name) {
116 | // strncpy(line, name, MAX_NAME_LENGTH - 1);
117 | char line[MAX_NAME_LENGTH], token[MAX_NAME_LENGTH];
118 |
119 | while(name != NULL) {
120 | // set "token" to the next word
121 | name = strwrd(name, token, sizeof(token), ".,");
122 |
123 | // add the word
124 | add_item(token);
125 | }
126 | }
127 |
128 | void add_item(char *name) {
129 | name = capitalize(trim_whitespace(name));
130 |
131 | if(s_checklist_length < MAX_CHECKLIST_ITEMS && strlen(name) > 0) {
132 | strncpy(s_checklist_items[s_checklist_length].name, name, MAX_NAME_LENGTH - 1);
133 | s_checklist_items[s_checklist_length].is_checked = false;
134 | s_checklist_items[s_checklist_length].sublist_id = 0;
135 |
136 | // capitalize the item
137 | // s_checklist_items[s_checklist_length].name[0] = toupper(int);
138 |
139 | s_checklist_length++;
140 | } else {
141 | APP_LOG(APP_LOG_LEVEL_WARNING, "Failed to add checklist item; list exceeded maximum size.");
142 | }
143 | }
144 |
145 | void checklist_item_toggle_checked(int id) {
146 | s_checklist_items[id].is_checked = !(s_checklist_items[id].is_checked);
147 |
148 | if(s_checklist_items[id].is_checked) {
149 | s_checklist_num_checked++;
150 | } else {
151 | s_checklist_num_checked--;
152 | }
153 |
154 | // save the edited item to persist
155 | // save_data_to_storage();
156 |
157 | // printf("Num items checked: %i, Num items: %i", checklist_get_num_items_checked(), checklist_get_num_items());
158 | }
159 |
160 | int checklist_delete_completed_items() {
161 | // Clear the completed items
162 | int num_deleted = 0;
163 |
164 | int i = 0;
165 |
166 | while (i < s_checklist_length) {
167 | if(s_checklist_items[i].is_checked) { // is the item checked?
168 | // delete the item by shuffling the array backwards
169 | memmove(&s_checklist_items[i], &s_checklist_items[i+1], sizeof(s_checklist_items[0])*(s_checklist_length - i));
170 |
171 | num_deleted++;
172 | s_checklist_length--;
173 | } else {
174 | i++;
175 | }
176 | }
177 |
178 | s_checklist_num_checked -= num_deleted;
179 |
180 | // save_data_to_storage();
181 |
182 | return num_deleted;
183 | }
184 |
185 | ChecklistItem *checklist_get_item_by_id(int id) {
186 | return &s_checklist_items[id];
187 | }
188 |
--------------------------------------------------------------------------------
/src/checklist.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #define MAX_NAME_LENGTH 50
7 | #define MAX_CHECKLIST_ITEMS 52
8 |
9 | typedef struct ChecklistItem {
10 | // the name displayed for the item
11 | char name[MAX_NAME_LENGTH];
12 |
13 | // is the item checked?
14 | bool is_checked;
15 |
16 | // reserved for future use
17 | uint8_t sublist_id;
18 | } ChecklistItem;
19 |
20 | extern void checklist_init();
21 | extern void checklist_deinit();
22 |
23 | /*
24 | * Returns the total number of checklist items
25 | */
26 | extern int checklist_get_num_items();
27 |
28 | /*
29 | * Returns the total number of checked items
30 | */
31 | extern int checklist_get_num_items_checked();
32 |
33 | /*
34 | * Adds one or more items to the list.
35 | * Each item is identified by splitting the "name" string by a specific character
36 | */
37 | extern void checklist_add_items(char *name);
38 |
39 | /*
40 | * Toggles whether or not the specified item is checked
41 | */
42 | extern void checklist_item_toggle_checked(int id);
43 |
44 | /*
45 | * Deletes all completed items from the checklist
46 | * Returns the number of items that aren't deleted
47 | */
48 | extern int checklist_delete_completed_items();
49 |
50 | /*
51 | * Returns the checklist item referred to by the given id
52 | */
53 | extern ChecklistItem* checklist_get_item_by_id(int id);
54 |
--------------------------------------------------------------------------------
/src/js/config.js:
--------------------------------------------------------------------------------
1 | // var BASE_CONFIG_URL = 'http://localhost:4000/';
2 | // var BASE_CONFIG_URL = 'http://192.168.0.103:4000/';
3 | var BASE_CONFIG_URL = 'http://freakified.github.io/PebbleChecklist/';
4 |
5 | Pebble.addEventListener('ready', function(e) {
6 | console.log('JS component loaded!');
7 | });
8 |
9 | // open the config page when requested
10 | Pebble.addEventListener('showConfiguration', function(e) {
11 | var configURL = BASE_CONFIG_URL + 'config.html';
12 |
13 | Pebble.openURL(configURL);
14 | });
15 |
16 | // react to the config page when new data is sent
17 | Pebble.addEventListener('webviewclosed', function(e) {
18 | var configData = decodeURIComponent(e.response);
19 |
20 | if(configData) {
21 | configData = JSON.parse(decodeURIComponent(e.response));
22 |
23 | console.log("Config data recieved!" + JSON.stringify(configData));
24 |
25 | // prepare a structure to hold everything we'll send to the watch
26 | var dict = {};
27 |
28 | // color settings
29 | if(configData.itemsToAdd) {
30 | dict.KEY_ITEMS_TO_ADD = configData.itemsToAdd;
31 | }
32 |
33 | console.log('Preparing message: ', JSON.stringify(dict));
34 |
35 | // Send settings to Pebble watchapp
36 | Pebble.sendAppMessage(dict, function(){
37 | console.log('Sent config data to Pebble');
38 | }, function() {
39 | console.log('Failed to send config data!');
40 | });
41 | } else {
42 | console.log("No settings changed!");
43 | }
44 | });
45 |
--------------------------------------------------------------------------------
/src/main.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include "windows/checklist_window.h"
3 | #include "messaging.h"
4 |
5 | static Window *s_main_window;
6 |
7 | static void init() {
8 | checklist_window_push();
9 | messaging_init(checklist_window_refresh);
10 | }
11 |
12 | static void deinit() {
13 | window_destroy(s_main_window);
14 | }
15 |
16 | int main() {
17 | init();
18 | app_event_loop();
19 | deinit();
20 | }
21 |
--------------------------------------------------------------------------------
/src/messaging.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include "messaging.h"
3 | #include "checklist.h"
4 |
5 | static char s_items_to_add_buffer[512];
6 |
7 | void (*message_processed_callback)(void);
8 |
9 | void messaging_init(void (*processed_callback)(void)) {
10 | // register my custom callback
11 | message_processed_callback = processed_callback;
12 |
13 | // Register callbacks
14 | app_message_register_inbox_received(inbox_received_callback);
15 | app_message_register_inbox_dropped(inbox_dropped_callback);
16 | app_message_register_outbox_failed(outbox_failed_callback);
17 | app_message_register_outbox_sent(outbox_sent_callback);
18 |
19 | // Open AppMessage
20 | app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());
21 |
22 | APP_LOG(APP_LOG_LEVEL_DEBUG, "Watch messaging is started!");
23 | app_message_register_inbox_received(inbox_received_callback);
24 | }
25 |
26 | void inbox_received_callback(DictionaryIterator *iterator, void *context) {
27 | // does this message contain weather information?
28 | Tuple *items_to_add_tuple = dict_find(iterator, KEY_ITEMS_TO_ADD);
29 |
30 | if(items_to_add_tuple != NULL) {
31 | strncpy(s_items_to_add_buffer, items_to_add_tuple->value->cstring, sizeof(s_items_to_add_buffer) - 1);
32 |
33 | checklist_add_items(s_items_to_add_buffer);
34 | }
35 |
36 | // notify the main screen, in case something changed
37 | message_processed_callback();
38 | }
39 |
40 | void inbox_dropped_callback(AppMessageResult reason, void *context) {
41 | APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped!");
42 | }
43 |
44 | void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) {
45 | APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox send failed! %d %d %d", reason, APP_MSG_SEND_TIMEOUT, APP_MSG_SEND_REJECTED);
46 |
47 | }
48 |
49 | void outbox_sent_callback(DictionaryIterator *iterator, void *context) {
50 | APP_LOG(APP_LOG_LEVEL_INFO, "Outbox send success!");
51 | }
52 |
--------------------------------------------------------------------------------
/src/messaging.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 |
4 | #define KEY_ITEMS_TO_ADD 0
5 |
6 | void messaging_init(void (*message_processed_callback)(void));
7 | void inbox_received_callback(DictionaryIterator *iterator, void *context);
8 | void inbox_dropped_callback(AppMessageResult reason, void *context);
9 | void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context);
10 | void outbox_sent_callback(DictionaryIterator *iterator, void *context);
11 |
--------------------------------------------------------------------------------
/src/util.c:
--------------------------------------------------------------------------------
1 | #include "util.h"
2 |
3 | bool menu_layer_menu_index_selected(MenuLayer *menu_layer, MenuIndex *index) {
4 | MenuIndex selected = menu_layer_get_selected_index(menu_layer);
5 | return selected.row == index->row && selected.section == index->section;
6 | }
7 |
8 | /*
9 | * Returns if a given character counts as whitespace
10 | */
11 | char is_space(char c) {
12 | return (c == ' ' || c == '\t' || c == '\n');
13 | }
14 |
15 | char *capitalize(char *str) {
16 | // capitalize the first letter
17 | (*str) = toupper((int)(*str));
18 |
19 | return str;
20 | }
21 |
22 | /*
23 | * Trims the whitespace from the specified string.
24 | * From: http://stackoverflow.com/questions/122616/how-do-i-trim-leading-trailing-whitespace-in-a-standard-way
25 | */
26 | char* trim_whitespace(char *str) {
27 | char *end;
28 |
29 | // Trim leading space
30 | while(is_space(*str)) str++;
31 |
32 | if(*str == 0) // All spaces?
33 | return str;
34 |
35 | // Trim trailing space
36 | end = str + strlen(str) - 1;
37 | while(end > str && is_space(*end)) end--;
38 |
39 | // Write new null terminator
40 | *(end+1) = 0;
41 |
42 | return str;
43 | }
44 |
45 | /* find the next word starting at 's', delimited by characters
46 | * in the string 'delim', and store up to 'len' bytes into *buf
47 | * returns pointer to immediately after the word, or NULL if done.
48 | * From http://pjd-notes.blogspot.com/2011/09/alternative-to-strtok3-in-c.html
49 | */
50 | char *strwrd(char *s, char *buf, size_t len, char *delim) {
51 | s += strspn(s, delim);
52 | unsigned int n = strcspn(s, delim); /* count the span (spn) of bytes in */
53 |
54 | if (len-1 < n) {
55 | n = len-1;
56 | } /* the complement (c) of *delim */
57 |
58 | memcpy(buf, s, n);
59 | buf[n] = 0;
60 | s += n;
61 | return (*s == 0) ? NULL : s;
62 | }
63 |
--------------------------------------------------------------------------------
/src/util.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | bool menu_layer_menu_index_selected(MenuLayer *menu_layer, MenuIndex *index);
7 |
8 | // various string handling functions to make up for the lack of a working strtok
9 |
10 | char is_space(char c);
11 | char *capitalize(char *str);
12 | char *trim_whitespace(char *str);
13 | char *strwrd(char *s, char *buf, size_t len, char *delim);
14 |
--------------------------------------------------------------------------------
/src/windows/checklist_window.c:
--------------------------------------------------------------------------------
1 | /**
2 | * The main checklist window, showing the the list of items with their associated checkboxes.
3 | */
4 |
5 | #include "checklist_window.h"
6 | #include "dialog_message_window.h"
7 | #include "../checklist.h"
8 | #include "../util.h"
9 |
10 | static Window *s_main_window;
11 | static MenuLayer *s_menu_layer;
12 | static StatusBarLayer *s_status_bar;
13 | static TextLayer *s_empty_msg_layer;
14 |
15 | static GBitmap *s_tick_black_bitmap;
16 | static GBitmap *s_tick_white_bitmap;
17 | static GBitmap *s_add_bitmap_black;
18 | static GBitmap *s_add_bitmap_white;
19 |
20 | static GTextAttributes *s_text_att;
21 |
22 | static DictationSession *s_dictation_session;
23 |
24 | // Declare a buffer for the DictationSession
25 | static char s_last_text[512];
26 |
27 | // buffer to hold alert message
28 | static char s_deleted_msg[30];
29 |
30 | static void draw_add_button(GContext *ctx, Layer *cell_layer) {
31 | GRect bounds = layer_get_bounds(cell_layer);
32 | GRect bitmap_bounds = gbitmap_get_bounds(s_add_bitmap_black);
33 |
34 | GPoint pos;
35 | pos.x = (bounds.size.w / 2) - (bitmap_bounds.size.w / 2);
36 | pos.y = (bounds.size.h / 2) - (bitmap_bounds.size.h / 2);
37 |
38 | graphics_context_set_compositing_mode(ctx, GCompOpSet);
39 |
40 | GBitmap *imageToUse = s_tick_black_bitmap;
41 |
42 | if(menu_cell_layer_is_highlighted(cell_layer)) {
43 | imageToUse = s_add_bitmap_white;
44 | } else {
45 | imageToUse = s_add_bitmap_black;
46 | }
47 |
48 | graphics_draw_bitmap_in_rect(ctx, imageToUse, GRect(pos.x, pos.y, bitmap_bounds.size.w, bitmap_bounds.size.h));
49 | }
50 |
51 | static void dictation_session_callback(DictationSession *session, DictationSessionStatus status,
52 | char *transcription, void *context) {
53 |
54 | // Print the results of a transcription attempt
55 | APP_LOG(APP_LOG_LEVEL_INFO, "Dictation status: %d", (int)status);
56 |
57 | if(status == DictationSessionStatusSuccess) {
58 | checklist_add_items(transcription);
59 | menu_layer_reload_data(s_menu_layer);
60 | }
61 | }
62 |
63 | static uint16_t get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *context) {
64 | if(checklist_get_num_items() == 0) {
65 | return 1;
66 | } else {
67 | if(checklist_get_num_items_checked() > 0) {
68 | return checklist_get_num_items() + 2;
69 | } else {
70 | return checklist_get_num_items() + 1;
71 | }
72 | }
73 | }
74 |
75 | static void draw_checkbox_cell(GContext *ctx, Layer *cell_layer, MenuIndex *cell_index) {
76 | // draw a checklist item
77 | int id = checklist_get_num_items() - (cell_index->row - 1) - 1;
78 |
79 | ChecklistItem *item = checklist_get_item_by_id(id);
80 |
81 | GRect bounds = layer_get_bounds(cell_layer);
82 |
83 | GRect text_bounds;
84 | GTextAlignment alignment = GTextAlignmentLeft;
85 |
86 | if(item->is_checked) {
87 | if(menu_cell_layer_is_highlighted(cell_layer)) {
88 | graphics_context_set_text_color(ctx, GColorLimerick);
89 | } else {
90 | graphics_context_set_text_color(ctx, GColorArmyGreen);
91 | }
92 | } else {
93 | if(menu_cell_layer_is_highlighted(cell_layer)) {
94 | graphics_context_set_text_color(ctx, GColorWhite);
95 | } else {
96 | graphics_context_set_text_color(ctx, GColorBlack);
97 | }
98 | }
99 |
100 | // for single-height cells, use a standard draw command
101 | if(bounds.size.h == CHECKLIST_CELL_MIN_HEIGHT) {
102 | menu_cell_basic_draw(ctx, cell_layer, item->name, NULL, NULL);
103 | } else {
104 | // on round watches, single line cells should always be center aligned with no margin,
105 | // (since anything else looks bad)
106 | #ifdef PBL_ROUND
107 | text_bounds = GRect(CHECKLIST_CELL_MARGIN, 0,
108 | bounds.size.w - CHECKLIST_WINDOW_BOX_SIZE * 4,
109 | bounds.size.h);
110 | #else
111 | text_bounds = GRect(CHECKLIST_CELL_MARGIN, 0,
112 | bounds.size.w - CHECKLIST_CELL_MARGIN * 2 - CHECKLIST_WINDOW_BOX_SIZE * 2,
113 | bounds.size.h);
114 | #endif
115 |
116 | graphics_draw_text(ctx, item->name,
117 | fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD),
118 | text_bounds,
119 | GTextOverflowModeTrailingEllipsis,
120 | alignment,
121 | s_text_att);
122 |
123 | }
124 |
125 | if(menu_cell_layer_is_highlighted(cell_layer)) {
126 | graphics_context_set_stroke_color(ctx, GColorWhite);
127 | }
128 |
129 | GRect bitmap_bounds = gbitmap_get_bounds(s_tick_black_bitmap);
130 |
131 | GBitmap *imageToUse = s_tick_black_bitmap;
132 |
133 | if(menu_cell_layer_is_highlighted(cell_layer)) {
134 | graphics_context_set_stroke_color(ctx, GColorWhite);
135 | imageToUse = s_tick_white_bitmap;
136 | }
137 |
138 | // on round watches, only show the checkbox for the selected item
139 | bool show_checkbox = true;
140 |
141 | #ifdef PBL_ROUND
142 | show_checkbox = menu_cell_layer_is_highlighted(cell_layer);
143 | #endif
144 |
145 | if(show_checkbox) {
146 | GRect r = GRect(
147 | bounds.size.w - (2 * CHECKLIST_WINDOW_BOX_SIZE),
148 | (bounds.size.h / 2) - (CHECKLIST_WINDOW_BOX_SIZE / 2),
149 | CHECKLIST_WINDOW_BOX_SIZE,
150 | CHECKLIST_WINDOW_BOX_SIZE
151 | );
152 |
153 | graphics_draw_rect(ctx, r);
154 |
155 | if(item->is_checked) {
156 | // draw the checkmark
157 | graphics_context_set_compositing_mode(ctx, GCompOpSet);
158 | graphics_draw_bitmap_in_rect(ctx, imageToUse, GRect(r.origin.x, r.origin.y - 3, bitmap_bounds.size.w, bitmap_bounds.size.h));
159 | }
160 | }
161 |
162 |
163 | // draw text strikethrough
164 | if(item->is_checked) {
165 | graphics_context_set_stroke_width(ctx, 2);
166 |
167 | GPoint strike_start_point, strike_end_point;
168 |
169 | strike_start_point.y = bounds.size.h / 2;
170 | strike_end_point.y = bounds.size.h / 2;
171 |
172 | // for single-height cells, draw a true strikethrough
173 | if(bounds.size.h == CHECKLIST_CELL_MIN_HEIGHT) {
174 | GSize text_size = graphics_text_layout_get_content_size(item->name,
175 | fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD),
176 | bounds,
177 | GTextOverflowModeTrailingEllipsis,
178 | PBL_IF_ROUND_ELSE(GTextAlignmentCenter, GTextAlignmentLeft));
179 |
180 | // draw centered for round, left-aligned for rect
181 | #ifdef PBL_ROUND
182 | strike_start_point.x = (bounds.size.w / 2) - (text_size.w / 2);
183 | strike_end_point.x = (bounds.size.w / 2) + (text_size.w / 2);
184 | #else
185 | strike_start_point.x = CHECKLIST_CELL_MARGIN;
186 | strike_end_point.x = CHECKLIST_CELL_MARGIN + text_size.w;
187 | #endif
188 |
189 | } else {
190 | // otherwise, draw a full-width line
191 |
192 | #ifdef PBL_ROUND
193 | strike_start_point.x = text_bounds.origin.x + CHECKLIST_CELL_MARGIN;
194 | strike_end_point.x = text_bounds.origin.x + text_bounds.size.w - CHECKLIST_CELL_MARGIN;
195 | #else
196 | strike_start_point.x = CHECKLIST_CELL_MARGIN;
197 | strike_end_point.x = CHECKLIST_CELL_MARGIN + text_bounds.size.w;
198 | #endif
199 | }
200 |
201 | graphics_draw_line(ctx, strike_start_point, strike_end_point);
202 | }
203 | }
204 |
205 | static void draw_row_callback(GContext *ctx, Layer *cell_layer, MenuIndex *cell_index, void *context) {
206 | layer_set_hidden(text_layer_get_layer(s_empty_msg_layer), (checklist_get_num_items() != 0));
207 |
208 | if(cell_index->row == 0) {
209 | // draw the add action
210 | draw_add_button(ctx, cell_layer);
211 | } else if(cell_index->row == checklist_get_num_items() + 1) {
212 | // draw the clear action
213 | menu_cell_basic_draw(ctx, cell_layer, "Clear completed", NULL, NULL);
214 | } else {
215 | // draw the checkbox
216 | draw_checkbox_cell(ctx, cell_layer, cell_index);
217 | }
218 |
219 | }
220 |
221 | static int16_t get_cell_height_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *callback_context) {
222 | if(cell_index->row == 0 || cell_index->row == checklist_get_num_items() + 1) {
223 | return CHECKLIST_CELL_MIN_HEIGHT;
224 | } else {
225 | int id = checklist_get_num_items() - (cell_index->row - 1) - 1;
226 |
227 | ChecklistItem *item = checklist_get_item_by_id(id);
228 |
229 | int screen_width = layer_get_bounds(window_get_root_layer(s_main_window)).size.w;
230 | int width = PBL_IF_ROUND_ELSE(screen_width - CHECKLIST_WINDOW_BOX_SIZE * 4, screen_width - CHECKLIST_CELL_MARGIN * 2 - CHECKLIST_WINDOW_BOX_SIZE * 2);
231 |
232 | GSize size = graphics_text_layout_get_content_size_with_attributes(item->name,
233 | fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD),
234 | GRect(0, 0, width, 500),
235 | GTextOverflowModeTrailingEllipsis,
236 | PBL_IF_ROUND_ELSE(GTextAlignmentCenter, GTextAlignmentLeft),
237 | NULL);
238 |
239 | if(size.h > CHECKLIST_CELL_MAX_HEIGHT) {
240 | return CHECKLIST_CELL_MAX_HEIGHT;
241 | } else if(size.h < CHECKLIST_CELL_MIN_HEIGHT) {
242 | return CHECKLIST_CELL_MIN_HEIGHT;
243 | } else {
244 | return size.h + CHECKLIST_CELL_MARGIN * 2;
245 | }
246 | }
247 | }
248 |
249 | static void select_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *callback_context) {
250 | if(cell_index->row == 0) {
251 | // the first row is always the "add" button
252 | if(s_dictation_session != NULL) {
253 | dictation_session_start(s_dictation_session);
254 | } else {
255 | dialog_warning_window_push("Voice is offline, add items via the settings page.");
256 | }
257 | } else if(cell_index->row == checklist_get_num_items() + 1) {
258 | // the last row is always the "clear completed" button
259 | int num_deleted = checklist_get_num_items_checked();
260 |
261 | // generate and display "items deleted" message
262 | snprintf(s_deleted_msg,
263 | sizeof(s_deleted_msg),
264 | ((num_deleted == 1) ? "%i Item Deleted" : "%i Items Deleted"),
265 | num_deleted);
266 |
267 | // do stuff
268 | dialog_shred_window_push(s_deleted_msg);
269 | checklist_delete_completed_items();
270 | menu_layer_reload_data(menu_layer);
271 |
272 | } else {
273 | // if the item is a checklist item, toggle its checked state
274 | // get the id number of the checklist item to delete
275 | int id = checklist_get_num_items() - (cell_index->row - 1) - 1;
276 |
277 | checklist_item_toggle_checked(id);
278 |
279 | menu_layer_reload_data(menu_layer);
280 | }
281 | }
282 |
283 | static void window_load(Window *window) {
284 | checklist_init();
285 |
286 | Layer *window_layer = window_get_root_layer(window);
287 | GRect windowBounds = layer_get_bounds(window_layer);;
288 |
289 | #ifdef PBL_ROUND
290 | GRect bounds = windowBounds;
291 | #else
292 | GRect bounds = GRect(0, STATUS_BAR_LAYER_HEIGHT, windowBounds.size.w, windowBounds.size.h - STATUS_BAR_LAYER_HEIGHT);
293 | #endif
294 |
295 | s_text_att = graphics_text_attributes_create();
296 |
297 | #ifdef PBL_ROUND
298 | graphics_text_attributes_enable_screen_text_flow(s_text_att, CHECKLIST_CELL_MARGIN * 2);
299 | #endif
300 |
301 | s_tick_black_bitmap = gbitmap_create_with_resource(RESOURCE_ID_TICK_BLACK);
302 | s_tick_white_bitmap = gbitmap_create_with_resource(RESOURCE_ID_TICK_WHITE);
303 | s_add_bitmap_black = gbitmap_create_with_resource(RESOURCE_ID_ADD_BLACK);
304 | s_add_bitmap_white = gbitmap_create_with_resource(RESOURCE_ID_ADD_WHITE);
305 |
306 | s_menu_layer = menu_layer_create(bounds);
307 | menu_layer_set_click_config_onto_window(s_menu_layer, window);
308 | menu_layer_set_center_focused(s_menu_layer, PBL_IF_ROUND_ELSE(true, false));
309 | menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks) {
310 | .get_num_rows = (MenuLayerGetNumberOfRowsInSectionsCallback)get_num_rows_callback,
311 | .draw_row = (MenuLayerDrawRowCallback)draw_row_callback,
312 | .get_cell_height = (MenuLayerGetCellHeightCallback)get_cell_height_callback,
313 | .select_click = (MenuLayerSelectCallback)select_callback,
314 | });
315 |
316 | window_set_background_color(window, BG_COLOR);
317 | menu_layer_set_normal_colors(s_menu_layer, BG_COLOR, GColorBlack);
318 | menu_layer_set_highlight_colors(s_menu_layer, GColorArmyGreen, GColorWhite);
319 |
320 | layer_add_child(window_layer, menu_layer_get_layer(s_menu_layer));
321 |
322 | s_status_bar = status_bar_layer_create();
323 | layer_add_child(window_layer, status_bar_layer_get_layer(s_status_bar));
324 |
325 | status_bar_layer_set_colors(s_status_bar, BG_COLOR, GColorBlack);
326 |
327 | // Create dictation session
328 | s_dictation_session = dictation_session_create(sizeof(s_last_text),
329 | dictation_session_callback, NULL);
330 |
331 | s_empty_msg_layer = text_layer_create(PBL_IF_ROUND_ELSE(
332 | GRect(0, bounds.size.h / 2 + 40, bounds.size.w, bounds.size.h),
333 | GRect(0, bounds.size.h / 2 + 25, bounds.size.w, bounds.size.h)
334 | ));
335 |
336 | text_layer_set_text(s_empty_msg_layer, "No items");
337 | text_layer_set_background_color(s_empty_msg_layer, GColorClear);
338 | text_layer_set_text_alignment(s_empty_msg_layer, GTextAlignmentCenter);
339 | text_layer_set_font(s_empty_msg_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD));
340 | layer_add_child(window_layer, text_layer_get_layer(s_empty_msg_layer));
341 |
342 | }
343 |
344 | static void window_unload(Window *window) {
345 | checklist_deinit();
346 |
347 | graphics_text_attributes_destroy(s_text_att);
348 |
349 | menu_layer_destroy(s_menu_layer);
350 | status_bar_layer_destroy(s_status_bar);
351 | text_layer_destroy(s_empty_msg_layer);
352 | dictation_session_destroy(s_dictation_session);
353 |
354 | gbitmap_destroy(s_tick_black_bitmap);
355 | gbitmap_destroy(s_tick_white_bitmap);
356 | gbitmap_destroy(s_add_bitmap_black);
357 | gbitmap_destroy(s_add_bitmap_white);
358 |
359 | window_destroy(window);
360 | s_main_window = NULL;
361 | }
362 |
363 | void checklist_window_push() {
364 | if(!s_main_window) {
365 | s_main_window = window_create();
366 | window_set_window_handlers(s_main_window, (WindowHandlers) {
367 | .load = window_load,
368 | .unload = window_unload,
369 | });
370 | }
371 | window_stack_push(s_main_window, true);
372 | }
373 |
374 | void checklist_window_refresh() {
375 | if(s_menu_layer != NULL) {
376 | menu_layer_reload_data(s_menu_layer);
377 | }
378 | }
379 |
--------------------------------------------------------------------------------
/src/windows/checklist_window.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | #define CHECKLIST_WINDOW_MAX_ITEMS 25
6 | #define CHECKLIST_WINDOW_BOX_SIZE 12
7 | #define CHECKLIST_CELL_MIN_HEIGHT PBL_IF_ROUND_ELSE(49, 45)
8 | #define CHECKLIST_CELL_MAX_HEIGHT 82
9 | #define CHECKLIST_CELL_MARGIN 5
10 |
11 |
12 | #define BG_COLOR PBL_IF_COLOR_ELSE(GColorYellow, GColorWhite)
13 |
14 |
15 | void checklist_window_push();
16 | void checklist_window_refresh();
17 |
--------------------------------------------------------------------------------
/src/windows/dialog_message_window.c:
--------------------------------------------------------------------------------
1 | /**
2 | * Example implementation of the dialog message UI pattern.
3 | */
4 |
5 | #include "windows/dialog_message_window.h"
6 |
7 | #define MARGIN 10
8 | #define DELTA 13
9 |
10 | static Window *s_main_window;
11 | static TextLayer *s_label_layer;
12 | static Layer *s_background_layer;
13 | static Layer *s_canvas_layer;
14 | static GDrawCommandSequence *s_command_seq;
15 | static AppTimer *s_timer;
16 | static char *s_message_text;
17 | static int s_current_frame_idx = 0;
18 |
19 | typedef enum {WARNING, SHRED} DialogType;
20 | static DialogType s_dialog_type;
21 |
22 | static GBitmap *s_icon_bitmap;
23 | static BitmapLayer *s_icon_layer;
24 | static GColor s_background_color;
25 |
26 | // animation code
27 | static void next_frame_handler(void *context) {
28 | // Draw the next frame
29 | layer_mark_dirty(s_canvas_layer);
30 |
31 | // Continue the sequence
32 | s_timer = app_timer_register(DELTA, next_frame_handler, NULL);
33 | }
34 |
35 | static void background_update_proc(Layer *layer, GContext *ctx) {
36 | graphics_context_set_fill_color(ctx, s_background_color);
37 | graphics_fill_rect(ctx, layer_get_bounds(layer), 0, 0);
38 | }
39 |
40 | static void update_proc(Layer *layer, GContext *ctx) {
41 | // Get the next frame
42 | GDrawCommandFrame *frame = gdraw_command_sequence_get_frame_by_index(s_command_seq, s_current_frame_idx);
43 |
44 | // If another frame was found, draw it
45 | if (frame) {
46 | gdraw_command_frame_draw(ctx, s_command_seq, frame, PBL_IF_ROUND_ELSE(GPoint(20, 10), GPoint(5, 10)));
47 | }
48 |
49 | // Advance to the next frame, wrapping if neccessary
50 | int num_frames = gdraw_command_sequence_get_num_frames(s_command_seq);
51 | s_current_frame_idx++;
52 | if (s_current_frame_idx == num_frames) {
53 | //if we run out of frames, stop the animation
54 | if(s_timer != NULL) {
55 | app_timer_cancel(s_timer);
56 | }
57 | window_stack_pop(true);
58 | }
59 | }
60 |
61 | static void window_load(Window *window) {
62 | Layer *window_layer = window_get_root_layer(window);
63 | GRect bounds = layer_get_bounds(window_layer);
64 |
65 | s_background_layer = layer_create(bounds);
66 | layer_set_update_proc(s_background_layer, background_update_proc);
67 | layer_add_child(window_layer, s_background_layer);
68 |
69 | if(s_dialog_type == SHRED) {
70 | // Create the canvas Layer
71 | s_canvas_layer = layer_create(GRect(30, 30, bounds.size.w, bounds.size.h));
72 |
73 | s_command_seq = gdraw_command_sequence_create_with_resource(RESOURCE_ID_DELETED_SEQUENCE);
74 |
75 | // Add to parent Window
76 | layer_add_child(window_layer, s_canvas_layer);
77 |
78 | // Set the LayerUpdateProc
79 | s_current_frame_idx = 0;
80 | layer_set_update_proc(s_canvas_layer, update_proc);
81 |
82 | s_timer = app_timer_register(DELTA, next_frame_handler, NULL);
83 |
84 | s_background_color = PBL_IF_COLOR_ELSE(GColorLimerick, GColorWhite);
85 |
86 | s_label_layer = text_layer_create(GRect(MARGIN, bounds.size.h / 2 + 15 + MARGIN, bounds.size.w - (2 * MARGIN), bounds.size.h));
87 | } else {
88 | s_background_color = PBL_IF_COLOR_ELSE(GColorOrange, GColorWhite);;
89 |
90 | s_icon_bitmap = gbitmap_create_with_resource(RESOURCE_ID_WARNING);
91 | GRect bitmap_bounds = gbitmap_get_bounds(s_icon_bitmap);
92 |
93 | s_icon_layer = bitmap_layer_create(GRect(bounds.size.w / 2 - bitmap_bounds.size.w / 2,
94 | MARGIN * 3, bitmap_bounds.size.w, bitmap_bounds.size.h));
95 |
96 | bitmap_layer_set_bitmap(s_icon_layer, s_icon_bitmap);
97 | bitmap_layer_set_compositing_mode(s_icon_layer, GCompOpSet);
98 | layer_add_child(window_layer, bitmap_layer_get_layer(s_icon_layer));
99 |
100 | // TODO: make this less hardcoded
101 | s_label_layer = text_layer_create(GRect(MARGIN, bounds.size.h / 2 - 10, bounds.size.w - (2 * MARGIN), bounds.size.h));
102 | }
103 |
104 | text_layer_set_text(s_label_layer, s_message_text);
105 | text_layer_set_background_color(s_label_layer, GColorClear);
106 | text_layer_set_text_alignment(s_label_layer, GTextAlignmentCenter);
107 | text_layer_set_font(s_label_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD));
108 | layer_add_child(window_layer, text_layer_get_layer(s_label_layer));
109 |
110 | // Start the animation
111 | }
112 |
113 | static void window_unload(Window *window) {
114 | if(s_canvas_layer != NULL) {
115 | layer_destroy(s_canvas_layer);
116 | }
117 |
118 | if(s_label_layer != NULL) {
119 | text_layer_destroy(s_label_layer);
120 | }
121 |
122 | if(s_icon_bitmap != NULL) {
123 | gbitmap_destroy(s_icon_bitmap);
124 | }
125 |
126 | if(s_icon_layer != NULL) {
127 | bitmap_layer_destroy(s_icon_layer);
128 | }
129 |
130 | if(s_command_seq != NULL) {
131 | gdraw_command_sequence_destroy(s_command_seq);
132 | }
133 |
134 | if(s_timer != NULL) {
135 | app_timer_cancel(s_timer);
136 | }
137 |
138 | layer_destroy(s_background_layer);
139 | window_destroy(window);
140 | s_main_window = NULL;
141 | }
142 |
143 | void dialog_shred_window_push(char *message) {
144 | s_dialog_type = SHRED;
145 | s_message_text = message;
146 |
147 | if(!s_main_window) {
148 | s_main_window = window_create();
149 | window_set_background_color(s_main_window, GColorBlack);
150 | window_set_window_handlers(s_main_window, (WindowHandlers) {
151 | .load = window_load,
152 | .unload = window_unload
153 | });
154 | }
155 |
156 | // display the window
157 | window_stack_push(s_main_window, true);
158 | }
159 |
160 | void dialog_warning_window_push(char *message) {
161 | s_dialog_type = WARNING;
162 | s_message_text = message;
163 |
164 |
165 | if(!s_main_window) {
166 | s_main_window = window_create();
167 | window_set_background_color(s_main_window, GColorBlack);
168 | window_set_window_handlers(s_main_window, (WindowHandlers) {
169 | .load = window_load,
170 | .unload = window_unload
171 | });
172 | }
173 |
174 | window_stack_push(s_main_window, true);
175 | }
176 |
--------------------------------------------------------------------------------
/src/windows/dialog_message_window.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | void dialog_shred_window_push(char *message);
6 | void dialog_warning_window_push(char *message);
7 |
--------------------------------------------------------------------------------
/wscript:
--------------------------------------------------------------------------------
1 | #
2 | # This file is the default set of rules to compile a Pebble project.
3 | #
4 | # Feel free to customize this to your needs.
5 | #
6 |
7 | import os.path
8 | try:
9 | from sh import CommandNotFound, jshint, cat, ErrorReturnCode_2
10 | hint = jshint
11 | except (ImportError, CommandNotFound):
12 | hint = None
13 |
14 | top = '.'
15 | out = 'build'
16 |
17 | def options(ctx):
18 | ctx.load('pebble_sdk')
19 |
20 | def configure(ctx):
21 | ctx.load('pebble_sdk')
22 |
23 | def build(ctx):
24 | if False and hint is not None:
25 | try:
26 | 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.
27 | except ErrorReturnCode_2 as e:
28 | ctx.fatal("\nJavaScript linting failed (you can disable this in Project Settings):\n" + e.stdout)
29 |
30 | # Concatenate all our JS files (but not recursively), and only if any JS exists in the first place.
31 | ctx.path.make_node('src/js/').mkdir()
32 | js_paths = ctx.path.ant_glob(['src/*.js', 'src/**/*.js'])
33 | if js_paths:
34 | ctx(rule='cat ${SRC} > ${TGT}', source=js_paths, target='pebble-js-app.js')
35 | has_js = True
36 | else:
37 | has_js = False
38 |
39 | ctx.load('pebble_sdk')
40 |
41 | build_worker = os.path.exists('worker_src')
42 | binaries = []
43 |
44 | for p in ctx.env.TARGET_PLATFORMS:
45 | ctx.set_env(ctx.all_envs[p])
46 | ctx.set_group(ctx.env.PLATFORM_NAME)
47 | app_elf='{}/pebble-app.elf'.format(p)
48 | ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'),
49 | target=app_elf)
50 |
51 | if build_worker:
52 | worker_elf='{}/pebble-worker.elf'.format(p)
53 | binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf})
54 | ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'),
55 | target=worker_elf)
56 | else:
57 | binaries.append({'platform': p, 'app_elf': app_elf})
58 |
59 | ctx.set_group('bundle')
60 | ctx.pbl_bundle(binaries=binaries, js='pebble-js-app.js' if has_js else [])
61 |
--------------------------------------------------------------------------------