From a8188dedb3aa01d794c631d61d726cca1fa3443a Mon Sep 17 00:00:00 2001 From: alanacheff Date: Sun, 6 Oct 2024 21:22:46 +0000 Subject: [PATCH] Upload files to "src/c" --- src/c/num2words.c | 217 ++++++++++++++ src/c/num2words.h | 7 + src/c/sliding_text.c | 692 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 916 insertions(+) create mode 100644 src/c/num2words.c create mode 100644 src/c/num2words.h create mode 100644 src/c/sliding_text.c diff --git a/src/c/num2words.c b/src/c/num2words.c new file mode 100644 index 0000000..a67c29b --- /dev/null +++ b/src/c/num2words.c @@ -0,0 +1,217 @@ +#include "num2words.h" + +#include +#include + +static const char* const ONES[] = { + "zero", + "one", + "two", + "three", + "four", + "five", + "six", + "seven", + "eight", + "nine" +}; + +static const char* const TEENS[] ={ + "", + "eleven", + "twelve", + "thirteen", + "fourteen", + "fifteen", + "sixteen", + "seventeen", + "eighteen", + "nineteen" +}; + +static const char* const TEENS_SPLIT[][2] = { + {"", ""}, + {"eleven",""}, + {"twelve",""}, + {"thirteen",""}, + {"four","teen"}, + {"fifteen",""}, + {"sixteen",""}, + {"seven","teen"}, + {"eight","teen"}, + {"nine","teen"} +}; + +static const char* const TENS[] = { + "", + "ten", + "twenty", + "thirty", + "forty", + "fifty", + "sixty", + "seventy", + "eighty", + "ninety" +}; + +static const char* STR_OH_TICK = "o'"; +static const char* STR_O_TICK = "o"; +static const char* STR_CLOCK = "clock"; +static const char* STR_NOON = "noon"; +static const char* STR_MIDNIGHT = "midnight"; +static const char* STR_QUARTER = "quarter"; +static const char* STR_TO = "to"; +static const char* STR_PAST = "past"; +static const char* STR_HALF = "half"; +static const char* STR_AFTER = "after"; + +static size_t append_number(char* words, int num) { + int tens_val = num / 10 % 10; + int ones_val = num % 10; + + size_t len = 0; + + if (tens_val > 0) { + if (tens_val == 1 && num != 10) { + strcat(words, TEENS[ones_val]); + return strlen(TEENS[ones_val]); + } + strcat(words, TENS[tens_val]); + len += strlen(TENS[tens_val]); + if (ones_val > 0) { + strcat(words, " "); + len += 1; + } + } + + if (ones_val > 0 || num == 0) { + strcat(words, ONES[ones_val]); + len += strlen(ONES[ones_val]); + } + return len; +} + +void fuzzy_time_to_words(int hours, int minutes, char* words) { + int fuzzy_hours = hours; + int fuzzy_minutes = ((minutes + 2) / 5) * 5; + + // Handle hour & minute roll-over. + if (fuzzy_minutes > 55) { + fuzzy_minutes = 0; + fuzzy_hours += 1; + if (fuzzy_hours > 23) { + fuzzy_hours = 0; + } + } + + time_to_common_words(fuzzy_hours, fuzzy_minutes, words); +} + +// returns number of lines written +void time_to_common_words(int hours, int minutes, char *words) { + // TODO: make an app-safe assert + // PBL_ASSERT(hours >= 0 && hours < 24, "Invalid number of hours"); + // PBL_ASSERT(minutes >= 0 && minutes < 60, "Invalid number of minutes"); + + size_t written = 0; + strcpy(words, ""); + + if (minutes != 0 && (minutes >= 10 || minutes == 5 || hours == 0 || hours == 12)) { + if (minutes == 15) { + written += sprintf(words, "%s %s ", STR_QUARTER, STR_AFTER); + } else if (minutes == 45) { + written += sprintf(words, "%s %s ", STR_QUARTER, STR_TO); + hours = (hours + 1) % 24; + } else if (minutes == 30) { + written += sprintf(words, "%s %s ", STR_HALF, STR_PAST); + } else if (minutes < 30) { + written += append_number(words, minutes); + written += sprintf(words + written, " %s ", STR_AFTER); + } else { + written += append_number(words, 60 - minutes); + written += sprintf(words + written, " %s ", STR_TO); + hours = (hours + 1) % 24; + } + } + + if (hours == 0) { + written += sprintf(words + written, "%s", STR_MIDNIGHT); + } else if (hours == 12) { + strcat(words, STR_NOON); + written += sprintf(words + written, "%s", STR_NOON); + } else { + written += append_number(words, hours % 12); + } + + if (minutes < 10 && minutes != 5 && !(hours == 0 || hours == 12)) { + if (minutes == 0) { + sprintf(words + written, " %s%s", STR_OH_TICK, STR_CLOCK); + } else { + sprintf(words + written, " %s%s", STR_OH_TICK, ONES[minutes]); + } + } +} + + + +// o'clock (0) and plain number words (10..) +void minute_to_formal_words(int minutes, int displayPrefix, char *first_word, char *second_word) { + // PBL_ASSERT(minutes >= 0 && minutes < 60, "Invalid number of minutes"); + + strcpy(first_word, ""); + strcpy(second_word, ""); + + + if (minutes == 0) { + strcat(first_word, STR_OH_TICK); + strcat(first_word, STR_CLOCK); + return; + } + if (minutes < 10) { + if (displayPrefix) { + strcat(first_word, STR_O_TICK); //add "o" + strcat(second_word, ONES[minutes%10]); //new + } + else { + strcat(first_word, ONES[minutes%10]); + } + return; + } + if (minutes > 10 && minutes < 20) { + strcat(first_word, TEENS_SPLIT[(minutes - 10) % 10][0]); + strcat(second_word, TEENS_SPLIT[(minutes - 10) % 10][1]); + return; + } + + strcat(first_word, TENS[minutes / 10 % 10]); + + int minute_ones = minutes % 10; + if (minute_ones) { + strcat(second_word, ONES[minute_ones]); + } +} + +void hour_to_12h_word(int hours, char *word) { + // PBL_ASSERT(hours >= 0 && hours < 24, "Invalid number of hours"); + hours = hours % 12; + + if (hours == 0) { + hours = 12; + } + + strcpy(word, ""); + + append_number(word, hours); +} + + +void hour_to_24h_word(int hours, char *words) { + // PBL_ASSERT(hours >= 0 && hours < 24, "Invalid number of hours"); + + hours = hours % 24; + + strcpy(words, ""); + + append_number(words, hours); +} \ No newline at end of file diff --git a/src/c/num2words.h b/src/c/num2words.h new file mode 100644 index 0000000..8445564 --- /dev/null +++ b/src/c/num2words.h @@ -0,0 +1,7 @@ +#pragma once + +void time_to_common_words(int hours, int minutes, char *words); +void fuzzy_time_to_words(int hours, int minutes, char* words); +void minute_to_formal_words(int minutes, int displayPrefix, char *first_word, char *second_word); +void hour_to_12h_word(int hours, char *word); +void hour_to_24h_word(int hours, char *words); \ No newline at end of file diff --git a/src/c/sliding_text.c b/src/c/sliding_text.c new file mode 100644 index 0000000..c7aea9f --- /dev/null +++ b/src/c/sliding_text.c @@ -0,0 +1,692 @@ +#include +#include "num2words.h" + +#define KEY_TEMPERATURE_IN_C 1 +#define KEY_CONDITIONS 2 +#define KEY_BACKGROUND_COLOR 3 +#define KEY_TEXT_COLOR 4 +#define KEY_WEATHER_FREQUENCY 5 +#define KEY_USE_CELSIUS 6 +#define KEY_DISPLAY_O_PREFIX 7 +#define KEY_DISPLAY_WEATHER 8 +#define KEY_MIN_SINCE_WEATHER_UPDATE 9 +#define KEY_WEATHERDATE_ALIGNMENT 10 +#define KEY_HOURMINUTES_ALIGNMENT 11 +#define KEY_GET_WEATHER 12 +#define KEY_TEMPERATURE 13 +#define KEY_OPTIONS 14 + +typedef struct { + int tempF; + int tempC; + int weatherDateAlignment; + int hourMinuteAlignment; + int display_weather; + int use_celsius; + int weather_frequency; + int min_since_last_forecast; + int display_prefix; + int text_color; + int background_color; + char conditions[32]; +} Options; + +Options options; + +TextLayer *weather_layer, *date_layer; +GFont weatherdate_text; + +const int DEFAULT_TEMPF = 0; +const int DEFAULT_TEMPC = 0; +const int DEFAULT_MIN_SINCE_WEATHER_UPDATE = 0; //weather just updated +const int DEFAULT_DISPLAY_WEATHER = 1; //true +const int DEFAULT_WEATHER_FREQUENCY = 30; //minutes +const int DEFAULT_USE_CELSIUS = 0; //false +const int DEFAULT_DISPLAY_O_PREFIX = 0; //false +const int DEFAULT_WEATHERDATE_ALIGNMENT = 1; //center +const int DEFAULT_HOURMINUTES_ALIGNMENT = 0; //left +const int DEFAULT_TEXT_COLOR = 0xFFFFFF; //white +const int DEFAULT_BACKGROUND_COLOR = 0x000000; //black + +const int HOUR_ROW = 0; +const int MINUTE_ROW1 = 1; +const int MINUTE_ROW2 = 2; + +typedef enum { + MOVING_IN, + IN_FRAME, + PREPARE_TO_MOVE, + MOVING_OUT +} SlideState; + +typedef struct { + TextLayer *label; + SlideState state; // animation state + char *next_string; // what to say in the next phase of animation + bool unchanged_font; + + int left_pos; + int right_pos; + int still_pos; + + int movement_delay; + int delay_count; +} SlidingRow; + +typedef struct { + TextLayer *demo_label; + SlidingRow rows[3]; + int last_hour; + int last_minute; + + GFont hour_text; + GFont minute_text; + + Window *window; + Animation *animation; + + struct SlidingTextRenderState { + // double buffered string storage + char hours[2][32]; + uint8_t next_hours; + char first_minutes[2][32]; + char second_minutes[2][32]; + uint8_t next_minutes; + + struct SlidingTextRenderDemoTime { + int secs; + int mins; + int hour; + } demo_time; + } render_state; + +} SlidingTextData; + +SlidingTextData *s_data; + +GTextAlignment row_alignment(int alignment) { + switch (alignment) { + case 0: return GTextAlignmentLeft; + case 1: return GTextAlignmentCenter; + case 2: return GTextAlignmentRight; + default: return GTextAlignmentLeft; + } +} + +static void send(int key, int value){ + DictionaryIterator *iterator; + app_message_outbox_begin(&iterator); + dict_write_int(iterator, key, &value, sizeof(int), true); + + app_message_outbox_send(); +} + +static void set_text_layers_color(GColor textColor) { + SlidingTextData *data = s_data; + SlidingRow *row; + + for (size_t i = 0; i < ARRAY_LENGTH(data->rows); ++i) { + row = &data->rows[i]; + text_layer_set_text_color(row->label, textColor); + } + + text_layer_set_text_color(weather_layer, textColor); + text_layer_set_text_color(date_layer, textColor); +} + +void init_options() { + + if (persist_exists(KEY_OPTIONS)) { + persist_read_data(KEY_OPTIONS, &options, sizeof(options)); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.tempF: %d", options.tempF); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.tempC: %d", options.tempC); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.weatherDateAlignment: %d", options.weatherDateAlignment); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.hourMinuteAlignment: %d", options.hourMinuteAlignment); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.display_weather: %d", options.display_weather); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.use_celsius: %d", options.use_celsius); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.weather_frequency: %d", options.weather_frequency); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.min_since_last_forecast: %d", options.min_since_last_forecast); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.display_prefix: %d", options.display_prefix); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.text_color: %d", options.text_color); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.background_color: %d", options.background_color); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options.conditions: %s", options.conditions); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_read_data options sizeof(options) %d", sizeof(options)); + } + else { + options.tempF = DEFAULT_TEMPF; + options.tempC = DEFAULT_TEMPC; + options.weatherDateAlignment = DEFAULT_WEATHERDATE_ALIGNMENT; + options.hourMinuteAlignment = DEFAULT_HOURMINUTES_ALIGNMENT; + options.display_weather = DEFAULT_DISPLAY_WEATHER; + options.use_celsius = DEFAULT_USE_CELSIUS; + options.weather_frequency = DEFAULT_WEATHER_FREQUENCY; + options.min_since_last_forecast = DEFAULT_MIN_SINCE_WEATHER_UPDATE; + options.display_prefix = DEFAULT_DISPLAY_O_PREFIX; + options.text_color = DEFAULT_TEXT_COLOR; + options.background_color = DEFAULT_BACKGROUND_COLOR; + memset(options.conditions, 0,sizeof(options.conditions)); + persist_write_data(KEY_OPTIONS, &options, sizeof(options)); + APP_LOG(APP_LOG_LEVEL_INFO, "init_options_new: persist_write_data options sizeof(options) %d", sizeof(options)); + } +} + +static void init_sliding_row(SlidingTextData *data, SlidingRow *row, GRect pos, GFont font, + int delay) { + + row->label = text_layer_create(pos); + + //set row alignment + text_layer_set_text_alignment(row->label, row_alignment(options.hourMinuteAlignment)); + + text_layer_set_background_color(row->label, GColorClear); + text_layer_set_text_color(row->label, GColorWhite); + + #ifdef PBL_COLOR // If on basalt + text_layer_set_text_color(row->label, GColorFromHEX(options.text_color)); + #else + if (options.text_color == 0xFFFFFF) {//white + text_layer_set_text_color(row->label, GColorWhite); + } + else if (options.text_color == 0x000000) { + text_layer_set_text_color(row->label, GColorBlack); + } + else { + text_layer_set_text_color(row->label, DEFAULT_TEXT_COLOR); + } + #endif + + if (font) { + text_layer_set_font(row->label, font); + row->unchanged_font = true; + } else { + row->unchanged_font = false; + } + + row->state = IN_FRAME; + row->next_string = NULL; + + row->left_pos = -pos.size.w; + row->right_pos = pos.size.w; + row->still_pos = pos.origin.x; + + row->movement_delay = delay; + row->delay_count = 0; + + data->last_hour = -1; + data->last_minute = -1; +} + +static void init_static_row(TextLayer *label, GFont font) { + + //set row alignment + text_layer_set_text_alignment(label, row_alignment(options.weatherDateAlignment)); + + text_layer_set_background_color(label, GColorClear); + text_layer_set_text_color(label, GColorWhite); + + #ifdef PBL_COLOR // If on basalt + text_layer_set_text_color(label, GColorFromHEX(options.text_color)); + #else + if (options.text_color == 0xFFFFFF) {//white + text_layer_set_text_color(label, GColorWhite); + } + else if (options.text_color == 0x000000) { + text_layer_set_text_color(label, GColorBlack); + } + else { + text_layer_set_text_color(label, DEFAULT_TEXT_COLOR); + } + #endif + + if (font) { + text_layer_set_font(label, font); + } +} + +static void slide_in_text(SlidingTextData *data, SlidingRow *row, char* new_text) { + (void) data; + + const char *old_text = text_layer_get_text(row->label); + if (old_text) { + row->next_string = new_text; + row->state = PREPARE_TO_MOVE; + } else { + text_layer_set_text(row->label, new_text); + GRect frame = layer_get_frame(text_layer_get_layer(row->label)); + frame.origin.x = row->right_pos; + layer_set_frame(text_layer_get_layer(row->label), frame); + row->state = MOVING_IN; + } +} + +static bool update_sliding_row(SlidingTextData *data, SlidingRow *row) { + (void) data; + + GRect frame = layer_get_frame(text_layer_get_layer(row->label)); + bool something_changed = true; + switch (row->state) { + case PREPARE_TO_MOVE: + frame.origin.x = row->still_pos; + row->delay_count++; + if (row->delay_count > row->movement_delay) { + row->state = MOVING_OUT; + row->delay_count = 0; + } + break; + + case MOVING_IN: { + int speed = abs(frame.origin.x - row->still_pos) / 3 + 1; + frame.origin.x -= speed; + if (frame.origin.x <= row->still_pos) { + frame.origin.x = row->still_pos; + row->state = IN_FRAME; + } + } + break; + + case MOVING_OUT: { + int speed = abs(frame.origin.x - row->still_pos) / 3 + 1; + frame.origin.x -= speed; + + if (frame.origin.x <= row->left_pos) { + frame.origin.x = row->right_pos; + row->state = MOVING_IN; + text_layer_set_text(row->label, row->next_string); + row->next_string = NULL; + } + } + break; + + case IN_FRAME: + default: + something_changed = false; + break; + } + if (something_changed) { + layer_set_frame(text_layer_get_layer(row->label), frame); + } + return something_changed; +} + +static void animation_update(struct Animation *animation, const AnimationProgress time_normalized) { + SlidingTextData *data = s_data; + + struct SlidingTextRenderState *rs = &data->render_state; + + time_t now = time(NULL); + struct tm t = *localtime(&now); + + bool something_changed = false; + + if (data->last_minute != t.tm_min) { + something_changed = true; + + minute_to_formal_words(t.tm_min, options.display_prefix, rs->first_minutes[rs->next_minutes], rs->second_minutes[rs->next_minutes]); + if(data->last_hour != t.tm_hour || t.tm_min <= 20 + || t.tm_min/10 != data->last_minute/10) { + slide_in_text(data, &data->rows[MINUTE_ROW1], rs->first_minutes[rs->next_minutes]); + } else { + // The tens line didn't change, so swap to the correct buffer but don't animate + text_layer_set_text(data->rows[MINUTE_ROW1].label, rs->first_minutes[rs->next_minutes]); + } + slide_in_text(data, &data->rows[MINUTE_ROW2], rs->second_minutes[rs->next_minutes]); + rs->next_minutes = rs->next_minutes ? 0 : 1; + data->last_minute = t.tm_min; + } + + if (data->last_hour != t.tm_hour) { + hour_to_12h_word(t.tm_hour, rs->hours[rs->next_hours]); + slide_in_text(data, &data->rows[HOUR_ROW], rs->hours[rs->next_hours]); + rs->next_hours = rs->next_hours ? 0 : 1; + data->last_hour = t.tm_hour; + } + + for (size_t i = 0; i < ARRAY_LENGTH(data->rows); ++i) { + something_changed = update_sliding_row(data, &data->rows[i]) || something_changed; + } + + if (!something_changed) { + animation_unschedule(data->animation); + } +} + +static void update_date_layer() { + + static char date_buffer[16]; + const char *current_date_layer; + + //get current date_layer text + current_date_layer = text_layer_get_text(date_layer); + + //get current date + time_t temp = time(NULL); + struct tm *tick_time = localtime(&temp); + strftime(date_buffer, sizeof(date_buffer), "%a %d %b", tick_time); + + if (current_date_layer != date_buffer) { + text_layer_set_text(date_layer, date_buffer); + APP_LOG(APP_LOG_LEVEL_INFO, "update_date_layer: text_layer_set_text date_layer :%s", date_buffer); + } +} + +static void update_weather_layer() { + + static char weather_layer_buffer[32]; + + // Get a tm structure + time_t temp = time(NULL); + struct tm *tick_time = localtime(&temp); + static char time_buffer[] = "00:00"; + strftime(time_buffer, sizeof("00:00"), "%I:%M", tick_time); + + if (options.use_celsius){ + snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%dC - %s", options.tempC, options.conditions); + //snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%dC - %s (%s)", options.tempC, options.conditions, time_buffer); + APP_LOG(APP_LOG_LEVEL_INFO, "C: Time %s set weather_layer_buffer: %s", time_buffer, weather_layer_buffer); + } + else { + snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%dF - %s", options.tempF, options.conditions); + //snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%dF - %s (%s)", options.tempF, options.conditions, time_buffer); + APP_LOG(APP_LOG_LEVEL_INFO, "F: Time %s set weather_layer_buffer: %s", time_buffer, weather_layer_buffer); + } + + //write weather text row + APP_LOG(APP_LOG_LEVEL_INFO, "update_weather_layer: sizeof(options.conditions) %d. ", strlen(options.conditions)); + if (strlen(options.conditions) != 0) { + //if (strlen(weather_layer_buffer) > 5) { + text_layer_set_text(weather_layer, weather_layer_buffer); + APP_LOG(APP_LOG_LEVEL_INFO, "update_weather_layer: Successful weather row information written at %s. ", time_buffer); + } + else { //empty temp & conditions + text_layer_set_text(weather_layer, ""); + APP_LOG(APP_LOG_LEVEL_INFO, "update_weather_layer: Error displaying weather %s. ", time_buffer); + } +} + +static void make_animation() { + s_data->animation = animation_create(); + animation_set_duration(s_data->animation, ANIMATION_DURATION_INFINITE); + // the animation will stop itself + static const struct AnimationImplementation s_animation_implementation = { + .update = animation_update, + }; + animation_set_implementation(s_data->animation, &s_animation_implementation); + animation_schedule(s_data->animation); +} + +static void handle_minute_tick(struct tm *tick_time, TimeUnits units_changed) { + + if (units_changed & DAY_UNIT) { + //vibes_double_pulse(); //hourly vibration (future option) + } + + make_animation(); //animate mew time + update_date_layer(); //check/update date layer + + //if min_since_last_forecast is 0 or greater + if(options.min_since_last_forecast >= 0) { + + //add one to min_since_last_forecast + ++options.min_since_last_forecast; + + APP_LOG(APP_LOG_LEVEL_INFO, "handle_minute_tick options.min_since_last_forecast : options.weather_frequency %d : %d", options.min_since_last_forecast, options.weather_frequency); + APP_LOG(APP_LOG_LEVEL_INFO, "handle_minute_tick strlen(options.conditions) : %d", strlen(options.conditions)); + //if min_since_last_forecast greater than or equal to weatherUpdateFrequency + if(options.min_since_last_forecast >= options.weather_frequency || + strlen(options.conditions) == 0) { + + options.min_since_last_forecast = 0; + + //get updated weather + send(KEY_GET_WEATHER, 1); + + //update weather layer + update_weather_layer(); + } + } +} + +static void inbox_received_callback(DictionaryIterator *iterator, void *context) { + SlidingTextData *data = s_data; + SlidingRow *row; + + // Read first item + Tuple *t = dict_read_first(iterator); + + // For all items + while(t != NULL) { + // Which key was received? + switch(t->key) { + + case KEY_TEMPERATURE: + options.tempF = (int)t->value->int32; + break; + + case KEY_TEMPERATURE_IN_C: + options.tempC = (int)t->value->int32; + break; + + case KEY_CONDITIONS: + snprintf(options.conditions, sizeof(options.conditions), "%s", t->value->cstring); + break; + + case KEY_DISPLAY_O_PREFIX: + options.display_prefix = (int)t->value->int32; + break; + + case KEY_USE_CELSIUS: + options.use_celsius = (int)t->value->int32; + break; + + case KEY_WEATHER_FREQUENCY: + options.weather_frequency = (int)t->value->int32; + break; + + case KEY_BACKGROUND_COLOR: + options.background_color = (int)t->value->int32; + #ifdef PBL_COLOR // If on basalt + window_set_background_color(data->window, GColorFromHEX(options.background_color)); + #else + if (options.background_color == 0xFFFFFF) { + window_set_background_color(data->window, GColorWhite);//white + } + else if (options.background_color == 0x000000) {//black + window_set_background_color(data->window, GColorBlack); + } + else { //didn't select white or black, default to black + window_set_background_color(data->window, GColorBlack); + } + #endif + break; + + case KEY_WEATHERDATE_ALIGNMENT: + options.weatherDateAlignment = (int)t->value->int32; + text_layer_set_text_alignment(weather_layer, row_alignment(options.weatherDateAlignment)); + text_layer_set_text_alignment(date_layer, row_alignment(options.weatherDateAlignment)); + break; + + case KEY_HOURMINUTES_ALIGNMENT: + row = &data->rows[HOUR_ROW]; + options.hourMinuteAlignment = (int)t->value->int32; + text_layer_set_text_alignment(row->label, row_alignment(options.hourMinuteAlignment)); + row = &data->rows[MINUTE_ROW1]; + text_layer_set_text_alignment(row->label, row_alignment(options.hourMinuteAlignment)); + row = &data->rows[MINUTE_ROW2]; + text_layer_set_text_alignment(row->label, row_alignment(options.hourMinuteAlignment)); + break; + + case KEY_TEXT_COLOR: + options.text_color = (int)t->value->int32; + #ifdef PBL_COLOR // If on basalt + for (size_t i = 0; i < ARRAY_LENGTH(data->rows); ++i) { + row = &data->rows[i]; + text_layer_set_text_color(row->label, GColorFromHEX(options.text_color)); + } + text_layer_set_text_color(weather_layer, GColorFromHEX(options.text_color)); + text_layer_set_text_color(date_layer, GColorFromHEX(options.text_color)); + #else + if (options.text_color == 0xFFFFFF) {//white + set_text_layers_color(GColorWhite); + } + else if (options.text_color == 0x000000) { //black + set_text_layers_color(GColorBlack); + } + else { //didn't select white or black, default to white + set_text_layers_color(GColorWhite); + } + #endif + break; + } + // Look for next item + t = dict_read_next(iterator); + } + + //weather layer + update_weather_layer(); +} + +char *translate_error(AppMessageResult result) { + switch (result) { + case APP_MSG_OK: return "APP_MSG_OK"; + case APP_MSG_SEND_TIMEOUT: return "APP_MSG_SEND_TIMEOUT"; + case APP_MSG_SEND_REJECTED: return "APP_MSG_SEND_REJECTED"; + case APP_MSG_NOT_CONNECTED: return "APP_MSG_NOT_CONNECTED"; + case APP_MSG_APP_NOT_RUNNING: return "APP_MSG_APP_NOT_RUNNING"; + case APP_MSG_INVALID_ARGS: return "APP_MSG_INVALID_ARGS"; + case APP_MSG_BUSY: return "APP_MSG_BUSY"; + case APP_MSG_BUFFER_OVERFLOW: return "APP_MSG_BUFFER_OVERFLOW"; + case APP_MSG_ALREADY_RELEASED: return "APP_MSG_ALREADY_RELEASED"; + case APP_MSG_CALLBACK_ALREADY_REGISTERED: return "APP_MSG_CALLBACK_ALREADY_REGISTERED"; + case APP_MSG_CALLBACK_NOT_REGISTERED: return "APP_MSG_CALLBACK_NOT_REGISTERED"; + case APP_MSG_OUT_OF_MEMORY: return "APP_MSG_OUT_OF_MEMORY"; + case APP_MSG_CLOSED: return "APP_MSG_CLOSED"; + case APP_MSG_INTERNAL_ERROR: return "APP_MSG_INTERNAL_ERROR"; + default: return "UNKNOWN ERROR"; + } +} + +void message_dropped(AppMessageResult reason, void *context) { + APP_LOG(APP_LOG_LEVEL_ERROR, "Dropped message! Reason given: %s", translate_error(reason)); +} + +void message_out_success(DictionaryIterator *iter, void *context) { + APP_LOG(APP_LOG_LEVEL_DEBUG, "Message sent."); +} + +void message_out_failed(DictionaryIterator *iter, AppMessageResult reason, void *context) { + APP_LOG(APP_LOG_LEVEL_DEBUG, "Failed to send message. Reason = %s", translate_error(reason)); +} + +static void handle_deinit(void) { + + //save watchface options + persist_write_data(KEY_OPTIONS, &options, sizeof(options)); + APP_LOG(APP_LOG_LEVEL_INFO, "handle_deinit: persist_write_data options sizeof(options) %d", sizeof(options)); + + text_layer_destroy(weather_layer); + text_layer_destroy(date_layer); + + tick_timer_service_unsubscribe(); + free(s_data); +} + +static void handle_init() { + SlidingTextData *data = (SlidingTextData*)malloc(sizeof(SlidingTextData)); + s_data = data; + + data->render_state.next_hours = 0; + data->render_state.next_minutes = 0; + data->render_state.demo_time.secs = 0; + data->render_state.demo_time.mins = 0; + data->render_state.demo_time.hour = 0; + + data->window = window_create(); + + //set-retrieve watchface options + init_options(); + + window_set_background_color(data->window, GColorBlack); + #ifdef PBL_COLOR // If on basalt + window_set_background_color(data->window, GColorFromHEX(options.background_color)); + #else + if (options.background_color == 0xFFFFFF) { + window_set_background_color(data->window, GColorWhite);//white + } + else if (options.background_color == 0x000000) {//black + window_set_background_color(data->window, GColorBlack); + } + else { //didn't select white or black, default to white + window_set_background_color(data->window, DEFAULT_BACKGROUND_COLOR); + } + #endif + + data->hour_text = fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD); + data->minute_text = fonts_get_system_font(FONT_KEY_BITHAM_42_LIGHT); + weatherdate_text = fonts_get_system_font(FONT_KEY_GOTHIC_18); + + Layer *window_layer = window_get_root_layer(data->window); + GRect layer_frame = layer_get_frame(window_layer); + const int16_t width = layer_frame.size.w; + + init_sliding_row(data, &data->rows[HOUR_ROW], GRect(0, 20, width, 60), data->hour_text, 6); + layer_add_child(window_layer, text_layer_get_layer(data->rows[HOUR_ROW].label)); + + init_sliding_row(data, &data->rows[MINUTE_ROW1], GRect(0, 56, width, 96), data->minute_text, 3); + layer_add_child(window_layer, text_layer_get_layer(data->rows[MINUTE_ROW1].label)); + + init_sliding_row(data, &data->rows[MINUTE_ROW2], GRect(0, 92, width, 132), data->minute_text, 0); + layer_add_child(window_layer, text_layer_get_layer(data->rows[MINUTE_ROW2].label)); + + //weather layer + weather_layer = text_layer_create(GRect(0, 0, width, 24)); + init_static_row(weather_layer, weatherdate_text); + layer_add_child(window_layer, text_layer_get_layer(weather_layer)); + + //date layer + date_layer = text_layer_create(GRect(0, 145, width, 198)); + init_static_row(date_layer, weatherdate_text); + layer_add_child(window_layer, text_layer_get_layer(date_layer)); + + GFont norm14 = fonts_get_system_font(FONT_KEY_GOTHIC_14); + + data->demo_label = text_layer_create(GRect(0, -3, 100, 20)); + text_layer_set_background_color(data->demo_label, GColorClear); + text_layer_set_text_color(data->demo_label, GColorWhite); + text_layer_set_font(data->demo_label, norm14); + text_layer_set_text(data->demo_label, "demo mode"); + layer_add_child(window_layer, text_layer_get_layer(data->demo_label)); + + layer_set_hidden(text_layer_get_layer(data->demo_label), true); + layer_mark_dirty(window_layer); + + app_message_register_inbox_received(inbox_received_callback); + app_message_register_inbox_dropped(message_dropped); + app_message_register_outbox_sent(message_out_success); + app_message_register_outbox_failed(message_out_failed); + + app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum()); + + update_date_layer(); + + APP_LOG(APP_LOG_LEVEL_DEBUG, "strlen conditions %d", strlen(options.conditions)); + if (strlen(options.conditions) == 0) { + //no previous conditions, get updated weather + send(KEY_GET_WEATHER, 1); + } + + update_weather_layer(); //check/update date layer + make_animation(); //animate new time layers + + tick_timer_service_subscribe(MINUTE_UNIT, handle_minute_tick); + + const bool animated = true; + window_stack_push(data->window, animated); +} + +int main(void) { + handle_init(); + + app_event_loop(); + + handle_deinit(); +} \ No newline at end of file