diff --git a/package.json b/package.json index accec77..a6375d1 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,13 @@ { "name": "ForecasWatch 2", "author": "Matt Rossman", - "version": "1.14.0", - "keywords": ["pebble-app"], + "version": "1.15.0", + "keywords": [ + "pebble-app" + ], "private": true, - "dependencies": {}, + "dependencies": { + }, "pebble": { "displayName": "FCW2", "uuid": "7ed9b053-1521-4e49-be8b-6e12c68978ae", @@ -69,7 +72,11 @@ "CLAY_SHOW_BT", "CLAY_SHOW_BT_DISCONNECT", "CLAY_VIBE", - "CLAY_SHOW_AM_PM" + "CLAY_SHOW_AM_PM", + "CLAY_COLOR_SATURDAY", + "CLAY_COLOR_SUNDAY", + "CLAY_COLOR_US_FEDERAL", + "CLAY_COLOR_TIME" ] } } diff --git a/src/c/appendix/app_message.c b/src/c/appendix/app_message.c index c9efe0e..b4bbdbb 100644 --- a/src/c/appendix/app_message.c +++ b/src/c/appendix/app_message.c @@ -29,6 +29,10 @@ static void inbox_received_callback(DictionaryIterator *iterator, void *context) Tuple *clay_show_bt_tuple = dict_find(iterator, MESSAGE_KEY_CLAY_SHOW_BT); Tuple *clay_show_bt_disconnect_tuple = dict_find(iterator, MESSAGE_KEY_CLAY_SHOW_BT_DISCONNECT); Tuple *clay_show_am_pm_tuple = dict_find(iterator, MESSAGE_KEY_CLAY_SHOW_AM_PM); + Tuple *clay_color_saturday_tuple = dict_find(iterator, MESSAGE_KEY_CLAY_COLOR_SATURDAY); + Tuple *clay_color_sunday_tuple = dict_find(iterator, MESSAGE_KEY_CLAY_COLOR_SUNDAY); + Tuple *clay_color_us_federal_tuple = dict_find(iterator, MESSAGE_KEY_CLAY_COLOR_US_FEDERAL); + Tuple *clay_color_time_tuple = dict_find(iterator, MESSAGE_KEY_CLAY_COLOR_TIME); if(temp_trend_tuple && temp_trend_tuple && forecast_start_tuple && num_entries_tuple && city_tuple && sun_events_tuple) { // Weather data received @@ -54,8 +58,9 @@ static void inbox_received_callback(DictionaryIterator *iterator, void *context) forecast_layer_refresh(); weather_status_layer_refresh(); } - if (clay_celsius_tuple && clay_axis_12h_tuple && clay_start_mon_tuple && clay_prev_week_tuple && clay_color_today_tuple - && clay_show_qt_tuple && clay_show_bt_tuple && clay_show_bt_disconnect_tuple && clay_show_am_pm_tuple) { + else if (clay_celsius_tuple && clay_axis_12h_tuple && clay_start_mon_tuple && clay_prev_week_tuple && clay_color_today_tuple + && clay_show_qt_tuple && clay_show_bt_tuple && clay_show_bt_disconnect_tuple && clay_show_am_pm_tuple + && clay_color_saturday_tuple && clay_color_sunday_tuple && clay_color_us_federal_tuple && clay_color_time_tuple) { // Clay config data received bool clay_celsius = (bool) (clay_celsius_tuple->value->int16); bool time_lead_zero = (bool) (clay_time_lead_zero_tuple->value->int16); @@ -68,6 +73,10 @@ static void inbox_received_callback(DictionaryIterator *iterator, void *context) bool show_am_pm = (bool) (clay_show_am_pm_tuple->value->int16); int16_t time_font = clay_time_font_tuple->value->int16; GColor color_today = GColorFromHEX(clay_color_today_tuple->value->int32); + GColor color_saturday = GColorFromHEX(clay_color_saturday_tuple->value->int32); + GColor color_sunday = GColorFromHEX(clay_color_sunday_tuple->value->int32); + GColor color_us_federal = GColorFromHEX(clay_color_us_federal_tuple->value->int32); + GColor color_time = GColorFromHEX(clay_color_time_tuple->value->int32); Config config = (Config) { .celsius = clay_celsius, .time_lead_zero = time_lead_zero, @@ -79,11 +88,18 @@ static void inbox_received_callback(DictionaryIterator *iterator, void *context) .show_qt = show_qt, .show_bt = show_bt, .show_bt_disconnect = show_bt_disconnect, - .show_am_pm = show_am_pm + .show_am_pm = show_am_pm, + .color_saturday = color_saturday, + .color_sunday = color_sunday, + .color_us_federal = color_us_federal, + .color_time = color_time }; persist_set_config(config); main_window_refresh(); } + else { + APP_LOG(APP_LOG_LEVEL_WARNING, "Bad payload received in app_message.c"); + } } static void inbox_dropped_callback(AppMessageResult reason, void *context) { @@ -99,4 +115,4 @@ void app_message_init() { const int inbox_size = 256; const int outbox_size = 0; app_message_open(inbox_size, outbox_size); -} \ No newline at end of file +} diff --git a/src/c/appendix/config.c b/src/c/appendix/config.c index 5fc5331..4267ecc 100644 --- a/src/c/appendix/config.c +++ b/src/c/appendix/config.c @@ -63,11 +63,6 @@ int config_n_today() { return wday; } -GColor config_today_color() { - GColor color = g_config->color_today; - return color; -} - GFont config_time_font() { const char *font_keys[] = { FONT_KEY_ROBOTO_BOLD_SUBSET_49, diff --git a/src/c/appendix/config.h b/src/c/appendix/config.h index 05b075a..3e6f636 100644 --- a/src/c/appendix/config.h +++ b/src/c/appendix/config.h @@ -15,6 +15,10 @@ typedef struct { bool show_am_pm; int16_t time_font; GColor color_today; + GColor color_saturday; + GColor color_sunday; + GColor color_us_federal; + GColor color_time; } Config; Config *g_config; @@ -33,6 +37,4 @@ int config_axis_hour(int hour); int config_n_today(); -GColor config_today_color(); - -GFont config_time_font(); \ No newline at end of file +GFont config_time_font(); diff --git a/src/c/appendix/persist.c b/src/c/appendix/persist.c index b26520e..aceca71 100644 --- a/src/c/appendix/persist.c +++ b/src/c/appendix/persist.c @@ -53,7 +53,11 @@ void persist_init() { .show_bt = true, .show_bt_disconnect = true, .vibe = false, - .show_am_pm = false + .show_am_pm = false, + .color_saturday = GColorWhite, + .color_sunday = GColorWhite, + .color_us_federal = GColorWhite, + .color_time = GColorWhite }; persist_set_config(config); } @@ -146,4 +150,4 @@ void persist_set_sun_event_times(time_t *data, const size_t size) { void persist_set_config(Config config) { persist_write_data(CONFIG, &config, sizeof(Config)); config_refresh(); // Refresh global config variable -} \ No newline at end of file +} diff --git a/src/c/layers/calendar_layer.c b/src/c/layers/calendar_layer.c index f51d75c..377e167 100644 --- a/src/c/layers/calendar_layer.c +++ b/src/c/layers/calendar_layer.c @@ -9,6 +9,76 @@ static Layer *s_calendar_layer; static TextLayer *s_calendar_text_layers[NUM_WEEKS * DAYS_PER_WEEK]; + +static struct tm *relative_tm(int days_from_today) +{ + /* Get a time structure for n days from today (only accurate to the day) + Use this function to avoid edge cases from daylight savings time + */ + time_t timestamp = time(NULL); + tm *local_time = localtime(×tamp); + // Set arbitrary hour so there's no daylight savings rounding error: + local_time->tm_hour = 5; + timestamp = mktime(local_time) + days_from_today * SECONDS_PER_DAY; + return localtime(×tamp); +} + +static int relative_day_of_month(int days_from_today) { + // What is the day of the month n days from today? + tm *local_time = relative_tm(days_from_today); + return local_time->tm_mday; +} + +static bool is_us_federal_holiday(struct tm *t) +{ + // These holidays are on a specific weekday, so no special cases + if ((t->tm_mon == 0 && t->tm_mday >= 15 && t->tm_mday <= 21 && t->tm_wday == 1) || // MLK Day + (t->tm_mon == 1 && t->tm_mday >= 15 && t->tm_mday <= 21 && t->tm_wday == 1) || // Washington's Birthday + (t->tm_mon == 4 && t->tm_mday >= 25 && t->tm_mday <= 31 && t->tm_wday == 1) || // Memorial Day + (t->tm_mon == 8 && t->tm_mday >= 1 && t->tm_mday <= 7 && t->tm_wday == 1) || // Labor Day + (t->tm_mon == 9 && t->tm_mday >= 8 && t->tm_mday <= 14 && t->tm_wday == 1) || // Columbus Day + (t->tm_mon == 10 && t->tm_mday >= 22 && t->tm_mday <= 28 && t->tm_wday == 4)) // Thanksgiving + return true; + + // These remaining holidays are on a specific day of the month, which get + // moved if they fall on a weekend + + // Friday special cases + if (t->tm_wday == 5 && ( + (t->tm_mon == 11 && t->tm_mday == 31) || // New Years + (t->tm_mon == 6 && t->tm_mday == 3) || // Independence Day + (t->tm_mon == 10 && t->tm_mday == 10) || // Veterans Day + (t->tm_mon == 11 && t->tm_mday == 24))) // Christmas + return true; + // Monday special cases + if (t->tm_wday == 1 && ( + (t->tm_mon == 0 && t->tm_mday == 2) || // New Years + (t->tm_mon == 6 && t->tm_mday == 5) || // Independence Day + (t->tm_mon == 10 && t->tm_mday == 12) || // Veterans Day + (t->tm_mon == 11 && t->tm_mday == 26))) // Christmas + return true; + // Non special cases + if ((t->tm_mon == 0 && t->tm_mday == 1) || // New Years + (t->tm_mon == 6 && t->tm_mday == 4) || // Independence Day + (t->tm_mon == 10 && t->tm_mday == 11) || // Veterans Day + (t->tm_mon == 11 && t->tm_mday == 25)) // Christmas + return true; + + // Default to no holiday + return false; +} + +static GColor date_color(struct tm *t) { + // Get color for a date, considering weekends and holidays + if (is_us_federal_holiday(t)) + return g_config->color_us_federal; + if (t->tm_wday == 0) + return g_config->color_sunday; + if (t->tm_wday == 6) + return g_config->color_saturday; + return GColorWhite; +} + static void calendar_update_proc(Layer *layer, GContext *ctx) { GRect bounds = layer_get_bounds(layer); int w = bounds.size.w; @@ -18,8 +88,10 @@ static void calendar_update_proc(Layer *layer, GContext *ctx) { // Calculate which box holds today's date const int i_today = config_n_today(); + struct tm *t = relative_tm(0); - graphics_context_set_fill_color(ctx, PBL_IF_COLOR_ELSE(config_today_color(), GColorWhite)); + GColor background_color = PBL_IF_COLOR_ELSE(date_color(t), GColorWhite); + graphics_context_set_fill_color(ctx, background_color); graphics_fill_rect(ctx, GRect((i_today % DAYS_PER_WEEK) * box_w, (i_today / DAYS_PER_WEEK) * box_h, box_w, box_h), 1, GCornersAll); @@ -48,13 +120,6 @@ void calendar_layer_create(Layer* parent_layer, GRect frame) { layer_add_child(parent_layer, s_calendar_layer); } -static int relative_day_of_month(int days_from_today) { - // What is the day of the month relative to today? - time_t timestamp = time(NULL); - timestamp += days_from_today * SECONDS_PER_DAY; - tm *local_time = localtime(×tamp); - return local_time->tm_mday; -} void calendar_layer_refresh() { static char s_calendar_box_buffers[NUM_WEEKS * DAYS_PER_WEEK][4]; @@ -67,18 +132,21 @@ void calendar_layer_refresh() { // Fill each box with an appropriate relative day number for (int i = 0; i < NUM_WEEKS * DAYS_PER_WEEK; ++i) { char *buffer = s_calendar_box_buffers[i]; + struct tm *t = relative_tm(i - i_today); if (i == i_today) { + GColor background_color = PBL_IF_COLOR_ELSE(date_color(t), GColorWhite); text_layer_set_text_color(s_calendar_text_layers[i], - PBL_IF_COLOR_ELSE(gcolor_legible_over(config_today_color()), GColorBlack)); + gcolor_legible_over(background_color)); text_layer_set_font(s_calendar_text_layers[i], fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD)); } else { - text_layer_set_text_color(s_calendar_text_layers[i], GColorWhite); + GColor text_color = PBL_IF_COLOR_ELSE(date_color(t), GColorWhite); + text_layer_set_text_color(s_calendar_text_layers[i], text_color); text_layer_set_font(s_calendar_text_layers[i], fonts_get_system_font(FONT_KEY_GOTHIC_18)); } - snprintf(buffer, 4, "%d", relative_day_of_month(i - i_today)); + snprintf(buffer, 4, "%d", t->tm_mday); text_layer_set_text(s_calendar_text_layers[i], buffer); } } @@ -88,4 +156,4 @@ void calendar_layer_destroy() { text_layer_destroy(s_calendar_text_layers[i]); } layer_destroy(s_calendar_layer); -} \ No newline at end of file +} diff --git a/src/c/layers/time_layer.c b/src/c/layers/time_layer.c index 56cee15..8de376d 100644 --- a/src/c/layers/time_layer.c +++ b/src/c/layers/time_layer.c @@ -19,7 +19,6 @@ void time_layer_create(Layer* parent_layer, GRect frame) { // Main time formatting text_layer_set_background_color(s_time_layer, GColorClear); - text_layer_set_text_color(s_time_layer, GColorWhite); text_layer_set_text(s_time_layer, "00:00"); text_layer_set_text_alignment(s_time_layer, GTextAlignmentLeft); @@ -79,6 +78,7 @@ void time_layer_tick() { void time_layer_refresh() { text_layer_set_font(s_time_layer, config_time_font()); + text_layer_set_text_color(s_time_layer, g_config->color_time); time_layer_tick(); // Update main time text and layer positions } diff --git a/src/pkjs/clay/config.js b/src/pkjs/clay/config.js index 4393854..fad7392 100644 --- a/src/pkjs/clay/config.js +++ b/src/pkjs/clay/config.js @@ -61,7 +61,15 @@ module.exports = [ "value": "bitham" }, ] - } + }, + { + "type": "color", + "label": "Main time color", + "messageKey": "colorTime", + "defaultValue": "#FFFFFF", + "sunlight": false, + "capabilities": ["COLOR"] + }, ] }, { @@ -71,14 +79,6 @@ module.exports = [ "type": "heading", "defaultValue": "Calendar", }, - { - "type": "color", - "label": "Today highlight", - "messageKey": "colorToday", - "defaultValue": "#FFFFFF", - "sunlight": false, - "capabilities": ["COLOR"] - }, { "type": "select", "label": "Start week on", @@ -110,7 +110,39 @@ module.exports = [ "value": "curr" } ] - } + }, + { + "type": "color", + "label": "Today highlight", + "messageKey": "colorToday", + "defaultValue": "#FFFFFF", + "sunlight": false, + "capabilities": ["COLOR"] + }, + { + "type": "color", + "label": "Sunday color", + "messageKey": "colorSunday", + "defaultValue": "#FFFFFF", + "sunlight": false, + "capabilities": ["COLOR"] + }, + { + "type": "color", + "label": "Saturday color", + "messageKey": "colorSaturday", + "defaultValue": "#FFFFFF", + "sunlight": false, + "capabilities": ["COLOR"] + }, + { + "type": "color", + "label": "US federal holidays color", + "messageKey": "colorUSFederal", + "defaultValue": "#FFFFFF", + "sunlight": false, + "capabilities": ["COLOR"] + }, ] }, { diff --git a/src/pkjs/index.js b/src/pkjs/index.js index eb61775..cd7fcbf 100644 --- a/src/pkjs/index.js +++ b/src/pkjs/index.js @@ -64,6 +64,10 @@ function sendClaySettings() { "CLAY_SHOW_BT_DISCONNECT": app.settings.btIcons === "disconnected" || app.settings.btIcons === "both", "CLAY_VIBE": app.settings.vibe, "CLAY_SHOW_AM_PM": app.settings.timeShowAmPm, + "CLAY_COLOR_SUNDAY": app.settings.hasOwnProperty('colorSunday') ? app.settings.colorSunday : 16777215, + "CLAY_COLOR_SATURDAY": app.settings.hasOwnProperty('colorSaturday') ? app.settings.colorSaturday : 16777215, + "CLAY_COLOR_US_FEDERAL": app.settings.hasOwnProperty('colorUSFederal') ? app.settings.colorUSFederal : 16777215, + "CLAY_COLOR_TIME": app.settings.hasOwnProperty('colorTime') ? app.settings.colorTime : 16777215, } Pebble.sendAppMessage(payload, function() { console.log('Message sent successfully: ' + JSON.stringify(payload)); @@ -177,4 +181,4 @@ function needRefresh() { } // If the most recent fetch is more than 30 minutes old return (Date.now() - roundDownMinutes(new Date(lastFetchSuccess.time), 30) > 1000 * 60 * 30); -} \ No newline at end of file +}