From 0840f22e8df17d25a65b69e9e6966602428c30a4 Mon Sep 17 00:00:00 2001 From: Subinita Ray Date: Sat, 4 Jul 2026 23:40:39 +0530 Subject: [PATCH 1/3] feat(pin): add step progress bar to PIN setup flow Adds theme_create_progress_bar(), a slim lv_bar indicator anchored below a page title, and wires it into pin_page.c's build_chrome() so the 4-step first-time PIN setup (choose, confirm, split, show words) shows current progress. Gated on current_mode != PIN_PAGE_UNLOCK so it never appears on unlock or the delay/wipe screen. --- main/pages/pin/pin_page.c | 28 ++++++++++++++++++++++++++++ main/ui/theme_widgets.c | 25 +++++++++++++++++++++++++ main/ui/theme_widgets.h | 5 +++++ 3 files changed, 58 insertions(+) diff --git a/main/pages/pin/pin_page.c b/main/pages/pin/pin_page.c index 4c5a741..076c6a2 100644 --- a/main/pages/pin/pin_page.c +++ b/main/pages/pin/pin_page.c @@ -375,9 +375,37 @@ static void create_back_or_power_button(void) { ui_create_power_button(page_screen, power_btn_cb); } +// Number of visible screens in the first-time PIN setup flow (choose, +// confirm, split, show words). STATE_SETUP_EFUSE is a dialog overlay, not a +// screen, so it doesn't get a step. +#define PIN_SETUP_STEP_COUNT 4 + +static int32_t setup_step_for_state(pin_flow_state_t state) { + switch (state) { + case STATE_SETUP_FULL_PIN: + return 1; + case STATE_SETUP_CONFIRM_PIN: + return 2; + case STATE_SETUP_SPLIT: + return 3; + case STATE_SETUP_SHOW_WORDS: + return 4; + 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); + + // Setup-only: never shown during unlock or the delay/wipe screen. + if (current_mode != PIN_PAGE_UNLOCK) { + int32_t step = setup_step_for_state(current_state); + if (step > 0) + theme_create_progress_bar(page_screen, title_label, step, + PIN_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..0805f3c 100644 --- a/main/ui/theme_widgets.c +++ b/main/ui/theme_widgets.c @@ -200,6 +200,31 @@ 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) { + if (!parent || total <= 0) + return NULL; + + lv_obj_t *bar = lv_bar_create(parent); + int32_t thickness = theme_min_dim() / 100 + 2; + lv_obj_set_size(bar, LV_PCT(60), 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); + + if (anchor) + lv_obj_align_to(bar, anchor, LV_ALIGN_OUT_BOTTOM_MID, 0, + theme_small_padding()); + else + lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, theme_default_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..f578c10 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); pass NULL to anchor to +// the top of `parent` instead. +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); From 5f09a418659f34c40834dfe34e3f168a5f72ed0b Mon Sep 17 00:00:00 2001 From: Subinita Ray Date: Sun, 5 Jul 2026 03:01:56 +0530 Subject: [PATCH 2/3] style(pin): shrink setup progress bar Narrow the bar from 60% to 40% width and thin its track to match, so it reads as a subtle indicator rather than competing with the title. --- main/ui/theme_widgets.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/ui/theme_widgets.c b/main/ui/theme_widgets.c index 0805f3c..f9a47b3 100644 --- a/main/ui/theme_widgets.c +++ b/main/ui/theme_widgets.c @@ -206,8 +206,8 @@ lv_obj_t *theme_create_progress_bar(lv_obj_t *parent, lv_obj_t *anchor, return NULL; lv_obj_t *bar = lv_bar_create(parent); - int32_t thickness = theme_min_dim() / 100 + 2; - lv_obj_set_size(bar, LV_PCT(60), thickness); + 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); From be21307b597bf8735a9064ce8754beed4391de97 Mon Sep 17 00:00:00 2001 From: Subinita Ray Date: Fri, 10 Jul 2026 02:41:56 +0530 Subject: [PATCH 3/3] fix(pin): address review feedback on setup progress bar Drop STATE_SETUP_SHOW_WORDS from the step count since it's only reached when eFuse provisioning is accepted and succeeds, so the bar no longer promises an unreachable 4th step when it's declined or fails. Replace the disconnected #define + switch with an enum so the total is derived from the same switch that assigns steps. Remove the redundant PIN_PAGE_UNLOCK mode guard in build_chrome: every unlock/delay/wipe state already maps to 0, so step > 0 was already doing all the work. Require a non-NULL anchor in theme_create_progress_bar via LV_ASSERT_NULL instead of a silent NULL-tolerant fallback, since the only caller always passes the page title and the fallback's offset landed inside the corner-button band. --- main/pages/pin/pin_page.c | 36 ++++++++++++++++++++---------------- main/ui/theme_widgets.c | 8 +++----- main/ui/theme_widgets.h | 4 ++-- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/main/pages/pin/pin_page.c b/main/pages/pin/pin_page.c index 076c6a2..52a7fc7 100644 --- a/main/pages/pin/pin_page.c +++ b/main/pages/pin/pin_page.c @@ -375,21 +375,27 @@ static void create_back_or_power_button(void) { ui_create_power_button(page_screen, power_btn_cb); } -// Number of visible screens in the first-time PIN setup flow (choose, -// confirm, split, show words). STATE_SETUP_EFUSE is a dialog overlay, not a -// screen, so it doesn't get a step. -#define PIN_SETUP_STEP_COUNT 4 +// 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 1; + return SETUP_STEP_FULL_PIN; case STATE_SETUP_CONFIRM_PIN: - return 2; + return SETUP_STEP_CONFIRM_PIN; case STATE_SETUP_SPLIT: - return 3; - case STATE_SETUP_SHOW_WORDS: - return 4; + return SETUP_STEP_SPLIT; default: return 0; } @@ -399,13 +405,11 @@ static void build_chrome(const char *title_text) { create_back_or_power_button(); title_label = theme_create_page_title(page_screen, title_text); - // Setup-only: never shown during unlock or the delay/wipe screen. - if (current_mode != PIN_PAGE_UNLOCK) { - int32_t step = setup_step_for_state(current_state); - if (step > 0) - theme_create_progress_bar(page_screen, title_label, step, - PIN_SETUP_STEP_COUNT); - } + // 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 f9a47b3..9c78b11 100644 --- a/main/ui/theme_widgets.c +++ b/main/ui/theme_widgets.c @@ -202,6 +202,7 @@ lv_obj_t *theme_create_page_title(lv_obj_t *parent, const char *text) { 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; @@ -217,11 +218,8 @@ lv_obj_t *theme_create_progress_bar(lv_obj_t *parent, lv_obj_t *anchor, lv_obj_set_style_radius(bar, LV_RADIUS_CIRCLE, LV_PART_INDICATOR); lv_obj_clear_flag(bar, LV_OBJ_FLAG_CLICKABLE); - if (anchor) - lv_obj_align_to(bar, anchor, LV_ALIGN_OUT_BOTTOM_MID, 0, - theme_small_padding()); - else - lv_obj_align(bar, LV_ALIGN_TOP_MID, 0, theme_default_padding()); + lv_obj_align_to(bar, anchor, LV_ALIGN_OUT_BOTTOM_MID, 0, + theme_small_padding()); return bar; } diff --git a/main/ui/theme_widgets.h b/main/ui/theme_widgets.h index f578c10..294dc2a 100644 --- a/main/ui/theme_widgets.h +++ b/main/ui/theme_widgets.h @@ -20,8 +20,8 @@ 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); pass NULL to anchor to -// the top of `parent` instead. +// 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);