Upload files to "src/c"
This commit is contained in:
217
src/c/num2words.c
Normal file
217
src/c/num2words.c
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "num2words.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
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);
|
||||
}
|
||||
7
src/c/num2words.h
Normal file
7
src/c/num2words.h
Normal file
@@ -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);
|
||||
692
src/c/sliding_text.c
Normal file
692
src/c/sliding_text.c
Normal file
@@ -0,0 +1,692 @@
|
||||
#include <pebble.h>
|
||||
#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();
|
||||
}
|
||||
Reference in New Issue
Block a user