diff --git a/CHANGELOG.md b/CHANGELOG.md index 9962af1..dc5bad7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,28 @@ text to paste into the **Release notes** field when uploading the `.pbw` to the the build, so the portal does not auto-fill it). The store **Description** lives in [`PUBLISHING.md`](PUBLISHING.md). +## 0.0.2 + +- Settings are now WYSIWYG: saving the config page applies **every** value it + shows, not only the ones you touched. Previously the page sent just the keys + that changed against a phone-side baseline; when that baseline had drifted from + the watch (reinstall, a different phone, or defaults that never matched), a + setting shown in the page but left untouched was silently never applied — so + e.g. a complication could read "Location" in the settings yet stay "Date" on + the watch. Every non-translation setting is now always sent. Translation + strings stay delta-only (they are bulk and must fit the watch's message inbox); + the language picker resends them when it changes them. +- Config page defaults now match the watch's firmware defaults (the + above-calendar Week and AM/PM slots, low-battery threshold), so a fresh install + shows the watch's real out-of-box state before the first save. +- Coloured weather icons on colour watches (basalt, emery): the condition glyph + is tinted by meaning — sun amber, rain/snow blue, lightning amber, moon pale + yellow — while the temperature keeps the theme colour. Black-and-white watches + (diorite, flint) and the Mono theme are unchanged. +- New **Weather → Icon style** setting: *Default* (colour on colour watches, B&W + on the rest), *Colour* (force the tinted icons, overriding the Mono theme), or + *Black & White* (force the classic monochrome glyph on colour watches too). + ## 0.0.1 — first public release - Configurable complication rows (status bar, above the time, above the diff --git a/package.json b/package.json index d5844d4..a757061 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "TimelyNG", "author": "Emanuele 'Lele' Calo", - "version": "0.0.1", + "version": "0.0.2", "description": "A calendar watchface for Pebble: date, time and weather up top, a 3-week calendar below, and fully configurable complication rows. Appstore copy in PUBLISHING.md; release notes in CHANGELOG.md.", "private": true, "keywords": [ @@ -155,6 +155,7 @@ "slot_ctr_l": 116, "slot_ctr_r": 117, "batt_style": 118, + "weather_icons": 119, "trans_abbr_sunday": 500, "trans_abbr_monday": 501, "trans_abbr_tuesday": 502, diff --git a/src/Timely.c b/src/Timely.c index b49350c..da9f62d 100644 --- a/src/Timely.c +++ b/src/Timely.c @@ -151,6 +151,7 @@ static bool showing_statusbar = true; #define AK_SLOT_CTR_L 116 #define AK_SLOT_CTR_R 117 #define AK_BATT_STYLE 118 +#define AK_WEATHER_ICONS 119 #define AK_TRANS_ABBR_SUNDAY 500 #define AK_TRANS_ABBR_MONDAY 501 @@ -1980,6 +1981,13 @@ void in_configuration_handler(DictionaryIterator *received, void *context) { adv_settings_get()->weather_update = appkey->value->uint8; } + // AK_WEATHER_ICONS == weather icon style (0 default / 1 colour / 2 B&W) + appkey = dict_find(received, AK_WEATHER_ICONS); + if (appkey != NULL) { + adv_settings_get()->weather_icons = appkey->value->uint8; + weather_mark_dirty(); + } + // AK_THEME == color theme id; AK_THEME_MODE == 0 light / 1 dark / 2 auto appkey = dict_find(received, AK_THEME); if (appkey != NULL && appkey->value->uint8 < THEME_COUNT) { diff --git a/src/js/app.js b/src/js/app.js index 9aa3af9..170f95b 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -312,9 +312,10 @@ V = waning crescent 0.25 + Pebble.addEventListener("webviewclosed", function (e) { if (!e || !e.response) { return; } // user cancelled - // The page sends only the keys that changed. Merge them into the stored - // settings (don't overwrite) so the config page keeps the full picture and - // doesn't show untouched settings as reset on the next open. + // The page sends every non-translation setting plus any changed translation + // string. Merge them into the stored settings so the config page keeps the + // full picture (incl. translations) and doesn't show untouched settings as + // reset on the next open. var dict; try { dict = JSON.parse(decodeURIComponent(e.response)); } catch (err) { return; } var stored = {}; diff --git a/src/js/config.js b/src/js/config.js index 6f5f9f8..6020a3e 100644 --- a/src/js/config.js +++ b/src/js/config.js @@ -105,8 +105,8 @@ module.exports = [ { key: 'slot_stat_r', label: 'Status bar — right', type: 'select', def: 15, options: SLOTS }, { key: 'slot_ctr_l', label: 'Above time — left', type: 'select', def: 18, options: SLOTS }, { key: 'slot_ctr_r', label: 'Above time — right', type: 'select', def: 0, options: SLOTS }, - { key: 'style_week', label: 'Above calendar — left', type: 'select', def: 0, options: SLOTS }, - { key: 'style_am_pm', label: 'Above calendar — right', type: 'select', def: 0, options: SLOTS }, + { key: 'style_week', label: 'Above calendar — left', type: 'select', def: 3, options: SLOTS }, + { key: 'style_am_pm', label: 'Above calendar — right', type: 'select', def: 5, options: SLOTS }, { key: 'batt_style', label: 'Battery style', type: 'select', def: 2, options: [['Bar (fills) with %', 0], ['Text %', 1], ['Icon + %', 2], ['Bar + icon', 3]], note: 'How the Watch/Phone battery complications are drawn.' }, @@ -134,7 +134,10 @@ module.exports = [ { key: 'weather_fmt', label: 'Units', type: 'select', def: 0, options: [['Celsius', 0], ['Fahrenheit', 1]] }, { key: 'weather_update', label: 'Update every', type: 'select', def: 15, - options: [['Off', 0], ['15 min', 15], ['30 min', 30], ['60 min', 60]] } + options: [['Off', 0], ['15 min', 15], ['30 min', 30], ['60 min', 60]] }, + { key: 'weather_icons', label: 'Icon style', type: 'select', def: 0, + options: [['Default', 0], ['Colour', 1], ['Black & White', 2]], + note: 'Default: colour on colour watches, B&W on the rest. Colour and Black & White force it.' } ] }, { diff --git a/src/js/configpage.js b/src/js/configpage.js index 22afe3b..39c5ab0 100644 --- a/src/js/configpage.js +++ b/src/js/configpage.js @@ -167,9 +167,11 @@ var CSS = [ function buildConfigPage(spec, current) { current = current || {}; var body = ''; - // Baseline = the value each control starts at; on submit we send only the keys - // that actually changed, keeping the pebblejs://close payload small (a full - // dump of every setting exceeds the close-URL length and gets dropped). + // Baseline = the value each control starts at. It is only used to delta the + // bulky translation strings (trans_*): every other setting is always sent so + // the watch ends up matching exactly what the page shows (WYSIWYG). The phone + // copy used to seed this baseline is not authoritative — it can drift from the + // watch — so a "shown but unchanged" value must never be silently dropped. var baseline = {}; for (var s = 0; s < spec.length; s++) { var sec = spec[s]; @@ -237,8 +239,14 @@ function buildConfigPage(spec, current) { 'els=document.querySelectorAll("[data-key]");for(var i=0;i' + '' + diff --git a/src/settings.c b/src/settings.c index 2241579..50877e5 100644 --- a/src/settings.c +++ b/src/settings.c @@ -32,7 +32,7 @@ static persist_adv_settings s_adv = { .invertTopSlot = 0, .invertBotSlot = 0, .showStatus = 1, - .showStatusBat = 100, + .showStatusBat = 20, // matches the config page default; threshold for "when battery low" .showDate = 1, .DND_start = 0, .DND_stop = 0, @@ -54,7 +54,8 @@ static persist_adv_settings s_adv = { .clock_font = 1, .token_type = { 0, 0 }, .token_code = { "", "" }, - .slots = { 0, 1, 2, 3, 0, 1, 0, 1, 0, 1 } + .slots = { 0, 1, 2, 3, 0, 1, 0, 1, 0, 1 }, + .weather_icons = 0 // Default: colour on colour watches, B&W on the rest }; persist *settings_get(void) { return &s_settings; } diff --git a/src/settings.h b/src/settings.h index 5cdb39c..3d3f03f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -57,7 +57,10 @@ typedef struct persist_adv_settings { // 243 bytes uint8_t token_type[2]; // 2 bytes char token_code[2][65]; //130 bytes uint8_t slots[10]; // 10 bytes + uint8_t weather_icons; // 1 byte: 0 default (colour where supported), 1 force colour, 2 force B&W } __attribute__((__packed__)) persist_adv_settings; +// New fields go at the END: persist_read_data tolerates a shorter stored blob, +// leaving appended fields at their settings.c default for pre-existing installs. persist *settings_get(void); persist_adv_settings *adv_settings_get(void); diff --git a/src/theme.c b/src/theme.c index d89eb86..40a8aba 100644 --- a/src/theme.c +++ b/src/theme.c @@ -84,3 +84,45 @@ void setTodayColors(GContext* ctx) { graphics_context_set_fill_color(ctx, p.accent); graphics_context_set_text_color(ctx, p.accent_fg); } + +GColor weather_glyph_color(char glyph) { + Palette p = theme_palette(); +#ifdef PBL_COLOR + uint8_t style = adv_settings_get()->weather_icons; + if (style == WEATHER_ICONS_BW) { return p.fg; } // forced monochrome + // Default follows the Mono theme's single-colour look; Colour overrides it. + if (style == WEATHER_ICONS_DEFAULT && settings_get()->theme == THEME_MONO) { return p.fg; } + // Saturated tints chosen to read on both light and dark backgrounds. Clouds, + // fog, tornado and anything unmapped stay the theme fg (which reads anywhere). + switch (glyph) { + case 'I': // sun + return GColorChromeYellow; + case 'J': case 'K': // sunset / sunrise + return GColorOrange; + case 'N': case 'O': case 'P': case 'Q': case 'R': // moon phases + case 'S': case 'T': case 'U': case 'V': + return GColorPastelYellow; + case '$': case '%': case '&': // rain + case '\'': case '(': case ')': // showers + case '*': case '+': case ',': // downpour + case '-': case '.': case '/': // drizzle + return GColorVividCerulean; + case '0': case '1': case '2': // sleet + case '3': case '4': case '5': // hail + return GColorBlue; + case '6': case '7': case '8': // flurries + case '9': case ':': case ';': // snow + case 'W': // snowflake + return GColorVividCerulean; + case 'B': case 'C': case 'D': case 'E': // wind + return GColorJaegerGreen; + case 'F': case 'G': case 'H': // lightning + return GColorChromeYellow; + default: // clouds, fog, haze, … + return p.fg; + } +#else + (void)glyph; + return p.fg; +#endif +} diff --git a/src/theme.h b/src/theme.h index 9554c57..6b7be0a 100644 --- a/src/theme.h +++ b/src/theme.h @@ -14,6 +14,9 @@ typedef struct { enum { THEME_MONO = 0, THEME_FUNCTIONAL = 1, THEME_MINIMAL = 2, THEME_VIBRANT = 3, THEME_COUNT }; enum { THEME_MODE_LIGHT = 0, THEME_MODE_DARK = 1, THEME_MODE_AUTO = 2 }; +// Weather icon style (adv_settings.weather_icons): Default follows the platform +// and the Mono theme; Colour/B&W force it regardless. +enum { WEATHER_ICONS_DEFAULT = 0, WEATHER_ICONS_COLOUR = 1, WEATHER_ICONS_BW = 2 }; // The active palette, resolved from settings (theme + mode; AUTO uses night). Palette theme_palette(void); @@ -22,3 +25,8 @@ Palette theme_palette(void); void setColors(GContext* ctx); // base: bg + fg void setInvColors(GContext* ctx); // swapped patch: fg-on-bg void setTodayColors(GContext* ctx);// accent + +// Meaningful tint for a Climacons weather glyph (sun yellow, rain blue, …), +// honouring the weather_icons setting. Returns the palette fg (monochrome) on +// 1-bit watches, when B&W is forced, or under Default + the Mono theme. +GColor weather_glyph_color(char glyph); diff --git a/src/weather.c b/src/weather.c index 2fa3e02..8d923e2 100644 --- a/src/weather.c +++ b/src/weather.c @@ -42,7 +42,11 @@ static void weather_render(Layer *me, GContext *ctx) { int block = gap + temp_h; // total visual height of icon+temp int top = (band_h - block) / 2; if (top < 0) { top = 0; } + // Tint the condition glyph (color platforms, non-Mono theme); the temperature + // stays the theme color, so restore it afterwards. + graphics_context_set_text_color(ctx, weather_glyph_color(cond_current[0])); graphics_draw_text(ctx, cond_current, climacons, GRect(2, top, icon_w, gap + 8), GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL); + graphics_context_set_text_color(ctx, theme_palette().fg); graphics_draw_text(ctx, temp_current, temp_font, GRect(2, top + gap, icon_w + 2, temp_h + 8), GTextOverflowModeWordWrap, GTextAlignmentCenter, NULL); if (debug_get()->general) { app_log(APP_LOG_LEVEL_DEBUG, __FILE__, __LINE__, "Weather redrawing: %d, %s", weather_state()->current, weather_state()->condition); } }