diff --git a/main/pages/pin/pin_page.c b/main/pages/pin/pin_page.c index 4c5a741..52a7fc7 100644 --- a/main/pages/pin/pin_page.c +++ b/main/pages/pin/pin_page.c @@ -375,9 +375,41 @@ static void create_back_or_power_button(void) { ui_create_power_button(page_screen, power_btn_cb); } +// Steps of the PIN-construction wizard (choose, confirm, split). Verifying +// the current PIN (change mode) and showing anti-phishing words (deferred +// eFuse epilogue) are uncounted bookends: STATE_SETUP_SHOW_WORDS only runs +// when eFuse provisioning is accepted and succeeds, so it isn't a guaranteed +// step. STATE_SETUP_EFUSE is a dialog overlay, not a screen. Adding a setup +// screen means adding an enum member here; SETUP_STEP_COUNT tracks it. +enum { + SETUP_STEP_FULL_PIN = 1, + SETUP_STEP_CONFIRM_PIN, + SETUP_STEP_SPLIT, + SETUP_STEP_COUNT = SETUP_STEP_SPLIT, +}; + +static int32_t setup_step_for_state(pin_flow_state_t state) { + switch (state) { + case STATE_SETUP_FULL_PIN: + return SETUP_STEP_FULL_PIN; + case STATE_SETUP_CONFIRM_PIN: + return SETUP_STEP_CONFIRM_PIN; + case STATE_SETUP_SPLIT: + return SETUP_STEP_SPLIT; + default: + return 0; + } +} + static void build_chrome(const char *title_text) { create_back_or_power_button(); title_label = theme_create_page_title(page_screen, title_text); + + // Unlock, delay, and wipe states all map to 0; only setup screens get a + // bar. + int32_t step = setup_step_for_state(current_state); + if (step > 0) + theme_create_progress_bar(page_screen, title_label, step, SETUP_STEP_COUNT); } static void build_entry_state(const char *title_text) { diff --git a/main/ui/theme_widgets.c b/main/ui/theme_widgets.c index 25db64f..9c78b11 100644 --- a/main/ui/theme_widgets.c +++ b/main/ui/theme_widgets.c @@ -200,6 +200,29 @@ lv_obj_t *theme_create_page_title(lv_obj_t *parent, const char *text) { return label; } +lv_obj_t *theme_create_progress_bar(lv_obj_t *parent, lv_obj_t *anchor, + int32_t current, int32_t total) { + LV_ASSERT_NULL(anchor); + if (!parent || total <= 0) + return NULL; + + lv_obj_t *bar = lv_bar_create(parent); + int32_t thickness = theme_min_dim() / 150 + 1; + lv_obj_set_size(bar, LV_PCT(40), thickness); + lv_bar_set_range(bar, 0, total); + lv_bar_set_value(bar, current, LV_ANIM_OFF); + lv_obj_set_style_bg_color(bar, disabled_color(), LV_PART_MAIN); + lv_obj_set_style_bg_opa(bar, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_bg_color(bar, accent_color(), LV_PART_INDICATOR); + lv_obj_set_style_radius(bar, LV_RADIUS_CIRCLE, LV_PART_MAIN); + lv_obj_set_style_radius(bar, LV_RADIUS_CIRCLE, LV_PART_INDICATOR); + lv_obj_clear_flag(bar, LV_OBJ_FLAG_CLICKABLE); + + lv_obj_align_to(bar, anchor, LV_ALIGN_OUT_BOTTOM_MID, 0, + theme_small_padding()); + return bar; +} + void theme_apply_transparent_container(lv_obj_t *obj) { if (!obj) return; diff --git a/main/ui/theme_widgets.h b/main/ui/theme_widgets.h index a03a906..294dc2a 100644 --- a/main/ui/theme_widgets.h +++ b/main/ui/theme_widgets.h @@ -19,6 +19,11 @@ lv_obj_t *theme_create_button(lv_obj_t *parent, const char *text, lv_obj_t *theme_create_label(lv_obj_t *parent, const char *text, bool is_secondary); lv_obj_t *theme_create_page_title(lv_obj_t *parent, const char *text); +// Slim step indicator for multi-screen flows (e.g. PIN setup). Aligns +// directly below `anchor` (typically the page title). `anchor` must be +// non-NULL (asserted); returns NULL if `parent` is NULL or `total` <= 0. +lv_obj_t *theme_create_progress_bar(lv_obj_t *parent, lv_obj_t *anchor, + int32_t current, int32_t total); void theme_apply_transparent_container(lv_obj_t *obj); lv_obj_t *theme_create_scroll_column(lv_obj_t *parent, int32_t pad, int32_t gap);