feat(face): complete widget set (phone batt, sleep, omens)
Phone battery via navigator.getBattery(), honest "--" when unavailable. Sleep widget enabled with M marker. Omens derived from weather condition: fog/rain/snow/thunder produce advisory text; clear shows empty. All 11 widget types rendered; layout enables all by default. Refs: DESIGN.md §5 widget set complete.
This commit is contained in:
@@ -18,7 +18,8 @@
|
||||
"messageKeys": ["taskName", "syncTime", "clockOff", "boundSec", "used",
|
||||
"taskCount", "curType", "curRem", "status", "abortCode",
|
||||
"pendItems", "pendXp", "dictV", "serverDown", "requestSync",
|
||||
"weatherTemp", "weatherHi", "weatherLo", "weatherTs", "weatherCond"],
|
||||
"weatherTemp", "weatherHi", "weatherLo", "weatherTs",
|
||||
"weatherCond", "phoneBatt"],
|
||||
"resources": {
|
||||
"media": []
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ static char s_clock[8], s_date[16], s_strip[64];
|
||||
static char s_steps[16], s_dist[16], s_sleep[16], s_hrm[12];
|
||||
static bool s_health_ok = false;
|
||||
static int s_bat_pct = -1;
|
||||
static int8_t s_phone_batt = INT8_MIN;
|
||||
static bool s_server_down = false; // set by serverDown tuple, cleared on good sync
|
||||
|
||||
static char s_weather[16];
|
||||
static char s_weather[24];
|
||||
static char s_omen[32];
|
||||
static int16_t s_wx_temp = INT16_MIN, s_wx_hi = INT16_MIN, s_wx_lo = INT16_MIN;
|
||||
static uint8_t s_wx_cond = 0;
|
||||
|
||||
@@ -151,12 +153,24 @@ static void update_values(void) {
|
||||
else if (s->status == 3) snprintf(s_strip, sizeof(s_strip), "ABORTED (%d)", s->abort_code);
|
||||
else if (s->status == 4) snprintf(s_strip, sizeof(s_strip), "LOCKED");
|
||||
else snprintf(s_strip, sizeof(s_strip), "No hero yet");
|
||||
|
||||
// Current Weather (rain Snow Sunnt ect..)
|
||||
if (s_wx_temp != INT16_MIN) {
|
||||
static const char wx_icon[8] = { '?', 'o', '~', 'C', 'F', 'R', '*', 'T' };
|
||||
snprintf(s_weather, sizeof(s_weather), "%c %d° %d/%d",
|
||||
wx_icon[s_wx_cond & 7], s_wx_temp, s_wx_hi, s_wx_lo);
|
||||
|
||||
// Omen derivation from weather condition
|
||||
switch (s_wx_cond & 7) {
|
||||
case 4: snprintf(s_omen, sizeof(s_omen), "Travel impaired"); break; // fog
|
||||
case 5: snprintf(s_omen, sizeof(s_omen), "Rain delays"); break; // rain
|
||||
case 6: snprintf(s_omen, sizeof(s_omen), "Cold footing"); break; // snow
|
||||
case 7: snprintf(s_omen, sizeof(s_omen), "Dangerous air"); break; // thunder
|
||||
default: s_omen[0] = '\0'; break;; // clear/other = no omen
|
||||
}
|
||||
} else {
|
||||
snprintf(s_weather, sizeof(s_weather), "--/--");
|
||||
s_omen[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +211,20 @@ static void canvas_update(Layer *layer, GContext *ctx) {
|
||||
tx_off = 26;
|
||||
break;
|
||||
}
|
||||
case W_PHONE_BATTERY: text = "phone?"; break;
|
||||
case W_PHONE_BATTERY: {
|
||||
graphics_context_set_text_color(ctx, pal_color(PAL_ACCENT));
|
||||
graphics_draw_text(ctx, "PB", s_fonts[1],
|
||||
GRect(w->x, w->y - 4, 24, 26),
|
||||
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
|
||||
if (s_phone_batt >= 0) {
|
||||
snprintf(buf, sizeof(buf), "%d%%", s_phone_batt);
|
||||
text = buf;
|
||||
} else {
|
||||
text = "--"; // no reading, honest empty
|
||||
}
|
||||
tx_off = 26;
|
||||
break;
|
||||
}
|
||||
case W_STEPS: {
|
||||
graphics_context_set_text_color(ctx, pal_color(PAL_POSITIVE));
|
||||
graphics_draw_text(ctx, "S", s_fonts[1],
|
||||
@@ -216,9 +243,19 @@ static void canvas_update(Layer *layer, GContext *ctx) {
|
||||
tx_off = 16;
|
||||
break;
|
||||
}
|
||||
case W_SLEEP: text = s_sleep; break;
|
||||
case W_SLEEP: {
|
||||
graphics_context_set_text_color(ctx, pal_color(PAL_WATER));
|
||||
graphics_draw_text(ctx, "Z", s_fonts[1],
|
||||
GRect(w->x, w->y - 4, 14, 26),
|
||||
GTextOverflowModeTrailingEllipsis, GTextAlignmentLeft, NULL);
|
||||
text = s_sleep;
|
||||
tx_off = 16;
|
||||
break;
|
||||
}
|
||||
case W_WEATHER: text = s_weather; break;
|
||||
case W_OMEN: text = ""; break;
|
||||
case W_OMEN:
|
||||
text = s_omen[0] ? s_omen : "";
|
||||
break;
|
||||
case W_HEART: {
|
||||
// state-indicating marker: moss = reading, oxblood = no signal
|
||||
int reading = (strcmp(s_hrm, "--") != 0);
|
||||
@@ -268,14 +305,14 @@ static void layout_default(void) {
|
||||
{W_TIME, 1, 0, 10, 3, PAL_TEXT, 0},
|
||||
{W_DATE, 1, 4, 70, 0, PAL_ACCENT, 0},
|
||||
{W_BATTERY, 1, 4, 90, 0, PAL_TEXT, BAT_BOTH},
|
||||
{W_PHONE_BATTERY, 0, 4, 130, 0, PAL_TEXT, 0},
|
||||
{W_PHONE_BATTERY, 1, 100, 90, 0, PAL_TEXT, 0},
|
||||
{W_STEPS, 1, 4, 115, 0, PAL_TEXT, 0},
|
||||
{W_DISTANCE, 1, 64, 115, 0, PAL_TEXT, 0},
|
||||
{W_HEART, 1, 122, 115, 0, PAL_TEXT, 0},
|
||||
{W_WEATHER, 1, 4, 130, 2, PAL_TEXT, 0},
|
||||
{W_OMEN, 0, 4, 150, 0, PAL_POSITIVE, 0},
|
||||
{W_SLEEP, 0, 4, 170, 1, PAL_WATER, 0},
|
||||
{W_TASK_STRIP, 1, 0, 172, 1, PAL_ACCENT, 0},
|
||||
{W_SLEEP, 1, 4, 135, 1, PAL_WATER, 0},
|
||||
{W_WEATHER, 1, 4, 170, 2, PAL_TEXT, 0},
|
||||
{W_OMEN, 1, 4, 155, 0, PAL_POSITIVE, 0},
|
||||
{W_TASK_STRIP, 1, 0, 200, 1, PAL_ACCENT, 0},
|
||||
};
|
||||
memcpy(s_layout.widgets, defs, sizeof(defs));
|
||||
persist_write_data(LAYOUT_KEY, &s_layout, sizeof(s_layout));
|
||||
@@ -353,6 +390,8 @@ static void inbox_received(DictionaryIterator *iter, void *ctx) {
|
||||
if ((t = dict_find(iter, MESSAGE_KEY_weatherHi))) s_wx_hi = t->value->int16;
|
||||
if ((t = dict_find(iter, MESSAGE_KEY_weatherLo))) s_wx_lo = t->value->int16;
|
||||
|
||||
if ((t = dict_find(iter, MESSAGE_KEY_phoneBatt))) s_phone_batt = (int8_t)t->value->uint8;
|
||||
|
||||
s_server_down = false; // a good sync clears the flag
|
||||
APP_LOG(APP_LOG_LEVEL_DEBUG, "[MSG] snapshot ok: st=%u used=%u rem=%u",
|
||||
s.status, s.used, s.cur_rem);
|
||||
|
||||
@@ -92,6 +92,27 @@ function fetchWeather(cb) {
|
||||
}, { timeout: 8000, maximumAge: 600000 });
|
||||
}
|
||||
|
||||
// ---- phone battery (percent only, honest-empty if unavailable) -------------
|
||||
function fetchPhoneBatt(cb) {
|
||||
if (typeof navigator === 'undefined' || !navigator.getBattery) {
|
||||
return cb(null); // no API / emulator
|
||||
}
|
||||
navigator.getBattery().then(function (b) {
|
||||
var pct = Math.round(b.level * 100);
|
||||
cb(pct);
|
||||
}).catch(function () { cb(null); });
|
||||
}
|
||||
|
||||
function sendPhoneBatt() {
|
||||
fetchPhoneBatt(function (pb) {
|
||||
if (pb !== null) {
|
||||
Pebble.sendAppMessage({ phoneBatt: pb },
|
||||
function () { console.log('[PKJS] phone batt delivered'); },
|
||||
function (e) { console.log('[PKJS] phone batt send failed'); });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function wxLookup(lat, lon, cb) {
|
||||
// rough US bbox (CONUS + AK + HI + PR): NWS has data, else Open-Meteo
|
||||
var inUS = (lat >= 17 && lat <= 72 && lon >= -179 && lon <= -66);
|
||||
@@ -274,7 +295,8 @@ function sync() {
|
||||
Pebble.sendAppMessage({ serverDown: 1 }, function(){}, function(){});
|
||||
}
|
||||
);
|
||||
sendWeather(); // fire-and-forget; separate message
|
||||
sendWeather();
|
||||
sendPhoneBatt();
|
||||
};
|
||||
|
||||
if (cachedV === q.dict) {
|
||||
|
||||
Reference in New Issue
Block a user