@@ -35,6 +35,7 @@
|
||||
"BIRTH_UTC_OFFSET_MINUTES",
|
||||
"BIRTH_LAT_MICRODEG",
|
||||
"BIRTH_LON_MICRODEG",
|
||||
"GENDER",
|
||||
"LANG",
|
||||
"NOTIFY_ENABLED",
|
||||
"NOTIFY_HOUR",
|
||||
|
||||
@@ -44,6 +44,7 @@ static void inbox_received_handler(DictionaryIterator *iter, void *context) {
|
||||
cfg.birth.longitude = v / 1000000.0;
|
||||
got_birth = true;
|
||||
}
|
||||
if (read_int(iter, MESSAGE_KEY_GENDER, &v)) cfg.gender = (Gender)v;
|
||||
|
||||
Tuple *lang_tuple = dict_find(iter, MESSAGE_KEY_LANG);
|
||||
if (lang_tuple) {
|
||||
|
||||
@@ -17,6 +17,7 @@ enum {
|
||||
PKEY_BIRTH_UTC_OFFSET_MINUTES,
|
||||
PKEY_BIRTH_LAT_MICRODEG,
|
||||
PKEY_BIRTH_LON_MICRODEG,
|
||||
PKEY_GENDER,
|
||||
PKEY_LANG,
|
||||
PKEY_NOTIFY_ENABLED,
|
||||
PKEY_NOTIFY_HOUR,
|
||||
@@ -40,6 +41,7 @@ void config_load(WatchConfig *out) {
|
||||
out->birth.utc_offset_hours = (double)(int32_t)persist_read_int(PKEY_BIRTH_UTC_OFFSET_MINUTES) / 60.0;
|
||||
out->birth.latitude = (double)(int32_t)persist_read_int(PKEY_BIRTH_LAT_MICRODEG) / 1000000.0;
|
||||
out->birth.longitude = (double)(int32_t)persist_read_int(PKEY_BIRTH_LON_MICRODEG) / 1000000.0;
|
||||
out->gender = (Gender)persist_read_int(PKEY_GENDER); /* defaults to 0 = GENDER_UNSPECIFIED */
|
||||
|
||||
if (persist_read_string(PKEY_LANG, out->lang, sizeof out->lang) <= 0) {
|
||||
strcpy(out->lang, "en");
|
||||
@@ -61,6 +63,7 @@ void config_save(const WatchConfig *cfg) {
|
||||
persist_write_int(PKEY_BIRTH_UTC_OFFSET_MINUTES, (int32_t)(cfg->birth.utc_offset_hours * 60.0));
|
||||
persist_write_int(PKEY_BIRTH_LAT_MICRODEG, (int32_t)(cfg->birth.latitude * 1000000.0));
|
||||
persist_write_int(PKEY_BIRTH_LON_MICRODEG, (int32_t)(cfg->birth.longitude * 1000000.0));
|
||||
persist_write_int(PKEY_GENDER, (int32_t)cfg->gender);
|
||||
|
||||
persist_write_string(PKEY_LANG, cfg->lang);
|
||||
|
||||
@@ -79,8 +82,9 @@ void config_log(const WatchConfig *cfg) {
|
||||
APP_LOG(APP_LOG_LEVEL_INFO, "config: birth %04d-%02d-%02d %02d:%02d UTC offset %dmin",
|
||||
cfg->birth.year, cfg->birth.month, cfg->birth.day, cfg->birth.hour, cfg->birth.minute,
|
||||
(int)(cfg->birth.utc_offset_hours * 60));
|
||||
APP_LOG(APP_LOG_LEVEL_INFO, "config: lat=%ld/1e6 lon=%ld/1e6 lang=%s",
|
||||
(long)(cfg->birth.latitude * 1000000), (long)(cfg->birth.longitude * 1000000), cfg->lang);
|
||||
APP_LOG(APP_LOG_LEVEL_INFO, "config: lat=%ld/1e6 lon=%ld/1e6 gender=%d lang=%s",
|
||||
(long)(cfg->birth.latitude * 1000000), (long)(cfg->birth.longitude * 1000000),
|
||||
(int)cfg->gender, cfg->lang);
|
||||
APP_LOG(APP_LOG_LEVEL_INFO, "config: notify_enabled=%d at %02d:%02d",
|
||||
cfg->notify_enabled, cfg->notify_hour, cfg->notify_minute);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <pebble.h>
|
||||
|
||||
#include "../../../engine/src/astro.h"
|
||||
#include "../../../engine/src/tarot.h" /* Gender */
|
||||
|
||||
/* Everything the watch needs that isn't computed fresh each day: birth
|
||||
* data (the same fields as dist/user.properties), display language, and
|
||||
@@ -13,6 +14,9 @@
|
||||
typedef struct {
|
||||
bool configured; /* false until the config page has been submitted once */
|
||||
BirthData birth;
|
||||
Gender gender; /* who the reading is for - only affects Celtic Cross
|
||||
* position text pronouns, see engine/src/tarot.h.
|
||||
* Defaults to GENDER_UNSPECIFIED (0). */
|
||||
char lang[8]; /* e.g. "en", "de" - matches engine/i18n/<lang>.lang's own code */
|
||||
bool notify_enabled;
|
||||
int notify_hour; /* 0-23, the watch's own local wall-clock time (i.e.
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ static void on_config_updated(const WatchConfig *cfg) {
|
||||
if (!s_config.configured) return;
|
||||
|
||||
config_log(&s_config); /* the values this newly-recomputed reading is based on */
|
||||
reading_state_recompute(&s_config.birth);
|
||||
reading_state_recompute(&s_config.birth, s_config.gender);
|
||||
if (was_configured) {
|
||||
report_window_reload();
|
||||
} else {
|
||||
@@ -41,7 +41,7 @@ int main(void) {
|
||||
config_log(&s_config); /* the values this session's reading (if any) is based on */
|
||||
|
||||
if (s_config.configured) {
|
||||
reading_state_recompute(&s_config.birth);
|
||||
reading_state_recompute(&s_config.birth, s_config.gender);
|
||||
report_window_push();
|
||||
} else {
|
||||
show_setup_required();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
static DailyReading s_reading;
|
||||
static DailyInterpretation s_interp;
|
||||
|
||||
void reading_state_recompute(const BirthData *birth) {
|
||||
void reading_state_recompute(const BirthData *birth, Gender gender) {
|
||||
time_t now = time(NULL);
|
||||
/* Plain gmtime() (not the POSIX-only gmtime_r()), used immediately -
|
||||
* same justification as astro.c's own time_from_unix(). */
|
||||
@@ -14,7 +14,7 @@ void reading_state_recompute(const BirthData *birth) {
|
||||
char seed[16];
|
||||
snprintf(seed, sizeof seed, "%04d-%02d-%02d", utc->tm_year + 1900, utc->tm_mon + 1, utc->tm_mday);
|
||||
|
||||
reading_generate(seed, birth, now, &s_reading);
|
||||
reading_generate(seed, birth, gender, now, &s_reading);
|
||||
interpret_daily_reading(&s_reading, &s_interp);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
#include "../../../interpreter/src/significance.h"
|
||||
|
||||
/* Recomputes today's reading (astro + tarot + interpretation) from
|
||||
* `birth`, using the watch's current clock: today's UTC date as the
|
||||
* tarot seed (same convention as dist/run-engine.sh - see CLAUDE.md's
|
||||
* `birth`/`gender`, using the watch's current clock: today's UTC date as
|
||||
* the tarot seed (same convention as dist/run-engine.sh - see CLAUDE.md's
|
||||
* "Build & run") and the current moment for the transit snapshot. Safe
|
||||
* to call again later (e.g. the day rolled over while the app stayed
|
||||
* open) - overwrites the previous result in place. */
|
||||
void reading_state_recompute(const BirthData *birth);
|
||||
void reading_state_recompute(const BirthData *birth, Gender gender);
|
||||
|
||||
/* Valid only after at least one reading_state_recompute() call. */
|
||||
const DailyReading *reading_state_get_reading(void);
|
||||
|
||||
@@ -259,7 +259,7 @@ static void build_content(int16_t width) {
|
||||
const TarotDraw *draw = &reading->spread.positions[i];
|
||||
|
||||
snprintf(s_position_titles[i], sizeof s_position_titles[i], "%s: %s %s",
|
||||
tarot_position_name((CelticCrossPosition)i), tarot_card_name(draw->card),
|
||||
tarot_position_name((CelticCrossPosition)i, reading->gender), tarot_card_name(draw->card),
|
||||
reversed_marker(draw->reversed));
|
||||
y = add_text(width, y, s_position_titles[i], s_font_subhead);
|
||||
|
||||
@@ -272,7 +272,7 @@ static void build_content(int16_t width) {
|
||||
scroll_layer_add_child(s_scroll_layer, bitmap_layer_get_layer(s_slots[i].bitmap_layer));
|
||||
y += s_slots[i].height + MARGIN;
|
||||
|
||||
y = add_text(width, y, tarot_position_description((CelticCrossPosition)i), s_font_body);
|
||||
y = add_text(width, y, tarot_position_description((CelticCrossPosition)i, reading->gender), s_font_body);
|
||||
y = add_text(width, y, tarot_card_meaning(draw->card, draw->reversed), s_font_body);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,14 @@ var LANGUAGES = [
|
||||
{ code: 'en', label: 'English' },
|
||||
{ code: 'de', label: 'Deutsch' }
|
||||
];
|
||||
// Matches engine/src/tarot.h's Gender enum (GENDER_UNSPECIFIED = 0,
|
||||
// GENDER_MALE = 1, GENDER_FEMALE = 2) - only affects the Celtic Cross
|
||||
// position text's pronouns, never the chart/draw itself.
|
||||
var GENDERS = [
|
||||
{ code: 0, label: 'Prefer not to say' },
|
||||
{ code: 1, label: 'Male' },
|
||||
{ code: 2, label: 'Female' }
|
||||
];
|
||||
|
||||
// 120x120 PNG of res/logo-small.png, inlined as base64 - this page is a
|
||||
// bare `data:` URI with no server, so there's nowhere else to load an
|
||||
@@ -65,6 +73,16 @@ function buildLanguageOptions(selected) {
|
||||
return html;
|
||||
}
|
||||
|
||||
function buildGenderOptions(selected) {
|
||||
var html = '';
|
||||
for (var i = 0; i < GENDERS.length; i++) {
|
||||
var gender = GENDERS[i];
|
||||
var isSelected = gender.code === selected ? ' selected' : '';
|
||||
html += '<option value="' + gender.code + '"' + isSelected + '>' + gender.label + '</option>';
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
// Runs *inside* the webview (embedded as a <script> string below), not
|
||||
// in pkjs's own context - reads the form, validates it, and hands the
|
||||
// result back to pkjs via the standard Pebble config-page convention:
|
||||
@@ -80,6 +98,7 @@ var FORM_SCRIPT =
|
||||
' var utcOffset = parseFloat(document.getElementById("birth_utc_offset").value);' +
|
||||
' var lat = parseFloat(document.getElementById("birth_lat").value);' +
|
||||
' var lon = parseFloat(document.getElementById("birth_lon").value);' +
|
||||
' var gender = parseInt(document.getElementById("gender").value, 10);' +
|
||||
' var lang = document.getElementById("lang").value;' +
|
||||
' var notifyEnabled = document.getElementById("notify_enabled").checked;' +
|
||||
' var notifyTime = document.getElementById("notify_time").value || "08:00";' +
|
||||
@@ -105,6 +124,7 @@ var FORM_SCRIPT =
|
||||
' birth_utc_offset_minutes: Math.round(utcOffset * 60),' +
|
||||
' birth_lat_microdeg: Math.round(lat * 1000000),' +
|
||||
' birth_lon_microdeg: Math.round(lon * 1000000),' +
|
||||
' gender: gender,' +
|
||||
' lang: lang,' +
|
||||
' notify_enabled: notifyEnabled ? 1 : 0,' +
|
||||
' notify_hour: parseInt(notifyParts[0], 10),' +
|
||||
@@ -121,6 +141,7 @@ var FORM_SCRIPT =
|
||||
|
||||
function buildConfigHtml(saved) {
|
||||
var lang = saved.lang || 'en';
|
||||
var gender = typeof saved.gender === 'number' ? saved.gender : 0;
|
||||
var notifyChecked = saved.notify_enabled ? ' checked' : '';
|
||||
return '<!doctype html>' +
|
||||
'<html><head><meta charset="utf-8">' +
|
||||
@@ -162,6 +183,8 @@ function buildConfigHtml(saved) {
|
||||
'<input type="number" id="birth_lon" step="0.0001" min="-180" max="180" value="' +
|
||||
escapeHtml(saved.birth_lon) + '" required></div>' +
|
||||
'</div>' +
|
||||
'<label>Gender (used for reading pronouns)</label>' +
|
||||
'<select id="gender">' + buildGenderOptions(gender) + '</select>' +
|
||||
'<h2>Display</h2>' +
|
||||
'<label>Language</label>' +
|
||||
'<select id="lang">' + buildLanguageOptions(lang) + '</select>' +
|
||||
@@ -205,6 +228,7 @@ Pebble.addEventListener('webviewclosed', function (e) {
|
||||
birth_utc_offset: result.saved_birth_utc_offset,
|
||||
birth_lat: result.saved_birth_lat,
|
||||
birth_lon: result.saved_birth_lon,
|
||||
gender: result.gender,
|
||||
lang: result.lang,
|
||||
notify_enabled: result.notify_enabled === 1,
|
||||
notify_time: result.saved_notify_time
|
||||
@@ -219,6 +243,7 @@ Pebble.addEventListener('webviewclosed', function (e) {
|
||||
BIRTH_UTC_OFFSET_MINUTES: result.birth_utc_offset_minutes,
|
||||
BIRTH_LAT_MICRODEG: result.birth_lat_microdeg,
|
||||
BIRTH_LON_MICRODEG: result.birth_lon_microdeg,
|
||||
GENDER: result.gender,
|
||||
LANG: result.lang,
|
||||
NOTIFY_ENABLED: result.notify_enabled,
|
||||
NOTIFY_HOUR: result.notify_hour,
|
||||
|
||||
Reference in New Issue
Block a user