diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h index 6605cccd6c..132a279df0 100644 --- a/core/embed/rust/librust_qstr.h +++ b/core/embed/rust/librust_qstr.h @@ -323,6 +323,7 @@ static void _librust_qstrs(void) { MP_QSTR_max_feerate; MP_QSTR_max_len; MP_QSTR_max_rounds; + MP_QSTR_menu_title; MP_QSTR_message; MP_QSTR_min_count; MP_QSTR_misc__decrypt_value; diff --git a/core/embed/rust/src/ui/api/firmware_micropython.rs b/core/embed/rust/src/ui/api/firmware_micropython.rs index 0b853c4806..d5b2066d83 100644 --- a/core/embed/rust/src/ui/api/firmware_micropython.rs +++ b/core/embed/rust/src/ui/api/firmware_micropython.rs @@ -736,12 +736,16 @@ extern "C" fn new_show_danger(n_args: usize, args: *const Obj, kwargs: *mut Map) let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?; let description: TString = kwargs.get(Qstr::MP_QSTR_description)?.try_into()?; let value: TString = kwargs.get_or(Qstr::MP_QSTR_value, "".into())?; + let menu_title: Option = kwargs + .get(Qstr::MP_QSTR_menu_title) + .unwrap_or_else(|_| Obj::const_none()) + .try_into_option()?; let verb_cancel: Option = kwargs .get(Qstr::MP_QSTR_verb_cancel) .unwrap_or_else(|_| Obj::const_none()) .try_into_option()?; - let layout = ModelUI::show_danger(title, description, value, verb_cancel)?; + let layout = ModelUI::show_danger(title, description, value, menu_title, verb_cancel)?; Ok(LayoutObj::new_root(layout)?.into()) }; unsafe { util::try_with_args_and_kwargs(n_args, args, kwargs, block) } @@ -1518,6 +1522,7 @@ pub static mp_module_trezorui_api: Module = obj_module! { /// title: str, /// description: str, /// value: str = "", + /// menu_title: str | None = None, /// verb_cancel: str | None = None, /// ) -> LayoutObj[UiResult]: /// """Warning modal that makes it easier to cancel than to continue.""" diff --git a/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs b/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs index 217a0a0a07..b60748a2e8 100644 --- a/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_bolt/ui_firmware.rs @@ -815,6 +815,7 @@ impl FirmwareUI for UIBolt { _title: TString<'static>, _description: TString<'static>, _value: TString<'static>, + _menu_title: Option>, _verb_cancel: Option>, ) -> Result { Err::, Error>(Error::ValueError(c"show_danger not supported")) diff --git a/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs b/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs index 2c08b9beeb..30e3280dbf 100644 --- a/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_caesar/ui_firmware.rs @@ -986,6 +986,7 @@ impl FirmwareUI for UICaesar { _title: TString<'static>, _description: TString<'static>, _value: TString<'static>, + _menu_title: Option>, _verb_cancel: Option>, ) -> Result { Err::, Error>(Error::ValueError(c"show_danger not supported")) diff --git a/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs b/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs index 6b5a1e42b3..cf2a5e8c11 100644 --- a/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_delizia/ui_firmware.rs @@ -829,6 +829,7 @@ impl FirmwareUI for UIDelizia { title: TString<'static>, description: TString<'static>, value: TString<'static>, + _menu_title: Option>, verb_cancel: Option>, ) -> Result { let flow = flow::show_danger::new_show_danger(title, description, value, verb_cancel)?; diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs b/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs index 47c7a2f4b4..78d5d34eb8 100644 --- a/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs +++ b/core/embed/rust/src/ui/layout_eckhart/flow/mod.rs @@ -2,10 +2,12 @@ pub mod confirm_reset; pub mod get_address; pub mod prompt_backup; pub mod request_passphrase; +pub mod show_danger; pub mod show_share_words; pub use confirm_reset::new_confirm_reset; pub use get_address::GetAddress; pub use prompt_backup::PromptBackup; pub use request_passphrase::RequestPassphrase; +pub use show_danger::ShowDanger; pub use show_share_words::new_show_share_words_flow; diff --git a/core/embed/rust/src/ui/layout_eckhart/flow/show_danger.rs b/core/embed/rust/src/ui/layout_eckhart/flow/show_danger.rs new file mode 100644 index 0000000000..513d072994 --- /dev/null +++ b/core/embed/rust/src/ui/layout_eckhart/flow/show_danger.rs @@ -0,0 +1,137 @@ +use crate::{ + error, + strutil::TString, + translations::TR, + ui::{ + component::{ + text::paragraphs::{Paragraph, ParagraphSource}, + ComponentExt, + }, + flow::{ + base::{Decision, DecisionBuilder as _}, + FlowController, FlowMsg, SwipeFlow, + }, + geometry::{Alignment, Direction, LinearPlacement, Offset}, + }, +}; + +use super::super::{ + component::Button, + firmware::{ + ActionBar, Header, HeaderMsg, TextScreen, TextScreenMsg, VerticalMenu, VerticalMenuScreen, + VerticalMenuScreenMsg, + }, + theme, +}; + +const TIMEOUT_MS: u32 = 2000; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum ShowDanger { + Message, + Menu, + Cancelled, +} + +impl FlowController for ShowDanger { + #[inline] + fn index(&'static self) -> usize { + *self as usize + } + + fn handle_swipe(&'static self, direction: Direction) -> Decision { + match (self, direction) { + _ => self.do_nothing(), + } + } + + fn handle_event(&'static self, msg: FlowMsg) -> Decision { + match (self, msg) { + (Self::Message, FlowMsg::Info) => Self::Menu.goto(), + (Self::Message, FlowMsg::Cancelled) => Self::Cancelled.goto(), + (Self::Menu, FlowMsg::Choice(1)) => self.return_msg(FlowMsg::Confirmed), + (Self::Menu, FlowMsg::Choice(_)) => Self::Cancelled.goto(), + (Self::Menu, FlowMsg::Cancelled) => Self::Message.goto(), + (Self::Cancelled, _) => self.return_msg(FlowMsg::Cancelled), + _ => self.do_nothing(), + } + } +} + +pub fn new_show_danger( + title: TString<'static>, + description: TString<'static>, + value: TString<'static>, + menu_title: Option>, + verb_cancel: Option>, +) -> Result { + let verb_cancel = verb_cancel.unwrap_or(TR::words__cancel_and_exit.into()); + + // Message + let paragraphs = [ + Paragraph::new(&theme::TEXT_REGULAR, description), + Paragraph::new(&theme::TEXT_MONO_EXTRA_LIGHT, value), + ] + .into_paragraphs() + .with_placement(LinearPlacement::vertical().with_spacing(35)); + + let content_message = TextScreen::new(paragraphs) + .with_header( + Header::new(title) + .with_menu_button() + .with_icon(theme::ICON_INFO, theme::ORANGE) + .with_text_style(theme::label_title_danger()), + ) + .with_action_bar(ActionBar::new_single(Button::with_text(verb_cancel))) + .map(|msg| match msg { + TextScreenMsg::Menu => Some(FlowMsg::Info), + TextScreenMsg::Confirmed => Some(FlowMsg::Cancelled), + _ => None, + }); + + // Menu + let content_menu = VerticalMenuScreen::new( + VerticalMenu::empty() + .with_separators() + .item( + Button::with_text(verb_cancel) + .styled(theme::menu_item_title()) + .with_text_align(Alignment::Start) + .with_content_offset(Offset::x(12)), + ) + .item( + Button::with_text(TR::words__continue_anyway.into()) + .styled(theme::menu_item_title_orange()) + .with_text_align(Alignment::Start) + .with_content_offset(Offset::x(12)), + ), + ) + .with_header( + Header::new(menu_title.unwrap_or("".into())) + .with_right_button(Button::with_icon(theme::ICON_CROSS), HeaderMsg::Cancelled), + ) + .map(|msg| match msg { + VerticalMenuScreenMsg::Selected(i) => Some(FlowMsg::Choice(i)), + VerticalMenuScreenMsg::Close => Some(FlowMsg::Cancelled), + _ => None, + }); + + // Cancelled + let content_cancelled = TextScreen::new( + Paragraph::new(&theme::TEXT_REGULAR, TR::words__operation_cancelled) + .into_paragraphs() + .with_placement(LinearPlacement::vertical()), + ) + .with_header(Header::new(TR::words__title_done.into()).with_icon(theme::ICON_DONE, theme::GREY)) + .with_action_bar(ActionBar::new_timeout( + Button::with_text(TR::instructions__continue_in_app.into()), + TIMEOUT_MS, + )) + .map(|_| Some(FlowMsg::Confirmed)); + + let mut res = SwipeFlow::new(&ShowDanger::Message)?; + res.add_page(&ShowDanger::Message, content_message)? + .add_page(&ShowDanger::Menu, content_menu)? + .add_page(&ShowDanger::Cancelled, content_cancelled)?; + Ok(res) +} diff --git a/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs b/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs index 523461c024..d18c801031 100644 --- a/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs +++ b/core/embed/rust/src/ui/layout_eckhart/theme/firmware.rs @@ -39,10 +39,10 @@ pub const TEXT_BIG: TextStyle = TextStyle::new( /// TT Satoshi Regular - 38 (Screen text, Menu item label) pub const TEXT_REGULAR: TextStyle = TextStyle::new( fonts::FONT_SATOSHI_REGULAR_38, - GREY_EXTRA_LIGHT, + GREY_LIGHT, BG, - GREY_EXTRA_LIGHT, - GREY_EXTRA_LIGHT, + GREY_LIGHT, + GREY_LIGHT, ); /// TT Satoshi Medium - 26 (Screen text, Button label, Input value) pub const TEXT_MEDIUM: TextStyle = TextStyle::new( @@ -105,6 +105,14 @@ pub fn get_chunkified_text_style(_character_length: usize) -> &'static TextStyle &TEXT_MONO_ADDRESS_CHUNKS } +pub const TEXT_MONO_EXTRA_LIGHT: TextStyle = TextStyle::new( + fonts::FONT_MONO_LIGHT_30, + GREY_EXTRA_LIGHT, + BG, + GREY_EXTRA_LIGHT, + GREY_EXTRA_LIGHT, +); + // Macro for styles differing only in text color macro_rules! label_title { ($color:expr) => { diff --git a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs index 346b80b128..166002383c 100644 --- a/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs +++ b/core/embed/rust/src/ui/layout_eckhart/ui_firmware.rs @@ -498,12 +498,15 @@ impl FirmwareUI for UIEckhart { } fn show_danger( - _title: TString<'static>, - _description: TString<'static>, - _value: TString<'static>, - _verb_cancel: Option>, + title: TString<'static>, + description: TString<'static>, + value: TString<'static>, + menu_title: Option>, + verb_cancel: Option>, ) -> Result { - Err::, Error>(Error::ValueError(c"not implemented")) + let flow = + flow::show_danger::new_show_danger(title, description, value, menu_title, verb_cancel)?; + Ok(flow) } fn show_error( diff --git a/core/embed/rust/src/ui/ui_firmware.rs b/core/embed/rust/src/ui/ui_firmware.rs index 4311c24bc1..0ed6421f33 100644 --- a/core/embed/rust/src/ui/ui_firmware.rs +++ b/core/embed/rust/src/ui/ui_firmware.rs @@ -278,6 +278,7 @@ pub trait FirmwareUI { title: TString<'static>, description: TString<'static>, value: TString<'static>, + menu_title: Option>, verb_cancel: Option>, ) -> Result; diff --git a/core/mocks/generated/trezorui_api.pyi b/core/mocks/generated/trezorui_api.pyi index d06abc20ba..09080112b0 100644 --- a/core/mocks/generated/trezorui_api.pyi +++ b/core/mocks/generated/trezorui_api.pyi @@ -491,6 +491,7 @@ def show_danger( title: str, description: str, value: str = "", + menu_title: str | None = None, verb_cancel: str | None = None, ) -> LayoutObj[UiResult]: """Warning modal that makes it easier to cancel than to continue."""