Migrate to standard action bar; prep for prev/next buttons

This commit is contained in:
2026-07-15 00:03:09 -04:00
parent 8f9c79f641
commit b6ad2e5831
13 changed files with 89 additions and 189 deletions
-4
View File
@@ -1,4 +0,0 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22 18L16 21L11 20L5 18L3 14L9 13L12 2H16L20 5L17 15L22 18Z" fill="white"/>
<path d="M12 17L10 24M9 13L3 14L5 18L16 21L22 18L17 15M9 13L13 13L17 15M9 13L12 2H16L20 5L17 15" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 366 B

-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 20.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.2" baseProfile="tiny" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px" viewBox="0 0 25 23" xml:space="preserve">
<polygon fill="#FFFFFF" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
18,1 24,7 24,15 17,22 9,22 7,20 1,22 4,17 2,15 2,7 8,1 "/>
<polyline fill="#FFFFFF" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
9,8 16,8 9,15 16,15 "/>
</svg>

Before

Width:  |  Height:  |  Size: 690 B

+20 -12
View File
@@ -38,20 +38,28 @@
"name": "LOGO_25", "name": "LOGO_25",
"spaceOptimization": "storage", "spaceOptimization": "storage",
"type": "bitmap", "type": "bitmap",
"menuIcon": true, "menuIcon": true
"targetPlatforms": [
"basalt",
"diorite",
"chalk",
"flint",
"emery",
"gabbro"
]
}, },
{ {
"type": "raw", "file": "pin.png",
"name": "PIN_ICON", "memoryFormat": "Smallest",
"file": "pin.pdc" "name": "PIN",
"spaceOptimization": "storage",
"type": "bitmap"
},
{
"file": "prev.png",
"memoryFormat": "Smallest",
"name": "PREV",
"spaceOptimization": "storage",
"type": "bitmap"
},
{
"file": "next.png",
"memoryFormat": "Smallest",
"name": "NEXT",
"spaceOptimization": "storage",
"type": "bitmap"
} }
] ]
}, },
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 B

