diff --git a/core/embed/rust/src/ui/api/firmware_upy.rs b/core/embed/rust/src/ui/api/firmware_upy.rs index 76e57fdd4e..091e5e09d5 100644 --- a/core/embed/rust/src/ui/api/firmware_upy.rs +++ b/core/embed/rust/src/ui/api/firmware_upy.rs @@ -1,6 +1,8 @@ use crate::{ io::BinaryData, micropython::{ + gc::Gc, + list::List, macros::{obj_fn_1, obj_fn_kw, obj_module}, map::Map, module::Module, @@ -92,6 +94,19 @@ extern "C" fn new_confirm_coinjoin(n_args: usize, args: *const Obj, kwargs: *mut unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } +extern "C" fn new_confirm_fido(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 app_name: TString = kwargs.get(Qstr::MP_QSTR_app_name)?.try_into()?; + let icon: Option = kwargs.get(Qstr::MP_QSTR_icon_name)?.try_into_option()?; + let accounts: Gc = kwargs.get(Qstr::MP_QSTR_accounts)?.try_into()?; + + let layout = ModelUI::confirm_fido(title, app_name, icon, accounts)?; + Ok(LayoutObj::new_root(layout)?.into()) + }; + unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } +} + // TODO: there was `no_mangle` attribute in TT, should we apply it? extern "C" fn new_confirm_firmware_update( n_args: usize, @@ -676,6 +691,19 @@ pub static mp_module_trezorui_api: Module = obj_module! { /// """Confirm coinjoin authorization.""" Qstr::MP_QSTR_confirm_coinjoin => obj_fn_kw!(0, new_confirm_coinjoin).as_obj(), + /// def confirm_fido( + /// *, + /// title: str, + /// app_name: str, + /// icon_name: str | None, + /// accounts: list[str | None], + /// ) -> LayoutObj[int | UiResult]: + /// """FIDO confirmation. + /// + /// Returns page index in case of confirmation and CANCELLED otherwise. + /// """ + Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(), + /// def confirm_firmware_update( /// *, /// description: str, diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_fido.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_fido.rs index 6c2bf97324..02c87fd9df 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/confirm_fido.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_fido.rs @@ -1,6 +1,6 @@ use crate::{ error, - micropython::{gc::Gc, list::List, map::Map, obj::Obj, qstr::Qstr, util}, + micropython::{gc::Gc, list::List}, strutil::TString, translations::TR, ui::{ @@ -14,7 +14,6 @@ use crate::{ FlowController, FlowMsg, SwipeFlow, SwipePage, }, geometry::Direction, - layout::obj::LayoutObj, }, }; @@ -39,6 +38,7 @@ pub enum ConfirmFido { static CRED_SELECTED: AtomicUsize = AtomicUsize::new(0); static SINGLE_CRED: AtomicBool = AtomicBool::new(false); +const EXTRA_PADDING: i16 = 6; impl FlowController for ConfirmFido { #[inline] @@ -50,7 +50,7 @@ impl FlowController for ConfirmFido { match (self, direction) { (Self::Intro, Direction::Left) => Self::Menu.swipe(direction), (Self::Menu, Direction::Right) => { - if Self::single_cred() { + if single_cred() { Self::Details.swipe_right() } else { Self::Intro.swipe_right() @@ -69,7 +69,7 @@ impl FlowController for ConfirmFido { (_, FlowMsg::Info) => Self::Menu.goto(), (Self::Menu, FlowMsg::Choice(0)) => self.return_msg(FlowMsg::Cancelled), (Self::Menu, FlowMsg::Cancelled) => { - if Self::single_cred() { + if single_cred() { Self::Details.swipe_right() } else { Self::Intro.swipe_right() @@ -88,11 +88,6 @@ impl FlowController for ConfirmFido { } } -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, ConfirmFido::new_obj) } -} - fn footer_update_fn( content: &SwipeContent TString<'static>>>>, ctx: &mut EventCtx, @@ -103,124 +98,120 @@ fn footer_update_fn( footer.update_page_counter(ctx, current_page, Some(total_pages)); } -impl ConfirmFido { - const EXTRA_PADDING: i16 = 6; +fn single_cred() -> bool { + SINGLE_CRED.load(Ordering::Relaxed) +} - fn single_cred() -> bool { - SINGLE_CRED.load(Ordering::Relaxed) +pub fn new_confirm_fido( + title: TString<'static>, + app_name: TString<'static>, + icon_name: Option>, + accounts: Gc, +) -> Result { + let num_accounts = accounts.len(); + SINGLE_CRED.store(num_accounts <= 1, Ordering::Relaxed); + CRED_SELECTED.store(0, Ordering::Relaxed); + + let content_intro = Frame::left_aligned( + title, + SwipeContent::new(Paragraphs::new(Paragraph::new::( + &theme::TEXT_MAIN_GREY_LIGHT, + TR::fido__select_intro.into(), + ))), + ) + .with_menu_button() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_swipe(Direction::Up, SwipeSettings::default()) + .with_swipe(Direction::Right, SwipeSettings::immediate()) + .map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info)); + + // Closure to lazy-load the information on given page index. + // Done like this to allow arbitrarily many pages without + // the need of any allocation here in Rust. + let label_fn = move |page_index| { + let account = unwrap!(accounts.get(page_index)); + account + .try_into() + .unwrap_or_else(|_| TString::from_str("-")) + }; + + let content_choose_credential = Frame::left_aligned( + TR::fido__title_select_credential.into(), + SwipeContent::new(SwipePage::vertical(PagedVerticalMenu::new( + num_accounts, + label_fn, + ))), + ) + .with_subtitle(TR::fido__title_for_authentication.into()) + .with_menu_button() + .with_footer_page_hint( + TR::fido__more_credentials.into(), + TR::buttons__go_back.into(), + TR::instructions__swipe_up.into(), + TR::instructions__swipe_down.into(), + ) + .register_footer_update_fn(footer_update_fn) + .with_swipe(Direction::Down, SwipeSettings::default()) + .with_swipe(Direction::Right, SwipeSettings::immediate()) + .with_vertical_pages() + .map(|msg| match msg { + FrameMsg::Button(_) => Some(FlowMsg::Info), + FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), + }); + + let get_account = move || { + let current = CRED_SELECTED.load(Ordering::Relaxed); + let account = unwrap!(accounts.get(current)); + account.try_into().unwrap_or_else(|_| TString::from_str("")) + }; + let content_details = Frame::left_aligned( + TR::fido__title_credential_details.into(), + SwipeContent::new(FidoCredential::new(icon_name, app_name, get_account)), + ) + .with_footer(TR::instructions__swipe_up.into(), Some(title)) + .with_swipe(Direction::Up, SwipeSettings::default()) + .with_swipe(Direction::Right, SwipeSettings::immediate()); + let content_details = if single_cred() { + content_details.with_menu_button() + } else { + content_details.with_cancel_button() } + .map(|msg| match msg { + FrameMsg::Button(bm) => Some(bm), + _ => None, + }); - fn new_obj(_args: &[Obj], kwargs: &Map) -> Result { - let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; - let app_name: TString = kwargs.get(Qstr::MP_QSTR_app_name)?.try_into()?; - let icon_name: Option = kwargs.get(Qstr::MP_QSTR_icon_name)?.try_into_option()?; - let accounts: Gc = kwargs.get(Qstr::MP_QSTR_accounts)?.try_into()?; - let num_accounts = accounts.len(); - SINGLE_CRED.store(num_accounts <= 1, Ordering::Relaxed); - CRED_SELECTED.store(0, Ordering::Relaxed); - - let content_intro = Frame::left_aligned( - title, - SwipeContent::new(Paragraphs::new(Paragraph::new::( - &theme::TEXT_MAIN_GREY_LIGHT, - TR::fido__select_intro.into(), - ))), - ) + let content_tap = Frame::left_aligned(title, PromptScreen::new_tap_to_confirm()) .with_menu_button() - .with_footer(TR::instructions__swipe_up.into(), None) - .with_swipe(Direction::Up, SwipeSettings::default()) - .with_swipe(Direction::Right, SwipeSettings::immediate()) - .map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info)); - - // Closure to lazy-load the information on given page index. - // Done like this to allow arbitrarily many pages without - // the need of any allocation here in Rust. - let label_fn = move |page_index| { - let account = unwrap!(accounts.get(page_index)); - account - .try_into() - .unwrap_or_else(|_| TString::from_str("-")) - }; - - let content_choose_credential = Frame::left_aligned( - TR::fido__title_select_credential.into(), - SwipeContent::new(SwipePage::vertical(PagedVerticalMenu::new( - num_accounts, - label_fn, - ))), - ) - .with_subtitle(TR::fido__title_for_authentication.into()) - .with_menu_button() - .with_footer_page_hint( - TR::fido__more_credentials.into(), - TR::buttons__go_back.into(), - TR::instructions__swipe_up.into(), - TR::instructions__swipe_down.into(), - ) - .register_footer_update_fn(footer_update_fn) + .with_footer(TR::instructions__tap_to_confirm.into(), None) .with_swipe(Direction::Down, SwipeSettings::default()) .with_swipe(Direction::Right, SwipeSettings::immediate()) - .with_vertical_pages() .map(|msg| match msg { + FrameMsg::Content(PromptMsg::Confirmed) => Some(FlowMsg::Confirmed), FrameMsg::Button(_) => Some(FlowMsg::Info), - FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), - }); - - let get_account = move || { - let current = CRED_SELECTED.load(Ordering::Relaxed); - let account = unwrap!(accounts.get(current)); - account.try_into().unwrap_or_else(|_| TString::from_str("")) - }; - let content_details = Frame::left_aligned( - TR::fido__title_credential_details.into(), - SwipeContent::new(FidoCredential::new(icon_name, app_name, get_account)), - ) - .with_footer(TR::instructions__swipe_up.into(), Some(title)) - .with_swipe(Direction::Up, SwipeSettings::default()) - .with_swipe(Direction::Right, SwipeSettings::immediate()); - let content_details = if Self::single_cred() { - content_details.with_menu_button() - } else { - content_details.with_cancel_button() - } - .map(|msg| match msg { - FrameMsg::Button(bm) => Some(bm), _ => None, }); - let content_tap = Frame::left_aligned(title, PromptScreen::new_tap_to_confirm()) - .with_menu_button() - .with_footer(TR::instructions__tap_to_confirm.into(), None) - .with_swipe(Direction::Down, SwipeSettings::default()) - .with_swipe(Direction::Right, SwipeSettings::immediate()) - .map(|msg| match msg { - FrameMsg::Content(PromptMsg::Confirmed) => Some(FlowMsg::Confirmed), - FrameMsg::Button(_) => Some(FlowMsg::Info), - _ => None, - }); + let content_menu = Frame::left_aligned( + "".into(), + VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::buttons__cancel.into()), + ) + .with_cancel_button() + .with_swipe(Direction::Right, SwipeSettings::immediate()) + .map(|msg| match msg { + FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), + FrameMsg::Button(_) => Some(FlowMsg::Cancelled), + }); - let content_menu = Frame::left_aligned( - "".into(), - VerticalMenu::empty().danger(theme::ICON_CANCEL, TR::buttons__cancel.into()), - ) - .with_cancel_button() - .with_swipe(Direction::Right, SwipeSettings::immediate()) - .map(|msg| match msg { - FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => Some(FlowMsg::Choice(i)), - FrameMsg::Button(_) => Some(FlowMsg::Cancelled), - }); - - let initial_page = if Self::single_cred() { - &ConfirmFido::Details - } else { - &ConfirmFido::Intro - }; - let res = SwipeFlow::new(initial_page)? - .with_page(&ConfirmFido::Intro, content_intro)? - .with_page(&ConfirmFido::ChooseCredential, content_choose_credential)? - .with_page(&ConfirmFido::Details, content_details)? - .with_page(&ConfirmFido::Tap, content_tap)? - .with_page(&ConfirmFido::Menu, content_menu)?; - Ok(LayoutObj::new_root(res)?.into()) - } + let initial_page = if single_cred() { + &ConfirmFido::Details + } else { + &ConfirmFido::Intro + }; + SwipeFlow::new(initial_page)? + .with_page(&ConfirmFido::Intro, content_intro)? + .with_page(&ConfirmFido::ChooseCredential, content_choose_credential)? + .with_page(&ConfirmFido::Details, content_details)? + .with_page(&ConfirmFido::Tap, content_tap)? + .with_page(&ConfirmFido::Menu, content_menu) } diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index 8be8660391..880d61debe 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -785,13 +785,6 @@ extern "C" fn new_prompt_backup() -> Obj { unsafe { util::try_or_raise(block) } } -extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj { - #[cfg(feature = "universal_fw")] - return flow::confirm_fido::new_confirm_fido(n_args, args, kwargs); - #[cfg(not(feature = "universal_fw"))] - panic!(); -} - extern "C" fn new_warning_hi_prio(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()?; @@ -914,19 +907,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Transaction summary. Always hold to confirm.""" Qstr::MP_QSTR_confirm_total => obj_fn_kw!(0, new_confirm_total).as_obj(), - /// def confirm_fido( - /// *, - /// title: str, - /// app_name: str, - /// icon_name: str | None, - /// accounts: list[str | None], - /// ) -> LayoutObj[int | UiResult]: - /// """FIDO confirmation. - /// - /// Returns page index in case of confirmation and CANCELLED otherwise. - /// """ - Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(), - /// def confirm_with_info( /// *, /// title: str, 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 f05b732961..60582e67b8 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 @@ -3,7 +3,7 @@ use core::cmp::Ordering; use crate::{ error::{value_error, Error}, io::BinaryData, - micropython::{gc::Gc, util}, + micropython::{gc::Gc, list::List, util}, strutil::TString, translations::TR, ui::{ @@ -125,6 +125,22 @@ impl UIFeaturesFirmware for ModelMercuryFeatures { Ok(flow) } + fn confirm_fido( + title: TString<'static>, + app_name: TString<'static>, + icon: Option>, + accounts: Gc, + ) -> Result { + #[cfg(feature = "universal_fw")] + return Ok(flow::confirm_fido::new_confirm_fido( + title, app_name, icon, accounts, + )?); + #[cfg(not(feature = "universal_fw"))] + Err::, Error>(Error::ValueError( + c"confirm_fido not used in bitcoin-only firmware", + )) + } + fn confirm_firmware_update( description: TString<'static>, fingerprint: TString<'static>, diff --git a/core/embed/rust/src/ui/model_tr/layout.rs b/core/embed/rust/src/ui/model_tr/layout.rs index 1806c27d63..c0dfb29047 100644 --- a/core/embed/rust/src/ui/model_tr/layout.rs +++ b/core/embed/rust/src/ui/model_tr/layout.rs @@ -778,70 +778,6 @@ extern "C" fn new_multiple_pages_texts(n_args: usize, args: *const Obj, kwargs: unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } -extern "C" fn new_confirm_fido(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 app_name: TString = kwargs.get(Qstr::MP_QSTR_app_name)?.try_into()?; - let accounts: Gc = kwargs.get(Qstr::MP_QSTR_accounts)?.try_into()?; - - // Cache the page count so that we can move `accounts` into the closure. - let page_count = accounts.len(); - - // Closure to lazy-load the information on given page index. - // Done like this to allow arbitrarily many pages without - // the need of any allocation here in Rust. - let get_page = move |page_index| { - let account_obj = unwrap!(accounts.get(page_index)); - let account = TString::try_from(account_obj).unwrap_or_else(|_| TString::empty()); - - let (btn_layout, btn_actions) = if page_count == 1 { - // There is only one page - ( - ButtonLayout::cancel_none_text(TR::buttons__confirm.into()), - ButtonActions::cancel_none_confirm(), - ) - } else if page_index == 0 { - // First page - ( - ButtonLayout::cancel_armed_arrow(TR::buttons__select.into()), - ButtonActions::cancel_confirm_next(), - ) - } else if page_index == page_count - 1 { - // Last page - ( - ButtonLayout::arrow_armed_none(TR::buttons__select.into()), - ButtonActions::prev_confirm_none(), - ) - } else { - // Page in the middle - ( - ButtonLayout::arrow_armed_arrow(TR::buttons__select.into()), - ButtonActions::prev_confirm_next(), - ) - }; - - let ops = OpTextLayout::new(theme::TEXT_NORMAL) - .newline() - .text_normal(app_name) - .newline() - .text_bold(account); - let formatted = FormattedText::new(ops); - - Page::new(btn_layout, btn_actions, formatted) - }; - - let pages = FlowPages::new(get_page, page_count); - // Returning the page index in case of confirmation. - let obj = LayoutObj::new( - Flow::new(pages) - .with_common_title(title) - .with_return_confirmed_index(), - )?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_confirm_with_info(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()?; @@ -1050,19 +986,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Confirm details about altcoin transaction.""" Qstr::MP_QSTR_altcoin_tx_summary => obj_fn_kw!(0, new_altcoin_tx_summary).as_obj(), - /// def confirm_fido( - /// *, - /// title: str, - /// app_name: str, - /// icon_name: str | None, # unused on TR - /// accounts: list[str | None], - /// ) -> LayoutObj[int | UiResult]: - /// """FIDO confirmation. - /// - /// Returns page index in case of confirmation and CANCELLED otherwise. - /// """ - Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(), - /// def multiple_pages_texts( /// *, /// title: str, 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 f8dbfd2fd2..8b477d3f3a 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 @@ -4,7 +4,7 @@ use crate::{ error::Error, io::BinaryData, maybe_trace::MaybeTrace, - micropython::gc::Gc, + micropython::{gc::Gc, list::List}, strutil::TString, translations::TR, ui::{ @@ -113,6 +113,68 @@ impl UIFeaturesFirmware for ModelTRFeatures { ) } + fn confirm_fido( + title: TString<'static>, + app_name: TString<'static>, + icon: Option>, + accounts: Gc, + ) -> Result { + // Cache the page count so that we can move `accounts` into the closure. + let page_count = accounts.len(); + + // Closure to lazy-load the information on given page index. + // Done like this to allow arbitrarily many pages without + // the need of any allocation here in Rust. + let get_page = move |page_index| { + let account_obj = unwrap!(accounts.get(page_index)); + let account = TString::try_from(account_obj).unwrap_or_else(|_| TString::empty()); + + let (btn_layout, btn_actions) = if page_count == 1 { + // There is only one page + ( + ButtonLayout::cancel_none_text(TR::buttons__confirm.into()), + ButtonActions::cancel_none_confirm(), + ) + } else if page_index == 0 { + // First page + ( + ButtonLayout::cancel_armed_arrow(TR::buttons__select.into()), + ButtonActions::cancel_confirm_next(), + ) + } else if page_index == page_count - 1 { + // Last page + ( + ButtonLayout::arrow_armed_none(TR::buttons__select.into()), + ButtonActions::prev_confirm_none(), + ) + } else { + // Page in the middle + ( + ButtonLayout::arrow_armed_arrow(TR::buttons__select.into()), + ButtonActions::prev_confirm_next(), + ) + }; + + let ops = OpTextLayout::new(theme::TEXT_NORMAL) + .newline() + .text_normal(app_name) + .newline() + .text_bold(account); + let formatted = FormattedText::new(ops); + + Page::new(btn_layout, btn_actions, formatted) + }; + + let pages = FlowPages::new(get_page, page_count); + // Returning the page index in case of confirmation. + let obj = RootComponent::new( + Flow::new(pages) + .with_common_title(title) + .with_return_confirmed_index(), + ); + Ok(obj) + } + fn confirm_firmware_update( description: TString<'static>, fingerprint: TString<'static>, diff --git a/core/embed/rust/src/ui/model_tt/layout.rs b/core/embed/rust/src/ui/model_tt/layout.rs index feb0fb2ced..0c57550c7e 100644 --- a/core/embed/rust/src/ui/model_tt/layout.rs +++ b/core/embed/rust/src/ui/model_tt/layout.rs @@ -702,37 +702,6 @@ extern "C" fn new_confirm_total(n_args: usize, args: *const Obj, kwargs: *mut Ma unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } } -extern "C" fn new_confirm_fido(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 app_name: TString = kwargs.get(Qstr::MP_QSTR_app_name)?.try_into()?; - let icon: Option = kwargs.get(Qstr::MP_QSTR_icon_name)?.try_into_option()?; - let accounts: Gc = kwargs.get(Qstr::MP_QSTR_accounts)?.try_into()?; - - // Cache the page count so that we can move `accounts` into the closure. - let page_count = accounts.len(); - // Closure to lazy-load the information on given page index. - // Done like this to allow arbitrarily many pages without - // the need of any allocation here in Rust. - let get_page = move |page_index| { - let account = unwrap!(accounts.get(page_index)); - account.try_into().unwrap_or_else(|_| "".into()) - }; - - let controls = Button::cancel_confirm( - Button::with_icon(theme::ICON_CANCEL), - Button::with_text(TR::buttons__confirm.into()).styled(theme::button_confirm()), - true, - ); - - let fido_page = FidoConfirm::new(app_name, get_page, page_count, icon, controls); - - let obj = LayoutObj::new(Frame::centered(theme::label_title(), title, fido_page))?; - Ok(obj.into()) - }; - unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } -} - extern "C" fn new_confirm_with_info(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()?; @@ -929,19 +898,6 @@ pub static mp_module_trezorui2: Module = obj_module! { /// """Transaction summary. Always hold to confirm.""" Qstr::MP_QSTR_confirm_total => obj_fn_kw!(0, new_confirm_total).as_obj(), - /// def confirm_fido( - /// *, - /// title: str, - /// app_name: str, - /// icon_name: str | None, - /// accounts: list[str | None], - /// ) -> LayoutObj[int | UiResult]: - /// """FIDO confirmation. - /// - /// Returns page index in case of confirmation and CANCELLED otherwise. - /// """ - Qstr::MP_QSTR_confirm_fido => obj_fn_kw!(0, new_confirm_fido).as_obj(), - /// def confirm_with_info( /// *, /// 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 b1b47d3a6d..32664a49c0 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 @@ -3,7 +3,7 @@ use core::cmp::Ordering; use crate::{ error::{value_error, Error}, io::BinaryData, - micropython::gc::Gc, + micropython::{gc::Gc, list::List}, strutil::TString, translations::TR, ui::{ @@ -27,8 +27,8 @@ use crate::{ use super::{ component::{ check_homescreen_format, Bip39Input, Button, ButtonMsg, ButtonPage, ButtonStyleSheet, - CancelConfirmMsg, CoinJoinProgress, Dialog, Frame, Homescreen, IconDialog, Lockscreen, - MnemonicKeyboard, NumberInputDialog, PassphraseKeyboard, PinKeyboard, Progress, + CancelConfirmMsg, CoinJoinProgress, Dialog, FidoConfirm, Frame, Homescreen, IconDialog, + Lockscreen, MnemonicKeyboard, NumberInputDialog, PassphraseKeyboard, PinKeyboard, Progress, SelectWordCount, SetBrightnessDialog, Slip39Input, }, theme, ModelTTFeatures, @@ -118,6 +118,34 @@ impl UIFeaturesFirmware for ModelTTFeatures { Ok(layout) } + fn confirm_fido( + title: TString<'static>, + app_name: TString<'static>, + icon: Option>, + accounts: Gc, + ) -> Result { + // Cache the page count so that we can move `accounts` into the closure. + let page_count = accounts.len(); + // Closure to lazy-load the information on given page index. + // Done like this to allow arbitrarily many pages without + // the need of any allocation here in Rust. + let get_page = move |page_index| { + let account = unwrap!(accounts.get(page_index)); + account.try_into().unwrap_or_else(|_| "".into()) + }; + + let controls = Button::cancel_confirm( + Button::with_icon(theme::ICON_CANCEL), + Button::with_text(TR::buttons__confirm.into()).styled(theme::button_confirm()), + true, + ); + + let fido_page = FidoConfirm::new(app_name, get_page, page_count, icon, controls); + + let layout = RootComponent::new(Frame::centered(theme::label_title(), title, fido_page)); + Ok(layout) + } + fn confirm_firmware_update( description: TString<'static>, fingerprint: TString<'static>, diff --git a/core/embed/rust/src/ui/ui_features_fw.rs b/core/embed/rust/src/ui/ui_features_fw.rs index cc62d9bd76..ac2b7e307b 100644 --- a/core/embed/rust/src/ui/ui_features_fw.rs +++ b/core/embed/rust/src/ui/ui_features_fw.rs @@ -1,7 +1,7 @@ use crate::{ error::Error, io::BinaryData, - micropython::{gc::Gc, obj::Obj}, + micropython::{gc::Gc, list::List, obj::Obj}, strutil::TString, }; @@ -35,6 +35,13 @@ pub trait UIFeaturesFirmware { max_feerate: TString<'static>, ) -> Result; + fn confirm_fido( + title: TString<'static>, + app_name: TString<'static>, + icon: Option>, + accounts: Gc, // TODO: replace Gc + ) -> Result; + fn confirm_firmware_update( description: TString<'static>, fingerprint: TString<'static>, diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index 8b17f69e0f..3a2b9fa8f3 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -108,19 +108,6 @@ def confirm_total( """Transaction summary. Always hold to confirm.""" -# rust/src/ui/model_mercury/layout.rs -def confirm_fido( - *, - title: str, - app_name: str, - icon_name: str | None, - accounts: list[str | None], -) -> LayoutObj[int | UiResult]: - """FIDO confirmation. - Returns page index in case of confirmation and CANCELLED otherwise. - """ - - # rust/src/ui/model_mercury/layout.rs def confirm_with_info( *, @@ -367,19 +354,6 @@ def altcoin_tx_summary( """Confirm details about altcoin transaction.""" -# rust/src/ui/model_tr/layout.rs -def confirm_fido( - *, - title: str, - app_name: str, - icon_name: str | None, # unused on TR - accounts: list[str | None], -) -> LayoutObj[int | UiResult]: - """FIDO confirmation. - Returns page index in case of confirmation and CANCELLED otherwise. - """ - - # rust/src/ui/model_tr/layout.rs def multiple_pages_texts( *, @@ -534,19 +508,6 @@ def confirm_total( """Transaction summary. Always hold to confirm.""" -# rust/src/ui/model_tt/layout.rs -def confirm_fido( - *, - title: str, - app_name: str, - icon_name: str | None, - accounts: list[str | None], -) -> LayoutObj[int | UiResult]: - """FIDO confirmation. - Returns page index in case of confirmation and CANCELLED otherwise. - """ - - # rust/src/ui/model_tt/layout.rs def confirm_with_info( *, diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi index 98474a578c..e80aec2e8d 100644 --- a/core/mocks/generated/trezorui_api.pyi +++ b/core/mocks/generated/trezorui_api.pyi @@ -105,6 +105,19 @@ def confirm_coinjoin( """Confirm coinjoin authorization.""" +# rust/src/ui/api/firmware_upy.rs +def confirm_fido( + *, + title: str, + app_name: str, + icon_name: str | None, + accounts: list[str | None], +) -> LayoutObj[int | UiResult]: + """FIDO confirmation. + Returns page index in case of confirmation and CANCELLED otherwise. + """ + + # rust/src/ui/api/firmware_upy.rs def confirm_firmware_update( *, diff --git a/core/src/trezor/ui/layouts/mercury/fido.py b/core/src/trezor/ui/layouts/mercury/fido.py index 93ed556ce7..a4b54e0636 100644 --- a/core/src/trezor/ui/layouts/mercury/fido.py +++ b/core/src/trezor/ui/layouts/mercury/fido.py @@ -1,4 +1,3 @@ -import trezorui2 import trezorui_api from trezor import ui from trezor.enums import ButtonRequestType @@ -13,7 +12,7 @@ async def confirm_fido( accounts: list[str | None], ) -> int: """Webauthn confirmation for one or more credentials.""" - confirm = trezorui2.confirm_fido( + confirm = trezorui_api.confirm_fido( title=header, app_name=app_name, icon_name=icon_name, diff --git a/core/src/trezor/ui/layouts/tr/fido.py b/core/src/trezor/ui/layouts/tr/fido.py index b2c85ac540..18bff9df3a 100644 --- a/core/src/trezor/ui/layouts/tr/fido.py +++ b/core/src/trezor/ui/layouts/tr/fido.py @@ -1,4 +1,3 @@ -import trezorui2 import trezorui_api from trezor import ui from trezor.enums import ButtonRequestType @@ -9,13 +8,14 @@ from ..common import interact async def confirm_fido( header: str, app_name: str, - icon_name: str | None, + _icon_name: str | None, # unused on TR accounts: list[str | None], ) -> int: """Webauthn confirmation for one or more credentials.""" - confirm = trezorui2.confirm_fido( # type: ignore [Argument missing for parameter "icon_name"] + confirm = trezorui_api.confirm_fido( # type: ignore [Argument missing for parameter "icon_name"] title=header, app_name=app_name, + icon_name=None, accounts=accounts, ) result = await interact(confirm, "confirm_fido", ButtonRequestType.Other) diff --git a/core/src/trezor/ui/layouts/tt/fido.py b/core/src/trezor/ui/layouts/tt/fido.py index ef435a026c..64940470d7 100644 --- a/core/src/trezor/ui/layouts/tt/fido.py +++ b/core/src/trezor/ui/layouts/tt/fido.py @@ -1,4 +1,3 @@ -import trezorui2 import trezorui_api from trezor import ui from trezor.enums import ButtonRequestType @@ -13,7 +12,7 @@ async def confirm_fido( accounts: list[str | None], ) -> int: """Webauthn confirmation for one or more credentials.""" - confirm = trezorui2.confirm_fido( + confirm = trezorui_api.confirm_fido( title=header, app_name=app_name, icon_name=icon_name,