diff --git a/core/.changelog.d/noissue.fixed b/core/.changelog.d/noissue.fixed new file mode 100644 index 0000000000..26a97edf5b --- /dev/null +++ b/core/.changelog.d/noissue.fixed @@ -0,0 +1 @@ +[T3T1] Improved ETH staking flow. diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index a06861a1e7..6daa251c29 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -327,6 +327,7 @@ static void _librust_qstrs(void) { MP_QSTR_max_feerate; MP_QSTR_max_len; MP_QSTR_max_rounds; + MP_QSTR_message; MP_QSTR_min_count; MP_QSTR_misc__decrypt_value; MP_QSTR_misc__encrypt_value; diff --git a/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs b/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs index 6724f63903..645562428d 100644 --- a/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs +++ b/core/embed/rust/src/ui/model_mercury/flow/confirm_output.rs @@ -31,7 +31,8 @@ use super::{ const MENU_ITEM_CANCEL: usize = 0; const MENU_ITEM_FEE_INFO: usize = 1; -const MENU_ITEM_ACCOUNT_INFO: usize = 2; +const MENU_ITEM_ADDRESS_INFO: usize = 2; +const MENU_ITEM_ACCOUNT_INFO: usize = 3; #[derive(Copy, Clone, PartialEq, Eq)] pub enum ConfirmOutput { @@ -117,9 +118,10 @@ impl FlowState for ConfirmOutputWithAmount { #[derive(Copy, Clone, PartialEq, Eq)] pub enum ConfirmOutputWithSummary { - Address, - AddressMenu, - AddressMenuCancel, + Main, + MainMenu, + MainMenuCancel, + AddressInfo, Summary, SummaryMenu, SummaryMenuCancel, @@ -138,12 +140,13 @@ impl FlowState for ConfirmOutputWithSummary { fn handle_swipe(&'static self, direction: SwipeDirection) -> StateChange { match (self, direction) { - (Self::Address, SwipeDirection::Left) => Self::AddressMenu.swipe(direction), - (Self::Address, SwipeDirection::Up) => Self::Summary.swipe(direction), - (Self::AccountInfo, SwipeDirection::Right) => Self::AddressMenu.swipe(direction), + (Self::Main, SwipeDirection::Left) => Self::MainMenu.swipe(direction), + (Self::Main, SwipeDirection::Up) => Self::Summary.swipe(direction), + (Self::AddressInfo, SwipeDirection::Right) => Self::MainMenu.swipe(direction), + (Self::AccountInfo, SwipeDirection::Right) => Self::MainMenu.swipe(direction), (Self::Summary, SwipeDirection::Left) => Self::SummaryMenu.swipe(direction), (Self::Summary, SwipeDirection::Up) => Self::Hold.swipe(direction), - (Self::Summary, SwipeDirection::Down) => Self::Address.swipe(direction), + (Self::Summary, SwipeDirection::Down) => Self::Main.swipe(direction), (Self::FeeInfo, SwipeDirection::Right) => Self::SummaryMenu.swipe(direction), (Self::Hold, SwipeDirection::Left) => Self::HoldMenu.swipe(direction), (Self::Hold, SwipeDirection::Down) => Self::Summary.swipe(direction), @@ -153,11 +156,12 @@ impl FlowState for ConfirmOutputWithSummary { fn handle_event(&'static self, msg: FlowMsg) -> StateChange { match (self, msg) { - (Self::Address, FlowMsg::Info) => Self::AddressMenu.transit(), - (Self::AddressMenu, FlowMsg::Choice(MENU_ITEM_CANCEL)) => { - Self::AddressMenuCancel.swipe_left() + (Self::Main, FlowMsg::Info) => Self::MainMenu.transit(), + (Self::MainMenu, FlowMsg::Choice(MENU_ITEM_CANCEL)) => { + Self::MainMenuCancel.swipe_left() } - (Self::AddressMenuCancel, FlowMsg::Cancelled) => Self::AddressMenu.swipe_right(), + (Self::MainMenuCancel, FlowMsg::Cancelled) => Self::MainMenu.swipe_right(), + (Self::AddressInfo, FlowMsg::Info) => Self::MainMenu.transit(), (Self::Summary, FlowMsg::Info) => Self::SummaryMenu.transit(), (Self::SummaryMenu, FlowMsg::Choice(MENU_ITEM_CANCEL)) => { Self::SummaryMenuCancel.swipe_left() @@ -169,18 +173,21 @@ impl FlowState for ConfirmOutputWithSummary { } (Self::HoldMenuCancel, FlowMsg::Cancelled) => Self::HoldMenu.swipe_right(), (Self::SummaryMenu, FlowMsg::Choice(MENU_ITEM_FEE_INFO)) => Self::FeeInfo.swipe_left(), - (Self::AddressMenu, FlowMsg::Choice(MENU_ITEM_ACCOUNT_INFO)) => { + (Self::MainMenu, FlowMsg::Choice(MENU_ITEM_ADDRESS_INFO)) => { + Self::AddressInfo.swipe_left() + } + (Self::MainMenu, FlowMsg::Choice(MENU_ITEM_ACCOUNT_INFO)) => { Self::AccountInfo.swipe_left() } - (Self::AddressMenu, FlowMsg::Cancelled) => Self::Address.swipe_right(), + (Self::MainMenu, FlowMsg::Cancelled) => Self::Main.swipe_right(), (Self::SummaryMenu, FlowMsg::Cancelled) => Self::Summary.swipe_right(), (Self::FeeInfo, FlowMsg::Cancelled) => Self::SummaryMenu.swipe_right(), (Self::HoldMenu, FlowMsg::Cancelled) => Self::Hold.swipe_right(), ( - Self::AddressMenuCancel | Self::SummaryMenuCancel | Self::HoldMenuCancel, + Self::MainMenuCancel | Self::SummaryMenuCancel | Self::HoldMenuCancel, FlowMsg::Confirmed, ) => self.return_msg(FlowMsg::Cancelled), - (Self::Address, FlowMsg::Cancelled) => Self::AddressMenu.transit(), + (Self::Main, FlowMsg::Cancelled) => Self::MainMenu.transit(), (Self::Summary, FlowMsg::Cancelled) => Self::SummaryMenu.transit(), (Self::Hold, FlowMsg::Cancelled) => Self::HoldMenu.transit(), (Self::Hold, FlowMsg::Confirmed) => self.return_msg(FlowMsg::Confirmed), @@ -213,6 +220,7 @@ pub extern "C" fn new_confirm_output(n_args: usize, args: *const Obj, kwargs: *m fn new_confirm_output_obj(_args: &[Obj], kwargs: &Map) -> Result { let title: Option = kwargs.get(Qstr::MP_QSTR_title)?.try_into_option()?; + let subtitle: Option = kwargs.get(Qstr::MP_QSTR_subtitle)?.try_into_option()?; let account: Option = kwargs.get(Qstr::MP_QSTR_account)?.try_into_option()?; let account_path: Option = @@ -221,12 +229,16 @@ fn new_confirm_output_obj(_args: &[Obj], kwargs: &Map) -> Result = kwargs.get(Qstr::MP_QSTR_amount)?.try_into_option()?; let chunkify: bool = kwargs.get_or(Qstr::MP_QSTR_chunkify, false)?; let text_mono: bool = kwargs.get_or(Qstr::MP_QSTR_text_mono, true)?; + let address: Option = kwargs.get(Qstr::MP_QSTR_address)?.try_into_option()?; + let address_title: Option = + kwargs.get(Qstr::MP_QSTR_address_title)?.try_into_option()?; + let summary_items: Obj = kwargs.get(Qstr::MP_QSTR_summary_items)?; let fee_items: Obj = kwargs.get(Qstr::MP_QSTR_fee_items)?; @@ -241,9 +253,9 @@ fn new_confirm_output_obj(_args: &[Obj], kwargs: &Map) -> Result = kwargs.get(Qstr::MP_QSTR_cancel_text)?.try_into_option()?; - // Address - let content_address = ConfirmBlobParams::new(TR::words__address.into(), address, None) - .with_subtitle(title) + // Main + let main_content = ConfirmBlobParams::new(title.unwrap_or(TString::empty()), message, None) + .with_subtitle(subtitle) .with_menu_button() .with_footer(TR::instructions__swipe_up.into(), None) .with_chunkify(chunkify) @@ -251,55 +263,59 @@ fn new_confirm_output_obj(_args: &[Obj], kwargs: &Map) -> Result::new(); + // MainMenu + let mut main_menu = VerticalMenu::empty(); + let mut main_menu_items = Vec::::new(); + if address.is_some() { + main_menu = main_menu.item( + theme::ICON_CHEVRON_RIGHT, + address_title.unwrap_or(TR::words__address.into()), + ); + unwrap!(main_menu_items.push(MENU_ITEM_ADDRESS_INFO)); + } if account.is_some() && account_path.is_some() { - address_menu = address_menu.item( + main_menu = main_menu.item( theme::ICON_CHEVRON_RIGHT, TR::address_details__account_info.into(), ); - unwrap!(address_menu_items.push(MENU_ITEM_ACCOUNT_INFO)); + unwrap!(main_menu_items.push(MENU_ITEM_ACCOUNT_INFO)); } - address_menu = address_menu.danger( + main_menu = main_menu.danger( theme::ICON_CANCEL, cancel_text.unwrap_or(TR::send__cancel_sign.into()), ); - unwrap!(address_menu_items.push(MENU_ITEM_CANCEL)); - let content_address_menu = Frame::left_aligned(TString::empty(), address_menu) + unwrap!(main_menu_items.push(MENU_ITEM_CANCEL)); + let content_main_menu = Frame::left_aligned(TString::empty(), main_menu) .with_cancel_button() .with_swipe(SwipeDirection::Right, SwipeSettings::immediate()) .map(move |msg| match msg { FrameMsg::Content(VerticalMenuChoiceMsg::Selected(i)) => { - let selected_item = address_menu_items[i]; + let selected_item = main_menu_items[i]; Some(FlowMsg::Choice(selected_item)) } FrameMsg::Button(_) => Some(FlowMsg::Cancelled), }); // AccountInfo - let ad = AddressDetails::new(TR::send__send_from.into(), account, account_path)?; - let content_account = ad.map(|_| Some(FlowMsg::Cancelled)); + let ac = AddressDetails::new(TR::send__send_from.into(), account, account_path)?; + let account_content = ac.map(|_| Some(FlowMsg::Cancelled)); let res = if amount.is_some() { - let content_amount = ConfirmBlobParams::new( - TR::words__amount.into(), - amount.unwrap_or(Obj::const_none()), - None, - ) - .with_subtitle(title) - .with_menu_button() - .with_footer(TR::instructions__swipe_up.into(), None) - .with_text_mono(text_mono) - .with_swipe_down() - .into_layout()? - .one_button_request(ButtonRequest::from_num(br_code, br_name)); + let content_amount = + ConfirmBlobParams::new(TR::words__amount.into(), amount.unwrap(), None) + .with_subtitle(subtitle) + .with_menu_button() + .with_footer(TR::instructions__swipe_up.into(), None) + .with_text_mono(text_mono) + .with_swipe_down() + .into_layout()? + .one_button_request(ButtonRequest::from_num(br_code, br_name)); SwipeFlow::new(&ConfirmOutputWithAmount::Address)? - .with_page(&ConfirmOutputWithAmount::Address, content_address)? + .with_page(&ConfirmOutputWithAmount::Address, main_content)? .with_page(&ConfirmOutputWithAmount::Amount, content_amount)? - .with_page(&ConfirmOutputWithAmount::Menu, content_address_menu)? - .with_page(&ConfirmOutputWithAmount::AccountInfo, content_account)? + .with_page(&ConfirmOutputWithAmount::Menu, content_main_menu)? + .with_page(&ConfirmOutputWithAmount::AccountInfo, account_content)? .with_page(&ConfirmOutputWithAmount::CancelTap, get_cancel_page())? } else if summary_items != Obj::const_none() { // Summary @@ -389,14 +405,31 @@ fn new_confirm_output_obj(_args: &[Obj], kwargs: &Map) -> Result Some(FlowMsg::Cancelled), }); - SwipeFlow::new(&ConfirmOutputWithSummary::Address)? - .with_page(&ConfirmOutputWithSummary::Address, content_address)? - .with_page(&ConfirmOutputWithSummary::AddressMenu, content_address_menu)? - .with_page( - &ConfirmOutputWithSummary::AddressMenuCancel, - get_cancel_page(), - )? - .with_page(&ConfirmOutputWithSummary::Summary, content_summary)? + let mut flow = SwipeFlow::new(&ConfirmOutputWithSummary::Main)? + .with_page(&ConfirmOutputWithSummary::Main, main_content)? + .with_page(&ConfirmOutputWithSummary::MainMenu, content_main_menu)? + .with_page(&ConfirmOutputWithSummary::MainMenuCancel, get_cancel_page())?; + if address.is_some() { + let address_content = ConfirmBlobParams::new( + address_title.unwrap_or(TR::words__address.into()), + address.unwrap(), + None, + ) + .with_cancel_button() + .with_chunkify(true) + .with_text_mono(true) + .into_layout()?; + flow = flow.with_page(&ConfirmOutputWithSummary::AddressInfo, address_content)?; + } else { + // dummy page - this will never be shown since there is no menu item pointing to + // it, but the page has to exist in the flow + flow = flow.with_page( + &ConfirmOutputWithSummary::AddressInfo, + Frame::left_aligned(TString::empty(), VerticalMenu::empty()) + .map(|_| Some(FlowMsg::Cancelled)), + )?; + } + flow.with_page(&ConfirmOutputWithSummary::Summary, content_summary)? .with_page(&ConfirmOutputWithSummary::SummaryMenu, content_summary_menu)? .with_page( &ConfirmOutputWithSummary::SummaryMenuCancel, @@ -406,12 +439,12 @@ fn new_confirm_output_obj(_args: &[Obj], kwargs: &Map) -> Result>, extra: Option>, menu_button: bool, + cancel_button: bool, chunkify: bool, text_mono: bool, swipe_down: bool, @@ -50,6 +51,7 @@ impl ConfirmBlobParams { description, extra: None, menu_button: false, + cancel_button: false, chunkify: false, text_mono: true, swipe_down: false, @@ -71,6 +73,11 @@ impl ConfirmBlobParams { self } + pub const fn with_cancel_button(mut self) -> Self { + self.cancel_button = true; + self + } + pub const fn with_swipe_down(mut self) -> Self { self.swipe_down = true; self @@ -128,6 +135,9 @@ impl ConfirmBlobParams { if self.menu_button { frame = frame.with_menu_button(); } + if self.cancel_button { + frame = frame.with_cancel_button(); + } if let Some(instruction) = self.footer_instruction { frame = frame.with_footer(instruction, self.footer_description); frame = frame.with_swipe(SwipeDirection::Left, SwipeSettings::default()); diff --git a/core/embed/rust/src/ui/model_mercury/layout.rs b/core/embed/rust/src/ui/model_mercury/layout.rs index 57375acb51..7f02f41dca 100644 --- a/core/embed/rust/src/ui/model_mercury/layout.rs +++ b/core/embed/rust/src/ui/model_mercury/layout.rs @@ -1678,13 +1678,17 @@ pub static mp_module_trezorui2: Module = obj_module! { /// def flow_confirm_output( /// *, /// title: str | None, - /// address: str, + /// subtitle: str | None, + /// message: str, /// amount: str | None, /// chunkify: bool, + /// text_mono: bool, /// account: str | None, /// account_path: str | None, /// br_code: ButtonRequestType, /// br_name: str, + /// address: str | None, + /// address_title: str | None, /// summary_items: Iterable[tuple[str, str]] | None = None, /// fee_items: Iterable[tuple[str, str]] | None = None, /// summary_title: str | None = None, diff --git a/core/mocks/generated/trezorui2.pyi b/core/mocks/generated/trezorui2.pyi index f3b8ea88c1..d83b7d20a3 100644 --- a/core/mocks/generated/trezorui2.pyi +++ b/core/mocks/generated/trezorui2.pyi @@ -573,13 +573,17 @@ def flow_warning_hi_prio( def flow_confirm_output( *, title: str | None, - address: str, + subtitle: str | None, + message: str, amount: str | None, chunkify: bool, + text_mono: bool, account: str | None, account_path: str | None, br_code: ButtonRequestType, br_name: str, + address: str | None, + address_title: str | None, summary_items: Iterable[tuple[str, str]] | None = None, fee_items: Iterable[tuple[str, str]] | None = None, summary_title: str | None = None, diff --git a/core/src/trezor/ui/layouts/mercury/__init__.py b/core/src/trezor/ui/layouts/mercury/__init__.py index 182b70b4a1..3a8385e2f0 100644 --- a/core/src/trezor/ui/layouts/mercury/__init__.py +++ b/core/src/trezor/ui/layouts/mercury/__init__.py @@ -649,12 +649,16 @@ async def confirm_output( await raise_if_not_confirmed( RustLayout( trezorui2.flow_confirm_output( - title=title, - address=address, + title=TR.words__address, + subtitle=title, + message=address, amount=amount, chunkify=chunkify, + text_mono=True, account=source_account, account_path=source_account_path, + address=None, + address_title=None, br_code=br_code, br_name="confirm_output", summary_items=None, @@ -1032,12 +1036,16 @@ if not utils.BITCOIN_ONLY: await raise_if_not_confirmed( RustLayout( trezorui2.flow_confirm_output( - title=TR.words__recipient, - address=recipient, + title=TR.words__address, + subtitle=TR.words__recipient, + message=recipient, amount=None, chunkify=chunkify, + text_mono=True, account=None, account_path=None, + address=None, + address_title=None, br_code=ButtonRequestType.Other, br_name="confirm_output", summary_items=( @@ -1066,36 +1074,36 @@ if not utils.BITCOIN_ONLY: br_name: str = "confirm_ethereum_staking_tx", br_code: ButtonRequestType = ButtonRequestType.SignTx, ) -> None: - - # intro - await confirm_value( - title, - intro_question, - "", - br_name, - br_code, - verb=verb, - value_text_mono=False, - info_items=(("", address),), - info_title=address_title, - chunkify_info=chunkify, - ) - - # confirmation if verb == TR.ethereum__staking_claim: - items = ((TR.send__maximum_fee, maximum_fee),) + summary_items = ((TR.send__maximum_fee, maximum_fee),) else: - items = ( + summary_items = ( (TR.words__amount, total_amount), (TR.send__maximum_fee, maximum_fee), ) - await _confirm_summary( - items=items, - title=title, - info_title=TR.confirm_total__title_fee, - info_items=info_items, - br_name=br_name, - br_code=br_code, + await raise_if_not_confirmed( + RustLayout( + trezorui2.flow_confirm_output( + title=verb, + subtitle=None, + message=intro_question, + amount=None, + chunkify=False, + text_mono=False, + account=None, + account_path=None, + br_code=br_code, + br_name=br_name, + address=address, + address_title=address_title, + summary_items=summary_items, + fee_items=info_items, + summary_title=verb, + summary_br_name="confirm_total", + summary_br_code=ButtonRequestType.SignTx, + cancel_text=TR.buttons__cancel, # cancel staking + ) + ) ) def confirm_solana_tx(