Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
# Phone IP used by `mise install-phone`
IP=192.168.1.100

# Enable extra heap debug logs in Pebble C builds
# ENABLE_MEMORY_LOGGING=1


##################################
# Supabase environment variables #
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ If you use Supabase GitHub sync/branching, Supabase can auto-apply migrations an

`mise build` and `mise build release` automatically generate `package.json` from the template/profile before building.

If you want the extra Pebble heap debug logs, set `ENABLE_MEMORY_LOGGING=1` in your `.env` before building or installing. This is independent of the dev/release package profile.

Release notification copy (optional “what’s new” toast on upgrade) lives in `release-notifications.json`, keyed by the exact `version` string from the template (e.g. `"1.26.0"`). `prepare-package` copies only the entry for the version being built into `package.json`; versions with no key ship without a notification.

If you want to regenerate `package.json` without building:
Expand Down
13 changes: 10 additions & 3 deletions src/c/layers/battery_layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

static Layer *s_battery_layer;
static GBitmap *s_battery_power_bitmap;
static GColor *s_battery_palette;
static GColor s_battery_palette[2];

static void battery_state_handler(BatteryChargeState charge) {
battery_layer_refresh();
Expand Down Expand Up @@ -77,18 +77,26 @@ static void battery_update_proc(Layer *layer, GContext *ctx) {
}

void battery_layer_create(Layer* parent_layer, GRect frame) {
MemoryHeapProbe probe = MEMORY_HEAP_PROBE_START("battery_layer_create");

s_battery_layer = layer_create(frame);
MEMORY_HEAP_PROBE_SAMPLE("after_layer_create", &probe);

s_battery_power_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_BATTERY_CHARGING);
MEMORY_HEAP_PROBE_SAMPLE("after_bitmap_create", &probe);

s_battery_palette = malloc(2 * sizeof(GColor));
s_battery_palette[0] = GColorWhite;
s_battery_palette[1] = GColorClear;
gbitmap_set_palette(s_battery_power_bitmap, s_battery_palette, false);
MEMORY_HEAP_PROBE_SAMPLE("after_palette_set", &probe);

layer_set_update_proc(s_battery_layer, battery_update_proc);
battery_state_service_subscribe(battery_state_handler);
MEMORY_HEAP_PROBE_SAMPLE("after_battery_subscribe", &probe);
layer_add_child(parent_layer, s_battery_layer);
MEMORY_HEAP_PROBE_SAMPLE("after_layer_add_child", &probe);
MEMORY_LOG_HEAP("after_battery_layer_create");
MEMORY_HEAP_PROBE_LOG_MIN(&probe);
}