-66
View File
@@ -1,66 +0,0 @@
#include "gdraw_anim.h"
static uint8_t s_pin_stroke_width = 1;
static Animation *s_pin_stroke_anim;
static Layer *s_pin_layer;
static uint8_t get_stroke_width_for_progress(AnimationProgress progress) {
const uint8_t min_width = PBL_IF_ROUND_ELSE(1, 2);
const uint8_t max_width = PBL_IF_ROUND_ELSE(3, 4);
const uint8_t delta = max_width - min_width;
const AnimationProgress half = ANIMATION_NORMALIZED_MAX / 2;
if (progress <= half) {
return min_width + (uint8_t)(delta * progress * 2 / ANIMATION_NORMALIZED_MAX);
}
return max_width - (uint8_t)(delta * (progress - half) * 2 / ANIMATION_NORMALIZED_MAX);
}
static void pin_stroke_anim_update(Animation *animation, const AnimationProgress progress) {
s_pin_stroke_width = get_stroke_width_for_progress(progress);
if (s_pin_layer) {
layer_mark_dirty(s_pin_layer);
}
}
static void pin_stroke_anim_stopped(Animation *animation, bool finished, void *context) {
s_pin_stroke_anim = NULL;
animation_destroy(animation);
}
void pin_animation_init(Layer *pin_layer) {
s_pin_layer = pin_layer;
s_pin_stroke_width = PBL_IF_ROUND_ELSE(1, 2);
}
void pin_animation_start(void) {
if (s_pin_stroke_anim) {
animation_unschedule(s_pin_stroke_anim);
animation_destroy(s_pin_stroke_anim);
s_pin_stroke_anim = NULL;
}
static const AnimationImplementation s_pin_stroke_anim_impl = {
.update = pin_stroke_anim_update,
};
s_pin_stroke_anim = animation_create();
animation_set_implementation(s_pin_stroke_anim, &s_pin_stroke_anim_impl);
animation_set_duration(s_pin_stroke_anim, 400);
animation_set_curve(s_pin_stroke_anim, AnimationCurveEaseInOut);
animation_set_handlers(s_pin_stroke_anim, (AnimationHandlers){.stopped = pin_stroke_anim_stopped}, NULL);
animation_schedule(s_pin_stroke_anim);
}
void pin_animation_deinit(void) {
if (s_pin_stroke_anim) {
animation_unschedule(s_pin_stroke_anim);
animation_destroy(s_pin_stroke_anim);
s_pin_stroke_anim = NULL;
}
s_pin_layer = NULL;
}
uint8_t pin_animation_get_stroke_width(void) {
return s_pin_stroke_width;
}
-8
View File
@@ -1,8 +0,0 @@
#pragma once
#include <pebble.h>
void pin_animation_init(Layer *pin_layer);
void pin_animation_deinit(void);
void pin_animation_start(void);
uint8_t pin_animation_get_stroke_width(void);
+69 -90
View File
@@ -1,29 +1,31 @@
#include "gdraw_anim.h"
#include <pebble.h> #include <pebble.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
// #define DEBUG_MODE 1 #define DEBUG_MODE 1
// declare settings-derived statics // declare settings-derived statics
static uint8_t s_is_jellyfin; static uint8_t s_is_jellyfin;
static GColor s_color_primary = GColorWhite; static GColor s_color_primary = GColorWhite;
static GColor s_color_secondary = GColorWhite; static GColor s_color_secondary = GColorWhite;
// declare bitmaps
static GBitmap *s_logo_icon;
static GBitmap *s_pin_icon;
static GBitmap *s_next_icon;
static GBitmap *s_prev_icon;
// declare window/layer statics // declare window/layer statics
static Window *s_main_window; static Window *s_main_window;
static TextLayer *s_config_app_layer; static TextLayer *s_config_app_layer;
static TextLayer *s_sleep_time_layer; static TextLayer *s_sleep_time_layer;
static TextLayer *s_last_watched_layer; static TextLayer *s_last_watched_layer;
static Layer *s_button_bar_layer; static ActionBarLayer *s_action_bar_layer;
static Layer *s_sleep_bar_layer; static Layer *s_sleep_bar_layer;
static BitmapLayer *s_logo_layer; static BitmapLayer *s_logo_layer;
static Layer *s_pin_icon_layer; static const uint8_t s_logo_width = 25;
static GBitmap *s_logo;
static GDrawCommandImage *s_pin_icon;
static const uint8_t s_icon_width = 25;
#if PBL_ROUND #if PBL_ROUND
#if PBL_DISPLAY_WIDTH >= 200 #if PBL_DISPLAY_WIDTH >= 260
static const uint8_t s_sleep_bar_drop = 54; static const uint8_t s_sleep_bar_drop = 54;
#else #else
static const uint8_t s_sleep_bar_drop = 50; static const uint8_t s_sleep_bar_drop = 50;
@@ -39,7 +41,7 @@ static const uint8_t s_sleep_bar_drop = 34;
// declare animation statics // declare animation statics
static GRect s_sleep_bar_start = GRect(0, 0, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT); static GRect s_sleep_bar_start = GRect(0, 0, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT);
static GRect s_logo_start = GRect((PBL_DISPLAY_WIDTH / 2) - 13, (PBL_DISPLAY_HEIGHT / 2) - 17, s_icon_width, s_icon_width); static GRect s_logo_start = GRect((PBL_DISPLAY_WIDTH / 2) - 13, (PBL_DISPLAY_HEIGHT / 2) - 17, s_logo_width, s_logo_width);
// declare time tracking statics // declare time tracking statics
static time_t s_sleep_timestamp; static time_t s_sleep_timestamp;
@@ -67,10 +69,9 @@ static void write_last_watched(const char *text) {
y = s_sleep_bar_drop - s_wasted_top_text_pixels + s_margin; y = s_sleep_bar_drop - s_wasted_top_text_pixels + s_margin;
} }
layer_set_frame(text_layer_get_layer(s_last_watched_layer), GRect(PBL_IF_ROUND_ELSE(s_icon_width, 0) + s_margin, GRect last_watched_frame = layer_get_frame(text_layer_get_layer(s_last_watched_layer));
y, last_watched_frame.origin.y = y;
PBL_DISPLAY_WIDTH - (PBL_IF_ROUND_ELSE(s_icon_width * 2, s_icon_width) + (s_margin) * 2), layer_set_frame(text_layer_get_layer(s_last_watched_layer), last_watched_frame);
s_available_height));
// APP_LOG(APP_LOG_LEVEL_DEBUG, "height=%d rows=%d/%d", text_size.h, current_rows, s_max_rows); // APP_LOG(APP_LOG_LEVEL_DEBUG, "height=%d rows=%d/%d", text_size.h, current_rows, s_max_rows);
} }
@@ -95,25 +96,46 @@ static void sleep_bar_update_proc(Layer *layer, GContext *ctx) {
graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone); graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone);
} }
static void draw_pin() { static void send_pin_to_pkjs() {
GDrawCommandList *command_list = gdraw_command_image_get_command_list(s_pin_icon); DictionaryIterator *out;
const uint32_t command_count = gdraw_command_list_get_num_commands(command_list); AppMessageResult result = app_message_outbox_begin(&out);
for (uint32_t i = 0; i < command_count; ++i) { if (result != APP_MSG_OK) {
GDrawCommand *command = gdraw_command_list_get_command(command_list, i); write_last_watched("outbox_begin failure");
if (command) { return;
#if PBL_ROUND }
gdraw_command_set_fill_color(command, GColorClear); if (s_sleep_session_found) {
gdraw_command_set_stroke_color(command, s_color_secondary); dict_write_uint32(out, MESSAGE_KEY_PKJS_PIN_TIMESTAMP, s_sleep_timestamp);
#endif } else {
gdraw_command_set_stroke_width(command, pin_animation_get_stroke_width()); dict_write_uint32(out, MESSAGE_KEY_PKJS_PIN_TIMESTAMP, time(NULL));
} }
dict_write_cstring(out, MESSAGE_KEY_PKJS_PIN_TITLE, text_layer_get_text(s_last_watched_layer));
result = app_message_outbox_send();
if (result != APP_MSG_OK) {
write_last_watched("outbox_send failure");
return;
} }
} }
static void pin_icon_update_proc(Layer *layer, GContext *ctx) { static void select_click_handler(ClickRecognizerRef recognizer, void *context) {
graphics_context_set_antialiased(ctx, false); send_pin_to_pkjs();
draw_pin(); static const uint32_t segments[] = {60};
gdraw_command_image_draw(ctx, s_pin_icon, GPoint(0, 0)); VibePattern pattern = {
.durations = segments,
.num_segments = ARRAY_LENGTH(segments),
};
vibes_enqueue_custom_pattern(pattern);
}
void up_click_handler(ClickRecognizerRef recognizer, void *context) {
}
void down_click_handler(ClickRecognizerRef recognizer, void *context) {
}
void click_config_provider(void *context) {
window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler);
window_single_click_subscribe(BUTTON_ID_UP, up_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler);
} }
static void main_window_load(Window *window) { static void main_window_load(Window *window) {
@@ -134,18 +156,23 @@ static void main_window_load(Window *window) {
return; return;
} }
// action bar
s_action_bar_layer = action_bar_layer_create();
action_bar_layer_set_click_config_provider(s_action_bar_layer, click_config_provider);
action_bar_layer_set_icon(s_action_bar_layer, BUTTON_ID_UP, s_prev_icon);
action_bar_layer_set_icon(s_action_bar_layer, BUTTON_ID_SELECT, s_pin_icon);
action_bar_layer_set_icon(s_action_bar_layer, BUTTON_ID_DOWN, s_next_icon);
action_bar_layer_set_background_color(s_action_bar_layer, s_color_secondary);
action_bar_layer_set_icon_press_animation(s_action_bar_layer, BUTTON_ID_SELECT, ActionBarLayerIconPressAnimationMoveLeft);
action_bar_layer_add_to_window(s_action_bar_layer, window);
// sleep bar and icon // sleep bar and icon
s_sleep_bar_layer = layer_create(s_sleep_bar_start); s_sleep_bar_layer = layer_create(s_sleep_bar_start);
s_logo_layer = bitmap_layer_create(s_logo_start); s_logo_layer = bitmap_layer_create(s_logo_start);
bitmap_layer_set_compositing_mode(s_logo_layer, GCompOpSet); bitmap_layer_set_compositing_mode(s_logo_layer, GCompOpSet);
bitmap_layer_set_bitmap(s_logo_layer, s_logo); bitmap_layer_set_bitmap(s_logo_layer, s_logo_icon);
// pin icon s_last_watched_layer = text_layer_create(GRect(s_margin, 0, PBL_DISPLAY_WIDTH - (2 * s_margin) - ACTION_BAR_WIDTH, s_available_height)); // y position updated when layer is written to
s_pin_icon_layer = layer_create(GRect(PBL_DISPLAY_WIDTH - PBL_IF_ROUND_ELSE(s_icon_width + 3, s_icon_width), (PBL_DISPLAY_HEIGHT / 2) - 13, s_icon_width, s_icon_width));
layer_set_update_proc(s_pin_icon_layer, pin_icon_update_proc);
pin_animation_init(s_pin_icon_layer);
s_last_watched_layer = text_layer_create(GRect(0, 0, 0, 0)); // placeholder, updated when layer is written to
#if PBL_DISPLAY_WIDTH >= 200 #if PBL_DISPLAY_WIDTH >= 200
s_sleep_time_layer = text_layer_create(GRect(0, PBL_IF_ROUND_ELSE(22, 2), PBL_DISPLAY_WIDTH, 30)); s_sleep_time_layer = text_layer_create(GRect(0, PBL_IF_ROUND_ELSE(22, 2), PBL_DISPLAY_WIDTH, 30));
text_layer_set_font(s_sleep_time_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); text_layer_set_font(s_sleep_time_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD));
@@ -195,13 +222,6 @@ static void main_window_load(Window *window) {
} }
// add layers as children to windows // add layers as children to windows
#if PBL_RECT
// button bar
s_button_bar_layer = layer_create(GRect(PBL_DISPLAY_WIDTH - s_icon_width, 0, s_icon_width, PBL_DISPLAY_HEIGHT));
layer_set_update_proc(s_button_bar_layer, button_bar_update_proc);
layer_add_child(window_layer, s_button_bar_layer);
#endif
layer_add_child(window_layer, s_pin_icon_layer);
layer_add_child(window_layer, text_layer_get_layer(s_last_watched_layer)); layer_add_child(window_layer, text_layer_get_layer(s_last_watched_layer));
layer_add_child(window_layer, s_sleep_bar_layer); layer_add_child(window_layer, s_sleep_bar_layer);
layer_add_child(window_layer, bitmap_layer_get_layer(s_logo_layer)); layer_add_child(window_layer, bitmap_layer_get_layer(s_logo_layer));
@@ -212,13 +232,11 @@ static void main_window_unload(Window *window) {
if (s_is_jellyfin == 0) { if (s_is_jellyfin == 0) {
text_layer_destroy(s_config_app_layer); text_layer_destroy(s_config_app_layer);
} else { } else {
pin_animation_deinit();
text_layer_destroy(s_last_watched_layer); text_layer_destroy(s_last_watched_layer);
text_layer_destroy(s_sleep_time_layer); text_layer_destroy(s_sleep_time_layer);
bitmap_layer_destroy(s_logo_layer); bitmap_layer_destroy(s_logo_layer);
layer_destroy(s_sleep_bar_layer); layer_destroy(s_sleep_bar_layer);
layer_destroy(s_pin_icon_layer); action_bar_layer_destroy(s_action_bar_layer);
layer_destroy(s_button_bar_layer);
} }
} }
@@ -245,41 +263,6 @@ static void send_sleep_time_to_pkjs() {
} }
} }
static void send_pin_to_pkjs() {
DictionaryIterator *out;
AppMessageResult result = app_message_outbox_begin(&out);
if (result != APP_MSG_OK) {
write_last_watched("outbox_begin failure");
return;
}
if (s_sleep_session_found) {
dict_write_uint32(out, MESSAGE_KEY_PKJS_PIN_TIMESTAMP, s_sleep_timestamp);
} else {
dict_write_uint32(out, MESSAGE_KEY_PKJS_PIN_TIMESTAMP, time(NULL));
}
dict_write_cstring(out, MESSAGE_KEY_PKJS_PIN_TITLE, text_layer_get_text(s_last_watched_layer));
result = app_message_outbox_send();
if (result != APP_MSG_OK) {
write_last_watched("outbox_send failure");
return;
}
}
static void select_single_click_handler(ClickRecognizerRef recognizer, void *context) {
pin_animation_start();
send_pin_to_pkjs();
static const uint32_t segments[] = {60};
VibePattern pattern = {
.durations = segments,
.num_segments = ARRAY_LENGTH(segments),
};
vibes_enqueue_custom_pattern(pattern);
}
static void click_config_provider(Window *window) {
window_single_click_subscribe(BUTTON_ID_SELECT, select_single_click_handler);
}
static void soft_reload(bool first_load) { static void soft_reload(bool first_load) {
if (!first_load) { if (!first_load) {
window_stack_remove(s_main_window, false); window_stack_remove(s_main_window, false);
@@ -291,7 +274,6 @@ static void soft_reload(bool first_load) {
s_main_window, s_main_window,
(WindowHandlers){.load = main_window_load, .unload = main_window_unload}); (WindowHandlers){.load = main_window_load, .unload = main_window_unload});
window_stack_push(s_main_window, false); window_stack_push(s_main_window, false);
window_set_click_config_provider(s_main_window, (ClickConfigProvider)click_config_provider);
send_sleep_time_to_pkjs(); send_sleep_time_to_pkjs();
} }
@@ -322,11 +304,6 @@ static void inbox_received_handler(DictionaryIterator *iter, void *context) {
s_color_secondary = GColorLightGray; s_color_secondary = GColorLightGray;
} }
layer_mark_dirty(s_sleep_bar_layer); layer_mark_dirty(s_sleep_bar_layer);
#if PBL_ROUND
layer_mark_dirty(s_pin_icon_layer);
#else
layer_mark_dirty(s_button_bar_layer);
#endif
soft_reload(false); // app needs "restarted" when settings are changed soft_reload(false); // app needs "restarted" when settings are changed
return; return;
} }
@@ -337,7 +314,7 @@ static void inbox_received_handler(DictionaryIterator *iter, void *context) {
PropertyAnimation *sleep_bar_prop = property_animation_create_layer_frame(s_sleep_bar_layer, &s_sleep_bar_start, &GRect(0, -PBL_DISPLAY_HEIGHT + s_sleep_bar_drop, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT)); PropertyAnimation *sleep_bar_prop = property_animation_create_layer_frame(s_sleep_bar_layer, &s_sleep_bar_start, &GRect(0, -PBL_DISPLAY_HEIGHT + s_sleep_bar_drop, PBL_DISPLAY_WIDTH, PBL_DISPLAY_HEIGHT));
Animation *sleep_bar_anim = property_animation_get_animation(sleep_bar_prop); Animation *sleep_bar_anim = property_animation_get_animation(sleep_bar_prop);
animation_set_duration(sleep_bar_anim, 512); animation_set_duration(sleep_bar_anim, 512);
PropertyAnimation *logo_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_logo_layer), &s_logo_start, &GRect(PBL_IF_ROUND_ELSE((PBL_DISPLAY_WIDTH / 2) - 13, s_margin), 3, s_icon_width, s_icon_width)); PropertyAnimation *logo_prop = property_animation_create_layer_frame(bitmap_layer_get_layer(s_logo_layer), &s_logo_start, &GRect(PBL_IF_ROUND_ELSE((PBL_DISPLAY_WIDTH / 2) - 13, s_margin), 3, s_logo_width, s_logo_width));
Animation *logo_anim = property_animation_get_animation(logo_prop); Animation *logo_anim = property_animation_get_animation(logo_prop);
animation_set_duration(logo_anim, 512); animation_set_duration(logo_anim, 512);
Animation *load_spawn_anim = animation_spawn_create(sleep_bar_anim, logo_anim, NULL); Animation *load_spawn_anim = animation_spawn_create(sleep_bar_anim, logo_anim, NULL);
@@ -370,8 +347,10 @@ static void init() {
s_color_primary = GColorWhite; s_color_primary = GColorWhite;
s_color_secondary = GColorWhite; s_color_secondary = GColorWhite;
#endif #endif
s_pin_icon = gdraw_command_image_create_with_resource(RESOURCE_ID_PIN_ICON); s_logo_icon = gbitmap_create_with_resource(RESOURCE_ID_LOGO_25);
s_logo = gbitmap_create_with_resource(RESOURCE_ID_LOGO_25); s_pin_icon = gbitmap_create_with_resource(RESOURCE_ID_PIN);
s_next_icon = gbitmap_create_with_resource(RESOURCE_ID_NEXT);
s_prev_icon = gbitmap_create_with_resource(RESOURCE_ID_PREV);
// initialize pkjs // initialize pkjs
app_message_register_inbox_received(inbox_received_handler); app_message_register_inbox_received(inbox_received_handler);