diff --git a/core/embed/rust/src/ui/api/firmware_upy.rs b/core/embed/rust/src/ui/api/firmware_upy.rs index 5fcceb3995..6318327674 100644 --- a/core/embed/rust/src/ui/api/firmware_upy.rs +++ b/core/embed/rust/src/ui/api/firmware_upy.rs @@ -227,6 +227,21 @@ extern "C" fn new_set_brightness(n_args: usize, args: *const Obj, kwargs: *mut M unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { + let block = move |_args: &[Obj], kwargs: &Map| { + let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; + let button: TString = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; + let active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?; + let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; + + let items: [TString<'static>; 3] = util::iter_into_array(items)?; + + let layout = ModelUI::show_checklist(title, button, active, items)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + extern "C" fn new_show_homescreen(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let label: TString<'static> = kwargs @@ -543,6 +558,17 @@ pub static mp_module_trezorui_api: Module = obj_module! { /// """Show the brightness configuration dialog.""" Qstr::MP_QSTR_set_brightness => obj_fn_kw!(0, new_set_brightness).as_obj(), + /// def show_checklist( + /// *, + /// title: str, + /// items: Iterable[str], + /// active: int, + /// button: str, + /// ) -> LayoutObj[UiResult]: + /// """Checklist of backup steps. Active index is highlighted, previous items have check + /// mark next to them. Limited to 3 items.""" + Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(), + /// def show_homescreen( /// *, /// label: str | None, diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index 5d01b0996e..00ba1a6dba 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -1050,47 +1050,6 @@ extern "C" fn new_prompt_backup() -> Obj { unsafe { util::try_or_raise(block) } } -extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let _button: TString = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - let active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecLong::new(); - for (i, item) in IterBuf::new().try_iterate(items)?.enumerate() { - let style = match i.cmp(&active) { - Ordering::Less => &theme::TEXT_CHECKLIST_DONE, - Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED, - Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT, - }; - let text: TString = item.try_into()?; - paragraphs.add(Paragraph::new(style, text)); - } - - let checklist_content = Checklist::from_paragraphs( - theme::ICON_CHEVRON_RIGHT, - theme::ICON_BULLET_CHECKMARK, - active, - paragraphs - .into_paragraphs() - .with_spacing(theme::CHECKLIST_SPACING), - ) - .with_check_width(theme::CHECKLIST_CHECK_WIDTH) - .with_numerals() - .with_icon_done_color(theme::GREEN) - .with_done_offset(theme::CHECKLIST_DONE_OFFSET); - - let obj = LayoutObj::new(SwipeUpScreen::new( - Frame::left_aligned(title, SwipeContent::new(checklist_content)) - .with_footer(TR::instructions__swipe_up.into(), None) - .with_swipe(Direction::Up, SwipeSettings::default()), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_show_tutorial() -> Obj { let block = || { let flow = flow::show_tutorial::new_show_tutorial()?; @@ -1389,17 +1348,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// confirmation.""" Qstr::MP_QSTR_flow_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), - /// def show_checklist( - /// *, - /// title: str, - /// items: Iterable[str], - /// active: int, - /// button: str, - /// ) -> LayoutObj[UiResult]: - /// """Checklist of backup steps. Active index is highlighted, previous items have check - /// mark next to them.""" - Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(), - /// def flow_continue_recovery( /// *, /// first_screen: bool, diff --git a/core/embed/rust/src/ui/model_mercury/ui_features_fw.rs b/core/embed/rust/src/ui/model_mercury/ui_features_fw.rs index 0d54d1b1b7..13e7cd9866 100644 --- a/core/embed/rust/src/ui/model_mercury/ui_features_fw.rs +++ b/core/embed/rust/src/ui/model_mercury/ui_features_fw.rs @@ -1,3 +1,5 @@ +use core::cmp::Ordering; + use crate::{ error::{value_error, Error}, io::BinaryData, @@ -8,7 +10,10 @@ use crate::{ component::{ connect::Connect, swipe_detect::SwipeSettings, - text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs}, + text::paragraphs::{ + Checklist, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort, + Paragraphs, VecExt, + }, CachedJpeg, ComponentExt, Never, Timeout, }, geometry::Direction, @@ -215,6 +220,43 @@ impl UIFeaturesFirmware for ModelMercuryFeatures { Ok(flow) } + fn show_checklist( + title: TString<'static>, + button: TString<'static>, + active: usize, + items: [TString<'static>; 3], + ) -> Result { + let mut paragraphs = ParagraphVecLong::new(); + for (i, item) in items.into_iter().enumerate() { + let style = match i.cmp(&active) { + Ordering::Less => &theme::TEXT_CHECKLIST_DONE, + Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED, + Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT, + }; + paragraphs.add(Paragraph::new(style, item)); + } + + let checklist_content = Checklist::from_paragraphs( + theme::ICON_CHEVRON_RIGHT, + theme::ICON_BULLET_CHECKMARK, + active, + paragraphs + .into_paragraphs() + .with_spacing(theme::CHECKLIST_SPACING), + ) + .with_check_width(theme::CHECKLIST_CHECK_WIDTH) + .with_numerals() + .with_icon_done_color(theme::GREEN) + .with_done_offset(theme::CHECKLIST_DONE_OFFSET); + + let layout = RootComponent::new(SwipeUpScreen::new( + Frame::left_aligned(title, SwipeContent::new(checklist_content)) + .with_footer(TR::instructions__swipe_up.into(), None) + .with_swipe(Direction::Up, SwipeSettings::default()), + )); + Ok(layout) + } + fn show_homescreen( label: TString<'static>, hold: bool, diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index c8de9d6aac..6b9e3df134 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -1205,46 +1205,6 @@ extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } -extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let button: TString<'static> = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - let active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecLong::new(); - for (i, item) in IterBuf::new().try_iterate(items)?.enumerate() { - let style = match i.cmp(&active) { - Ordering::Less => &theme::TEXT_NORMAL, - Ordering::Equal => &theme::TEXT_BOLD, - Ordering::Greater => &theme::TEXT_NORMAL, - }; - let text: TString = item.try_into()?; - paragraphs.add(Paragraph::new(style, text)); - } - - let confirm_btn = Some(ButtonDetails::text(button)); - - let obj = LayoutObj::new( - ButtonPage::new( - Checklist::from_paragraphs( - theme::ICON_ARROW_RIGHT_FAT, - theme::ICON_TICK_FAT, - active, - paragraphs - .into_paragraphs() - .with_spacing(theme::CHECKLIST_SPACING), - ) - .with_check_width(theme::CHECKLIST_CHECK_WIDTH) - .with_current_offset(theme::CHECKLIST_CURRENT_OFFSET), - theme::BG, - ) - .with_confirm_btn(confirm_btn), - )?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; @@ -1548,17 +1508,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Shows a backup seed.""" Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), - /// def show_checklist( - /// *, - /// title: str, # unused on TR - /// items: Iterable[str], - /// active: int, - /// button: str, - /// ) -> LayoutObj[UiResult]: - /// """Checklist of backup steps. Active index is highlighted, previous items have check - /// mark next to them.""" - Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(), - /// def confirm_recovery( /// *, /// title: str, # unused on TR diff --git a/core/embed/rust/src/ui/model_tr/ui_features_fw.rs b/core/embed/rust/src/ui/model_tr/ui_features_fw.rs index 3c86922833..df57b5c5b4 100644 --- a/core/embed/rust/src/ui/model_tr/ui_features_fw.rs +++ b/core/embed/rust/src/ui/model_tr/ui_features_fw.rs @@ -1,3 +1,5 @@ +use core::cmp::Ordering; + use crate::{ error::Error, io::BinaryData, @@ -8,7 +10,10 @@ use crate::{ ui::{ component::{ connect::Connect, - text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt}, + text::paragraphs::{ + Checklist, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort, + Paragraphs, VecExt, + }, Component, ComponentExt, Empty, Label, LineBreaking, Paginate, Timeout, }, layout::{ @@ -219,6 +224,42 @@ impl UIFeaturesFirmware for ModelTRFeatures { )) } + fn show_checklist( + title: TString<'static>, + button: TString<'static>, + active: usize, + items: [TString<'static>; 3], + ) -> Result { + let mut paragraphs = ParagraphVecLong::new(); + for (i, item) in items.into_iter().enumerate() { + let style = match i.cmp(&active) { + Ordering::Less => &theme::TEXT_NORMAL, + Ordering::Equal => &theme::TEXT_BOLD, + Ordering::Greater => &theme::TEXT_NORMAL, + }; + paragraphs.add(Paragraph::new(style, item)); + } + let confirm_btn = Some(ButtonDetails::text(button)); + + let layout = RootComponent::new( + ButtonPage::new( + Checklist::from_paragraphs( + theme::ICON_ARROW_RIGHT_FAT, + theme::ICON_TICK_FAT, + active, + paragraphs + .into_paragraphs() + .with_spacing(theme::CHECKLIST_SPACING), + ) + .with_check_width(theme::CHECKLIST_CHECK_WIDTH) + .with_current_offset(theme::CHECKLIST_CURRENT_OFFSET), + theme::BG, + ) + .with_confirm_btn(confirm_btn), + ); + Ok(layout) + } + fn show_homescreen( label: TString<'static>, hold: bool, diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index 8c32f975ed..572bc5ca45 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -1144,49 +1144,6 @@ extern "C" fn new_show_share_words(n_args: usize, args: *const Obj, kwargs: *mut unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } -extern "C" fn new_show_checklist(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - let block = move |_args: &[Obj], kwargs: &Map| { - let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let button: TString = kwargs.get(Qstr::MP_QSTR_button)?.try_into()?; - let active: usize = kwargs.get(Qstr::MP_QSTR_active)?.try_into()?; - let items: Obj = kwargs.get(Qstr::MP_QSTR_items)?; - - let mut paragraphs = ParagraphVecLong::new(); - for (i, item) in IterBuf::new().try_iterate(items)?.enumerate() { - let style = match i.cmp(&active) { - Ordering::Less => &theme::TEXT_CHECKLIST_DONE, - Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED, - Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT, - }; - let text: TString = item.try_into()?; - paragraphs.add(Paragraph::new(style, text)); - } - - let obj = LayoutObj::new(Frame::left_aligned( - theme::label_title(), - title, - Dialog::new( - Checklist::from_paragraphs( - theme::ICON_LIST_CURRENT, - theme::ICON_LIST_CHECK, - active, - paragraphs - .into_paragraphs() - .with_spacing(theme::CHECKLIST_SPACING), - ) - .with_check_width(theme::CHECKLIST_CHECK_WIDTH) - .with_current_offset(theme::CHECKLIST_CURRENT_OFFSET) - .with_done_offset(theme::CHECKLIST_DONE_OFFSET), - theme::button_bar(Button::with_text(button).map(|msg| { - (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) - })), - ), - ))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_confirm_recovery(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { let block = move |_args: &[Obj], kwargs: &Map| { let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; @@ -1511,17 +1468,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Show mnemonic for backup. Expects the words pre-divided into individual pages.""" Qstr::MP_QSTR_show_share_words => obj_fn_kw!(0, new_show_share_words).as_obj(), - /// def show_checklist( - /// *, - /// title: str, - /// items: Iterable[str], - /// active: int, - /// button: str, - /// ) -> LayoutObj[UiResult]: - /// """Checklist of backup steps. Active index is highlighted, previous items have check - /// mark next to them.""" - Qstr::MP_QSTR_show_checklist => obj_fn_kw!(0, new_show_checklist).as_obj(), - /// def confirm_recovery( /// *, /// title: str, diff --git a/core/embed/rust/src/ui/model_tt/ui_features_fw.rs b/core/embed/rust/src/ui/model_tt/ui_features_fw.rs index e5083249e6..64eef877fb 100644 --- a/core/embed/rust/src/ui/model_tt/ui_features_fw.rs +++ b/core/embed/rust/src/ui/model_tt/ui_features_fw.rs @@ -1,3 +1,5 @@ +use core::cmp::Ordering; + use crate::{ error::{value_error, Error}, io::BinaryData, @@ -8,7 +10,10 @@ use crate::{ component::{ connect::Connect, image::BlendedImage, - text::paragraphs::{Paragraph, ParagraphSource, ParagraphVecShort, Paragraphs, VecExt}, + text::paragraphs::{ + Checklist, Paragraph, ParagraphSource, ParagraphVecLong, ParagraphVecShort, + Paragraphs, VecExt, + }, ComponentExt, Empty, Jpeg, Label, Never, Timeout, }, layout::{ @@ -240,6 +245,45 @@ impl UIFeaturesFirmware for ModelTTFeatures { Ok(layout) } + fn show_checklist( + title: TString<'static>, + button: TString<'static>, + active: usize, + items: [TString<'static>; 3], + ) -> Result { + let mut paragraphs = ParagraphVecLong::new(); + for (i, item) in items.into_iter().enumerate() { + let style = match i.cmp(&active) { + Ordering::Less => &theme::TEXT_CHECKLIST_DONE, + Ordering::Equal => &theme::TEXT_CHECKLIST_SELECTED, + Ordering::Greater => &theme::TEXT_CHECKLIST_DEFAULT, + }; + paragraphs.add(Paragraph::new(style, item)); + } + + let layout = RootComponent::new(Frame::left_aligned( + theme::label_title(), + title, + Dialog::new( + Checklist::from_paragraphs( + theme::ICON_LIST_CURRENT, + theme::ICON_LIST_CHECK, + active, + paragraphs + .into_paragraphs() + .with_spacing(theme::CHECKLIST_SPACING), + ) + .with_check_width(theme::CHECKLIST_CHECK_WIDTH) + .with_current_offset(theme::CHECKLIST_CURRENT_OFFSET) + .with_done_offset(theme::CHECKLIST_DONE_OFFSET), + theme::button_bar(Button::with_text(button).map(|msg| { + (matches!(msg, ButtonMsg::Clicked)).then(|| CancelConfirmMsg::Confirmed) + })), + ), + )); + Ok(layout) + } + fn show_homescreen( label: TString<'static>, hold: bool, diff --git a/core/embed/rust/src/ui/ui_features_fw.rs b/core/embed/rust/src/ui/ui_features_fw.rs index 46bf6d4428..1f6d8b307d 100644 --- a/core/embed/rust/src/ui/ui_features_fw.rs +++ b/core/embed/rust/src/ui/ui_features_fw.rs @@ -75,6 +75,13 @@ pub trait UIFeaturesFirmware { fn set_brightness(current_brightness: Option) -> Result; + fn show_checklist( + title: TString<'static>, + button: TString<'static>, + active: usize, + items: [TString<'static>; 3], + ) -> Result; + fn show_homescreen( label: TString<'static>, hold: bool, diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 44ed2295fa..8e79661305 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -252,18 +252,6 @@ def flow_show_share_words( confirmation.""" -# rust/src/ui/model_mercury/layout.rs -def show_checklist( - *, - title: str, - items: Iterable[str], - active: int, - button: str, -) -> LayoutObj[UiResult]: - """Checklist of backup steps. Active index is highlighted, previous items have check - mark next to them.""" - - # rust/src/ui/model_mercury/layout.rs def flow_continue_recovery( *, @@ -613,18 +601,6 @@ def show_share_words( """Shows a backup seed.""" -# rust/src/ui/model_tr/layout.rs -def show_checklist( - *, - title: str, # unused on TR - items: Iterable[str], - active: int, - button: str, -) -> LayoutObj[UiResult]: - """Checklist of backup steps. Active index is highlighted, previous items have check - mark next to them.""" - - # rust/src/ui/model_tr/layout.rs def confirm_recovery( *, @@ -926,18 +902,6 @@ def show_share_words( """Show mnemonic for backup. Expects the words pre-divided into individual pages.""" -# rust/src/ui/model_tt/layout.rs -def show_checklist( - *, - title: str, - items: Iterable[str], - active: int, - button: str, -) -> LayoutObj[UiResult]: - """Checklist of backup steps. Active index is highlighted, previous items have check - mark next to them.""" - - # rust/src/ui/model_tt/layout.rs def confirm_recovery( *, diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi index 01b722898e..8f22ecfbee 100644 --- a/core/mocks/generated/trezorui_api.pyi +++ b/core/mocks/generated/trezorui_api.pyi @@ -196,6 +196,18 @@ def set_brightness( """Show the brightness configuration dialog.""" +# rust/src/ui/api/firmware_upy.rs +def show_checklist( + *, + title: str, + items: Iterable[str], + active: int, + button: str, +) -> LayoutObj[UiResult]: + """Checklist of backup steps. Active index is highlighted, previous items have check + mark next to them. Limited to 3 items.""" + + # rust/src/ui/api/firmware_upy.rs def show_homescreen( *, diff --git a/core/src/trezor/ui/layouts/mercury/reset.py b/core/src/trezor/ui/layouts/mercury/reset.py index d5ca027b66..47cfbd36c4 100644 --- a/core/src/trezor/ui/layouts/mercury/reset.py +++ b/core/src/trezor/ui/layouts/mercury/reset.py @@ -98,7 +98,7 @@ async def slip39_show_checklist( ) -> None: items = _slip_39_checklist_items(step, advanced, count, threshold) result = await interact( - trezorui2.show_checklist( + trezorui_api.show_checklist( title=TR.reset__title_shamir_backup, button=TR.buttons__continue, active=step, diff --git a/core/src/trezor/ui/layouts/tr/reset.py b/core/src/trezor/ui/layouts/tr/reset.py index 586d63c7bb..1c0197df32 100644 --- a/core/src/trezor/ui/layouts/tr/reset.py +++ b/core/src/trezor/ui/layouts/tr/reset.py @@ -122,7 +122,7 @@ def slip39_show_checklist( ) return raise_if_not_confirmed( - trezorui2.show_checklist( + trezorui_api.show_checklist( title=TR.reset__slip39_checklist_title, button=TR.buttons__continue, active=step, diff --git a/core/src/trezor/ui/layouts/tt/reset.py b/core/src/trezor/ui/layouts/tt/reset.py index 82b1a40c4c..9ee1c66140 100644 --- a/core/src/trezor/ui/layouts/tt/reset.py +++ b/core/src/trezor/ui/layouts/tt/reset.py @@ -120,7 +120,7 @@ def slip39_show_checklist( ) return raise_if_not_confirmed( - trezorui2.show_checklist( + trezorui_api.show_checklist( title=TR.reset__slip39_checklist_title, button=TR.buttons__continue, active=step,