void battery_layer_refresh() {
Expand All @@ -98,7 +106,6 @@ void battery_layer_refresh() {
void battery_layer_destroy() {
MEMORY_LOG_HEAP("battery_layer_destroy:before");
battery_state_service_unsubscribe();
free(s_battery_palette);
gbitmap_destroy(s_battery_power_bitmap);
layer_destroy(s_battery_layer);
MEMORY_LOG_HEAP("battery_layer_destroy:after");
Expand Down
75 changes: 24 additions & 51 deletions src/c/layers/calendar_layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
#define FONT_OFFSET 5

static Layer *s_calendar_layer;
static TextLayer *s_calendar_text_layers[NUM_WEEKS * DAYS_PER_WEEK];

static GRect calendar_cell_rect(GRect bounds, int i) {
float box_w = (float) bounds.size.w / DAYS_PER_WEEK;
float box_h = (float) bounds.size.h / NUM_WEEKS;
return GRect((i % DAYS_PER_WEEK) * box_w, (i / DAYS_PER_WEEK) * box_h - FONT_OFFSET,
box_w, box_h + FONT_OFFSET);
}

/* Copy struct tm out of localtime's static buffer — see localtime(3). */
static struct tm relative_tm(int days_from_today)
Expand Down Expand Up @@ -106,26 +112,27 @@ static void calendar_update_proc(Layer *layer, GContext *ctx) {
graphics_fill_rect(ctx,
GRect((i_today % DAYS_PER_WEEK) * box_w, (i_today / DAYS_PER_WEEK) * box_h,
box_w, box_h), 1, GCornersAll);

for (int i = 0; i < NUM_WEEKS * DAYS_PER_WEEK; ++i) {
struct tm t = relative_tm(i - i_today);
bool highlight_holiday = (config_highlight_holidays() && is_us_federal_holiday(&t));
bool highlight_sunday = (config_highlight_sundays() && t.tm_wday == 0);
bool highlight_saturday = (config_highlight_saturdays() && t.tm_wday == 6);
bool bold = (i == i_today) || highlight_holiday || highlight_sunday || highlight_saturday;
GColor text_color = (i == i_today) ? gcolor_legible_over(today_color())
: PBL_IF_COLOR_ELSE(date_color(&t), GColorWhite);
char buffer[4];

graphics_context_set_text_color(ctx, text_color);
graphics_draw_text(ctx,
(snprintf(buffer, sizeof(buffer), "%d", t.tm_mday), buffer),
fonts_get_system_font(bold ? FONT_KEY_GOTHIC_18_BOLD : FONT_KEY_GOTHIC_18),
calendar_cell_rect(bounds, i), GTextOverflowModeFill, GTextAlignmentCenter, NULL);
}
}

void calendar_layer_create(Layer* parent_layer, GRect frame) {
s_calendar_layer = layer_create(frame);
GRect bounds = layer_get_bounds(s_calendar_layer);
int w = bounds.size.w;
int h = bounds.size.h;
float box_w = (float) w / DAYS_PER_WEEK;
float box_h = (float) h / NUM_WEEKS;

for (int i = 0; i < NUM_WEEKS * DAYS_PER_WEEK; ++i) {
// Place a text box in that space
TextLayer *s_box_text_layer = text_layer_create(
GRect((i % DAYS_PER_WEEK) * box_w, (i / DAYS_PER_WEEK) * box_h - FONT_OFFSET,
box_w, box_h + FONT_OFFSET));
text_layer_set_background_color(s_box_text_layer, GColorClear);
text_layer_set_text_alignment(s_box_text_layer, GTextAlignmentCenter);
s_calendar_text_layers[i] = s_box_text_layer;
layer_add_child(s_calendar_layer, text_layer_get_layer(s_box_text_layer));
}
layer_set_update_proc(s_calendar_layer, calendar_update_proc);
calendar_layer_refresh();
layer_add_child(parent_layer, s_calendar_layer);
Expand All @@ -134,46 +141,12 @@ void calendar_layer_create(Layer* parent_layer, GRect frame) {


void calendar_layer_refresh() {
static char s_calendar_box_buffers[NUM_WEEKS * DAYS_PER_WEEK][4];
// Request redraw (of today's highlight)
layer_mark_dirty(s_calendar_layer);

// Calculate which box holds today's date
const int i_today = config_n_today();

// Fill each box with an appropriate relative day number
for (int i = 0; i < NUM_WEEKS * DAYS_PER_WEEK; ++i) {
char *buffer = s_calendar_box_buffers[i];
struct tm t = relative_tm(i - i_today);

// Set the text color
if (i == i_today) {
GColor text_color = gcolor_legible_over(today_color());
text_layer_set_text_color(s_calendar_text_layers[i], text_color);
}
else {
GColor text_color = PBL_IF_COLOR_ELSE(date_color(&t), GColorWhite);
text_layer_set_text_color(s_calendar_text_layers[i], text_color);
}

// Use bold font for today, and holidays/weekends if colored
bool highlight_holiday = (config_highlight_holidays() && is_us_federal_holiday(&t));
bool highlight_sunday = (config_highlight_sundays() && t.tm_wday == 0);
bool highlight_saturday = (config_highlight_saturdays() && t.tm_wday == 6);
bool bold = (i == i_today) || highlight_holiday || highlight_sunday || highlight_saturday;
text_layer_set_font(s_calendar_text_layers[i],
fonts_get_system_font(bold ? FONT_KEY_GOTHIC_18_BOLD : FONT_KEY_GOTHIC_18));

snprintf(buffer, 4, "%d", t.tm_mday);
text_layer_set_text(s_calendar_text_layers[i], buffer);
}
}

void calendar_layer_destroy() {
MEMORY_LOG_HEAP("calendar_layer_destroy:before");
for (int i = 0; i < NUM_WEEKS * DAYS_PER_WEEK; ++i) {
text_layer_destroy(s_calendar_text_layers[i]);
}
layer_destroy(s_calendar_layer);
MEMORY_LOG_HEAP("calendar_layer_destroy:after");
}
93 changes: 50 additions & 43 deletions src/c/layers/calendar_status_layer.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,85 +16,101 @@ static TextLayer *s_calendar_month_layer;
static GBitmap *s_mute_bitmap;
static GBitmap *s_bt_bitmap;
static GBitmap *s_bt_disconnect_bitmap;
static BitmapLayer *s_mute_bitmap_layer;
static BitmapLayer *s_bt_bitmap_layer;
static BitmapLayer *s_bt_disconnect_bitmap_layer;
static GColor *s_bt_palette;
static GColor *s_bt_disconnect_palette;
static GColor *s_mute_palette;

static GColor s_bt_palette[2];
static GColor s_bt_disconnect_palette[2];
static GColor s_mute_palette[2];

static void draw_bitmap(GContext *ctx, GBitmap *bitmap, GRect frame) {
graphics_context_set_compositing_mode(ctx, GCompOpSet);
graphics_draw_bitmap_in_rect(ctx, bitmap, frame);
graphics_context_set_compositing_mode(ctx, GCompOpAssign);
}

static void bitmap_layer_move_frame(BitmapLayer *bitmap_layer, GRect frame) {
layer_set_frame(bitmap_layer_get_layer(bitmap_layer), frame);
static void calendar_status_update_proc(Layer *layer, GContext *ctx) {
bool show_qt = show_qt_icon();
bool connected = connection_service_peek_pebble_app_connection();
int icon_x = show_qt ? ICON_SLOT_2.origin.x : ICON_SLOT_1.origin.x;

if (show_qt) {
draw_bitmap(ctx, s_mute_bitmap, ICON_SLOT_1);
}

if (connected && g_config->show_bt) {
draw_bitmap(ctx, s_bt_bitmap, GRect(icon_x, 0, 10, 10));
} else if (!connected && g_config->show_bt_disconnect) {
draw_bitmap(ctx, s_bt_disconnect_bitmap, GRect(icon_x, 0, 10, 10));
}
}

void calendar_status_layer_create(Layer* parent_layer, GRect frame) {
MemoryHeapProbe probe = MEMORY_HEAP_PROBE_START("calendar_status_layer_create");

s_calendar_status_layer = layer_create(frame);
MEMORY_HEAP_PROBE_SAMPLE("after_layer_create", &probe);

GRect bounds = layer_get_bounds(s_calendar_status_layer);
int w = bounds.size.w;
int h = bounds.size.h;

// Set up icons
s_mute_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_MUTE);
MEMORY_HEAP_PROBE_SAMPLE("after_mute_bitmap", &probe);

s_bt_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_BT_CONNECT);
MEMORY_HEAP_PROBE_SAMPLE("after_bt_bitmap", &probe);

s_bt_disconnect_bitmap = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_BT_DISCONNECT);
MEMORY_HEAP_PROBE_SAMPLE("after_bt_disconnect_bitmap", &probe);

// Set up color palette
s_bt_palette = malloc(2*sizeof(GColor));
s_bt_palette[0] = PBL_IF_COLOR_ELSE(GColorPictonBlue, GColorWhite);
s_bt_palette[1] = GColorClear;
gbitmap_set_palette(s_bt_bitmap, s_bt_palette, false);
MEMORY_HEAP_PROBE_SAMPLE("after_bt_palette", &probe);

s_bt_disconnect_palette = malloc(2*sizeof(GColor));
s_bt_disconnect_palette[0] = PBL_IF_COLOR_ELSE(GColorRed, GColorWhite);
s_bt_disconnect_palette[1] = GColorClear;
gbitmap_set_palette(s_bt_disconnect_bitmap, s_bt_disconnect_palette, false);
MEMORY_HEAP_PROBE_SAMPLE("after_bt_disconnect_palette", &probe);

s_mute_palette = malloc(2*sizeof(GColor));
s_mute_palette[0] = GColorWhite;
s_mute_palette[1] = GColorClear;
gbitmap_set_palette(s_mute_bitmap, s_mute_palette, false);

s_mute_bitmap_layer = bitmap_layer_create(ICON_SLOT_1);
bitmap_layer_set_compositing_mode(s_mute_bitmap_layer, GCompOpSet);
bitmap_layer_set_bitmap(s_mute_bitmap_layer, s_mute_bitmap);

s_bt_bitmap_layer = bitmap_layer_create(ICON_SLOT_2);
bitmap_layer_set_compositing_mode(s_bt_bitmap_layer, GCompOpSet);
bitmap_layer_set_bitmap(s_bt_bitmap_layer, s_bt_bitmap);

s_bt_disconnect_bitmap_layer = bitmap_layer_create(ICON_SLOT_2);
bitmap_layer_set_compositing_mode(s_bt_disconnect_bitmap_layer, GCompOpSet);
bitmap_layer_set_bitmap(s_bt_disconnect_bitmap_layer, s_bt_disconnect_bitmap);

MEMORY_HEAP_PROBE_SAMPLE("after_mute_palette", &probe);

// Set up month text layer
s_calendar_month_layer = text_layer_create(GRect(0, -MONTH_FONT_OFFSET, w, 25));
text_layer_set_background_color(s_calendar_month_layer, GColorClear);
text_layer_set_text_alignment(s_calendar_month_layer, GTextAlignmentCenter);
text_layer_set_text_color(s_calendar_month_layer, GColorWhite);
text_layer_set_font(s_calendar_month_layer, fonts_get_system_font(FONT_KEY_GOTHIC_18));
MEMORY_HEAP_PROBE_SAMPLE("after_month_text_layer", &probe);

// Set up bluetooth handler
connection_service_subscribe((ConnectionHandlers) {
.pebble_app_connection_handler = bluetooth_callback
});
MEMORY_HEAP_PROBE_SAMPLE("after_connection_subscribe", &probe);

calendar_status_layer_refresh();
layer_add_child(s_calendar_status_layer, bitmap_layer_get_layer(s_mute_bitmap_layer));
layer_add_child(s_calendar_status_layer, bitmap_layer_get_layer(s_bt_bitmap_layer));
layer_add_child(s_calendar_status_layer, bitmap_layer_get_layer(s_bt_disconnect_bitmap_layer));
layer_add_child(s_calendar_status_layer, text_layer_get_layer(s_calendar_month_layer));
MEMORY_HEAP_PROBE_SAMPLE("after_month_child_added", &probe);

layer_set_update_proc(s_calendar_status_layer, calendar_status_update_proc);
MEMORY_HEAP_PROBE_SAMPLE("after_update_proc_set", &probe);

battery_layer_create(s_calendar_status_layer, GRect(w - BATTERY_W - PADDING, 1, BATTERY_W, BATTERY_H));
MEMORY_HEAP_PROBE_SAMPLE("after_battery_layer_create", &probe);

layer_add_child(parent_layer, s_calendar_status_layer);
MEMORY_HEAP_PROBE_SAMPLE("after_parent_child_added", &probe);

MEMORY_LOG_HEAP("after_calendar_status_layer_create");
MEMORY_HEAP_PROBE_LOG_MIN(&probe);
}

void bluetooth_icons_refresh(bool connected) {
bool show_bt = connected && g_config->show_bt;
bool show_bt_disconnect = !connected && g_config->show_bt_disconnect;
layer_set_hidden(bitmap_layer_get_layer(s_bt_bitmap_layer), !show_bt);
layer_set_hidden(bitmap_layer_get_layer(s_bt_disconnect_bitmap_layer), !show_bt_disconnect);
(void)connected;
layer_mark_dirty(s_calendar_status_layer);
}

void bluetooth_callback(bool connected) {
Expand All @@ -108,10 +124,7 @@ bool show_qt_icon() {
}

void status_icons_refresh() {
bool show_qt = show_qt_icon();
layer_set_hidden(bitmap_layer_get_layer(s_mute_bitmap_layer), !show_qt);
bitmap_layer_move_frame(s_bt_bitmap_layer, show_qt ? ICON_SLOT_2 : ICON_SLOT_1);
bitmap_layer_move_frame(s_bt_disconnect_bitmap_layer, show_qt ? ICON_SLOT_2 : ICON_SLOT_1);
layer_mark_dirty(s_calendar_status_layer);

// Ensure bt icons are correct at start
bluetooth_icons_refresh(connection_service_peek_pebble_app_connection());
Expand All @@ -130,15 +143,9 @@ void calendar_status_layer_refresh() {
void calendar_status_layer_destroy() {
MEMORY_LOG_HEAP("calendar_status_layer_destroy:before");
battery_layer_destroy();
free(s_bt_palette);
free(s_bt_disconnect_palette);
free(s_mute_palette);
gbitmap_destroy(s_mute_bitmap);
gbitmap_destroy(s_bt_bitmap);
gbitmap_destroy(s_bt_disconnect_bitmap);
bitmap_layer_destroy(s_mute_bitmap_layer);
bitmap_layer_destroy(s_bt_bitmap_layer);
bitmap_layer_destroy(s_bt_disconnect_bitmap_layer);
layer_destroy(s_calendar_status_layer);
MEMORY_LOG_HEAP("calendar_status_layer_destroy:after");
}
Loading