diff --git a/package.json b/package.json index 4ee6d50..c3ec7cb 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "type": "font", "name": "FONT_ICONS_28", "file": "fonts/Lilex/LilexNerdFontMono-Regular.ttf", - "characterRegex": "[ ]" + "characterRegex": "[ ]" }, { "type": "font", @@ -83,7 +83,7 @@ "targetPlatforms": [ "emery" ], - "characterRegex": "[ ]" + "characterRegex": "[ ]" }, { "type": "font", diff --git a/src/c/battery.c b/src/c/battery.c index f5fcc3c..0306dff 100644 --- a/src/c/battery.c +++ b/src/c/battery.c @@ -5,6 +5,8 @@ static TextLayer *s_battery_layer_text; static TextLayer *s_battery_layer_icon; +int BATTERY_ROW_MAX_WIDTH = 0; + /** * Update battery icon and text. */ @@ -34,25 +36,36 @@ void battery_init() { battery_state_service_subscribe(battery_update_handler); } +// WARNING: Must be called before bluetooth loads. void battery_load(Window *window, int row_height) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); // Battery icon. - s_battery_layer_icon = font_render_icon_small(window_layer, ICON_BATTERY_50, PADDING_X, 0, true, false); + s_battery_layer_icon = font_render_icon_small(window_layer, ICON_BATTERY_100, PADDING_X, 0, true, false); text_layer_set_text_color(s_battery_layer_icon, THEME.text_color); // Battery percentage. GRect battery_icon_bounds = layer_get_bounds(text_layer_get_layer(s_battery_layer_icon)); - s_battery_layer_text = - text_layer_create(GRect(battery_icon_bounds.size.w - battery_icon_bounds.size.w, 0, - bounds.size.w - battery_icon_bounds.size.w - PADDING_X - 2, row_height)); + s_battery_layer_text = text_layer_create( + GRect(battery_icon_bounds.size.w - battery_icon_bounds.size.w, 0, + bounds.size.w - battery_icon_bounds.size.w - PADDING_X - BATTERY_TEXT_RIGHT_INSET, row_height)); text_layer_set_text_alignment(s_battery_layer_text, GTextAlignmentRight); text_layer_set_font(s_battery_layer_text, s_font_primary_small); text_layer_set_text_color(s_battery_layer_text, THEME.text_color); text_layer_set_background_color(s_battery_layer_text, GColorClear); layer_add_child(window_layer, text_layer_get_layer(s_battery_layer_text)); + + // Set it to 100 so we can calculate the max width. + text_layer_set_text(s_battery_layer_text, "100"); + + // Before we call the update handler, calculate the max width of the + // battery text + icon. + GSize battery_icon_size = text_layer_get_content_size(s_battery_layer_icon); + GSize battery_text_size = text_layer_get_content_size(s_battery_layer_text); + BATTERY_ROW_MAX_WIDTH = battery_icon_size.w + battery_text_size.w; + battery_update_handler(battery_state_service_peek()); } diff --git a/src/c/battery.h b/src/c/battery.h index 0bc16b4..e67566f 100644 --- a/src/c/battery.h +++ b/src/c/battery.h @@ -2,6 +2,10 @@ #include +#define BATTERY_TEXT_RIGHT_INSET 2 + +extern int BATTERY_ROW_MAX_WIDTH; + void battery_init(); void battery_load(Window *window, int row_height); void battery_unload(); diff --git a/src/c/bluetooth.c b/src/c/bluetooth.c index f699015..d8ce4e3 100644 --- a/src/c/bluetooth.c +++ b/src/c/bluetooth.c @@ -1,4 +1,6 @@ #include "bluetooth.h" +#include "battery.h" +#include "health.h" #include "log.h" #include "pebble.h" #include "settings.h" @@ -42,7 +44,8 @@ void bluetooth_deinit(void) { connection_service_unsubscribe(); } -void bluetooth_load(Window *window) { +static void bluetooth_load_middle_right(Window *window) { + Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); @@ -55,6 +58,26 @@ void bluetooth_load(Window *window) { s_bluetooth_layer_icon = font_render_icon_xsmall(window_layer, ICON_BLUETOOTH_CONNECTED, PADDING_X, icon_y, true, false); text_layer_set_text_color(s_bluetooth_layer_icon, THEME.text_color); +} + +static void bluetooth_load_top(Window *window) { + Layer *window_layer = window_get_root_layer(window); + + int right_offset = PADDING_X + BATTERY_TEXT_RIGHT_INSET + BATTERY_ROW_MAX_WIDTH + 2; + int top_offset = 8; + + s_bluetooth_layer_icon = + font_render_icon_xsmall(window_layer, ICON_BLUETOOTH_CONNECTED, right_offset, top_offset, true, false); + text_layer_set_text_color(s_bluetooth_layer_icon, THEME.text_color); +} + +// WARNING: Must be called after battery loads. +void bluetooth_load(Window *window) { +#ifdef HEART_RATE_SUPPORTED + bluetooth_load_top(window); +#else + bluetooth_load_middle_right(window); +#endif // Refresh state in case the initial peek happened before the service was ready. bluetooth_refresh_connected_state(); diff --git a/src/c/font.c b/src/c/font.c index 6ce51fa..3632811 100644 --- a/src/c/font.c +++ b/src/c/font.c @@ -17,6 +17,7 @@ const char *ICON_BATTERY_50 = ""; const char *ICON_BATTERY_75 = ""; const char *ICON_BATTERY_100 = ""; const char *ICON_STEPS = ""; +const char *ICON_HEART_RATE = ""; const char *ICON_UTC = ""; const char *ICON_BLUETOOTH_CONNECTED = "󰂯"; const char *ICON_SUNRISE = ""; diff --git a/src/c/font.h b/src/c/font.h index 44cf4f1..855dd92 100644 --- a/src/c/font.h +++ b/src/c/font.h @@ -19,6 +19,7 @@ extern const char *ICON_BATTERY_50; extern const char *ICON_BATTERY_75; extern const char *ICON_BATTERY_100; extern const char *ICON_STEPS; +extern const char *ICON_HEART_RATE; extern const char *ICON_UTC; extern const char *ICON_BLUETOOTH_CONNECTED; extern const char *ICON_SUNRISE; diff --git a/src/c/health.c b/src/c/health.c index 0b7d073..012b53e 100644 --- a/src/c/health.c +++ b/src/c/health.c @@ -1,11 +1,28 @@ #include "health.h" #include "common.h" #include "font.h" +#include "settings.h" +#include "time.h" -TextLayer *s_steps_layer_text; -TextLayer *s_steps_layer_icon; +static TextLayer *s_steps_layer_text; +static TextLayer *s_steps_layer_icon; + +#if defined(HEART_RATE_SUPPORTED) +static TextLayer *s_heart_rate_layer_text; +static TextLayer *s_heart_rate_layer_icon; + +#if defined(PBL_PLATFORM_EMERY) +static const int HEART_RATE_Y_OFFSET = 9; +#else +static const int HEART_RATE_Y_OFFSET = 0; +#endif +#endif + +static void health_update_steps(void) { + if (!s_steps_layer_text) { + return; + } -static void health_update(void) { static char steps_buffer[16]; HealthServiceAccessibilityMask access = @@ -20,9 +37,43 @@ static void health_update(void) { text_layer_set_text(s_steps_layer_text, steps_buffer); } +static void health_update_heart_rate(void) { +#if defined(HEART_RATE_SUPPORTED) + if (!s_heart_rate_layer_text) { + return; + } + + static char heart_rate_buffer[16]; + + HealthServiceAccessibilityMask access = health_service_metric_aggregate_averaged_accessible( + HealthMetricHeartRateBPM, time(NULL), time(NULL), HealthAggregationAvg, HealthServiceTimeScopeOnce); + if (access & HealthServiceAccessibilityMaskAvailable) { + HealthValue heart_rate = health_service_peek_current_value(HealthMetricHeartRateBPM); + if (heart_rate > 0) { + snprintf(heart_rate_buffer, sizeof(heart_rate_buffer), "%ld", (long)heart_rate); + } else { + snprintf(heart_rate_buffer, sizeof(heart_rate_buffer), "--"); + } + } else { + snprintf(heart_rate_buffer, sizeof(heart_rate_buffer), "--"); + } + + text_layer_set_text(s_heart_rate_layer_text, heart_rate_buffer); +#endif +} + +static void health_update(void) { + health_update_steps(); + health_update_heart_rate(); +} + static void health_handler(HealthEventType event, void *context) { if (event == HealthEventMovementUpdate || event == HealthEventSignificantUpdate) { - health_update(); + health_update_steps(); + } + + if (event == HealthEventHeartRateUpdate || event == HealthEventSignificantUpdate) { + health_update_heart_rate(); } } @@ -35,23 +86,69 @@ void health_load(Window *window, int row_height) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); - int steps_y = row_height + 2; - s_steps_layer_icon = font_render_icon_small(window_layer, ICON_STEPS, PADDING_X, steps_y, true, false); - text_layer_set_text_color(s_steps_layer_icon, THEME.text_color); - GRect steps_icon_bounds = layer_get_bounds(text_layer_get_layer(s_steps_layer_icon)); + // --- Steps --- + + if (app_settings.show_steps) { + int steps_y = row_height + 2; + s_steps_layer_icon = font_render_icon_small(window_layer, ICON_STEPS, PADDING_X, steps_y, true, false); + text_layer_set_text_color(s_steps_layer_icon, THEME.text_color); + GRect steps_icon_bounds = layer_get_bounds(text_layer_get_layer(s_steps_layer_icon)); + + s_steps_layer_text = + text_layer_create(GRect(0, steps_y, bounds.size.w - steps_icon_bounds.size.w - PADDING_X - 2, row_height)); + text_layer_set_text_alignment(s_steps_layer_text, GTextAlignmentRight); + text_layer_set_font(s_steps_layer_text, s_font_primary_small); + text_layer_set_text_color(s_steps_layer_text, THEME.text_color); + text_layer_set_background_color(s_steps_layer_text, GColorClear); + layer_add_child(window_layer, text_layer_get_layer(s_steps_layer_text)); + } + + // --- Heart Rate --- + +#if defined(HEART_RATE_SUPPORTED) +#if defined(PBL_PLATFORM_EMERY) + int heart_rate_y = (bounds.size.h / 2) - (TIME_CONTAINER_HEIGHT / 2) - 20 - HEART_RATE_Y_OFFSET; +#else + int heart_rate_y = (bounds.size.h / 2) - (TIME_CONTAINER_HEIGHT / 2) - 14 - HEART_RATE_Y_OFFSET; +#endif + s_heart_rate_layer_icon = + font_render_icon_small(window_layer, ICON_HEART_RATE, PADDING_X, heart_rate_y, true, false); + text_layer_set_text_color(s_heart_rate_layer_icon, THEME.text_color); + GRect heart_rate_icon_bounds = layer_get_bounds(text_layer_get_layer(s_heart_rate_layer_icon)); - s_steps_layer_text = text_layer_create(GRect(steps_icon_bounds.size.w - steps_icon_bounds.size.w, steps_y, - bounds.size.w - steps_icon_bounds.size.w - PADDING_X - 2, row_height)); - text_layer_set_text_alignment(s_steps_layer_text, GTextAlignmentRight); - text_layer_set_font(s_steps_layer_text, s_font_primary_small); - text_layer_set_text_color(s_steps_layer_text, THEME.text_color); - text_layer_set_background_color(s_steps_layer_text, GColorClear); - layer_add_child(window_layer, text_layer_get_layer(s_steps_layer_text)); + s_heart_rate_layer_text = text_layer_create( + GRect(0, heart_rate_y, bounds.size.w - heart_rate_icon_bounds.size.w - PADDING_X - 2, row_height)); + text_layer_set_text_alignment(s_heart_rate_layer_text, GTextAlignmentRight); + text_layer_set_font(s_heart_rate_layer_text, s_font_primary_small); + text_layer_set_text_color(s_heart_rate_layer_text, THEME.text_color); + text_layer_set_background_color(s_heart_rate_layer_text, GColorClear); + layer_add_child(window_layer, text_layer_get_layer(s_heart_rate_layer_text)); +#endif + + health_update(); } void health_unload() { - text_layer_destroy(s_steps_layer_icon); - text_layer_destroy(s_steps_layer_text); + if (s_steps_layer_icon) { + text_layer_destroy(s_steps_layer_icon); + s_steps_layer_icon = NULL; + } + + if (s_steps_layer_text) { + text_layer_destroy(s_steps_layer_text); + s_steps_layer_text = NULL; + } +#if defined(HEART_RATE_SUPPORTED) + if (s_heart_rate_layer_icon) { + text_layer_destroy(s_heart_rate_layer_icon); + s_heart_rate_layer_icon = NULL; + } + + if (s_heart_rate_layer_text) { + text_layer_destroy(s_heart_rate_layer_text); + s_heart_rate_layer_text = NULL; + } +#endif } void health_deinit() { diff --git a/src/c/health.h b/src/c/health.h index ba97d9e..5219c04 100644 --- a/src/c/health.h +++ b/src/c/health.h @@ -2,6 +2,10 @@ #include +#if defined(PBL_PLATFORM_DIORITE) || defined(PBL_PLATFORM_EMERY) +#define HEART_RATE_SUPPORTED +#endif + void health_init(); void health_load(Window *window, int row_height); void health_unload(); diff --git a/src/c/main.c b/src/c/main.c index 15336b7..de88afd 100644 --- a/src/c/main.c +++ b/src/c/main.c @@ -16,6 +16,16 @@ static int s_last_vibrate_hour = -1; +#if defined(PBL_HEALTH) +static bool health_should_run(void) { +#if defined(HEART_RATE_SUPPORTED) + return true; +#else + return app_settings.show_steps; +#endif +} +#endif + static void tick_handler(struct tm *tick_time, TimeUnits units_changed) { LOG_DEBUG("tick handler..."); time_update(); @@ -36,7 +46,7 @@ void load_top_right(Window *window) { #endif battery_load(window, row_height); #if defined(PBL_HEALTH) - if (app_settings.show_steps) { + if (health_should_run()) { health_load(window, row_height); } #endif @@ -59,7 +69,7 @@ static void window_unload(Window *window) { bluetooth_unload(); battery_unload(); #if defined(PBL_HEALTH) - if (app_settings.show_steps) { + if (health_should_run()) { health_unload(); } #endif @@ -80,7 +90,7 @@ static void init(void) { battery_init(); bluetooth_init(); #if defined(PBL_HEALTH) - if (app_settings.show_steps) { + if (health_should_run()) { health_init(); } #endif @@ -92,7 +102,7 @@ static void deinit(void) { bluetooth_deinit(); battery_deinit(); #if defined(PBL_HEALTH) - if (app_settings.show_steps) { + if (health_should_run()) { health_deinit(); } #endif